3

I am in the process of developing a 2D enemy that emulates the behavior of a slime, specifically jumping towards the player. I have been able to successfully implement this feature. However, upon further testing, I have observed that while the first jump executes in accordance with the intended behavior, subsequent jumps inexplicably occur in a direction opposite to the player's location. I would appreciate if you could help me solve this problem.

enter image description here

Jump Code here:

if is_on_floor():
        var player_pos = get_node("../Player").get_global_position()
        var self_pos = get_global_position()        
        var jump_distance = (player_pos - self_pos).normalized()
        var distance_to_player = player_pos.distance_to(self_pos)
        
        var horizontal_speed = min_speed + (distance_to_player / max_distance) * (max_speed - min_speed)
        horizontal_speed = min(max_speed, max(min_speed, horizontal_speed))
        
        velocity = jump_distance 
        velocity.x *= horizontal_speed * direction
        velocity.y = -jumpmove_height #-jump_height
    
        velocity = move_and_slide(velocity, Vector2.UP)
        
        $Position2D/SlimeSkin/AnimationPlayer.play("SlimePrototype Attack")
        $Jump_Cooldown.start()
    print("Do Jump")

All Code here: extends KinematicBody2D

var speed = 20

var min_speed = 0 #min_speed for jump horizontal speed
var max_speed = 250 #max_speed for jump horizontal speed
var max_distance = 300 #max distance for jump distance

var direction = 1

export var jumpmove_height : float
export var jump_time_to_peak : float
export var jump_time_to_descent : float

onready var jump_velocity : float = ((2.0 * jumpmove_height) / jump_time_to_peak) * -1.0
onready var jump_gravity : float = ((-2.0 * jumpmove_height) / (jump_time_to_peak * jump_time_to_peak)) * -1.0
onready var fall_gravity : float = ((-2.0 * jumpmove_height) / (jump_time_to_descent * jump_time_to_descent)) * -1.0

var velocity = Vector2.ZERO
var is_idle = true

func _ready():
    is_idle = true
    $Idle_Timer.start()
    pass

func get_gravity() -> float:
    return jump_gravity if velocity.y < 0.0 else fall_gravity

func _physics_process(delta):
    if velocity.x>0:
        $Position2D.scale.x=1
    elif velocity.x<0:
        $Position2D.scale.x=-1
    
    if is_on_wall() or not $Position2D/FloorRay.is_colliding() and is_on_floor():
        direction = direction * -1
    
    velocity.y += get_gravity() * delta
    if is_on_floor():
        velocity.x = 0
    velocity = move_and_slide(velocity, Vector2.UP)

    if is_idle:
        velocity.x = speed * direction
        if is_on_wall() or not $Position2D/FloorRay.is_colliding() and is_on_floor():
            direction = direction * -1
        velocity = move_and_slide(velocity, Vector2.UP)
        $Position2D/SlimeSkin/AnimationPlayer.play("SlimePrototype Idle")

func _on_Timer_timeout():
    if is_on_floor():
        var player_pos = get_node("../Player").get_global_position()
        var self_pos = get_global_position()        
        var jump_distance = (player_pos - self_pos).normalized()
        var distance_to_player = player_pos.distance_to(self_pos)
        
        var horizontal_speed = min_speed + (distance_to_player / max_distance) * (max_speed - min_speed)
        horizontal_speed = min(max_speed, max(min_speed, horizontal_speed))
        
        velocity = jump_distance 
        velocity.x *= horizontal_speed * direction
        velocity.y = -jumpmove_height #-jump_height
    
        velocity = move_and_slide(velocity, Vector2.UP)
        
        $Position2D/SlimeSkin/AnimationPlayer.play("SlimePrototype Attack")
        $Jump_Cooldown.start()
    print("Do Jump")

func _on_Idle_Timer_timeout():
    is_idle = false
    $Timer.start()
    $Position2D/SlimeSkin/AnimationPlayer.play("SlimePrototype Charge")
    print("IDLE TIMER TIMEOUT")
    pass # Replace with function body.

func _on_Jump_Cooldown_timeout():
    yield(get_tree().create_timer(0.5), "timeout")
    is_idle = true
    pass # Replace with function body.
