(摘) Godot 三十行代码实现多人游戏

声明:内容源自网络,版权归原作者所有。若有侵权请在网页聊天中联系我

看看别人的写的简单多人游戏代码 B站视频
B站上还有一个E文的无语音视频,似乎更简单,也可以借鉴理解。
希望更简单的,可以直接跳到第二个示例

示例1

主场景


代码并不复杂

MainScene关联代码

extends Node2D
@onready var players: Node = $Players
@onready var camera : Camera2D = $Camera2D
const PLAYER = preload("res://player.tscn")
var peer = ENetMultiplayerPeer.new()

## 创建服务器
func _on_create_button_down() -> void:
	#创建监听的服务器,即创建了一个地址为 127.0.0.1:7788 的服务器
	var error = peer.create_server(7788)
	if error != OK:
		printerr("创建服务器失败, 错误码", error)
		return
	multiplayer.multiplayer_peer = peer   
	
	multiplayer.peer_connected.connect(_on_peer_connected)  # 监听连接事件
	add_player(multiplayer.get_unique_id())  # 服务端也创建一个玩家

## 添加玩家到$Players节点下
func add_player(id: int) -> void:
	var player = PLAYER.instantiate()
	player.name = str(id)
	players.add_child(player)

## 当有新的客户端连接时,该方法会被触发, 该方法只有主机端会被触发!
func _on_peer_connected(id: int) -> void:
	print("有玩家连接,ID为",id)	
	add_player(id) # 添加新玩家

## 创建客户端连接
func _on_join_button_down() -> void:	
	peer.create_client("127.0.0.1", 7788)
	multiplayer.multiplayer_peer = peer

Camera2D关联代码,主要是让镜头跟着玩家移动

extends Camera2D

var target_position = Vector2.ZERO

func _ready():
	make_current()

func _process(delta):
	acquire_target()
	global_position = global_position.lerp(target_position, 1.0 - exp(-delta * 20))

func acquire_target():
	var player_nodes = get_tree().get_nodes_in_group("player")
	if player_nodes.size() > 0:
		var p_id = multiplayer.get_unique_id()
		for p_node in player_nodes:
			var player = p_node as Node2D
			if(player.name.to_int() == p_id):
				target_position = player.global_position
				break

场景中MultiplayerSpawner节点的SpawnPath属性设为Players节点,即客户端加入后,自动生成实例在Players节点下。
AutoSpawnList设置为player.tscn场景,即按此场景进行实例化。

MultiplayerSpawner 和 MultiplayerSynchronize 是多人网络中数据同步重要的两个节点。后者用于配置同步的具体数据(字段)

玩家场景

玩家定义了 player 分组名
在AnimationPlayer中定义了 run、idle 两组动画.
点击MultiplayerSynchronizer节点,界面上方可以添加需要同步的属性。这里他添加了Player:position属性的同步。

玩家关联代码

extends CharacterBody2D
class_name Player
@onready var graphic: Node2D = $Graphic
@onready var animation_player: AnimationPlayer = $AnimationPlayer
@onready var sprite_2d: Sprite2D = $Graphic/Sprite2D

func _enter_tree() -> void:
	set_multiplayer_authority(name.to_int()) # 设置该节点的多人权限(本节点所有者)

func _ready() -> void:
	position = Vector2(100,0)

func _physics_process(delta: float) -> void:
	# 如果不是该节点的控制者,则无法移动,直接终止方法
	# 根据设置的节点的权限id,与本身的唯一id作对比,如果一致,则权限正确
	# get_unique_id获取的id即为本机的id标识,在创建客户端或者服务器的时候生成
	if not is_multiplayer_authority():
		return
	move(delta)

func move(delta) -> void:
	# 监测按键输入
	var direction = Input.get_axis("move_left", "move_right")
	# 设置角色移动速度
	velocity.x = direction * 100
	# 设置玩家重力加速度
	velocity.y += ProjectSettings.get("physics/2d/default_gravity") * delta # 从系统工程配置中获取设置的重力值
	# 如果存在水平方向的运动
	if not is_zero_approx(direction):
		# 设置对应的朝向
		graphic.scale.x = 1 if direction > 0 else -1
		# 执行函数,执行动画
		update_player_animation.rpc("run")
	else:
		# 执行函数,执行动画
		update_player_animation.rpc("idle")
	# 玩家移动
	move_and_slide()

# "authority":只有多人权限(服务器)才能远程调用
# "any_peer":允许客户远程呼叫。对于传输用户输入很有用
# "call_remote":该函数不会在本地对等点上调用
# "call_local":该函数可以在本地peer上调用。当服务器同时也是玩家时很有用
# "unreliable"数据包不会被确认,可能会丢失,并且可能以任何顺序到达
# "unreliable_ordered"数据包按照发送的顺序接收。这是通过忽略后来到达的数据包(如果已经收到在它们之后发送的另一个数据包)来实现的。如果使用不当可能会导致丢包
# "reliable"发送重新发送尝试直到数据包被确认,并且它们的顺序被保留。具有显着的性能损失
@rpc("authority", "call_local")
## 更新玩家动画
func update_player_animation(animation_name:String) -> void:
	# 播放指定动画
	animation_player.play(animation_name)

可以看出网络多人游戏的几个要素/节点
ENetMultiplayerPeer定义网络连接、MultiplayerSpawner和MultiplayerSynchronize负责同步数据、RPC负责远程函数调用。


示例2

上面提到的英文视频更简单,没有涉及到玩家的太多节点,就是只是一个图片可以由玩家分别控制。


为了验证学习,我在原基础上添加了随着鼠标旋转

主场景


只用了三个节点,MultiplayerSpawner 设置好SpawnPath为根节点,AutoSpawnList设置为玩家场景(这里player.tscn)

extends Node2D

var peer = ENetMultiplayerPeer.new()
@export var player_scene: PackedScene

func _on_join_button_pressed() -> void:
	if peer.create_client("localhost",1234)!=OK:
		print("创建客户端出错")
	multiplayer.multiplayer_peer = peer
	#_add_player(peer.get_unique_id())

func _on_server_button_pressed() -> void:
	if peer.create_server(1234)!=OK:
		print("创建服务端出错")
	multiplayer.multiplayer_peer = peer
	multiplayer.peer_connected.connect(_add_player)
	multiplayer.peer_disconnected.connect(_remove_player)
	_add_player()
	
func _remove_player(id=1):
	for i in get_children():
		if i.name == str(id):
			remove_child(i)

func _add_player(id = 1):
	print("来客",id)
	var player = player_scene.instantiate()
	player.name = str(id)
	call_deferred("add_child",player)

玩家场景

玩家场景player.tscn更简单,只是一个精灵节点,一个数据同步节点

MultiplayerSynchronizer设置好要同步的数据

extends Node2D

func _physics_process(delta: float) -> void:
	if is_multiplayer_authority():
		position += Input.get_vector("ui_left","ui_right","ui_up","ui_down") * 400 * delta
		look_at(get_global_mouse_position()*0.8)  # 这一句也可以不要

func _enter_tree() -> void:
	set_multiplayer_authority(name.to_int())

根据以上的学习,是不是可以不用 MultiplayerSynchronizer MultiplayerSpawner 这两个节点,通过RPC来实现数据的传输呢?稍后实践。

相关文章