Updated Code For Godot 4.1 Users Part 2

Godot
MilkDrinker

So as promised, I've now done the scripts for the Player.gd, PlayerState.gd and the Move.gd all updated and working for Godot 4.1


As a side note to anyone following along, I'd highly, HIGHLY recommend  switching over from the built in code editor to visual studio code and running the godot language server for intellisense through there. It makes the dev experience actually workable and not cringe. Check out this website to see how to do it: https://www.showwcase.com/show/34429/effortless-scripting-in-godot-4-with-visual-studio-code-a-step-by-step-guide

Anyways, onto the new scripts

Player.gd

extends CharacterBody3D
class_name Player

@onready var skin: Mannequiny = $Mannequiny
@onready var state_machine: StateMachine = $StateMachine

Minimal changes here, just the annotated @onready's

PlayerState.gd

extends State
class_name PlayerState

var player: Player
var skin: Mannequiny


func _ready():
super._ready()
await owner.ready
player = owner
skin = player.skin

So this here script gave me massive conniptions until i read another comment made here that in Godot 4, you now have the ability to manually call the parent classes methods using the super. keyword. Before the game engine just automatically called everything and they decided to change it for more fine control. In this case, we're calling the _ready() function so that the _state_machine and _parent nodes inside the State.gd class get set. This then allows the rest of the things to work 😎. I found it still needed to keep the await.

Move.gd

extends PlayerState

@export var max_speed: float = 250.0
@export var move_speed: float = 250.0
@export var gravity: float = -80.0
@export var jump_impulse: float = 25.0

func physics_process(delta: float):
var input_direction: Vector3 = get_input_direction()

var move_direction: Vector3 = input_direction
if move_direction.length() > 1.0:
move_direction = move_direction.normalized()
move_direction.y = 0.0

if move_direction:
player.look_at(player.global_transform.origin + move_direction, Vector3.UP, true)
player.velocity = calculate_velocity(player.velocity, move_direction, delta)
player.move_and_slide()

static func get_input_direction() -> Vector3:
return Vector3(
Input.get_action_strength("right") - Input.get_action_strength("left"),
0.0,
Input.get_action_strength("backwards") - Input.get_action_strength("forwards")
)

func calculate_velocity(velocity_current: Vector3, move_direction: Vector3, delta: float) -> Vector3:
var new_velocity : Vector3 = velocity_current

new_velocity = move_direction * delta * move_speed

if new_velocity.length() > max_speed:
new_velocity = new_velocity.normalized() * max_speed
new_velocity.y = velocity_current.y + gravity * delta

return new_velocity

The biggest differences I noticed here is that the player.look_at() can also take a bool called use_model_front which is defaulted to false, but if you make it true then you dont need to manually change the orientation of the model in your player scene.

Another thing that i noticed that is a bit strange is that i had to significantly increase the max_speed and move_speed in order to get the same type of speed as shown in the video. I'm not entirely sure why but i suspect it has something to do with the fact that CharacterBody3D now has an in built velocity that im using instead of a separate velocity and i suspect that is what makes it slower as its doing some other things im not smart enough to understand.

Also note that move_and_slide() now takes no params and returns a bool, whereas before it took in the velocity and upwards_direction vector. The upwards direction vector is now also a property on the CharacterBody3D and it defaults to Vector3.Up, so i saw no need to add it here.

Mannequiney.gd

extends Node3D
class_name Mannequiny

enum States {IDLE, RUN, AIR}

@onready var animation_tree : AnimationTree = $AnimationTree

@onready var _playback: AnimationNodeStateMachinePlayback = animation_tree["parameters/playback"]

func _ready():
animation_tree.active = true

func transition_to(state_id: int):
match state_id:
States.IDLE:
_playback.travel("idle")
States.RUN:
_playback.travel("run")
_:
_playback.travel("idle")

So nothing from a code perspective really changes here, but we need to speak about the AnimationTree Node. I dont really understand why, but in the video he makes some transition connections from the idle to the run and visa versa. In Godot 4, things look quite different.


Theres now a Start and End node. I found these to be useless as you dont need to (AFAIK) set the idle or run to be the start and worry about the end at all.

Also, DON'T make connections between these nodes, as they completely freak out and make the skin jump between the idle and run at the frame rate of the computer. Im not entirely sure how this will effect the settings like the smoothing/ blending between the animation states, but i suspect im going to have to drive this thing entirely via code.

Idle.gd

extends PlayerState


func unhandled_input(event: InputEvent):
_parent.unhandled_input(event)

func physics_process(delta: float):
_parent.physics_process(delta)
if player.is_on_floor() and player.velocity.length() > 0.01:
_state_machine.transition_to("Move/Run")

func enter(message: Dictionary = {}):
player.velocity = Vector3.ZERO
skin.transition_to(skin.States.IDLE)
_parent.enter(message)

func exit():
_parent.exit()

Run.gd

extends PlayerState

func unhandled_input(event: InputEvent):
_parent.unhandled_input(event)

func physics_process(delta: float):
_parent.physics_process(delta)

if player.velocity.length() < 0.01 :
_state_machine.transition_to("Move/Idle")

func enter(message: Dictionary = {}):
skin.transition_to(skin.States.RUN)
_parent.enter(message)

func exit():
_parent.exit()

Nothing to report here for these 2 scripts


As always if theres ways that the code can be made better, please feel free to add on

2 loves
Reply
  • Nathan Lovato replied

    Thanks much for taking the time to share updated code for Godot 4 🙂