feat: Updated example controllers

RuntimeData struct for inspector folding
This commit is contained in:
MrGadget 2024-09-30 03:49:43 -04:00
parent 53d8812281
commit 70351d96f4
6 changed files with 573 additions and 359 deletions

View File

@ -22,7 +22,7 @@ public struct OptionsKeys
public KeyCode ToggleUI; public KeyCode ToggleUI;
} }
public enum GroundState : byte { Jumping, Falling, Grounded } public enum GroundState : byte { Grounded, Jumping, Falling }
[Serializable] [Serializable]
public struct MoveKeys public struct MoveKeys
@ -145,46 +145,123 @@ public enum ControlOptions : byte
[Tooltip("Roll acceleration in degrees per second squared")] [Tooltip("Roll acceleration in degrees per second squared")]
public float rollAcceleration = 3f; public float rollAcceleration = 3f;
// Runtime data in a struct so it can be folded up in inspector
[Serializable]
public struct RuntimeData
{
[ReadOnly, SerializeField, Range(-1f, 1f)] float _horizontal;
[ReadOnly, SerializeField, Range(-1f, 1f)] float _vertical;
[ReadOnly, SerializeField, Range(-300f, 300f)] float _turnSpeed;
[ReadOnly, SerializeField, Range(-180f, 180f)] float _pitchAngle;
[ReadOnly, SerializeField, Range(-180f, 180f)] float _pitchSpeed;
[ReadOnly, SerializeField, Range(-180f, 180f)] float _rollAngle;
[ReadOnly, SerializeField, Range(-180f, 180f)] float _rollSpeed;
[ReadOnly, SerializeField, Range(-1.5f, 1.5f)] float _animVelocity;
[ReadOnly, SerializeField, Range(-1.5f, 1.5f)] float _animRotation;
[ReadOnly, SerializeField, Range(-1f, 1f)] float _mouseInputX;
[ReadOnly, SerializeField, Range(0, 30f)] float _mouseSensitivity;
[ReadOnly, SerializeField] GroundState _groundState;
[ReadOnly, SerializeField] Vector3 _direction;
[ReadOnly, SerializeField] Vector3Int _velocity;
[ReadOnly, SerializeField] GameObject _controllerUI;
#region Properties
public float horizontal
{
get => _horizontal;
internal set => _horizontal = value;
}
public float vertical
{
get => _vertical;
internal set => _vertical = value;
}
public float turnSpeed
{
get => _turnSpeed;
internal set => _turnSpeed = value;
}
public float pitchAngle
{
get => _pitchAngle;
internal set => _pitchAngle = value;
}
public float pitchSpeed
{
get => _pitchSpeed;
internal set => _pitchSpeed = value;
}
public float rollAngle
{
get => _rollAngle;
internal set => _rollAngle = value;
}
public float rollSpeed
{
get => _rollSpeed;
internal set => _rollSpeed = value;
}
public float animVelocity
{
get => _animVelocity;
internal set => _animVelocity = value;
}
public float animRotation
{
get => _animRotation;
internal set => _animRotation = value;
}
public float mouseInputX
{
get => _mouseInputX;
internal set => _mouseInputX = value;
}
public float mouseSensitivity
{
get => _mouseSensitivity;
internal set => _mouseSensitivity = value;
}
public GroundState groundState
{
get => _groundState;
internal set => _groundState = value;
}
public Vector3 direction
{
get => _direction;
internal set => _direction = value;
}
public Vector3Int velocity
{
get => _velocity;
internal set => _velocity = value;
}
public GameObject controllerUI
{
get => _controllerUI;
internal set => _controllerUI = value;
}
#endregion
}
[Header("Diagnostics")] [Header("Diagnostics")]
[ReadOnly, SerializeField] public RuntimeData runtimeData;
GroundState groundState = GroundState.Grounded;
[ReadOnly, SerializeField, Range(-1f, 1f)]
float horizontal;
[ReadOnly, SerializeField, Range(-1f, 1f)]
float vertical;
[ReadOnly, SerializeField, Range(-1f, 1f)]
float mouseInputX;
[ReadOnly, SerializeField, Range(0, 30f)]
float mouseSensitivity;
[ReadOnly, SerializeField, Range(-300f, 300f)]
float turnSpeed;
[ReadOnly, SerializeField, Range(-180f, 180f)]
float pitchAngle;
[ReadOnly, SerializeField, Range(-180f, 180f)]
float pitchSpeed;
[ReadOnly, SerializeField, Range(-180f, 180f)]
float rollAngle;
[ReadOnly, SerializeField, Range(-180f, 180f)]
float rollSpeed;
[ReadOnly, SerializeField, Range(-1.5f, 1.5f)]
float animVelocity;
[ReadOnly, SerializeField, Range(-1.5f, 1.5f)]
float animRotation;
[ReadOnly, SerializeField]
Vector3 direction;
[ReadOnly, SerializeField]
Vector3Int velocity;
[ReadOnly, SerializeField]
GameObject controllerUI;
#region Network Setup #region Network Setup
@ -235,8 +312,7 @@ public override void OnStartAuthority()
{ {
// Calculate DPI-aware sensitivity // Calculate DPI-aware sensitivity
float dpiScale = (Screen.dpi > 0) ? (Screen.dpi / BASE_DPI) : 1f; float dpiScale = (Screen.dpi > 0) ? (Screen.dpi / BASE_DPI) : 1f;
mouseSensitivity = turnAcceleration * dpiScale; runtimeData.mouseSensitivity = turnAcceleration * dpiScale;
//Debug.Log($"Screen DPI: {Screen.dpi}, DPI Scale: {dpiScale}, Adjusted Turn Acceleration: {turnAccelerationDPI}");
SetCursor(controlOptions.HasFlag(ControlOptions.MouseSteer)); SetCursor(controlOptions.HasFlag(ControlOptions.MouseSteer));
@ -262,22 +338,22 @@ public override void OnStopAuthority()
public override void OnStartLocalPlayer() public override void OnStartLocalPlayer()
{ {
if (ControllerUIPrefab != null) if (ControllerUIPrefab != null)
controllerUI = Instantiate(ControllerUIPrefab); runtimeData.controllerUI = Instantiate(ControllerUIPrefab);
if (controllerUI != null) if (runtimeData.controllerUI != null)
{ {
if (controllerUI.TryGetComponent(out FlyerControllerUI canvasControlPanel)) if (runtimeData.controllerUI.TryGetComponent(out FlyerControllerUI canvasControlPanel))
canvasControlPanel.Refresh(moveKeys, flightKeys, optionsKeys); canvasControlPanel.Refresh(moveKeys, flightKeys, optionsKeys);
controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI)); runtimeData.controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
} }
} }
public override void OnStopLocalPlayer() public override void OnStopLocalPlayer()
{ {
if (controllerUI != null) if (runtimeData.controllerUI != null)
Destroy(controllerUI); Destroy(runtimeData.controllerUI);
controllerUI = null; runtimeData.controllerUI = null;
} }
#endregion #endregion
@ -306,12 +382,12 @@ void Update()
// Reset ground state // Reset ground state
if (characterController.isGrounded) if (characterController.isGrounded)
groundState = GroundState.Grounded; runtimeData.groundState = GroundState.Grounded;
else if (groundState != GroundState.Jumping) else if (runtimeData.groundState != GroundState.Jumping)
groundState = GroundState.Falling; runtimeData.groundState = GroundState.Falling;
// Diagnostic velocity...FloorToInt for display purposes // Diagnostic velocity...FloorToInt for display purposes
velocity = Vector3Int.FloorToInt(characterController.velocity); runtimeData.velocity = Vector3Int.FloorToInt(characterController.velocity);
} }
void SetCursor(bool locked) void SetCursor(bool locked)
@ -335,8 +411,8 @@ void HandleOptions()
{ {
controlOptions ^= ControlOptions.ShowUI; controlOptions ^= ControlOptions.ShowUI;
if (controllerUI != null) if (runtimeData.controllerUI != null)
controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI)); runtimeData.controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
} }
if (flightKeys.AutoLevel != KeyCode.None && Input.GetKeyUp(flightKeys.AutoLevel)) if (flightKeys.AutoLevel != KeyCode.None && Input.GetKeyUp(flightKeys.AutoLevel))
@ -354,28 +430,28 @@ void HandleTurning(float deltaTime)
if (moveKeys.TurnRight != KeyCode.None && Input.GetKey(moveKeys.TurnRight)) if (moveKeys.TurnRight != KeyCode.None && Input.GetKey(moveKeys.TurnRight))
targetTurnSpeed += maxTurnSpeed; targetTurnSpeed += maxTurnSpeed;
turnSpeed = Mathf.MoveTowards(turnSpeed, targetTurnSpeed, turnAcceleration * maxTurnSpeed * deltaTime); runtimeData.turnSpeed = Mathf.MoveTowards(runtimeData.turnSpeed, targetTurnSpeed, turnAcceleration * maxTurnSpeed * deltaTime);
transform.Rotate(0f, turnSpeed * deltaTime, 0f); transform.Rotate(0f, runtimeData.turnSpeed * deltaTime, 0f);
} }
void HandleMouseSteer(float deltaTime) void HandleMouseSteer(float deltaTime)
{ {
// Accumulate mouse input over time // Accumulate mouse input over time
mouseInputX += Input.GetAxisRaw("Mouse X") * mouseSensitivity; runtimeData.mouseInputX += Input.GetAxisRaw("Mouse X") * runtimeData.mouseSensitivity;
// Clamp the accumulator to simulate key press behavior // Clamp the accumulator to simulate key press behavior
mouseInputX = Mathf.Clamp(mouseInputX, -1f, 1f); runtimeData.mouseInputX = Mathf.Clamp(runtimeData.mouseInputX, -1f, 1f);
// Calculate target turn speed // Calculate target turn speed
float targetTurnSpeed = mouseInputX * maxTurnSpeed; float targetTurnSpeed = runtimeData.mouseInputX * maxTurnSpeed;
// Use the same acceleration logic as HandleTurning // Use the same acceleration logic as HandleTurning
turnSpeed = Mathf.MoveTowards(turnSpeed, targetTurnSpeed, mouseSensitivity * maxTurnSpeed * deltaTime); runtimeData.turnSpeed = Mathf.MoveTowards(runtimeData.turnSpeed, targetTurnSpeed, runtimeData.mouseSensitivity * maxTurnSpeed * deltaTime);
// Apply rotation // Apply rotation
transform.Rotate(0f, turnSpeed * deltaTime, 0f); transform.Rotate(0f, runtimeData.turnSpeed * deltaTime, 0f);
mouseInputX = Mathf.MoveTowards(mouseInputX, 0f, mouseSensitivity * deltaTime); runtimeData.mouseInputX = Mathf.MoveTowards(runtimeData.mouseInputX, 0f, runtimeData.mouseSensitivity * deltaTime);
} }
void HandlePitch(float deltaTime) void HandlePitch(float deltaTime)
@ -396,15 +472,15 @@ void HandlePitch(float deltaTime)
inputDetected = true; inputDetected = true;
} }
pitchSpeed = Mathf.MoveTowards(pitchSpeed, targetPitchSpeed, pitchAcceleration * maxPitchSpeed * deltaTime); runtimeData.pitchSpeed = Mathf.MoveTowards(runtimeData.pitchSpeed, targetPitchSpeed, pitchAcceleration * maxPitchSpeed * deltaTime);
// Apply pitch rotation // Apply pitch rotation
pitchAngle += pitchSpeed * deltaTime; runtimeData.pitchAngle += runtimeData.pitchSpeed * deltaTime;
pitchAngle = Mathf.Clamp(pitchAngle, -maxPitchUpAngle, maxPitchDownAngle); runtimeData.pitchAngle = Mathf.Clamp(runtimeData.pitchAngle, -maxPitchUpAngle, maxPitchDownAngle);
// Return to zero when no input // Return to zero when no input
if (!inputDetected && controlOptions.HasFlag(ControlOptions.AutoLevel)) if (!inputDetected && controlOptions.HasFlag(ControlOptions.AutoLevel))
pitchAngle = Mathf.MoveTowards(pitchAngle, 0f, maxPitchSpeed * deltaTime); runtimeData.pitchAngle = Mathf.MoveTowards(runtimeData.pitchAngle, 0f, maxPitchSpeed * deltaTime);
ApplyRotation(); ApplyRotation();
} }
@ -427,15 +503,15 @@ void HandleRoll(float deltaTime)
inputDetected = true; inputDetected = true;
} }
rollSpeed = Mathf.MoveTowards(rollSpeed, targetRollSpeed, rollAcceleration * maxRollSpeed * deltaTime); runtimeData.rollSpeed = Mathf.MoveTowards(runtimeData.rollSpeed, targetRollSpeed, rollAcceleration * maxRollSpeed * deltaTime);
// Apply roll rotation // Apply roll rotation
rollAngle += rollSpeed * deltaTime; runtimeData.rollAngle += runtimeData.rollSpeed * deltaTime;
rollAngle = Mathf.Clamp(rollAngle, -maxRollAngle, maxRollAngle); runtimeData.rollAngle = Mathf.Clamp(runtimeData.rollAngle, -maxRollAngle, maxRollAngle);
// Return to zero when no input // Return to zero when no input
if (!inputDetected && controlOptions.HasFlag(ControlOptions.AutoLevel)) if (!inputDetected && controlOptions.HasFlag(ControlOptions.AutoLevel))
rollAngle = Mathf.MoveTowards(rollAngle, 0f, maxRollSpeed * deltaTime); runtimeData.rollAngle = Mathf.MoveTowards(runtimeData.rollAngle, 0f, maxRollSpeed * deltaTime);
ApplyRotation(); ApplyRotation();
} }
@ -446,7 +522,7 @@ void ApplyRotation()
float currentYaw = transform.localRotation.eulerAngles.y; float currentYaw = transform.localRotation.eulerAngles.y;
// Apply all rotations // Apply all rotations
transform.localRotation = Quaternion.Euler(pitchAngle, currentYaw, rollAngle); transform.localRotation = Quaternion.Euler(runtimeData.pitchAngle, currentYaw, runtimeData.rollAngle);
} }
void HandleMove(float deltaTime) void HandleMove(float deltaTime)
@ -464,39 +540,36 @@ void HandleMove(float deltaTime)
if (targetMoveX == 0f) if (targetMoveX == 0f)
{ {
if (!controlOptions.HasFlag(ControlOptions.AutoRun)) if (!controlOptions.HasFlag(ControlOptions.AutoRun))
horizontal = Mathf.MoveTowards(horizontal, targetMoveX, inputGravity * deltaTime); runtimeData.horizontal = Mathf.MoveTowards(runtimeData.horizontal, targetMoveX, inputGravity * deltaTime);
} }
else else
horizontal = Mathf.MoveTowards(horizontal, targetMoveX, inputSensitivity * deltaTime); runtimeData.horizontal = Mathf.MoveTowards(runtimeData.horizontal, targetMoveX, inputSensitivity * deltaTime);
if (targetMoveZ == 0f) if (targetMoveZ == 0f)
{ {
if (!controlOptions.HasFlag(ControlOptions.AutoRun)) if (!controlOptions.HasFlag(ControlOptions.AutoRun))
vertical = Mathf.MoveTowards(vertical, targetMoveZ, inputGravity * deltaTime); runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputGravity * deltaTime);
} }
else else
vertical = Mathf.MoveTowards(vertical, targetMoveZ, inputSensitivity * deltaTime); runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputSensitivity * deltaTime);
} }
void ApplyMove(float deltaTime) void ApplyMove(float deltaTime)
{ {
// Create initial direction vector without jumpSpeed (y-axis). // Create initial direction vector without jumpSpeed (y-axis).
direction = new Vector3(horizontal, 0f, vertical); runtimeData.direction = new Vector3(runtimeData.horizontal, 0f, runtimeData.vertical);
// Clamp so diagonal strafing isn't a speed advantage. // Clamp so diagonal strafing isn't a speed advantage.
direction = Vector3.ClampMagnitude(direction, 1f); runtimeData.direction = Vector3.ClampMagnitude(runtimeData.direction, 1f);
// Transforms direction from local space to world space. // Transforms direction from local space to world space.
direction = transform.TransformDirection(direction); runtimeData.direction = transform.TransformDirection(runtimeData.direction);
// Multiply for desired ground speed. // Multiply for desired ground speed.
direction *= maxMoveSpeed; runtimeData.direction *= maxMoveSpeed;
//// Add jumpSpeed to direction as last step.
//direction.y = jumpSpeed;
// Finally move the character. // Finally move the character.
characterController.Move(direction * deltaTime); characterController.Move(runtimeData.direction * deltaTime);
} }
} }
} }

