3个不稳定版本
0.5.0 | 2022年2月11日 |
---|---|
0.4.1 | 2021年10月6日 |
0.4.0 | 2021年9月22日 |
#1273 in 游戏开发
56KB
866 行
godot-ggrs-wrapper
此仓库的目的是创建一个ggrs Rust包的包装器,以便可以在Godot游戏引擎中使用它。为此,使用了godot-rust GDNative绑定。尽管该项目目前实现了所有GGRS功能,但我无法保证它已准备好投入生产使用。
注意
- 建议您在Godot资源
GDNativeLibrary
中将reloadable
设置为false
。 - 已在Godot版本3.3.2上测试,Godot 4.0将具有大量的GDNative功能,因此当它发布时,可以假设此项目将崩溃。
- GodotGGRS的输入当前为无符号32位整数格式。
- 状态为GodotByteArrays,您将godot变体转换为ByteArray并传递给GodotGGRS。
- 编译需要Clang,详细信息请参见此处。
链接
示例
使用godot-ggrs-wrapper的项目
如果你的项目使用了godot-ggrs-wrapper,请告诉我,我将很高兴添加链接!
快速开始
由于此项目使用godot-rust GDNative绑定,因此你应该熟悉如何在Godot中设置godot-rust GDNative节点。如果你不熟悉,请查看此页面以获取详细信息。你想要的设置是一个场景,其中包含一个绑定到GodotGGRSP2PSession类的节点。
使用GodotGGRSP2PSession启动会话
要设置会话,你想要一个脚本,它解决了GGRS节点。仅仅在场景中有一个节点并不能为你创建一个会话。在这里,我使用了根节点,创建了一个在_ready()函数上创建会话的脚本
func _ready():
var local_handle: int #This var is later used to identify your local inputs
var remote_handle: int #This var is later used to identify the remote_players inputs
if(OS.get_cmdline_args()[0] == "listen"):
$GodotGGRS.create_new_session(7070, 2) # Port 7070, 2 max players, max 8 prediction frames
local_handle = $GodotGGRS.add_local_player()
remote_handle = $GodotGGRS.add_remote_player("127.0.0.1:7071")
elif(OS.get_cmdline_args()[0] == "join"):
$GodotGGRS.create_new_session(7071, 2) # Port 7071, 2 max players, max 8 prediction frames
remote_handle = $GodotGGRS.add_remote_player("127.0.0.1:7070")
local_handle = $GodotGGRS.add_local_player()
$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.
如你所见,我们根据谁是“主机”交换添加玩家的顺序。在现实中,由于它是一个对等库,没有真正的主机。但是你应该有一种方法来区分玩家1和玩家2。
推进帧
现在我们已经有了会话,我们想要开始实现循环。Godot 的默认 _process() 和 _physics_process() 在这里会很好地为我们服务。
func _process(_delta):
$GodotGGRS.poll_remote_clients() # GGRS needs to periodically process UDP requests and such, sticking it in \_process() works nicely since it's only called on idle.
func _physics_process(_delta):
if($GodotGGRS.is_running()): # This will return true when all players and spectators have joined and have been synched.
$GodotGGRS.advance_frame(local_handle, raw_input_to_int("con1")) # raw_input_to_int is a method that parses InputActions that start with "con1" into a integer.
func raw_input_to_int(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.is_action_pressed(prefix + "_left")): #The action it checks here would be "con1_left" if the prefix is set to "con1"
result |= 1
if(Input.is_action_pressed(prefix + "_right")):
result |= 2
if(Input.is_action_pressed(prefix + "_up")):
result |= 4
if(Input.is_action_pressed(prefix + "_down")):
result |= 8
return result;
调用 advance_frame 将会告诉 GGRS 你已经准备好使用作为参数提供的输入进入下一帧。GGRS 会完成它的工作,并在准备好继续时回调 Godot。
处理 GGRS 回调
因此,如何处理 GGRS 回调比之前的步骤更加主观,并且会根据你的游戏构建方式有很大差异。唯一需要的是实现回调函数,但内部的逻辑可以是几乎任何东西,以适应你的游戏。以下是如何实现回调方法的方式。
func ggrs_advance_frame(inputs: Array):
# inputs is an array of input data indexed by handle.
# input_data 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 net1_inputs := 0;
var net2_inputs := 0;
if(local_handle < remote_handle):
net1_inputs = inputs[local_handle][2]
net2_inputs = inputs[remote_handle][2]
else:
net1_inputs = inputs[remote_handle][2]
net2_inputs = inputs[local_handle][2]
int_to_raw_input("net1", net1_inputs) # Player objects check for InputActions that aren't bound to any controller.
int_to_raw_input("net2", net2_inputs) # Player objects check for InputActions that aren't bound to any controller.
_handle_player_frames()
func ggrs_load_game_state(frame: int, buffer: PoolByteArray, checksum: int):
var state : Dictionary = bytes2var(buffer);
P1.load_state(state.get("P1", {}))
P2.load_state(state.get("P2", {}))
func ggrs_save_game_state(frame: int)->PoolByteArray: # frame parameter can be used as a sanity check (making sure it matches your internal frame counter).
var save_state = {}
save_state["P1"] = _save_P1_state("");
save_state["P2"] = _save_P2_state("");
return var2bytes(save_state);
func int_to_raw_input(prefix: String, inputs: int):
_set_action(prefix + "_left", inputs & 1)
_set_action(prefix + "_right", inputs & 2)
_set_action(prefix + "_up", inputs & 4)
_set_action(prefix + "_down", inputs & 8)
func _set_action(action: String, pressed: bool):
if(pressed):
Input.action_press(action)
else:
Input.action_release(action)
处理 Rust Panic
创建一个包含以下内容的 Godot 脚本
extends Node
func rust_panic_hook(error_msg: String) -> void:
assert(false, error_msg)
使用以下名称将此脚本作为单例自动加载到 Godot 项目中:"RustPanicHook"。现在所有 Rust panic 都应该被正确捕获。这个解决方案基于 Godot-Rust - Rust Panic Hook Recipe。
依赖项
~5–8.5MB
~157K SLoC