告别后端!用 Trystero 轻松实现 Three.js 实时多人在线互动

AI 知识库5个月前发布
793 0 0
熊猫办公

你是否曾想用 Three.js 做一个能和朋友一起玩酷炫原型,却在“多人游戏编程”这座大山前望而却步?别担心,多人游戏开发不必那么令人头大!今天,我将向你介绍一个神奇的工具,它能让你轻松、免费地为你的项目添加多人功能:Trystero

告别后端!用 Trystero 轻松实现 Three.js 实时多人在线互动

浏览器中的P2P多人游戏,无需服务器,完全免费✨ Trystero 是什么?Trystero是一个极简、开源(MIT 许可)的 JavaScript 库,它能让你在没有自己服务器的情况下构建多人 Web 应用。它通过 WebRTC 和各种去中心化的信令后端(如 BitTorrent、Nostr、MQTT、IPFS、Supabase 和 Firebase)来处理节点匹配,因此你所有的数据都直接在客户端之间点对点(P2P)流动,并且全程端到端加密。更棒的是,Trystero 提供了非常友好的开发者抽象,例如:

  • 房间 (Rooms):轻松创建隔离的会话空间。
  • 事件广播 (Event Broadcasting):向房间内的所有人发送消息。
  • 数据分块与进度跟踪:轻松传输大文件。
  • React Hooks:与现代前端框架无缝集成。

这一切都使它成为用 Three.js 创建无服务器多人体验的完美选择。而这,正是我在我的多人技术演示中使用的工具!得益于 Trystero 的简洁高效,我只花了一天时间就完成了整个编码工作。🚀 一个真实世界的案例与其枯燥地解释 Trystero 的概念,不如直接展示我是如何用它来制作一个多人 Demo 的。你可以通过这个案例,直观地感受到它的强大。

我的 Demo 演示地址在这里:点击查看。
在这个 Demo 中,新玩家会自动进入一个队列寻找空闲房间,在房间里可以进行互动,几秒钟后会自动下线,然后重复这个过程。

下面,我将分享我是如何一步步实现的。第一步:分层开发!在触碰任何多人代码之前,首先要用“单人游戏”的思维来构建你的应用,但有一个关键原则:将玩家的输入逻辑与实体的行为逻辑分离开。这意味着,你的实体(比如玩家角色)应该有一个清晰的、暴露出来的公共方法接口,用于控制它的行为。把它想象成一个控制面板,上面有各种按钮。你不在乎是谁按下了按钮——是玩家的手指、一只猫还是一根棍子——你只关心按钮被按下后应该发生什么。// 定义一个实体接口
interfaceIEntity{
walkTo(targetPosition:Vector3,from?:Vector3):void;
sayHi():void;
wakeup(playerId:string):void;
// etc...
}

通过这种方式,你的游戏实体对输入方式一无所知,它们只响应接口调用。在实体内部,你可以用一个简单的状态机来处理各种状态和转换。这种**“输入无关”**的设计,将使后续接入 Trystero 变得超级简单。第二步:接入 Trystero好了,假设你现在已经有了一个功能完善的“单人应用”,并且你的可控实体都实现了你设计的接口。现在,是时候给它加上多人功能了!首先,安装 Trystero:npm install trystero

玩家之间通过 P2P 直接连接,中间没有服务器。但在建立连接之前,他们需要一种方式来互相“发现”对方。这就是 Trystero 的用武之地,它提供了多种信令方案。在我的 Demo 中,我选择了Firebase,因为它对于快速原型来说既简单又可靠。创建一个“房间”在 Trystero 中,所有事情都发生在一个“房间”(Room)里。你可以把它想象成一个共享的舞台,所有玩家聚集在这里。在这个空间里发生的每一个动作、事件,都会被在场的所有人即时看到。import{ joinRoom }from'trystero/firebase';// 因为我选择了 Firebase

// Firebase 的配置
constserverConfig = {appId:"https://XXXXXXX.firebaseio.com"};
// 加入一个房间
constroom =joinRoom(serverConfig,"mySuperFunRoom");

是不是很简单?现在,room对象就是我们与“外部世界”沟通的接口。我们将用它来和其他玩家“对话”。定义“动作”与“监听器”我们已经进入了一个房间,接下来呢?逻辑上,你会问“我能做什么?”以及“如果别人做了什么,我该如何响应?”。这正是我们需要告诉room的。Trystero 的makeAction方法可以完美解决这个问题。// 以下代码直接从我的 Demo 中复制而来,是一个真实的例子:

// 定义“请求状态”的动作和监听器
const[askStatus, onSomeoneAskStatus] = room.makeAction("askstatus");

// 定义“发送状态”的动作和监听器
const[sendStatusOf, onStatusOf] = room.makeAction("statusof");

// 定义“玩家移动”的动作和监听器
const[sendPlayerMoved, onSomeoneMoved] = room.makeAction("move");

// 定义“聊天”的动作和监听器
const[sendChat, onChat] = room.makeAction("chat");

// 定义“打招呼”的动作和监听器
const[sendHi, onPlayerHi] = room.makeAction("hi");

room.makeAction的使用方式和 React Hooks 非常相似。它会返回一个元组(tuple):

  • 第一个元素(askStatus,sendStatusOf等) 是一个函数,用来触发这个动作,并广播给房间里的其他人。
  • 第二个元素(onSomeoneAskStatus,onStatusOf等) 是一个函数,用来订阅这个动作。当别人触发这个动作时,你注册的回调函数就会被执行。

