Godot

Follow these steps to quickly set up and start using Planetary Processing with your Godot game. For more detailed information on both the plugin and the server-side API, please visit our documentation.

Pre-requisites

  • Godot Engine v4.2.1+ .net version required

  • .NET SDK 6.0+ (Desktop target)

  • .NET SDK 7.0+ (Android target)

  • .NET SDK 8.0+ (iOS target)

Create a Planetary Processing Game

  1. Click Create Game in the top right

  2. Provide the details of your game, selecting Godot as your engine, and specifying whether you are making a 2D or 3D game. Our example below is for a 3D game, but the steps are easily adjusted to work for 2D. Upon creation, you will be taken to your Game Dashboard.

  3. For this quickstart, 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

  1. Clone the git repository as listed in your game dashboard - this is your Planetary Processing backend code. The repository should be cloned into a location within your Godot project.

git clone https://git.planetaryprocessing.io/git/*your-game*

Download Planetary Processing (Godot Plugin)

  1. Clone the Planetary Processing Godot plugin from GitHub.

git clone https://github.com/planetary-processing/godot-plugin planetary_processing

Integrate your Game with Planetary Processing

  1. Open your Godot project.

  2. Create the addons directory in your project if it does not exist

  3. Copy the cloned planetary_processing directory into the addons directory

  4. Enable the Planetary Processing plugin via the Godot project settings

  1. If not already setup on your project, you must trigger the creation of the C# solution via the Godot toolbar menu. This creates a .csproj and a .sln file in the root of your project.

  1. We need to add a reference to the Planetary Processing C# DLL to our .csproj file. This can either be done manually, or via the button in the PPRootNode inspector. To add manually, please ensure the following reference is added to your ItemGroup in your Godot project's .csproj file

<Reference Include="Planetary">
  <HintPath>addons/planetary_processing/sdk/csharp-sdk.dll</HintPath>
</Reference>
  1. Add the PPRootNode as a direct child of your main scene's root node.

  1. Select your root node, and enter your Game ID in the inspector menu. Your Game ID is available in your game dashboard in our web panel

  1. In the root node inspector, configure the location of your Planetary Processing repository by selecting the root directory of your cloned repository. This directory should contain init.lua and an entity directory.

  2. Open (or create) a scene representing one of your entities. In our example, we are using a scene representing trees in our world, with a simple 3D mesh and collision shape. Add the PPEntityNode as a direct child of the root node of your scene.

  1. Select your PPEntityNode and view the inspector. The type is set to match the name of the root node of the scene by default. You can adjust the type if necessary. Click Generate Lua Skeleton File to generate a lua file for the type (tree.lua) in the entity directory of your Planetary Processing repository. The tree has no specific backend behaviour, so we do not need to modify this lua script.

  1. We now need to handle adding and removing entity scenes from our world based on the Planetary Processing simulation. The PPRootNode emits a signal when new entities are added and removed. Attach a new script to the root node of your main scene (or extend your existing one), with the following code to handle adding and removing instances of the tree scene when these signals are received.

# root.gd
extends Node3D

var pp_root_node

var tree_scene = preload("res://scenes/tree.tscn")

var scene_map = {
  "tree": tree_scene,
}

func _ready():
  if Engine.is_editor_hint():
    return
  pp_root_node = get_tree().current_scene.get_node('PPRootNode')
  assert(pp_root_node, "PPRootNode not found")
  # connect to entity create / remove events
  pp_root_node.new_entity.connect(_on_new_entity)
  pp_root_node.remove_entity.connect(_on_remove_entity)

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)
  # create an instance of your entity scene
  var entity_instance = entity_scene.instantiate()
  entity_instance.global_transform.origin = Vector3(state.x, state.z, state.y) # NOTE Planetary Processing uses 'y' for depth in 3 dimensional games, and 'z' for height
  var pp_entity_node = entity_instance.get_node("PPEntityNode")
  if pp_entity_node:
    pp_entity_node.entity_id = entity_id
  else:
    print("PPEntityNode not found in the instance")
  add_child(entity_instance)

