Follow these steps to quickly set up and start using Planetary Processing with your Godot game. For more detailed information on both the Godot SDK and the server-side API, please visit our documentation.
Godot Version
We recommend using Godot's most recent release version. If you are using an older version, we recommend a minimum of v4.2.1 for a successful integration.
You will also need the most recent compatible version of the .NET SDK.
Provide the details of your game. Upon its creation, you will be taken to your Game Dashboard.
For this quick start, we will be using Anonymous Auth, which allows players to connect without a username and password. To enable this, navigate to the settings section of your Game Dashboard and enable Anonymous Auth as the Player Authentication Type within Game Settings.
Clone your Game Repository
Clone the git repository as listed in your game dashboard - this is your Planetary Processing backend code.
Select the ‘Project Settings’ from the ‘Project’ tab in the topbar.
Navigate to the 'Plugins' tab and enable the Planetary Processing plugin.
Setting Up the PP Root Node
Create a Node3D root node in the Scene panel, and save it as 'main'.
Add a PPRootNode as child node to your scene's root.
In the Inspector panel of the PPRootNode, enter the Game ID of your game. This is a number which can be found on your game dashboard, next to your game’s name and repo link.
Select 'Tools > C# > Create C# Solution' from the ‘Project’ tab in the topbar.
This creates a .csproj and a .sln file in the root folder of your project. (Tabbing in and out of the Godot Editor will refresh the FileSystem Panel to show the new .csproj file).
In the Inspector panel of the PPRootNode, press the "Add Csproj Reference" button.
Setting Up your Starting Scene
Select the ‘Project Settings’ from the ‘Project’ tab in the topbar.
In the 'General' tab, find the 'Application > Run' section.
Set the Main Scene to be 'main.tscn'.
Creating a player character
Create a new scene to represent your player, and save it as 'player'.
Add the PPEntityNode as a direct child of the root node of your scene.
In the Inspector for the PPEntityNode, input player as the Type.
Add a MeshInstance to the root node of your scene.
In the Inspector assign the Mesh parameter a 'New BoxMesh' to make it visible.
Add a Camera3D to the root node of your scene and position it to look at the player.
Creating entities
Every Entity has a ‘Type’. These Entity Types are defined in the backend code downloaded from your game repository.
Make note of the names of the .lua files inside the ‘entity’ folder. These are your Entity Types.
For the demo game repository, the Entity Types: cat, tree, and player are used. Entities in Godot are formed from scenes containing the PPEntityNode and a valid Type parameter.
Create a scene to represent one of your entities, such as a cat, and save it as 'cat'.
Add the PPEntityNode as a direct child of the root node of your scene.
In the Inspector for the PPEntityNode, input a Type. Entering ‘cat’ will sync this prefab with the ‘cat.lua’ entity in the server-side code.
Add a MeshInstance to the root node of your scene.
In the Inspector assign the Mesh parameter a 'New SphereMesh', to make it visible.
Repeat the process for trees and give them a CylinderMesh.
Creating other players
Other players are created in the same way as most entities, however they share their type 'player' with the player character.
Create a scene to represent other players, and save it as 'other_player'.
Add the PPEntityNode as a direct child of the root node of your scene.
In the Inspector for the PPEntityNode, input the Type 'player'.
Add a MeshInstance to the root node of your scene.
In the Inspector assign the Mesh parameter a 'New CapsuleMesh', to make it visible.
Moving entities
The PPRootNode emits a signal when entities change position or state on the server. These signals can be used to update an entity's position with new x, y, and z values.
Attach a new script called 'entity_movement.gd' to the root node parameter (not PPEnityNode) of each of your entity scenes (except the player character scene), with the following code.
# entity_movement.gd script
# extend the functionality of your root node (here Node3D)
extends Node3D
# when the scene is loaded
func _ready():
# connect to the state_changed signal from pp_entity_node
var pp_entity_node= get_node_or_null("PPEntityNode")
if pp_entity_node:
pp_entity_node.state_changed.connect(_on_state_changed)
else:
print("PPEntityNode not found")
func _on_state_changed(state):
# set the entity's position, using the server's values
# NOTE: Planetary Processing uses 'y' for depth in 3D games, and 'z' for height. The depth axis is also inverted.
# To convert, set Godot's 'y' to negative, then swap 'y' and 'z'.
global_transform.origin = Vector3(state.x, state.z, -state.y)
entity_movement.gd script (no comments)
extends Node3D
func _ready():
var ppEntityNode = get_node_or_null("PPEntityNode")
if pp_entity_node:
pp_entity_node.state_changed.connect(_on_state_changed)
else:
print("PPEntityNode not found")
func _on_state_changed(state):
global_transform.origin = Vector3(state.x, state.z, -state.y)
entity_movement.gd script (2D)
extends Node2D
func _ready():
var pp_entity_node= get_node_or_null("PPEntityNode")
if pp_entity_node:
pp_entity_node.state_changed.connect(_on_state_changed)
else:
print("PPEntityNode not found")
func _on_state_changed(state):
global_transform.origin = Vector2(state.x, -state.y)
Spawning entities
The PPRootNode emits a signal when new entities are added and removed on the server. These signals can be used to create or destroy entities in game.
Attach a new script called 'root.gd' to the root node parameter (not PPRootNode) of your main scene, with the following code.
Use this code to extend the root node's functionality and prepare the entity scenes for use in the main scene.
# root.gd script
# extend the functionality of your root node (here Node3D)
extends Node3D
# create a variable to store the PPRootNode
var pp_root_node
# preload all the scenes for use by this script
var player_scene = preload("res://player.tscn")
var cat_scene = preload("res:///cat.tscn")
var tree_scene = preload("res://tree.tscn")
var other_player_scene = preload("res://other_player.tscn")
# define all the scenes by their entity type, except the player character
var scene_map = {
"cat": cat_scene,
"tree": tree_scene,
"player": other_player_scene,
}
Create a _ready() function and start listening for trigger events sent from the server to the PPRootNode.
# when the scene is loaded
func _ready():
# access the PPRootNode from the scene's node tree
pp_root_node = get_tree().current_scene.get_node_or_null('PPRootNode')
assert(pp_root_node, "PPRootNode not found")
# using signals from the PPRootNode,
# trigger functions for entity spawning/despawning/positioning
pp_root_node.new_player_entity.connect(_on_new_player_entity)
pp_root_node.new_entity.connect(_on_new_entity)
pp_root_node.remove_entity.connect(_on_remove_entity)
# create a new player instance, and add it as a child node
func _on_new_player_entity(entity_id, state):
...
# create an entity instance matching its type, and add it as a child node
func _on_new_entity(entity_id, state):
...
# remove an entity instance, from the current child nodes
func _on_remove_entity(entity_id):
...
Create a function to handle the player character's spawning.
# create a new player instance, and add it as a child node
func _on_new_player_entity(entity_id, state):
#check if the player instance already exists, and exit function if so
for instance in get_children():
var pp_entity = instance.get_node_or_null('PPEntityNode')
if pp_entity and "entity_id" in pp_entity:
if pp_entity.entity_id == entity_id:
return
# create the player instance
var player_instance = player_scene.instantiate()
# validate that the player scene has a PPEntityNode
var pp_entity_node = player_instance.get_node_or_null("PPEntityNode")
if pp_entity_node:
pp_entity_node.entity_id = entity_id
else:
print("PPEntityNode not found in the player instance")
# add the player as a child of the root node
add_child(player_instance)
# position the player based on its server location
# NOTE: Planetary Processing uses 'y' for depth in 3D games, and 'z' for height. The depth axis is also inverted.
# To convert, set Godot's 'y' to negative, then swap 'y' and 'z'.
player_instance.global_transform.origin = Vector3(state.x, state.z, -state.y)
Create a function to handle entity spawning.
# create an entity instance matching its type, and add it as a child node
func _on_new_entity(entity_id, state):
# get the entity scene based on entity type
var entity_scene = scene_map.get(state.type)
# validate that the entity type has a matching scene
if not entity_scene:
print("matching scene not found: " + state.type)
# create an entity instance
var entity_instance = entity_scene.instantiate()
# validate that the entity scene has a PPEntityNode
var pp_entity_node = entity_instance.get_node_or_null("PPEntityNode")
if pp_entity_node:
pp_entity_node.entity_id = entity_id
else:
print("PPEntityNode not found in the instance")
# add the entity as a child of the root node
add_child(entity_instance)
# position the entity based on its server location
# NOTE: Planetary Processing uses 'y' for depth in 3D games, and 'z' for height. The depth axis is also inverted.
# To convert, set Godot's 'y' to negative, then swap 'y' and 'z'.
entity_instance.global_transform.origin = Vector3(state.x, state.z, -state.y)
Create a function to handle entity removal.
# remove an entity instance, from the current child nodes
func _on_remove_entity(entity_id):
for child in get_children():
# check if the child is an entity
var pp_entity_node = child.get_node_or_null("PPEntityNode")
# check if it matches the entity_id to be removed
if pp_entity_node and pp_entity_node.entity_id == entity_id:
# delink the child from the parent
remove_child(child)
# remove the child from processing
child.queue_free()
print('Entity ' + entity_id + ' removed')
return
print('Entity ' + entity_id + ' not found to remove')
Creating Chunk Scenes
If you wish to store data in game world chunks, you can add chunk objects to your game as scenes. These chunks will be spawned into the world as invisible nodes in their proper location, and will load and unload as the player moves around the world.
Chunk scenes are optional and you can create a game without them if you wish.
Create a scene and save it as 'chunk'.
Add the PPChunkNode as a direct child of the root node of your scene.
Attach a new script called 'chunk_update.gd' to the root node parameter (not PPChunkNode) with the following code:
# chunk_update.gd script
# extend the functionality of your root node (here Node3D)
extends Node3D
var pp_root_node
# when the scene is loaded
func _ready():
# connect to the state_changed signal from pp_chunk_node
var pp_chunk_node= get_node_or_null("PPChunkNode")
pp_root_node = get_tree().current_scene.get_node_or_null('PPRootNode')
if pp_chunk_node:
pp_chunk_node.state_changed.connect(_on_state_changed)
else:
print("PPChunkNode not found")
func _on_state_changed(state):
pass
# Add code to act if chunk data changes, if necessary
chunk_update.gd script (no comments)
extends Node3D
var pp_root_node
func _ready():
var pp_chunk_node= get_node_or_null("PPChunkNode")
pp_root_node = get_tree().current_scene.get_node_or_null('PPRootNode')
if pp_chunk_node:
pp_chunk_node.state_changed.connect(_on_state_changed)
else:
print("PPChunkNode not found")
func _on_state_changed(state):
pass
Spawning Chunks
The PPRootNode emits a signal when new chunks are added and removed on the server. These signals can be used to create or destroy chunks in game.
Go to your main scene and click on PPRootNode. Find ChunkSize in the inspector and set it to the same value as your game's chunk size (which can be found in the settings of your game on the Planetary Processing panel).
Go to 'root.gd', connected to the root node parameter (not PPRootNode) of your main scene, and update it with the following code. New lines are commented.
Use this code to extend the root node's functionality and prepare the chunk scenes for use in the main scene.
# preload all the scenes for use by this script
var player_scene = preload("res://player.tscn")
var cat_scene = preload("res:///cat.tscn")
var tree_scene = preload("res://tree.tscn")
var other_player_scene = preload("res://other_player.tscn")
### New Line ###
var chunk_scene = preload("res://chunk.tscn")
# define all the scenes by their entity type, except the player character
var scene_map = {
"cat": cat_scene,
"tree": tree_scene,
"player": other_player_scene,
### New Line ###
"chunk": chunk_scene
}
Change the _ready() function to include chunk events:
func _ready():
# access the PPRootNode from the scene's node tree
pp_root_node = get_tree().current_scene.get_node_or_null('PPRootNode')
assert(pp_root_node, "PPRootNode not found")
# using signals from the PPRootNode,
# trigger functions for entity spawning/despawning/positioning
pp_root_node.new_player_entity.connect(_on_new_player_entity)
pp_root_node.new_entity.connect(_on_new_entity)
pp_root_node.remove_entity.connect(_on_remove_entity)
### New Lines ###
pp_root_node.new_chunk.connect(_on_new_chunk)
pp_root_node.remove_chunk.connect(_on_remove_chunk)
# authorise your connection to the game server
#(Anonymous Auth uses empty strings as parameters)
pp_root_node.authenticate_player("", "")
print("authenticated and starting")
Create a function to handle chunk spawning:
#create an chunk instance matching its type, and add it as a child node
func _on_new_chunk(chunk_id, state):
if not chunk_scene:
print("matching scene not found: chunk_scene" )
# create an chunk instance
var chunk_instance = chunk_scene.instantiate()
# validate that the entity scene has a PPEntityNode
var pp_chunk_node = chunk_instance.get_node_or_null("PPChunkNode")
if pp_chunk_node:
pp_chunk_node.chunk_id = chunk_id
else:
print("PPChunkNode not found in the instance")
# add the chunk as a child of the root node
add_child(chunk_instance)
# position the entity based on its server location
# NOTE: Planetary Processing uses 'y' for depth in 3D games, and 'z' for height. The depth axis is also inverted.
# To convert, set Godot's 'y' to negative, then swap 'y' and 'z'.
chunk_instance.global_transform.origin = Vector3((state.x * pp_root_node.Chunk_Size), 0, -(state.y * pp_root_node.Chunk_Size))
Create a function to handle chunk removal:
# remove an chunk instance, from the current child nodes
func _on_remove_chunk(chunk_id):
for child in get_children():
# check if the child is a chunk
var pp_chunk_node = child.get_node_or_null("PPChunkNode")
# check if it matches the chunk_id to be removed
if pp_chunk_node and pp_chunk_node.chunk_id == chunk_id:
# delink the child from the parent
remove_child(child)
# remove the child from processing
child.queue_free()
print('Chunk ' + chunk_id + ' removed')
return
print('Chunk ' + chunk_id + ' not found to remove')
Connecting the player
Add the following line of code to the end of your _ready() function in 'root.gd'. This will allow the player to join and interact with the game world.
# authorise your connection to the game server
#(Anonymous Auth uses empty strings as parameters)
pp_root_node.authenticate_player("", "")
Full root.gd script code (commented)
# root.gd script
# extend the functionality of your root node (here Node3D)
extends Node3D
# create a variable to store the PPRootNode
var pp_root_node
# preload all the scenes for use by this script
var player_scene = preload("res://player.tscn")
var cat_scene = preload("res:///cat.tscn")
var tree_scene = preload("res://tree.tscn")
var other_player_scene = preload("res://other_player.tscn")
var chunk_scene = preload("res://chunk.tscn")
# define all the scenes by their entity type, except the player character
var scene_map = {
"cat": cat_scene,
"tree": tree_scene,
"player": other_player_scene,
"chunk": chunk_scene
}
# when the scene is loaded
func _ready():
# access the PPRootNode from the scene's node tree
pp_root_node = get_tree().current_scene.get_node_or_null('PPRootNode')
assert(pp_root_node, "PPRootNode not found")
# using signals from the PPRootNode,
# trigger functions for entity spawning/despawning/positioning
pp_root_node.new_player_entity.connect(_on_new_player_entity)
pp_root_node.new_entity.connect(_on_new_entity)
pp_root_node.remove_entity.connect(_on_remove_entity)
pp_root_node.new_chunk.connect(_on_new_chunk)
pp_root_node.remove_chunk.connect(_on_remove_chunk)
# authorise your connection to the game server
#(Anonymous Auth uses empty strings as parameters)
pp_root_node.authenticate_player("", "")
print("authenticated and starting")
# create a new player instance, and add it as a child node
func _on_new_player_entity(entity_id, state):
#check if the player instance already exists, and exit function if so
for instance in get_children():
var pp_entity = instance.get_node_or_null('PPEntityNode')
if pp_entity and "entity_id" in pp_entity:
if pp_entity.entity_id == entity_id:
return
# create the player instance
var player_instance = player_scene.instantiate()
# validate that the player scene has a PPEntityNode
var pp_entity_node = player_instance.get_node_or_null("PPEntityNode")
if pp_entity_node:
pp_entity_node.entity_id = entity_id
print("making new player entity")
else:
print("PPEntityNode not found in the player instance")
# add the player as a child of the root node
add_child(player_instance)
# position the player based on its server location
# NOTE: Planetary Processing uses 'y' for depth in 3D games, and 'z' for height. The depth axis is also inverted.
# To convert, set Godot's 'y' to negative, then swap 'y' and 'z'.
player_instance.global_transform.origin = Vector3(state.x, state.z, -state.y)
#create an entity instance matching its type, and add it as a child node
func _on_new_entity(entity_id, state):
# get the entity scene based on entity type
var entity_scene = scene_map.get(state.type)
# validate that the entity type has a matching scene
if not entity_scene:
print("matching scene not found: " + state.type)
# create an entity instance
var entity_instance = entity_scene.instantiate()
# validate that the entity scene has a PPEntityNode
var pp_entity_node = entity_instance.get_node_or_null("PPEntityNode")
if pp_entity_node:
pp_entity_node.entity_id = entity_id
else:
print("PPEntityNode not found in the instance")
# add the entity as a child of the root node
add_child(entity_instance)
# position the entity based on its server location
# NOTE: Planetary Processing uses 'y' for depth in 3D games, and 'z' for height. The depth axis is also inverted.
# To convert, set Godot's 'y' to negative, then swap 'y' and 'z'.
entity_instance.global_transform.origin = Vector3(state.x, state.z, -state.y)
# remove an entity instance, from the current child nodes
func _on_remove_entity(entity_id):
for child in get_children():
# check if the child is an entity
var pp_entity_node = child.get_node_or_null("PPEntityNode")
# check if it matches the entity_id to be removed
if pp_entity_node and pp_entity_node.entity_id == entity_id:
# delink the child from the parent
remove_child(child)
# remove the child from processing
child.queue_free()
print('Entity ' + entity_id + ' removed')
return
print('Entity ' + entity_id + ' not found to remove')
#create an chunk instance matching its type, and add it as a child node
func _on_new_chunk(chunk_id, state):
if not chunk_scene:
print("matching scene not found: chunk_scene" )
# create an chunk instance
var chunk_instance = chunk_scene.instantiate()
# validate that the entity scene has a PPEntityNode
var pp_chunk_node = chunk_instance.get_node_or_null("PPChunkNode")
if pp_chunk_node:
pp_chunk_node.chunk_id = chunk_id
else:
print("PPChunkNode not found in the instance")
# add the chunk as a child of the root node
add_child(chunk_instance)
# position the entity based on its server location
# NOTE: Planetary Processing uses 'y' for depth in 3D games, and 'z' for height. The depth axis is also inverted.
# To convert, set Godot's 'y' to negative, then swap 'y' and 'z'.
chunk_instance.global_transform.origin = Vector3((state.x * pp_root_node.Chunk_Size), 0, -(state.y * pp_root_node.Chunk_Size))
# remove an chunk instance, from the current child nodes
func _on_remove_chunk(chunk_id):
for child in get_children():
# check if the child is a chunk
var pp_chunk_node = child.get_node_or_null("PPChunkNode")
# check if it matches the chunk_id to be removed
if pp_chunk_node and pp_chunk_node.chunk_id == chunk_id:
# delink the child from the parent
remove_child(child)
# remove the child from processing
child.queue_free()
print('Chunk ' + chunk_id + ' removed')
return
print('Chunk ' + chunk_id + ' not found to remove')
Full root.gd script code (no comments)
extends Node3D
var pp_root_node
var player_scene = preload("res://player.tscn")
var cat_scene = preload("res:///cat.tscn")
var tree_scene = preload("res://tree.tscn")
var other_player_scene = preload("res://other_player.tscn")
var chunk_scene = preload("res://chunk.tscn")
var scene_map = {
"cat": cat_scene,
"tree": tree_scene,
"player": other_player_scene,
"chunk": chunk_scene
}
func _ready():
pp_root_node = get_tree().current_scene.get_node_or_null('PPRootNode')
assert(pp_root_node, "PPRootNode not found")
pp_root_node.new_player_entity.connect(_on_new_player_entity)
pp_root_node.new_entity.connect(_on_new_entity)
pp_root_node.remove_entity.connect(_on_remove_entity)
pp_root_node.new_chunk.connect(_on_new_chunk)
pp_root_node.remove_chunk.connect(_on_remove_chunk)
pp_root_node.authenticate_player("", "")
print("authenticated and starting")
func _on_new_player_entity(entity_id, state):
for instance in get_children():
var pp_entity = instance.get_node_or_null('PPEntityNode')
if pp_entity and "entity_id" in pp_entity:
if pp_entity.entity_id == entity_id:
return
var player_instance = player_scene.instantiate()
var pp_entity_node = player_instance.get_node_or_null("PPEntityNode")
if pp_entity_node:
pp_entity_node.entity_id = entity_id
print("making new player entity")
else:
print("PPEntityNode not found in the player instance")
add_child(player_instance)
player_instance.global_transform.origin = Vector3(state.x, state.z, -state.y)
func _on_new_entity(entity_id, state):
var entity_scene = scene_map.get(state.type)
if not entity_scene:
print("matching scene not found: " + state.type)
var entity_instance = entity_scene.instantiate()
var pp_entity_node = entity_instance.get_node_or_null("PPEntityNode")
if pp_entity_node:
pp_entity_node.entity_id = entity_id
else:
print("PPEntityNode not found in the instance")
add_child(entity_instance)
entity_instance.global_transform.origin = Vector3(state.x, state.z, -state.y)
func _on_remove_entity(entity_id):
for child in get_children():
var pp_entity_node = child.get_node_or_null("PPEntityNode")
if pp_entity_node and pp_entity_node.entity_id == entity_id:
remove_child(child)
child.queue_free()
print('Entity ' + entity_id + ' removed')
return
print('Entity ' + entity_id + ' not found to remove')
func _on_new_chunk(chunk_id, state):
if not chunk_scene:
print("matching scene not found: chunk_scene" )
var chunk_instance = chunk_scene.instantiate()
var pp_chunk_node = chunk_instance.get_node_or_null("PPChunkNode")
if pp_chunk_node:
pp_chunk_node.chunk_id = chunk_id
else:
print("PPChunkNode not found in the instance")
add_child(chunk_instance)
chunk_instance.global_transform.origin = Vector3((state.x * pp_root_node.Chunk_Size), 0, -(state.y * pp_root_node.Chunk_Size))
func _on_remove_chunk(chunk_id):
for child in get_children():
var pp_chunk_node = child.get_node_or_null("PPChunkNode")
if pp_chunk_node and pp_chunk_node.chunk_id == chunk_id:
remove_child(child)
child.queue_free()
print('Chunk ' + chunk_id + ' removed')
return
print('Chunk ' + chunk_id + ' not found to remove')
Full root.gd script code (2D)
extends Node2D
var pp_root_node
var player_scene = preload("res://player.tscn")
var cat_scene = preload("res:///cat.tscn")
var tree_scene = preload("res://tree.tscn")
var other_player_scene = preload("res://other_player.tscn")
var chunk_scene = preload("res://chunk.tscn")
var scene_map = {
"cat": cat_scene,
"tree": tree_scene,
"player": other_player_scene,
"chunk": chunk_scene
}
func _ready():
pp_root_node = get_tree().current_scene.get_node_or_null('PPRootNode')
assert(pp_root_node, "PPRootNode not found")
pp_root_node.new_player_entity.connect(_on_new_player_entity)
pp_root_node.new_entity.connect(_on_new_entity)
pp_root_node.remove_entity.connect(_on_remove_entity)
pp_root_node.new_chunk.connect(_on_new_chunk)
pp_root_node.remove_chunk.connect(_on_remove_chunk)
pp_root_node.authenticate_player("", "")
print("authenticated and starting")
func _on_new_player_entity(entity_id, state):
for instance in get_children():
var pp_entity = instance.get_node_or_null('PPEntityNode')
if pp_entity and "entity_id" in pp_entity:
if pp_entity.entity_id == entity_id:
return
var player_instance = player_scene.instantiate()
var pp_entity_node = player_instance.get_node_or_null("PPEntityNode")
if pp_entity_node:
pp_entity_node.entity_id = entity_id
print("making new player entity")
else:
print("PPEntityNode not found in the player instance")
add_child(player_instance)
player_instance.global_transform.origin = Vector2(state.x, -state.y)
func _on_new_entity(entity_id, state):
var entity_scene = scene_map.get(state.type)
if not entity_scene:
print("matching scene not found: " + state.type)
var entity_instance = entity_scene.instantiate()
var pp_entity_node = entity_instance.get_node_or_null("PPEntityNode")
if pp_entity_node:
pp_entity_node.entity_id = entity_id
else:
print("PPEntityNode not found in the instance")
add_child(entity_instance)
entity_instance.global_transform.origin = Vector2(state.x, -state.y)
func _on_remove_entity(entity_id):
for child in get_children():
var pp_entity_node = child.get_node_or_null("PPEntityNode")
if pp_entity_node and pp_entity_node.entity_id == entity_id:
remove_child(child)
child.queue_free()
print('Entity ' + entity_id + ' removed')
return
print('Entity ' + entity_id + ' not found to remove')
func _on_new_chunk(chunk_id, state):
if not chunk_scene:
print("matching scene not found: chunk_scene" )
var chunk_instance = chunk_scene.instantiate()
var pp_chunk_node = chunk_instance.get_node_or_null("PPChunkNode")
if pp_chunk_node:
pp_chunk_node.chunk_id = chunk_id
else:
print("PPChunkNode not found in the instance")
add_child(chunk_instance)
chunk_instance.global_transform.origin = Vector2((state.x * pp_root_node.Chunk_Size), -(state.y * pp_root_node.Chunk_Size))
func _on_remove_chunk(chunk_id):
for child in get_children():
var pp_chunk_node = child.get_node_or_null("PPChunkNode")
if pp_chunk_node and pp_chunk_node.chunk_id == chunk_id:
remove_child(child)
child.queue_free()
print('Chunk ' + chunk_id + ' removed')
return
print('Chunk ' + chunk_id + ' not found to remove')
Moving the player
Player inputs need to be passed to the server.
Select the ‘Project Settings’ from the ‘Project’ tab in the topbar.
Navigate to the 'Input Map' tab and add four new actions: 'move_forward', 'move_left', 'move_backward', and 'move_right'.
Use the plus symbol to add an input event to each of them.
Attach a new script called 'player.gd' to the root node parameter (not PPEntityNode) of your player scene, with the following code.
# player.gd script
# extend the functionality of your root node (Node3D if 3D)
extends Node3D
# create a variable to store the PPRootNode
var pp_root_node
# create a variable to handle movement speed
var speed = 5.0
# when the scene is loaded
func _ready() -> void:
# access the PPRootNode from the scene's node tree
pp_root_node = get_tree().current_scene.get_node('PPRootNode')
assert(pp_root_node, "PPRootNode not found")
# connect to the state_changed signal from pp_entity_node
var pp_entity_node= get_node_or_null("PPEntityNode")
if pp_entity_node:
pp_entity_node.state_changed.connect(_on_state_changed)
else:
print("PPEntityNode not found")
func _on_state_changed(state):
# sync the player's position, using the server's values
# NOTE: Planetary Processing uses 'y' for depth in 3D games, and 'z' for height. The depth axis is also inverted.
# To convert, set Godot's 'y' to negative, then swap 'y' and 'z'.
var diff_in_position = (global_transform.origin - Vector3(state.x, state.z, -state.y)).abs()
if diff_in_position > Vector3(1,1,1):
global_transform.origin = Vector3(state.x, state.z, -state.y)
func _process(delta: float) -> void:
# get the raw input values
var input_direction = Vector3(
Input.get_action_strength("move_right") - Input.get_action_strength("move_left"),
0,
Input.get_action_strength("move_backward") - Input.get_action_strength("move_forward")
)
# calculate the input direction
input_direction = input_direction.normalized()
# move the player
var movement = input_direction * speed * delta
translate(movement)
# message the server to update the player's x and y positions
# NOTE: Planetary Processing uses 'y' for depth in 3D games, and 'z' for height. The depth axis is also inverted.
# To convert, set Godot's 'y' to negative, then swap 'y' and 'z'.
pp_root_node.message({
"x": movement[0],
"y": -movement[2],
"z": 0,
})
player.gd script (no comments)
extends Node3D
var pp_root_node
var speed = 5.0
func _ready() -> void:
pp_root_node = get_tree().current_scene.get_node('PPRootNode')
assert(pp_root_node, "PPRootNode not found")
var pp_entity_node= get_node_or_null("PPEntityNode")
if pp_entity_node:
pp_entity_node.state_changed.connect(_on_state_changed)
else:
print("PPEntityNode not found")
func _on_state_changed(state):
var diff_in_position = (global_transform.origin - Vector3(state.x, state.z, -state.y)).abs()
if diff_in_position > Vector3(1,1,1):
global_transform.origin = Vector3(state.x, state.z, -state.y)
func _process(delta: float) -> void:
var input_direction = Vector3(
Input.get_action_strength("move_right") - Input.get_action_strength("move_left"),
0,
Input.get_action_strength("move_backward") - Input.get_action_strength("move_forward")
)
input_direction = input_direction.normalized()
var movement = input_direction * speed * delta
translate(movement)
pp_root_node.message({
"x": movement[0],
"y": -movement[2],
"z": 0,
})
player.gd script (2D)
extends Node2D
var pp_root_node
var speed = 5.0
func _ready() -> void:
pp_root_node = get_tree().current_scene.get_node('PPRootNode')
assert(pp_root_node, "PPRootNode not found")
var pp_entity_node= get_node_or_null("PPEntityNode")
if pp_entity_node:
pp_entity_node.state_changed.connect(_on_state_changed)
else:
print("PPEntityNode not found")
func _on_state_changed(state):
var diff_in_position = (global_transform.origin - Vector2(state.x, -state.y)).abs()
if diff_in_position > Vector2(1,1):
global_transform.origin = Vector2(state.x, -state.y)
func _process(delta: float) -> void:
var input_direction = Vector2(
Input.get_action_strength("move_right") - Input.get_action_strength("move_left"),
Input.get_action_strength("move_backward") - Input.get_action_strength("move_forward")
)
input_direction = input_direction.normalized()
# move the player
var movement = input_direction * speed * delta
translate(movement)
pp_root_node.message({
"x": movement[0],
"y": -movement[1],
"z": 0,
})
Test your connection
You now have everything you need to establish a basic connection between Godot and the server-side demo code.
On the Planetary Processing games web panel, select your game to enter its dashboard.
Click 'Actions>Start Game' to start the server-side simulation.
Launch your game from Godot.
As your project runs, you should now be able to see the game world and all its entities. Your Planetary Processing game dashboard map should also show that a player has joined!
Editing your backend code
Using the repo we cloned earlier you can edit the behaviour of entities by changing their Lua file within the ‘entity’ directory.
You can also change how many and what entities are spawned in the init.lua file.
We recommend experimenting here to get a sense of what you can do with Planetary Processing. When you add or change entities, make sure your server-side changes match up with your game engine client.
Push your Planetary Processing backend code to the game repository
After configuring your game entities and logic, push your changes to the game repository:
git add .
git commit -m "Configure game entities and logic for Planetary Processing"
git push
Deploy Latest Version in the Web UI
Go back to your game dashboard in our web panel
From the actions menu in the top right, stop the game if it's running.
Select "Deploy Latest Version" - this will roll out your updated server-side code.
Play and update your game
Start up your game again in Godot and in the web panel, to see the changes you have made!