Search Unity

Question Giving a boost to a character

Discussion in '2D' started by JagGentlemann, Nov 27, 2022.

  1. JagGentlemann

    JagGentlemann

    Joined:
    Feb 10, 2016
    Posts:
    25
    Hello, I'm doing a 2D game where the main character can go left and right, and for this I'm using body.velocity. The thing is that I also want a button that gives them a boost to one of the directions, and I do this with AddForce2D.
    The problem is that with body.velocity I'm telling the character that he must be this exact velocity number, so the boost doesn't apply, unless you don't touch any button, because then there isn't any specific velocity to follow. This is important because I want to be able to jump while boosting, so the jump is also longer. If I jump while holding a direction, the character will just stop when the boost ends. Next there's the code I'm using (which I know it isn't the most optimal, I might change things when this works).

    Character movement:
    Code (CSharp):
    1. if (boostsc.boosting == false)
    2.             {
    3.                 if(Input.GetAxis("Horizontal")>0.95f) {
    4.                     // Movimiento a la derecha
    5.                     mov=1;
    6.                     move = mov * speed;
    7.                     body.velocity = new Vector3(move, body.velocity.y, 0.0f);
    8.                 } else if (Input.GetAxis("Horizontal")<-0.95f) {
    9.                     // Movimiento a la izquierda
    10.                     mov=-1;
    11.                     move = mov * speed;
    12.                     body.velocity = new Vector3(move, body.velocity.y, 0.0f);
    13.             } else {
    14.                 move = 0;
    15.             }
    Boosting:
    Code (CSharp):
    1.  
    2.         body.AddForce((Quaternion.AngleAxis(angle, Vector3.forward)*Vector3.right) * 0.5f, ForceMode2D.Impulse);
    3.  
    There's more code but I think this is the essential part.
     
  2. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,455
    Why can't the "boost" just add to the existing velocity rather than just stomp over it? After all, an impulse is just that; instantly adding something to the velocity.

    NOTE: You keep using Vector3 but it's 2D physics, it's Vector2. Creating a Vector3, using 0 as Z then just letting unity implicitly convert it down to a Vector2 is wasteful.

    Also, you need to be careful modifying things in physics per-frame because like anything else, this'll make it a frame-dependent change. Higher frame-rates, more change etc.
     
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,688
    You can't really mix forces and velocities. Force can cause a velocity to change. Setting a velocity makes the force irrelevant.

    One approach is to just use a simple cooldown timer to allow the boost (usually called a dash) to operate over a short period of time and not be interrupted by inputs.

    Cooldown timers, gun bullet intervals, shot spacing, rate of fire:

    https://forum.unity.com/threads/fire-rate-issues.1026154/#post-6646297
     
  4. JagGentlemann

    JagGentlemann

    Joined:
    Feb 10, 2016
    Posts:
    25
    That's the first thing I tried to do, but I don't know how to do it well. When I try to do a simple sum, it goes crazy, as if the values are super high, when in reality the character has like velocity.x = 4 and 11 when is boosting.

    Yes, I should be careful about that. I made this into a FixedUpdate, not sure how safe it is.

    I'm naming it boost because he's using a jet pack, but yeah, maybe dash would be easier when asking for help.
    What you are telling me is done, I have a canMove bool, the thing is that the Horizontal input still has values if the button is being pressed, no matter if the character is allowed or not to move. So I'm not sure how could I do that.
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,688
    Awesome! Time to start debugging then to find out what the problem is. Here's how to get started:

    You must find a way to get the information you need in order to reason about what the problem is.

    Once you understand what the problem is, you may begin to reason about a solution to the problem.

    What is often happening in these cases is one of the following:

    - the code you think is executing is not actually executing at all
    - the code is executing far EARLIER or LATER than you think
    - the code is executing far LESS OFTEN than you think
    - the code is executing far MORE OFTEN than you think
    - the code is executing on another GameObject than you think it is
    - you're getting an error or warning and you haven't noticed it in the console window

    To help gain more insight into your problem, I recommend liberally sprinkling
    Debug.Log()
    statements through your code to display information in realtime.

    Doing this should help you answer these types of questions:

    - is this code even running? which parts are running? how often does it run? what order does it run in?
    - what are the values of the variables involved? Are they initialized? Are the values reasonable?
    - are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

    Knowing this information will help you reason about the behavior you are seeing.

    You can also supply a second argument to Debug.Log() and when you click the message, it will highlight the object in scene, such as
    Debug.Log("Problem!",this);


    If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

    You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

    You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

    You could also just display various important quantities in UI Text elements to watch them change as you play the game.

    If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer or iOS: https://forum.unity.com/threads/how-to-capturing-device-logs-on-ios.529920/ or this answer for Android: https://forum.unity.com/threads/how-to-capturing-device-logs-on-android.528680/

    Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

    Here's an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

    https://forum.unity.com/threads/coroutine-missing-hint-and-error.1103197/#post-7100494

    When in doubt, print it out!(tm)

    Note: the
    print()
    function is an alias for Debug.Log() provided by the MonoBehaviour class.
     
  6. JagGentlemann

    JagGentlemann

    Joined:
    Feb 10, 2016
    Posts:
    25
    Thanks for the tips, but I already know where's the problem after some testings.
    Code (CSharp):
    1. body.velocity = new Vector3(move, body.velocity.y, 0.0f);
    That line is the culprit, that's for sure, because the "move" variable is overwritting the physics of the impulse. With my conditions, this overwrite happens any time left or right is pressed, so what I have to do is to force the Horizontal Axis to 0 or, even better, add the impulse to the velocity that is already there. The problem is that I don't know how to do one nor another.
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,688
    I agree. That's why I wrote this in my first post:

    I also suggested a workaround that I have successfully used to implement dash-like functions: a cooldown timer that inhibits input while dashing.

    This does not sound like a timer to me:

     
  8. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,455
    Velocity isn't something special. If you did the same to a float, it's work the same. If you add some value to it per-frame at 200 fps then it'll get 200 times that amount added. To stop it being frame-rate dependent, you'd typically scale it by the elapsed time.

    Like this:
    Code (CSharp):
    1. distance += speed * Time.deltaTime;
    You can simply move code to FixedUpdate for sure and if you don't change the rate of FixedUpdate then it'll always be the same rate unlike the variable frame-rate but it won't be in something universal such as seconds.
     
  9. JagGentlemann

    JagGentlemann

    Joined:
    Feb 10, 2016
    Posts:
    25
    Okay, so I've tried Kurt's solution, but everything remains the same, I might've implemented it wrong. Anyways, I'm not sure a cooldown is exactly what I need, the problem is that just when the boost finishes, any momemtum is lost because the velocity is now in control of the inputs, and I want the inputs at that time, just with some momemtum from the boost.

    Melv has given me an idea: making the sum of the velocity via input + velocity via the boost. I can use a float that is decaying over time to give the effect I was looking for. I can also limit the speed to the boost + pressing left or right isn't too much.

    Right now it's working pretty well, I think I can get a good effect my changing some numbers. I have a problem where you can go to the opposite direction to the boost after it's finished and the character goes faster that it should, but I think it won't be that difficulty to fix. But that's for another day, I have to clear my head. Thanks for the help.
     
  10. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,688
    Sounds like you didn't do it correctly. It works 100% of the time when done correctly.

    As I said, have a cooldown timer (see links above)

    Code (csharp):
    1. private float boostTimer;
    When you boost, set it nonzero:

    Code (csharp):
    1. if (userWantsToBoost)
    2. {
    3.   boostTimer = 0.5f;  // how long to boost
    4. }
    In Update(), count it down and act on it:

    Code (csharp):
    1. float movementSpeed = 5.0f; // assume normal walk speed
    2.  
    3. if (boostTimer > 0)
    4. {
    5.   boostTimer -= Time.deltaTime; // count down
    6.   movementSpeed = 15.0f;   // overwrite with boost speed
    7. }
    8.  
    9. // TODO: use movementSpeed to move
    10.  
    It's really THAT SIMPLE. I know people want it to be more complicated but it just isn't.
     
  11. JagGentlemann

    JagGentlemann

    Joined:
    Feb 10, 2016
    Posts:
    25
    What I have now is more or less the same idea. What I understand from your code is that the movement speed is increased from 5 to 15 for X seconds. What I want is not exactly that, actually is a mix of the solutions you both offered to me. I'm using the sum of velocities and also -= Time.deltaTime.

    I'm sorry if I still don't understand what you are trying to tell me, programming isn't my strong suit, but I think I got what I was looking for inspired by your suggestions.
     
    Kurt-Dekker likes this.