Need some help on randomizing deviation angle

Godot Version

4.3

Question

I am using these two functions for my Bullet scene right now:

func head_to(dest : Vector3, speed : float) -> void:
	velocity = speed
	var dv : Vector3 = dest - global_position
	var d : float = sqrt( dv.x*dv.x + dv.y*dv.y + dv.z*dv.z )
	
	velocity_vector.x = dv.x/d * velocity
	velocity_vector.y = dv.y/d * velocity
	velocity_vector.z = dv.z/d * velocity
	
	look_at_from_position( global_position , dest )

func _physics_process(delta: float) -> void:
	global_position += velocity_vector*delta

In this case, point A will be the global_position of the Bullet, and it will travel to point B (the parameter dest) at speed of parameter speed. We will calculate for the velocity_vector and apply delta in _physics_process. All of these are working validly already.

But I would like to add one more thing: deviation. I know point A and point B but the gun could have a bad deviation and when you shoot it, the bullet could have a randomized deviation angle of -5 to 5 degrees for every Bullet, for example. So how could this be accomplished mathematically?

NOTE: adding extra randomized x,y,z values on dest is NOT what I am looking for. I would like to be able to control the exact range of deviation angle of the bullet being shot.

See below my little demo and explanation of how this can be achieved.

This is my main scene structure:

This is the bullet scene structure:

This is the script on the Player node:

extends Node3D


@export var camera: Camera3D
@export var bullet_scene: PackedScene
var new_bullet: RigidBody3D
var shooting_interval: float = 0.05
var shooting_direction: Vector3 = Vector3.FORWARD
var shooting_force: float = 100.0
var max_angle_deviation: float = 5.0


func _ready() -> void:
	await get_tree().create_timer(2.0).timeout
	var tween: Tween = create_tween().set_loops()
	tween.tween_callback(shoot)
	tween.tween_interval(shooting_interval)


func shoot() -> void:
	var horizontal_angle_deviation = randf_range(-deg_to_rad(max_angle_deviation), deg_to_rad(max_angle_deviation))
	var random_rotation = randf_range(0.0, TAU)
	var shooting_direction_with_deviation = shooting_direction.rotated(Vector3.UP.rotated(Vector3.BACK, random_rotation), horizontal_angle_deviation).normalized()
	
	new_bullet = bullet_scene.instantiate()
	get_tree().current_scene.add_child(new_bullet)
	new_bullet.global_position = camera.global_position
	new_bullet.global_position.y = camera.global_position.y / 2
	new_bullet.apply_impulse(shooting_direction_with_deviation * shooting_force)

This is the script on the Bullet node:

extends RigidBody3D


@export var collision_shape: CollisionShape3D


func _ready() -> void:
	body_entered.connect(_on_body_entered)


func _on_body_entered(_body: Node) -> void:
	disable_collisions.call_deferred()


func disable_collisions() -> void:
	collision_shape.disabled = true
	freeze = true

How this works is that the initial shooting_direction vector (Vector3.FORWARD) is rotated horizontally within the ± max_angle_deviation restrictions around a random axis calculated with the random_rotation angle.
The rest of the code is just boiler plate code for the demo video.
You might still need to adjust it to fit your project, but maybe this will nudge you into the correct direction.

Hi @wchc ,

Thank you for this, but unfortunately, I am not using RigidBody3D for performance reasons. All of my bullets are Area3D, so apply_impulse does not exist in that class. This is why I am moving all those Bullets myself in _physics_process .

Do you appear to know the math for applying the deviation into the velocity_vector for above?

My code should be universally applicable regardless if you use RigidBody3D, or any other Node, just reuse this part to calculate the shooting_direction_with_deviation vector and apply it to your bullet.

var shooting_direction: Vector3 = Vector3.FORWARD
var max_angle_deviation: float = 5.0

func shoot() -> void:
	var horizontal_angle_deviation = randf_range(-deg_to_rad(max_angle_deviation), deg_to_rad(max_angle_deviation))
	var random_rotation = randf_range(0.0, TAU)
	var shooting_direction_with_deviation = shooting_direction.rotated(Vector3.UP.rotated(Vector3.BACK, random_rotation), horizontal_angle_deviation).normalized()

In your code I think you can implement it as follow, but without knowing your full scene structure I’m not 100% sure this will work correctly. If there are any issues with this, let me know and we can try to debug.

var max_angle_deviation: float = 5.0
var shooting_direction_with_deviation: Vector3

func head_to(dest : Vector3, speed : float) -> void:
	var shooting_direction: Vector3 = (dest - global_position).normalized()
	
	var horizontal_angle_deviation = randf_range(-deg_to_rad(max_angle_deviation), deg_to_rad(max_angle_deviation))
	var random_rotation = randf_range(0.0, TAU)
	shooting_direction_with_deviation = shooting_direction.rotated(Vector3.UP.rotated(Vector3.BACK, random_rotation), horizontal_angle_deviation).normalized()
	
	velocity_vector = shooting_direction_with_deviation * speed
	look_at_from_position(global_position, dest)

func _physics_process(delta: float) -> void:
	global_position += velocity_vector * delta

A side note - using Area3D and changing the global_position directly might not be the best idea for managing bullets, because if your bullet is flying fast enough or your game lags for a second, it may miss the target by simply teleporting behind the target without registering collision.
I would still recommend you to use RigidBody3D, or CharacterBody3D, as these have tools that can ensure the collision is registered.

1 Like

Hi @wchc ,

Your code on random deviation does indeed work. Thank you :slight_smile:

Regarding the game lagging, I did consider that too. This is why I am using _physics_process function which runs every 16 ms instead of _process. I have tried spawning multiple bullets against a single target on both 1000 fps and 10 fps , and I see that for both cases, no bullets went through the target.

In fact, I used to ask this question before here: Can objects "go through" walls in a very low framerate? , and I already got my answer, but at the same time, while RigidBody is recommended for this case, this statement here from my link is a bit of a concern:

CastRay will use a raycast to detect passthorugh.
And Cast Shape will use a shapecast that is more precise, but much more expensive

I didn’t do a thorough measurement yet. (1000 RigidBody3D bullets VS 1000 Area3D bullets) But at this moment, I am thinking using Area3D with _physics_process above should be computationally less expensive than RigidBody3D. Or do you have an experience you would like to share? :slight_smile:

But do you need this performance? In general, you shouldn’t try to optimize the game if you don’t need to.
I couldn’t argue with you if you said “rigidbodies lagged my game too much, so I decided to use areas instead”.
But here, if you decided to use areas without having issues with rigidbodies in the first place, I’d say this might have been a bad call. It’s because this decision while maybe having a slight improvement of performance (which you have not measured, and I would guess there is no visible difference in performance), it has its own additional drawbacks, which you need to take into consideration now.

1 Like

@wchc , I see. I guess I will do some more tests for both cases and see then.

1 Like