Search code examples
c#xna

Specific yet simple code for player movement not working as intended (XNA)


Problem: Sometimes, when my fighter's running in one direction (because Im'm holding direction button like A,D,Left or Right), and I quicly release button for that direction and press button for opposite direction, the fighter continues to run in the first mentioned direction. This doesn't happen all the time, it's "random". Also, this has nothing to do with jumping/falling, because it doesn't matter if fighter's in air or not.
I have 4 classes in which fighter movement is regulated in update methods:

Input class, just some functions

public static void UpdateNew(GameTime gt)
{
    // fires before any other update is called (before player/level/fighter update...)
    frameTime = (float)gt.ElapsedGameTime.TotalSeconds;
    ksNew = Keyboard.GetState();
    msNew = Mouse.GetState();
}
public static void UpdateOld()
{
     // fires after every other update is called (after player/level/fighter update...)
    ksOld = ksNew;
    msOld = msNew;
    scrollValue = msOld.ScrollWheelValue;
}
public static bool Down(Keys k)
{ return ksNew.IsKeyDown(k); }


Player class:

Fighter fighter; //set in constructor, it's not null
public void Update()
{
    fighter.RunDirection = Input.Down(rightK).ToInt() - Input.Down(leftK).ToInt();
    fighter.Jump = Input.Down(jumpK);
    fighter.Punch = Input.Clicked(punchK);
}
// where .ToInt() converts bool to 1 if it's true, and to 0 if it's not true


Fighter class

public int              RunDirection
{ set { runMultiplier = MathHelper.Clamp(value, -1, 1); } }

public override void    Update              (float elTime, List<Entity> entities)
{
    // region: checks for jump, works fine

    AddForce(
         // X - Horizontal Force
         ((Math.Abs(Force.X) < runSpeed) ? runMultiplier * runSpeed * 5 * elTime : 0) // movement controls
         + ((runMultiplier == 0) ? -Force.X.Sign() * runSpeed * 5 * elTime : 0) // traction when not moving
         ,
         // Y - Vertical Force
         ((!canJump) ? 900 * elTime : 0) // gravity
         - jumpDecreasingSpeed * elTime // jump
         );

    // region: Check collision and puts force.X or force.Y to 0 when it's colliding with other 
    // entities and put's player next to that entity

    //set bool that stores last direction of looking (left/right)
    if (runMultiplier != 0) lookingRight = runMultiplier > 0;

    //checks if animation should be changed (idle, running, jumping or falling)
    CheckAnimation();

    //update currently playing animation
    currentAnim.Update(elTime);

    //stop fighters horisontal movement if it's small enough
    if (Math.Abs(Force.X) < 1) AddForce(-Force.X, 0);

    // after all calculations, move fighter by the force that is currently imacting him.
    Position += Force * elTime;
}


Level class

public void         Update          (float elTime)
{
    // PLAYERS
    p1.Update(); // updates only controls, as mentioned above, not the fighter itself.
    p2.Update();

    // update ENTITIES, player's fighters, and walls (their update method is empty)
    // player's fighters are added to maps "entities" list.
    for (int i = 0; i < map.entities.Count; i++)
        map.entities[i].Update(elTime, map.entities.Where(q => q != map.entities[i]).ToList());
}


Brief update order for each frame
GameMainUpdate
---Input.UpdateNew
---Level.Update
------Player.Update
------Fighter.Update
---------Check if player want's you to move, and add force (velocity)
---------Check collision, set force to 0 moving if colliding
---------when all calculations are done, add force to current position
---Input.UpdateOld

Question: Why is this happening? Why is it so "random"? I understand that it happends for a reason, but I can't put my finger on it.


Solution

  • One possible scenario that causes it is when Math.Abs(Force.X) >= runSpeed then you add 0 force.

    If Force.X >= runSpeed then 0

    runMultiplier == 0 (false due to immediate switch) then 0

    AddForce(0+0)

         // X - Horizontal Force
         ((Math.Abs(Force.X) < runSpeed) ? runMultiplier * runSpeed * 5 * elTime : 0) // movement controls
         + ((runMultiplier == 0) ? -Force.X.Sign() * runSpeed * 5 * elTime : 0) // traction when not moving
    

    Then this will never be executed because Math.Abs(Force.X) >= 1 (I assume runspeed is >= 1).

    //stop fighters horisontal movement if it's small enough
    if (Math.Abs(Force.X) < 1) AddForce(-Force.X, 0);
    

    This is a possible reason why switching keys within a 1 frame buffer where the one direction is immediately replaced by the other can net you a force of 0; which would cause the fighter to continue in the previous direction.