Creating Bullet Hells in 3D in Godot 4

Godot Version

4.2.1

Question

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:

image

image

Questions:

  1. What would be the appropriate way to tackle this problem for Godot 4 in 3D?
  2. 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)

I don’t think you need trig. Let’s say I want 5 levels.

I would put a vector straight up and place one bullet.

Then rotate the vector at Pi/4 and rotate in place. placing N bullets at a frequency of 2Pi/F.

Then put the vector at Pi/2, do the same thing

Then put the vector at 3pi/4, …

Then put one bullet at Pi.

I would want to understand the circumference of the shape to determine the the placement density. So it will become 2CĎ€/f.

I’m sure there are probably better algorithms out there to find point on a surface. You could also use a mesh and look at vertex coordinates.

@pennyloafers would you mind provide some sample codes for that? I’m not sure which functions to go for.

If I remember right, Theres an addon that helps with that. Look for bullet hell addons for godot

@tool
extends Node3D

@export var segs : int = 5 :
	set(value):
		segs = value
		setup()
@export var rings : int = 3 :
	set(value):
		rings = value
		setup()

@export var size : float = 1.0 :
	set(value):
		size = value
		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 mesh = SphereMesh.new()
	mesh.radial_segments = segs
	mesh.rings = rings
	mesh.height = size
	mesh.radius = size/2
	var vertexes = mesh.get_mesh_arrays()[Mesh.ARRAY_VERTEX]
	for vertex_position in vertexes:
		var bullet = create_bullet()
		bullet.position = vertex_position
		add_child(bullet)

1 Like
@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))
		
		

I think this has an off by one bug. but you get the picture

1 Like

Thank you @pennyloafers for various suggestions.

I also found another alternative on my side too:

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.
1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.