Can I use metadata to handle move_and_slide collisions?

Godot Ver 4.4: Node2D

Now, I’ve got the silliest question. I’m (still) not amazing with GDScript, so with my latest rendition of trying to make a new project and sticking to it, I got stuck with detecting collisions. I’ve never coded 2D movement, unfortunately, and I can’t get any of the normal detection methods to work (mostly because I’m not referencing the base objects correctly, leading to null errors). I really just need help finding the correct documentation on collision detection and how to affect objects the script isn’t attached to. That doesn’t stop me from doing it in the weirdest way possible though!

So, is there a way, be it efficient or not, to scan the collided object (with the player) metadata and use the metadata to determine how the player should be nudged back into play? Basically, can I base all of my collision physics on metadata?

here's the script I'm trying to change, btw. just in case.
extends CharacterBody2D

var ActiveMoveX = 0

func _process(delta: float) -> void: 
	var speed = delta * 1
	if Input.is_action_pressed("ui_right"):
		velocity.x += 5 * speed
		ActiveMoveX += 1
	if Input.is_action_pressed("ui_left"):
		velocity.x -= 5 * speed
		ActiveMoveX -= 1
	velocity.x = clamp(velocity.x, -50, 50)
	print(str(delta) + "; " + str(velocity))
	if ActiveMoveX == 0:
		velocity.x = 0
	ActiveMoveX = 0

func _physics_process(_delta: float) -> void:
	move_and_slide()

You can use get_last_slide_collision(), or get_slide_collision(index) to get collision data from move_and_slide

func _physics_process(_delta: float) -> void:
	var direction: float = Input.get_axis("ui_left", "ui_right")
	velocity.x = direction * speed

	move_and_slide()

	var last_collision := get_last_slide_collision()
	if last_collision:
		var collided_object: Object = last_collision.get_collider()