DeruDeru
  • 65
  • 5

1 Answers1

3

The issue is direction. You are encoding twice the direction of the jump. Once in jump_distance (which, as I note below is actually a direction) and the other in direction. And you multiply them… So we have four cases:

+-------------------+--------------------------+------------------+
| moving direction  | direction to player      | result           |
+-------------------+--------------------------+------------------+
| (-1) moving left  | (-1) player to the left  | (+1) jumps right |
| (-1) moving left  | (+1) player to the right | (-1) jumps left  |
| (+1) moving right | (-1) player to the left  | (-1) jumps left  |
| (+1) moving right | (+1) player to the right | (+1) jumps right |
+-------------------+--------------------------+------------------+

So it only jumps in the direction to the player when it was moving to the right to begin with.

Use only one of the two directions. Either make it jump in in the direction it is moving (direction) or in the direction to the player.


Code review

I'm going to go over the jump code, and write what I notice, in the order I read it. Ok?


First:

var jump_distance = (player_pos - self_pos).normalized()

That jump_distance is not a distance. It is a unit vector (it is normalized) in the direction to the player. You can also write it like this:

var jump_distance = self_pos.direction_to(player_pos)

In fact, direction_to_player or jump_direction would be better names. However, given that you only use it here:

velocity = jump_distance

You can probably get rid of the variable:

velocity = self_pos.direction_to(player_pos)

Second:

var horizontal_speed = min_speed + (distance_to_player / max_distance) * (max_speed - min_speed)

That is a lerp. We can write it as such:

var horizontal_speed = lerp(min_speed, max_speed, distance_to_player / max_distance)

Third:

horizontal_speed = min(max_speed, max(min_speed, horizontal_speed))

That is a clamp. We can write it as such:

horizontal_speed = clamp(horizontal_speed, min_speed, max_speed)

Fourth:

velocity = self_pos.direction_to(player_pos) # this was jump_distance
velocity.x *= horizontal_speed * direction

This is the problem. You had already set the direction to the player in the first line. And then you multiply by direction in the second, which could flip the direction away from the player.

If you want to always jump in the direction it is moving, then do this:

velocity.x = horizontal_speed * direction

Alternatively, if you want it to be in the direction of the player, then do this:

velocity.x = sign(self_pos.direction_to(player_pos).x) * horizontal_speed

The reason why I'm taking the sign here, is because the direction also has a vertical component (it is a vector in the unit circle, its x component will only be 1 or -1 when the y component is 0).

Of course, you can make it move towards the player:

direction = sign(self_pos.direction_to(player_pos).x)
velocity.x = horizontal_speed * direction
Theraot
  • 31,890
  • 5
  • 57
  • 86
  • Firstly, I would like to express my gratitude for your response. Not only did you kindly point out the problem and offer a solution, but you also went above and beyond by conducting a code review, for which I am truly appreciative. Thanks to your assistance, I have learned a great deal. May I inquire about something? How can I consistently make the enemy character land at the player's position? Additionally, how can I make it bounce 2 or 3 times after landing? I would be grateful if you could provide guidance on this matter. – DeruDeru Feb 28 '23 at 14:32
  • @DeruDeru there are six ways to define a vertical jump. These are the combinations of choosing two of these six variables: Gravity, Time, Speed and Height. From those two you can compute the others (see the formulas for vertical throw). Usually beginners go with Gravity and Speed, but some other combination might be convenient. Regardless, you also need an horizontal velocity that will land on the target on the time that the jump takes, so you need to compute the time of the jump. With the time of the jump and the distance you compute the horizontal velocity. – Theraot Feb 28 '23 at 23:55
  • @DeruDeru for bouncing you decompose (using vector projection) the velocity as two vectors: one along the normal of the surface, and one perpendicular to the normal (these two vectors add up to the original velocity). The vector that is along the normal you flip, and add up with the other to have the new velocity. Now, if you know that the bounces are over an horizontal surface, then it is a matter of changing the sign of the y component. You might also introduce a factor to make the velocity smaller (so the bounce is smaller). Alternatively, make bounces other jumps. – Theraot Mar 01 '23 at 00:00