,

Godot: Single Level 2D Platformer (Part 6) Moving an obstacle between points

This continues on from part 5.

Make sure that you have an obstacle or object that you wish to move. added to a scene.

In this example there is a node / scene called Obstacle which we will use.

Note you can only have one script attached to each node. Open the script called Obstacle.gd

We are going to add an array to this script which will store all of the positions to move to. We’ll call this locations. This is shown in point 5

When the node is loaded we want to collect the positions / waypoints for the node to move to.

We also need to set up the target_pos_index which is the index of the target vector in the array. This is shown in point 6.

We also need to set the initial target_pos to be the position of the obstacle which is shown at point 7.

extends RigidBody2D

# Declare member variables here. Examples:
# var a = 2
# var b = "text"

# 5 positions array to store Vector2 positions for the obstacle to move to.
# Set size to 1 for static obstacle, 2 for two positions, 3 for three positions etc.
# Position 0 will be the start position and set from the initial location in the scene
var locations = []
# 6 Target position index
var target_pos_index = 0
# 7 Variable to store the target position of the obstacle
var target_pos = global_position

# Called when the node enters the scene tree for the first time.
func _ready():
	pass


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	pass


func _on_KillArea_body_entered(body):
	# 1 Print hit message
	print("Obstacle hit player")
	# 2 Call the respawn function of the player (body) using dot notation
	body.respawn()

func _on_TopArea_body_entered(body):
	# 3 Print message to test that a collision / overlap has been detected.
	print("Player hit top of obstacle")
	# 4 Queue free destroys the node / object
	queue_free()
	pass # Replace with function body.

We need to load data into the array correctly. We will do this in the _ready function. before adding the waypoint positions onto the level scene.

The initial value stored at index 0 of the array will be the starting position of the obstacle / node. We append the initial position of the obstacle to the array as shown in point 8.

Then we set the target_pos to be the first node in the array. We will loop through these nodes to get all of the locations to move the obstacle to later. This is shown at 9.

extends RigidBody2D

# Declare member variables here. Examples:
# var a = 2
# var b = "text"

# 5 positions array to store Vector2 positions for the obstacle to move to.
# Set size to 1 for static obstacle, 2 for two positions, 3 for three positions etc.
# Position 0 will be the start position and set from the initial location in the scene
var locations = []
# 6 Target position index
var target_pos_index = 0
# 7 Variable to store the target position of the obstacle
var target_pos = global_position

# Called when the node enters the scene tree for the first time.
func _ready():
	# 8 Add the starting position to the array
	locations.append(global_position)
	# 9 Set the target position to the starting position of the array
	target_pos = locations[0]
	

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	pass


func _on_KillArea_body_entered(body):
	# 1 Print hit message
	print("Obstacle hit player")
	# 2 Call the respawn function of the player (body) using dot notation
	body.respawn()

func _on_TopArea_body_entered(body):
	# 3 Print message to test that a collision / overlap has been detected.
	print("Player hit top of obstacle")
	# 4 Queue free destroys the node / object
	queue_free()
	pass # Replace with function body.

We then add code to loop through the array of positions.

First we need to setup the scene and set up some test data / nodes / examples.

Save the script.

Open the Obstacle scene.

Add a Node and call it Waypoints, this will act as a container to store the waypoint locations for the obstacle.

Switch back to the scene view for the Level.

Right click on an obstacle and check Editable children

You should now see all of the nodes of the obstacle greyed out.

Add position node or nodes as children of the Waypoints node by right clicking on Waypoint

Here you can see two obstacles with waypoints added.

Move these nodes to locations you wish for the obstacle to move to.

The nodes will not move yet. We need to load them into the array and then add code to move them.

Switch back to the Obstacle.gd script

Add in the code at point 10. This will loop through all of the child nodes of the Waypoints node for this obstacle and then append each position to the locations array.

extends RigidBody2D

# Declare member variables here. Examples:
# var a = 2
# var b = "text"

