In 2D Bullet Hell games, imagine a boss that spreads 36 bullets in a full 360 circle shape and all bullets will have the same velocity. We could do that by doing something like this pseudocode for loop:
for a = 0 to 360, increment by 10:
create bullet with velocity 100 at angle a
Back then before I know Godot, I used to do this on my own in 2D with the SOHCAHTOA trigonometry, and I would add the trigonometry right into the code and manipulate the positioning, velocity and angle directly as needed. For example, I could set center of the “explosion” to be a little far away like 8:08 in https://www.youtube.com/watch?v=JJgy5skcMJI
Now in Godot 4 in 3D, I would like to study and imitate something similar in 3D by spreading bullets all over in spherical shape. Something like this at 1:00 in https://www.youtube.com/watch?v=ac8z0YNkNwg . The boss launches missiles from all over its launchers, but for me, I just want to do a “spherical shape” like when a firework exploded “spherically”.
See these 2 images and imagine the “vertices” are missiles that will expand out:
Questions:
What would be the appropriate way to tackle this problem for Godot 4 in 3D?
Does Godot 4 have built-in 3D trigonometry functions for this? (ex. send in x1, y1, z1, angle 1 and angle 2, and get x2, y2, z2)
@tool
extends Node3D
@export var segs : int = 5 :
set(value):
segs = value
if Engine.is_editor_hint():
setup()
@export var rings : int = 5 :
set(value):
rings = value
if Engine.is_editor_hint():
setup()
@export var size : float = 1.0 :
set(value):
size = value
if Engine.is_editor_hint():
setup()
func _ready():
setup()
func setup():
var c = get_children()
for child in c:
child.queue_free()
place_bullets()
func create_bullet():
return preload("res://bullet.tscn").instantiate()
func place_bullets():
var bullet_vector = Vector3.UP * size
for ring in rings:
if ring == 0 or ring == rings - 1:
var bullet = create_bullet()
bullet.position = bullet_vector
add_child(bullet)
else:
bullet_vector = bullet_vector.rotated(Vector3.RIGHT,2*PI/rings)
for seg in segs:
var bullet = create_bullet()
bullet.position = bullet_vector
add_child(bullet)
bullet_vector = bullet_vector.rotated(Vector3.UP, 2*PI/(segs))
Assuming I have a shotgun that shoots 11 x 11 bullets out extremely wide at -45 degrees to 45 degrees on 2 axis from the reticle.
On my “shotgun weapon” object, I placed a Node3D (called extraAimPivot below) at my “gun” point where bullets will spawn , and a Marker3D placed very far away (called Marker3D_for_raycast below and is actually used for raycast elsewhere as well.
The extraAimPivot is like the beginning end of a very long stick and at the other end is the Marker3D. Whenever I rotate the extraAimPivot, the Marker3D will be displaced respectively to the extraAimPivot’s rotation.
When I fire the shotgun, I use 2 for loops for each bullet, and rotate the extraAimPivot accordingly below. After the rotation, Marker3D_for_raycast’s position will change with respect to the extraAimPivot’s rotation. Now, you can get the direction to it with the direction_to and you can use this to set the bullet’s velocity and use look_at_from_position to set bullet’s spawn position and set its angle toward Marker3D_for_raycast’s position as well. Afterward, add_child. Finally, reset extraAimPivot’s rotation, because Marker3D_for_raycast is to be put back at where the “shotgun” is actually aiming at.
var num_bullets_horizontal = 11
var num_bullets_vertical = 11
var bullet_create_at_position = gun.global_transform.origin
var target_position = hitMarker.global_transform.origin # assume this is where the shotgun's reticle is pointing at.
var bullet_speed = 60
for i in range(num_bullets_horizontal):
for j in range(num_bullets_vertical):
var bullet_instance = bullet.instantiate()
extraAimPivot.look_at(target_position)
extraAimPivot.rotate_y(deg_to_rad( 45 - (9*i) ))
extraAimPivot.rotate_x(deg_to_rad( 45 - (9*j) ))
var bullet_direction = bullet_create_at_position.direction_to(Marker3D_for_raycast.global_transform.origin)
bullet_instance.velocity = bullet_speed * bullet_direction
bullet_instance.look_at_from_position( bullet_create_at_position, Marker3D_for_raycast.global_transform.origin)
get_tree().get_root().add_child(bullet_instance)
extraAimPivot.rotation = Vector3.ZERO # reset the Node3D Pivot back.