500 likes | 649 Views
Hoorcollege 13. Animatie , game physics. De speler. Wordt aangestuurd via toetsenbord Kent verschillende soorten bewegingen Rennen Stilstaan Springen Doodgaan Botsingen moeten afgehandeld worden We willen simpele ‘physics’ voor de speler. Animatie.
E N D
Hoorcollege 13 Animatie, game physics
De speler • Wordtaangestuurd via toetsenbord • Kent verschillendesoortenbewegingen • Rennen • Stilstaan • Springen • Doodgaan • Botsingenmoetenafgehandeldworden • We willensimpele ‘physics’ voor de speler
Animatie • We hebben al simpele animaties gezien • In de Painter game: bal, paintcans, … • Bewegende paddles in Pong • Tetrisblokjes die naar beneden vallen • Enzovoorts… • Maar tot nu toe verplaatsen we alleen sprites • Hoe zit het met animaties?
Wat is animatie? • Animatie is een snelle opeenvolging van net iets andere plaatjes • Als dat snel genoeg gebeurt, dan denken we dat iets beweegt. • “Persistence of Vision” + “Persistence of Motion”
Animatie in C# • Verschillendemogelijkheden • Iedere frame = een sprite • Een sprite bevateenaantal frames • Hiervoorkunnen we mooi sprite sheets gebruiken! 1 frame De sprite met alle frames
AnimatedCharacter Geanimeerde sprite
Overzicht • Animation klasse • Uitbreiding van SpriteSheet • Namelijk: sheet index aanpassen aan de verstreken tijd • AnimatedGameObject klasse • Bevat een aantal Animation objecten • Kan verschillende animaties afspelen
Animation klasse public class Animation : SpriteSheet { protectedfloatframeTime; protectedboolisLooping; protectedfloattime; public Animation(string asset, boolisLooping, floatfrtime = 0.1f) : base(asset) { this.frameTime = frtime; this.isLooping = isLooping; } public int CountFrames { get { return this.NumberSheetElements; } } … Handige property dat het aantal frames berekent De tijdtusseniedere frame, oftewel: ditbepaalt de snelheid van de animatie!
public floatFrameTime { get { returnframeTime; } } public boolIsLooping { get { returnisLooping; } } public boolAnimationEnded { get { return !this.isLooping && sheetIndex >= NumberSheetElements - 1; } } Nogmeer properties…
public voidPlay() { this.sheetIndex = 0; this.time = 0.0f; } public override void Update(GameTimegameTime) { time += (float)gameTime.ElapsedGameTime.TotalSeconds; while (time > frameTime) { time -= frameTime; if (isLooping) sheetIndex = (sheetIndex + 1) % this.CountFrames; else sheetIndex = Math.Min(sheetIndex + 1, this.CountFrames - 1); } } Start de animatie. Bereken huidig af te beelden frame.
Spiegelen van sprites • Naar links lopen of naar rechts lopen • Spiegel de sprite in plaats van twee aparte sprites public class SpriteSheet { … protectedboolmirror; … public boolMirror { get { returnmirror; } set { mirror = value; } } … }
SpriteSheet public void Draw(SpriteBatch s, Vector2 position, Vector2 origin) { intcolumnIndex = sheetIndex % sheetColumns; introwIndex = sheetIndex / sheetColumns; Rectangle spritePart = new Rectangle(columnIndex * this.Width, rowIndex * this.Height, this.Width, this.Height); SpriteEffectsspriteEffects = SpriteEffects.None; if(mirror) spriteEffects = SpriteEffects.FlipHorizontally; spriteBatch.Draw(sprite, position, spritePart, Color.White, 0.0f, origin, 1.0f, spriteEffects, 0.0f); } Sprite spiegelen
AnimatedGameObject public class AnimatedGameObject : SpriteGameObject { protected Dictionary<string, Animation> animations; publicAnimatedGameObject(int layer = 0, string id = "") : base(layer, id) { animations = new Dictionary<string, Animation>(); } public AnimationCurrent { get { return sprite as Animation; } } … }
AnimatedGameObject public void LoadAnimation(string asset, string id, bool looping, floatframetime = 0.1f) { Animation anim = new Animation(assetname, looping, frametime); animations[id] = anim; } public voidPlayAnimation(stringid) { if (sprite == animations[id]) return; if(sprite != null) animations[id].Mirror = sprite.Mirror; animations[id].Play(); sprite = animations[id]; origin = new Vector2(sprite.Width / 2, sprite.Height); }
AnimatedGameObject public override void Update(GameTimegameTime) { if (sprite == null) return; Current.Update(gameTime); base.Update(gameTime); }
De Player klasse classPlayer : AnimatedGameObject { public Player(Vector2 start) : base(2, "player") { this.LoadAnimation("spr_idle", "idle", true); this.LoadAnimation("spr_run@13", "run", true, 0.05f); this.LoadAnimation("spr_jump@14", "jump", false, 0.05f); this.LoadAnimation("spr_celeb@14", "celebrate", false, 0.05f); this.LoadAnimation("spr_die@5", "die", false); this.LoadAnimation("spr_expl@5x5", "explode", false, 0.04f); startPosition = start; Reset(); } …
public overridevoidReset() { this.position = startPosition; isOnTheGround = true; this.PlayAnimation("idle"); previousYPosition = BoundingBox.Bottom; } public override void Update(GameTimegameTime) { base.Update(gameTime); if (isOnTheGround) if (velocity.X == 0) this.PlayAnimation("idle"); else this.PlayAnimation("run"); elseif(velocity.Y < 0) this.PlayAnimation("jump"); DoPhysics(); } Spelerbegint op een bepaaldepositie. startconfiguratie “idle” animatie positie
public overridevoidHandleInput(InputHelperinputHelper) { floatwalkingSpeed = 400; if (inputHelper.IsKeyDown(Keys.Left)) velocity.X = -walkingSpeed; elseif(inputHelper.IsKeyDown(Keys.Right)) velocity.X = walkingSpeed; elseif(isOnTheGround) velocity.X = 0.0f; if (velocity.X != 0.0f) Mirror = velocity.X < 0; if((inputHelper.KeyPressed(Keys.Space) || inputHelper.KeyPressed(Keys.Up)) && isOnTheGround) Jump(); } Kies de juiste beweegrichting Willen we springen?
Speler physics public void Jump(float speed = 1100) { velocity.Y = -speed; } private void DoPhysics() { velocity.Y += 55f; HandleCollisions(); } We vallen steeds harder naarbeneden. Springen = negatieve y-snelheid.
Botsingen tussen sprites • Twee manieren: • Kijk per pixel of de sprites overlappen • Gebruikeenvereenvoudigdevoorstelling van de sprites
Botsingen tussen sprites • Twee veel voorkomende vormen: cirkel, rechthoek • Noemen we ook wel ‘bounding box’ en ‘bounding circle’ (of ‘bounding sphere’ in 3D) • Dit kunnen we ook generaliseren: • Convexe polygonen met behulp van Separating Axis Theorem • Wij beperken ons tot cirkels en rechthoeken
Botsingen tussen sprites • Berekenen van bounding cirkels en bounding boxes. Rectangle bounding = new Rectangle(spritePositie.X, spritePositie.Y, sprite.Width, sprite.Height); sprite.Width spritePositie (X,Y) sprite.Height
Botsingen tussen sprites • Bounding cirkel berekenen we als volgt: Vector2 middelpunt = new Vector2(spritePositie.X + sprite.Width / 2, spritePositie.Y + sprite.Height / 2); floatstraal = Math.Max(sprite.Width / 2, sprite.Height / 2); spritePositie (X,Y) sprite.Width straal sprite.Height middelpunt
Botsingen tussen sprites • Hoe berekenen we dit? • Drie mogelijkheden:
Botsingen tussen sprites • Twee cirkels botsen met elkaar als de afstand tussen de middelpunten kleiner is dan de som van de twee stralen
Botsingen tussen sprites • In code: boolInBotsing(Vector2 middel1, float straal1, Vector2 middel2, float straal2) { floatafstand = (middel1 – middel2).Length(); returnafstand < straal1 + straal2 == true; } #$&*(@!!!
Botsingen tussen sprites • Twee rechthoeken • Is al voor ons gedaan! Rectangle box1, box2; … boolbotst = box1.Intersects(box2);
Botsingen tussen sprites • Tussen een cirkel en een rechthoek • Via het dichtstbijzijnde punt op de rechthoek tot middelpunt van de cirkel
Botsingen tussen sprites boolInBotsing(Vector2 middel, floatstraal, Rectangle box) { Vector2 dichtstbij = new Vector2( MathHelper.Clamp(middel.X, box.Left, box.Right), MathHelper.Clamp(middel.Y, box.Top, box.Bottom)); floatafstand = (dichtstbij – middel).Length(); returnafstand < straal; }
Wat te doen bij een botsing? • Snelheidomdraaien • Twee biljartballen die botsen • Het balletje in Pong vliegt de anderekant op • Een van de sprites nietmeertekenen • Als je over een ‘powerup’ vliegt • Game over • De spelerloopttegeneenvijandaan • …
Botsingen verwerken • De spelerkanbotsen met: • Vijanden • Waterdruppels • Walltiles • Vijanden + waterdruppelshandelen we af in de Enemy/WaterDrop-klassen (komt later) • Speler + Tiles doen we in de Spelerklasse
Botsingen met tiles • Kijkvoorelke tile of hijbotst met de speler • Dithoeven we alleenmaartedoenvoor tiles die geenachtergrondtilezijn. • En alleenvoor tiles die in de buurt van de spelerzijn
Botsingen verwerken private void HandleCollisions() { isOnTheGround = false; TileField tiles = GameWorld.Find("tiles") asTileField; intx_floor = (int)position.X / tiles.CellWidth; inty_floor = (int)position.Y / tiles.CellHeight; for (int y = y_floor - 2; y <= y_floor + 1; ++y) for (int x = x_floor - 1; x <= x_floor + 1; ++x) { TileTypetileType = tiles.GetTileType(x, y); if (tileType == TileType.Background) continue; // kijk of ereenbotsing is } .. }
Break vs continue • break • Stopt de huidige iteratie en verlaat de loop • continue • Stopt ook de huidige iteratie, maar gaat door met de loop! • Wat is de uiteindelijke waarde van z? inti, z = 0; for (i = 0; i < 10; i++) { if (i == 3) break; z++; } inti, z = 0; for (i = 0; i < 10; i++) { if (i == 3) continue; z++; } inti, z = 0; for (i = 0; i < 10; i++) { z++; } 10 3 9
Botsingen verwerken Rectangle tileBounds = new Rectangle(x * tiles.CellWidth, y * tiles.CellHeight, tiles.CellWidth, tiles.CellHeight); RectangleboundingBox = this.BoundingBox; boundingBox.Height += 1; TilecurrentTile = tiles.Get(x, y) asTile; if (((currentTile != null&& !currentTile.CollidesWith(this)) || currentTile == null) && !tileBounds.Intersects(boundingBox)) continue; • De tiles bewegennietindienereenbotsing is • Dusmoeten we de spelerpositiecorrigeren!
Botsing tussen twee sprites Y intersectiediepte X intersectiediepte
Diepte in de X-richting is kleiner. Botsingen verwerken Dezemethodemoeten we nogmaken. Vector2 depth = Collision.CalculateIntersectionDepth(boundingBox, tileBounds); if(Math.Abs(depth.X) < Math.Abs(depth.Y)) { if(tileType == TileType.Normal) position.X += depth.X; }
Botsingen verwerken Diepte in de Y-richting is kleiner. else { if (previousYPosition <= tileBounds.Top && tileType != TileType.Background) { isOnTheGround = true; velocity.Y = 0; } if (tileType == TileType.Normal || isOnTheGround) position.Y += depth.Y; } position = new Vector2((float)Math.Floor(position.X), (float)Math.Floor(position.Y)); Indien op de grond of geenplatformtile… Staan we op de grond? float afrondingsfouten voorkomen
Intersectiediepte • Stap 1: bereken de maximaleafstandtussen de twee middenpunten • Stap 2: bereken de werkelijkeafstandtussen de twee middenpunten • Stap 3: bereken het verschil, dit is de intersectiediepte • Het teken (+/-) geeft de volgorde van de 2 objectenaan - + =
Intersectiediepteberekenen public static Vector2 CalculateIntersectionDepth(Rectangle rectA, Rectangle rectB) { Vector2 minDistance = new Vector2(rectA.Width + rectB.Width, rectA.Height + rectB.Height) / 2; Vector2 centerA = new Vector2(rectA.Center.X, rectA.Center.Y); Vector2 centerB = new Vector2(rectB.Center.X, rectB.Center.Y); Vector2 distance = centerA - centerB; Vector2 depth = Vector2.Zero; if(distance.X > 0) depth.X = minDistance.X - distance.X; elsedepth.X = -minDistance.X - distance.X; if(distance.Y > 0) depth.Y = minDistance.Y - distance.Y; elsedepth.Y = -minDistance.Y - distance.Y; returndepth; }
Intersectiediepteberekenen public static Vector2 CalculateIntersectionDepth(Rectangle rectA, Rectangle rectB) { Vector2 minDistance = new Vector2(rectA.Width + rectB.Width, rectA.Height + rectB.Height) / 2; Vector2 centerA = new Vector2(rectA.Center.X, rectA.Center.Y); Vector2 centerB = new Vector2(rectB.Center.X, rectB.Center.Y); Vector2 distance = centerA - centerB; Vector2 depth = Vector2.Zero; depth.X = distance.X > 0 ? minDistance.X - distance.X; : -minDistance.X - distance.X; depth.Y = distance.Y > 0 ? minDistance.Y - distance.Y; : -minDistance.Y - distance.Y; return depth; } Verkorte versie if-opdracht
Dan is het resultaat Uitgebreide versie Alternatiefresultaat “if (distance.X > 0)” “else” Verkorte versie if-opdracht Vector2 depth = Vector2.Zero; depth.X = distance.X > 0 ? minDistance.X - distance.X; : -minDistance.X - distance.X; … Vector2 depth = Vector2.Zero; if (distance.X > 0) depth.X = minDistance.X - distance.X; else depth.X = -minDistance.X - distance.X; if (distance.Y > 0) // etc…
Per-pixel botsing afhandelen • De bounding boxes overlappen, maar de ‘echte’ objecten botsen niet met elkaar • Enige oplossing: botsing afhandelen door per pixel te kijken. • Hoeft alleen voor het overlappende stuk te gebeuren!
Per pixel botsing afhandelen public staticRectangleIntersection(Rectangle rect1, Rectangle rect2) { intxmin = (int)MathHelper.Max(rect1.Left, rect2.Left); int xmax = (int)MathHelper.Min(rect1.Right, rect2.Right); intymin = (int)MathHelper.Max(rect1.Top, rect2.Top); intymax = (int)MathHelper.Min(rect1.Bottom, rect2.Bottom); return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin); }
Per pixel botsing afhandelen public boolCollidesWith(SpriteGameObjectobj) { if (!this.Visible || !obj.Visible || !BoundingBox.Intersects(obj.BoundingBox)) return false; Rectangle b = Collision.Intersection(BoundingBox, obj.BoundingBox); for (int x = 0; x < b.Width; x++) for (int y = 0; y < b.Height; y++) { intthisx = b.X - (int)(GlobalPosition.X - origin.X) + x; intthisy = b.Y - (int)(GlobalPosition.Y - origin.Y) + y; intobjx = b.X - (int)(obj.GlobalPosition.X - obj.origin.X) + x; intobjy = b.Y - (int)(obj.GlobalPosition.Y - obj.origin.Y) + y; if (sprite.GetPixelColor(thisx, thisy).A != 0 && obj.sprite.GetPixelColor(objx, objy).A != 0) return true; } return false; }
De volgende keer Be afraid… Be VERY afraid!! I’mthirsty… I hatemy life • Vijanden!