Hoorcollege 9 Jewel Jam The revenge
Hierarchie van game objecten Game object (lijst) Game object (grid) Game object (lijst) Game object
Game object communicatie Hoe ‘praten’ game objecten met elkaar? Verschillende mogelijkheden – Ze praten niet met elkaar alle interactie handelen we af in de game wereld – Objecten zoeken zelf uit met wie ze willen praten, en kunnen andere objecten vinden in de game wereld
Wat hebben we nodig? Game objecten moeten toegang hebben tot de gamewereld GameWorld object is beschikbaar als static variabele Game objecten moeten andere game objecten kunnen vinden GameObject instanties hebben een ID nodig
Communicatie via GameWorld object class GameObject { // members … protected string id; public GameObject(int layer = 0, string id = "") { … } public virtual void HandleInput(InputHelper inputHelper) { … } public virtual void Update(GameTime gameTime) { … } public virtual void Draw(GameTime gameTime, SpriteBatch spriteBatch ) { … } … GameObject instanties hebben een ID
Zoeken naar game objecten public GameObject Find(string id) { foreach (GameObject obj in gameObjects) { if (obj.ID == id) return obj; if (obj is GameObjectList) { GameObjectList objlist = obj as GameObjectList; GameObject subobj = objlist.Find(id); if (subobj != null) return subobj; } } return null; } Methode in de GameObjectList klasse. Methode roept ‘zichzelf’ aan: recursie Gebruik is om te kijken of een object van een bepaald type is.
Ander voorbeeld van recursie public virtual Vector2 GlobalPosition { get { if (parent != null) return parent.GlobalPosition + this.Position; else return this.Position; } Zelfde property aanroepen in ander object
Wat is recursie? Recursie = in een methode X diezelfde methode X een of meerdere keren aanroepen, normaalgesproken met andere parameterwaarden Om recursie te snappen, moet je recursie snappen ;)
Recursie In een methode met recursie vind je vaak een aantal alternatieven – Minstens 1 van die alternatieven moet een base case / terminatie zijn! Bijvoorbeeld: public int product(unsigned int a, unsigned int b) { return (a + product(a-1, b)); } Gaat oneindig door!
Recursie Zelfde methode, maar met terminatie: public int product(unsigned int a, unsigned int b) { if (a == 0) return 0; else return (b + product(a-1, b)); } Stoppen indien a == 0
Wat is hier de terminatieconditie? public virtual Vector2 GlobalPosition { get { if (parent != null) return parent.GlobalPosition + this.Position; else return this.Position; }
Publieksvraag Schrijf een methode grootste, met de volgende header: Maak gebruik van recursie! public int grootste(List lijst, int aantal)
Uitwerking public int grootste(List lijst, int aantal) { if (aantal == 1) return lijst[0]; else { int grootsteRest = grootste(lijst, aantal-1); if (grootsteRest > lijst[aantal-1]) return grootsteRest; else return lijst[aantal-1]; } }
Publieksvraag Wat is de uitkomst als we de getallen 1 t/m 9 aan de volgende methode als parameter meegeven? int puzzle(int n) { if (n == 1) return 1; if (n % 2 == 0) return puzzle(n/2); else return puzzle(3*n+1); }
RowSelectGameObject public override void HandleInput(InputHelper inputHelper) { JewelGrid grid = GameWorld.Find("grid") as JewelGrid; if (inputHelper.KeyPressed(Keys.Up)) selectedRow--; else if (inputHelper.KeyPressed(Keys.Down)) selectedRow++; selectedRow = (int)MathHelper.Clamp(selectedRow,0,grid.Rows-1); this.position = new Vector2(-10, grid.CellHeight*selectedRow - 10); if (inputHelper.KeyPressed(Keys.Left)) grid.ShiftRowLeft(selectedRow); else if (inputHelper.KeyPressed(Keys.Right)) grid.ShiftRowRight(selectedRow); } Grid object zoeken Gebruikmaken van het object.
JewelGrid class JewelGrid : GameObjectGrid { public JewelGrid(int rows, int columns, int layer = 0, string id = "") : base(rows, columns, layer, id) { }... } We erven van GameObjectGrid
Geldige combinaties vinden Elk juweel heeft drie eigenschappen: kleur, vorm en aantal Encoderen dmv. 3 getallen in verzameling {0, 1, 2} Drie elementen hebben dezelfde eigenschap: 0+0+0=0, of 1+1+1=3 of 2+2+2=6 – Som is altijd deelbaar door 3! Drie elementen hebben verschillende eigenschap: 0+1+2=3 ook deelbaar door 3!
Geldige combinaties vinden Hoe vinden we de encodering? – We hebben een getal (variation) tussen 0-27 Variation/9 geeft de kleur Variation%9 is dan een getal tussen 0-8 Variation%9/3 geeft de vorm Rest is een getal tussen 0-2 geeft het aantal weer
public bool IsValidCombination(Jewel a, Jewel b, Jewel c) { int curra = a.Variation; int currb = b.Variation; int currc = c.Variation; int divider = 9; for (int i = 0; i < 3; i++) { if ((curra/divider + currb/divider + currc/divider)%3 != 0) return false; curra %= divider; currb %= divider; currc %= divider; divider /= 3; } return true; }
if (!inputHelper.KeyPressed(Keys.Space)) return; int middleCol = this.Columns / 2, i = 0; ScoreGameObject score = GameWorld.Find("score") as ScoreGameObject; while (i < this.Rows - 2) { if (IsValidCombination((Jewel)grid[middleCol, i], (Jewel)grid[middleCol, i + 1], (Jewel)grid[middleCol, i + 2])) { ReplaceJewel(middleCol, i, -objsize); ReplaceJewel(middleCol, i + 1, -objsize * 2); ReplaceJewel(middleCol, i + 2, -objsize * 3); score.Score += 10;... i = 0; } else i++; } Het score game object zoeken. HandleInput methode (JewelGrid)
public void ReplaceJewel(int x, int y, int newYPosition) { this.Clear(x, y); Jewel s = new Jewel(); this.Add(s); s.Position = new Vector2(x * CellWidth, newYPosition); }
JewelCart
class JewelCart : SpriteGameObject { protected float push, minxpos; public JewelCart(int layer = 0, string id = "") : base("spr_jewelcart", layer, id) { velocity.X = 6; push = 50.0f; } public void Push() { position.X = MathHelper.Max(position.X - push, minxpos); }... } We erven van SpriteGameObject
if (!inputHelper.KeyPressed(Keys.Space)) return; int middleCol = this.Columns / 2, i = 0; while (i < this.Rows - 2) { if (IsValidCombination((Jewel)grid[middleCol, i], (Jewel)grid[middleCol, i + 1], (Jewel)grid[middleCol, i + 2])) {... JewelCart jewelCart = GameWorld.Find("jewelcart") as JewelCart; jewelCart.Push(); i = 0; } else i++; }
Paar seconden laten zien
Omgaan met tijd Hoe vaak wordt Update/Draw eigenlijk aangeroepen? Twee mogelijkheden: – Zo vaak mogelijk – Op vaste tijdstippen (bijv. een aantal keer per seconde) fixed time step Standaard wordt de tweede optie gebruikt – Update+Draw 60 keer per seconde
Omgaan met tijd Twee soorten tijd in de GameTime klasse: – Game time – Real time Total real time = echte tijd verstreken sinds de start van het spel Total game time = tijd verstreken afhankelijk van het aantal updates – Game tijd vertraagt bij moeilijke berekeningen!
Omgaan met tijd Voorbeeld bij fixed time step = 10ms: Update Draw Update Draw Update Draw TotalGameTimeTotalRealTime10ms 20ms 30ms 40ms 50ms70ms 60ms80ms Echte tijd gaat door, game tijd vertraagt!
De TimeSpan klasse Properties: – TotalMilliseconds – TotalSeconds – Milliseconds – Seconds – Minutes – Etc. Methoden: – Add, Subtract, en nog een paar
Gebruikmaken van tijd class VisibilityTimer : GameObject { protected GameObject target; protected float timeleft; protected float totaltime; public VisibilityTimer(GameObject target, int layer=0, string id = "") : base(layer, id) { totaltime = 1; timeleft = 0; this.target = target; }... } Subklasse van GameObject Zichtbaarheid beheren van een doelobject 1 seconde laten zien
Gebruikmaken van tijd public override void Update(GameTime gameTime) { timeleft -= (float)gameTime.ElapsedGameTime.TotalSeconds; if (timeleft <= 0) target.Visible = false; } public override void Reset() {... } public void StartVisible() { timeleft = totaltime; target.Visible = true; } Overgebleven tijd neemt af Object zichtbaar maken
Game wereld uitbreiden SpriteGameObject dOvrly = new SpriteGameObject("spr_double", 1); dOvrly.Position = new Vector2(800, 400); VisibilityTimer dTmr = new VisibilityTimer(dOvrly, 0, "doubleTimer"); this.Add(dOvrly); this.Add(dTmr); SpriteGameObject tOvrly = new SpriteGameObject("spr_triple", 1); tOvrly.Position = new Vector2(800, 400); VisibilityTimer tTmr = new VisibilityTimer(tOvrly, 0, "tripleTimer"); this.Add(tOvrly); this.Add(tTmr);
(Drie)dubbele combinaties int nrCombis = 0; if (IsValidCombination((Jewel)grid[middleCol, i], (Jewel)grid[middleCol, i + 1], (Jewel)grid[middleCol, i + 2])) {... nrCombis++; i = 0; } else i++; }...
(Drie)dubbele combinaties if (nrCombis == 1) JewelJam.AssetManager.PlaySound("snd_combi"); else if (nrCombis == 2) { score.Score += 50; VisibilityTimer v = GameWorld.Find("doubleTimer") as VisibilityTimer; doubleTimer.StartVisible(); JewelJam.AssetManager.PlaySound("snd_double"); } else if (nrCombis == 3) { score.Score += 100; VisibilityTimer v = GameWorld.Find("tripleTimer") as VisibilityTimer; tripleTimer.StartVisible(); JewelJam.AssetManager.PlaySound("snd_triple"); }
Glitters We definieren een klasse GlitterField Die krijgt een target texture (sprite) Willekeurige glitter sprites afbeelden bovenop de target sprite – Maar alleen op plekken waar de sprite niet doorzichtig is!
GlitterField klasse class GlitterField : GameObject { protected List positions; protected List scales; protected int width, height, xoffset; protected Texture2D glitter; protected Texture2D target; … }
Willekeurige positie berekenen public Vector2 CreateRandomPosition() { Vector2 randomPos = Vector2.Zero; while (true) { randomPos = new Vector2(JewelJam.Random.Next(width), JewelJam.Random.Next(height)); Rectangle sourceRectangle = new Rectangle((int)randomPos.X + xoffset, (int)randomPos.Y, 1, 1); Color[] retrievedColor = new Color[1]; target.GetData (0, sourceRectangle, retrievedColor, 0, 1); if (retrievedColor[0].A == 255) break; } return randomPos; } Pixeldata opvragen “stop de oneindige while loop”
Update methode public override void Update(GameTime gameTime) { for (int i = 0; i < scales.Count; i++) { if (scales[i] == 0 && JewelJam.Random.NextDouble() < 0.001) scales[i] += 0.05f; else if (scales[i] != 0) { scales[i] += 0.05f; if (scales[i] >= 2.0f) { scales[i] = 0f; positions[i] = this.CreateRandomPosition(); } } } }
Draw methode public override void Draw(GameTime gameTime, SpriteBatch s) { Vector2 glitterCenter = new Vector2(glitter.Width, glitter.Height) / 2; for (int i = 0; i < scales.Count; i++) { float scale = scales[i]; if (scales[i] > 1) scale = 2 - scales[i]; s.Draw(glitter, this.GlobalPosition + positions[i], null, Color.White, 0f, glitterCenter, scale, SpriteEffects.None, 0); } }
De volgende keer Penguin Pairs! – Sprite sheets – Menu’s – Game state management