看看别人的写的简单多人游戏代码 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来实现数据的传输呢?稍后实践。