This doesn’t work. I added gravity after just testing it by itself and trying to collide with a wall (which did not activate my prints nor give a result that is not <Object#null>), and I was unable to get a print that isn’t null.

Modified answer code
extends CharacterBody2D
func _physics_process(_delta: float) -> void:
	var direction: float = Input.get_axis("ui_left", "ui_right")
	velocity.x = direction * 10
	velocity.y = 10
	move_and_slide()
	var last_collision := get_last_slide_collision()
	if last_collision:
		var collided_object: Object = last_collision.get_collider()
		velocity.y = 0
		print("co " + str(collided_object))
	print("lo" + str(last_collision))

(all other code was put into a comment, so this was the only code active in the doc)

I am still curious about doing the collision detection via metadata, but this may not have worked due to how my objects are structured.

Perhaps I’m not using the right nodes? I structured it like how I would structure for 3D physics, but that may not be the correct way for this collision detection method. Also, what code would I use to find the metadata of a collided object?

Your floor is an Area2D it only detects bodies, it does not collide with them. Use a StaticBody2D to block collisions with CharacterBody2D nodes.

You keep referring to “metadata” but that is a different system in Godot unrelated to collisions.

yes, in fact meta_data is one of the best ways to do it because it’s safe and requires no type conversion.

I would use a collision signal. unfortunately, CharacterBody2D does not have a signal for this. So what we do is add an Area2D for collision detection.

so either connect it from the node panel in the inspector, which will create a new function, or do it from code.

from code, if Area2D is child of characterbody2d:

func _ready() -> void:
	$Area2D.body_entered.connect(my_collide_function)

func my_collide_function(body : Node2D) -> void:
	#body is the node we just collided with
	ActiveMoveX += body.get_meta("bouncy", 0)#0 is a default value. get_meta will return 0 if the node does not have a "bouncy" metadata
2 Likes
So, right now, my code looks like this
extends CharacterBody2D

var ActiveMoveX = 0
var CollisionPush = Vector2()

func _process(delta: float) -> void: 
	var speed = delta * 1
	if Input.is_action_pressed("ui_right"):
		velocity.x += 5 * speed
		ActiveMoveX += 1
	if Input.is_action_pressed("ui_left"):
		velocity.x -= 5 * speed
		ActiveMoveX -= 1
	velocity.x = clamp(velocity.x, -50, 50)
#	print(str(delta) + "; " + str(velocity))
#	$Area2D.body_entered.connect(collision)
	if ActiveMoveX == 0:
		velocity.x = 0
	ActiveMoveX = 0

func _physics_process(_delta: float) -> void:
	_ready()
	move_and_slide()

#func collision(body : Node2D) -> void:
#	#body is the node we just collided with
#	if body.get_meta("WallLeft", false) and velocity.x <= 0:
#		ActiveMoveX = 0

func _ready() -> void:
	$Area2D.body_entered.connect(my_collide_function)

func my_collide_function(body : Node2D) -> void:
	#body is the node we just collided with
	print(str(body.get_meta("WallLeft", 0)))#0 is a default value. get_meta will return 0 if the node does not have a "bouncy" metadata

I tried two ways of putting this in, the first I commented out (there was no notable difference with or without it) and the second is basically just a copy-paste except I call the _ready function in _physics_process (not calling _ready again results in no change). Trying to do this second one gave me the following every single frame.

Just for the sake of it, I decided to throw a static body into the node tree of the wall, but that didn’t change anything

_ready() runs once when the game starts, you should not call it yourself, especially not every frame/physics process.

Your new player node doesn’t work, it has a warning that will tell you it doesn’t have a collision shape, so it can’t collide with anything. The Area2D as a child of player isn’t a collision shape, you will still need a collision shape for the player as a direct child.

1 Like

I called _ready simply because it didn’t do anything when I did not. I’ll move it out and try again with area2D on it’s own.

revised code before i go to sleep
extends Area2D

var ActiveMoveX = 0
var pos = Vector2(0,0)

func _process(delta: float) -> void: 
	var speed = delta * 1
	if Input.is_action_pressed("ui_right"):
		pos.x += 5 * speed
		ActiveMoveX += 1
	if Input.is_action_pressed("ui_left"):
		pos.x -= 5 * speed
		ActiveMoveX -= 1
	pos.x = clamp(pos.x, -50, 50)
#	print(str(delta) + "; " + str(pos))
	body_entered.connect(collision) #This needs to be revised - It doesn't like calling Area2D
	if ActiveMoveX == 0:
		pos.x = 0
	ActiveMoveX = 0
	position += pos

func _physics_process(_delta: float) -> void:
	pass

func collision(body : Area2D) -> void:
	#body is the node we just collided with
	if body.get_meta("WallLeft", false) and pos.x <= 0:
		ActiveMoveX = 0


I’ll start on this again tomorrow. I should say that there simply isn’t a ready function because this is the modified version of the provided code. I’ll likely throw it back in tomorrow.

you had to chose one but did both. Imagine yourself trying to force two plugs into the same socket, that’s what you did.

NO. ready and physics_process are called by the engine, all you are supposed to do is override them. do not call ready ever.

you connected the signal, left it connected, then added code that connects the signal that is already connected, to no function because you commented it, with a function with a name different to the name of the function that is connected.

just delete the node and start over at this point.
you need to go to signals and disconnect the signal, then you can use my code, but I don’t trust you to do it right and not get more errors.

listen to @gertkeno.

that is an Area2D, the code is for a CharacterBody2D with an Area2D child.
stop deleting essential nodes, they do different things and are both needed.

Only about half of that made any sense, if I’m being honest. I did not delete my node or use any of your code as it didn’t make sense to me nor do I know how to push it into my current movement code. I did use the power of arrays, though. That, well, that actually worked for me.

Collision worthy code
extends Area2D

var ActiveMoveX = 0
var pos = Vector2(0,0)
var lastColliedObjects = Array()
var TimeDelay = 0.3
var FixedDelay = 0.3

func _process(delta: float) -> void: 
	var speed = delta * 1
	if str(lastColliedObjects) != "[]" and TimeDelay > 0:
		TimeDelay -= delta
	if Input.is_action_pressed("ui_right"):
		pos.x += 5 * speed
		ActiveMoveX += 1
	if Input.is_action_pressed("ui_left"):
		pos.x -= 5 * speed
		ActiveMoveX -= 1
	pos.x = clamp(pos.x, -50, 50)
#	print(str(delta) + "; " + str(pos)) 
#	print(str(lastColliedObjects) + "; " + str(TimeDelay))
	isColliding()
	if ActiveMoveX == 0:
		pos.x = 0
	ActiveMoveX = 0
	position += pos

func _physics_process(_delta: float) -> void:
	pass

func isColliding():
	for list in get_overlapping_areas(): #Give me an array of objects 
		lastColliedObjects = list
		TimeDelay = FixedDelay
	if TimeDelay <= 0:
		lastColliedObjects = Array()
	if str(lastColliedObjects) != "[]":
		if str(lastColliedObjects.get_meta_list()).contains("WallLeft") and pos.x <= 0:
			ActiveMoveX = 0

I should note that this stops the player, not that this is copy-paste worthy. Currently, this code simply prevents the player from moving further into the wall, it does not push the player out nor does it stop the player before they enter the wall. It…still needs work. HOWEVER. It does grab the object IDs themselves, so I imagine it could ultimately work with enough research.

it also uses an Area2D as the player, and no CharacterBody2D

I’m going to let this sit a day longer to see if I can get any further responses, but I’ll likely clean it up, post a final version of the code, and mark a solution tomorrow.

If all you want to do is have collisions in 2D then use a CharacterBody2D for the player and StaticBody2D for the floor/walls.

Your player script could be this short, the other nodes do not need a script. To get collided objects use get_slide_collision as I stated previously.

extends CharacterBody2D

@export var speed: float = 300

func _physics_process(delta: float) -> void:
	if not is_on_floor():
		velocity += get_gravity() * delta

	var direction := Input.get_axis("ui_left", "ui_right")
	velocity.x = direction * speed

	move_and_slide()

	# to get collisions using `get_slide_collision`
	var last_collisions: Array = []
	for index in get_slide_collision_count():
		var collision := get_slide_collision(index)
		last_collisions.append(collision.get_collider())

Here’s what the scene should look like

You could continue with your way, but you are doing this in the hardest way possible against the grain of the game engine.

1 Like

Yeah…
I never really wanted to do things efficiently. This is why this is a silly question - it shouldn’t have been done this way. But I was curious enough to ask if it was possible to do with metadata, no matter how hard, tedious, inefficient, or otherwise terrible doing it that way may be (hence why it’s mentioned in the original issue).

And now we are here.

Thank you for pointing out the correct way, though. When I get a proper solution formatted tomorrow, I’ll make sure to quote this way as well. Thank you for your help, it did genuinely lead me to figure it out!

One last urm acktually :point_up: :nerd_face:

Metadata is not related to collision, it is a small system to store various data on objects without requiring a script. Collision cannot be done with get_meta and set_meta, those are the only “metadata” functions. The docs say this on metadata:

Lastly, every object can also contain metadata (data about data). set_meta() can be useful to store information that the object itself does not depend on. To keep your code clean, making excessive use of metadata is discouraged.

1 Like

yeah, thats fair

So, to answer the original question, yes. Yes you can. Should you? Please don’t.

The 'finalized' version of the function I used to detect metadata of collided objects
func isColliding():
	var lastColliedObjects = Array()
	for list in get_overlapping_areas(): #Give me an array of objects 
		lastColliedObjects = list
	if str(lastColliedObjects) != "[]":
		if str(lastColliedObjects.get_meta_list()).contains("WallLeft") and pos.x <= 0:
			ActiveMoveX = 0
			push.x = 1
		if str(lastColliedObjects.get_meta_list()).contains("WallRight") and pos.x <= 0:
			ActiveMoveX = 0
			push.x = -1
		if str(lastColliedObjects.get_meta_list()).contains("Ceiling") and pos.x <= 0:
			ActiveMoveY = 0
			push.y = -1
		if str(lastColliedObjects.get_meta_list()).contains("Floor") and pos.x <= 0:
			ActiveMoveY = 0
			push.y = 1
	else:
		push.x = 0

Now, this isn’t to say its not useful, in fact I’d say it could work for another movement mechanic. But for base movement? I’d honestly probably have to spend a week or two before the code I wrote actually resembles the normal and proper way to do it. In a practical sense, I’d only need to fix the floor being a bouncy mess, buuut that’s not smooth movement in of itself.

The function works in three steps (if we only look at metadata):

  1. We first use the code for list in get_overlapping_areas(): to make an array and assign the ‘list’ variable to another variable.
  2. Then, we get the available metadata using <your_variable_here>.get_meta_list() to find what metadata options there are. Please note that this doesn’t show if they are on or off, just if they are used in the collided objects.
  3. Finally, we check if the metadata name we want is on the object using str(<your_variable_here>.get_meta_list()).contains("<your_metadata_name_here>")

This isn’t fully utilizing metadata, so it’s not an amazing way of doing it, but it does work. Somehow.

But what if you just want the normal, actually good way of checking collisions? Not any of the metadata nonsense that I used? Then you should check the post that gertkeno made.

Here's the code from their reply

They finish their reply by simply saying “You could continue with your way, but you are doing this in the hardest way possible against the grain of the game engine.”

So, if there is no express purpose for doing it with metadata instead of the code above, then you should probably just follow that code instead.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.