第三步:处理玩家加入与同步我们定义好了动作,但如何知道有新玩家加入了房间呢?答案是:room.onPeerJoin()// 当有新玩家加入房间时,这个回调会被触发
room.onPeerJoin(peerId=>{
// 1. 在场景中生成一个新玩家的虚拟形象,但暂时让他处于“待机”状态
this.game.spawnPlayer(peerId,false);

// 2. 向房间里的其他人询问这个新玩家的数据
// (可能他只是掉线重连,别人还保留着他的状态)
askStatus(peerId, peerId);
});

当一个新玩家加入时,我的 Demo 做了两件事:

  1. 1. 生成这个玩家的模型。
  2. 2. 立即调用askStatus动作,向房间里的所有人广播:“嘿,有谁认识这个 ID (peerId) 的家伙吗?把他的信息告诉我。”

然后,我们需要实现askStatus的响应逻辑:// 监听“请求状态”的动作
onSomeoneAskStatus(async(targetId, askerId) => {
// 只有被请求的目标本人才能回应,因为每个玩家是自己状态的“权威来源”
if(targetId !== selfId)return;

// 获取我自己的玩家对象
constplayer =this.game.getPlayer(targetId);

// 如果我已经准备好了(不是“待机”状态),就把我的状态发给提问者
if(player && !player.inLimbo) {
sendStatusOf(this.packPlayerStatus(player), askerId);
}
});

当某个客户端收到了sendStatusOf的数据后,它就可以更新这个玩家的状态了:// 监听“收到状态”的动作
onStatusOf((status, fromPeerId) =>{
constplayerId = status[0];
letplayer =this.game.getPlayer(playerId);

if(!player)return;

// 如果玩家之前是“待机”状态,现在正式激活他
if(player.inLimbo) {
this.game.writeChat(`${status[4]}joined the room.`);
}

// 这就是“桥梁”:将 Trystero 的数据同步到我们的 Three.js 世界中
this.game.updatePlayerStatus(playerId, {
position:newVector3(status[1], status[2], status[3]),
username: status[4]
});
});

🤯 “精神分裂”式编程思维

告别后端!用 Trystero 轻松实现 Three.js 实时多人在线互动

使用 Trystero 开发时,你常常需要一种“精神分裂”式的思维模式。在同一段代码中,你既要发出动作,又要处理仿佛来自其他客户端的相同动作。这是一种奇特的双重视角——你同时是发送者和接收者,假装自己是两个不同的人。这种思维方式至关重要,因为在 P2P 系统中,没有中央权威来为你处理事件。你发出的每一个动作,都必须被你的客户端像处理别人的动作一样处理,以确保你的本地状态与网络状态保持同步。🌉 搭建沟通的“桥梁”你需要清醒地认识到:Trystero 不会魔法般地理解你的应用世界,它只负责在节点之间传递原始数据。这意味着,你,作为开发者,必须编写一座**“桥梁”**:一个监听 Trystero 事件,并将其转化为场景中有意义变化的逻辑层。如果一个数据包说“玩家 A 移动了”,你的桥梁代码就要负责接收这些数据,并相应地更新 Three.js 场景中玩家 A 的position。换句话说,Trystero 提供了网络管道,而你负责将这些管道中流淌的信号映射到你的 3D 世界中,让它们变得有意义。

告别后端!用 Trystero 轻松实现 Three.js 实时多人在线互动

当 `hi()` 被调用时,触发“打招呼”的 AnimationClip下面是一个完美的“桥梁”示例:// 监听“打招呼”动作
onPlayerHi((_, who) =>{
// Trystero 告诉我们有个玩家 (ID: who) 在打招呼
// 于是我们就在 Three.js 场景中找到这个玩家
letsender =this.game.getPlayer(who);
if(sender) {
// ...然后调用我们为实体定义的 hi() 方法,来播放动画!
sender.hi();
}
});

看到了吗?这就是 Trystero 的调用和我们实体接口调用之间的舞蹈。正因为我们一开始就将游戏逻辑设计为“输入无关”,现在接入 Trystero 几乎是小事一桩。👋 处理玩家离开当一个玩家离开房间时,Trystero 会触发room.onPeerLeave回调。room.onPeerLeave(peerId=>{
// 在我们的 Three.js 场景中查找这个玩家
letplayer =this.game.getPlayer(peerId);
if(!player)return;

// 在聊天UI里显示经典提示
this.game.writeChat(`${player.username}left the room.`);

// 从我们的场景中移除这个玩家
this.game.removePlayer(peerId);
});

在这里,你可以执行清理工作,比如将实体返回对象池,或者直接销毁它们。总结Trystero 让浏览器中的多人游戏开发变得前所未有的轻松。它通过处理繁重的 P2P 网络工作,消除了对服务器、账户系统或复杂后端设置的需求,让你能够完全专注于你的 3D 体验。将 Trystero 与 Three.js 结合,是进行快速原型开发的理想工具箱。你只需几行代码,就能搭建起实时的、协作的 3D 场景,而无需担心技术障碍或持续的服务器成本。无论你是在试验新的游戏玩法、构建交互式艺术,还是测试多人机制,Trystero 都为你提供了一条将概念变为现实的最简单路径。🚀


原文链接:

https://medium.com/@pablobandinopla/effortless-serverless-multiplayer-in-three-js-with-trystero-f025f31150c6

© 版权声明

相关文章