DirectX - Flysimulator

Texture

Tidligere har vi i en tekst om brug af modeller i programmet udviklet en lille flysimulator med en papirflyver, der kunne styres.

I denne tekst genoptager vi udviklingen af flysimulatoren. I første omgang vil vi se på, hvordan man kan lægge et billede ud som underlag for sceneriet. Dette sker ved hjælp af et såkaldt Texture. Dernæst udskiftes papirflyveren med et mesh af et triplan, som det f.eks. kendes fra 1. Verdenskrig.

Udover Game Engine består programmet af en model, som indeholder klasser som Model, Airplane, Ground osv.

  1. Indlæsning af texture fra fil
  2. Triplanet
  3. Texture på flyet
  4. Flyveren skal lære at flyve
  5. Ændring af kameraets position
  6. Collision detection

1. Indlæsning af texture fra fil (Ground klassen)

Flysimulatoren bliver baseret på den tidligere udviklede Game Engine - se f.eks. denne tekst.

Layoutet for jorden i flysimulatoren er defineret klassen Ground. Først defineres nogle variabler, der skal bruges i denne klasse. Bemærk erklæringen af vertices tabellen - til dette formål baseret på klassen CustomVertex.PositionTextured.

	Device theDevice;
	Matrix theWorld;
	private Texture ground = null;
	private Material material = new Material();
	CustomVertex.PositionTextured[] vertices;

I klassens constructor oprettes to trekanter, som tilsammen danner en firkant. Denne firkant skal senere indeholde det billede, som vil blive hentet fra en fil. Punkterne opbygges i denne sammenhæng med strukturen PositionTextured. I forhold til de CustomVertex typer, som vi tidligere har arbejdet med, er denne udstyret med yderligere attributter, Tu og Tv. Disse attributter skal indeholde billedets 2D-koordinater, således at øverste venstre hjørne får koordinaterne (0, 0), øverste højre hjørne får koordinaterne (1, 0), nederste højre hjørne får koordinaterne (1, 1) og nederste venstre hjørne (0, 1). Hvis man vender rundt på disse 2D punkter kan man spejlvende og rotere billedet.

	public Ground(Device aDevice)
	{
		theDevice = aDevice;
		theWorld = Matrix.Identity;
		vertices = new CustomVertex.PositionTextured[6];
		vertices[0].Position = new Vector3(100f, 0f, 100f);
		vertices[0].Tu = 1;
		vertices[0].Tv = 0;
		vertices[1].Position = new Vector3(-100f, 0f, -100f);
		vertices[1].Tu = 0;
		vertices[1].Tv = 1;
		vertices[2].Position = new Vector3(100f, 0f, -100f);
		vertices[2].Tu = 1;
		vertices[2].Tv = 1;
		vertices[3].Position = new Vector3(-100f, 0f, -100f);
		vertices[3].Tu = 0;
		vertices[3].Tv = 1;
		vertices[4].Position = new Vector3(100f, 0f, 100f);
		vertices[4].Tu = 1;
		vertices[4].Tv = 0;
		vertices[5].Position = new Vector3(-100f, 0f, 100f);
		vertices[5].Tu = 0;
		vertices[5].Tv = 0;
		LoadTexture();
	}

Det billede, der vil blive indsat her, er et luftfoto af byen Brisbane. Billedet stammer fra en tutorial om texture, og det ses herunder.

I Craig Andera's tutorial er proceduren omkring brugen af Tu og Tv attributterne beskrevet. Nedenstående figur stammer fra denne tutorial:

I LoadTexture metoden indlæses et billede i Texture objektet ground. I dette tilfælde indlæses data fra filen 2001.jpg, som indeholder det luftfoto af byen Brisbane, som ses ovenfor.

	private void LoadTexture()
	{
		String path = @"..\..\";
		material.Diffuse = Color.White;
		material.Specular = Color.LightGray;
		material.SpecularSharpness = 15.0F;
		theDevice.Material = material;
		ground = TextureLoader.FromFile(theDevice, path + "2001.jpg");
	}

Herunder ses Render metoden:

	public void Render()
	{
		theDevice.RenderState.Lighting = false;
		theDevice.Transform.World = theWorld;
		theDevice.VertexFormat = CustomVertex.PositionTextured.Format;
		theDevice.SetTexture(0, ground);
		theDevice.DrawUserPrimitives(PrimitiveType.TriangleList, 2, vertices);
	}

En foreløbig stump kode tænkt som udgangspunkt for videre eksperimenter kan hentes her.

2. Triplanet

Lad os for en stund vende os mod brugen af mesh. I MeshX programmets bibliotek har jeg fundet et mesh med et triplan, som ligner en flyver fra 1. verdenskrig. Her skal vi se, hvordan dette triplan defineres og tegnes i klassen Airplane.

