2D Top Down Zelda-like Jump Physics question (SOLVED)


(christopf) #1

Hi again! My head is like burning for 3 or 4 days now (since i posted here last time) and i ask for your help. I just throw it in:

I want to make a zelda-like 2d top down game and after getting the movement in i’m now struggling with the physics especially jumping. I found several tutorials and threads in the web asking for kind of similiar questions but i found no light in them. One was in here at the flashpunk headquarter [SOLVED] Variable Mario Type Jump that brought me to the 2d Physics of Matt Tuttle GitHub - Flashpunk Physics. I guess i’ve learned a lot of analyizing his code (like what is FP.sign, how does a for loop work and about states) and i’m sure there is way more to learn out of it.

But his code doesnt work for my game so i tried first to rewrite it, came to the point to write my “own” engine, messed it up (i guess 2 days way not enough for that kinda project with my experience) and came now back to the idea, to readjust his code through adding for each of his variables another one for the z-axis.

But now when i start the game, my hero is just falling out of screen and i need some fresh code inspiration to keep on working on this.

So if you want to spend some of your time to crawl through my code jungle and give me some advice would make me glad.

Here we go

package 

{ import flash.geom.Point; import net.flashpunk.; import net.flashpunk.graphics.; import net.flashpunk.utils.*;

    public class Physics extends Entity
    {
            public static const WEST:uint = 0;
            public static const OST:uint = 1;
            public static const NORD:uint = 2;
            public static const SUD:uint = 3;
            
            // Define variables
            public var velocityz:Number = new Number(0);
			public var velocityx:Number = new Number(0);
            public var velocityy:Number = new Number(0);

            public var accelerationz:Number = new Number(0);
            public var accelerationx:Number = new Number(0);
            public var accelerationy:Number = new Number(0);
			
            public var frictionx:Number = new Number(0);
			public var frictiony:Number = new Number(0);
			
            public var maxVelocityz:Number = new Number(0);
            public var maxVelocityx:Number = new Number(0);
            public var maxVelocityy:Number = new Number(0);

            public var gravityz:Number = new Number(0);
            public var gravityx:Number = new Number(0);
            public var gravityy:Number = new Number(0);
            
            public var facing:uint;
            public var solid:String = "solid";
            
            public function Physics()
            {
                    _onGround = _onWall = false;
                    facing = WEST;
            }
            
            public function get onGround():Boolean { return _onGround; }
            public function get onWall():Boolean { return _onWall; }
            
            override public function update():void
            {
                    // Apply acceleration and velocity
                    velocityx += accelerationx;
                    velocityy += accelerationy;
                    velocityz += accelerationz;
                    applyVelocity();
                    applyGravity();
                    checkMaxVelocity();
                    applyFriction();
                    super.update();
            }
            
            public function applyGravity():void
            {
                    //increase velocity based on gravity
                    velocityx += gravityx;
                    velocityy += gravityy;
                    velocityz += gravityz;  
			}
            
            private function checkMaxVelocity():void
            {
                    if (maxVelocityx > 0 && Math.abs(velocityx) > maxVelocityx)
                    {
                            velocityx = maxVelocityx * FP.sign(velocityx);
                    }
                    
                    if (maxVelocityy > 0 && Math.abs(velocityy) > maxVelocityy)
                    {
                            velocityy = maxVelocityy * FP.sign(velocityy);
                    }
					
					if (maxVelocityz > 0 && Math.abs(velocityz) > maxVelocityz)
                    {
                            velocityz = maxVelocityz * FP.sign(velocityz);
                    }
            }
            
            private function applyFriction():void
            {
                    // If we're on the ground, apply friction
                    if (onGround && frictionx)
                    {
                            if (velocityx > 0)
                            {
                                    velocityx -= frictionx;
                                    if (velocityx < 0)
                                    {
                                            velocityx = 0;
                                    }
                            }
                            else if (velocityx < 0)
                            {
                                    velocityx += frictionx;
                                    if (velocityx > 0)
                                    {
                                            velocityx = 0;
                                    }
                            }
                    }
					
					if (onGround && frictiony)
                    {
                            if (velocityy > 0)
                            {
                                    velocityy -= frictiony;
                                    if (velocityy < 0)
                                    {
                                            velocityy = 0;
                                    }
                            }
                            else if (velocityy < 0)
                            {
                                    velocityy += frictiony;
                                    if (velocityy > 0)
                                    {
                                            velocityy = 0;
                                    }
                            }
                    }
                    
            }
            
            private function applyVelocity():void
            {
                    var i:int;
                    
                    _onGround = false;
                    _onWall = false;
                    
                    for (i = 0; i < Math.abs(velocityx); i++)
                    {
                            if (collide(solid, x + FP.sign(velocityx), y))
                            {
                                    _onWall = true;
                                    velocityx = 0;
                                    break;
                            }
                            else
                            {
                                    x += FP.sign(velocityx);
                            }
                    }
                    
				    for (i = 0; i < Math.abs(velocityy); i++)
                    {
                            if (collide(solid, x, y + FP.sign(velocityy)))
                            {
                                    _onWall = true;
                                    velocityy = 0;
                                    break;
                            }
                            else
                            {
                                    x += FP.sign(velocityy);
                            }
                    }
					
                    for (i = 0; i < Math.abs(velocityz); i++)
                    {
                            if (collide(solid, x, y + FP.sign(velocityz)))
                            {
                                    if (FP.sign(velocityz) == FP.sign(gravityz))
                                            _onGround = true;
                                    velocityz = 0;
                                    break;
                            }
                            else
                            {
                                    y += FP.sign(velocityz);
                            }
                    }
            }
            
            private var _onGround:Boolean;
            private var _onWall:Boolean;
            
    }

}

