godot-ggrs-wrapper

The purpose of this repo is to create a wrapper for the ggrs Rust crate so that this can be used inside the Godot Game Engine. To accomplish this the godot-rust GDNative bindings are used. Although the project currently implements all of the GGRS features, i can't guarantee that it's ready for use in production.

Notes

Links

Examples

Quick start

Since this project uses godot-rust GDNative bindings you should be familiar with how to set up a godot-rust GDNative node inside Godot. If you aren't please check out this page for details. The setup you want to have is a scene with a node that has the GodotGGRSP2PSession class bound to it.

Starting a session with GodotGGRSP2PSession

To set up a session you want to have a script that addresses the GGRS node. Just having the node inside your scene doesn't create a session for you. Here i took the root node to make a script that makes a session on the _ready() function

```gdscript func ready(): var localhandle: int #This var is later used to identify your local inputs var remotehandle: int #This var is later used to identify the remoteplayers inputs if(OS.getcmdlineargs()[0] == "listen"): $GodotGGRS.createsession(7070, 2) # Port 7070, 2 max players localhandle = $GodotGGRS.addlocalplayer() remotehandle = $GodotGGRS.addremoteplayer("127.0.0.1:7071") elif(OS.getcmdlineargs()[0] == "join"): $GodotGGRS.createsession(7071, 2) # Port 7071, 2 max players remotehandle = $GodotGGRS.addremoteplayer("127.0.0.1:7070") localhandle = $GodotGGRS.addlocalplayer()

$GodotGGRS.receive_callback_node(self) # Set the node which will implement the callback methods
$GodotGGRS.set_frame_delay(2, local_handle) # Set personal frame_delay, works only for local_handles.
$GodotGGRS.start_session() #Start listening for a session.

```

As you can see we swap the order of adding players depending on who's the "host". In reality since it's a peer 2 peer library, there is no true host. However you should have a way to distinguish between player 1 and player 2.

Advancing frames

Now that we have a session we want to start implementing our loop. Godot's default _process() and _physics_process() will serve us nicely here.

```gdscript func process(delta): $GodotGGRS.pollremoteclients() # GGRS needs to periodically process UDP requests and such, sticking it in _process() works nicely since it's only called on idle.

func physicsprocess(delta): if($GodotGGRS.isrunning()): # This will return true when all players and spectators have joined and have been synched. $GodotGGRS.advanceframe(localhandle, rawinputtoint("con1")) # rawinputtoint is a method that parses InputActions that start with "con1" into a integer.

func rawinputtoint(prefix: String)->int: # This method is how i parse InputActions into an int, but as long as it's an int it doesn't matter how it's parsed. var result := 0; if(Input.isactionpressed(prefix + "left")): #The action it checks here would be "con1left" if the prefix is set to "con1" result |= 1 if(Input.isactionpressed(prefix + "right")): result |= 2 if(Input.isactionpressed(prefix + "up")): result |= 4 if(Input.isactionpressed(prefix + "down")): result |= 8 return result; ```

Calling advance_frame will tell GGRS that you are ready to go to the next frame using the input you've given as a parameter. GGRS will do it's thing and callback to Godot once it's ready to continue.

Handling GGRS callbacks

So how to handle GGRS callbacks is alot more subjective than the steps before and will vary greatly on how your game is built. The only thing required is that you implement the callback functions, but the logic inside can be pretty much anything to fit to your game. Here's how i implemented the callback methods.

```gdscript func ggrsadvanceframe(inputs: Array): # inputs is an array of input data indexed by handle. # inputdata itself is also an array with the following: [frame: int, size: int, inputs: int] # frame can be used as a sanity check, size is used internally to properly slice the buffer of bytes and inputs is the int we created in our previous step. var net1inputs := 0; var net2inputs := 0; if(localhandle < remotehandle): net1inputs = inputs[localhandle][2] net2inputs = inputs[remotehandle][2] else: net1inputs = inputs[remotehandle][2] net2inputs = inputs[localhandle][2] inttorawinput("net1", net1inputs) # Player objects check for InputActions that aren't bound to any controller. inttorawinput("net2", net2inputs) # Player objects check for InputActions that aren't bound to any controller. _handleplayer_frames()

func ggrsloadgamestate(frame: int, buffer: PoolByteArray, checksum: int): var state : Dictionary = bytes2var(buffer); P1.loadstate(state.get("P1", {})) P2.load_state(state.get("P2", {}))

func ggrssavegamestate(frame: int)->PoolByteArray: # frame parameter can be used as a sanity check (making sure it matches your internal frame counter). var savestate = {} savestate["P1"] = _saveP1state(""); savestate["P2"] = saveP2state(""); return var2bytes(savestate);

func inttorawinput(prefix: String, inputs: int): _setaction(prefix + "left", inputs & 1) _setaction(prefix + "right", inputs & 2) _setaction(prefix + "up", inputs & 4) _setaction(prefix + "_down", inputs & 8)

func setaction(action: String, pressed: bool): if(pressed): Input.actionpress(action) else: Input.actionrelease(action)

```

Handling Rust Panics

Create a godot script containing the following:

```gdscript extends Node

func rustpanichook(errormsg: String) -> void: assert(false, errormsg) ```

Make the Godot Project autoload this script as a singleton using the following name: "RustPanicHook". Now all Rust panics should always be catched properly. This solution is based off of the Godot-Rust - Rust Panic Hook Recipe.