I klassens constructor defineres flyets position. Metoden CreateMesh kaldes (se senere). Yaw sættes til 90 grader og Pitch til 10 grader - det sidste for at sænke næsen en smule, så den er orienteret mere naturligt, end den er, når flyet står på jorden.

	public Airplane(Device aDevice, Vector3 aPosition)
		: base(aPosition)
	{
		theDevice = aDevice;
		CreateMesh();
		Yaw = (float)(2F * Math.PI / 360F) * 90F;
		Pitch = (float)(2F * Math.PI / 360F) * 10F;
	}

Herefter ser vi på metoden CreateMesh. Det meste ligner noget, vi har set i et tidligere eksempel.

	public void CreateMesh()
	{
		ExtendedMaterial[] materials = null;
		String path = @"..\..\";
		mesh = Mesh.FromFile(path + "triplane.x",
		MeshFlags.SystemMemory, theDevice, out materials);
		if (meshTextures == null)
		{
			// Egenskaber for materialer og texture udtrækkes 
			meshTextures = new Texture[materials.Length];
			meshMaterials = new Material[materials.Length];
			for (int i = 0; i < materials.Length; i++)
			{
				meshMaterials[i] = materials[i].Material3D;
				meshMaterials[i].Ambient = meshMaterials[i].Diffuse;
			}
		}
	}

Og til sidst Render metoden, hvor flyveren får en lyseblå farve:

	public void Render()
	{
		Color color = Color.LightSteelBlue;
		Material meshMaterial = new Material();
		Texture meshTexture = null;
		meshMaterial.AmbientColor = new ColorValue(color.R, color.G, color.B);
		meshMaterial.DiffuseColor = new ColorValue(color.R, color.G, color.B);
		theDevice.RenderState.Lighting = true;
		theDevice.Transform.World = Matrix.RotationYawPitchRoll(Yaw, Pitch, Roll) *
			Matrix.Translation(Position);
		theDevice.Material = meshMaterial;
		theDevice.SetTexture(0, meshTexture);
		mesh.DrawSubset(0);
	}

Herunder ses et billede af en afprøvning, hvor luftfotoet af jorden er fjernet, for at gøre flyet mere tydeligt. Kameraet er flyttet, så flyet ses skråt oppefra.

En stump kode, som man kan eksperimentere videre med, kan hentes her.

3. Texture på flyet

Hvad nu, hvis vi har lyst til at bemale flyet med et mønster, som vi f.eks. har indlæst i et Texture objekt. I det følgende vil jeg vise, hvordan vi kan ændre i Airplane klassens CreateMesh og Render metoder for at beklæde flyets ydre med en texture. Jeg vil hertil anvende et mønster, der ligner en moden banan:

CreateMesh metoden ændres, så det ønskede mønster indlæses i meshet:

	public void CreateMesh()
	{
		ExtendedMaterial[] materials = null;
		String path = @"..\..\";
		mesh = Mesh.FromFile(path + "triplane.x",
		MeshFlags.SystemMemory, theDevice, out materials);
		if (meshTextures == null)
		{
			// Egenskaber for materialer og texture udtrækkes 
			meshTextures = new Texture[materials.Length];
			meshMaterials = new Material[materials.Length];
			for (int i = 0; i < materials.Length; i++)
			{
				meshMaterials[i] = materials[i].Material3D;
				meshMaterials[i].Ambient = meshMaterials[i].Diffuse;
				meshTextures[i] = TextureLoader.FromFile(theDevice, path + "banana.bmp");
			}
		}
	}

Render metoden ændres, således at texturens mønster anvendes i stedet for den lyseblå farve:

	public void Render()
	{
		theDevice.RenderState.Lighting = true;
		theDevice.Transform.World = Matrix.RotationYawPitchRoll(Yaw, Pitch, Roll) *
		Matrix.Translation(Position);
		for (int i = 0; i < meshMaterials.Length; i++)
		{
			theDevice.Material = meshMaterials[i];
			theDevice.SetTexture(0, meshTextures[i]);
			mesh.DrawSubset(i);
		}
	}

Resultatet ses herunder - flyet ligner nu (med lidt god vilje) en banan.

En stump kode, som man kan eksperimentere videre med, kan hentes her.

4. Flyveren skal lære at flyve

Denne opgave består hovedsagelig i at tilføje de samme egenskaber til triplanet, som vi i et tidligere eksempel havde tilføjet til en papirflyver. Et par nye attributter tilføjes til Airplane klassen, og der laves properties til disse attributter:

	private float speed;
	private float yawIncrement;

Update metoden får en større overhaling. Der indlægges kode til behandling af de tastetryk, som brugeren skal styre flyet med. Højre pil for at krænge og styre flyet mod højre, venstre pil for at krænge og styre flyet mod venstre. Pil op for at sænke næsen og pil ned for at hæve næsen.