And here the hero

package  

{ import flash.geom.Point; import net.flashpunk.Entity; import net.flashpunk.utils.Key; import net.flashpunk.utils.Input; import net.flashpunk.graphics.Spritemap; import net.flashpunk.FP;

public class Hero extends Physics
{
	[Embed(source="../assets/sprGehen.png")]private const sprMori:Class;
	
	public var Mori:Spritemap = new Spritemap(sprMori, 31, 56);
	
	private static const kMoveSpeed:uint = 2;
	private static const kJumpForce:uint = 20;
	
	
	public var curAnimation:String = "sud_stehen";
	
	public function Hero(x:int, y:int) 
	{
		this.x = x;
		this.y = y;
		
		Mori.add("nord_stehen", [0], 0, false);
		Mori.add("nord_gehen", [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], 16, true);
		Mori.add("sud_stehen", [17], 0, false);
		Mori.add("sud_gehen", [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33], 16, true);
		Mori.add("west_stehen", [34], 0 , false);
		Mori.add("west_gehen", [35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50], 16, true);
		Mori.add("ost_stehen", [51], 0, false);
		Mori.add("ost_gehen", [52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67], 16, true);
		Mori.add("nord_springen", [17], 0, false);
		Mori.add("sud_springen", [0], 0, false);
		Mori.add("west_springen", [51], 0, false);
		Mori.add("ost_springen", [34], 0, false);
		
		
		graphic = Mori;
		setHitbox(28, 56);
		
		gravityz = 2.6;
		maxVelocityz = kJumpForce;
		maxVelocityx = maxVelocityy = kMoveSpeed * 2;
		frictionx = 0.7;
		frictiony = 0.7;
		
		type = "mori", "unit";
		
		Input.define("MoveLeft", Key.A, Key.LEFT);
		Input.define("MoveUp", Key.W, Key.UP);
		Input.define("MoveDown", Key.S, Key.DOWN);
		Input.define("MoveRight", Key.D, Key.RIGHT);
		Input.define("Jump", Key.SPACE);
	}
	
	
	override public function update():void
	{
		
		if (Input.check("MoveLeft"))
		{
			accelerationx = -kMoveSpeed;
		}
		
		if (Input.check("MoveRight"))
		{
			accelerationx = kMoveSpeed;
		}
		
		if (Input.check("MoveUp"))
		{
			accelerationy = -kMoveSpeed;
		}
		
		if (Input.check("MoveDown"))
		{
			accelerationy = kMoveSpeed;
		}
	
		//facedirection
		if (velocityx > velocityy)
		{
			if (velocityx > 0)
			{
				facing = OST;
			}
			else if (velocityx < 0)
			{
				facing = WEST;
			}
		}
		else if (velocityx < velocityy)
		{
			if (velocityy > 0)
			{
				facing = SUD;
			}
			else if (velocityy < 0)
			{
				facing = NORD;
			}
		}
		
	
		if (Input.pressed("Jump") && onGround && ((facing == NORD) || (facing == SUD)))
		{
			accelerationz = -FP.sign(gravityz) * kJumpForce;
			accelerationy = -FP.sign(gravityy) * kJumpForce;
		}
		else if (Input.pressed("Jump") && onGround && ((facing == OST) || (facing == WEST)))
		{
			accelerationz = -FP.sign(gravityz) * kJumpForce;
			accelerationx = -FP.sign(gravityx) * kJumpForce;
		}
	
		setAnimation();

		super.update();
		
		
	}
	
	private function setAnimation():void
	{
		var animation:String;
		
		if (facing == WEST)
			animation = "west_";
		else if (facing == OST)
			animation = "ost_";
		else if (facing == NORD)
			animation = "nord_";
		else
			animation = "sud_";
			
		if (onGround)
		{
			if ((velocityx == 0) && (velocityy == 0))
				animation += "stehen";
			else
				animation += "gehen";
		}
		else
		{
			animation += "springen";
		}
		
		Mori.play(animation);
	}
	
}

}