# 5 positions array to store Vector2 positions for the obstacle to move to.
# Set size to 1 for static obstacle, 2 for two positions, 3 for three positions etc.
# Position 0 will be the start position and set from the initial location in the scene
var locations = []
# 6 Target position index
var target_pos_index = 0
# 7 Variable to store the target position of the obstacle
var target_pos = global_position

# Called when the node enters the scene tree for the first time.
func _ready():
	# 8 Add the starting position to the array
	locations.append(global_position)
	# 9 Set the target position to the starting position of the array
	target_pos = locations[0]
	
	# 10 Go through each node added as children of Waypoints and get the position
	for n in get_node("Waypoints").get_children():
		locations.append(n.global_position)


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	pass


func _on_KillArea_body_entered(body):
	# 1 Print hit message
	print("Obstacle hit player")
	# 2 Call the respawn function of the player (body) using dot notation
	body.respawn()

func _on_TopArea_body_entered(body):
	# 3 Print message to test that a collision / overlap has been detected.
	print("Player hit top of obstacle")
	# 4 Queue free destroys the node / object
	queue_free()
	pass # Replace with function body.

Now we have an array that contains the positions for the obstacle to move to.

We need to add code to move the obstacle to each waypoint.

This involves the following process.

  1. Check if the obstacle is at the target position
  2. if at the target position get the next position in the array
  3. if at the end of the array get the first item in the array instead (otherwise the program will crash)
  4. Set the target position to the next position in the array
  5. Move the obstacle towards the new target position
  6. Loop through this process until we reach the target position

This is a self contained process that will use the Rigidbody node for the obstacle.

We could put all of this in the _process function but it would be better to create our own function to handle this and call it from the _process function to keep the code cleaner. We’ll call this function _move_around_locations and pass it the time delta parameter as shown in points 11 and 12.

We will also need to set a speed for the object to move at, this is shown at point 13 where a speed variable is created and set to 25.

Now we need to add in the code to move the obstacle towards the target position. This is shown at point 14 and uses the built in move_toward function.

extends RigidBody2D

# Declare member variables here. Examples:
# var a = 2
# var b = "text"

# 5 positions array to store Vector2 positions for the obstacle to move to.
# Set size to 1 for static obstacle, 2 for two positions, 3 for three positions etc.
# Position 0 will be the start position and set from the initial location in the scene
var locations = []
# 6 Target position index
var target_pos_index = 0
# 7 Variable to store the target position of the obstacle
var target_pos = global_position

# 13 Set the speed for the obstacle to move at
var speed = 25;

# Called when the node enters the scene tree for the first time.
func _ready():
	# 8 Add the starting position to the array
	locations.append(global_position)
	# 9 Set the target position to the starting position of the array
	target_pos = locations[0]
	
	# 10 Go through each node added as children of Waypoints and get the position
	for n in get_node("Waypoints").get_children():
		locations.append(n.global_position)


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	# 11 Call the local function that handles movement
	_move_around_locations(delta)
	
	pass

# 12 Function to move the obstacle around waypoints
func _move_around_locations(delta):
	# 14 Move the obstacle towards the target position at set speed
	position = position.move_toward(target_pos, delta * speed)

func _on_KillArea_body_entered(body):
	# 1 Print hit message
	print("Obstacle hit player")
	# 2 Call the respawn function of the player (body) using dot notation
	body.respawn()

func _on_TopArea_body_entered(body):
	# 3 Print message to test that a collision / overlap has been detected.
	print("Player hit top of obstacle")
	# 4 Queue free destroys the node / object
	queue_free()
	pass # Replace with function body.

Finally we need to check the position of the obstacle ro see if we have reached the target position. Note that this example requires and exact match. You would need to modify the condition at point 15 to check if the object was within a set distance or range of the target.

Point 16 will get the index of the next location in the array.

Next we check if the index if larger than the size of the array as shown at point 17. Note here we add another 1 to the target_pos_index as size returns the total number of items in the array and the index starts at 0 so will the last index is one less that the size of the array.

If it is we reset the target_pos_index to 0, the first item in the array.

