I’ve been working on a dungeon crawler, using buttons on-screen to move around, so a point-and-click game with a 3D environment. I used tweens in order to get the movement. However, not sure if its the tweens, but even though I’ve added collisions to 3D objects and added gravity to the CharacterBody 3D being effected, gravity nor collisions are affecting it, and I instead move through walls and do not fall if I am not on a floor.
This is the code. The signals are connected to 5 buttons on-screen. I can provide additional code or screenshots if needed.
extends CharacterBody3D
@onready var camera := $"Camera-z"
const SPEED = 5.0
var tween
var dur = 0.3
@onready var rayfront := $RayFront
@onready var rayback := $RayBack
@onready var audio: AudioStreamPlayer2D = $"../../../../../SoundEffects"
func _physics_process(delta: float) -> void:
# Add the gravity.
if not is_on_floor():
velocity += get_gravity() * delta
pass
if rayfront.is_colliding() or rayback.is_colliding():
velocity.z = 0 * delta
velocity.x = 0 * delta
if tween and tween.is_valid():
if tween.is_running():
return
func _on_button_up_pressed() -> void:
tween = create_tween()
tween.tween_property(self,"transform", transform.translated(-camera.get_global_transform().basis.z*SPEED),dur)
#
func _on_button_right_pressed() -> void:
tween = create_tween()
tween.tween_property(self,"transform", transform.translated(camera.get_global_transform().basis.x*SPEED),dur)
#
func _on_button_left_pressed() -> void:
tween = create_tween()
tween.tween_property(self,"transform", transform.translated(-camera.get_global_transform().basis.x*SPEED),dur)
#
func _on_button_down_pressed() -> void:
tween = create_tween()
tween.tween_property(self,"transform", transform.translated(camera.get_global_transform().basis.z*SPEED),dur)
func _on_button_cam_move_pressed() -> void:
tween = create_tween()
tween.tween_property(self,"transform:basis", transform.basis.rotated(Vector3.UP, PI/2),dur)
This thread on grid based movement and the collisions that follow may help you. In short try to set a “target position” from inputs and then use the _physics_process + move_and_slide to reach that target position.
Also, i dont see see you actually doing anything with the velocity. Do you do anything with it elsewhere in your code? In the code you shared, you just set velocity to a value.
I also wanted to ask in case you had an idea, I noticed there was an Input.get_vector() to get the movement, would this work with clicking input on buttons? I’m just unsure a bit on how to apply that part to my situation, since currently I use signals to initate that movement from buttons.
If I had to guess, the vector would contain references to the buttons? though I haven’t tested that, so I’m unsure if that function even accepts that.
Input.get_vector is purely for actions as made in the project setting’s InputMap, it will not correlate control Button nodes (aside from TouchScreenButton but those are tough to use, mostly for mobile).
To use your buttons I’d recommend changing the target position similar to how you are changing the transform, just without the tweens.
I’m really sorry if I’m not fully grasping; I tried to apply this and look into the thread you sent me, I feel quite confused on how to proceed with my circumstances as I am unsure how to get a substitute for input_dir as they are using for the inputs. I decided to test this (it currently does not work as intended):
extends CharacterBody3D
@onready var camera := $"Camera-z"
const SPEED = 5.0
const TILE_SIZE := 2.0
var tween
var dur = 0.3
var movement_remaining: float = 0.0
var direction : Vector3 = Vector3.ZERO
@onready var rayfront := $RayFront
@onready var rayback := $RayBack
var target_position : Vector3
@onready var audio: AudioStreamPlayer2D = $"../../../../../SoundEffects"
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity += get_gravity() * delta
pass
if rayfront.is_colliding() or rayback.is_colliding():
velocity.z = 0 * delta
velocity.x = 0 * delta
if tween and tween.is_valid():
if tween.is_running():
return
direction = target_position
if direction:
if movement_remaining == 0.0:
movement_remaining = TILE_SIZE
if movement_remaining > 0.0:
movement_remaining -= delta * SPEED * TILE_SIZE
velocity.x = direction.x * SPEED * TILE_SIZE
velocity.z = direction.z * SPEED * TILE_SIZE
else:
movement_remaining = 0.0
direction = Vector3.ZERO
velocity = Vector3.ZERO
print(movement_remaining)
move_and_slide()
func _on_button_up_pressed() -> void:
target_position += -camera.get_global_transform().basis.z*SPEED
func _on_button_right_pressed() -> void:
target_position += camera.get_global_transform().basis.x*SPEED
func _on_button_left_pressed() -> void:
target_position += -camera.get_global_transform().basis.z*SPEED
func _on_button_down_pressed() -> void:
target_position += camera.get_global_transform().basis.x*SPEED
func _on_button_cam_move_pressed() -> void:
tween = create_tween()
tween.tween_property(self,"transform:basis", transform.basis.rotated(Vector3.UP, PI/2),dur)
I’m unsure how to proceed, but printing movement_remaining showed me that there was something off with when it was being changed to the value of TILE_SIZE
Nice! It seems movement_remaining is largely unused. It is set to zero on instantiation and only subtracted from if it is non-zero which doesn’t happen. It’s probably best to remove movement_remaining as that value can be calculated elsewhere through other means.
direction should be a normalized (sort of) vector, these can represent direction because the vector points towards a target and has a length of 1, the length of 1 means we can multiply it by our speed to get a displacement vector for that frame/second.
To get the direction we must first get the difference from our position to the target.
var difference := target_position - position
This difference has both the direction and the distance (or length) to our target position, .normalized() will extract the direction, and .length() will extract the distance to the target. Normalized is good for most cases but as we get closer to the target it will overshoot and cause a jittery effect, using limit_length(1.0) helps to fix this, and dividing by delta changes our units so that we don’t overshoot for the frame, rather than per-second.
var difference := target_position - position
var direction := (difference / delta).limit_length(1.0)
# finally, apply your velocity!
velocity = direction * SPEED
move_and_slide()
I think I understand what these do. I decided to try and apply them; I’m having some slight buggyness with inputs overlapping/ the new velocity being added every click so my new position ends up being a lot farther than I anticipated(?), the inputs also have become all messed up, however I could also be applying your code wrong. I replaced all of the code referencing movement_remaining with your equations.
extends CharacterBody3D
@onready var camera := $"Camera-z"
const SPEED = 5.0
const TILE_SIZE := 2.0
var tween
var dur = 0.3
@onready var rayfront := $RayFront
@onready var rayback := $RayBack
var target_position : Vector3
var direction := target_position
@onready var audio: AudioStreamPlayer2D = $"../../../../../SoundEffects"
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity += get_gravity() * delta
pass
if rayfront.is_colliding() or rayback.is_colliding():
velocity.z = 0 * delta
velocity.x = 0 * delta
if tween and tween.is_valid():
if tween.is_running():
return
var difference := target_position - position
direction = (difference / delta).limit_length(1.0)
# finally, apply your velocity!
velocity = direction * SPEED
move_and_slide()
func _on_button_up_pressed() -> void:
target_position += -camera.get_global_transform().basis.z*SPEED
func _on_button_right_pressed() -> void:
target_position += camera.get_global_transform().basis.x*SPEED
func _on_button_left_pressed() -> void:
target_position += -camera.get_global_transform().basis.z*SPEED
func _on_button_down_pressed() -> void:
target_position += camera.get_global_transform().basis.x*SPEED
func _on_button_cam_move_pressed() -> void:
tween = create_tween()
tween.tween_property(self,"transform:basis", transform.basis.rotated(Vector3.UP, PI/2),dur)
I am very sorry again! I’m not very good with visualizing math and that like, I hope I’m on the right track nontheless though.
This looks like what I’ve asked of you, sorry if it’s a big change and not working right just yet. I’m not sure about velocity being added every click; could you explain what you expect to happen versus what really happens? I could see the inputs being flipped or something after all these changes, or rapid-clicking behaves differently than you’d expect?
Some minor things you’d probably prefer would also include using velocity.x and velocity.z to keep your gravity, currently i’ve recommended a change that destroys your gravity code, apologies there.
# not touching .y to keep gravity
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
move_and_slide()
You may also want to move the rayfront checks just before move_and_slide otherwise the difference will always overtake velocity
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
# cancel that velocity if ray collides
if rayfront.is_colliding() or rayback.is_colliding():
velocity.z = 0
velocity.x = 0
move_and_slide()
Basically upon loading the game, the character body immediately begins to slide towards a random direction at an odd pace. my inputs for forward and right only move me forward (when right should be moving me right of where I’m facing), and back and left buttons move me only backwards (same issue, trying to go left causes me to only go backwards.) Basically when I repeatedly click the buttons, the movement caused by each input “stacks” into one long translation of my position, when I do not want to be able to move further when I’m in the middle of moving to the new position until that action is over. There is also some jittering when I am not moving.
This is probably from the target_position starting at 0,0,0 add a ready function to start the game out targeting your current position
func _ready() -> void:
target_position = position
Your inputs are strange looking closer at them; your up_pressed uses basis.z but your down_pressed is using basis.x, I’d imagine they should be opposites of the same axis, down being positive basis.z. Though you should still be able to move in every direction despite this mix-up. Are your buttons connected to the correct functions?
Getting the distance remaining may help both of these problems, sorry to re-introduce movement_remaining but that’s how it goes. As I mentioned in a previous post the difference contains both the direction and the distance, so let’s extract that distance too.
var difference := target_position - position
movement_remaining = difference.length()
direction = (difference / delta).limit_length(1.0)
From here you could check in each of your button pressed functions if the distance is within a desirable range
Thank you. The movement seems to be (mostly) fixed. The only thing confusing me that I forgot to mention was that I slide upwards when colliding with walls, altering my y position. Something seems to be overriding the gravity.
Good to hear, what does your script look like now?
Your target_position also has a y value it may be good to set it equal to the current position’s y value to reduce it’s affects on the equations, you could do this every frame or when the target position changes.
target_position.y = position.y
Though it’s only applied to the velocity x and z, so I doubt it’s the cause of your characters floating.
Ah you are modifying velocity twice, I meant separating axis as a replacement. Try replacing this block of code with this sample instead
# get your calculations in first
var difference := target_position - position
direction = (difference / delta).limit_length(1.0)
#movement_remaining = difference.length() # uncomment this if you want to check distance
# apply velocity
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
# optionally override velocity here, such as ray collision checks
# another good override may be `if movement_remaining < 0.01`, to prevent jittering
# submit velocity, move and slide should *almost* always be last
move_and_slide()
Thank you very much for helping, I think It’s finally working as intended. Though I had to tweak one thing; that is that limiting the length of difference once it becomes the direction caused a lot of sliding. Once I removed the limit, the movement was more accurate to how I wanted it to be. Thank you very much again.