3D Character dashing in movement direction

Godot Version

I’m using Godot 4

Question

I am new to coding and just been doing my best since leaving college and trying to make stuff and i want to have my player able to dash in the direction they are moving but they only dash in one direction my player code script is below any tips???

extends RigidBody3D
class_name Player

var respawn_position := global_position

var mouse_sensitivity := 0.001
var twist_input := 0.0
var pitch_input := 0.0


@onready var character := $Character
@onready var twist_pivot := $TwistPivot
@onready var pitch_pivot := $TwistPivot/PitchPivot


func _ready() -> void:
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
	
	

func _process(delta: float) -> void:
	var input := Vector3.ZERO
	input.x = Input.get_axis("move_right", "move_left")
	input.z = Input.get_axis("move_back", "move_forward")
	
	apply_central_force(twist_pivot.basis * input * 1200.0 * delta)
	
	if Input.is_action_just_pressed("ui_cancel"):
		Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
		
	twist_pivot.rotate_y(twist_input)
	pitch_pivot.rotate_x(pitch_input)
	pitch_pivot.rotation.x = clamp(pitch_pivot.rotation.x, 
	deg_to_rad(-20), 
	deg_to_rad(20)
	)
	twist_input = 0.0
	pitch_input = 0.0
	
	if not input.is_zero_approx():
		var move_direction = twist_pivot.basis * input
		var align = character.transform.looking_at(character.transform.origin - move_direction)
		character.transform = character.transform.interpolate_with(align, delta * 20.0)


func _unhandled_input(event: InputEvent) -> void: 

	if event is InputEventMouseMotion:
		if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
			twist_input = - event.relative.x * mouse_sensitivity
			pitch_input = event.relative.y * mouse_sensitivity

	if event.is_action_pressed("jump"):
		if $RayCast3D.is_colliding():
			apply_central_impulse(Vector3.UP * 16.0)
	if event.is_action_pressed("dash"):
		apply_central_impulse (Vector3.BACK * 16.0)

It sounds like you need to add velocity to the player in the direction of where the inputs are. In _process, you get the direction and set it to the ‘input’ variable. To fix this, you could remove the final event.is_action_pressed and put something like this in the _process function:

If Input.is_action_just_pressed("dash"):
     var  offset = 0 # this is an offset so the direction gets set correctly
     apply_central_impulse((input + offset) * 16)

Hope this helps!

Hey I put it in fine and stuff but then I dash and i get an error for like this

Invalid operands ‘Vector3’ and ‘int’ in operator ‘+’.

for this line

apply_central_impulse((input + offset) * 16)

Sorry, that’s my bad. It should be

apply_central_impulse(input.rotated(Vector3(0,1,0), offset) * 16)

although you should try it without the rotated and the offset first.

There is a recent similar topic where I posted a possible approach.

Maybe it could be helpful for you

I’m sure it would still apply to a 3D workspace like mine but just in general I don’t understand the block of code attached too much sorry sorry

Without the offset and rotated its still just going in one direction not the way I’m moving and sorry if this is like really simple stuff I’m still new to it all haha your help is greatly appreciated its now giving me this and i understand it needs another thing but not sure what to even put

Still only dashing me in one direction and genuinely dont know why here is now my current code for the player script

extends RigidBody3D
class_name Player

var respawn_position := global_position

var mouse_sensitivity := 0.001
var twist_input := 0.0
var pitch_input := 0.0


@onready var character := $Character
@onready var twist_pivot := $TwistPivot
@onready var pitch_pivot := $TwistPivot/PitchPivot


func _ready() -> void:
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
	
	

func _process(delta: float) -> void:
	var input := Vector3.ZERO
	input.x = Input.get_axis("move_right", "move_left")
	input.z = Input.get_axis("move_back", "move_forward")
	
	apply_central_force(twist_pivot.basis * input * 1200.0 * delta)
	
	if Input.is_action_just_pressed("ui_cancel"):
		Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
		
	twist_pivot.rotate_y(twist_input)
	pitch_pivot.rotate_x(pitch_input)
	pitch_pivot.rotation.x = clamp(pitch_pivot.rotation.x, 
	deg_to_rad(-20), 
	deg_to_rad(20)
	)
	twist_input = 0.0
	pitch_input = 0.0
	
	if not input.is_zero_approx():
		var move_direction = twist_pivot.basis * input
		var align = character.transform.looking_at(character.transform.origin - move_direction)
		character.transform = character.transform.interpolate_with(align, delta * 20.0)
		
	if Input.is_action_just_pressed("dash"):
		var offset = 0 # this is an offset so the direction gets set correctly
		apply_central_impulse(input.rotated(Vector3(0,1,0), offset) * 16)

