マルチプレイの環境構築【後編】
1.ZepetoPlayersを作成
「Publish」横の▼マークから「Login」を選択してログインを行います。
図ではログインした後の為名前が表示されています。
その後、下記の手順を行ってください。
Create a ZEPETO Character
!注意
CharacterControllerは作成しないでください。
もし作成している場合、無効化してください。
2.ClientStarterを作成
Assets内で右クリック「作成」>「ZEPETO」>「TypeScript」
名前を「ClientStarter」に変更します。
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)); } // Send the local character transform to the server at the scheduled Interval Time. 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) { // When the first OnStateChange event is received, a full state snapshot is recorded. if (isFirst) { // [CharacterController] (Local) Called when the Player instance is fully loaded in Scene ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => { const myPlayer = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer; this.zepetoPlayer = myPlayer; }); // [CharacterController] (Local) Called when the Player instance is fully loaded in Scene ZepetoPlayers.instance.OnAddedPlayer.AddListener((sessionId: string) => { const isLocal = this.room.SessionId === sessionId; if (!isLocal) { const player: Player = this.currentPlayers.get(sessionId); // [RoomState] Called whenever the state of the player instance is updated. player.OnChange += (changeValues) => this.OnUpdatePlayer(sessionId, player); } }); } const join = new Map<string, Player>(); const 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] Create a player instance for players that enter the Room join.forEach((player: Player, sessionId: string) => this.OnJoinPlayer(sessionId, player)); // [RoomState] Remove the player instance for players that exit the room 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); let 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 );} }
ヒエラルキーの「+」から「空のオブジェクトを作成」を選択。
名称を「ClientStarter」に変更します。
インスペクターにて先ほど作成したScriptを追加、Multiplay欄を「WorldMultiPlay」に変更します。
「WorldMultiPlay」は前編で作成したオブジェクトです。
このコンポーネントは、ClientにRoomCreatedやRoomJoined等のServerRoomEventを連動させるInterfaceを提供します。
詳しくはこちらを参照ください。
3.マルチプレイが可能かテスト
サーバーマーク(ZEPETOマークの横)に緑丸が表示されていることを確認してください。
ZEPETOマークを押下して、下図画面が表示される為「OK」ボタンを押下します。
QRコードが表示される為、ZEPETOアプリのQRコードスキャナから読み込み、作成したWorldに入場します。
自分と、「やみ」にも入場してもらいました。