ååã¯åç¬ã§åããã¬ã€ã€ãŒãçæãããšãããŸã§è¡ããŸãããZEPETOã«æçš¿ããã«ã¯è€æ°ã®ãŠãŒã¶ãŒãåå ã§ããããã«ããã¬ã€ãã®ç°å¢ãæ§ç¯ããå¿ èŠããããŸããä»åã¯ãã®åºæ¬çãªã€ããæ¹ã®ã玹ä»ã«ãªããŸãã
ä»åã®å®æã€ã¡ãŒãžã¯ãããªæãã§ãã
5ïŒMuiliplayïŒãã«ããã¬ã€ïŒ
ZepetoPlayer 㯠Multiplay ã¯ãŒã«ãã³ã³ãã³ãã§ã¢ãã¿ãŒãäœæãåé€ã管çã§ãããã£ã©ã¯ã¿ãŒã€ã³ã¹ã¿ã³ã¹ãŠãããã§ããåPlayer ã® UserIdå€ ãéããŠãã¢ãã¿ãŒã®ã€ã³ã¹ã¿ã³ã¹ã Scene ã« Load ããã³ Unload ããããšãã§ããŸãã
5.1,Multiplay Serverã®ã€ã³ã¹ããŒã«
ãŸãã¯ãMultiplay ServerããååŸããŸãã projectã®Assetå 㧠Create â ZEPETO â Multiplay Server ãéžæã Assetãã©ã«ãå ã«ãWorld.MultiPlayããšãããã©ã«ãçæãããŠããŸããäžã«ã¯ããã€ãã®ã¹ã¯ãªãããã¡ã€ã«ããã©ã«ããé 眮ãããŠããã®ã確èªã§ãããšæããŸãã ãã®äžã® index ãšããã¹ã¯ãªãããã¡ã€ã«ãš schemas ãšãã jsonãã¡ã€ã«ãå€æŽããŸãã
index.ts ã¯ãµãŒããŒã®ã¡ã€ã³ããžãã¯ã³ãŒãã§ããã«ããã¬ã€ã®èãšãªãéšåã§ãã 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;
player.transform = transform;
});
this.onMessage("onChangedState", (client, message) => {
const player = this.state.players.get(client.sessionId);
player.state = message.state;
player.subState = message.subState;
});
}
async onJoin(client: SandboxPlayer) {
console.log(`[OnJoin] sessionId : ${client.sessionId}, HashCode : ${client.hashCode}, userId : ${client.userId}`)
const player = new Player();
player.sessionId = client.sessionId;
if (client.hashCode) {
player.zepetoHash = client.hashCode;
}
if (client.userId) {
player.zepetoUserId = client.userId;
}
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}'s visiting count : ${visit_cnt}`)
await storage.set("VisitCount", ++visit_cnt);
this.state.players.set(client.sessionId, player);
}
onTick(deltaTime: number): void {
}
async onLeave(client: SandboxPlayer, consented?: boolean) {
this.state.players.delete(client.sessionId);
}
}
ããäžã€ãschemasãããã¯ãµãŒããŒãšã¯ã©ã€ã¢ã³ãã®éä¿¡çšã®ããŒã¿æ§é ïŒjsonãã¡ã€ã«ïŒã®æ§ã§ãã 以äžã®ããã«å€æŽããŸãã
{
"State" : {"players" : {"map" : "Player"}},
"Player" : {"sessionId" : "string","zepetoHash" : "string","zepetoUserId" : "string","transform" : "Transform","state" : "number","subState" : "number"},
"Transform" : {"position" : "Vector3","rotation" : "Vector3"},
"Vector3" : {"x" : "number","y" : "number","z" : "number"}
}
ãschemasããInspectorã§ç¢ºèªãããšä»¥äžã®ãããªç¶æ ã§ãã
5.2,ãã¹ããµãŒããŒã®å®è¡
äœæè ãéçºäžã«ãµãŒããŒ/ã¯ã©ã€ã¢ã³ãããã¹ãããã«ã¯ãããŒã«ã«ãµãŒããŒç°å¢ãã®æ§ç¯ãå¿ èŠã§ãããã®ç°å¢ã®åäœã確èªããã«ã¯ãMultiplay ServerããåŒã³åºããŸãã äžéšã¡ãã¥ãŒã®Window ãã ZEPETO â Multiplay Servet ãéžæããç¶æ ãŠã£ã³ããŠã衚瀺ãããŸãã
Unityãšãã£ã¿äžéšã®ãŒãããã¢ã€ã³ã³ã®å³åŽã®ãã¿ã³ãæŒãããšã§ãç¶æ ãŠã£ã³ããŠã®æ å ±ãæŽæ°ãããŸãã
ããã«ãPublishã®é£ã®âŒãã¿ã³ãããPlay with Multiplay Serverããæå¹ã«ããŸãããã§ãã¯ãå ¥ã£ãŠããã°OKãå ¥ã£ãŠããªããã°ã¯ãªãã¯ããŠãã§ãã¯ãå ¥ããŠãããŸãã
5.3,ã¯ã©ã€ã¢ã³ãæ¥ç¶
Hierachy 㧠GameObject ãäœæããMultiPlayããšããååãä»ããŠãããŸãã ããã«Inspectorãã AddCompornent ã§ãZepetoWorldMultiplayãã³ã³ããŒãã³ããè¿œå ããŸãã è¿œå ãããZepetoWorldMultiplayã³ã³ããŒãã³ãã¯ãMultiplay Packageã«èªåçã«æ¥ç¶ãããŸãã
ãã®ç¶æ ã§ãã¬ã€ãã¿ã³ãã¯ãªãã¯ãããšãæ å ±ãŠã£ã³ããŠããã¯ã©ã€ã¢ã³ãã¢ã¯ã»ã¹ãã°ã確èªããããšãã§ããŸãã
5.4,MultiPlay åŒã³åºã
MultiPlayã®æºåãã§ããŸããããå®éã®ãã¬ã€ã®åã«ãã®ç°å¢ãåŒã³åºãå¿ èŠããããŸãã Hierarchyã§ç©ºã® GameObject ãçæããååããClientStarterãã«å€æŽããŸãã ããã«Inspectorãã AddCompornent ã§ãZepetScriptããã»ããããŸãã
ç¶ããŠAssetãã©ã«ãã®å éšã§ Create ãã ZEPETO â TypeScript ãäœæããååããClientStarterãã«æå®ããŠãããŸãã
ClientStarter ã«ã¯Playerã®åŒã³åºããåé€ãªã©ã®ã³ãŒããè¿œèšããŸãããµã³ãã«ã³ãŒããèŒããŠãããŸãããå¿ èŠã«å¿ã䜿ã£ãŠã¿ãŠãã ããã
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} from 'ZEPETO.Character.Controller'
import * as UnityEngine from "UnityEngine";
export default class Starter extends ZepetoScriptBehaviour {
public multiplay: ZepetoWorldMultiplay;
private room: Room;
private currentPlayers: Map<string, Player> = new Map<string, Player>();
public playerPos: Vector3;
private Start() {
this.multiplay.RoomCreated += (room: Room) => {
this.room = room;
};
this.multiplay.RoomJoined += (room: Room) => {
room.OnStateChange += this.OnStateChange;
};
this.StartCoroutine(this.SendMessageLoop(0.1));
}
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 myPlayer = ZepetoPlayers.instance.GetPlayer(this.room.SessionId);
if (myPlayer.character.CurrentState != CharacterState.Idle)
this.SendTransform(myPlayer.character.transform);
}
}
}
}
private OnStateChange(state: State, isFirst: boolean) {
if (isFirst) {
ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
const myPlayer = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer;
myPlayer.character.OnChangedState.AddListener((cur, prev) => {
this.SendState(cur);
});
});
ZepetoPlayers.instance.OnAddedPlayer.AddListener((sessionId: string) => {
const isLocal = this.room.SessionId === sessionId;
if (!isLocal) {
const player: Player = this.currentPlayers.get(sessionId);
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);
});
join.forEach((player: Player, sessionId: string) => this.OnJoinPlayer(sessionId, player));
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);
zepetoPlayer.character.MoveToPosition(position);
if (player.state === CharacterState.JumpIdle || player.state === CharacterState.JumpMove)
zepetoPlayer.character.Jump();
}
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);
this.room.Send("onChangedState", data.GetObject());
}
private ParseVector3(vector3: Vector3): UnityEngine.Vector3 {
return new UnityEngine.Vector3
(
vector3.x,
vector3.y,
vector3.z
);
}
}
äœæããClientStarter.ts ã ClientStarter ã® ZepetScript ã«ã»ãããããšãPublicå€æ°ã§å®£èšããMultiPlay ãã»ããããã¹ããŒã¹ãçãŸããŸããããã«ã¯äºåã«Hierarchyã«äœæãããMultiPlayããassignããŠãããŸãã
ããã§æºåãæŽããŸãããæåŸã«ãããžã§ã¯ããèµ·åãããŠã¿ãŸãããã
äžã®å³ã®ããã«ãäºåã«äœã£ãã¹ãã³ãããã®ã¢ãã¿ãŒãšãèªèº«ã®ã¢ãã¿ãŒãåæã«è¡šç€ºãããã°OKã§ããå®éã«åãã®ã¯ãèªèº«ã®ã¢ãã¿ãŒã§ãã
æåŸã¯ãäžèŠãªã¹ãã³ãããã¢ãã¿ãŒãé衚瀺ã«ãããã®ã§ãHierarchyäžã«ã®GameObject ã® Inspectorããã¢ã¯ãã£ãç¶æ ã®ãã§ãã¯ãã¯ãããç¡å¹åããŠãããŸãã
ããã§äžã®ãããªã¯ãŒã«ããã§ããŸããã ãèå³ããããŸããããStoneCircle on Islandãã§æ€çŽ¢ããŠã¿ãŠéãã§ãã£ãŠãã ããïŒ
ããŠããããŸã§ã®åãã§ããŸãåããªãã£ãå Žåããã¡ã€ãããã¯ã¹ã§ã¯ãªã³ã©ã€ã³çã§ããµããŒãèŽããŸãããæ°è»œã«ãåãåãããã ããããåãåããã¯ããã¡ããããã
次åã¯ã«ã¡ã©ã«ã€ããŠã説æããããŸãã
ãã¡ã€ãããã¯ã¹ã§ã¯å®éã«éã£ãŠé ããŠã®ææ¥ã®ä»ãã¡ãã£ãšãããå°ãããšã«å¯Ÿãããªã³ã©ã€ã³ææ¥ããªã³ã©ã€ã³ãµããŒããè¡ã£ãŠãããŸãã ãå°ãããšã®ããæ¹ããèå³ãããæ¹ã¯ããã²ãåãåãããã ããã ãåãåããã¯ããã¡ããããã äœéšææ¥ã®ãç³èŸŒã¿ã¯ããã¡ããããã