I don't understand my own movment code, can you help me out?
Asked Answered
M

26

0

I know that this isn't a problem that is holding me back but, I fear that not understanding my own code could cause problems in the future; I also can't get this topic of my mind right now. So I'm just going to show the movement code and explain why I can't understand it fully.
This is the movement code for my player character:

func movement_code(delta):
	var horizontal_velocity = Input.get_vector("move_left","move_right","move_forward","move_backward").normalized() * player.speed
	var global_transform_basis = player.global_transform.basis
	player.velocity = horizontal_velocity.x * global_transform_basis.x + horizontal_velocity.y * global_transform_basis.z
	if player.is_on_floor():
		player.velocity_y = 0
		if Input.is_action_just_pressed("Jump"):
			player.velocity_y = jump_velocity
	else:player.velocity_y -= gravity * delta
	player.velocity.y = player.velocity_y
	player.move_and_slide()

It works well and it does what it supposed to do. I got the code from a Youtube tutorial titled "Godot 4 First-Person Controller with 22 Lines of Code". Here it is:

A challenge that I have had with this code, is when I tried to edit it for enemy AI patrol movement; I just don't know where to start. Now I do have code that works well for the enemy AI patrolling. However, that code wasn't a modification of the player movement code. Instead, this is what I got.

func move_forward():
	enemy.position -= enemy.transform.basis.z * speed
	enemy.move_and_slide()

The code makes the enemy move forward, while another function makes the enemy turn away from obstracles it detects with the raycast. It works well but, it doesn't involve manipulating the enemy's velocity property while the movement code for the player does involve manipulating the player's velocity property.
Everytime when I try to get the enemy to move by manipulating it's velocity property, I don't seem to get the results I want; this is probably indicative of my ignorance regarding how the player movement code works or how the velocity property works in Godot 4.

The documentation says that velocity is a Vector3 for CharacterBody3D nodes. However, my enemy wouldn't move at all if I were to make the code like this:

func move_forward():
	enemy.velocity.z += 5
	enemy.move_and_slide()

I'm not sure if anything I'm saying here even makes any sense to anyone else. Could anyone explain to me what is going on?

Mucker answered 15/1 at 11:1 Comment(0)
F
1

Mucker Add comments to every line in your player movement code, describing your understanding of what is happening in that line. Post that commented code up for discussion.

Fichu answered 15/1 at 11:36 Comment(0)
M
0

Fichu Okay. Here's what I added.

func movement_code(delta):
	var horizontal_velocity = Input.get_vector("move_left","move_right","move_forward","move_backward").normalized() * player.speed
	#var horizontal_velocity is a vector2 manipulated by player inputs; it's being multipled by speed and normalized to make horizontal movement consistent.
	var global_transform_basis = player.global_transform.basis
	#I don't understand what var global_transform_basis means completely but, I think it makes local movement possible. If that's the case, I'd find it useful for steering/patrolling.
	player.velocity = horizontal_velocity.x * global_transform_basis.x + horizontal_velocity.y * global_transform_basis.z
	#I know that velocity property is a Vector3. 
	#I don't know how the horizontal_velocity.x * global_transform_basis.x + horizontal_velocity.y * global_transform_basis.z is also a Vector3.
	#The gravity code seems easy and straight forward. I think I understand it but, I also suspect it's irrelevant to what I'm concerned about right now.
	if player.is_on_floor():
		player.velocity_y = 0
		if Input.is_action_just_pressed("Jump"):
			player.velocity_y = jump_velocity
	else:player.velocity_y -= gravity * delta
	player.velocity.y = player.velocity_y
	player.move_and_slide()
Mucker answered 15/1 at 14:17 Comment(0)
F
1

Mucker
Nice.
Firstly, just a stylistic remark; there's a widely accepted convention to place comments above the block/line they refer to, or at the end of the line. As a general advice, get into habit of commenting as much as possible. It makes code more readable and easier to get into for other persons as well as yourself after not seeing it for a long time. When implementing an algorithm it's often a good approach to first write the whole function/class just as a series of comments, and then implement the actual code piece by piece, leaving relevant comments in place.

From comments in your code πŸ˜‰ it seems that the only thing you actually don't understand is basis and how it's used for calculating the velocity vector.

Basis is a collection of 3 vectors. Each of those vectors is one coordinate axis of your object's local coordinate system. The basis is needed to represent the orientation of an object in 3D space.

You can visualize it like this:

Make the depicted gesture with your right hand.
If the hand is your object then each finger represents one of it local coordinate axes: x, y, and z. Those coordinate axes are the basis vectors. The length of each basis vector is always 1.0 (i.e. they are unit vectors)

