물리
Fusion - Network Project Config - Server Physics Mode를 Client Prediction으로 하면 더 많이 연산이 들지만 더 정확하게 물리를 계산한다.
물리 객체는 NetworkRigidbody 컴포넌트로 동기화한다.
이전에 transform 이동과 비슷하게 구현했는데 여기선 Rigidbody의 Velocity를 forward로 하여 초기 속도 값을 정해줬다.
public class PhysxBall : NetworkBehaviour
{
[Networked] TickTimer life { get; set; }
public void Init(Vector3 forward)
{
life = TickTimer.CreateFromSeconds(Runner, 5.0f);
GetComponent<Rigidbody>().velocity = forward;
}
public override void FixedUpdateNetwork()
{
if (life.Expired(Runner))
{
Runner.Despawn(Object);
}
}
}
마우스 버튼을 하나 더 매핑해주고
public struct NetworkInputData : INetworkInput
{
// const로 선언한 상수는 내부에서 자동으로 static 가 된다.
public const byte MOUSEBUTTON1 = 0x01;
public const byte MOUSEBUTTON2 = 0x02;
public byte buttons;
public Vector3 direction;
}
조건 또한 비슷하게 만들어주었다.
public class PhotonInstantiate : MonoBehaviour, INetworkRunnerCallbacks
{
// NetworkObject의 요소로 GUID(고유ID)를 가지고 있다.
[SerializeField] NetworkPrefabRef _playerPrefab;
// PlayerRef default 0 : None, 1 : index 0, 2 : index 1
Dictionary<PlayerRef, NetworkObject> _spawnedCharacters = new Dictionary<PlayerRef, NetworkObject>();
NetworkRunner _runner;
bool _mouseButton0;
bool _mouseButton1;
void Update()
{
// 입력은 FixedUpdateNetwork에서 틱 단위로 호출되고
// bool 체크는 Update에서 하니까 frame마다 호출되어 단위가 달라
// OnInput에서 실행하고 false로 만들어주는 식이다.
_mouseButton0 |= Input.GetMouseButton(0);
_mouseButton1 |= Input.GetMouseButton(1);
}
async void StartGame(GameMode mode)
{
// GameMode
// 1. 싱글 2. 쉐어드 - 포톤 클라우드 이용(사용자는 클라이언트) 3. 서버(게임 서버를 직접 지원하고 원격 플레이어만 허용)
// 4. 호스트(로컬 플레이어를 허용하는 게임서버) 5. 클라이언트(호스트나 게임 모드에 클라이언트로 시작)
// 6. 자동(첫 번째 접속시 호스트 모드, 나중 접속시 클라이언트 모드)
// NetworkRunner : 플레이어와 관련된 변수들을 가지고있는 클래스
_runner = gameObject.AddComponent<NetworkRunner>();
// NetworkRunner가 클라이언트로부터 Input을 받을지 여부
_runner.ProvideInput = true;
// NetworkRunner.StartGame(StartGameArgs args) : 매치메이킹 세팅을 해주는 메서드
// 비동기로 NetworkRunner.StartGame(StartGameArgs args) 실행
await _runner.StartGame(new StartGameArgs
{
GameMode = mode,
SessionName = "TestRoom", // 세션 이름(클라이언트와 서버 세션 이름)
Scene = SceneManager.GetActiveScene().buildIndex, // Scene은 구조체인 SceneRef?타입
// NetworkSceneManagerDefault : 비동기 씬 관련 메서드가 포함된 클래스
SceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>(), // SceneManager INetworkSceneManager 타입이다.
});
}
void OnGUI()
{
if (!_runner)
{
if (GUI.Button(new Rect(0, 0, 400, 100), "Host"))
{
StartGame(GameMode.Host);
}
if (GUI.Button(new Rect(0, 150, 400 , 100), "Join"))
{
StartGame(GameMode.Client);
}
}
}
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
// 연결중이면
if (runner.IsServer)
{
// 특정한 위치 저장
Vector3 spawnPosition =
new Vector3((player.RawEncoded % runner.Config.Simulation.DefaultPlayers) * 3, 1, 0);
// NetworkRunner.Spawn이 네트워크 Instantitate와 비슷한 역할인데 추가로 PlayerRef를 받는다.
NetworkObject networkPlayerObject = runner.Spawn(_playerPrefab, spawnPosition, Quaternion.identity, player);
// 딕셔너리에 추가
_spawnedCharacters.Add(player, networkPlayerObject);
}
}
public void OnPlayerLeft(NetworkRunner runner, PlayerRef player)
{
if (_spawnedCharacters.TryGetValue(player, out NetworkObject networkObject))
{
// 파괴 후 반환
runner.Despawn(networkObject);
_spawnedCharacters.Remove(player);
}
}
public void OnInput(NetworkRunner runner, NetworkInput input)
{
var data = new NetworkInputData();
if (Input.GetKey(KeyCode.W))
data.direction += Vector3.forward;
if (Input.GetKey(KeyCode.S))
data.direction += Vector3.back;
if (Input.GetKey(KeyCode.A))
data.direction += Vector3.left;
if (Input.GetKey(KeyCode.D))
data.direction += Vector3.right;
if (_mouseButton0)
{
data.buttons |= NetworkInputData.MOUSEBUTTON1;
}
if (_mouseButton1)
{
data.buttons |= NetworkInputData.MOUSEBUTTON2;
}
_mouseButton0 = false;
_mouseButton1 = false;
input.Set(data);
}
}
발사하는 부분도 프리펩과 Init()의 인자만 다를 뿐 다 같게 설정해주었다.
public class Player : NetworkBehaviour
{
[Networked] TickTimer delay { get; set; }
[SerializeField] Ball _prefabBall;
[SerializeField] PhysxBall _prefabPhysxBall;
NetworkCharacterControllerPrototype _cc;
Vector3 _forward;
void Awake()
{
_cc = GetComponent<NetworkCharacterControllerPrototype>();
_forward = transform.forward;
}
public override void FixedUpdateNetwork()
{
if (GetInput(out NetworkInputData data))
{
data.direction.Normalize();
_cc.Move(5 * data.direction * Runner.DeltaTime);
// OnInput에서 Set된 NetworkInputData.direction 값이 0보다 클 때
if (data.direction.sqrMagnitude > 0)
{
_forward = data.direction;
}
// NetworkRunner의 TickTimer가 도달했거나 세팅되지 않았을 때 -> 생성 빈도의 제한을 거는 것
if (delay.ExpiredOrNotRunning(Runner))
{
// NetworkInputData.MOUSEBUTTON1이 0x01이니 byte인 data.button이 1이고 &연산(둘 다 1일 때)으로 1이 될 때
if ((data.buttons & NetworkInputData.MOUSEBUTTON1) != 0)
{
delay = TickTimer.CreateFromSeconds(Runner, 0.5f);
// NetworkRunner의 바라보는 forward만큼 앞으로, 회전은 입력의 방향으로(data.direction의 방향)
// Object.InputAuthority는 누구의 입력에서 나왔는지에 대한 PlayerRef이다(PlayerID를 포함한 정보).
// Spawn하기 전에 파괴될 틱을 설정해주어야 하기 때문에(Life.CreateFromSeconds(NetworkRunner, int delayInSeconds))
// 마지막의 람다식 delegate void OnBeforeSpawned(NetworkRunner runner, NetworkObject obj)에서 Ball의 Init을 호출한다.
Runner.Spawn(_prefabBall, transform.position + _forward, Quaternion.LookRotation(_forward),
Object.InputAuthority,
(runner, o) =>
{
o.GetComponent<Ball>().Init();
});
}
else if ((data.buttons & NetworkInputData.MOUSEBUTTON2) != 0)
{
delay = TickTimer.CreateFromSeconds(Runner, 0.5f);
// NetworkRunner의 바라보는 forward만큼 앞으로, 회전은 입력의 방향으로(data.direction의 방향)
// Object.InputAuthority는 누구의 입력에서 나왔는지에 대한 PlayerRef이다(PlayerID를 포함한 정보).
// Spawn하기 전에 파괴될 틱을 설정해주어야 하기 때문에(Life.CreateFromSeconds(NetworkRunner, int delayInSeconds))
// 마지막의 람다식 delegate void OnBeforeSpawned(NetworkRunner runner, NetworkObject obj)에서 Ball의 Init을 호출한다.
Runner.Spawn(_prefabPhysxBall, transform.position + _forward, Quaternion.LookRotation(_forward),
Object.InputAuthority,
(runner, o) =>
{
o.GetComponent<PhysxBall>().Init(10 * _forward);
});
}
}
}
}
}
'공부 모음 > 포톤 퓨전 시작해보기' 카테고리의 다른 글
포톤 퓨전 원격 프로시져 호출(RPC) (0) | 2022.12.09 |
---|---|
포톤 퓨전 속성 변경 (0) | 2022.12.09 |
포톤 퓨전 Prediction (0) | 2022.12.08 |
포톤 퓨전 시작하기, 씬 설정하기 (0) | 2022.12.07 |