Alright, some update.
Moved to godot 4.7 stable From originally 4.5. Starting fresh with clean scenes and scripts.
What i reworked:
NPC - animation
NPC - targeting and movement (basic/non combat)
Player - script based statemachine (back from node based statemachine)

state_interface.gd
extends RefCounted
class_name StateInterface
var state_machine: StateMachine
func enter(_prev_state: String = "")-> void:
pass
func exit() -> void:
pass
func update(_delta: float) -> void:
pass
func physics_update(_delta: float) -> void:
pass
func handle_input(_event: InputEvent) -> void:
pass
state_machine.gd
extends RefCounted
class_name StateMachine
var states: Dictionary = {}
var current_state: StateInterface
var current_state_name: String = ""
var owner
func add_state(name: String, state: StateInterface):
states[name.to_lower()] = state
state.state_machine = self
func set_initial_state(state_name: String) -> void:
change_state(state_name)
func change_state(new_state_name: String) -> void:
var prev_state_name = current_state_name
if current_state:
current_state.exit()
current_state_name = new_state_name.to_lower()
current_state = states.get(current_state_name)
if current_state:
current_state.enter(prev_state_name)
print("Changing state to: ", current_state_name)
else:
push_warning("State not found: " + new_state_name)
func update(delta: float) -> void:
if current_state:
current_state.update(delta)
func physics_update(delta: float) -> void:
if current_state:
current_state.physics_update(delta)
func handle_input(event: InputEvent) -> void:
if current_state:
current_state.handle_input(event)
func get_current_state_name() -> String:
return current_state_name
idle_state.gd
extends StateInterface
class_name IdleState
var character: CharacterBody3D
func enter(_prev_state: String = "")-> void:
character = state_machine.owner
func physics_update(_delta: float) -> void:
character.move_and_slide()
func handle_input(_event: InputEvent) -> void:
if Input.get_vector("k_left", "k_right", "k_forward", "k_backward"):
state_machine.change_state("walk")
elif Input.is_action_just_pressed("k_jump"):
state_machine.change_state("jump")
jump_state.gd
extends StateInterface
class_name JumpState
var character: CharacterBody3D
func enter(_prev_state: String = "")-> void:
character = state_machine.owner
character.velocity.y = character.JUMP_VELOCITY
func physics_update(_delta: float) -> void:
#character.velocity += character.get_gravity() * delta
character.velocity.x = character.direction.x * character.SPEED
character.velocity.z = character.direction.z * character.SPEED
character.move_and_slide()
if character.is_on_floor():
if character.direction:
state_machine.change_state("walk")
else:
state_machine.change_state("idle")
walk_state.gd
extends StateInterface
class_name WalkState
var character: CharacterBody3D
func enter(_prev_state: String = "")-> void:
character = state_machine.owner
func physics_update(_delta: float) -> void:
var stop_time := 0.05
var slow_down : float = min(_delta/stop_time, 1.0)
character.move_and_slide()
if character.direction:
var up := Vector3.UP
# For root motion
var forward : Vector3 = -character.direction # Check this value if its in correct way
var right := up.cross(forward).normalized()
var corrected_up := forward.cross(right).normalized()
var target_basis := Basis(right, corrected_up, forward).orthonormalized()
var a = character.transform.basis.get_rotation_quaternion()
var b = target_basis.get_rotation_quaternion()
var c = a.slerp(b,0.3)
character.transform.basis = Basis(c).orthonormalized()
character.velocity.x = character.direction.x * character.SPEED
character.velocity.z = character.direction.z * character.SPEED
else:
character.velocity.x -= character.velocity.x * slow_down
character.velocity.z -= character.velocity.z * slow_down
if character.velocity.is_equal_approx(Vector3.ZERO):
state_machine.change_state("idle")
return
func handle_input(_event: InputEvent) -> void:
if Input.is_action_just_pressed("k_jump") and character.is_on_floor():
state_machine.change_state("jump")
player.gd
extends CharacterBody3D
class_name Player
const SPEED = 5.0
const JUMP_VELOCITY = 4.5
@onready var player_camera: PlayerCamera = $PlayerCamera
var direction: Vector3
var state_machine: StateMachine
func _ready() -> void:
state_machine = StateMachine.new()
state_machine.owner = self
state_machine.add_state("idle", IdleState.new())
state_machine.add_state("walk", WalkState.new())
state_machine.add_state("jump", JumpState.new())
state_machine.set_initial_state("idle")
func _process(delta: float) -> void:
state_machine.update(delta)
func _physics_process(delta: float) -> void:
# Add the gravity.
if not is_on_floor():
velocity += get_gravity() * delta
var input_dir := Input.get_vector("k_left", "k_right", "k_forward", "k_backward")
direction = (player_camera.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
state_machine.physics_update(delta)
func _input(event: InputEvent) -> void:
state_machine.handle_input(event)
Node based statemachine at first seems like a good idea but then you come to a point where the states get too convoluted, and you got so many of them that organising them is a nightmare. Then you have polluted the node tree with unnecessary basically empty nodes that use up process time. Which will make the game very laggy when you have like hundreds of players that each has a 9000 nodes in their trees.
Then i remodelled my slime from an Ico sphere to a cube/quad, The deformations are much more smoother with quads than tris.
Ico

Quad

Small progress overall, i got busy with life problems so i wasnt in the right mind to get back to work on this.
Ofc also procrastinating, and dread of too many things to focus on, and this and that and - i dont know how to do this... you know how it is 🙂
For the statemachine concept: Easy to follow, 10min and youre done.
Next i want to implement maybe more convoluted statemachine with combat and stuff like that - both on player and NPC to see how and if its possible.
And again, these tests are not a game im fixing to make. This i where i practice what i learn and learn what to practice.
Goal is the same as it was last two years - to have some kind of basic gameplay functionality by the end of the year. 😃