In a platformer game built in godot, this is how I would go about making an enemy move autonomously from side to side (meaning he will walk up to a wall, collide with it, and turn around.
extends CharacterBody3D
var speed = 0.1
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
func _physics_process(delta):
if !is_on_floor():
velocity.y = gravity
velocity.x = speed
#collided is a boolean representing whether or not the character collided with a body or not
var collided = move_and_collide(velocity)
if collided:
speed *= -1
However, A problem arises with this code. since I have gravity which moves the enemy downwards the enemy collides with the floor, causing the collided
boolean to return true. Which causes the enemy to turn around rapidly, keeping him in place.
A solution I thought of is distinguishing between colliding with the floor and a wall so that I could have the enemy turn around only when colliding with a wall. However I am not sure how to do this. I am also wondering whether or not there is a better and easier to implement solution for this.
move_and_slide
There was here an answer by "user22159971" suggesting to use move_and_slide
and is_on_wall
, which is a good idea. However the explanation provided was for Godot 3. And "user22159971" removed the answer instead of updating it.
In Godot 3 you would have used move_and_slide
in a KinematicBody
like this:
move_and_slide(velocity, up_direction)
In Godot 4 you need to set the velocity
and up_direction
properties of the CharacterBody3D
like this:
velocity = something # you are already working with velocity, so keep that
up_direction = something_else # probably Vector3.UP
move_and_slide()
In either case, you could then check is_on_wall
to know if the body collided with a wall (or is_on_floor
or is_on_ceiling
to check for floor or ceiling repsectively):
if is_on_wall():
# it is a wall
pass
move_and_collide
Currently you are using move_and_collide
. Perhaps you want to continue using that.
Multiply by delta
First of all, the method move_and_collide
does not multiply internally by delta
, so use it like this:
move_and_collide(velocity * delta)
Since delta
is less than 1.0
(or you have bigger problems), this will result in the body moving slower than it did, so you might want to increase the velocity
to compensate.
Get the returned KinematicCollision3D
The method move_and_collide
returns a KinematicCollision3D
or null
if it didn't collide. It does not return a boolean.
Actually this code:
var collided = move_and_collide(velocity * delta)
if collided:
speed *= -1
Is equivalent to this code:
var collided:KinematicCollision3D = move_and_collide(velocity * delta)
if collided != null:
speed *= -1
I bring this up, because you could look into the properties of the KinematicCollision3D
to get more information, in particular the normal would allow you to distinguish if the body collided with a wall (or floor or ceiling):
var collision := move_and_collide(velocity * delta)
if collision == null:
# did not collide
pass
else:
var normal := collision.get_normal()
var angle := mormal.angle_to(Vector3.UP) # or whatever up you are using
if angle < floor_max_angle:
# it is a floor
pass
elif angle > (PI - floor_max_angle):
# it is a ceiling
pass
else:
# it is a wall
pass
Handle multiple collisions
I should further point out that obviously the body might have collided with multiple things. The last parameter of move_and_collide
allows you to tell Godot how many collisions to get. It is 1
by default. We can change that:
var collisions := move_and_collide(velocity * delta, false, 0.001, false, 32)
Here we are telling it to:
velocity * delta
distance.false
here means to not do a simulation test).0.001
(this is an extra margin distance for the collision, 0.001
is the default).false
here means we don't want to get those in the result).32
collisions (the thing we actually wanted to change, again 1
is the default).Now, we can iterate over the collisions:
var collisions := move_and_collide(velocity * delta, false, 0.001, false, 32)
if collisions == null:
# did not collide
pass
else:
for index in collisions.get_collision_count():
var normal := collision.get_normal(index)
var angle := mormal.angle_to(Vector3.UP) # or whatever up you are using
if angle < floor_max_angle:
# it is a floor
pass
elif angle > (PI - floor_max_angle):
# it is a ceiling
pass
else:
# it is a wall
pass