func _unhandled_input(event: InputEvent) -> void: 

	if event is InputEventMouseMotion:
		if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
			twist_input = - event.relative.x * mouse_sensitivity
			pitch_input = event.relative.y * mouse_sensitivity

	if event.is_action_pressed("jump"):
		if $RayCast3D.is_colliding():
			apply_central_impulse(Vector3.UP * 16.0)

The important part of my fragment is the use of tween.

You should have a dash_impulse variable, that normally will be zero. It will be added to your general force apply always (I assume that method takes care of moving your character). And when you press dash you update the impulse value and use the tween to interpolate the value to zero again.

var dash_tween: Tween
var dash_speed := 2000.0
var dash_impulse := 0.0

func _process(delta: float) -> void:
  ...
  apply_central_force(twist_pivot.basis * input * (1200.0 + dash_impulse ) * delta)
  ...
func _unhandled_input(event: InputEvent) -> void:
  if event.is_action_pressed("dash"):
    dash_impulse = dash_speed
    dash_tween = create_tween()
    tween.tween_property(self, "dash_impulse", 0, 0.3).set_ease(Tween.EASE_OUT)

The only other thing I can think of doing would be to replace both the var line and the impulse line and replacing them with a modified version of your movement:

apply_central_impulse(twist_pivot.basis * input * 16 * delta)

You can replace the 16 with any strength of dash.

Ok I’m only getting this error
Line 63:Identifier “tween” not declared in the current scope
which I understand but I just don’t know why its not seeing it yknow???

Here’s the updated block of code:

extends RigidBody3D
class_name Player

var respawn_position := global_position

var mouse_sensitivity := 0.001
var twist_input := 0.0
var pitch_input := 0.0

var dash_tween: Tween
var dash_speed := 2000.0
var dash_impulse := 0.0

@onready var character := $Character
@onready var twist_pivot := $TwistPivot
@onready var pitch_pivot := $TwistPivot/PitchPivot

func _ready() → void:
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)

func _process(delta: float) → void:
var input := Vector3.ZERO
input.x = Input.get_axis(“move_right”, “move_left”)
input.z = Input.get_axis(“move_back”, “move_forward”)

apply_central_force(twist_pivot.basis * input * (1200.0 + dash_impulse ) * delta)

if Input.is_action_just_pressed("ui_cancel"):
	Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
	
twist_pivot.rotate_y(twist_input)
pitch_pivot.rotate_x(pitch_input)
pitch_pivot.rotation.x = clamp(pitch_pivot.rotation.x, 
deg_to_rad(-20), 
deg_to_rad(20)
)
twist_input = 0.0
pitch_input = 0.0

if not input.is_zero_approx():
	var move_direction = twist_pivot.basis * input
	var align = character.transform.looking_at(character.transform.origin - move_direction)
	character.transform = character.transform.interpolate_with(align, delta * 20.0)

func _unhandled_input(event: InputEvent) → void:

if event is InputEventMouseMotion:
	if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
		twist_input = - event.relative.x * mouse_sensitivity
		pitch_input = event.relative.y * mouse_sensitivity

if event.is_action_pressed("jump"):
	if $RayCast3D.is_colliding():
		apply_central_impulse(Vector3.UP * 16.0)

if event.is_action_pressed("dash"):
	dash_impulse = dash_speed
	dash_tween = create_tween()
	tween.tween_property(self, "dash_impulse", 0, 0.3).set_ease(Tween.EASE_OUT)

In the last line, you use tween.tween_property, when you haven’t defined tween as a variable, only dash_tween.

How should i fix just like

var tween: Tween

or like what I can try once i wake tomorrow

Comment out the tweens to start, as the impulse and friction will handle the smoothness of the dash. Test the impulse, and if it doesn’t work, then try the tweens. The reason for the tweens is the other script extends CharacterBody3D, which doesn’t handle as much physics as the RigidBody3D that you are using. Try this for your script first:

