DirectX - Game Engines

Typisk indhold i en Game Engine

En Game Engine er som ordet siger en motor, der driver et spil.

I denne tekst vil jeg prøve at opbygge en simpel Game Engine i C# under Visual Studio. Samtidig vil jeg prøve at beskrive de elementer der kan indgå i en Game Engine.

Foruden en game engine omfatter et spil også en model, som indeholder en repræsentation af de forskellige elementer, der indgår i spillet som f.eks. spillebordet (Board), brikkerne, spillerne osv.

Herudover er der som regel brug for en brugergrænseflade, hvor anvisninger og statusmeddelelser kan gives til spilleren.

Spillet virker i praksis på den måde, at Game engine sørger for flowet: Henter input fra tastatur og mus, opdaterer skærmbilledet, tildeler point og opdater modellen. Ofte indgår der også et element af kunstig intelligens (AI) i spillet, hvis computeren skal agere modspiller. Heri indgår forskellige strategier og overvejelser om, hvordan man kan vinde et spil efter de gældende regler.

  1. Simpel Game Engine
  2. Samspillet mellem Game Engine og Model
  3. Opbygning af modellen
  4. ModelComponent og Brick klasserne
  5. Model klassen
  6. Brikker anbragt i to-dimensionelt array

Kommende udfordringer og udvidelser

1. Simpel Game Engine

Dette er en simpel Game Engine baseret på de principper, der er opbygget igennem de foregående eksempler. Se f.eks. DirectX Eksempel 1, hvor der redegøres for opbygningen af en meget simpel Game Engine.

Denne engine består af en Main funktion, hvori de forskellige elementer initialiseres, og hvor et loop sættes i gang, som skal fortsætte i resten af programmets køretid:

	static void Main()
   	{
   		using (Engine engine = new Engine())
   		{
   			engine.InitializeGraphics();
   			engine.Show();
   			// While the form is still valid, render and process messages
   			while (engine.Created)
   			{
   				engine.UpdateModel();
   				engine.Render();
   				Application.DoEvents();
   			}
   		}
   	}

Klassens øvrige metoder går igen fra de foregående eksempler:

Metode Vigtigste indhold
Constructor Opsætning af skærmbilledets dimensioner og caption
InitializeGraphics Opsætning af device
Opsætning af view matrix - kameraets placering og synsretning
Kald af RestoreDevice
Initiering af de involverede modellers objekter
RestoreDevice Opsætning af parametre til håndtering af dybdebuffer og lys
Opsætning af lyskilder
Opsætning af projektionsmatrix
Render Blankstilling af Backbuffer med ønskede baggrundsfarve
Tegning af modellens grafik og tekst
Kald af render metoder for hvert af modellens objekter
UpdateModel Opdatering af scenen
Keydown Indlæsning af tastetryk fra tastaturet
Mousedown Indlæsning af knaptryk fra tastaturet