(christopf) #2

I think the case with the down-falling hero might be cause i have no solid ground like matt tuttle has in his demo. that makes sense in a 2d plattformer but for my plans it aint working. i guess i got to write something own :>


(Zachary Lewis) #3

I didn’t read your code, but I would like to have a little talk with you about jumping logic in a top-down game.

When working with a top-down game like this, the bulk of the jumping logic is merely to display to the user that the character is jumping. Unlike a simple side scroller, in a top-down game you never have to worry about platform height or banging your head — your character is either jumping or not jumping.

With that in mind, here’s the facts that come to mind when thinking about jumping in a top-down game:

  • When I jump, I am jumping for a set time.
  • If I am jumping, I cannot collide with ground tiles.
  • If I am jumping, I cannot attack.
  • If I am jumping, I cannot change my direction.
  • If I am jumping, I cannot jump.

Let’s put that logic into psuedocode, gameplay only. I’m not concerned with graphics if this logic doesn’t work.

public class TopDownJumpWorld extends World
{
  /**
   * Time (in frames) the player is in the air for after jumping.
   * This is a one second jump for a game running at 60fps.
   */
  public static const JUMP_LENGTH:uint = 60;

  /**
   * The direction the player was moving last frame.
   * This variable can be whatever type is best for you.
   */
  protected var _lastDirection;

  /** The amount of time the player has been jumping. */
  protected var _jumpTimer:uint;

  /** The player Entity. */
  protected var _player:Entity;

  public function TopDownJumpWorld()
  {
    // Constructor stuff.
  }

  override public function begin():void
  {
    // Create _player and add to world.
  }

  override public function update():void
  {
    if (_jumpTimer == 0)
    {
      // The player is not jumping.

      if (Input.pressed("MOVE"))
      {
        // Handle player movement in a direction.
        // Do this however you'd like, but make sure to
        // store the direction in case the player jumps.
        _player.move(currentDirection);
        _lastDirection = currentDirection;
      }
      else
      {
        // "Did not move" is also a possible direction.
        _lastDirection = null;
      }

      // Begin collision.
      if (_player.collideTypes(["hole", "spikes", "lava", "ground_enemy", "tall_enemy"], _player.x, _player.y))
      {
        // Handle colliding with damaging objects.
      }

      if (_player.collideTypes(["short_wall", "tall_wall", "conveyor_belt"]))
      {
        // Handle colliding with obstacles.
      }

      if (Input.pressed("ATTACK"))
      {
        // Handle player attack.
      }

      if (Input.pressed("JUMP"))
      {
        // Handle player jump.
        _jumpTimer = JUMP_LENGTH;
      }
    }
    else
    {
      // The player is jumping.

      // Continue moving in the direction the player is heading.
      _player.move(_lastDirection);

      // Begin collision.
      if (_player.collideTypes(["tall_enemy", "air_enemy"], _player.x, _player.y))
      {
        // Handle colliding with damaging objects.
      }

      if (_player.collideTypes(["tall_wall"]))
      {
        // Handle colliding with obstacles.
      }

      // Decrease the jump timer.
      _jumpTimer--;
    }

  }
}

This code makes some assumptions about your game layout, but it does cover all the facts mentioned earlier.

Now that jumping is working, you’ll want to visually indicate it to the player, probably by scaling the character to simulate a jump.

In the Zelda games, it’s more of a 3/4 view than a strict top-down. They jump by changing the size of the player’s shadow and by shifting the graphic along the y-axis while leaving the hitbox stationary.


(christopf) #4

Oh man, i really appreciate this. I’m not reading in detail now cause i got to care for my baby the next hours but i will later. I was just thinking about dropping the jump ability but now i’ve got a good feeling about this. thank you


(Ultima2876) #5

As a general rule, Zach’s approach to programming there is always a good way to go. Break the problem down into the smallest pieces you can - in this instance, his list of ‘rules’ for jumping, then tackle them one by one. Never fails.


(Zachary Lewis) #6

You heard it here first, folks.

“Zach’s approach to programming… never fails.” — @Ultima2876


(christopf) #7

thanks again, finally its working pretty fine!