HostMigrationManager.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Bolt;
using Bolt.Matchmaking;
using UdpKit;
using System.Linq;
public class HostMigrationManager : GlobalEventListener
{
[SerializeField] GameObject titlePanel;
[SerializeField] GameObject hostMigrationPlayerPrefab;
[SerializeField] BoltEntity myEntity;
[SerializeField] Vector3 myEntityPos;
[SerializeField] bool nextHost;
int serverPort = 1000;
#region 참여와 셧다운
void Start()
{
titlePanel.SetActive(true);
}
public void StartServer()
{
serverPort = Random.Range(1000, 25000);
BoltLauncher.StartServer(new UdpEndPoint(UdpIPv4Address.Any, (ushort)serverPort));
}
public void StartClient() => BoltLauncher.StartClient(UdpEndPoint.Any);
public override void BoltStartDone()
{
myEntity = null;
nextHost = false;
if (BoltNetwork.IsServer)
BoltMatchmaking.CreateSession(sessionID: "room");
else
BoltMatchmaking.JoinSession("room");
}
public override void BoltShutdownBegin(AddCallback registerDoneCallback, UdpConnectionDisconnectReason disconnectReason)
{
registerDoneCallback(BoltShutdownCallback);
}
void BoltShutdownCallback()
{
if (nextHost)
StartServer();
else
StartClient();
}
#endregion
public override void SceneLoadLocalDone(string scene, IProtocolToken token)
{
titlePanel.SetActive(false);
Vector3 spawnPos = new Vector3(Random.Range(-3f, 3f), 0, 0);
if (myEntityPos != Vector3.zero)
spawnPos = myEntityPos;
myEntity = BoltNetwork.Instantiate(hostMigrationPlayerPrefab, spawnPos, Quaternion.identity);
myEntity.TakeControl();
if (BoltNetwork.IsServer)
StartCoroutine(NextHostCheckCo());
}
IEnumerator NextHostCheckCo()
{
// 서버에서 다음 호스트를 체크함
while (true)
{
yield return new WaitForSeconds(5);
var entities = BoltNetwork.Entities.ToList();
bool isSetNextHost = false;
foreach (var entity in entities)
{
bool nextHost = entity.NetworkId != myEntity.NetworkId && !isSetNextHost;
if (nextHost)
isSetNextHost = true;
var evnt = HostMigrationNextHostEvent.Create();
evnt.networkId = entity.NetworkId;
evnt.nextHost = nextHost;
evnt.nextPort = serverPort;
evnt.Send();
}
}
}
public override void OnEvent(HostMigrationNextHostEvent evnt)
{
if (evnt.networkId == myEntity.NetworkId)
{
nextHost = evnt.nextHost;
serverPort = evnt.nextPort;
}
}
void Update()
{
if (myEntity != null)
myEntityPos = myEntity.transform.position;
}
}
HostMigrationPlayer.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Bolt;
public class HostMigrationPlayer : EntityBehaviour<IHostMigrationPlayerState>
{
[SerializeField] float speed;
void Update()
{
if (entity.IsOwner)
{
transform.Translate(new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0)
* Time.deltaTime * speed);
state.position = transform.position;
}
else
{
transform.position = Vector3.Lerp(transform.position, state.position, 0.3f);
}
}
}
NetworkManager.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Bolt.Matchmaking;
using Bolt;
public class NetworkManager : GlobalEventListener
{
public static NetworkManager Inst { get; private set; }
void Awake() => Inst = this;
[SerializeField] GameObject playerPrefab;
[SerializeField] GameObject titlePanel;
[SerializeField] InputField nickInput;
public string myNickname => nickInput.text;
void Start()
{
titlePanel.SetActive(true);
}
public void StartServer() => BoltLauncher.StartServer();
public void StartClient() => BoltLauncher.StartClient();
public override void BoltStartDone()
{
if (BoltNetwork.IsServer)
BoltMatchmaking.CreateSession(sessionID: "room");
else
BoltMatchmaking.JoinSession("room");
}
public override void SceneLoadLocalDone(string scene, IProtocolToken token)
{
titlePanel.SetActive(false);
if (playerPrefab == null)
return;
Vector3 spawnPos = new Vector3(Random.Range(-3f, 3f), 0, 0);
BoltEntity entity = BoltNetwork.Instantiate(playerPrefab, spawnPos, Quaternion.identity);
entity.TakeControl();
}
}
InputSyncPlayer.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Bolt;
public class InputSyncPlayer : EntityBehaviour<IInputSyncPlayerState>
{
[SerializeField] float speed;
public override void Attached()
{
state.SetTransforms(state.transform, transform);
}
public override void SimulateController()
{
IInputSyncCommandInput input = InputSyncCommand.Create();
input.up = Input.GetKey(KeyCode.W);
input.down = Input.GetKey(KeyCode.S);
input.left = Input.GetKey(KeyCode.A);
input.right = Input.GetKey(KeyCode.D);
entity.QueueInput(input);
}
public override void ExecuteCommand(Command command, bool resetState)
{
InputSyncCommand cmd = (InputSyncCommand)command;
if (resetState)
{
transform.position = cmd.Result.position;
}
else
{
Vector3 dir = Vector3.zero;
if (cmd.Input.left ^ cmd.Input.right)
dir.x += cmd.Input.right ? 1 : -1;
if (cmd.Input.up ^ cmd.Input.down)
dir.y += cmd.Input.up ? 1 : -1;
transform.Translate(dir.normalized * BoltNetwork.FrameDeltaTime * speed);
cmd.Result.position = transform.position;
}
}
}
PhysicsPrecisionPlayer.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Bolt;
public class PhysicsPrecisionPlayer : EntityBehaviour<IPhysicsPrecisionPlayerState>
{
[SerializeField] float addforcePower;
Rigidbody rbody;
public override void Attached()
{
rbody = GetComponent<Rigidbody>();
state.SetTransforms(state.transform, transform);
}
public override void SimulateController()
{
IPhysicsPrecisionCommandInput input = PhysicsPrecisionCommand.Create();
input.up = Input.GetKey(KeyCode.W);
input.down = Input.GetKey(KeyCode.S);
input.left = Input.GetKey(KeyCode.A);
input.right = Input.GetKey(KeyCode.D);
entity.QueueInput(input);
}
public override void ExecuteCommand(Command command, bool resetState)
{
PhysicsPrecisionCommand cmd = (PhysicsPrecisionCommand)command;
if (resetState)
{
rbody.velocity = cmd.Result.velocity;
rbody.angularVelocity = cmd.Result.angularVelocity;
}
else
{
if (cmd.Input.up)
rbody.AddForce(Vector3.forward * BoltNetwork.FrameDeltaTime * addforcePower);
else if (cmd.Input.down)
rbody.AddForce(Vector3.back * BoltNetwork.FrameDeltaTime * addforcePower);
if (cmd.Input.right)
rbody.AddForce(Vector3.right * BoltNetwork.FrameDeltaTime * addforcePower);
else if (cmd.Input.left)
rbody.AddForce(Vector3.left * BoltNetwork.FrameDeltaTime * addforcePower);
cmd.Result.velocity = rbody.velocity;
cmd.Result.angularVelocity = rbody.angularVelocity;
}
}
}
ManyAgentSpawner.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using Bolt;
using UnityEngine;
public class ManyAgentSpawner : GlobalEventListener
{
[SerializeField] GameObject agentPrefab;
[SerializeField] int agentCount;
public override void SceneLoadLocalDone(string scene, IProtocolToken token)
{
if (BoltNetwork.IsServer)
{
for (int i = 0; i < agentCount; i++)
BoltNetwork.Instantiate(agentPrefab, new Vector3(0, 2, 0), Quaternion.identity);
}
}
}
ManyAgent.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class ManyAgent : Bolt.EntityEventListener<IManyAgentState>
{
[SerializeField] NavMeshAgent agent;
NavMeshHit navMeshHit;
Vector3 curPos;
public override void Attached()
{
state.SetTransforms(state.transform, transform);
if (BoltNetwork.IsServer)
StartCoroutine(Navigation());
}
IEnumerator Navigation()
{
while (true)
{
while (true)
{
Vector3 randPos = Random.insideUnitCircle;
randPos = new Vector3(randPos.x, 0, randPos.y);
randPos = randPos * 30f + transform.position;
NavMesh.SamplePosition(randPos, out navMeshHit, 10f, NavMesh.AllAreas);
if (navMeshHit.position.x != Mathf.Infinity)
break;
}
agent.SetDestination(navMeshHit.position);
yield return new WaitForSeconds(5f);
}
}
void Update()
{
if (!entity.IsOwner)
{
transform.position = Vector3.Lerp(curPos, transform.position, 0.1f);
curPos = transform.position;
}
}
}
Tips.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Bolt;
using System.Linq;
public class Tips : GlobalEventListener
{
[SerializeField] GameObject spawnPrefab;
void Start()
{
// API문서 참조 : https://doc-api.photonengine.com/en/bolt/current/index.html
// 셧다운
BoltLauncher.Shutdown();
// 싱글플레이로 실행, CCU 포함 안됨
BoltLauncher.StartSinglePlayer();
// OverlapSphereAll 원을 그려 충돌한 모든 엔티티 가져오기
if (GetComponent<BoltEntity>().IsOwner)
{
Vector3 origin = Vector3.zero;
float radius = 2f;
var hits = BoltNetwork.OverlapSphereAll(origin, radius);
for (int i = 0; i < hits.count; i++)
{
var hit = hits.GetHit(i);
var targetEntity = hit.body.GetComponent<BoltEntity>();
}
}
// RaycastAll 레이를 쏴 충돌한 모든 엔티티 가져오기
if (GetComponent<BoltEntity>().IsOwner)
{
var hits = BoltNetwork.RaycastAll(new Ray(transform.position, Vector3.down));
var hit = hits.GetHit(0);
var targetEntity = hit.body.GetComponent<BoltEntity>();
}
// 접속한 모든 클라이언트들 가져오기
{
IEnumerable<BoltConnection> clients = BoltNetwork.Clients;
List<BoltConnection> clientList = clients.ToList();
print(clientList[0].ConnectionId);
print(clientList[0].PingNetwork);
clientList[0].Disconnect();
}
// 현재 씬에 로드된 모든 엔티티 가져오기
IEnumerable<BoltEntity> entities = BoltNetwork.Entities;
// Time.fixedDeltaTime, Time.time과 같다
float frameDeltaTime = BoltNetwork.FrameDeltaTime;
float time = BoltNetwork.Time;
// bool
bool isServer = BoltNetwork.IsServer;
bool isClient = BoltNetwork.IsClient;
bool isSinglePlayer = BoltNetwork.IsSinglePlayer;
bool isDebugMode = BoltNetwork.IsDebugMode;
bool isRunning = BoltNetwork.IsRunning;
// 하나이상 연결이 되었는지, 서버 혼자 열면 연결된게 아니라,
// 최소한 하나의 통신로가 존재해야 하므로 하나의 클라가 있어야함
bool isConnected = BoltNetwork.IsConnected;
// 서버 프레임, 시간 가져오기
int serverFrame = BoltNetwork.ServerFrame;
float serverTime = BoltNetwork.ServerTime;
// Photon Bolt 어셈블리 버전 번호
System.Version version = BoltNetwork.Version;
version.ToString();
// Bolt탭 - Settings - Miscellaneous - Log Targets를 Unity, Console 체크 -
// Show Debug Info 체크 - Tab으로 디버그 보이게 할수 있다
BoltLog.Info("Info");
BoltLog.Warn("Warn");
BoltLog.Error("Error");
}
public override void SceneLoadLocalDone(string scene, IProtocolToken token)
{
if (BoltNetwork.IsServer)
{
BoltEntity entity = BoltNetwork.Instantiate(spawnPrefab);
// 생성도 서버가 하고 서버가 조종하도록 컨트롤을 달아줌
entity.TakeControl();
}
}
// 리모트 씬이 불러와짐
public override void SceneLoadRemoteDone(BoltConnection connection, IProtocolToken token)
{
if (BoltNetwork.IsServer)
{
BoltEntity entity = BoltNetwork.Instantiate(spawnPrefab);
// 생성은 서버가 했지만 연결된 클라이언트가 제어하도록 컨트롤을 달아줌, 서버에서 연산에 유리
entity.AssignControl(connection);
// 엔티티 판단
bool hasControl = entity.HasControl;
bool isOwner = entity.IsOwner;
bool isControllerOrOwner = entity.IsControllerOrOwner;
NetworkId networkId = entity.NetworkId;
print(networkId.PackedValue);
}
}
}
FallGuysManager.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Bolt;
using Bolt.Matchmaking;
public class FallGuysManager : GlobalEventListener
{
public static FallGuysManager Inst { get; private set; }
void Awake() => Inst = this;
[SerializeField] GameObject playerPrefab;
[SerializeField] GameObject titlePanel;
[SerializeField] InputField nickInput;
public string myNickname => nickInput.text;
void Start()
{
titlePanel.SetActive(true);
}
public void StartServer() => BoltLauncher.StartServer();
public void StartClient() => BoltLauncher.StartClient();
public override void BoltStartDone()
{
if (BoltNetwork.IsServer)
BoltMatchmaking.CreateSession(sessionID: "room");
else
BoltMatchmaking.JoinSession("room");
}
public override void SceneLoadLocalDone(string scene, IProtocolToken token)
{
titlePanel.SetActive(false);
Vector3 spawnPos = new Vector3(Random.Range(-5f, 5f), 0, Random.Range(-5f, 5f));
BoltEntity entity = BoltNetwork.Instantiate(playerPrefab, spawnPos, Quaternion.identity);
entity.TakeControl();
}
}
FallGuysCameraArm.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FallGuysCameraArm : MonoBehaviour
{
[SerializeField] float minY;
[SerializeField] float maxY;
[SerializeField] Transform cmCamera;
public Vector3 camForward;
public Transform target;
void Update()
{
if (target == null)
return;
transform.position = target.position + Vector3.up * 1.66f;
Vector2 mouseDelta = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));
Vector3 camAngle = transform.rotation.eulerAngles;
transform.rotation = Quaternion.Euler(Mathf.Clamp(camAngle.x + mouseDelta.y, minY, maxY), camAngle.y + mouseDelta.x, camAngle.z);
camForward = transform.position - cmCamera.position;
camForward.y = 0;
camForward.Normalize();
}
}
FallGuysPlayer.cs 소스입니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Bolt;
public class FallGuysPlayer : EntityBehaviour<IFallGuysPlayerState>
{
[SerializeField] float speed;
[SerializeField] float rotationLerp;
[SerializeField] float jumpPower;
[SerializeField] float jumpCoolTime;
[SerializeField] Text nickText;
[SerializeField] Transform playerCanvas;
Animator animator;
Rigidbody rbody;
FallGuysCameraArm cameraArm;
bool isGround;
bool jumpable = true;
public override void Attached()
{
rbody = GetComponent<Rigidbody>();
animator = GetComponent<Animator>();
state.SetTransforms(state.transform, transform);
state.SetAnimator(animator);
state.nickname = FallGuysManager.Inst.myNickname;
state.AddCallback("nickname", NicknameCallback);
}
void NicknameCallback()
{
nickText.text = state.nickname;
}
void Start()
{
cameraArm = FindObjectOfType<FallGuysCameraArm>();
if (entity.IsOwner)
cameraArm.target = transform;
}
public override void SimulateController()
{
IFallGuysPlayerCommandInput input = FallGuysPlayerCommand.Create();
input.up = Input.GetKey(KeyCode.W);
input.down = Input.GetKey(KeyCode.S);
input.left = Input.GetKey(KeyCode.A);
input.right = Input.GetKey(KeyCode.D);
input.jump = Input.GetKey(KeyCode.Space);
entity.QueueInput(input);
}
public override void ExecuteCommand(Command command, bool resetState)
{
FallGuysPlayerCommand cmd = (FallGuysPlayerCommand)command;
if (resetState)
{
rbody.velocity = cmd.Result.velocity;
rbody.angularVelocity = cmd.Result.angularVelocity;
}
else
{
Vector3 dir = Vector3.zero;
// WASD 방향벡터와 부드러운 회전
if (cmd.Input.up)
{
dir += cameraArm.camForward;
transform.rotation = Quaternion.Lerp(transform.rotation,
Quaternion.LookRotation(cameraArm.camForward), rotationLerp);
}
else if (cmd.Input.down)
{
dir += -cameraArm.camForward;
transform.rotation = Quaternion.Lerp(transform.rotation,
Quaternion.LookRotation(-cameraArm.camForward), rotationLerp);
}
if (cmd.Input.right)
{
Vector3 camRight = Quaternion.Euler(0, 90, 0) * cameraArm.camForward;
dir += camRight;
transform.rotation = Quaternion.Lerp(transform.rotation,
Quaternion.LookRotation(camRight), rotationLerp);
}
else if (cmd.Input.left)
{
Vector3 camLeft = Quaternion.Euler(0, -90, 0) * cameraArm.camForward;
dir += camLeft;
transform.rotation = Quaternion.Lerp(transform.rotation,
Quaternion.LookRotation(camLeft), rotationLerp);
}
// 걷기 애니메이션
state.isWalk = dir != Vector3.zero;
animator.SetBool("isWalk", state.isWalk);
rbody.velocity = dir.normalized * speed + rbody.velocity.y * Vector3.up;
isGround = Physics.Raycast(transform.position + Vector3.up, Vector3.down, 1.1f);
// 점프
if (cmd.Input.jump && jumpable && isGround)
{
state.jumpTrigger();
rbody.velocity = Vector3.zero;
rbody.AddForce(Vector3.up * jumpPower);
Invoke(nameof(JumpCoolTimeDelay), jumpCoolTime);
jumpable = false;
}
cmd.Result.velocity = rbody.velocity;
cmd.Result.angularVelocity = rbody.angularVelocity;
}
}
void JumpCoolTimeDelay() => jumpable = true;
void LateUpdate() => playerCanvas.rotation = Camera.main.transform.rotation;
void Update()
{
// Y만 따로 동기화
if (entity.IsOwner)
state.Y = transform.position.y;
else
{
Vector3 pos = transform.position;
pos.y = state.Y;
transform.position = pos;
}
}
}