func _on_remove_entity(entity_id):
  for child in get_children():
    var pp_entity_node = child.get_node("PPEntityNode")
    if pp_entity_node and pp_entity_node.entity_id == entity_id:
      # remove your entity scene from the tree
      remove_child(child)
      child.queue_free()
      print('Entity ' + entity_id + ' removed')
      return
  print('Entity ' + entity_id + ' not found to remove')
  1. Planetary Processing allows you to position entities as descendants of your main scene in the editor and have these entities created in the backend simulation. To make use of this functionality, create instances of your entity scene (in this case tree) anywhere in your main scene tree in the editor.

  1. With your entities added to the scene, select your PPRootNode and click Generate Init.json in the inspector. This will generate an init.json file in your Planetary Processing repository directory. Planetary Processing will look for this file whenever a game is reset, creating these entities for you.

  1. Open (or create) your player scene. For this example to work, your player scene is expected to have a script attached handling player input for movement as a minimum. Add the PPEntityNode as a direct child of the root node of your player scene, and select the PPEntityNode to view the inspector.

  1. You will notice that if you click Generate Lua Skeleton File that your editor output panel will display an error message "lua file named player.lua already exists". The skeleton lua file in the repository contains an example player.lua, which handles basic player movement for both 2D and 3D games, as follows:

--- player.lua
local function init(self)
end

local function update(self, dt)
end

local function message(self, msg)
  local x, y, z = msg.Data.x, msg.Data.y, msg.Data.z
  if msg.Client then
    if msg.Data.threedee then
      self:MoveTo(x,z,y) -- NOTE Planetary Processing uses 'y' for depth in 3 dimensional games, and 'z' for height
    else
      self:Move(x,y,0)
    end
  end
end

return {init=init,update=update,message=message}
  1. Our player.lua above has a handler for messages which tell the player to move to a set of coordinates, so now we need to update our player scene to send these messages. Open the script on your player scene which handles player input for movement. We want to extend your script with some code which sends a message via your root node to the Planetary Processing backend.

# player.gd
var pp_root_node

func _ready() -> void:
  # ... your existing _ready code ...

  pp_root_node = get_tree().current_scene.get_node('PPRootNode')
  assert(pp_root_node, "PPRootNode not found")

func _process(delta: float) -> void:
  # ... your existing player movement code ...

  var current_position = global_transform.origin
  pp_root_node.message({
    "x": current_position[0],
    "y": current_position[1],
    "z": current_position[2],
    "threedee": true
  })
  1. Now that we have our player entity scene ready, we need to add to to our root node script to add our player scene to the tree when the player joins the game. The PPRootNode emits a different signal when the current player entity joining the game, called new_player_entity. Update your script as follows:

# root.gd
extends Node3D

var pp_root_node

var player_scene = preload("res://scenes/player.tscn") # load the player scene
var tree_scene = preload("res://scenes/tree.tscn")

var scene_map = {
  "tree": tree_scene,
}

func _ready():
  if Engine.is_editor_hint():
    return
  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) # connect to the new player signal
  pp_root_node.new_entity.connect(_on_new_entity)
  pp_root_node.remove_entity.connect(_on_remove_entity)

# add the new player handler
func _on_new_player_entity(entity_id, state):
  var player_instance = player_scene.instantiate()
  player_instance.global_transform.origin = Vector3(state.x, state.z, state.y) # NOTE Planetary Processing uses 'y' for depth in 3 dimensional games, and 'z' for height
  var pp_entity_node = player_instance.get_node("PPEntityNode")
  if pp_entity_node:
    pp_entity_node.entity_id = entity_id
  else:
    print("PPEntityNode not found in the player instance")
  add_child(player_instance)

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()
  entity_instance.global_transform.origin = Vector3(state.x, state.z, state.y)
  var pp_entity_node = entity_instance.get_node("PPEntityNode")
  if pp_entity_node:
    pp_entity_node.entity_id = entity_id
  else:
    print("PPEntityNode not found in the instance")
  add_child(entity_instance)

