Trouble with emulating a mouse-based shooting mechanic in 3D top-down space

Godot Version

4.6

Question

I’ve delved a bit into raycasts and projecting a ray onto 3D space in order to get mouse interaction in 3D space. I’ve managed to do this so far, and get an object to rotate itself based on the mouse position. So after that I decided to see how people were getting a bullet object to spawn at a given position in 3d space hoping I could somehow get the bullet to accelerate in the direction of my mouse, but that proved to be more confusing than I thought. I tried following an FPS shotting mechanic, but realized that it was based off of the camera transformation and nothing else, so I was a bit defeated. Then I tried to get the cursor_pos_plane variable I made below to direct the direction of the bullet, however no avail.

The reason I couldn’t do it based off of the camera of course, unsuprisingly, is because I have the game in a 3rd person prespective, with an orothogonal camera. So its been a bit tricky. But this is my ShootingComponent script that contains the mouse raycasting and everything else I tried.

class_name ShootComponent extends Node

@onready var pos := %BulletSpawnPointer

@onready var camera: Camera3D = $"../Camera3D"
@onready var player = get_parent()

const BULLET = preload("res://Scenes/projectile.tscn")

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
	
		
	#print(shoot_origin)
	screen_point_to_ray()
		
func screen_point_to_ray():
	var target_plane_mouse = Plane(Vector3(0,1,0),player.position.y)
	
	#var space_state = player.get_world_3d().direct_space_state
	var mouse_pos = get_viewport().get_mouse_position()
	#var cam = get_tree().root.get_camera()
	var ray_origin = camera.project_ray_origin(mouse_pos)
	var ray_normal = camera.project_ray_normal(mouse_pos)
	var cursor_pos_plane = target_plane_mouse.intersects_ray(ray_origin, ray_normal)
	pos.look_at(cursor_pos_plane, Vector3.UP,0)
	
	
	var shoot_origin = pos.global_position
	if Input.is_action_just_pressed("shoot"):
		var instance = BULLET.instantiate()
		
		#instance.position = pos.position
		instance.position = pos.global_position
		instance.transform.basis = pos.global_transform.basis
		
		add_child(instance)
		
		

In case I was misunderstood, this code does not do what I want it to, specifically the instance.positionand instance.transform.basis lines, where I tried to solve the issue.

Start by putting a debug sprite at the plane/mouse intersection to check if that position is what you expect it to be.

Hm, I did this, and I see an odd offset of the debug mesh from my mouse.

Can you post a video and the exact code?

func screen_point_to_ray():
	var target_plane_mouse = Plane(Vector3(0,1,0),player.position.y)
	
	#var space_state = player.get_world_3d().direct_space_state
	var mouse_pos = get_viewport().get_mouse_position()
	#var cam = get_tree().root.get_camera()
	var ray_origin = camera.project_ray_origin(mouse_pos)
	var ray_normal = camera.project_ray_normal(mouse_pos)
	var cursor_pos_plane = target_plane_mouse.intersects_ray(ray_origin, ray_normal)
	pos.look_at(cursor_pos_plane, Vector3.UP,0)
	
	mousepointer.position = cursor_pos_plane

The mousepointer variable holds a reference to the debug mesh, in the video its represented by a red sphere as you’ll see.

Assign to global_position, not position.

I see. This worked out okay.

just to mention, I haven’t fully figured this problem out yet. Only managed to confirm the plane is at the right position. I’m unsure how to proceed with the rotation of the bullet object, which currently does the same as when I made this post.

Post the current version of the code that correctly places the debug sprite.

class_name ShootComponent extends Node

@onready var pos := %BulletSpawnPointer
@onready var mousepointer: CSGSphere3D = $"../Mousepointer"



@onready var camera: Camera3D = $"../Camera3D"
@onready var player = get_parent()

const BULLET = preload("res://Scenes/projectile.tscn")

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
	
		
	#print(shoot_origin)
	screen_point_to_ray()
		
func screen_point_to_ray():
	var target_plane_mouse = Plane(Vector3(0,1,0),player.position.y)
	
	#var space_state = player.get_world_3d().direct_space_state
	var mouse_pos = get_viewport().get_mouse_position()
	#var cam = get_tree().root.get_camera()
	var ray_origin = camera.project_ray_origin(mouse_pos)
	var ray_normal = camera.project_ray_normal(mouse_pos)
	var cursor_pos_plane = target_plane_mouse.intersects_ray(ray_origin, ray_normal)
	pos.look_at(cursor_pos_plane, Vector3.UP,0)
	
	mousepointer.global_position = cursor_pos_plane
	
	var shoot_origin = pos.global_position
	if Input.is_action_just_pressed("shoot"):
		var instance = BULLET.instantiate()
		
		add_child(instance)
		
		#instance.position = pos.position
		instance.global_position = pos.global_position
		instance.transform.basis = player.global_transform.basis

So what exactly is still not working as expected, look_at()?

With things like this, you always want to use global instead of local transform. Assign to instance.global_transform.basis or instance.global_basis

The transformation of instance. I’m not sure if I did so correctly, but I tried to make its global transform based on pos, which successfully looks at my mouse position. However, when trying to apply the global transform basis of pos to the instance variable (the bullet scene) , it does not “look” in the direction of my mouse like pos does when it accelerates outwards. My goal is to get it to accelerate outwards facing where my mouse is positioned from the origin.

Your code assign to instance.transform.basis. Assign to instance.global_basis instead, or just call intance.look_at().

Like this?

instance.global_position = pos.global_position
instance.global_basis = pos.global_transform.basis
instance.look_at(cursor_pos_plane, Vector3.UP)

I have tried these, I also tried to add the transforms within the instance object myself, but its not really yielding any new results.

What exactly do you want instance to do?

I’m trying to make it so that when you click a certain input, the instance will accelerate at a given speed (this is handled in the bullet object/instance itself, where I add a Vector3 to the global position) in the direction of where the mouse is on the screen from where it spawns (in this case the origin is in the middle of the player) I thought the look_at function might do this, but if I had to guess it only rotates towards the mouse from the instance’s origin and not the player itself?

I tried to change the origin, but it seems like I’m unable to access this property on whichever objects I’m trying to apply it to (that being the instance)

I asked what do you want it to do. Just describe how does it look from the player perspective. No origins, transforms and vectors.

From the players prespective, the instance shoot towards the mouse. I tried to make a visual of what I’m trying to do

Where should the instance first appear? At player position?

When you tried this, what happened? Describe in detail.
Did the bullet spawn where it’s supposed to?
Did the bullet move to where it’s supposed to, but the rotation of the bullet was wrong?
Or did it move in the wrong direction?