3
\$\begingroup\$

I wrote a simple script to control my 'Player' object:

using UnityEngine;
 using System.Collections;

 public class PlayerMovement : MonoBehaviour {

     public float jumpVelocity = 10;

     private Transform groundCheck;
     private Animator anim;
     private Rigidbody2D rigidbody2D;
     private bool grounded;
     private Physics2D gravity;

     [SerializeField] private LayerMask whatIsGround;

     const float groundedRadius = .2f;


     void Awake () {

         // Setting up references.
         groundCheck = transform.Find("GroundCheck");
         anim = GetComponent<Animator>();
         rigidbody2D = GetComponent<Rigidbody2D>();

     }

     // Use it for Graphic & Input
     void Update () {

     }

     //Physics and ground check
     void FixedUpdate () {

         grounded = false;

         Collider2D[] colliders = Physics2D.OverlapCircleAll(groundCheck.position, groundedRadius, whatIsGround);
         for (int i = 0; i < colliders.Length; i++)
         {
             if (colliders[i].gameObject != gameObject)
                 grounded = true;
         }

     }

     //Jump function
     public void Jump () {

         if (grounded) 
             rigidbody2D.velocity = jumpVelocity * Vector2.up;    
     }

     //Gliding function
     public void GlideON () {

         rigidbody2D.gravityScale = 1;
         Debug.Log ("Graviti is on");
     }

     public void GlideOFF () {

         rigidbody2D.gravityScale = 0;
         Debug.Log ("Graviti is off");

         if (!grounded) {
             rigidbody2D.velocity = ( -jumpVelocity / 10 ) * Vector2.up;
         }
     }

 }

To operate touch screen I used GUI function like this:

enter image description here

What I want to achieve:

  • One click - Player jump
  • Hold button - Player starts glide.
  • Player can not glide when he's grounded.
  • It does not matter whether I start from 'PointerClick' or 'PointerDown'. If player is grounded the first action should be 'Jump' always.
  • If we start with 'PointerDown', Player should jump and after reaching the maximum height he starts glide.
  • If we start with 'PointerClick' Player should jump.
  • If Player is not grounded and we want to start glide - we have to touch screen and hold it.

Where is the problem:

  • When I start with 'PointerDown', my Player begins infinite jump until 'PointerUp'.

I would be grateful for your help and suggestions.

\$\endgroup\$
1
\$\begingroup\$

One of the best solutions for managing behaviour like this is the state pattern. The problem with simply adding a bool for "can jump" is it can quickly turn into a mess as you add more and more behaviours and mechanics and more and more different combinations of what the player can and can't be doing. You're swinging down to bug central!

The state pattern resolves this by neatly keeping all the relevant information in the state it concerns. The first thing you'd do to achieve this is make a separate class for each state that extends an abstract base class (interface is probably better but I haven't done one in c# for a long while so I'm sticking to what I know):

public abstract class PlayerState
{
   public virtual void handleInput( ref PlayerMovement player);
   public virtual void update( ref PlayerMovement player);
}

and then each of your states would provide a special override for the special fun it gets up to:

public class PlayerStateJumping : PlayerState
{
   [SerializeField] private LayerMask whatIsGround;

   private Transform groundCheck;
   private Physics2D gravity;

   private float jumpVelocity;
   private float groundedRadius;

   public PlayerStateJumping()
   {
      groundedRadius = .2f;
      groundCheck = transform.Find("GroundCheck");
      player.setRigidBody2D(jumpVelocity * Vector2.up); 
      // Perform other necessary setup here 
   }

   public override void handleInput(PlayerMovement player)
   {
      if ( Input.getButtonDown("Glide") )
      {
         player.setState( new PlayerStateGliding() );
      }
   }

   public override void update(PlayerMovement player)
   {

     Collider2D[] colliders = Physics2D.OverlapCircleAll(groundCheck.position, groundedRadius, whatIsGround);
     for (int i = 0; i < colliders.Length; i++)
     {
         if (colliders[i].gameObject != gameObject)
         {
             player.setState(new PlayerStateIdle());
         }        
     }

   }
}

Now you can't jump again in the jump state, you can only move to the glide state or return to idle when you reach the ground. All the information specific to jumping is kept isolated as it shouldn't be tangling up the rest of the states.

Note This is just an example of what makes up the state pattern. This isn't perfect code you can just copy in (since you have a lot of different variables getting set up all over the place and I probably goofed it up somewhere Unity-wise.)

It would then free up the PlayerMovement To have a much more elegant class with no chance of confusion:

public class PlayerMovement : Monobehaviour 
{
    private Animator anim;
    private Rigidbody2D rigidbody2D;

    private PlayerState currentState; 

    void Awake()
    {
      anim = GetComponent<Animator>();
      rigidbody2D = GetComponent<Rigidbody2D>();
      currentState = PlayerStateIdle();
    }

    void Update() 
    {

    }

    void FixedUpdate()
    {
       currentState.Update();
    }

    void setState( ref PlayerState playerState )
    {
       this.currentState = playerState;
    }

}

This stops PlayerMovement getting dogpiled with a bunch of bools and nonsense that will end up being a bug breeding ground. Each state only knows what it needs to know and the transitions out are very explicit. Essentially this is a model of a Finite State Machine.

If you wanted to add functionality not related to player movement (e.g being able to shoot at an enemy) to stop the doubling up of states you'd probably make a PlayerShooting class and have a state in there as well.

\$\endgroup\$
0
\$\begingroup\$

You could remedy to this by adding another bool canJump :

public void Jump () {
       if(canJump) {
         if (grounded) 
             rigidbody2D.velocity = jumpVelocity * Vector2.up;
       canJump = false;    
    }
 }

So when you are clicking, the Jump() method will run the jumping loop one time.After this code, you will need to set the canJump boolean to true once he hits the ground again.

EDIT : By the way, using Event handlers isn't the most effective way you could use to handle Android/iOs touch events. You can simply add code to your script using Input.GetTouch which offers not just 2 phases (Down, Up) but 4-(5) phases of "Touching".

\$\endgroup\$
  • \$\begingroup\$ It's half - solution because still don't work exactly as I want...but it's next step forward. Thank you for showing the direction of development. \$\endgroup\$ – MaD May 19 '15 at 19:35
  • \$\begingroup\$ Hahah! I clearly said "After this code, you will need to set the canJump boolean to true once he hits the ground again." So that's the only other half left. You need to write down something by your own no? :) Especially if it's this easy \$\endgroup\$ – Zee May 19 '15 at 19:38
  • \$\begingroup\$ Even if its calling the function over and over it shouldn't jump repeatedly if your character isn't grounded, so I would take a look at your grounded code in FixedUpdate and see just when your character is being reported as grounded. You can also control the repeatDelay via the InputModule, setting it to 0.5f or so will give your character enough time to leave the ground before its called again. \$\endgroup\$ – Jeremiah Leslie Jun 17 '15 at 22:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.