Herunder ses koden for en standard engine - der er indsat nogle opsætninger for de enkelte parametre; men hvis man genbruger denne engine skal disse parametre naturligvis ændres, så de passer til den aktuelle applikation:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace DirectXEksempel05
{
   	public partial class Engine : Form
   	{
   		// Global variables for this project
   		private Device device = null; // Rendering device
   		private Matrix view; // Positions the camera in the scene
   		private Matrix projection; // Controls scene perspective
		
		public Engine()
   		{
   			InitializeComponent();
   			// Set the initial size of the form
   			this.ClientSize = new System.Drawing.Size(600, 600);
   			// And its caption
   			this.Text = "DirectX Eksempel 5A - Game Engine";
   		}

		private void UpdateModel()
   		{
   			// Her tilføjes opdateringer af modellen
   		}

		private void Render()
   		{
   			//Clear the backbuffer to a blue color 
   			device.Clear(ClearFlags.ZBuffer | ClearFlags.Target, 
   			System.Drawing.Color.DarkBlue, 1.0f, 0);
   			device.BeginScene(); //Begin the scene
			
			// Her indsættes flere metodekald
			device.EndScene(); //End the scene
   			device.Present();
   		}
   
   		private void RestoreDevice()
   		{
   			device.RenderState.ZBufferEnable = true; // Enable z-buffering
   			device.RenderState.ZBufferWriteEnable = true;
   			device.RenderState.Lighting = true; // Enable lighting
   			device.RenderState.Ambient = Color.Gray; // Set the ambient color
			// Configure a directional light
   			device.Lights[0].Type = LightType.Directional;
   			device.Lights[0].Direction = new Vector3(10, 3, 0);
   			device.Lights[0].Diffuse = Color.White; 
   			device.Lights[0].Enabled = true;
			// projection transform
   			float aspectRatio = this.Width / this.Height;
   			projection = Matrix.PerspectiveFovLH((float)(Math.PI / 4.0f),
   				1.0f, 1.0f, 100.0f);
   		}

		public void InitializeGraphics()
   		{
   			// Device til DirectX Rendering
   			PresentParameters presentParams = new PresentParameters();
   			presentParams.Windowed = true;
   			presentParams.SwapEffect = SwapEffect.Discard;
   			presentParams.AutoDepthStencilFormat = DepthFormat.D16;
   			presentParams.EnableAutoDepthStencil = true;
   			device = new Device(0, DeviceType.Hardware, this,
   				CreateFlags.SoftwareVertexProcessing, presentParams);
   			// Initial values for the view and world transforms
   			view = Matrix.LookAtLH(new Vector3(0, 0, -10), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
   			device.RenderState.Ambient = Color.White;
   			device.RenderState.CullMode = Cull.None;
			this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Keydown);
			this.MouseDown += new MouseEventHandler(this.Mousedown);
   			RestoreDevice();
   		}

		private void Keydown(Object sender, System.Windows.Forms.KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Escape)
			{
				Application.Exit();
			}
		} 

		private void Mousedown(object sender, System.Windows.Forms.MouseEventArgs e)
   		{
   			// Update the mouse path with the mouse information
   			Point mouseDownLocation = new Point(e.X, e.Y); 
			string eventString = null;
			switch (e.Button)
			{
				case MouseButtons.Left:
					eventString = "L";
					break;
				case MouseButtons.Right:
					eventString = "R";
					break;
				case MouseButtons.Middle:
					eventString = "M";
					break;
				case MouseButtons.XButton1:
					eventString = "X1";
					break;
				case MouseButtons.XButton2:
					eventString = "X2";
					break;
				case MouseButtons.None:
				default:
					break;
			}
			MessageBox.Show(eventString + " " + e.X + " " + e.Y);
		}
 
		static void Main()
		{
			using (Engine engine = new Engine())
			{
   				engine.InitializeGraphics();
   				engine.Show();
   				// While the form is still valid, render and process messages
   				while (engine.Created)
   				{
   					engine.Render();
   					engine.UpdateModel();
   					Application.DoEvents();
   				}
   			}
   		}
   	}
}

Koden kan hentes her.

2. Samspillet mellem Game Engine og Model

I denne udvidelse af programmet er formålet at vise, hvad den udviklede Game Engine kan bruges til. Der skal i den forbindelse opbygges en model, som skal spille sammen med vores Game Engine. I den første kodestump ser vi, hvordan modellen erklæres og associeres til vores Game Engine. Der er tilføjet nogle metoder og properties til Engine klassen, fordi oplysninger fra Engine klassen skal bruges af modellens metoder - mere herom senere.

	// Global variables for this project
	private Device device = null; // Rendering device
	private Matrix view; // Positions the camera in the scene
	private Matrix projection; // Controls scene perspective
	private Model model;

	public Matrix View
	{
		set { view = value; }
		get { return view; }
	}

	public Matrix Projection
	{
		set { projection = value; }
		get { return projection; }
	}
   
	public Engine()
	{
		// Set the initial size of the form
		this.ClientSize = new System.Drawing.Size(600, 600);
		// And its caption
		this.Text = "DirectX Eksempel 5B - Game Engine og Model";
	}
	public Device GetDevice()
	{
		return device;
	}

Metoden UpdateModel (i den tidligere version af vores Game Engine kaldt UpdateScene) skal bruges, når modellen skal opdateres - metoden kaldes fra event handleren MouseDown. Som det ses, sker der ikke andet, end at vi kalder en tilsvarende metode i model objektet. I realiteten kunne vi godt kalde modellens UpdateModel metode direkte fra MouseDown; men jeg har medtaget dette stoppested for at vise princippet.

	private void UpdateModel()
	{
		// Her tilføjes opdateringer af modellen
		// Kaldes fra MouseDown
		model.UpdateModel();
	}