Imagine that your room is the global space. If you align your fingers with the room walls then the basis vectors will be the same as global coordinate system axes; your x basis will be (1, 0, 0) etc...

However if you rotate your hand into any other random position, then your basis vectors may become some arbitrary vectors (in respect to global space of the room).

Now if you want to tell your hand to move in the direction of your index finger, the velocity vector will need to be aligned with that index finger, meaning the velocity vector should be set to have the direction of your basis x vector, and magnitude of the movement speed.

Fichu answered 15/1 at 16:19 Comment(0)
C
1

Mucker it seems like all your movement code is separated from your main script.
xyz explained it pretty well, so just gonna provide another script.

extends CharacterBody3D

var speed = 200
var gravity = 40

func get_input(): 
	var input_vec : Vector3 #create a vector3 and assign each direction to it. (
	if Input.is_action_pressed("forward"):
		input_vec += Vector3.FORWARD
	if Input.is_action_pressed("backward"):
		input_vec += Vector3.BACK
	if Input.is_action_pressed("left"):
		input_vec += Vector3.LEFT
	if Input.is_action_pressed("right"):
		input_vec += Vector3.RIGHT
	input_vec = input_vec.normalized() #normalize it so going diagonally doesn't go faster 
	input_vec = input_vec.rotated(Vector3.UP, rotation.y) #rotate the input vec depending on the rotation of the player 
	return input_vec #return the vector

func _physics_process(delta):
	if !is_on_floor():
		velocity.y -= gravity * delta 
	velocity = get_input() * delta * speed #velocity is a vector3, you add delta and some speed to it to control it how you want 
	
	move_and_slide() #call move and slide at the end
Censorship answered 15/1 at 16:34 Comment(0)
M
0

Censorship

Okay. So this is what I came up with for my enemy.

func enemy_forward_direction():
	var forward : Vector3 = Vector3.FORWARD #Vector representing forward movement.
	forward = forward.normalized()# This prevents diagonal movement from being faster.
	forward = forward.rotated(Vector3.UP,enemy.rotation.y) #This ensures that the rotation of the forward vector is always the same as the enemy.
	return forward

func move_forward(delta):
	enemy.velocity = enemy_forward_direction() * speed * delta
	enemy.move_and_slide()

I didn't get any errors but, I don't see any movements. I don't know what I'm doing work here.

Mucker answered 16/1 at 12:8 Comment(0)
S
0

Mucker maybe I missed it glancing through the thread. Where do you call move_forward? Show that code.

Skean answered 16/1 at 12:12 Comment(0)
F
0

Mucker
I guess my explanation wasn't really understood πŸ˜‰

Your forward vector should be one of the basis vectors in object's global transform. You decide which one. That vector is already "properly" rotated, so no need to do any additional rotation. Just take that vector. You can normalize it just in case the object is scaled. It really takes only one line of code to calculate the forward velocity vector.

There isn't any visible movement probably because you multiply your velocity vector by delta. Delta is typically a very small number. This makes velocity magnitude small as well, resulting in object barely moving πŸ™‚

Besides, according to movement equations in physics, velocity multiplied by time equals distance travelled. You only need to specify the velocity. The distance travelled is calculated/applied by move_and_slide()

Fichu answered 16/1 at 12:19 Comment(0)
M
0

Fichu

So I did this:

func enemy_forward_direction():
	var forward  = enemy.transform.basis.z #Vector representing forward movement.
	forward = forward.normalized()# This prevents diagonal movement from being faster.
	return forward

func move_forward(delta):
#	enemy.position -= enemy.transform.basis.z * speed
	enemy.velocity = enemy_forward_direction() * speed
	enemy.move_and_slide()

I still don't have any error, which is good. However, I still don't see any movement. The speed is at 7.

Mucker answered 16/1 at 12:46 Comment(0)
M
0

Skean I place it in another function for patrolling and then that's running somewhere else in the state machine:

func patrol_demo(delta):
	if raycast.is_colliding():
		var target = raycast.get_collision_point()
		if enemy.position.distance_to(target) < 25:
			if raycast.rotation_degrees.y >= 1:
				enemy.rotation_degrees.y -= 1
			elif raycast.rotation_degrees.y <= 1:
				enemy.rotation_degrees.y += 1
	move_forward(delta)

and here's where all of that is being called:

func on_state_update(delta):#Function for running the state.
	sweeping()
	patrol_demo(delta)
Mucker answered 16/1 at 12:51 Comment(0)
F
1

Mucker Don't touch the position. That's move_and_slide() 's job. Also you'd typically want to take the basis from global_transform, not transform. But this really depends on your node setup. And make sure move_forward() is actually called from _process() or _physics_process() as others have pointed out. Put a print statement in there just to be sure it's called.

