|
|
@ -4,13 +4,80 @@ using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
|
|
|
|
namespace SemiColinGames { |
|
|
|
public class Player { |
|
|
|
private enum Pose { Walking, Standing, SwordSwing, Jumping }; |
|
|
|
|
|
|
|
private const int moveSpeed = 180; |
|
|
|
interface IPlayerState : IState<History<Input>> { |
|
|
|
public Vector2 Movement { get; } |
|
|
|
public void PostUpdate(bool standingOnGround); |
|
|
|
} |
|
|
|
|
|
|
|
class StandState : IPlayerState { |
|
|
|
private Vector2 result; |
|
|
|
|
|
|
|
// private double swordSwingTime = 0;
|
|
|
|
// private int swordSwingNum = 0;
|
|
|
|
private float ySpeed = 0; |
|
|
|
private int jumps = 1; |
|
|
|
private const int jumpSpeed = -600; |
|
|
|
private const int moveSpeed = 180; |
|
|
|
private const int gravity = 1600; |
|
|
|
|
|
|
|
public void Enter() { |
|
|
|
} |
|
|
|
|
|
|
|
public string Update(float modelTime, World world, History<Input> input) { |
|
|
|
result = new Vector2() { |
|
|
|
X = input[0].Motion.X * moveSpeed * modelTime |
|
|
|
}; |
|
|
|
|
|
|
|
if (input[0].Jump && !input[1].Jump && jumps > 0) { |
|
|
|
jumps--; |
|
|
|
ySpeed = jumpSpeed; |
|
|
|
} |
|
|
|
|
|
|
|
// if (input[0].Attack && !input[1].Attack && swordSwingTime <= 0) {
|
|
|
|
// swordSwingTime = 0.3;
|
|
|
|
// swordSwingNum++;
|
|
|
|
// SoundEffects.SwordSwings[swordSwingNum % SoundEffects.SwordSwings.Length].Play();
|
|
|
|
// }
|
|
|
|
|
|
|
|
result.Y = ySpeed * modelTime; |
|
|
|
ySpeed += gravity * modelTime; |
|
|
|
// swordSwingTime -= modelTime;
|
|
|
|
|
|
|
|
if (input[0].IsAbsoluteMotion) { |
|
|
|
if (input[1].Motion.X == 0) { |
|
|
|
result.X = input[0].Motion.X; |
|
|
|
} else { |
|
|
|
result.X = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
public Vector2 Movement { |
|
|
|
get { return result; } |
|
|
|
} |
|
|
|
|
|
|
|
// TODO: Maybe this should be Update(), and CalculateMovement() should be the Player-specific
|
|
|
|
// function?
|
|
|
|
public void PostUpdate(bool standingOnGround) { |
|
|
|
if (standingOnGround) { |
|
|
|
jumps = 1; |
|
|
|
ySpeed = -0.0001f; |
|
|
|
// Debug.AddRect(Box(position), Color.Cyan);
|
|
|
|
} else { |
|
|
|
jumps = 0; |
|
|
|
// Debug.AddRect(Box(position), Color.Orange);
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public class Player { |
|
|
|
private readonly FSM<History<Input>> fsm; |
|
|
|
// TODO: get rid of Pose.
|
|
|
|
private enum Pose { Walking, Standing, SwordSwing, Jumping }; |
|
|
|
|
|
|
|
// Details of the sprite image.
|
|
|
|
// player_1x is 48 x 48, yOffset=5, halfSize=(7, 14)
|
|
|
|
// Ninja_Female is 96 x 64, yOffset=1, halfSize=(11, 24)
|
|
|
@ -28,11 +95,7 @@ namespace SemiColinGames { |
|
|
|
// 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 float ySpeed = 0; |
|
|
|
private float invincibilityTime = 0; |
|
|
|
|
|
|
|
// For passing into Line.Rasterize() during movement updates.
|
|
|
@ -45,6 +108,9 @@ namespace SemiColinGames { |
|
|
|
Facing = facing; |
|
|
|
Health = MaxHealth; |
|
|
|
StandingOnGround = false; |
|
|
|
fsm = new FSM<History<Input>>("run", new Dictionary<string, IState<History<Input>>> { |
|
|
|
{ "run", new StandState() }, |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
public bool StandingOnGround { get; private set; } |
|
|
@ -68,7 +134,7 @@ namespace SemiColinGames { |
|
|
|
|
|
|
|
invincibilityTime -= modelTime; |
|
|
|
|
|
|
|
Vector2 inputMovement = HandleInput(modelTime, input); |
|
|
|
Vector2 inputMovement = HandleInput(modelTime, world, input); |
|
|
|
Vector2 movement = Vector2.Add(residual, inputMovement); |
|
|
|
residual = new Vector2(movement.X - (int) movement.X, movement.Y - (int) movement.Y); |
|
|
|
|
|
|
@ -145,14 +211,7 @@ namespace SemiColinGames { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (StandingOnGround) { |
|
|
|
jumps = 1; |
|
|
|
ySpeed = -0.0001f; |
|
|
|
Debug.AddRect(Box(position), Color.Cyan); |
|
|
|
} else { |
|
|
|
jumps = 0; |
|
|
|
Debug.AddRect(Box(position), Color.Orange); |
|
|
|
} |
|
|
|
((IPlayerState) fsm.State).PostUpdate(StandingOnGround); |
|
|
|
|
|
|
|
if (harmedByCollision && invincibilityTime <= 0) { |
|
|
|
world.ScreenShake(); |
|
|
@ -165,11 +224,7 @@ namespace SemiColinGames { |
|
|
|
} else if (inputMovement.X < 0) { |
|
|
|
Facing = -1; |
|
|
|
} |
|
|
|
if (swordSwingTime > 0) { |
|
|
|
pose = Pose.SwordSwing; |
|
|
|
} else if (jumps == 0) { |
|
|
|
pose = Pose.Jumping; |
|
|
|
} else if (inputMovement.X != 0) { |
|
|
|
if (inputMovement.X != 0) { |
|
|
|
pose = Pose.Walking; |
|
|
|
} else { |
|
|
|
pose = Pose.Standing; |
|
|
@ -177,35 +232,10 @@ namespace SemiColinGames { |
|
|
|
} |
|
|
|
|
|
|
|
// Returns the desired (dx, dy) for the player to move this frame.
|
|
|
|
Vector2 HandleInput(float modelTime, History<Input> input) { |
|
|
|
Vector2 result = new Vector2() { |
|
|
|
X = input[0].Motion.X * moveSpeed * modelTime |
|
|
|
}; |
|
|
|
|
|
|
|
if (input[0].Jump && !input[1].Jump && jumps > 0) { |
|
|
|
jumps--; |
|
|
|
ySpeed = jumpSpeed; |
|
|
|
} |
|
|
|
|
|
|
|
if (input[0].Attack && !input[1].Attack && swordSwingTime <= 0) { |
|
|
|
swordSwingTime = 0.3; |
|
|
|
swordSwingNum++; |
|
|
|
SoundEffects.SwordSwings[swordSwingNum % SoundEffects.SwordSwings.Length].Play(); |
|
|
|
} |
|
|
|
|
|
|
|
result.Y = ySpeed * modelTime; |
|
|
|
ySpeed += gravity * modelTime; |
|
|
|
swordSwingTime -= modelTime; |
|
|
|
|
|
|
|
if (input[0].IsAbsoluteMotion) { |
|
|
|
if (input[1].Motion.X == 0) { |
|
|
|
result.X = input[0].Motion.X; |
|
|
|
} else { |
|
|
|
result.X = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return result; |
|
|
|
Vector2 HandleInput(float modelTime, World world, History<Input> input) { |
|
|
|
fsm.Update(modelTime, world, input); |
|
|
|
// TODO: remove ugly cast.
|
|
|
|
return ((IPlayerState) fsm.State).Movement; |
|
|
|
} |
|
|
|
|
|
|
|
private Rectangle GetTextureSource(Pose pose) { |
|
|
@ -216,8 +246,8 @@ namespace SemiColinGames { |
|
|
|
return Sprites.Ninja.GetTextureSource("run", time); |
|
|
|
case Pose.SwordSwing: |
|
|
|
// TODO: make a proper animation class & FSM-driven animations.
|
|
|
|
return Sprites.Ninja.GetTextureSource( |
|
|
|
"attack_sword", 0.3 - swordSwingTime); |
|
|
|
//return Sprites.Ninja.GetTextureSource(
|
|
|
|
// "attack_sword", 0.3 - swordSwingTime);
|
|
|
|
case Pose.Standing: |
|
|
|
default: |
|
|
|
return Sprites.Ninja.GetTextureSource("idle", time); |
|
|
|