0
\$\begingroup\$

I'm working on a 2D top-down game (like the picture below) and am wondering what can I do for "jumping"

Legend of Zelda - Link's awakening

So far, I've done these:

  • adding two events in jumping animation (one for when the enemy is in the air and one for when it's coming back to the ground)
  • the events turn enemy's colliders off and on

And it worked well, the enemy dodged the attacks and all but upon more testing, I found out some serious issues:

  1. the enemy can pass objects like trees but it shouldn't
  2. explosions should affect enemies even if they are on air but without any colliders, it is not possible

So what I want to know is how I can add "jumping" to my game? Is there any "standard" way to do this in top-down games? I don't want a piece of code or something like that, just an explanation of how I can achieve this is enough. Thanks in advance!

\$\endgroup\$
2
  • 2
    \$\begingroup\$ docs.unity3d.com/Manual/LayerBasedCollision.html You could make a jump layer that is still affected by things like explosions but turns immun to collision with enemies (since you dont collide with them) and still get stuck against hard objects like trees/ buildings \$\endgroup\$
    – Zibelas
    Mar 21 at 18:47
  • \$\begingroup\$ Oh man, that's perfect! I already have my enemies on an enemies layer and have used layer collision matrix but didn't think about adding an enemies_onair layer. thanks for the link! \$\endgroup\$
    – Arian_ki
    Mar 21 at 18:51

2 Answers 2

2
\$\begingroup\$

So, the way jumping works in Zelda is kind of unintuitive at first, but if you think about it, it makes a lot of sense.

In modern games, sprites and models ARE the game objects, so we've become used to manipulating the thing as a whole whenever we need to do something. But in older games, sprites are only a visual representation of what is going on in the background.

Whenever you jump in Zelda, a flag (bool) gets set to indicate that the player is in the air, but the collider isn't affected. Whenever that "in air" flag is set, the player collider simply ignores the effects of other colliders like pits or enemies, but the collider still moves along the ground like normal.

While that's happening in the background, the sprite is just being moved up and down in a way that looks like jumping, while still following the position of the collider underneath, but it has no actual impact on what is happening in the background.

So, what I'm saying is that you should separate the collider from the sprite, then add your jumping animation to the sprite, and an "in air" bool to your collider interactions.

Though depending on how you've set up your code it might be easier to switch the object to a different layer.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ This is an excellent answer -- multiple colliders is definitely the best solution, IMO too. \$\endgroup\$
    – ROGRat
    Mar 23 at 5:41
  • 1
    \$\begingroup\$ Actually, I'm doing something similiar to that, the enemies just visualy go up but not the collider. though, I don't think in the Zelda case it's just a simple flag. As I recently re-played it, I found out that it's a bit hard to get flying objects ()like hearts) and needs a presice jump so I guess they used a variable to hold height and gave the objects a height too \$\endgroup\$
    – Arian_ki
    Mar 23 at 13:22
  • \$\begingroup\$ @ArianKeshvari You could also add another collider that does follow the sprite. \$\endgroup\$
    – basklein
    Mar 24 at 12:31
1
\$\begingroup\$

For prototyping purposes, you could always use a coroutine. It will get things moving, but I would try to structure your code so physics are in a fixed update, eventually.

A coroutine is basically a process that runs on the side. It gives you enough flexibility to get the jump working while you fix your enemies. Be careful with coroutines though, I have seen some get way out of hand. You don't want too many, or too much going on in one. Memory leaks can happen here.

This is not how I would handle a jump, but it works. I would consider utilizing a state pattern for your character (even a small pseudo pattern could work.) This is not a terrible approach to a jump, though.

I am not in a place where I can test this, so this is just an example off the top of my head to show you what the syntax would look like.

void Update(){
  HandleInput(this);
}

private void HandleInput(IControllable controller){
  ...
  if(controller.jump)
    StartCoroutine(HandleJump());
}

// jumpHeightVector is a vector 3 with y being your jump height applied, the rest 0
private Vector3 jumpHeightVector = (0, jumpHeight, 0);

// every 10th of a second increments current time by 1
private float jumpInterval = 0.1f; // this would be responsible for a choppy movement
[SerializeField] jumpTime; //serialize this to show in inspector


private IEnumerator HandleJump(){

  // Important: let update know that this method is off limits
  controller.jump = false;
  anim.SetBool("jump", true);
  curJumpTime = 0;

  while(curJumpTime < jumpTime)
  {
    
    // may need better validation for landing in the right spot
    if(curJumpTime * 2 < jumpTime) Transform.Translate(jumpHeightVector); // up
    else Transform.Translate(-jumpHeightVector); // down

    // this always creates a new wait for seconds, consider assigning one of these in Start()
    yield return new WaitForSeconds(jumpInterval);

    curJumpTime++;
  }
  anim.SetBool("jump", false);
}

We have a coroutine that loops through a while loop while current jump time curJumpTime is less than our allowed jump time jumpTime . if we are less than half of the way through our jump, move up. If we are less than half, move down. When our elapsed jump time is equal to our allowed jumptime, we break the while loop. We also set our animation bool (you could even just use anim.Play("jump").

\$\endgroup\$
3
  • \$\begingroup\$ Thanks for the answer and good explanation! But what others mentioned (changing layer) is way easier and, probably, more efficient \$\endgroup\$
    – Arian_ki
    Mar 22 at 22:51
  • \$\begingroup\$ While it may be easier, I disagree with the latter. Good luck :) \$\endgroup\$
    – Mike
    Mar 23 at 4:40
  • \$\begingroup\$ Remember that WaitForSeconds resumes on the first frame after that many seconds has elapsed, which could be anywhere from 0 ms to 33 ms later at 30 fps. This will make the jump inconsistent, sometimes running longer or shorter, or looking jerky if different steps in the jump get different delays based on how they mismatch against the frame timings. You'll likely want to use yield return null instead to resume every frame, and use Time.deltaTime to adjust the jump update to match how much time has actually passed, to smoothly match whatever the framerate conditions happen to be. \$\endgroup\$
    – DMGregory
    Apr 11 at 10:55

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .