How to prevent Bullet sliding against floor and objects

Godot Version

4.6

Question

How do I stop the bullets in my game from sliding across the floor or pushing objects? I want them to collide.

You’re going to need to give us more info. How did you make them? Area3D? RigidBody3D? CharacterBody3D? Please then post the code for the bullet.

Format it this way:

```gd
#Paste your code here and replace this line
```

It will look like this:

#Paste your code here and replace this line
1 Like

Oh, sorry, here it is. Its a CharacterBody3D

extends CharacterBody3D

@onready var _bullet_timer := $Timer
@onready var _bullet_sprite := $Sprite3D
@onready var damage_amount :=  10.0

@export var speed = 15.0
@export var health = 0.0


var damagable = false

func _process(delta):
	position += transform.basis * Vector3(0, 0, speed) * delta

func _physics_process(delta: float) -> void:
	move_and_collide(velocity)
	var collision = move_and_collide(velocity * delta)
	if collision:
		speed = 0.0
		var target = collision.get_collider()
		if target is CharacterBody3D and target.health > 0 and target.damagable:
			target.health -= damage_amount * _bullet_sprite.modulate.a
		queue_free()
	
	_bullet_sprite.modulate.a -= 0.01
	
	if _bullet_timer.is_stopped():
		queue_free()
	

move_and_collide() is supposed to be called only once per frame, preferably in _physics_process(). And if you do that, then there’s no need to update bullet’s position manually. Your code currently tries to move the bullet 3 times in a frame.

2 Likes

Um… so how do I do that? Like, isnt move_and_collide in _physics_process already here?

It’s there, meaning you should remove the other one as well as manual moving code you have in _process(). If you decided to move the bullet using move_and_collide(), only a single call to it is sufficient to perform the move.

2 Likes

The bullet still slides across the floor (like in the video). Thats what Im trying to fix. How do I prevent that?

Let’s see the code now.

1 Like
extends CharacterBody3D

@onready var _bullet_timer := $Timer
@onready var _bullet_sprite := $Sprite3D
@onready var damage_amount :=  10.0

@export var speed = 15.0
@export var health = 0.0


var damagable = false

func _physics_process(delta: float) -> void:
	position += transform.basis * Vector3(0, 0, speed) * delta
	move_and_collide(velocity)
	var collision = move_and_collide(velocity * delta)
	if collision:
		speed = 0.0
		var target = collision.get_collider()
		if target is CharacterBody3D and target.health > 0 and target.damagable:
			target.health -= damage_amount * _bullet_sprite.modulate.a
		queue_free()
	
	_bullet_sprite.modulate.a -= 0.01
	
	if _bullet_timer.is_stopped():
		queue_free()

You’re still calling move_and_collide() twice. Oh, you’re still doing everything you did before, just moved it in _physics_process().

Yo need to:

  • call move_and_collide() only once.
  • not manually update postition
2 Likes

You shouldn’t be using a CharacterBody3D. You are way overcomplicating things. If you want your bullets to ricochet, just use a RigidBody3D and play with the physics material on it until it does what you want. Then all you need to do is give it an initial impulse when it’s fired and have collisions detection code. No movement code necessary.

@normalized is correct about the problems with your code, but for what you’re trying to accomplish, you are making it really hard for yourself using a CharacterBody3D.

3 Likes

Is this what u mean?

extends CharacterBody3D

@onready var _bullet_timer := $Timer
@onready var _bullet_sprite := $Sprite3D
@onready var damage_amount :=  10.0

@export var speed = 15.0
@export var health = 0.0


var damagable = false

func _physics_process(delta: float) -> void:
	position += transform.basis * Vector3(0, 0, speed) * delta
	var collision = move_and_collide(velocity * delta)
	if collision:
		speed = 0.0
		var target = collision.get_collider()
		if target is CharacterBody3D and target.health > 0 and target.damagable:
			target.health -= damage_amount * _bullet_sprite.modulate.a
		queue_free()
	
	_bullet_sprite.modulate.a -= 0.01
	
	if _bullet_timer.is_stopped():
		queue_free()

It worked! thanks.

You are still updating the position rather than making use of the collision system properly, if you are going to move_and_collide with velocity you must set velocity to something.

func _physics_process(delta: float) -> void:
	velocity = transform.basis * Vector3(0, 0, speed)
	var collision = move_and_collide(velocity * delta)
3 Likes

Can you briefly describe your understanding of what move_and_collide() does?

1 Like

Since you seem to have decided to use CharacterBody3D, be forewarned - do not put too many of them on the screen. They are a heavier object than a RigidBody3D and too many on the screen, like with a bullet hell game, will make your game lag.

2 Likes

Not if you don’t call move_and_*() on them, especially move_and_slide(). Technically, you can still move them manually, but that of course defeats the purpose of using character bodies in the first place.

But I completely agree that using character bodies for bullets is in most cases not necessary. The best option for fast bullets is actually short-ish raycasts.

2 Likes

I gotta admit, all I know is it helps me register if a collision has happened.

Well the clue is kinda in the function name. It literally says what it does: move and collide. First it moves the body by a movement vector you pass as an argument, then it checks if the body collides with anything in its new position. If it does, it moves the body back a bit to a “safe” non-collided position and returns collision information back to you.

Knowing this, it becomes clear why you shouldn’t call it twice in a single frame or update body’s position by other means.

3 Likes

Agreed. The only reason I suggested RigidBody3D instead of a Raycast3D is I inferred OP wanted to see them on screen (based on the video) and wanted them to ricochet instead of bounce. If there’s no need for them to ricochet, a RayCast3D is the better way to go.