func _on_remove_entity(entity_id):
  for child in get_children():
    var pp_entity_node = child.get_node("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')
  1. We now need to handle other players in our world. First, open (or create) the scene which represents other players and add the PPEntityNode as a direct child of the root node. In our example the scene just has a simple mesh.

  1. Since our root node here is called other_player, the type in our inspector defaults to "other_player". Change the type here to be "player", since we want this scene to represent entities of type "player" in our game world.

  1. We now need to handle updates to the position of other player entities. Attach a new script to the root node of the other_player scene. Open this script and add the following to set the position of the scene based on updates from Planetary Processing

# other_player.gd
extends CharacterBody3D

func _ready():
  var ppEntityNode = get_node("PPEntityNode")
  # connect to the state_changed signal from PPEntityNode
  ppEntityNode.state_changed.connect(_on_state_changed)

func _on_state_changed(state):
  global_transform.origin = Vector3(state.x, state.z, state.y) # NOTE Planetary Processing uses 'y' for depth in 3 dimensional games, and 'z' for height
  1. We now need to extend the script on our root node to add other players to the tree. Update your script as follows

# root.gd
extends Node3D

var pp_root_node

var other_player_scene = preload("res://scenes/other_player.tscn") # load the other player scene
var player_scene = preload("res://scenes/player.tscn")
var tree_scene = preload("res://scenes/tree.tscn")

var scene_map = {
  "player": other_player_scene, # add the other player scene to the map for type "player"
  "tree": tree_scene,
}

func _ready():
  if Engine.is_editor_hint():
    return
  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)

func _on_new_player_entity(entity_id, state):
  var player_instance = player_scene.instantiate()
  player_instance.global_transform.origin = Vector3(state.x, state.z, state.y)
  var pp_entity_node = player_instance.get_node("PPEntityNode")
  if pp_entity_node:
    pp_entity_node.entity_id = entity_id
  else:
    print("PPEntityNode not found in the player instance")
  add_child(player_instance)

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()
  entity_instance.global_transform.origin = Vector3(state.x, state.z, state.y)
  var pp_entity_node = entity_instance.get_node("PPEntityNode")
  if pp_entity_node:
    pp_entity_node.entity_id = entity_id
  else:
    print("PPEntityNode not found in the instance")
  add_child(entity_instance)

func _on_remove_entity(entity_id):
  for child in get_children():
    var pp_entity_node = child.get_node("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')
  1. We finally need to add code to connect your player to the game. To do so, we just call authenticate_player on PPRootNode. We have configured this game with anonymous auth, so we can call this function with empty strings for both the username and password. To have the player join the world immediately, add the following to the _ready function of the script in your root node:

# root.gd
extends Node3D
var pp_root_node

var other_player_scene = preload("res://scenes/other_player.tscn")
var player_scene = preload("res://scenes/player.tscn")
var tree_scene = preload("res://scenes/tree.tscn")

var scene_map = {
  "player": other_player_scene,
  "tree": tree_scene,
}

func _ready():
  if Engine.is_editor_hint():
    return
  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("", "") # authenticate the player

# ... rest of root.gd ...

Push Your Planetary Processing Backend Code to the Game Repository

  1. With your game set up, you're ready to push your Planetary Processing repo.

git add .
git commit -m "Planetary Processing quickstart"
git push

Deploy Latest Version in the Web UI

  1. Go back to your game dashboard in our web panel

  2. From the actions menu in the top right, select Deploy Latest Version - this will roll out your updated server-side code.

Start Game in the Web UI

  1. Click Start Game to begin your server-side simulation

Play Your Multiplayer Game!

  1. Launch your Godot project.

  2. Play the game from the Godot editor, or export and run the game binary.

  3. Connect to the multiplayer game through the game client, and have somebody else do the same.

Last updated