Finally at point 18 we update the target_pos to be the next position in the array.

extends RigidBody2D

# Declare member variables here. Examples:
# var a = 2
# var b = "text"

# 5 positions array to store Vector2 positions for the obstacle to move to.
# Set size to 1 for static obstacle, 2 for two positions, 3 for three positions etc.
# Position 0 will be the start position and set from the initial location in the scene
var locations = []
# 6 Target position index
var target_pos_index = 0
# 7 Variable to store the target position of the obstacle
var target_pos = global_position

# 13 Set the speed for the obstacle to move at
var speed = 25;

# Called when the node enters the scene tree for the first time.
func _ready():
	# 8 Add the starting position to the array
	locations.append(global_position)
	# 9 Set the target position to the starting position of the array
	target_pos = locations[0]
	
	# 10 Go through each node added as children of Waypoints and get the position
	for n in get_node("Waypoints").get_children():
		locations.append(n.global_position)


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	# 11 Call the local function that handles movement
	_move_around_locations(delta)
	
	pass

# 12 Function to move the obstacle around waypoints
func _move_around_locations(delta):
	# 15 Check if the obstacle is at the target position
	if position == target_pos:
		# 16 Get next position
		target_pos_index = target_pos_index + 1
		# 17 If next position falls off end of the array, reset to start of the array
		if target_pos_index + 1 > locations.size():
			target_pos_index = 0
		# 18 Update target position to new location
		target_pos = locations[target_pos_index]
		
	# 14 Move the obstacle towards the target position at set speed
	position = position.move_toward(target_pos, delta * speed)

func _on_KillArea_body_entered(body):
	# 1 Print hit message
	print("Obstacle hit player")
	# 2 Call the respawn function of the player (body) using dot notation
	body.respawn()

func _on_TopArea_body_entered(body):
	# 3 Print message to test that a collision / overlap has been detected.
	print("Player hit top of obstacle")
	# 4 Queue free destroys the node / object
	queue_free()
	pass # Replace with function body.

When we run our program now the obstacles will move

Completed Code

extends RigidBody2D

# Declare member variables here. Examples:
# var a = 2
# var b = "text"

# 5 positions array to store Vector2 positions for the obstacle to move to.
# Set size to 1 for static obstacle, 2 for two positions, 3 for three positions etc.
# Position 0 will be the start position and set from the initial location in the scene
var locations = []
# 6 Target position index
var target_pos_index = 0
# 7 Variable to store the target position of the obstacle
var target_pos = global_position

# 13 Set the speed for the obstacle to move at
var speed = 25;

# Called when the node enters the scene tree for the first time.
func _ready():
	# 8 Add the starting position to the array
	locations.append(global_position)
	# 9 Set the target position to the starting position of the array
	target_pos = locations[0]
	
	# 10 Go through each node added as children of Waypoints and get the position
	for n in get_node("Waypoints").get_children():
		locations.append(n.global_position)


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	# 11 Call the local function that handles movement
	_move_around_locations(delta)
	
	pass

# 12 Function to move the obstacle around waypoints
func _move_around_locations(delta):
	# 15 Check if the obstacle is at the target position
	if position == target_pos:
		# 16 Get next position
		target_pos_index = target_pos_index + 1
		# 17 If next position falls off end of the array, reset to start of the array
		if target_pos_index + 1 > locations.size():
			target_pos_index = 0
		# 18 Update target position to new location
		target_pos = locations[target_pos_index]
		
	# 14 Move the obstacle towards the target position at set speed
	position = position.move_toward(target_pos, delta * speed)

func _on_KillArea_body_entered(body):
	# 1 Print hit message
	print("Obstacle hit player")
	# 2 Call the respawn function of the player (body) using dot notation
	body.respawn()

func _on_TopArea_body_entered(body):
	# 3 Print message to test that a collision / overlap has been detected.
	print("Player hit top of obstacle")
	# 4 Queue free destroys the node / object
	queue_free()
	pass # Replace with function body.

You might also like