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.
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)extendsNode3D# when the scene is loadedfunc_ready():# connect to the state_changed signal from pp_entity_nodevar 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)
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)extendsNode3D# create a variable to store the PPRootNodevar pp_root_node# preload all the scenes for use by this scriptvar 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 charactervar 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 loadedfunc_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 nodefunc_on_new_player_entity(entity_id,state):...# create an entity instance matching its type, and add it as a child nodefunc_on_new_entity(entity_id,state):...# remove an entity instance, from the current child nodesfunc_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 nodefunc_on_new_player_entity(entity_id,state):# create the player instancevar player_instance = player_scene.instantiate()# validate that the player scene has a PPEntityNodevar pp_entity_node = player_instance.get_node_or_null("PPEntityNode")if pp_entity_node: pp_entity_node.entity_id = entity_idelse:print("PPEntityNode not found in the player instance")# add the player as a child of the root nodeadd_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 nodefunc_on_new_entity(entity_id,state):# get the entity scene based on entity typevar entity_scene = scene_map.get(state.type)# validate that the entity type has a matching sceneifnot entity_scene:print("matching scene not found: "+ state.type)# create an entity instancevar entity_instance = entity_scene.instantiate()# validate that the entity scene has a PPEntityNodevar pp_entity_node = entity_instance.get_node_or_null("PPEntityNode")if pp_entity_node: pp_entity_node.entity_id = entity_idelse: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 nodesfunc_on_remove_entity(entity_id):for child inget_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 removedif pp_entity_node and pp_entity_node.entity_id == entity_id:# delink the child from the parentremove_child(child)# remove the child from processing child.queue_free()print('Entity '+ entity_id +' removed')returnprint('Entity '+ entity_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)extendsNode3D# create a variable to store the PPRootNodevar pp_root_node# preload all the scenes for use by this scriptvar 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 main player charactervar scene_map ={"cat": cat_scene,"tree": tree_scene,"player": other_player_scene,}# when the scene is loadedfunc_ready():# 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") # 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)# authorise your connection to the game server #(Anonymous Auth uses empty strings as parameters) pp_root_node.authenticate_player("","")# create a new main player instance, and add it as a child nodefunc_on_new_player_entity(entity_id,state):# create the player instancevar player_instance = player_scene.instantiate()# validate that the player scene has a PPEntityNodevar pp_entity_node = player_instance.get_node("PPEntityNode")if pp_entity_node: pp_entity_node.entity_id = entity_idelse:print("PPEntityNode not found in the player instance")# add the player as a child of the root nodeadd_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 nodefunc_on_new_entity(entity_id,state):# get the entity scene based on entity typevar entity_scene = scene_map.get(state.type)# validate that the entity type has a matching sceneifnot entity_scene:print("matching scene not found: "+ state.type)# create an entity instancevar entity_instance = entity_scene.instantiate()# validate that the entity scene has a PPEntityNodevar pp_entity_node = entity_instance.get_node("PPEntityNode")if pp_entity_node: pp_entity_node.entity_id = entity_idelse: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 nodesfunc_on_remove_entity(entity_id):for child inget_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 removedif pp_entity_node and pp_entity_node.entity_id == entity_id:# delink the child from the parentremove_child(child)# remove the child from processing child.queue_free()print('Entity '+ entity_id +' removed')returnprint('Entity '+ entity_id +' not found to remove')
Full root.gd script code (no comments)
extendsNode3Dvar pp_root_nodevar 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 scene_map ={"cat": cat_scene,"tree": tree_scene,"player": other_player_scene,}func_ready(): pp_root_node =get_tree().current_scene.get_node('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.authenticate_player("","")func_on_new_player_entity(entity_id,state):var player_instance = player_scene.instantiate()var pp_entity_node = player_instance.get_node("PPEntityNode")if pp_entity_node: pp_entity_node.entity_id = entity_idelse: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)ifnot entity_scene:print("matching scene not found: "+ state.type)var entity_instance = entity_scene.instantiate()var pp_entity_node = entity_instance.get_node("PPEntityNode")if pp_entity_node: pp_entity_node.entity_id = entity_idelse: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 inget_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')returnprint('Entity '+ entity_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)extendsNode3D# create a variable to store the PPRootNodevar pp_root_node# create a variable to handle movement speedvar speed =5.0# when the scene is loadedfunc_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") func_process(delta:float) ->void:# get the raw input valuesvar 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 playervar movement = input_direction * speed * deltatranslate(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": input_direction[0] * speed * delta,"y": -input_direction[2] * speed * delta, "z": 0,})
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:
gitadd.gitcommit-m"Configure game entities and logic for Planetary Processing"gitpush
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!