Herudover tilføjes tasterne A og Z til at styre flyets fart med - A øger farten og Z sænker farten.

	public void Update()
	{
		switch (theModel.Keyboard)
		{
			case Keys.Right:
			{ // Flyet drejer og krænger til højre
				YawIncrement += 0.001f;
				Roll -= 0.01f;
				break;
			}
			case Keys.Left:
			{ // Flyet drejer og krænger til venstre
				YawIncrement -= 0.001f;
				Roll += 0.01f;
				break;
			}
			case Keys.Down:
			{ // Styrepind tilbage - flyet hæver næsen
				Pitch -= 0.01f;
				break;
			}
			case Keys.Up:
			{ // Styrepind tilbage - flyet hæver næsen
				Pitch += 0.01f;
				break;
			}
			case Keys.A:
			{ // Mere fart
				Speed += 0.01F;
				break;
			}
			case Keys.Z:
			{ // Mindre fart
				Speed -= 0.01F;
				break;
			}
		}
		theModel.Keyboard = Keys.Space;
		// Beregning af næste position
		Vector3 v = new Vector3(0, 0, 0);
		v.TransformCoordinate(Matrix.Translation(new Vector3(0, 0, Speed))
			* Matrix.RotationYawPitchRoll(Yaw, Pitch - (float)(2F * Math.PI / 360F) * 10F, Roll));
		Position += v;
		if (Position.Y < 2f)
		{
			Position = new Vector3(Position.X, 2f, Position.Z);
		}
		Yaw += YawIncrement;
	}

Flyets nye position findes ved at kigge frem i den retning flyets næse peger. Denne retning er bestemt ved de tre attributter yaw, pitch og roll. Disse attributter indsættes i en beregning, hvor resultatet er en vektor (v), som indeholder forskellen på den nuværende og den næste position. Tilbage er så kun at addere denne vektor til flyets aktuelle position, og vi ved, hvor flyet vil være henne næste gang, scenen skal renderes. Bemærk at der i beregningen kompenseres for, at vi generelt har sænket flyets næse med 10 grader, for at få det til at se mere realistisk ud.

5. Ændring af kameraets position

I flysimulatorer og andre spil, hvor man styrer fartøjer i bevægelse, er det almindeligt, at man kan ændre det punkt, hvorfra man iagttager scenen. En lignende facilitet vil vi nu indbygge i dette program. Ved hjælp af tasten K skal det være muligt at skifte mellem forskellige kamerapositioner. Selve styringen af valget af kameraposition sker med følgende udvidelse af switch sætningen i Update metoden.

			case Keys.K: 
			{ // Toggle kameraposition 
				if (kameraPosition == 1) 
					kameraPosition = 2; 
				else if (kameraPosition == 2) 
					kameraPosition = 3; 
				else kameraPosition = 1; 
				break; 
			}

I position 1 er kameraet placeret i et fast punkt (Tower view), som er anbragt i koordinaten (0, 20, -100). I position 2 er der kameraet placeret i en afstand af 20 enheder bag flyet (Spot plane view) og i position 3 er kameraet anbragt inde i flyet (Cockpit view). Koden herunder tilføjes til slutningen af Update metoden:

		Vector3 nyPosition;
		Vector3 nyOp;
switch (kameraPosition) { case 1: { theDevice.Transform.View = Matrix.LookAtLH(new Vector3(0, 20, -100), Position, new Vector3(0, 1, 0)); break; } case 2: { v = new Vector3(0, 0, 0); v.TransformCoordinate(Matrix.Translation(new Vector3(0, 0, Distance)) * Matrix.RotationYawPitchRoll(Yaw, Pitch, 0f)); nyPosition = Position + v; if (nyPosition.Y < 1) nyPosition.Y = 1; theDevice.Transform.View = Matrix.LookAtLH(nyPosition, Position, new Vector3(0, 1, 0)); break; } case 3: { v = new Vector3(0, 0, 0);
v.TransformCoordinate(Matrix.Translation(new Vector3(0, 0, 20f))
* Matrix.RotationYawPitchRoll(Yaw, Pitch, Roll));
nyPosition = Position + v;
nyOp = new Vector3(0, 1, 0);
nyOp.TransformCoordinate(Matrix.RotationYawPitchRoll(Yaw, Pitch, Roll));
theDevice.Transform.View =
Matrix.LookAtLH(Position + new Vector3(0f, 3f, 0f),
nyPosition, nyOp);
break; } }

I case 1 opsættes det hidtidige faste Tower View - med den lille forskel, at kameraet nu hele tiden drejes, således at det hele tiden peger på flyet.

I case 2 skal vi først have beregnet et punkt, der ligger 20 enheder bag flyet.

I case 3 beregnes først et punkt, der ligger 20 enheder foran flyet i næsens retning. Dette er det punkt kameraet skal pege på. Dernæst beregnes nyOp - en vektor, der peger i samme retning som flyets Y-koordinat, som hælder til en af siderne, når flyet drejer. Dette er nødvendigt for at opleve flyets krængning, som den vil tage sig ud set fra cockpittet.

Hvis man ønsker at lege eller eksperimentere videre, er koden her.

6. Ændring af kameraets position

Kode til at eksperimentere med her.


Sidst opdateret: 14. marts 2007

Index