adding multiplayer
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using RebootKit.Engine.Services.Simulation;
|
||||
using RebootKit.Engine.Simulation;
|
||||
using RebootReality.jelycho.Main;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -13,7 +14,7 @@ namespace RebootReality.jelycho.Abilities {
|
||||
public void RemoveAbility(AbilityEffect ability) {
|
||||
}
|
||||
|
||||
public override void OnTick(float deltaTime) {
|
||||
public override void Tick(float deltaTime) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using RebootKit.Engine.Services.Simulation;
|
||||
using RebootKit.Engine.Simulation;
|
||||
|
||||
namespace RebootReality.jelycho.Beacons {
|
||||
public class BeaconActor : Actor {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using RebootKit.Engine.Services.Simulation;
|
||||
using RebootKit.Engine.Simulation;
|
||||
using RebootReality.jelycho.Main;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2eebc735c0624eadb3b5fc5a7cc611d7
|
||||
timeCreated: 1746878778
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 408f07fbb2a14c019e5fc3324a24454c
|
||||
timeCreated: 1746878783
|
||||
@@ -1,59 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using RebootKit.Engine.Foundation;
|
||||
using RebootKit.Engine.Main;
|
||||
using RebootKit.Engine.Services.GameMode;
|
||||
using RebootKit.Engine.Services.Simulation;
|
||||
using RebootReality.jelycho.Player;
|
||||
using UnityEngine.AddressableAssets;
|
||||
|
||||
namespace RebootReality.jelycho.GameMode.Standard {
|
||||
public class StandardGameMode : IGameMode {
|
||||
[Serializable]
|
||||
public class Config {
|
||||
public PlayerControllerAsset playerControllerAsset;
|
||||
public AssetReferenceGameObject playerPrefab;
|
||||
|
||||
public WorldConfigAsset worldConfig;
|
||||
}
|
||||
|
||||
Config m_Config;
|
||||
|
||||
[Inject] DIContext m_DIContext;
|
||||
|
||||
CancellationTokenSource m_DisposeCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
ControllersManager<IController> m_ControllersManager;
|
||||
PlayerController m_PlayerController;
|
||||
|
||||
public StandardGameMode(Config config) {
|
||||
m_Config = config;
|
||||
|
||||
m_ControllersManager = new ControllersManager<IController>(m_DisposeCancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
public async UniTask OnInit(CancellationToken cancellationToken) {
|
||||
await RR.World.LoadAsync(m_Config.worldConfig.Config, cancellationToken);
|
||||
|
||||
m_ControllersManager.Add(m_Config.playerControllerAsset);
|
||||
m_PlayerController = m_ControllersManager.Get<PlayerController>();
|
||||
}
|
||||
|
||||
public void OnStart() {
|
||||
m_ControllersManager.Start(m_DisposeCancellationTokenSource.Token).Forget();
|
||||
}
|
||||
|
||||
public void OnStop() {
|
||||
m_ControllersManager.Stop();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
m_DisposeCancellationTokenSource.Cancel();
|
||||
}
|
||||
|
||||
public void OnTick() {
|
||||
m_ControllersManager.Tick();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 83af58c3064e420687c37222c18c472e
|
||||
timeCreated: 1746878772
|
||||
@@ -1,14 +0,0 @@
|
||||
using RebootKit.Engine.Services.GameMode;
|
||||
using RebootReality.jelycho.Main;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RebootReality.jelycho.GameMode.Standard {
|
||||
[CreateAssetMenu(menuName = GameConsts.k_AddComponentMenu + "Game Modes/Standard")]
|
||||
public class StandardGameModeAsset : GameModeAsset {
|
||||
[SerializeField] StandardGameMode.Config m_Config;
|
||||
|
||||
public override IGameMode Create() {
|
||||
return new StandardGameMode(m_Config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a6162ffd783458ba697dcc1e1c40bea
|
||||
timeCreated: 1746879123
|
||||
@@ -1,150 +1,168 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using R3;
|
||||
using System.Collections.Generic;
|
||||
using RebootKit.Engine.Main;
|
||||
using RebootKit.Engine.Services.Console;
|
||||
using RebootKit.Engine.Services.Crosshair;
|
||||
using RebootKit.Engine.Services.GameMode;
|
||||
using RebootKit.Engine.Services.Simulation;
|
||||
using RebootReality.jelycho.Player;
|
||||
using Unity.Collections;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using Logger = RebootKit.Engine.Foundation.Logger;
|
||||
|
||||
namespace RebootReality.jelycho.Main {
|
||||
public static class JelychoConsoleCommands {
|
||||
[RCCMD("start", "Starts game mode with given name. Usage: start <game_mode_name> <world_name>")]
|
||||
public static void StartGameMode(string[] args) {
|
||||
if (args.Length != 3) {
|
||||
RR.Console.WriteToOutput($"Usage: {args[0]} <game_mode_name> <world_name>");
|
||||
class PlayerState : INetworkSerializable {
|
||||
public ulong clientID;
|
||||
|
||||
public PlayerController Controller;
|
||||
public PlayerActor Actor;
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter { }
|
||||
}
|
||||
|
||||
public class JelychoGame : Game {
|
||||
static readonly Logger s_Logger = new Logger(nameof(JelychoGame));
|
||||
|
||||
[SerializeField] PlayerController m_PlayerControllerPrefab;
|
||||
[SerializeField] PlayerActor m_PlayerActorPrefab;
|
||||
[SerializeField] string m_MainWorldID = "dev";
|
||||
|
||||
List<PlayerState> m_PlayerStates = new List<PlayerState>();
|
||||
|
||||
void Awake() { }
|
||||
|
||||
public override void OnDestroy() {
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
public override void OnNetworkSpawn() {
|
||||
base.OnNetworkSpawn();
|
||||
|
||||
NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnected;
|
||||
NetworkManager.Singleton.OnClientDisconnectCallback += OnClientStopped;
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn() {
|
||||
base.OnNetworkDespawn();
|
||||
|
||||
NetworkManager.Singleton.OnClientConnectedCallback -= OnClientConnected;
|
||||
NetworkManager.Singleton.OnClientDisconnectCallback -= OnClientStopped;
|
||||
}
|
||||
|
||||
void OnClientConnected(ulong clientID) {
|
||||
s_Logger.Info($"Client {clientID} connected");
|
||||
|
||||
if (IsServer) {
|
||||
s_Logger.Info($"Creating player state for client {clientID}");
|
||||
|
||||
PlayerController controller = Instantiate(m_PlayerControllerPrefab);
|
||||
controller.name = $"PlayerController_{clientID}";
|
||||
controller.NetworkObject.SpawnAsPlayerObject(clientID);
|
||||
|
||||
m_PlayerStates.Add(new PlayerState {
|
||||
clientID = clientID,
|
||||
Controller = controller,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void OnClientStopped(ulong clientID) {
|
||||
s_Logger.Info($"Client {clientID} disconnected");
|
||||
|
||||
if (IsServer) {
|
||||
for (int i = m_PlayerStates.Count - 1; i >= 0; i--) {
|
||||
if (m_PlayerStates[i].clientID == clientID) {
|
||||
s_Logger.Info($"Removing player state for client {clientID}");
|
||||
m_PlayerStates.RemoveAtSwapBack(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnWorldLoaded() {
|
||||
base.OnWorldLoaded();
|
||||
|
||||
OnPlayerReadyRpc(NetworkManager.Singleton.LocalClientId);
|
||||
}
|
||||
|
||||
[Rpc(SendTo.Server)]
|
||||
void OnPlayerReadyRpc(ulong clientID) {
|
||||
if (!IsServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
string worldName = args[2];
|
||||
WorldConfigAsset worldConfig = RR.WorldConfigsAssets.FirstOrDefault(t => t.Config.name.Equals(worldName));
|
||||
if (worldConfig is null) {
|
||||
RR.Console.WriteToOutput($"World '{worldName}' not found");
|
||||
return;
|
||||
}
|
||||
PlayerActor actor = Instantiate(m_PlayerActorPrefab);
|
||||
actor.name = $"PlayerActor_{clientID}";
|
||||
actor.NetworkObject.SpawnAsPlayerObject(clientID);
|
||||
|
||||
foreach (GameModeAsset gameModeAsset in RR.GameModesAssets) {
|
||||
if (gameModeAsset.name == args[1]) {
|
||||
RR.Console.WriteToOutput($"Starting game mode '{gameModeAsset.name}'");
|
||||
RR.StartGameMode(gameModeAsset, worldConfig.Config);
|
||||
return;
|
||||
PlayerState playerState = GetPlayerState(clientID);
|
||||
Assert.IsNotNull(playerState);
|
||||
playerState.Actor = actor;
|
||||
playerState.Controller.SetActorClientRpc(actor.NetworkObjectId);
|
||||
}
|
||||
|
||||
PlayerState GetPlayerState(ulong clientID) {
|
||||
foreach (PlayerState state in m_PlayerStates) {
|
||||
if (state.clientID == clientID) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
RR.Console.WriteToOutput($"Game mode '{args[1]}' not found");
|
||||
s_Logger.Error($"Player state for client {clientID} not found");
|
||||
return null;
|
||||
}
|
||||
|
||||
[RCCMD("gamemodes", "Lists all game modes")]
|
||||
public static void GameModes(string[] args) {
|
||||
if (args.Length != 1) {
|
||||
RR.Console.WriteToOutput($"Usage: {args[0]}");
|
||||
return;
|
||||
}
|
||||
public override void OnWorldUnload() {
|
||||
base.OnWorldUnload();
|
||||
|
||||
RR.Console.WriteToOutput("Game modes:");
|
||||
foreach (GameModeAsset gameModeAsset in RR.GameModesAssets) {
|
||||
RR.Console.WriteToOutput($" {gameModeAsset.name}");
|
||||
}
|
||||
if (IsServer) { }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class JelychoConsoleCommands {
|
||||
[RCCMD("worlds", "Lists all worlds")]
|
||||
public static void Worlds(string[] args) {
|
||||
if (args.Length != 1) {
|
||||
RR.Console.WriteToOutput($"Usage: {args[0]}");
|
||||
RR.WriteToConsole($"Usage: {args[0]}");
|
||||
return;
|
||||
}
|
||||
|
||||
RR.Console.WriteToOutput("Worlds:");
|
||||
RR.WriteToConsole("Worlds:");
|
||||
foreach (WorldConfigAsset worldConfigAsset in RR.WorldConfigsAssets) {
|
||||
RR.Console.WriteToOutput($" {worldConfigAsset.Config.name}");
|
||||
RR.WriteToConsole($" {worldConfigAsset.Config.name}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[RCCMD("connect", "Connects to a server with given Steam ID. Usage: connect <steam_id>")]
|
||||
public static void ConnectToServer(string[] args) {
|
||||
RR.Console.WriteToOutput("Connecting to server...");
|
||||
|
||||
if (args.Length != 1) {
|
||||
RR.Console.WriteToOutput($"Usage: {args[0]} <steam_id>");
|
||||
if (args.Length < 1 || args.Length > 2) {
|
||||
RR.WriteToConsole($"Usage: {args[0]} <steam_id>");
|
||||
return;
|
||||
}
|
||||
|
||||
RR.ConnectToLobby();
|
||||
|
||||
RR.WriteToConsole("Connecting to server...");
|
||||
|
||||
// if (args.Length != 2) {
|
||||
// RR.Console.WriteToOutput($"Usage: {args[0]} <steam_id>");
|
||||
// return;
|
||||
// }
|
||||
|
||||
RR.Connect();
|
||||
}
|
||||
|
||||
|
||||
[RCCMD("disconnect", "Disconnects from the current server")]
|
||||
public static void DisconnectFromServer(string[] args) {
|
||||
if (args.Length != 1) {
|
||||
RR.Console.WriteToOutput($"Usage: {args[0]}");
|
||||
RR.WriteToConsole($"Usage: {args[0]}");
|
||||
return;
|
||||
}
|
||||
|
||||
RR.Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public class JelychoGame : IGame {
|
||||
static readonly Logger s_Logger = new Logger(nameof(JelychoGame));
|
||||
|
||||
[Serializable]
|
||||
public class Config {
|
||||
public string mainMenuWorld = "main_menu";
|
||||
public string mainGameplayWorld = "dev";
|
||||
|
||||
public string standardGameMode = "gm_standard";
|
||||
}
|
||||
|
||||
readonly Config m_Config;
|
||||
|
||||
DisposableBag m_DisposableBag;
|
||||
|
||||
public JelychoGame(Config config) {
|
||||
m_Config = config;
|
||||
m_DisposableBag = new DisposableBag();
|
||||
}
|
||||
|
||||
public async UniTask InitAsync(CancellationToken cancellationToken) {
|
||||
Screen.SetResolution(1600, 900, FullScreenMode.Windowed);
|
||||
|
||||
RR.CreateService<CrosshairService>();
|
||||
await UniTask.Yield();
|
||||
}
|
||||
|
||||
public void Run() {
|
||||
RR.GameModes.ActiveGameMode.Subscribe(gameMode => {
|
||||
if (gameMode == null) {
|
||||
RR.Console.WriteToOutput("Game mode stopped, loading main menu world");
|
||||
|
||||
WorldConfigAsset mainMenuWorldConfigAsset = RR.GetWorldConfigAsset(m_Config.mainMenuWorld);
|
||||
RR.World.LoadAsync(mainMenuWorldConfigAsset.Config, CancellationToken.None).Forget();
|
||||
}
|
||||
})
|
||||
.AddTo(ref m_DisposableBag);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
m_DisposableBag.Dispose();
|
||||
}
|
||||
|
||||
public void NewGame() {
|
||||
NewGameAsync().Forget();
|
||||
}
|
||||
|
||||
async UniTask NewGameAsync() {
|
||||
RR.HostServer();
|
||||
await UniTask.WaitForSeconds(0.5f);
|
||||
|
||||
s_Logger.Info("Starting new game");
|
||||
s_Logger.Info($"Is connected: {RR.IsClient()}");
|
||||
s_Logger.Info($"Is host: {RR.IsHost()}");
|
||||
|
||||
GameModeAsset gameModeAsset = RR.GetGameMode(m_Config.standardGameMode);
|
||||
WorldConfigAsset worldConfigAsset = RR.GetWorldConfigAsset(m_Config.mainGameplayWorld);
|
||||
RR.StartGameMode(gameModeAsset, worldConfigAsset.Config);
|
||||
[RCCMD("version", "Displays the current game version")]
|
||||
public static void GameVersion(string[] args) {
|
||||
RR.WriteToConsole($"Game version: {Application.version}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using RebootKit.Engine.Main;
|
||||
using TriInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RebootReality.jelycho.Main {
|
||||
[CreateAssetMenu(menuName = GameConsts.k_AddComponentMenu + "Jelycho Game")]
|
||||
public class JelychoGameAsset : GameAsset {
|
||||
[SerializeField, InlineProperty, HideLabel] JelychoGame.Config m_Config;
|
||||
|
||||
public override IGame CreateGame() {
|
||||
return new JelychoGame(m_Config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6db6aab28280422f92077cf32c409b0f
|
||||
timeCreated: 1746666083
|
||||
@@ -22,7 +22,8 @@ namespace RebootReality.jelycho.MainMenu {
|
||||
Button quitButton = root.Q<Button>(k_ButtonQuit);
|
||||
|
||||
playButton.RegisterCallback<ClickEvent>(e => {
|
||||
RR.Game<JelychoGame>().NewGame();
|
||||
RR.StartHost();
|
||||
RR.SetServerWorld("dev");
|
||||
});
|
||||
settingsButton.RegisterCallback<ClickEvent>(e => OpenSettings());
|
||||
quitButton.RegisterCallback<ClickEvent>(e => Application.Quit());
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using RebootKit.Engine.Foundation;
|
||||
using System;
|
||||
using RebootKit.Engine.Foundation;
|
||||
using RebootKit.Engine.Services.Simulation.Sensors;
|
||||
using RebootReality.jelycho.Main;
|
||||
using Unity.Cinemachine;
|
||||
@@ -32,8 +33,8 @@ namespace RebootReality.jelycho.Player {
|
||||
|
||||
readonly RaycastSensor m_RaycastSensor = new RaycastSensor();
|
||||
|
||||
public float Pitch { get; private set; }
|
||||
public float Yaw { get; private set; }
|
||||
[NonSerialized] public float Pitch;
|
||||
[NonSerialized] public float Yaw;
|
||||
|
||||
public ISensor Sensor {
|
||||
get {
|
||||
|
||||
@@ -1,19 +1,39 @@
|
||||
using RebootKit.Engine.Extensions;
|
||||
using RebootKit.Engine.Foundation;
|
||||
using RebootKit.Engine.Main;
|
||||
using RebootKit.Engine.Services.Simulation;
|
||||
using RebootKit.Engine.Simulation;
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using Logger = RebootKit.Engine.Foundation.Logger;
|
||||
|
||||
namespace RebootReality.jelycho.Player {
|
||||
public class PlayerActor : Actor {
|
||||
public struct PlayerActorState : INetworkSerializable {
|
||||
public Vector3 Position;
|
||||
public Vector3 Velocity;
|
||||
public float LookPitch;
|
||||
public float LookYaw;
|
||||
public bool IsGrounded;
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter {
|
||||
serializer.SerializeValue(ref Position);
|
||||
serializer.SerializeValue(ref Velocity);
|
||||
serializer.SerializeValue(ref LookPitch);
|
||||
serializer.SerializeValue(ref LookYaw);
|
||||
serializer.SerializeValue(ref IsGrounded);
|
||||
}
|
||||
}
|
||||
|
||||
public class PlayerActor : NetworkBehaviour {
|
||||
static readonly Logger s_Logger = new Logger(nameof(PlayerActor));
|
||||
|
||||
|
||||
[SerializeField] Animator m_Animator;
|
||||
|
||||
|
||||
[Header("Movement")]
|
||||
[SerializeField] PlayerFPPLocomotion m_Locomotion;
|
||||
|
||||
|
||||
[Header("Camera")]
|
||||
[SerializeField] FPPCamera m_Camera;
|
||||
[SerializeField] CameraSpring m_CameraSpring;
|
||||
@@ -25,29 +45,32 @@ namespace RebootReality.jelycho.Player {
|
||||
[SerializeField] float m_RunCameraBobbing = 0.5f;
|
||||
[SerializeField] float m_IdleCameraBobbing = 0.0f;
|
||||
[SerializeField] float m_CameraBobbingTransitionSpeed = 5.0f;
|
||||
|
||||
|
||||
float m_TargetCameraBobbing = 0.0f;
|
||||
float m_CurrentCameraBobbing = 0.0f;
|
||||
|
||||
|
||||
[Header("Character")]
|
||||
[SerializeField] Transform m_CharacterRootTransform;
|
||||
[SerializeField] Transform m_HeadBoneTransform;
|
||||
[SerializeField] Transform m_HeadAimTargetTransform;
|
||||
[SerializeField] Transform m_CharacterForwardTransform;
|
||||
|
||||
|
||||
[SerializeField, Range(0.0f, 90.0f)] float m_CharacterRotateDeadAngle = 5.0f;
|
||||
[SerializeField, Range(0.0f, 90.0f)] float m_CharacterRotateSoftAngle = 90.0f;
|
||||
|
||||
|
||||
[SerializeField] float m_CharacterRotateSpeed = 180.0f;
|
||||
[SerializeField] float m_CharacterRotateFastSpeed = 720.0f;
|
||||
|
||||
|
||||
float m_CharacterTurnVelocity = 0.0f;
|
||||
|
||||
[Header("Dragging")]
|
||||
[SerializeField] Transform m_DragGutStartPosition;
|
||||
[SerializeField] PhysicsObjectDragger m_PhysicsDragger;
|
||||
[SerializeField] FloatRange m_DragDistanceRange = new FloatRange(1.0f, 5.0f);
|
||||
|
||||
|
||||
[Header("Network")]
|
||||
[SerializeField] float m_MinTeleportDistance = 0.5f;
|
||||
|
||||
public float3 LookDirection {
|
||||
get {
|
||||
float pitchRad = math.radians(-m_Camera.Pitch);
|
||||
@@ -58,11 +81,114 @@ namespace RebootReality.jelycho.Player {
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnBeginPlay() {
|
||||
void Start() {
|
||||
m_CameraSpring.Initialize();
|
||||
}
|
||||
|
||||
public override void OnTick(float deltaTime) {
|
||||
public override void OnNetworkSpawn() {
|
||||
base.OnNetworkSpawn();
|
||||
|
||||
if (IsOwner) {
|
||||
SetupAsLocalPlayer();
|
||||
|
||||
RR.ClientTick += OnClientTick;
|
||||
} else {
|
||||
SetupAsRemotePlayer();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn() {
|
||||
base.OnNetworkDespawn();
|
||||
|
||||
if (IsOwner) {
|
||||
RR.ClientTick -= OnClientTick;
|
||||
}
|
||||
}
|
||||
|
||||
void SetupAsLocalPlayer() {
|
||||
m_Camera.enabled = true;
|
||||
m_Camera.Camera.enabled = true;
|
||||
m_Locomotion.enabled = true;
|
||||
|
||||
if (TryGetComponent(out Rigidbody rbody)) {
|
||||
rbody.isKinematic = false;
|
||||
}
|
||||
}
|
||||
|
||||
void SetupAsRemotePlayer() {
|
||||
m_Camera.enabled = false;
|
||||
m_Camera.Camera.enabled = false;
|
||||
m_Locomotion.enabled = false;
|
||||
|
||||
if (TryGetComponent(out Rigidbody rbody)) {
|
||||
rbody.isKinematic = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnGainedOwnership() {
|
||||
base.OnGainedOwnership();
|
||||
SetupAsLocalPlayer();
|
||||
}
|
||||
|
||||
public override void OnLostOwnership() {
|
||||
base.OnLostOwnership();
|
||||
SetupAsRemotePlayer();
|
||||
}
|
||||
|
||||
void Update() {
|
||||
if (!IsSpawned) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsOwner) {
|
||||
OwnerTick();
|
||||
} else {
|
||||
RemoteTick();
|
||||
}
|
||||
|
||||
// Character rotation
|
||||
float3 targetCharacterForward = math.normalize(LookDirection.With(y: 0.0f));
|
||||
float3 currentCharacterForward = math.normalize(m_CharacterForwardTransform.forward.With(y: 0.0f));
|
||||
|
||||
float angleRad =
|
||||
math.acos(math.clamp(math.dot(targetCharacterForward, currentCharacterForward) / (math.length(targetCharacterForward) * math.length(currentCharacterForward)),
|
||||
-1f, 1f));
|
||||
float angleDeg = math.degrees(angleRad);
|
||||
|
||||
bool rotateCharacter = false;
|
||||
float rotateCharacterSpeed = m_CharacterRotateSpeed;
|
||||
m_CharacterTurnVelocity = 0.0f;
|
||||
|
||||
if (math.abs(angleDeg) > m_CharacterRotateDeadAngle) {
|
||||
if (math.abs(angleDeg) < m_CharacterRotateSoftAngle) {
|
||||
rotateCharacter = true;
|
||||
} else {
|
||||
rotateCharacter = true;
|
||||
rotateCharacterSpeed = m_CharacterRotateFastSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
float velocityForward = m_Locomotion.Velocity.z;
|
||||
|
||||
if (!rotateCharacter && math.abs(velocityForward) > 0.01f) {
|
||||
rotateCharacter = true;
|
||||
}
|
||||
|
||||
if (rotateCharacter) {
|
||||
m_CharacterTurnVelocity = rotateCharacterSpeed * Time.deltaTime;
|
||||
|
||||
Vector3 newForward = Vector3.RotateTowards(currentCharacterForward,
|
||||
targetCharacterForward,
|
||||
math.radians(m_CharacterTurnVelocity),
|
||||
0.0f);
|
||||
m_CharacterForwardTransform.forward = newForward;
|
||||
}
|
||||
|
||||
// Aim Target adjustment
|
||||
m_HeadAimTargetTransform.position = (float3) m_HeadBoneTransform.position + LookDirection * 5.0f;
|
||||
}
|
||||
|
||||
void OwnerTick() {
|
||||
// Camera Stuff
|
||||
m_Camera.Tick();
|
||||
|
||||
@@ -76,63 +202,76 @@ namespace RebootReality.jelycho.Player {
|
||||
} else {
|
||||
m_TargetCameraBobbing = m_IdleCameraBobbing;
|
||||
}
|
||||
|
||||
|
||||
m_CurrentCameraBobbing = Mathf.MoveTowards(m_CurrentCameraBobbing,
|
||||
m_TargetCameraBobbing,
|
||||
m_CameraBobbingTransitionSpeed * Time.deltaTime);
|
||||
m_Camera.SetBobbing(m_CurrentCameraBobbing);
|
||||
|
||||
Vector3 actorUp = transform.up;
|
||||
Vector3 actorRight = transform.right;
|
||||
Vector3 actorForward = transform.forward;
|
||||
m_CameraSpring.UpdateSpring(Time.deltaTime, actorUp, actorRight, actorForward);
|
||||
|
||||
// Character rotation
|
||||
float3 targetCharacterForward = math.normalize(LookDirection.With(y: 0.0f));
|
||||
float3 currentCharacterForward = math.normalize(m_CharacterForwardTransform.forward.With(y: 0.0f));
|
||||
|
||||
float angleRad = math.acos(math.clamp(math.dot(targetCharacterForward, currentCharacterForward) / (math.length(targetCharacterForward) * math.length(currentCharacterForward)), -1f, 1f));
|
||||
float angleDeg = math.degrees(angleRad);
|
||||
m_CameraSpring.UpdateSpring(Time.deltaTime,
|
||||
m_CharacterForwardTransform.up,
|
||||
m_CharacterForwardTransform.right,
|
||||
m_CharacterForwardTransform.forward);
|
||||
|
||||
bool rotateCharacter = false;
|
||||
float rotateCharacterSpeed = m_CharacterRotateSpeed;
|
||||
m_CharacterTurnVelocity = 0.0f;
|
||||
|
||||
if (math.abs(angleDeg) > m_CharacterRotateDeadAngle) {
|
||||
if (math.abs(angleDeg) < m_CharacterRotateSoftAngle) {
|
||||
rotateCharacter = true;
|
||||
} else {
|
||||
rotateCharacter = true;
|
||||
rotateCharacterSpeed = m_CharacterRotateFastSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
float velocityForward = m_Locomotion.Velocity.z;
|
||||
|
||||
if (!rotateCharacter && math.abs(velocityForward) > 0.01f) {
|
||||
rotateCharacter = true;
|
||||
}
|
||||
|
||||
if (rotateCharacter) {
|
||||
m_CharacterTurnVelocity = rotateCharacterSpeed * deltaTime;
|
||||
|
||||
Vector3 newForward = Vector3.RotateTowards(currentCharacterForward,
|
||||
targetCharacterForward,
|
||||
math.radians(m_CharacterTurnVelocity),
|
||||
0.0f);
|
||||
m_CharacterForwardTransform.forward = newForward;
|
||||
}
|
||||
|
||||
// Aim Target adjustment
|
||||
m_HeadAimTargetTransform.position = (float3)m_HeadBoneTransform.position + LookDirection * 5.0f;
|
||||
|
||||
UpdateAnimator();
|
||||
UpdateAnimator(m_Locomotion.Velocity);
|
||||
}
|
||||
|
||||
public void WarpTo(float3 position) {
|
||||
m_Locomotion.WarpTo(position);
|
||||
|
||||
void RemoteTick() {
|
||||
Vector3 targetPosition = m_NetworkState.Position;
|
||||
|
||||
if ((transform.position - m_NetworkState.Position).sqrMagnitude <
|
||||
m_MinTeleportDistance * m_MinTeleportDistance) {
|
||||
targetPosition = Vector3.MoveTowards(transform.position,
|
||||
m_NetworkState.Position,
|
||||
m_Locomotion.runSpeed * Time.deltaTime);
|
||||
}
|
||||
|
||||
m_Locomotion.WarpTo(targetPosition);
|
||||
|
||||
m_Camera.Pitch = m_NetworkState.LookPitch;
|
||||
m_Camera.Yaw = m_NetworkState.LookYaw;
|
||||
|
||||
UpdateAnimator(m_NetworkState.Velocity);
|
||||
}
|
||||
|
||||
|
||||
void OnClientTick() {
|
||||
PlayerActorState state = new PlayerActorState {
|
||||
Position = transform.position,
|
||||
LookPitch = m_Camera.Pitch,
|
||||
LookYaw = m_Camera.Yaw,
|
||||
IsGrounded = m_Locomotion.IsGrounded,
|
||||
Velocity = m_Locomotion.Velocity
|
||||
};
|
||||
|
||||
UpdatePlayerStateRpc(state);
|
||||
}
|
||||
|
||||
PlayerActorState m_NetworkState;
|
||||
|
||||
[Rpc(SendTo.NotMe)]
|
||||
void UpdatePlayerStateRpc(PlayerActorState state) {
|
||||
if (IsOwner) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_NetworkState = state;
|
||||
}
|
||||
|
||||
[ServerRpc(RequireOwnership = false)]
|
||||
public void WarpToServerRpc(Vector3 position) {
|
||||
WarpToClientRpc(position);
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
void WarpToClientRpc(Vector3 position) {
|
||||
if (IsOwner) {
|
||||
m_Locomotion.WarpTo(position);
|
||||
} else {
|
||||
transform.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSprint(bool isSprinting) {
|
||||
m_Locomotion.SetSprint(isSprinting);
|
||||
}
|
||||
@@ -144,7 +283,7 @@ namespace RebootReality.jelycho.Player {
|
||||
public void Look(Vector2 input) {
|
||||
m_Camera.Rotate(input.x, input.y);
|
||||
}
|
||||
|
||||
|
||||
public void SetMoveInput(Vector2 input) {
|
||||
float3 direction = Quaternion.AngleAxis(m_Camera.Yaw, Vector3.up) *
|
||||
new float3(input.x, 0.0f, input.y);
|
||||
@@ -155,7 +294,7 @@ namespace RebootReality.jelycho.Player {
|
||||
GameObject pickedGameObject = m_Camera.Sensor.Sense();
|
||||
if (pickedGameObject != null && pickedGameObject.TryGetComponent(out Rigidbody rigidbody)) {
|
||||
m_PhysicsDragger.Grab(rigidbody);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void StopDrag() {
|
||||
@@ -165,7 +304,7 @@ namespace RebootReality.jelycho.Player {
|
||||
public void PrimaryAction() {
|
||||
m_Animator.SetTrigger(AnimatorParamHashes.Attack);
|
||||
}
|
||||
|
||||
|
||||
public void SecondaryAction() {
|
||||
m_Animator.SetTrigger(AnimatorParamHashes.Block);
|
||||
}
|
||||
@@ -179,9 +318,9 @@ namespace RebootReality.jelycho.Player {
|
||||
public static readonly int Attack = Animator.StringToHash("Attack");
|
||||
public static readonly int Block = Animator.StringToHash("Block");
|
||||
}
|
||||
|
||||
void UpdateAnimator() {
|
||||
Vector3 localVelocity = m_CharacterForwardTransform.InverseTransformDirection(m_Locomotion.Velocity);
|
||||
|
||||
void UpdateAnimator(Vector3 velocity) {
|
||||
Vector3 localVelocity = m_CharacterForwardTransform.InverseTransformDirection(velocity);
|
||||
float forwardNormalized = localVelocity.z / m_Locomotion.runSpeed;
|
||||
float rightNormalized = localVelocity.x / m_Locomotion.runSpeed;
|
||||
|
||||
@@ -195,7 +334,7 @@ namespace RebootReality.jelycho.Player {
|
||||
m_Animator.SetFloat(AnimatorParamHashes.VelocityForwardNormalized, forwardNormalized);
|
||||
m_Animator.SetFloat(AnimatorParamHashes.VelocityRightNormalized, rightNormalized);
|
||||
m_Animator.SetFloat(AnimatorParamHashes.TurnVelocity, turnVelocity);
|
||||
|
||||
|
||||
m_Animator.SetBool(AnimatorParamHashes.IsGrounded, m_Locomotion.IsGrounded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,89 +1,106 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using RebootKit.Engine.Foundation;
|
||||
using RebootKit.Engine.Main;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using UnityEngine.InputSystem;
|
||||
using Logger = RebootKit.Engine.Foundation.Logger;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace RebootReality.jelycho.Player {
|
||||
public class PlayerController : IController {
|
||||
readonly Logger m_Logger = new Logger(nameof(PlayerController));
|
||||
readonly Config m_Config;
|
||||
public class PlayerController : NetworkPlayerController {
|
||||
static readonly Logger s_Logger = new Logger(nameof(PlayerController));
|
||||
|
||||
PlayerActor m_FPPActor;
|
||||
[SerializeField] Config m_Config;
|
||||
|
||||
public PlayerController(Config config) {
|
||||
m_Config = config;
|
||||
PlayerActor m_Actor;
|
||||
|
||||
public override void OnNetworkSpawn() {
|
||||
base.OnNetworkSpawn();
|
||||
|
||||
if (IsOwner) {
|
||||
RR.Input.LockCursor();
|
||||
RR.Input.EnableControls();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
public override void OnNetworkDespawn() {
|
||||
base.OnNetworkDespawn();
|
||||
|
||||
if (IsOwner) {
|
||||
RR.Input.UnlockCursor();
|
||||
RR.Input.DisableControls();
|
||||
}
|
||||
}
|
||||
|
||||
public async UniTask OnStart(CancellationToken cancellationToken) {
|
||||
RR.Input.LockCursor();
|
||||
RR.Input.EnableControls();
|
||||
|
||||
m_FPPActor = await RR.World.SpawnActor<PlayerActor>(m_Config.playerActorPrefab, cancellationToken);
|
||||
m_FPPActor.IsPlaying = true;
|
||||
|
||||
if (RR.World.Context is WorldContext worldContext) {
|
||||
m_FPPActor.WarpTo(worldContext.PlayerSpawnPoint.position);
|
||||
[ClientRpc]
|
||||
public void SetActorClientRpc(ulong networkObjectID) {
|
||||
if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectID,
|
||||
out NetworkObject networkObject)) {
|
||||
m_Actor = networkObject.GetComponent<PlayerActor>();
|
||||
s_Logger.Info($"Found Player Actor NetworkObject ID: {networkObjectID}");
|
||||
} else {
|
||||
s_Logger.Error($"Failed to find PlayerActor with NetworkObject ID: {networkObjectID}");
|
||||
m_Actor = null;
|
||||
}
|
||||
|
||||
await UniTask.Yield(cancellationToken);
|
||||
if (m_Actor is null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsServer) {
|
||||
if (RR.World.Context is WorldContext worldContext) {
|
||||
m_Actor.WarpToServerRpc(worldContext.PlayerSpawnPoint.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnStop() {
|
||||
RR.Input.DisableControls();
|
||||
RR.Input.UnlockCursor();
|
||||
|
||||
RR.World.KillActor(m_FPPActor);
|
||||
[ClientRpc]
|
||||
public void SetNullActorClientRpc() {
|
||||
m_Actor = null;
|
||||
}
|
||||
|
||||
public void OnTick() {
|
||||
if (m_FPPActor is null) {
|
||||
m_Logger.Error("Player actor is not initialized. Make sure to call OnStart() before OnTick().");
|
||||
void Update() {
|
||||
if (m_Actor is null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsOwner) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 lookInput = SensitivityReader.Read(m_Config.lookActionReference.action);
|
||||
m_FPPActor.Look(lookInput * FPPConfig.MouseSensitivity.FloatValue);
|
||||
m_Actor.Look(lookInput * FPPConfig.MouseSensitivity.FloatValue);
|
||||
|
||||
Vector2 moveInput = m_Config.moveActionReference.action.ReadValue<Vector2>();
|
||||
m_FPPActor.SetMoveInput(moveInput);
|
||||
m_Actor.SetMoveInput(moveInput);
|
||||
|
||||
m_FPPActor.SetSprint(m_Config.sprintActionReference.action.IsPressed());
|
||||
m_Actor.SetSprint(m_Config.sprintActionReference.action.IsPressed());
|
||||
|
||||
if (m_Config.jumpActionReference.action.WasPerformedThisFrame()) {
|
||||
m_FPPActor.Jump();
|
||||
m_Actor.Jump();
|
||||
}
|
||||
|
||||
if (m_Config.dragObjectActionReference.action.WasPressedThisFrame()) {
|
||||
m_FPPActor.StartDrag();
|
||||
m_Actor.StartDrag();
|
||||
}
|
||||
|
||||
if (m_Config.dragObjectActionReference.action.WasReleasedThisFrame()) {
|
||||
m_FPPActor.StopDrag();
|
||||
m_Actor.StopDrag();
|
||||
}
|
||||
|
||||
if (m_Config.primaryActionReference.action.WasReleasedThisFrame()) {
|
||||
m_FPPActor.PrimaryAction();
|
||||
m_Actor.PrimaryAction();
|
||||
}
|
||||
|
||||
if (m_Config.secondaryActionReference.action.WasReleasedThisFrame()) {
|
||||
m_FPPActor.SecondaryAction();
|
||||
m_Actor.SecondaryAction();
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Config {
|
||||
public AssetReferenceT<GameObject> playerActorPrefab;
|
||||
|
||||
public InputActionReference moveActionReference;
|
||||
public InputActionReference lookActionReference;
|
||||
public InputActionReference jumpActionReference;
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
using RebootKit.Engine.Foundation;
|
||||
using RebootReality.jelycho.Main;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RebootReality.jelycho.Player {
|
||||
[CreateAssetMenu(menuName = GameConsts.k_CreateAssetMenu + "Player/Player Controller")]
|
||||
public class PlayerControllerAsset : ControllerAsset {
|
||||
[SerializeField] PlayerController.Config m_Config;
|
||||
|
||||
public override IController Create() {
|
||||
PlayerController controller = new PlayerController(m_Config);
|
||||
return controller;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a96eb98c0051478f95c5ee18399ec93f
|
||||
timeCreated: 1744662807
|
||||
@@ -1,4 +1,5 @@
|
||||
using RebootKit.Engine.Services.Simulation;
|
||||
using RebootKit.Engine.Simulation;
|
||||
using RebootReality.jelycho.Ropes;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user