I MouseDown metoden gemmes den aktuelle position for musen i modellens MouseLocation property. Denne position skal bruges til beregning af, hvilket af modellens objekter, der bliver peget på. I andre situationer kunne det være input fra tastaturet, som skulle bruges til manipulation med modellens objekter. Linien this.UpdateModel() kunne lige så godt have været model.UpdateModel(). Udover MouseLocation gemmes også MouseButton i modellen.

	private void Mousedown(object sender, System.Windows.Forms.MouseEventArgs e)
	{
		model.MouseLocation = new Point(e.X, e.Y);
		switch (e.Button)
		{
			case MouseButtons.Left:
				model.MouseButton = 'L';
				break;
			case MouseButtons.Right:
				model.MouseButton = 'R';
				break;
			case MouseButtons.Middle:
				model.MouseButton = 'M';
				break;
   			case MouseButtons.None:
   			default:
   				model.MouseButton = ' ';
				break;
		}
		this.UpdateModel();
	}

Den følgende kodestump er fra metoden InitializeGraphics, hvor View matricen bliver defineret. Kameraet stilles i positionen (6, 6, -25) og der fokuseres på punktet (6, 6, 0). Årsagen hertil er, at modellens objekter bliver placeret omkring dette fokuseringspunkt. I slutningen ses oprettelsen af model objektet, som får engine objektet med som parameter - de to objekter, model og engine, er herefter associerede og kan kalde metoder og properties hos hinanden.

		this.View = Matrix.LookAtLH(new Vector3(6, 6, -25), 
			new Vector3(6, 6, 0), new Vector3(0, 1, 0));
		device.RenderState.Ambient = Color.White;
		device.RenderState.CullMode = Cull.None;
		this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Keydown);
		this.MouseDown += new MouseEventHandler(this.Mousedown);
		model = new Model(this);
		RestoreDevice();

Render metoden indeholder nogle indledende klargøringer til tegning af objekterne: opsætning af view- og projektionsmatricerne. Derefter kaldes modellens Render metode.

		device.BeginScene(); //Begin the scene
		device.Transform.View = this.View;
		device.Transform.Projection = this.Projection;
		model.Render();
		device.EndScene(); //End the scene

3. Opbygning af modellen

Modellen skal indeholde nogle objekter kaldet Bricks - disse objekter placeres forskellige steder i rummet, hvorefter vi vil prøve at udpege de enkelte objekter med musen. Opbygningen af modellen følger nedenstående klassediagram:

Model objektet indeholder en samling af Brick objekter (i dette første eksempel 3 objekter). Modellen kan aktiveres fra Engine objektet på 2 måder:

  1. Via kald af Render metoden fra Engine objektets Render metode. Model objektet kalder Render metoden for de enkelte Brick objekter.
  2. Via kald af UpdateModel metoden fra Engine objektets UpdateModel metode. I denne metode analyseres musens position i vinduet med henblik på at afgøre, om musen peger på et af de 3 Brick objekter.

Brick klassen arver fra ModelComponent klassen - denne struktur er etableret, fordi jeg regner med, at senere modeller kan drage nytte af en lignende struktur. ModelComponent klassen indeholder attributter og properties, som vil være fælles for mange klasser, der indgår i en model af denne type.

4. ModelComponent og Brick klasserne

Herunder følger koden til ModelComponent klassen:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
namespace DirectXEksempel05
{
	class ModelComponent
	{
		private Vector3 position;
		private Matrix world;
		private float pitch;
		private float yaw;
		private float roll;
   
		protected ModelComponent()
		{
			position = new Vector3(0, 0, 0);
			world = Matrix.Identity;
			pitch = 0.0F;
			yaw = 0.0F;
			roll = 0.0F;
		}

		protected ModelComponent(Vector3 aPosition)
		{
			position = aPosition;
			world = Matrix.Identity;
			pitch = 0.0F;
			yaw = 0.0F;
			roll = 0.0F;
		}
		// Properties
		public Vector3 Position
		{
			get { return position; }
			set { position = value; }
		}
		public Matrix World
		{
			get { return world; }
			set { world = value; }
		}
		public float Pitch
		{
			get { return pitch; }
			set { pitch = value; }
		}
		public float Yaw
		{
			get { return yaw; }
			set { yaw = value; }
		}
		public float Roll
		{
			get { return roll; }
			set { roll = value; }
		}
	}
}

