ZEPETO/Unity(3): 월드 로직 작성(2)
위치 동기화 원리
접속한 모든 클라이언트는 서버의 state에 자신의 위치 정보를 전송한다.
이렇게 state에 저장된 클라이언트들의 위치 정보는 OnState 를 통해 동기화되게 된다.
순서 1. StartCoroutine으로 클라이언트 위치 정보를 정기적으로 전달
[ClientStarter.ts]
Start() {
...
// StartCoroutine정기적으로 클라이언트의 위치정보 전달
this.StartCoroutine(this.SendMessageLoop(0.1));
}
SendMessageLoop() 함수 정의
private * SendMessageLoop(tick: number) {
while (true) {
yield new UnityEngine.WaitForSeconds(tick);
if (this.room != null && this.room.IsConnected) {
// 로컬 플레이어의 인스턴스 존재여부를 판단 HasPlayer
const hasPlayer = ZepetoPlayers.instance.HasPlayer(this.room.SessionId);
// 로컬 플레이어가 있다면 myPlayer에 로컬 플레이어 인스턴스를 저장
if (hasPlayer) {
const myPlayer = ZepetoPlayers.instance.GetPlayer(this.room.SessionId);
// 만약 myPlayer가 움직이면, 내 위치 정보를 전송
// SendTransform 함수로 정의하여 전송
if (myPlayer.character.CurrentState != CharacterState.Idle) {
this.SendTransform(myPlayer.character.transform);
}
}
}
}
}
SendTransform() 함수로 서버에 전달할 Transform 함수
// 내 위치 정보를 전송하는 함수
private SendTransform(transform: UnityEngine.Transform) {
// transform을 전송하기 위해 RoomData 객체 생성
const data = new RoomData();
// position을 넣어줄 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());
// rotation을 넣어줄 RoomData 객체 생성
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());
// onChanged 타입으로 메세지를 전송
this.room.Send("onChangedTransform", data.GetObject);
}
순서 2. Room 상태 동기화하기
[index.ts]
onCreate() 함수에서 개별 클라이언트들의 위치를 수신하여 player에 전달
onCreate(options: SandboxOptions) {
// 개별 클라이언트들의 위치를 수신받을 수 있도록
// onChangedTransform 메시지 리스너
this.onMessage("onChangedTransform", (client, message) => {
const player = this.state.players.get(client.sessionId);
// 받아온 위치 정보를 받기 위한 transform 객체
const transform = new Transform();
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;
// message에서 전달받은 플레이어 위치 정보를 player에 저장
player.transform = transform;
});
순서 3. 다른 클라이언트 위치 수신받기
[ClientStarter.ts > OnStateChange()]
local player가 아닌 player의 위치정보를 업데이트하기 위한 리스너 추가
// 캐릭터 정보를 전송받기 위해 OnAddedPlayer에 새로운 리스너를 추가
ZepetoPlayers.instance.OnAddedPlayer.AddListener((sessionId: string) => {
// local player가 아닌 경우에만 업데이트하도록
// local player인지 판단하는 isLocal 상수를 생성
const isLocal = this.room.SessionId === sessionId;
// Local player가 아니라면
if (!isLocal) {
// current players에 sessionId에 해당하는 플레이어를 불러옴
const player: Player = this.currentPlayers.get(sessionId);
// player의 위치 정보를 업데이트 하는 함수 선언
player.OnChange += (ChangeValues) => this.OnUpdatePlayer(seesionId, player);
}
});
순서 4. 다른 클라이언트 위치 수신받기
[ClientStarter.ts > OnUpdatePlayer() 신규 생성]
player의 위치 정보를 받기 위한 함수 선언
private OnUpdatePlayer(sessoinId: string, player: Player) {
const position = this.ParseVector3(player.transform.position);
}
[ClientStarter.ts > ParseVector3() 신규 생성]
ParseVector3에서 인자로 포함된 Schema 타입의 Vector3를 유니티엔진의 Vector3 로 변환
// ParseVector3 에서 인자로 포함된 Schema 타입의 Vector3를 유니티엔진의 Vector3로 변환
private ParseVector3(vector3: Vector3): UnityEngine.Vector3 {
return new UnityEngine.Vector3(
vector3.x,
vector3.y,
vector3.z
);
}
위에서 Vetor3 타입을 받아오지 못해, 에러가 발생하는데 Schema로부터 Vector3 클래스를 받아와야한다.
import { Player, State, Vector3 } from 'ZEPETO.Multiplay.Schema';
player가 점프 상태라면 캐릭터를 점프시킴
private OnUpdatePlayer(sessionId: string, player: Player) {
...
// 위치를 업데이트할 player를 받아옴
const zepetoPlayer = ZepetoPlayers.instance.GetPlayer(sessionId);
// MoveToPosition() 함수로 플레이어를 이동
zepetoPlayer.character.MoveToPosition(position);
// 플레이어 상태가 점프인 경우, 점프 상태를 호출
if (player.state === CharacterState.JumpIdle || player.state === CharacterState.JumpMove)
zepetoPlayer.character.Jump();
}
순서 5. Room 퇴장 로직 작성
[index.ts > onLeave()]
Room 퇴장시 클라이언트의 sessionId를 삭제
// Room 퇴장시 클라이언트의 sessionId 삭제
onLeave(client: SandboxPlayer, consented?: boolean) {
this.state.players.delete(client.sessionId);
}
[ClientStarter.tst > OnStateChange() ]
OnStateChange()에서 leave를 처리
...
// leave 한 플레이어를 관리하기 위해 맵을 생성하여 새로운 변수 leave에 할당
let leave = new Map<string, Player>(this.currentPlayers);
// schema에 저장된 room state의 값을 foreach로 하나씩 조회
state.players.ForEach((sessionId: string, player: Player) => {
if (this.currentPlayers.has(sessionId)) {
join.set(sessionId, player);
}
// Room에 존재하는 player는 모두 제거하고, 퇴장한 player만 leave에 추가
leave.delete(sessionId);
});
// room에 플레이어가 입장할 때 event를 받을 수 있게 플레이어 객체에 .OnJoinPlayer()를 연결
join.forEach((player: Player, sessionId: string) => this.OnJoinPlayer(sessionId, player));
// Room에서 퇴장한 모든 player 인스턴스를 제거하기 위해 OnLeavePlayer() 호출
leave.forEach((player: Player, sessionId: string) => this.OnLeavePlayer(sessionId, player));
}
OnLeavePlayer() 함수 정의
private OnLeavePlayer(sessionId: string, player: Player) {
console.log(`[OnRemove] players - sessionId : ${sessionId}`);
this.currentPlayers.delete(sessionId);
ZepetoPlayers.instance.RemovePlayer(sessionId);
}
이것을 모든 월드 로직 작성은 끝났다.
그런데 실행을 하니, 플레이어 인스턴스와 모바일 인풋이 보이지 않는다.
아무래도 받아온 player 정보를 멀티플레이 월드에서 생성해야 되는 코드가 빠진 것 같다.
댓글남기기