From 393e9d14b9885f03b8a517aaaa4c21f5942d0408 Mon Sep 17 00:00:00 2001 From: Colin McMillen Date: Wed, 11 Mar 2020 16:04:22 -0400 Subject: [PATCH] Player: track position with a Vector2. Fixes #10 (mostly). --- Shared/Camera.cs | 4 ++-- Shared/Player.cs | 38 +++++++++++++++++++++----------------- Shared/World.cs | 2 +- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/Shared/Camera.cs b/Shared/Camera.cs index 98de030..b6171fa 100644 --- a/Shared/Camera.cs +++ b/Shared/Camera.cs @@ -21,8 +21,8 @@ namespace SemiColinGames { get => Matrix.CreateOrthographicOffCenter(Left, Left + Width, Height, 0, -1, 1); } - public void Update(float modelTime, Point player, int worldWidth) { - int diff = player.X - bbox.Center.X; + public void Update(float modelTime, Vector2 player, int worldWidth) { + float diff = player.X - bbox.Center.X; if (Math.Abs(diff) > 16) { bbox.Offset((int) (diff * 0.1), 0); } diff --git a/Shared/Player.cs b/Shared/Player.cs index 29debb8..810c3ed 100644 --- a/Shared/Player.cs +++ b/Shared/Player.cs @@ -22,18 +22,20 @@ namespace SemiColinGames { // Position is tracked at the Player's center. The Player's bounding box is a rectangle // centered at that point and extending out by halfSize.X and halfSize.Y. - private Point position; + private Vector2 position; private Vector2 halfSize = new Vector2(11, 24); + // Fractional-pixel movement that was left over from a previous frame's movement. + // Useful so that we can run at a slow time-step and still get non-zero motion. + private Vector2 residual = Vector2.Zero; private int jumps = 0; private Pose pose = Pose.Jumping; private double swordSwingTime = 0; private int swordSwingNum = 0; - private const int swordSwingMax = 6; private float ySpeed = 0; private float invincibilityTime = 0; - public Player(Point position, int facing) { + public Player(Vector2 position, int facing) { this.position = position; Facing = facing; Health = MaxHealth; @@ -45,20 +47,22 @@ namespace SemiColinGames { public int Facing { get; private set; } - public Point Position { get { return position; } } + public Vector2 Position { get { return position; } } public void Update(float modelTime, World world, History input) { - AABB BoxOffset(Point position, int yOffset) { + AABB BoxOffset(Vector2 position, int yOffset) { return new AABB(new Vector2(position.X, position.Y + yOffset), halfSize); } - AABB Box(Point position) { + AABB Box(Vector2 position) { return BoxOffset(position, 0); } invincibilityTime -= modelTime; - Vector2 movement = HandleInput(modelTime, input); + Vector2 inputMovement = HandleInput(modelTime, input); + Vector2 movement = Vector2.Add(residual, inputMovement); + residual = new Vector2(movement.X - (int) movement.X, movement.Y - (int) movement.Y); // Broad test: remove all collision targets nowhere near the player. // TODO: don't allocate a list here. @@ -69,8 +73,8 @@ namespace SemiColinGames { // sure (the only downside is a small number of false-positive AABBs, which should be // discarded by later tests anyhow.) AABB largeBox = new AABB( - new Vector2(position.X + movement.X / 2, position.Y + movement.Y / 2), - new Vector2(halfSize.X + Math.Abs(movement.X) + 1, halfSize.Y + Math.Abs(movement.Y) + 1)); + Vector2.Add(position, Vector2.Divide(movement, 2)), + Vector2.Add(halfSize, new Vector2(Math.Abs(movement.X) + 1, Math.Abs(movement.Y) + 1))); foreach (var box in world.CollisionTargets) { if (box.Intersect(largeBox) != null) { // Debug.AddRect(box, Color.Green); @@ -84,7 +88,7 @@ namespace SemiColinGames { int dx = movePoints[i].X - movePoints[i - 1].X; int dy = movePoints[i].Y - movePoints[i - 1].Y; if (dy != 0) { - Point newPosition = new Point(position.X, position.Y + dy); + Vector2 newPosition = new Vector2(position.X, position.Y + dy); AABB player = Box(newPosition); bool reject = false; foreach (var box in candidates) { @@ -102,7 +106,7 @@ namespace SemiColinGames { } } if (dx != 0) { - Point newPosition = new Point(position.X + dx, position.Y); + Vector2 newPosition = new Vector2(position.X + dx, position.Y); AABB player = Box(newPosition); bool reject = false; foreach (var box in candidates) { @@ -149,16 +153,16 @@ namespace SemiColinGames { invincibilityTime = 0.6f; } - if (movement.X > 0) { + if (inputMovement.X > 0) { Facing = 1; - } else if (movement.X < 0) { + } else if (inputMovement.X < 0) { Facing = -1; } if (swordSwingTime > 0) { pose = Pose.SwordSwing; } else if (jumps == 0) { pose = Pose.Jumping; - } else if (movement.X != 0) { + } else if (inputMovement.X != 0) { pose = Pose.Walking; } else if (input[0].Motion.Y > 0) { pose = Pose.Stretching; @@ -172,7 +176,7 @@ namespace SemiColinGames { // Returns the desired (dx, dy) for the player to move this frame. Vector2 HandleInput(float modelTime, History input) { Vector2 result = new Vector2() { - X = (int) (input[0].Motion.X * moveSpeed * modelTime) + X = input[0].Motion.X * moveSpeed * modelTime }; if (input[0].Jump && !input[1].Jump && jumps > 0) { @@ -182,7 +186,7 @@ namespace SemiColinGames { if (input[0].Attack && !input[1].Attack && swordSwingTime <= 0) { swordSwingTime = 0.3; - swordSwingNum = (swordSwingNum + 1) % swordSwingMax; + swordSwingNum++; SoundEffects.SwordSwings[swordSwingNum % SoundEffects.SwordSwings.Length].Play(); } @@ -219,7 +223,7 @@ namespace SemiColinGames { if (invincibilityTime > 0 && invincibilityTime % 0.2f > 0.1f) { color = new Color(0.5f, 0.5f, 0.5f, 0.5f); } - spriteBatch.Draw(Textures.Ninja.Get, position.ToVector2(), textureSource, color, 0f, + spriteBatch.Draw(Textures.Ninja.Get, Vector2.Floor(position), textureSource, color, 0f, spriteCenter, Vector2.One, effect, 0f); } } diff --git a/Shared/World.cs b/Shared/World.cs index d980a39..e6f53b7 100644 --- a/Shared/World.cs +++ b/Shared/World.cs @@ -141,7 +141,7 @@ namespace SemiColinGames { int y = entity.SelectToken("y").Value(); int facing = entity.SelectToken("flippedX").Value() ? -1 : 1; if (name == "player") { - player = new Player(new Point(x, y), facing); + player = new Player(new Vector2(x, y), facing); } else if (name == "executioner") { npcs.Add(new NPC(new Vector2(x, y), facing)); }