教程
创建多人游戏
15 分
示例项目 📘 多人游戏示例 https //github com/naverz/zepeto multiplay example 总结ㅁ true left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type 第1部分 安装多人游戏 从创建一个多人游戏服务器到客户端,设置开发多人游戏世界所需的环境。 https //www youtube com/watch?v=s68b1tma a8 第二部分:编写世界逻辑 了解服务器与客户端之间通信所需的架构,并定义架构类型和房间状态。 https //www youtube com/watch?v=tfky rabov0 请注意,视频中使用的服务器脚本,包括与 hashcode 相关的内容,已不再支持。 因此,在编写时请排除以下代码。 if (client hashcode) { 	player zepetohash = client hashcode; } 第三部分:编写世界逻辑 2 编写需要从玩家的位置同步到玩家出口的世界逻辑。 https //www youtube com/watch?v=h92gd2g9dhm 第4部分:运行服务器并连接到multiplay 运行服务器并连接到multiplay。 https //www youtube com/watch?v=fmk6jnlsjja 第5部分:脚本 schemas json schemas json { "state" {"players" {"map" "玩家"}}, "player" {"sessionid" "字符串","zepetouserid" "字符串","transform" "变换","state" "数字","substate" "数字"}, "transform" {"position" "向量3","rotation" "向量3"}, "vector3" {"x" "数字","y" "数字","z" "数字"} } 服务器代码 index ts import {sandbox, sandboxoptions, sandboxplayer} from "zepeto multiplay"; import {datastorage} from "zepeto multiplay datastorage"; import {player, transform, vector3} from "zepeto multiplay schema"; export default class extends sandbox { storagemap\ map\<string,datastorage> = new map\<string, datastorage>(); constructor() { super(); } oncreate(options sandboxoptions) { // 当房间对象被创建时调用。 // 处理房间对象的状态或数据初始化。 this onmessage("onchangedtransform", (client, message) => { const player = this state players get(client sessionid); const transform = new transform(); transform position = new vector3(); transform position x = message position x; transform position y = message position y; transform position z = message position z; transform rotation = new vector3(); transform rotation x = message rotation x; transform rotation y = message rotation y; transform rotation z = message rotation z; if (player) { player transform = transform; } }); this onmessage("onchangedstate", (client, message) => { const player = this state players get(client sessionid); if (player) { player state = message state; player substate = message substate; // 角色控制器 v2 } }); } async onjoin(client sandboxplayer) { // 创建在 schemas json 中定义的玩家对象并设置初始值。 console log(`\[onjoin] sessionid ${client sessionid}, userid ${client userid}`) const player = new player(); player sessionid = client sessionid; if (client userid) { player zepetouserid = client userid; } // \[datastorage] 进入玩家的数据存储加载 const storage datastorage = client loaddatastorage(); this storagemap set(client sessionid,storage); let visit cnt = await storage get("visitcount") as number; if (visit cnt == null) visit cnt = 0; console log(`\[onjoin] ${client sessionid} 的访问计数 ${visit cnt}`) // \[datastorage] 更新玩家的访问计数并保存存储 await storage set("visitcount", ++visit cnt); // 使用 sessionid 管理玩家对象,这是客户端对象的唯一键值。 // 客户端可以通过将 add onadd 事件添加到 players 对象来检查通过 set 添加的玩家对象的信息。 this state players set(client sessionid, player); } ontick(deltatime number) void { // 在服务器中每次设定时间重复调用,并且可以使用 deltatime 管理某个间隔事件。 } async onleave(client sandboxplayer, consented? boolean) { 	 // 通过设置 allowreconnection,可以保持电路的连接,但在基本指南中立即清理。 // 客户端可以通过将 add onremove 事件添加到 players 对象来检查已删除的玩家对象的信息。 this state players delete(client sessionid); } } 客户代码 clientstarter ts import {zepetoscriptbehaviour} from 'zepeto script' import {zepetoworldmultiplay} from 'zepeto world' import {room, roomdata} from 'zepeto multiplay' import {player, state, vector3} from 'zepeto multiplay schema' import {characterstate, spawninfo, zepetoplayers, zepetoplayer, characterjumpstate} from 'zepeto character controller' import as unityengine from "unityengine"; export default class clientstarterv2 extends zepetoscriptbehaviour { public multiplay zepetoworldmultiplay; private room room; private currentplayers map\<string, player> = new map\<string, player>(); private zepetoplayer zepetoplayer; private start() { this multiplay roomcreated += (room room) => { this room = room; }; this multiplay roomjoined += (room room) => { room onstatechange += this onstatechange; }; this startcoroutine(this sendmessageloop(0 04)); } // 在预定的间隔时间内将本地角色变换发送到服务器。 private sendmessageloop(tick number) { while (true) { yield new unityengine waitforseconds(tick); if (this room != null && this room isconnected) { const hasplayer = zepetoplayers instance hasplayer(this room sessionid); if (hasplayer) { const character = zepetoplayers instance getplayer(this room sessionid) character; this sendtransform(character transform); this sendstate(character currentstate); } } } } private onstatechange(state state, isfirst boolean) { // 当接收到第一个 onstatechange 事件时,记录完整的状态快照。 if (isfirst) { // \[charactercontroller] (本地) 当玩家实例在场景中完全加载时调用 zepetoplayers instance onaddedlocalplayer addlistener(() => { const myplayer = zepetoplayers instance localplayer zepetoplayer; this zepetoplayer = myplayer; }); // \[charactercontroller] (本地) 当玩家实例在场景中完全加载时调用 zepetoplayers instance onaddedplayer addlistener((sessionid string) => { const islocal = this room sessionid === sessionid; if (!islocal) { const player player = this currentplayers get(sessionid); // \[roomstate] 每当玩家实例的状态更新时调用。 player onchange += (changevalues) => this onupdateplayer(sessionid, player); } }); } let join = new map\<string, player>(); let leave = new map\<string, player>(this currentplayers); state players foreach((sessionid string, player player) => { if (!this currentplayers has(sessionid)) { join set(sessionid, player); } leave delete(sessionid); }); // \[roomstate] 为进入房间的玩家创建玩家实例 join foreach((player player, sessionid string) => this onjoinplayer(sessionid, player)); // \[roomstate] 移除退出房间的玩家实例 leave foreach((player player, sessionid string) => this onleaveplayer(sessionid, player)); } private onjoinplayer(sessionid string, player player) { console log(`\[onjoinplayer] players sessionid ${sessionid}`); this currentplayers set(sessionid, player); const spawninfo = new spawninfo(); const position = this parsevector3(player transform position); const rotation = this parsevector3(player transform rotation); spawninfo position = position; spawninfo rotation = unityengine quaternion euler(rotation); const islocal = this room sessionid === player sessionid; zepetoplayers instance createplayerwithuserid(sessionid, player zepetouserid, spawninfo, islocal); } private onleaveplayer(sessionid string, player player) { console log(`\[onremove] players sessionid ${sessionid}`); this currentplayers delete(sessionid); zepetoplayers instance removeplayer(sessionid); } private onupdateplayer(sessionid string, player player) { const position = this parsevector3(player transform position); const zepetoplayer = zepetoplayers instance getplayer(sessionid); var movedir = unityengine vector3 op subtraction(position, zepetoplayer character transform position); movedir = new unityengine vector3(movedir x, 0, movedir z); if (movedir magnitude < 0 05) { if (player state === characterstate moveturn) return; zepetoplayer character stopmoving(); } else { zepetoplayer character movecontinuously(movedir); } if (player state === characterstate jump) { if (zepetoplayer character currentstate !== characterstate jump) { zepetoplayer character jump(); } if (player substate === characterjumpstate jumpdouble) { zepetoplayer character doublejump(); } } } private sendtransform(transform unityengine transform) { const data = new roomdata(); const pos = new roomdata(); pos add("x", transform localposition x); pos add("y", transform localposition y); pos add("z", transform localposition z); data add("position", pos getobject()); const rot = new roomdata(); rot add("x", transform localeulerangles x); rot add("y", transform localeulerangles y); rot add("z", transform localeulerangles z); data add("rotation", rot getobject()); this room send("onchangedtransform", data getobject()); } private sendstate(state characterstate) { const data = new roomdata(); data add("state", state); if(state === characterstate jump) { data add("substate", this zepetoplayer character motionv2 currentjumpstate); } this room send("onchangedstate", data getobject()); } private parsevector3(vector3 vector3) unityengine vector3 { return new unityengine vector3 ( vector3 x, vector3 y, vector3 z ); } }