extends RigidBody3D
class_name Player

var respawn_position := global_position

var mouse_sensitivity := 0.001
var twist_input := 0.0
var pitch_input := 0.0
var DASH_SPEED := 2000 #Change this to change the speed of the dash

@onready var character := $Character
@onready var twist_pivot := $TwistPivot
@onready var pitch_pivot := $TwistPivot/PitchPivot


func _ready() -> void:
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
	
	

func _process(delta: float) -> void:
	var input := Vector3.ZERO
	input.x = Input.get_axis("move_right", "move_left")
	input.z = Input.get_axis("move_back", "move_forward")
	
	apply_central_force(twist_pivot.basis * input * 1200.0 * delta)
	
	if Input.is_action_just_pressed("dash"):
		apply_central_impulse(twist_pivot.basis * input * DASH_SPEED * delta) #See above for dash speed
		#Start with delta multiplied in, but know that it can be removed if speed issues exist. Check DASH_SPEED first though.

	if Input.is_action_just_pressed("ui_cancel"):
		Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
		
	twist_pivot.rotate_y(twist_input)
	pitch_pivot.rotate_x(pitch_input)
	pitch_pivot.rotation.x = clamp(pitch_pivot.rotation.x, 
	deg_to_rad(-20), 
	deg_to_rad(20)
	)
	twist_input = 0.0
	pitch_input = 0.0
	
	if not input.is_zero_approx():
		var move_direction = twist_pivot.basis * input
		var align = character.transform.looking_at(character.transform.origin - move_direction)
		character.transform = character.transform.interpolate_with(align, delta * 20.0)


func _unhandled_input(event: InputEvent) -> void: 

	if event is InputEventMouseMotion:
		if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
			twist_input = - event.relative.x * mouse_sensitivity
			pitch_input = event.relative.y * mouse_sensitivity

	if event.is_action_pressed("jump"):
		if $RayCast3D.is_colliding():
			apply_central_impulse(Vector3.UP * 16.0)

This is generally what you had before, but with a movement of the dash function and a slight change. Make sure to read the code comments.
If you really want, you can try this.

If you want to go down the route of tweens, you would use what you had in post 11, but on the last line, instead of tween.tween_propert… use dash_tween.tween_propert…

1 Like

Thanks you both so so much it works now sorry for some probably very simple questions I owe ya!!!

Just one last thing can I set it so that it like can only be done once per jump and then delay where as now you can spam it you know

Yea! You can add a variable (maybe can_dash) and in the dash if loop, also check if can_dash is true. Start with can_dash as false, and if touching the ground, set can_dash to true. Here are the methods to change:

#At the start, during variable declaration:
var can_dash: bool = false

#remove these dots, as they show there is code more between this.
...

if Input.is_action_just_pressed("dash") and can_dash:
		can_dash = false
		apply_central_impulse(twist_pivot.basis * input * DASH_SPEED * delta) #See above for dash speed
		#Start with delta multiplied in, but know that it can be removed if speed issues exist. Check DASH_SPEED first though.

#and these ones two
...

#also in _process
	if $RayCast3D.is_colliding():
		can_dash = true
1 Like

This works really well and sorry to still ask just one more question.

With the dash theres like instant fall off in the air is there a way to have it so like i dont fall instantly so like maybe once the dash ends only then my Y position changes???

For that, you can add a timer with:

get_tree().create_timer(seconds)

or use a timer node (use the timer node, it’s easier). From there, you can have a variable for if the y is allowed to decrease. When you dash, start the timer, and set the variable to not allow the gravity. Then, when the timer goes off, set the variable to allow gravity. In your gravity script, check that gravity is allowed before you add it to the player.

Or, if you really want, you could only add gravity if your speed is less than a certain number. It could cause more bugs, but hey, it’s an option, and it’s quick.

I actually don’t have a script for gravity ill try the timer node in i assume my player scene when i have some free time later. A little confused by it but hopefully i can get it to work but may be back for more help haha

Happy to help :slight_smile:

About doing it just once, you can add a var that allows you tween or not the property like @JoshForgotten said.

Other option is just to check if dash_speed is zero. This way the condition to dash again is just the prev dash has finished (just to give you another option).