Fichu answered 16/1 at 12:51 Comment(0)
M
0

Fichu I'm still a bit confused. I don't seem to have this problem with the movement code for the player, despite it having a similar finite state machine to the enemy. The code for the player movement is within a function that's being called from _process() and it works. However, similarly doesn't seem to work for the enemy movement. Here's what I have now (I commented out what I was using before just in case I need to go back to it):

func move_forward():
#	enemy.position -= enemy.transform.basis.z * speed
	enemy.velocity =enemy.global_transform.basis.z.normalized() * speed
	enemy.move_and_slide()
Mucker answered 16/1 at 14:59 Comment(0)
F
0

Mucker Does enemy have a state machine? From where do you call enemy's move_and_slide()? It surely doesn't make sense to call it from player's state machine. Put the enemy code into a script attached to enemy's character body node.

Fichu answered 16/1 at 15:6 Comment(0)
M
0

Fichu The enemy has it's own state machine, similar to that of the player. All the states of the enemy inherit from a class I call "enemy_class"; this makes them have access to the enemy node.
Here's the code that runs the current_state:

func _process(_delta): #All this does is run the code for the new state.
	current_state.on_state_update(_delta)

Here's the code for the patrol state.

func on_state_update(delta):#Function for running the state.
	sweeping()
	patrol_demo(delta)
	move_forward()

and here is the movement code.

func move_forward():
#	enemy.position -= enemy.transform.basis.z * speed
	enemy.velocity -= enemy.global_transform.basis.z.normalized() * speed
	enemy.move_and_slide()
Mucker answered 16/1 at 16:39 Comment(0)
F
1

Mucker Print the velocity vector just before calling move_and_slide(). It's debugging 101.

Fichu answered 16/1 at 16:42 Comment(0)
M
0

Fichu Damn! It's not doing anything at all! πŸ˜† I got back a vector with zero on all axis when I printed.

func move_forward():
#	enemy.position -= enemy.transform.basis.z * speed
	enemy.velocity += enemy.global_transform.basis.z.normalized() * speed
	print(velocity)
	enemy.move_and_slide()

Edit: Okay. I started to get a number back when I made this change:

func move_forward():
#	enemy.position -= enemy.transform.basis.z * speed
	enemy.velocity = enemy.global_transform.basis.z.normalized() * speed
	print(enemy.velocity)
	enemy.move_and_slide()

Edit again. I wasn't printing the enemy's velocity specifically. Now that I have, I do get back a value.

func move_forward():
#	enemy.position -= enemy.transform.basis.z * speed
	enemy.velocity += enemy.global_transform.basis.z.normalized() * speed
	print(enemy.velocity)
	enemy.move_and_slide()

The output keeps saying (0,0,7). However, I'm still seeing no movement from the enemy.

Mucker answered 16/1 at 16:47 Comment(0)
F
0

Mucker There's likely a problem with your setup. Try to do this in a clean project with just a couple of nodes, without any state machine stuff.

Fichu answered 16/1 at 16:53 Comment(0)
C
0

Fichu Add comments to every line in your player movement code, describing your understand of what is happening in that line.

One of the laws of programming: If code is too complicated to describe, then it's too complicated.

Considered answered 16/1 at 17:20 Comment(0)
M
0

Fichu You've got a point. The code seems to work well without the state machine stuff when I tried it out on another node. Now I just need to find a way to make it work with the state machine! πŸ˜†

Mucker answered 16/1 at 19:21 Comment(0)
C
1

Mucker Can you post your full scripts so we can check it out ?

Censorship answered 16/1 at 19:27 Comment(0)
R
0

Mucker What I like to do is to have a sandbox scene where I put in all the stuff I want to check out and a folder where I keep my test scripts and scenes. And if something doesn't work as intented and I can't find the problem in a short time I put the code piece by piece in a test script. Testing parts on its own also often helped me to understand things better.

Secretion Your explanation of transform basis somehow clicked for me. I guess I just needed some visualization to really understand it.

Return answered 16/1 at 20:23 Comment(0)
M
0

Censorship

Okay. So what I got here is a basic finite state machine for my enemy( which is a characterbody3d node). I learnt how to make it from a tutorial titled "A simplistic aproach to the Finite State Machines in Godot" by SebiTCR, here's a link to it for reference.
https://dev.to/sebitcr/a-simplistic-aproach-to-the-finite-state-machines-in-godot-2d1b

Now here's what mine looks like.

Here's the code for the enemy's state machine:

extends Node
class_name Enemy_Fsm

@export var first_state_name : String = "enemy_idle"

@onready var current_state = the_states()[first_state_name]