Herunder følger koden til Brick klassen:

namespace DirectXEksempel05
{
	class Brick : ModelComponent
	{
		private Mesh mesh;
		private Color color;


		public Brick(Device device, Vector3 aPosition, Color aColor)
			: base(aPosition)
		{
			mesh = Mesh.Box(device, 1f, 1f, 1f);
			color = aColor;
		}

		public void Render(Device device)
		{
			Material meshMaterial = new Material();
			meshMaterial.AmbientColor = new ColorValue(color.R, color.G, color.B);
			meshMaterial.DiffuseColor = new ColorValue(color.R, color.G, color.B);
			device.Transform.World = Matrix.RotationYawPitchRoll(Yaw, Pitch, Roll) *
				Matrix.Translation(Position);
			device.Material = meshMaterial;
			device.SetTexture(0, null);
			mesh.DrawSubset(0);
		}
	}
}

Brick klassen indeholder ikke meget andet end Render metoden - i andre situationer, hvor objekterne f.eks. skal animeres rundt i rummet, kan en UpdateModel metode også komme på tale. Render metoden er opbygget efter samme principper, som vi har set i tidligere eksempler - jeg har medtaget YawPitchRoll rotationen ved opbygningen af World matricen, selvom den ikke bidrager med noget i denne sammenhæng, hvor objekterne ikke skal flytte sig, når de først er placeret.

5. Model klassen

Model klassen er den klasse, der i dette tilfælde er forudset til at skulle indeholde styringen af spillet - i første omgang er der kun indbygget kode, som kan afgøre, om musen peger på et af de tre Brick objekter - senere skal det udvides med logik, der kan afgøre, om der er lovlige træk, og om spilleren eventuelt har vundet. Herunder følger koden til Model klassen:

namespace DirectXEksempel05
{
	class Model
	{
		private Engine engine; // Associeret Engine
		private Brick b1;
		private Brick b2;
		private Brick b3;
		private Point mouseLocation = new Point(0, 0);
		private char mouseButton;


		public Point MouseLocation
		{
			set { mouseLocation = value; }
			get { return mouseLocation; }
		}


		public char MouseButton
		{
			set { mouseButton = value; }
			get { return mouseButton; }
		}


		public Model(Engine anEngine)
		{
			engine = anEngine;
			MouseButton = ' ';
			b1 = new Brick(engine.GetDevice(), new Vector3(0, 0, 0), Color.Aquamarine);
			b2 = new Brick(engine.GetDevice(), new Vector3(5, 3, 0), Color.Yellow);
			b3 = new Brick(engine.GetDevice(), new Vector3(9, 8, 0), Color.Red);
		}


		public void Render()
		{
			b1.Render(engine.GetDevice());
			b2.Render(engine.GetDevice());
			b3.Render(engine.GetDevice());
		}