View File

@ -14,7 +14,7 @@ public class PlayerControllerBase : NetworkBehaviour
{ {
const float BASE_DPI = 96f; const float BASE_DPI = 96f;
public enum GroundState : byte { Jumping, Falling, Grounded } public enum GroundState : byte { Grounded, Jumping, Falling }
[Serializable] [Serializable]
public struct MoveKeys public struct MoveKeys
@ -112,39 +112,102 @@ public enum ControlOptions : byte
[Tooltip("Jump acceleration in meters per second squared")] [Tooltip("Jump acceleration in meters per second squared")]
public float jumpAcceleration = 4f; public float jumpAcceleration = 4f;
// Runtime data in a struct so it can be folded up in inspector
[Serializable]
public struct RuntimeData
{
[ReadOnly, SerializeField, Range(-1f, 1f)] float _horizontal;
[ReadOnly, SerializeField, Range(-1f, 1f)] float _vertical;
[ReadOnly, SerializeField, Range(-300f, 300f)] float _turnSpeed;
[ReadOnly, SerializeField, Range(-10f, 10f)] float _jumpSpeed;
[ReadOnly, SerializeField, Range(-1.5f, 1.5f)] float _animVelocity;
[ReadOnly, SerializeField, Range(-1.5f, 1.5f)] float _animRotation;
[ReadOnly, SerializeField, Range(-1f, 1f)] float _mouseInputX;
[ReadOnly, SerializeField, Range(0, 30f)] float _mouseSensitivity;
[ReadOnly, SerializeField] GroundState _groundState;
[ReadOnly, SerializeField] Vector3 _direction;
[ReadOnly, SerializeField] Vector3Int _velocity;
[ReadOnly, SerializeField] GameObject _controllerUI;
#region Properties
public float horizontal
{
get => _horizontal;
internal set => _horizontal = value;
}
public float vertical
{
get => _vertical;
internal set => _vertical = value;
}
public float turnSpeed
{
get => _turnSpeed;
internal set => _turnSpeed = value;
}
public float jumpSpeed
{
get => _jumpSpeed;
internal set => _jumpSpeed = value;
}
public float animVelocity
{
get => _animVelocity;
internal set => _animVelocity = value;
}
public float animRotation
{
get => _animRotation;
internal set => _animRotation = value;
}
public float mouseInputX
{
get => _mouseInputX;
internal set => _mouseInputX = value;
}
public float mouseSensitivity
{
get => _mouseSensitivity;
internal set => _mouseSensitivity = value;
}
public GroundState groundState
{
get => _groundState;
internal set => _groundState = value;
}
public Vector3 direction
{
get => _direction;
internal set => _direction = value;
}
public Vector3Int velocity
{
get => _velocity;
internal set => _velocity = value;
}
public GameObject controllerUI
{
get => _controllerUI;
internal set => _controllerUI = value;
}
#endregion
}
[Header("Diagnostics")] [Header("Diagnostics")]
[ReadOnly, SerializeField] public RuntimeData runtimeData;
GroundState groundState = GroundState.Grounded;
[ReadOnly, SerializeField, Range(-1f, 1f)]
float horizontal;
[ReadOnly, SerializeField, Range(-1f, 1f)]
float vertical;
[ReadOnly, SerializeField, Range(-1f, 1f)]
float mouseInputX;
[ReadOnly, SerializeField, Range(0, 30f)]
float mouseSensitivity;
[ReadOnly, SerializeField, Range(-300f, 300f)]
float turnSpeed;
[ReadOnly, SerializeField, Range(-10f, 10f)]
float jumpSpeed;
[ReadOnly, SerializeField, Range(-1.5f, 1.5f)]
float animVelocity;
[ReadOnly, SerializeField, Range(-1.5f, 1.5f)]
float animRotation;
[ReadOnly, SerializeField]
Vector3 direction;
[ReadOnly, SerializeField]
Vector3Int velocity;
[ReadOnly, SerializeField]
GameObject controllerUI;
#region Network Setup #region Network Setup
@ -187,17 +250,16 @@ void Reset()
void OnDisable() void OnDisable()
{ {
horizontal = 0f; runtimeData.horizontal = 0f;
vertical = 0f; runtimeData.vertical = 0f;
turnSpeed = 0f; runtimeData.turnSpeed = 0f;
} }
public override void OnStartAuthority() public override void OnStartAuthority()
{ {
// Calculate DPI-aware sensitivity // Calculate DPI-aware sensitivity
float dpiScale = (Screen.dpi > 0) ? (Screen.dpi / BASE_DPI) : 1f; float dpiScale = (Screen.dpi > 0) ? (Screen.dpi / BASE_DPI) : 1f;
mouseSensitivity = turnAcceleration * dpiScale; runtimeData.mouseSensitivity = turnAcceleration * dpiScale;
//Debug.Log($"Screen DPI: {Screen.dpi}, DPI Scale: {dpiScale}, Adjusted Turn Acceleration: {turnAccelerationDPI}");
SetCursor(controlOptions.HasFlag(ControlOptions.MouseSteer)); SetCursor(controlOptions.HasFlag(ControlOptions.MouseSteer));
@ -215,22 +277,22 @@ public override void OnStopAuthority()
public override void OnStartLocalPlayer() public override void OnStartLocalPlayer()
{ {
if (ControllerUIPrefab != null) if (ControllerUIPrefab != null)
controllerUI = Instantiate(ControllerUIPrefab); runtimeData.controllerUI = Instantiate(ControllerUIPrefab);
if (controllerUI != null) if (runtimeData.controllerUI != null)
{ {
if (controllerUI.TryGetComponent(out PlayerControllerUI canvasControlPanel)) if (runtimeData.controllerUI.TryGetComponent(out PlayerControllerUI canvasControlPanel))
canvasControlPanel.Refresh(moveKeys, optionsKeys); canvasControlPanel.Refresh(moveKeys, optionsKeys);
controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI)); runtimeData.controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
} }
} }
public override void OnStopLocalPlayer() public override void OnStopLocalPlayer()
{ {
if (controllerUI != null) if (runtimeData.controllerUI != null)
Destroy(controllerUI); Destroy(runtimeData.controllerUI);
controllerUI = null; runtimeData.controllerUI = null;
} }
#endregion #endregion
@ -255,12 +317,12 @@ void Update()
// Reset ground state // Reset ground state
if (characterController.isGrounded) if (characterController.isGrounded)
groundState = GroundState.Grounded; runtimeData.groundState = GroundState.Grounded;
else if (groundState != GroundState.Jumping) else if (runtimeData.groundState != GroundState.Jumping)
groundState = GroundState.Falling; runtimeData.groundState = GroundState.Falling;
// Diagnostic velocity...FloorToInt for display purposes // Diagnostic velocity...FloorToInt for display purposes
velocity = Vector3Int.FloorToInt(characterController.velocity); runtimeData.velocity = Vector3Int.FloorToInt(characterController.velocity);
} }
void SetCursor(bool locked) void SetCursor(bool locked)
@ -284,8 +346,8 @@ void HandleOptions()
{ {
controlOptions ^= ControlOptions.ShowUI; controlOptions ^= ControlOptions.ShowUI;
if (controllerUI != null) if (runtimeData.controllerUI != null)
controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI)); runtimeData.controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
} }
} }
@ -303,62 +365,62 @@ void HandleTurning(float deltaTime)
// If there's turn input or AutoRun is not enabled, adjust turn speed towards target // If there's turn input or AutoRun is not enabled, adjust turn speed towards target
// If no turn input and AutoRun is enabled, maintain the previous turn speed // If no turn input and AutoRun is enabled, maintain the previous turn speed
if (targetTurnSpeed != 0f || !controlOptions.HasFlag(ControlOptions.AutoRun)) if (targetTurnSpeed != 0f || !controlOptions.HasFlag(ControlOptions.AutoRun))
turnSpeed = Mathf.MoveTowards(turnSpeed, targetTurnSpeed, turnAcceleration * maxTurnSpeed * deltaTime); runtimeData.turnSpeed = Mathf.MoveTowards(runtimeData.turnSpeed, targetTurnSpeed, turnAcceleration * maxTurnSpeed * deltaTime);
transform.Rotate(0f, turnSpeed * deltaTime, 0f); transform.Rotate(0f, runtimeData.turnSpeed * deltaTime, 0f);
} }
void HandleMouseSteer(float deltaTime) void HandleMouseSteer(float deltaTime)
{ {
// Accumulate mouse input over time // Accumulate mouse input over time
mouseInputX += Input.GetAxisRaw("Mouse X") * mouseSensitivity; runtimeData.mouseInputX += Input.GetAxisRaw("Mouse X") * runtimeData.mouseSensitivity;
// Clamp the accumulator to simulate key press behavior // Clamp the accumulator to simulate key press behavior
mouseInputX = Mathf.Clamp(mouseInputX, -1f, 1f); runtimeData.mouseInputX = Mathf.Clamp(runtimeData.mouseInputX, -1f, 1f);
// Calculate target turn speed // Calculate target turn speed
float targetTurnSpeed = mouseInputX * maxTurnSpeed; float targetTurnSpeed = runtimeData.mouseInputX * maxTurnSpeed;
// Use the same acceleration logic as HandleTurning // Use the same acceleration logic as HandleTurning
turnSpeed = Mathf.MoveTowards(turnSpeed, targetTurnSpeed, mouseSensitivity * maxTurnSpeed * deltaTime); runtimeData.turnSpeed = Mathf.MoveTowards(runtimeData.turnSpeed, targetTurnSpeed, runtimeData.mouseSensitivity * maxTurnSpeed * deltaTime);
// Apply rotation // Apply rotation
transform.Rotate(0f, turnSpeed * deltaTime, 0f); transform.Rotate(0f, runtimeData.turnSpeed * deltaTime, 0f);
mouseInputX = Mathf.MoveTowards(mouseInputX, 0f, mouseSensitivity * deltaTime); runtimeData.mouseInputX = Mathf.MoveTowards(runtimeData.mouseInputX, 0f, runtimeData.mouseSensitivity * deltaTime);
} }
void HandleJumping(float deltaTime) void HandleJumping(float deltaTime)
{ {
if (groundState != GroundState.Falling && moveKeys.Jump != KeyCode.None && Input.GetKey(moveKeys.Jump)) if (runtimeData.groundState != GroundState.Falling && moveKeys.Jump != KeyCode.None && Input.GetKey(moveKeys.Jump))
{ {
if (groundState != GroundState.Jumping) if (runtimeData.groundState != GroundState.Jumping)
{ {
groundState = GroundState.Jumping; runtimeData.groundState = GroundState.Jumping;
jumpSpeed = initialJumpSpeed; runtimeData.jumpSpeed = initialJumpSpeed;
} }
else if (jumpSpeed < maxJumpSpeed) else if (runtimeData.jumpSpeed < maxJumpSpeed)
{ {
// Increase jumpSpeed using a square root function for a fast start and slow finish // Increase jumpSpeed using a square root function for a fast start and slow finish
float jumpProgress = (jumpSpeed - initialJumpSpeed) / (maxJumpSpeed - initialJumpSpeed); float jumpProgress = (runtimeData.jumpSpeed - initialJumpSpeed) / (maxJumpSpeed - initialJumpSpeed);
jumpSpeed += (jumpAcceleration * Mathf.Sqrt(1 - jumpProgress)) * deltaTime; runtimeData.jumpSpeed += (jumpAcceleration * Mathf.Sqrt(1 - jumpProgress)) * deltaTime;
} }
if (jumpSpeed >= maxJumpSpeed) if (runtimeData.jumpSpeed >= maxJumpSpeed)
{ {
jumpSpeed = maxJumpSpeed; runtimeData.jumpSpeed = maxJumpSpeed;
groundState = GroundState.Falling; runtimeData.groundState = GroundState.Falling;
} }
} }
else if (groundState != GroundState.Grounded) else if (runtimeData.groundState != GroundState.Grounded)
{ {
groundState = GroundState.Falling; runtimeData.groundState = GroundState.Falling;
jumpSpeed = Mathf.Min(jumpSpeed, maxJumpSpeed); runtimeData.jumpSpeed = Mathf.Min(runtimeData.jumpSpeed, maxJumpSpeed);
jumpSpeed += Physics.gravity.y * deltaTime; runtimeData.jumpSpeed += Physics.gravity.y * deltaTime;
} }
else else
// maintain small downward speed for when falling off ledges // maintain small downward speed for when falling off ledges
jumpSpeed = Physics.gravity.y * deltaTime; runtimeData.jumpSpeed = Physics.gravity.y * deltaTime;
} }
void HandleMove(float deltaTime) void HandleMove(float deltaTime)
@ -376,39 +438,40 @@ void HandleMove(float deltaTime)
if (targetMoveX == 0f) if (targetMoveX == 0f)
{ {
if (!controlOptions.HasFlag(ControlOptions.AutoRun)) if (!controlOptions.HasFlag(ControlOptions.AutoRun))
horizontal = Mathf.MoveTowards(horizontal, targetMoveX, inputGravity * deltaTime); runtimeData.horizontal = Mathf.MoveTowards(runtimeData.horizontal, targetMoveX, inputGravity * deltaTime);
} }
else else
horizontal = Mathf.MoveTowards(horizontal, targetMoveX, inputSensitivity * deltaTime); runtimeData.horizontal = Mathf.MoveTowards(runtimeData.horizontal, targetMoveX, inputSensitivity * deltaTime);
if (targetMoveZ == 0f) if (targetMoveZ == 0f)
{ {
if (!controlOptions.HasFlag(ControlOptions.AutoRun)) if (!controlOptions.HasFlag(ControlOptions.AutoRun))
vertical = Mathf.MoveTowards(vertical, targetMoveZ, inputGravity * deltaTime); runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputGravity * deltaTime);
} }
else else
vertical = Mathf.MoveTowards(vertical, targetMoveZ, inputSensitivity * deltaTime); runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputSensitivity * deltaTime);
} }
void ApplyMove(float deltaTime) void ApplyMove(float deltaTime)
{ {
// Create initial direction vector without jumpSpeed (y-axis). // Create initial direction vector without jumpSpeed (y-axis).
direction = new Vector3(horizontal, 0f, vertical); runtimeData.direction = new Vector3(runtimeData.horizontal, 0f, runtimeData.vertical);
// Clamp so diagonal strafing isn't a speed advantage. // Clamp so diagonal strafing isn't a speed advantage.
direction = Vector3.ClampMagnitude(direction, 1f); runtimeData.direction = Vector3.ClampMagnitude(runtimeData.direction, 1f);
// Transforms direction from local space to world space. // Transforms direction from local space to world space.
direction = transform.TransformDirection(direction); runtimeData.direction = transform.TransformDirection(runtimeData.direction);
// Multiply for desired ground speed. // Multiply for desired ground speed.
direction *= maxMoveSpeed; runtimeData.direction *= maxMoveSpeed;
// Add jumpSpeed to direction as last step. // Add jumpSpeed to direction as last step.
direction.y = jumpSpeed; //runtimeData.direction.y = runtimeData.jumpSpeed;
runtimeData.direction = new Vector3(runtimeData.direction.x, runtimeData.jumpSpeed, runtimeData.direction.z);
// Finally move the character. // Finally move the character.
characterController.Move(direction * deltaTime); characterController.Move(runtimeData.direction * deltaTime);
} }
} }
} }