func _ready():#This initialize the first state.
	current_state.on_state_enter()#This runs the code for entering the first state.

func change_state(state_name : String):#This is for chaning states. The parameter state is for the new state we're switching to.
	current_state.on_state_exit()#This runs the exist_state function for the current state.
	current_state = the_states()[state_name] #This turns the current state to the new state in the parameter.
	current_state.on_state_enter() #This runs the enter state function for the new state we made the current state.

func _process(_delta): #All this does is run the code for the new state.
	current_state.on_state_update(_delta)

func the_states():
	var the_states : Dictionary = {}
	for state in get_children():
		if state is EnemyState:
			the_states[state.name] = state
	return the_states

and here's a code for the enemy's state class.

extends enemy_class

class_name EnemyState


@onready var fsm : Enemy_Fsm = get_parent()

func on_state_enter():#Function for entering state.
	pass


func on_state_update(delta):#Function for running the state.
	pass


func on_state_exit():#Function for existing the state.
	pass

The enemy state class script inherits from the enemy_class script; the script that's on the enemy. Here's all the variables inside the enemy_class script:

extends Node

class_name enemy_class

var health = 100

const JUMP_VELOCITY = 4.5

var speed = 7
var acceleration = 10

var direction = Vector3()

var velocity = Vector3()

@export var path_2_enemy : CharacterBody3D


@onready var enemy: CharacterBody3D = path_2_enemy if path_2_enemy != null else owner
@onready var enemy_col : CollisionShape3D = $"%CollisionShape3D"
@onready var angle_checker : Node3D = $"%Angle_Checker"
@onready var anim_player : AnimationPlayer = $"%AnimationPlayer"
@onready var raycast : RayCast3D = $"%RayCast3D"
@onready var player = the_player()

The move_forward function is also from the enemy class script.

func move_forward():
#	enemy.position -= enemy.transform.basis.z * speed
	enemy.velocity += enemy.global_transform.basis.z.normalized() * speed
	print(enemy.velocity)
	enemy.move_and_slide()

It's being called in the script for the enemy_patrol node. Since I made "enemy_patrol" the "first_state_name" in the editor, the first state the enemy would be in at the beginning of running the project would the the enemy_patrol state. The code for that state looks like this:

extends EnemyState


func on_state_enter():#Function for entering state.
	print("LOL!")

func on_state_update(delta):#Function for running the state.
	sweeping()
	patrol_demo(delta)
	move_forward()

func _process(delta):
	enemy.velocity -= enemy.global_transform.basis.z.normalized() * speed
	enemy.move_and_slide()

func on_state_exit():#Function for existing the state.
	pass

That's basically all the relevant stuff. Also, here's a screenshot of the node hierarchy:

Mucker answered 16/1 at 20:43 Comment(0)
F
1

Mucker Node based state machines again, eh? No wonder you got stuck. I'll let someone else help you untangle the mess, but as a general advice for the future, stay away from this type of state machines. This approach is so clunky that it's practically guaranteed to crumble under its own weight once you need to go beyond the simplest player controller with few discrete states.

Fichu answered 16/1 at 21:19 Comment(0)
S
0

What type is 'enemy', it should be a CharacterBody3D right? Can you confirm path_2_enemy is correct and it really is the right type rather than 'owner' (which could be a Node?). Perhaps static typing will help find the error...

Skean answered 17/1 at 10:40 Comment(0)
M
0

Skean I think path_2_enemy is current, because my code for turning the enemy away from objects is working as intended. I really don't have a clue what the problem is. What makes all of this more frustrated/confusing is how I don't have any of these problems with the player's finite state machine. Also, I did manage to make the enemy move before using move_and_slide()/velocity with a previous function that made the enemy "fellow" the player. However, even that doesn't work. I'm dazed and confused. πŸ˜΅β€πŸ’«

Mucker answered 17/1 at 20:55 Comment(0)
S
0

Mucker the code itself looks like it should be working but there's so much architecture around it, it's really tough to say where the issue is. I suspect the problem is in the state machine logic, which I don't feel is working at all (for the enemy) how it is expected to work. Print the enemy states somewhere so you know what state it is in, I think that has been suggested. The state transition logic is likely at fault.

Isolate it with test code. Perhaps upload the project if anyone else is mad enough to dive in.

Skean answered 18/1 at 11:40 Comment(0)
B
0

Scoter

It's better if you just make a new project with just the enemy and a blank statemachine, then add your problematic state to see where it got stuck.

I find, usually, if i can not make it work with just a few states then it's my lack of understanding of the statemachine that is the problem.

Braille answered 19/1 at 1:26 Comment(0)

© 2022 - 2024 β€” McMap. All rights reserved.