		public void UpdateModel()
		{
			// Omregn musens placering til world koordinater
			Vector3 nearVector = new Vector3(MouseLocation.X, MouseLocation.Y, 0);
			Vector3 farVector = new Vector3(MouseLocation.X, MouseLocation.Y, 1);
			Vector3 lower;
			Vector3 upper;
			// Create ray.
			nearVector.Unproject(engine.GetDevice().Viewport,
				engine.Projection, engine.View,
				Matrix.Translation(b1.Position) * Matrix.Scaling(0.5f, 0.5f, 0.5f));
			farVector.Unproject(engine.GetDevice().Viewport,
				engine.Projection, engine.View,
				Matrix.Translation(b1.Position) * Matrix.Scaling(0.5f, 0.5f, 0.5f));
			farVector.Subtract(nearVector);
			// Perform intersection test for the bounding box and ray.
			lower = b1.Position - new Vector3(0.5f, 0.5f, 0.5f);
			upper = b1.Position + new Vector3(0.5f, 0.5f, 0.5f);
			if (Geometry.BoxBoundProbe(lower, upper, nearVector, farVector))
			{
				// Er boksen fundet?
				MessageBox.Show("Brick 1");
			}
			
			// Omregn musens placering til world koordinater
			nearVector = new Vector3(MouseLocation.X, MouseLocation.Y, 0);
			farVector = new Vector3(MouseLocation.X, MouseLocation.Y, 1);
			// Create ray.
			nearVector.Unproject(engine.GetDevice().Viewport,
				engine.GetDevice().Transform.Projection,
				engine.GetDevice().Transform.View,
				Matrix.Translation(b2.Position) * Matrix.Scaling(0.5f, 0.5f, 0.5f));
			farVector.Unproject(engine.GetDevice().Viewport,
				engine.GetDevice().Transform.Projection,
				engine.GetDevice().Transform.View,
				Matrix.Translation(b2.Position) * Matrix.Scaling(0.5f, 0.5f, 0.5f));
			farVector.Subtract(nearVector);
			// Perform intersection test for the bounding box and ray.
			lower = upper = b2.Position;
			lower.Subtract(new Vector3(0.5f, 0.5f, 0.5f));
			upper.Add(new Vector3(0.5f, 0.5f, 0.5f));
			if (Geometry.BoxBoundProbe(lower, upper, nearVector, farVector))
			{
				// Er boksen fundet?
				MessageBox.Show("Brick 2");
			}

			// Omregn musens placering til world koordinater
			nearVector = new Vector3(MouseLocation.X, MouseLocation.Y, 0);
			farVector = new Vector3(MouseLocation.X, MouseLocation.Y, 1);
			// Create ray.
			nearVector.Unproject(engine.GetDevice().Viewport,
				engine.GetDevice().Transform.Projection,
				engine.GetDevice().Transform.View,
				Matrix.Translation(b3.Position) * Matrix.Scaling(0.5f, 0.5f, 0.5f));
			farVector.Unproject(engine.GetDevice().Viewport,
				engine.GetDevice().Transform.Projection,
				engine.GetDevice().Transform.View,
				Matrix.Translation(b3.Position) * Matrix.Scaling(0.5f, 0.5f, 0.5f));
			farVector.Subtract(nearVector);
			// Perform intersection test for the bounding box and ray.
			lower = b3.Position - new Vector3(0.5f, 0.5f, 0.5f);
			upper = b3.Position + new Vector3(0.5f, 0.5f, 0.5f);
			if (Geometry.BoxBoundProbe(lower, upper, nearVector, farVector))
			{
				// Er boksen fundet?
				MessageBox.Show("Brick 3");
			}
		}
	}
}

Den mest interessante kode findes i metoden UpdateModel, som bliver kaldt, når brugeren har trykket på en af musens knapper - hvilken knap undersøges ikke her; men muligheden foreligger, da en kode for den aktiverede knap er tilgængelig i MouseButton propertien.

Her følger en gennemgang af en af de tre næsten ens sektioner i UpdateModel - hver sektion håndterer én bestemt Brick:

  1. Først beregnes nearVector og farVector. Disse punkter skal bruges ved beregningen af en ret linie (den såkaldte ray), som går fra kameraets linse gennem punktet på nearPlane, spidsen af musen og punktet på farPlane. Disse planer opsættes i forbindelse med erklæringen af projektions matricen i metoden RestoreDevice i Engine klassen. Denne del af processen handler om at genskabe Z-koordinaten i world-koordinaterne.
  2. Dernæst udføres metoden Unproject på begge vektorer. Denne metode omregner koordinaterne fra skærm-koordinater til world-koordinater. I processen indgår en skalering med faktoren 0.5 - denne skalering er nødvendig for at kunne beregne musens position korrekt. Hvis man undlader den, er objektet tilsyneladende placeret dobbelt så langt væk i X- og Y-aksens retninger. Jeg har endnu ikke fundet ud af, hvorfor denne skalering er nødvendig.
  3. De to omregnede vektorer i world-koordinater trækkes fra hinanden - nearVector trækkes fra farVector.
  4. Nederste venstre hjørne (lower) og øverste højre hjørne (upper) for objektet beregnes. I dette tilfælde finder jeg punkterne ved at fratrække 0.5 i alle dimensioner (lower) eller addere 0.5 i alle dimensiner (upper). Der er tale om en Box med defineret længde, bredde og højde på 1 enhed. Den er anbragt symmetrisk omkring den angivne position.
  5. Endelig kaldes metoden BoxBoundProbe, som afgør, om musen peger på objektet.