View File

@ -12,7 +12,7 @@ namespace Mirror.Examples.Common.Controllers.Tank
[DisallowMultipleComponent] [DisallowMultipleComponent]
public class TankControllerBase : NetworkBehaviour public class TankControllerBase : NetworkBehaviour
{ {
public enum GroundState : byte { Jumping, Falling, Grounded } public enum GroundState : byte { Grounded, Jumping, Falling }
[Serializable] [Serializable]
public struct MoveKeys public struct MoveKeys
@ -90,32 +90,81 @@ public enum ControlOptions : byte
[Tooltip("Rotation acceleration in degrees per second squared")] [Tooltip("Rotation acceleration in degrees per second squared")]
public float turnAcceleration = 3f; public float turnAcceleration = 3f;
// Runtime data in a struct so it can be folded up in inspector
[Serializable]
public struct RuntimeData
{
[ReadOnly, SerializeField, Range(-1f, 1f)] float _horizontal;
[ReadOnly, SerializeField, Range(-1f, 1f)] float _vertical;
[ReadOnly, SerializeField, Range(-300f, 300f)] float _turnSpeed;
[ReadOnly, SerializeField, Range(-1.5f, 1.5f)] float _animVelocity;
[ReadOnly, SerializeField, Range(-1.5f, 1.5f)] float _animRotation;
[ReadOnly, SerializeField] GroundState _groundState;
[ReadOnly, SerializeField] Vector3 _direction;
[ReadOnly, SerializeField] Vector3Int _velocity;
[ReadOnly, SerializeField] GameObject _controllerUI;
#region Properties
public float horizontal
{
get => _horizontal;
internal set => _horizontal = value;
}
public float vertical
{
get => _vertical;
internal set => _vertical = value;
}
public float turnSpeed
{
get => _turnSpeed;
internal set => _turnSpeed = value;
}
public float animVelocity
{
get => _animVelocity;
internal set => _animVelocity = value;
}
public float animRotation
{
get => _animRotation;
internal set => _animRotation = value;
}
public GameObject controllerUI
{
get => _controllerUI;
internal set => _controllerUI = value;
}
public Vector3 direction
{
get => _direction;
internal set => _direction = value;
}
public Vector3Int velocity
{
get => _velocity;
internal set => _velocity = value;
}
public GroundState groundState
{
get => _groundState;
internal set => _groundState = value;
}
#endregion
}
[Header("Diagnostics")] [Header("Diagnostics")]
[ReadOnly, SerializeField] public RuntimeData runtimeData;
GroundState groundState = GroundState.Grounded;
[ReadOnly, SerializeField, Range(-1f, 1f)]
float horizontal;
[ReadOnly, SerializeField, Range(-1f, 1f)]
float vertical;
[ReadOnly, SerializeField, Range(-300f, 300f)]
float turnSpeed;
[ReadOnly, SerializeField, Range(-1.5f, 1.5f)]
float animVelocity;
[ReadOnly, SerializeField, Range(-1.5f, 1.5f)]
float animRotation;
[ReadOnly, SerializeField]
Vector3 direction;
[ReadOnly, SerializeField]
Vector3Int velocity;
[ReadOnly, SerializeField]
GameObject controllerUI;
#region Network Setup #region Network Setup
@ -164,15 +213,13 @@ protected virtual void Reset()
void OnDisable() void OnDisable()
{ {
horizontal = 0f; runtimeData.horizontal = 0f;
vertical = 0f; runtimeData.vertical = 0f;
turnSpeed = 0f; runtimeData.turnSpeed = 0f;
} }
public override void OnStartAuthority() public override void OnStartAuthority()
{ {
// capsuleCollider and characterController are mutually exclusive
// Having both enabled would double fire triggers and other collisions
characterController.enabled = true; characterController.enabled = true;
this.enabled = true; this.enabled = true;
} }
@ -180,31 +227,28 @@ public override void OnStartAuthority()
public override void OnStopAuthority() public override void OnStopAuthority()
{ {
this.enabled = false; this.enabled = false;
// capsuleCollider and characterController are mutually exclusive
// Having both enabled would double fire triggers and other collisions
characterController.enabled = false; characterController.enabled = false;
} }
public override void OnStartLocalPlayer() public override void OnStartLocalPlayer()
{ {
if (ControllerUIPrefab != null) if (ControllerUIPrefab != null)
controllerUI = Instantiate(ControllerUIPrefab); runtimeData.controllerUI = Instantiate(ControllerUIPrefab);
if (controllerUI != null) if (runtimeData.controllerUI != null)
{ {
if (controllerUI.TryGetComponent(out TankControllerUI canvasControlPanel)) if (runtimeData.controllerUI.TryGetComponent(out TankControllerUI canvasControlPanel))
canvasControlPanel.Refresh(moveKeys, optionsKeys); canvasControlPanel.Refresh(moveKeys, optionsKeys);
controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI)); runtimeData.controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
} }
} }
public override void OnStopLocalPlayer() public override void OnStopLocalPlayer()
{ {
if (controllerUI != null) if (runtimeData.controllerUI != null)
Destroy(controllerUI); Destroy(runtimeData.controllerUI);
controllerUI = null; runtimeData.controllerUI = null;
} }
#endregion #endregion
@ -223,12 +267,12 @@ void Update()
// Reset ground state // Reset ground state
if (characterController.isGrounded) if (characterController.isGrounded)
groundState = GroundState.Grounded; runtimeData.groundState = GroundState.Grounded;
else if (groundState != GroundState.Jumping) else if (runtimeData.groundState != GroundState.Jumping)
groundState = GroundState.Falling; runtimeData.groundState = GroundState.Falling;
// Diagnostic velocity...FloorToInt for display purposes // Diagnostic velocity...FloorToInt for display purposes
velocity = Vector3Int.FloorToInt(characterController.velocity); runtimeData.velocity = Vector3Int.FloorToInt(characterController.velocity);
} }
void HandleOptions() void HandleOptions()
@ -240,8 +284,8 @@ void HandleOptions()
{ {
controlOptions ^= ControlOptions.ShowUI; controlOptions ^= ControlOptions.ShowUI;
if (controllerUI != null) if (runtimeData.controllerUI != null)
controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI)); runtimeData.controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
} }
} }
@ -259,9 +303,9 @@ void HandleTurning(float deltaTime)
// If there's turn input or AutoRun is not enabled, adjust turn speed towards target // If there's turn input or AutoRun is not enabled, adjust turn speed towards target
// If no turn input and AutoRun is enabled, maintain the previous turn speed // If no turn input and AutoRun is enabled, maintain the previous turn speed
if (targetTurnSpeed != 0f || !controlOptions.HasFlag(ControlOptions.AutoRun)) if (targetTurnSpeed != 0f || !controlOptions.HasFlag(ControlOptions.AutoRun))
turnSpeed = Mathf.MoveTowards(turnSpeed, targetTurnSpeed, turnAcceleration * maxTurnSpeed * deltaTime); runtimeData.turnSpeed = Mathf.MoveTowards(runtimeData.turnSpeed, targetTurnSpeed, turnAcceleration * maxTurnSpeed * deltaTime);
transform.Rotate(0f, turnSpeed * deltaTime, 0f); transform.Rotate(0f, runtimeData.turnSpeed * deltaTime, 0f);
} }
void HandleMove(float deltaTime) void HandleMove(float deltaTime)
@ -277,39 +321,39 @@ void HandleMove(float deltaTime)
if (targetMoveX == 0f) if (targetMoveX == 0f)
{ {
if (!controlOptions.HasFlag(ControlOptions.AutoRun)) if (!controlOptions.HasFlag(ControlOptions.AutoRun))
horizontal = Mathf.MoveTowards(horizontal, targetMoveX, inputGravity * deltaTime); runtimeData.horizontal = Mathf.MoveTowards(runtimeData.horizontal, targetMoveX, inputGravity * deltaTime);
} }
else else
horizontal = Mathf.MoveTowards(horizontal, targetMoveX, inputSensitivity * deltaTime); runtimeData.horizontal = Mathf.MoveTowards(runtimeData.horizontal, targetMoveX, inputSensitivity * deltaTime);
if (targetMoveZ == 0f) if (targetMoveZ == 0f)
{ {
if (!controlOptions.HasFlag(ControlOptions.AutoRun)) if (!controlOptions.HasFlag(ControlOptions.AutoRun))
vertical = Mathf.MoveTowards(vertical, targetMoveZ, inputGravity * deltaTime); runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputGravity * deltaTime);
} }
else else
vertical = Mathf.MoveTowards(vertical, targetMoveZ, inputSensitivity * deltaTime); runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputSensitivity * deltaTime);
} }
void ApplyMove(float deltaTime) void ApplyMove(float deltaTime)
{ {
// Create initial direction vector without jumpSpeed (y-axis). // Create initial direction vector without jumpSpeed (y-axis).
direction = new Vector3(horizontal, 0f, vertical); runtimeData.direction = new Vector3(runtimeData.horizontal, 0f, runtimeData.vertical);
// Clamp so diagonal strafing isn't a speed advantage. // Clamp so diagonal strafing isn't a speed advantage.
direction = Vector3.ClampMagnitude(direction, 1f); runtimeData.direction = Vector3.ClampMagnitude(runtimeData.direction, 1f);
// Transforms direction from local space to world space. // Transforms direction from local space to world space.
direction = transform.TransformDirection(direction); runtimeData.direction = transform.TransformDirection(runtimeData.direction);
// Multiply for desired ground speed. // Multiply for desired ground speed.
direction *= maxMoveSpeed; runtimeData.direction *= maxMoveSpeed;
// Add gravity in case we drove off a cliff. // Add gravity in case we drove off a cliff.
direction += Physics.gravity; runtimeData.direction += Physics.gravity;
// Finally move the character. // Finally move the character.
characterController.Move(direction * deltaTime); characterController.Move(runtimeData.direction * deltaTime);
} }
} }
} }

View File

@ -8,6 +8,8 @@ namespace Mirror.Examples.Common.Controllers.Tank
[DisallowMultipleComponent] [DisallowMultipleComponent]
public class TankTurretBase : NetworkBehaviour public class TankTurretBase : NetworkBehaviour
{ {
const float BASE_DPI = 96f;
[Serializable] [Serializable]
public struct OptionsKeys public struct OptionsKeys
{ {
@ -31,8 +33,6 @@ public struct OtherKeys
public KeyCode Shoot; public KeyCode Shoot;
} }
const float BASE_DPI = 96f;
[Flags] [Flags]
public enum ControlOptions : byte public enum ControlOptions : byte
{ {
@ -98,7 +98,7 @@ public enum ControlOptions : byte
[Range(0, 300f)] [Range(0, 300f)]
[Tooltip("Max Rotation in degrees per second")] [Tooltip("Max Rotation in degrees per second")]
public float maxTurretSpeed = 250f; public float maxTurretSpeed = 250f;
[Range(0, 10f)] [Range(0, 30f)]
[Tooltip("Rotation acceleration in degrees per second squared")] [Tooltip("Rotation acceleration in degrees per second squared")]
public float turretAcceleration = 10f; public float turretAcceleration = 10f;
@ -116,33 +116,69 @@ public enum ControlOptions : byte
[Tooltip("Pitch acceleration in degrees per second squared")] [Tooltip("Pitch acceleration in degrees per second squared")]
public float pitchAcceleration = 3f; public float pitchAcceleration = 3f;
[Header("Diagnostics")] // Runtime data in a struct so it can be folded up in inspector
[ReadOnly, SerializeField, Range(-1f, 1f)] [Serializable]
float mouseInputX; public struct RuntimeData
[ReadOnly, SerializeField, Range(0, 30f)]
float mouseSensitivity;
[ReadOnly, SerializeField, Range(-300f, 300f)]
float turretSpeed;
[ReadOnly, SerializeField, Range(-180f, 180f)]
float pitchAngle;
[ReadOnly, SerializeField, Range(-180f, 180f)]
float pitchSpeed;
[ReadOnly, SerializeField]
double lastShotTime;
[ReadOnly, SerializeField]
GameObject turretUI;
void OnPlayerColorChanged(Color32 _, Color32 newColor)
{ {
if (cachedMaterial == null) [ReadOnly, SerializeField, Range(-300f, 300f)] float _turretSpeed;
cachedMaterial = playerObject.GetComponent<Renderer>().material; [ReadOnly, SerializeField, Range(-180f, 180f)] float _pitchAngle;
[ReadOnly, SerializeField, Range(-180f, 180f)] float _pitchSpeed;
[ReadOnly, SerializeField, Range(-1f, 1f)] float _mouseInputX;
[ReadOnly, SerializeField, Range(0, 30f)] float _mouseSensitivity;
[ReadOnly, SerializeField] double _lastShotTime;
[ReadOnly, SerializeField] GameObject _turretUI;
cachedMaterial.color = newColor; #region Properties
playerObject.SetActive(newColor != Color.black);
public float mouseInputX
{
get => _mouseInputX;
internal set => _mouseInputX = value;
}
public float mouseSensitivity
{
get => _mouseSensitivity;
internal set => _mouseSensitivity = value;
}
public float turretSpeed
{
get => _turretSpeed;
internal set => _turretSpeed = value;
}
public float pitchAngle
{
get => _pitchAngle;
internal set => _pitchAngle = value;
}
public float pitchSpeed
{
get => _pitchSpeed;
internal set => _pitchSpeed = value;
}
public double lastShotTime
{
get => _lastShotTime;
internal set => _lastShotTime = value;
}
public GameObject turretUI
{
get => _turretUI;
internal set => _turretUI = value;
}
#endregion
} }
#region Unity Callbacks [Header("Diagnostics")]
public RuntimeData runtimeData;
#region Network Setup
protected override void OnValidate() protected override void OnValidate()
{ {
@ -160,7 +196,7 @@ protected virtual void Reset()
animator = GetComponentInChildren<Animator>(); animator = GetComponentInChildren<Animator>();
// Set default...this may be modified based on DPI at runtime // Set default...this may be modified based on DPI at runtime
mouseSensitivity = turretAcceleration; runtimeData.mouseSensitivity = turretAcceleration;
// Do a recursive search for a children named "Turret" and "ProjectileMount". // Do a recursive search for a children named "Turret" and "ProjectileMount".
// They will be several levels deep in the hierarchy. // They will be several levels deep in the hierarchy.
@ -215,6 +251,43 @@ Transform FindDeepChild(Transform aParent, string aName)
this.enabled = false; this.enabled = false;
} }
public override void OnStartLocalPlayer()
{
if (turretUIPrefab != null)
runtimeData.turretUI = Instantiate(turretUIPrefab);
if (runtimeData.turretUI != null)
{
if (runtimeData.turretUI.TryGetComponent(out TurretUI canvasControlPanel))
canvasControlPanel.Refresh(moveKeys, optionsKeys);
runtimeData.turretUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
}
}
public override void OnStopLocalPlayer()
{
if (runtimeData.turretUI != null)
Destroy(runtimeData.turretUI);
runtimeData.turretUI = null;
}
public override void OnStartAuthority()
{
// Calculate DPI-aware sensitivity
float dpiScale = (Screen.dpi > 0) ? (Screen.dpi / BASE_DPI) : 1f;
runtimeData.mouseSensitivity = turretAcceleration * dpiScale;
SetCursor(controlOptions.HasFlag(ControlOptions.MouseLock));
this.enabled = true;
}
public override void OnStopAuthority()
{
SetCursor(false);
this.enabled = false;
}
#endregion #endregion
void Update() void Update()
@ -232,6 +305,15 @@ void Update()
HandleShooting(); HandleShooting();
} }
void OnPlayerColorChanged(Color32 _, Color32 newColor)
{
if (cachedMaterial == null)
cachedMaterial = playerObject.GetComponent<Renderer>().material;
cachedMaterial.color = newColor;
playerObject.SetActive(newColor != Color.black);
}
void SetCursor(bool locked) void SetCursor(bool locked)
{ {
Cursor.lockState = locked ? CursorLockMode.Locked : CursorLockMode.None; Cursor.lockState = locked ? CursorLockMode.Locked : CursorLockMode.None;
@ -253,8 +335,8 @@ void HandleOptions()
{ {
controlOptions ^= ControlOptions.ShowUI; controlOptions ^= ControlOptions.ShowUI;
if (turretUI != null) if (runtimeData.turretUI != null)
turretUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI)); runtimeData.turretUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
} }
} }
@ -268,28 +350,28 @@ void HandleTurning(float deltaTime)
if (moveKeys.TurnRight != KeyCode.None && Input.GetKey(moveKeys.TurnRight)) if (moveKeys.TurnRight != KeyCode.None && Input.GetKey(moveKeys.TurnRight))
targetTurnSpeed += maxTurretSpeed; targetTurnSpeed += maxTurretSpeed;
turretSpeed = Mathf.MoveTowards(turretSpeed, targetTurnSpeed, turretAcceleration * maxTurretSpeed * deltaTime); runtimeData.turretSpeed = Mathf.MoveTowards(runtimeData.turretSpeed, targetTurnSpeed, turretAcceleration * maxTurretSpeed * deltaTime);
turret.Rotate(0f, turretSpeed * deltaTime, 0f); turret.Rotate(0f, runtimeData.turretSpeed * deltaTime, 0f);
} }
void HandleMouseTurret(float deltaTime) void HandleMouseTurret(float deltaTime)
{ {
// Accumulate mouse input over time // Accumulate mouse input over time
mouseInputX += Input.GetAxisRaw("Mouse X") * mouseSensitivity; runtimeData.mouseInputX += Input.GetAxisRaw("Mouse X") * runtimeData.mouseSensitivity;
// Clamp the accumulator to simulate key press behavior // Clamp the accumulator to simulate key press behavior
mouseInputX = Mathf.Clamp(mouseInputX, -1f, 1f); runtimeData.mouseInputX = Mathf.Clamp(runtimeData.mouseInputX, -1f, 1f);
// Calculate target turn speed // Calculate target turn speed
float targetTurnSpeed = mouseInputX * maxTurretSpeed; float targetTurnSpeed = runtimeData.mouseInputX * maxTurretSpeed;
// Use the same acceleration logic as HandleTurning // Use the same acceleration logic as HandleTurning
turretSpeed = Mathf.MoveTowards(turretSpeed, targetTurnSpeed, mouseSensitivity * maxTurretSpeed * deltaTime); runtimeData.turretSpeed = Mathf.MoveTowards(runtimeData.turretSpeed, targetTurnSpeed, runtimeData.mouseSensitivity * maxTurretSpeed * deltaTime);
// Apply rotation // Apply rotation
turret.Rotate(0f, turretSpeed * deltaTime, 0f); turret.Rotate(0f, runtimeData.turretSpeed * deltaTime, 0f);
mouseInputX = Mathf.MoveTowards(mouseInputX, 0f, mouseSensitivity * deltaTime); runtimeData.mouseInputX = Mathf.MoveTowards(runtimeData.mouseInputX, 0f, runtimeData.mouseSensitivity * deltaTime);
} }
void HandlePitch(float deltaTime) void HandlePitch(float deltaTime)
@ -310,19 +392,19 @@ void HandlePitch(float deltaTime)
inputDetected = true; inputDetected = true;
} }
pitchSpeed = Mathf.MoveTowards(pitchSpeed, targetPitchSpeed, pitchAcceleration * maxPitchSpeed * deltaTime); runtimeData.pitchSpeed = Mathf.MoveTowards(runtimeData.pitchSpeed, targetPitchSpeed, pitchAcceleration * maxPitchSpeed * deltaTime);
// Apply pitch rotation // Apply pitch rotation
pitchAngle += pitchSpeed * deltaTime; runtimeData.pitchAngle += runtimeData.pitchSpeed * deltaTime;
pitchAngle = Mathf.Clamp(pitchAngle, -maxPitchUpAngle, maxPitchDownAngle); runtimeData.pitchAngle = Mathf.Clamp(runtimeData.pitchAngle, -maxPitchUpAngle, maxPitchDownAngle);
// Return to -90 when no input // Return to -90 when no input
if (!inputDetected && controlOptions.HasFlag(ControlOptions.AutoLevel)) if (!inputDetected && controlOptions.HasFlag(ControlOptions.AutoLevel))
pitchAngle = Mathf.MoveTowards(pitchAngle, 0f, maxPitchSpeed * deltaTime); runtimeData.pitchAngle = Mathf.MoveTowards(runtimeData.pitchAngle, 0f, maxPitchSpeed * deltaTime);
// Apply rotation to barrel -- rotation is (-90, 0, 180) in the prefab // Apply rotation to barrel -- rotation is (-90, 0, 180) in the prefab
// so that's what we have to work towards. // so that's what we have to work towards.
barrel.localRotation = Quaternion.Euler(-90f + pitchAngle, 0f, 180f); barrel.localRotation = Quaternion.Euler(-90f + runtimeData.pitchAngle, 0f, 180f);
} }
#region Shooting #region Shooting
@ -336,7 +418,7 @@ void HandleShooting()
} }
} }
bool CanShoot => NetworkTime.time >= lastShotTime + cooldownTime; bool CanShoot => NetworkTime.time >= runtimeData.lastShotTime + cooldownTime;
[Command] [Command]
void CmdShoot() void CmdShoot()
@ -358,13 +440,13 @@ void RpcShoot()
// This has multiple callers in different contexts...don't consolidate, even if it looks like you could. // This has multiple callers in different contexts...don't consolidate, even if it looks like you could.
void DoShoot() void DoShoot()
{ {
//Debug.Log($"DoShoot isServerOnly:{isServerOnly} | isServer:{isServer} | isClient:{isClient}"); //Debug.Log($"DoShoot isServerOnly:{isServerOnly} | isServer:{isServer} | isClientOnly:{isClientOnly}");
// ProjectileMount.transform.parent.parent is the Barrel object with the Collider
// Turret // Turret
// - Barrel (with Collider) // - Barrel (with Collider)
// - BarrelEnd // - BarrelEnd
// - ProjectileMount // - ProjectileMount
// projectileMount.transform.parent.parent is the Barrel object with the Collider
if (isServerOnly) if (isServerOnly)
{ {
@ -373,7 +455,7 @@ void DoShoot()
Physics.IgnoreCollision(go.GetComponent<Collider>(), projectileMount.transform.parent.parent.GetComponent<Collider>()); Physics.IgnoreCollision(go.GetComponent<Collider>(), projectileMount.transform.parent.parent.GetComponent<Collider>());
// Update the last shot time // Update the last shot time
lastShotTime = NetworkTime.time; runtimeData.lastShotTime = NetworkTime.time;
} }
else if (isServer) else if (isServer)
{ {
@ -383,7 +465,7 @@ void DoShoot()
Physics.IgnoreCollision(go.GetComponent<Collider>(), projectileMount.transform.parent.parent.GetComponent<Collider>()); Physics.IgnoreCollision(go.GetComponent<Collider>(), projectileMount.transform.parent.parent.GetComponent<Collider>());
// Update the last shot time // Update the last shot time
lastShotTime = NetworkTime.time; runtimeData.lastShotTime = NetworkTime.time;
} }
if (isClientOnly) if (isClientOnly)
@ -394,54 +476,10 @@ void DoShoot()
Physics.IgnoreCollision(go.GetComponent<Collider>(), projectileMount.transform.parent.parent.GetComponent<Collider>()); Physics.IgnoreCollision(go.GetComponent<Collider>(), projectileMount.transform.parent.parent.GetComponent<Collider>());
// Update the last shot time // Update the last shot time
lastShotTime = NetworkTime.time; runtimeData.lastShotTime = NetworkTime.time;
} }
} }
#endregion #endregion
#region Start & Stop Callbacks
public override void OnStartServer() { }
public override void OnStartLocalPlayer()
{
if (turretUIPrefab != null)
turretUI = Instantiate(turretUIPrefab);
if (turretUI != null)
{
if (turretUI.TryGetComponent(out TurretUI canvasControlPanel))
canvasControlPanel.Refresh(moveKeys, optionsKeys);
turretUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
}
}
public override void OnStopLocalPlayer()
{
if (turretUI != null)
Destroy(turretUI);
turretUI = null;
}
public override void OnStartAuthority()
{
// Calculate DPI-aware sensitivity
float dpiScale = (Screen.dpi > 0) ? (Screen.dpi / BASE_DPI) : 1f;
mouseSensitivity = turretAcceleration * dpiScale;
//Debug.Log($"Screen DPI: {Screen.dpi}, DPI Scale: {dpiScale}, Adjusted Turn Acceleration: {turnAccelerationDPI}");
SetCursor(controlOptions.HasFlag(ControlOptions.MouseLock));
this.enabled = true;
}
public override void OnStopAuthority()
{
SetCursor(false);
this.enabled = false;
}
#endregion
} }
} }

View File

@ -8,8 +8,8 @@ namespace Mirror.Examples.Common.Controllers.Tank
public class TankTurretReliable : TankTurretBase public class TankTurretReliable : TankTurretBase
{ {
[Header("Components")] [Header("Components")]
public NetworkTransformReliable turretNTR; public NetworkTransformReliable turretNetworkTransform;
public NetworkTransformReliable barrelNTR; public NetworkTransformReliable barrelNetworkTransform;
protected override void Reset() protected override void Reset()
{ {
@ -22,41 +22,39 @@ protected override void Reset()
if (NTs.Length < 2) if (NTs.Length < 2)
{ {
turretNTR = gameObject.AddComponent<NetworkTransformReliable>(); turretNetworkTransform = gameObject.AddComponent<NetworkTransformReliable>();
turretNTR.transform.SetSiblingIndex(NTs[0].transform.GetSiblingIndex() + 1); turretNetworkTransform.transform.SetSiblingIndex(NTs[0].transform.GetSiblingIndex() + 1);
NTs = GetComponents<NetworkTransformReliable>(); NTs = GetComponents<NetworkTransformReliable>();
} }
else else
turretNTR = NTs[1]; turretNetworkTransform = NTs[1];
// Ensure SyncDirection is Client to Server // Ensure syncDirection is Client to Server
turretNTR.syncDirection = SyncDirection.ClientToServer; turretNetworkTransform.syncDirection = SyncDirection.ClientToServer;
turretNTR.syncPosition = false;
// Set SyncPosition to false because we only want to sync rotation // Set syncPosition to false because we only want to sync rotation
//turretNTR.syncPosition = false; turretNetworkTransform.syncPosition = false;
if (base.turret != null) if (base.turret != null)
turretNTR.target = turret; turretNetworkTransform.target = turret;
if (NTs.Length < 3) if (NTs.Length < 3)
{ {
barrelNTR = gameObject.AddComponent<NetworkTransformReliable>(); barrelNetworkTransform = gameObject.AddComponent<NetworkTransformReliable>();
barrelNTR.transform.SetSiblingIndex(NTs[1].transform.GetSiblingIndex() + 1); barrelNetworkTransform.transform.SetSiblingIndex(NTs[1].transform.GetSiblingIndex() + 1);
NTs = GetComponents<NetworkTransformReliable>(); NTs = GetComponents<NetworkTransformReliable>();
} }
else else
barrelNTR = NTs[2]; barrelNetworkTransform = NTs[2];
// Ensure SyncDirection is Client to Server // Ensure syncDirection is Client to Server
barrelNTR.syncDirection = SyncDirection.ClientToServer; barrelNetworkTransform.syncDirection = SyncDirection.ClientToServer;
barrelNTR.syncPosition = false;
// Set SyncPosition to false because we only want to sync rotation // Set syncPosition to false because we only want to sync rotation
//barrelNTR.syncPosition = false; barrelNetworkTransform.syncPosition = false;
if (barrel != null) if (barrel != null)
barrelNTR.target = barrel; barrelNetworkTransform.target = barrel;
} }
} }
} }

View File

@ -8,8 +8,8 @@ namespace Mirror.Examples.Common.Controllers.Tank
public class TankTurretUnreliable : TankTurretBase public class TankTurretUnreliable : TankTurretBase
{ {
[Header("Components")] [Header("Components")]
public NetworkTransformUnreliable turretNTR; public NetworkTransformUnreliable turretNetworkTransform;
public NetworkTransformUnreliable barrelNTR; public NetworkTransformUnreliable barrelNetworkTransform;
protected override void Reset() protected override void Reset()
{ {
@ -22,41 +22,39 @@ protected override void Reset()
if (NTs.Length < 2) if (NTs.Length < 2)
{ {
turretNTR = gameObject.AddComponent<NetworkTransformUnreliable>(); turretNetworkTransform = gameObject.AddComponent<NetworkTransformUnreliable>();
turretNTR.transform.SetSiblingIndex(NTs[0].transform.GetSiblingIndex() + 1); turretNetworkTransform.transform.SetSiblingIndex(NTs[0].transform.GetSiblingIndex() + 1);
NTs = GetComponents<NetworkTransformUnreliable>(); NTs = GetComponents<NetworkTransformUnreliable>();
} }
else else
turretNTR = NTs[1]; turretNetworkTransform = NTs[1];
// Ensure SyncDirection is Client to Server // Ensure syncDirection is Client to Server
turretNTR.syncDirection = SyncDirection.ClientToServer; turretNetworkTransform.syncDirection = SyncDirection.ClientToServer;
turretNTR.syncPosition = false;
// Set SyncPosition to false because we only want to sync rotation // Set syncPosition to false because we only want to sync rotation
//turretNTR.syncPosition = false; turretNetworkTransform.syncPosition = false;
if (base.turret != null) if (base.turret != null)
turretNTR.target = turret; turretNetworkTransform.target = turret;
if (NTs.Length < 3) if (NTs.Length < 3)
{ {
barrelNTR = gameObject.AddComponent<NetworkTransformUnreliable>(); barrelNetworkTransform = gameObject.AddComponent<NetworkTransformUnreliable>();
barrelNTR.transform.SetSiblingIndex(NTs[1].transform.GetSiblingIndex() + 1); barrelNetworkTransform.transform.SetSiblingIndex(NTs[1].transform.GetSiblingIndex() + 1);
NTs = GetComponents<NetworkTransformUnreliable>(); NTs = GetComponents<NetworkTransformUnreliable>();
} }
else else
barrelNTR = NTs[2]; barrelNetworkTransform = NTs[2];
// Ensure SyncDirection is Client to Server // Ensure syncDirection is Client to Server
barrelNTR.syncDirection = SyncDirection.ClientToServer; barrelNetworkTransform.syncDirection = SyncDirection.ClientToServer;
barrelNTR.syncPosition = false;
// Set SyncPosition to false because we only want to sync rotation // Set syncPosition to false because we only want to sync rotation
//barrelNTR.syncPosition = false; barrelNetworkTransform.syncPosition = false;
if (barrel != null) if (barrel != null)
barrelNTR.target = barrel; barrelNetworkTransform.target = barrel;
} }
} }
} }