How can I make a character walk around a physical body in 2D?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By newold

How can I make this movement:

?

Maybe you can trace around the StaticBody with a navigational path, and have the KinematicBody move along the path, adjusting the orientation of the sprite as necessary?

Ertain | 2019-11-15 19:27

I too would like to know how this would be possible…

Using nav paths to follow, would involve creating hundreds of paths (in my case) for a platformer game, I think that might end up being a processor hog or way too complex to deal with…

My initial thought would be to create a script that would follow the edges of a collision object, but I have no idea how to go about this. Any suggestions welcome. :slight_smile:

bingbong | 2019-11-16 20:29

I think an easier way to do this than the answer below is to use the move and slide() function for this, because it takes a floor vector. You could have a variable that gets the normal direction of the collision object the character is standing on, and use that variable for the floor normal. You would also in the move and slide function tell the character to move left, and it should give you that movement.

The upside to this method is that it’s (in my opinion) easier to write, the downside is that is would probably require a rounded collision shape instead of a rectangle.

Millard | 2019-11-17 17:28

:bust_in_silhouette: Reply From: newold

I solved it this way:

(Using 4 nodes Position2D positioned counterclockwise)

And script in Node2D parent is this:

extends Node2D

var _position = 1
var speed = 1.0
var can_move = true
var current_direction = -1


func _ready() -> void:
	_position = 4 if current_direction == 1 else 1
	
func _physics_process(delta: float) -> void:
	if can_move:
		var obj = get_node("Position%s" % _position).global_position
		var _direction = obj.direction_to($enemy.global_position) * current_direction
		if current_direction == -1:
			$enemy.global_position += speed * _direction
			if ((_direction.x < 0 && $enemy.global_position.x <= obj.x) ||
				(_direction.x > 0 && $enemy.global_position.x >= obj.x) ||
				(_direction.y < 0 && $enemy.global_position.y <= obj.y) ||
				(_direction.y > 0 && $enemy.global_position.y >= obj.y)):
				$enemy.global_position = obj
				rotate_enemy(true)
		else:
			$enemy.global_position -= speed * _direction
			if ((_direction.x > 0 && $enemy.global_position.x <= obj.x) ||
				(_direction.x < 0 && $enemy.global_position.x >= obj.x) ||
				(_direction.y > 0 && $enemy.global_position.y <= obj.y) ||
				(_direction.y < 0 && $enemy.global_position.y >= obj.y)):
				$enemy.global_position = obj
				rotate_enemy(false)
			
func rotate_enemy(reverse = true):
	can_move = false
	var o = $enemy
	var p = "rotation_degrees"
	var t = 0.4
	var vi = o.rotation_degrees
	var vf = o.rotation_degrees + (-90 if reverse else 90)
	$Tween.interpolate_property(o, p, vi, vf, t, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
	$Tween.reset_all()
	$Tween.start()
	yield($Tween, "tween_all_completed")
	can_move = true
	if current_direction == -1:
		_position += 1
		if _position > 4: _position = 1
	else:
		_position -= 1
		if _position < 1: _position = 4

Result:

Thanks for your detailed solution and code, I will look into this further when I get time. For many many platforms this is still going to be several hundred position2d nodes (for my idea at least!). A neat solution though.

With move_and_slide obviously detecting the uppermost edge of a collision object, I suspect a way of detecting and following the other edges may be much more efficient, but at the moment is WAY beyond my coding skills!

(Just seen Millard’s comment above - at least my thoughts are on the right track!)

If I ever figure it out I will add my method here for everyone. :slight_smile:

bingbong | 2019-11-20 00:34

I got the same results this way:

  • Static body with rectangle collision. (my wall)
  • Kinematic body (my player)
  • and it is all ^^: i discard the position nodes
  • P.S. The static body and kinematic body must have same collision layer and kinematic body should only be able to collide with the static body to which “it is attached”.

and then the script:

extends Node2D

export(int, -1, 1) var direction = -1 # 1 => To Right, -1 => to Left
export(int, 500, 60000) var speed = 5000 # speed to move

var normals = [Vector2.DOWN, Vector2.LEFT, Vector2.UP, Vector2.RIGHT]
var velocity_normals = [Vector2.RIGHT, Vector2.DOWN, Vector2.LEFT, Vector2.UP]
var current_pos = 0
var delay_to_check_collision = 0 # When rotate player, wait x frames to calculate collision

var current_speed # script change this

func _ready() -> void:
	if direction == -1: # (going to left)
		# Update normals array with new direction (To Left)
		var v1 = normals[1]
		normals[1] = normals[3]
		normals[3] = v1
		# Update velocity_normals array with new direction (To Left)
		v1 = velocity_normals[0]
		velocity_normals[0] = velocity_normals[2]
		velocity_normals[2] = v1
	elif direction != 1: # (going to Right)
		# ensure direction == 1 if direction != -1
		direction = 1
	# Calculate initial Velocity
	current_speed = speed * velocity_normals[current_pos]

func _physics_process(delta: float) -> void:
	# move your object
	$player.move_and_slide(current_speed*delta)
	# Check if player collide with current normal
	if delay_to_check_collision == 0 && !$player.test_move($player.transform, normals[current_pos]):
		# Player no collider, update position and velocity and rotate player
		current_pos += 1
		if current_pos == 4: current_pos = 0
		# Calculate new velocity
		current_speed = speed * velocity_normals[current_pos]
		# Rotate Player:
		$player.rotation_degrees += 90 * direction # or animate this rotation
		# Set delay
		delay_to_check_collision = 60 # depends on the speed (+ speed - delay)
	elif delay_to_check_collision > 0:
		delay_to_check_collision -= 1
		

Result:

newold | 2019-11-20 19:52