Hermed er en vigtig forudsætning opfyldt for at kunne lave forskellige spil, hvor man flytter brikker rundt på et bræt: at man kan udpege en brik eller et felt på brættet.

Snapshot fra en afprøvning af programmet - der er lige blevet trykket på den gule knap:

Koden til dette eksempel kan hentes her.

6. Brikker anbragt i todimensionelt array

I denne udvidelse vil jeg ændre modellen, således at der defineres et antal brikker med tilfældige farver anbragt i et to-dimensionelt array. Der skal laves nogle få ændringer i Model klassens kode. Først skal der erklæres flere variabler:

	private Engine engine; // Associeret Engine
	private Brick[,] bricks;
	private Point mouseLocation = new Point(0, 0);
	private char mouseButton;
	private int x; // Tabellens dimensioner
	private int y;

Variablerne x og y skal indeholde tabellens dimensioner - værdierne tildeles i Model klassens constructor, som ses herunder. Det vigtigste er oprettelsen af det to-dimensionelle array af Brick objekter. Hvert objekt tildeles en af 6 farver, som vælges tilfældigt.

	public Model(int antalX, int antalY, Engine anEngine)
	{
		engine = anEngine;
		MouseButton = ' ';
		x = antalX;
		y = antalY;
		Color color = new Color();
		// Tabel med Bricks i tilfældige farver
		bricks = new Brick[x, y];
		Random rnd = new Random();
		for (int i = 0; i < x; i++)
			for (int j = 0; j < y; j++)
			{
				int c = rnd.Next(6);
				switch (c)
				{
					case 0:
						color = Color.Red;
						break;
					case 1:
						color = Color.Blue;
						break;
					case 2:
						color = Color.Yellow;
						break;
					case 3:
						color = Color.Green;
						break;
					case 4:
						color = Color.Cyan;
						break;
					case 5:
						color = Color.DarkOrange;
						break;
				}
				bricks[i, j] = new Brick(engine.GetDevice(),
					new Vector3(i + 1, j + 1, 0), color);
			}
	}

Render metoden er ændret, så den gennemløber alle Brick objekter i det to-dimesionelle array:

	public void Render()
	{
		for (int i = 0; i < x; i++)
			for (int j = 0; j < y; j++)
			{
				bricks[i, j].Render(engine.GetDevice());
			}
	}

UpdateModel metoden er tilsvarende ændret, således at alle Brick objekter i det to-dimensionelle array gennemløbes for at finde ud af, om musen peger på en af dem:

	public void UpdateModel()
	{
		Vector3 nearVector;
		Vector3 farVector;
		Vector3 lower;
		Vector3 upper;
		for (int i = 0; i < x; i++)
			for (int j = 0; j < y; j++)
			{
				nearVector = new Vector3(MouseLocation.X, MouseLocation.Y, 0);
				farVector = new Vector3(MouseLocation.X, MouseLocation.Y, 1);
				// Create ray.
				nearVector.Unproject(engine.GetDevice().Viewport,
					engine.GetDevice().Transform.Projection,
					engine.GetDevice().Transform.View,
					Matrix.Translation(bricks[i, j].Position) 
					* Matrix.Scaling(0.5f, 0.5f, 0.5f));
				farVector.Unproject(engine.GetDevice().Viewport,
					engine.GetDevice().Transform.Projection,
					engine.GetDevice().Transform.View,
					Matrix.Translation(bricks[i, j].Position)
					* Matrix.Scaling(0.5f, 0.5f, 0.5f));
				farVector.Subtract(nearVector);
				// Perform intersection test for the bounding box and ray.
				lower = bricks[i, j].Position - new Vector3(0.5f, 0.5f, 0.5f);
				upper = bricks[i, j].Position + new Vector3(0.5f, 0.5f, 0.5f);
				if (Geometry.BoxBoundProbe(lower,
					upper, nearVector, farVector))
				{
					// Er boksen fundet?
					MessageBox.Show("X: " + i + " Y: " + j);
					break;
				}
			}
	}

Også denne version af projektet er tilgængelig for download. Herunder ses et billede fra en afprøvning af programmet.

 

Kommende udfordringer og udvidelser

Spilleregler tilføjes - denne udfordring tages op i et andet kapitel, som tilføjes senere.


Sidst opdateret: 25. februar 2007

Index