mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
feat: Updated example controllers
RuntimeData struct for inspector folding
This commit is contained in:
parent
53d8812281
commit
70351d96f4
@ -22,7 +22,7 @@ public struct OptionsKeys
|
||||
public KeyCode ToggleUI;
|
||||
}
|
||||
|
||||
public enum GroundState : byte { Jumping, Falling, Grounded }
|
||||
public enum GroundState : byte { Grounded, Jumping, Falling }
|
||||
|
||||
[Serializable]
|
||||
public struct MoveKeys
|
||||
@ -145,46 +145,123 @@ public enum ControlOptions : byte
|
||||
[Tooltip("Roll acceleration in degrees per second squared")]
|
||||
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")]
|
||||
[ReadOnly, SerializeField]
|
||||
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;
|
||||
public RuntimeData runtimeData;
|
||||
|
||||
#region Network Setup
|
||||
|
||||
@ -235,8 +312,7 @@ public override void OnStartAuthority()
|
||||
{
|
||||
// Calculate DPI-aware sensitivity
|
||||
float dpiScale = (Screen.dpi > 0) ? (Screen.dpi / BASE_DPI) : 1f;
|
||||
mouseSensitivity = turnAcceleration * dpiScale;
|
||||
//Debug.Log($"Screen DPI: {Screen.dpi}, DPI Scale: {dpiScale}, Adjusted Turn Acceleration: {turnAccelerationDPI}");
|
||||
runtimeData.mouseSensitivity = turnAcceleration * dpiScale;
|
||||
|
||||
SetCursor(controlOptions.HasFlag(ControlOptions.MouseSteer));
|
||||
|
||||
@ -262,22 +338,22 @@ public override void OnStopAuthority()
|
||||
public override void OnStartLocalPlayer()
|
||||
{
|
||||
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);
|
||||
|
||||
controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
runtimeData.controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnStopLocalPlayer()
|
||||
{
|
||||
if (controllerUI != null)
|
||||
Destroy(controllerUI);
|
||||
controllerUI = null;
|
||||
if (runtimeData.controllerUI != null)
|
||||
Destroy(runtimeData.controllerUI);
|
||||
runtimeData.controllerUI = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -306,12 +382,12 @@ void Update()
|
||||
|
||||
// Reset ground state
|
||||
if (characterController.isGrounded)
|
||||
groundState = GroundState.Grounded;
|
||||
else if (groundState != GroundState.Jumping)
|
||||
groundState = GroundState.Falling;
|
||||
runtimeData.groundState = GroundState.Grounded;
|
||||
else if (runtimeData.groundState != GroundState.Jumping)
|
||||
runtimeData.groundState = GroundState.Falling;
|
||||
|
||||
// Diagnostic velocity...FloorToInt for display purposes
|
||||
velocity = Vector3Int.FloorToInt(characterController.velocity);
|
||||
runtimeData.velocity = Vector3Int.FloorToInt(characterController.velocity);
|
||||
}
|
||||
|
||||
void SetCursor(bool locked)
|
||||
@ -335,8 +411,8 @@ void HandleOptions()
|
||||
{
|
||||
controlOptions ^= ControlOptions.ShowUI;
|
||||
|
||||
if (controllerUI != null)
|
||||
controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
if (runtimeData.controllerUI != null)
|
||||
runtimeData.controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
}
|
||||
|
||||
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))
|
||||
targetTurnSpeed += maxTurnSpeed;
|
||||
|
||||
turnSpeed = Mathf.MoveTowards(turnSpeed, targetTurnSpeed, turnAcceleration * maxTurnSpeed * deltaTime);
|
||||
transform.Rotate(0f, turnSpeed * deltaTime, 0f);
|
||||
runtimeData.turnSpeed = Mathf.MoveTowards(runtimeData.turnSpeed, targetTurnSpeed, turnAcceleration * maxTurnSpeed * deltaTime);
|
||||
transform.Rotate(0f, runtimeData.turnSpeed * deltaTime, 0f);
|
||||
}
|
||||
|
||||
void HandleMouseSteer(float deltaTime)
|
||||
{
|
||||
// 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
|
||||
mouseInputX = Mathf.Clamp(mouseInputX, -1f, 1f);
|
||||
runtimeData.mouseInputX = Mathf.Clamp(runtimeData.mouseInputX, -1f, 1f);
|
||||
|
||||
// Calculate target turn speed
|
||||
float targetTurnSpeed = mouseInputX * maxTurnSpeed;
|
||||
float targetTurnSpeed = runtimeData.mouseInputX * maxTurnSpeed;
|
||||
|
||||
// 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
|
||||
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)
|
||||
@ -396,15 +472,15 @@ void HandlePitch(float deltaTime)
|
||||
inputDetected = true;
|
||||
}
|
||||
|
||||
pitchSpeed = Mathf.MoveTowards(pitchSpeed, targetPitchSpeed, pitchAcceleration * maxPitchSpeed * deltaTime);
|
||||
runtimeData.pitchSpeed = Mathf.MoveTowards(runtimeData.pitchSpeed, targetPitchSpeed, pitchAcceleration * maxPitchSpeed * deltaTime);
|
||||
|
||||
// Apply pitch rotation
|
||||
pitchAngle += pitchSpeed * deltaTime;
|
||||
pitchAngle = Mathf.Clamp(pitchAngle, -maxPitchUpAngle, maxPitchDownAngle);
|
||||
runtimeData.pitchAngle += runtimeData.pitchSpeed * deltaTime;
|
||||
runtimeData.pitchAngle = Mathf.Clamp(runtimeData.pitchAngle, -maxPitchUpAngle, maxPitchDownAngle);
|
||||
|
||||
// Return to zero when no input
|
||||
if (!inputDetected && controlOptions.HasFlag(ControlOptions.AutoLevel))
|
||||
pitchAngle = Mathf.MoveTowards(pitchAngle, 0f, maxPitchSpeed * deltaTime);
|
||||
runtimeData.pitchAngle = Mathf.MoveTowards(runtimeData.pitchAngle, 0f, maxPitchSpeed * deltaTime);
|
||||
|
||||
ApplyRotation();
|
||||
}
|
||||
@ -427,15 +503,15 @@ void HandleRoll(float deltaTime)
|
||||
inputDetected = true;
|
||||
}
|
||||
|
||||
rollSpeed = Mathf.MoveTowards(rollSpeed, targetRollSpeed, rollAcceleration * maxRollSpeed * deltaTime);
|
||||
runtimeData.rollSpeed = Mathf.MoveTowards(runtimeData.rollSpeed, targetRollSpeed, rollAcceleration * maxRollSpeed * deltaTime);
|
||||
|
||||
// Apply roll rotation
|
||||
rollAngle += rollSpeed * deltaTime;
|
||||
rollAngle = Mathf.Clamp(rollAngle, -maxRollAngle, maxRollAngle);
|
||||
runtimeData.rollAngle += runtimeData.rollSpeed * deltaTime;
|
||||
runtimeData.rollAngle = Mathf.Clamp(runtimeData.rollAngle, -maxRollAngle, maxRollAngle);
|
||||
|
||||
// Return to zero when no input
|
||||
if (!inputDetected && controlOptions.HasFlag(ControlOptions.AutoLevel))
|
||||
rollAngle = Mathf.MoveTowards(rollAngle, 0f, maxRollSpeed * deltaTime);
|
||||
runtimeData.rollAngle = Mathf.MoveTowards(runtimeData.rollAngle, 0f, maxRollSpeed * deltaTime);
|
||||
|
||||
ApplyRotation();
|
||||
}
|
||||
@ -446,7 +522,7 @@ void ApplyRotation()
|
||||
float currentYaw = transform.localRotation.eulerAngles.y;
|
||||
|
||||
// Apply all rotations
|
||||
transform.localRotation = Quaternion.Euler(pitchAngle, currentYaw, rollAngle);
|
||||
transform.localRotation = Quaternion.Euler(runtimeData.pitchAngle, currentYaw, runtimeData.rollAngle);
|
||||
}
|
||||
|
||||
void HandleMove(float deltaTime)
|
||||
@ -464,39 +540,36 @@ void HandleMove(float deltaTime)
|
||||
if (targetMoveX == 0f)
|
||||
{
|
||||
if (!controlOptions.HasFlag(ControlOptions.AutoRun))
|
||||
horizontal = Mathf.MoveTowards(horizontal, targetMoveX, inputGravity * deltaTime);
|
||||
runtimeData.horizontal = Mathf.MoveTowards(runtimeData.horizontal, targetMoveX, inputGravity * deltaTime);
|
||||
}
|
||||
else
|
||||
horizontal = Mathf.MoveTowards(horizontal, targetMoveX, inputSensitivity * deltaTime);
|
||||
runtimeData.horizontal = Mathf.MoveTowards(runtimeData.horizontal, targetMoveX, inputSensitivity * deltaTime);
|
||||
|
||||
if (targetMoveZ == 0f)
|
||||
{
|
||||
if (!controlOptions.HasFlag(ControlOptions.AutoRun))
|
||||
vertical = Mathf.MoveTowards(vertical, targetMoveZ, inputGravity * deltaTime);
|
||||
runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputGravity * deltaTime);
|
||||
}
|
||||
else
|
||||
vertical = Mathf.MoveTowards(vertical, targetMoveZ, inputSensitivity * deltaTime);
|
||||
runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputSensitivity * deltaTime);
|
||||
}
|
||||
|
||||
void ApplyMove(float deltaTime)
|
||||
{
|
||||
// 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.
|
||||
direction = Vector3.ClampMagnitude(direction, 1f);
|
||||
runtimeData.direction = Vector3.ClampMagnitude(runtimeData.direction, 1f);
|
||||
|
||||
// Transforms direction from local space to world space.
|
||||
direction = transform.TransformDirection(direction);
|
||||
runtimeData.direction = transform.TransformDirection(runtimeData.direction);
|
||||
|
||||
// Multiply for desired ground speed.
|
||||
direction *= maxMoveSpeed;
|
||||
|
||||
//// Add jumpSpeed to direction as last step.
|
||||
//direction.y = jumpSpeed;
|
||||
runtimeData.direction *= maxMoveSpeed;
|
||||
|
||||
// Finally move the character.
|
||||
characterController.Move(direction * deltaTime);
|
||||
characterController.Move(runtimeData.direction * deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ public class PlayerControllerBase : NetworkBehaviour
|
||||
{
|
||||
const float BASE_DPI = 96f;
|
||||
|
||||
public enum GroundState : byte { Jumping, Falling, Grounded }
|
||||
public enum GroundState : byte { Grounded, Jumping, Falling }
|
||||
|
||||
[Serializable]
|
||||
public struct MoveKeys
|
||||
@ -112,39 +112,102 @@ public enum ControlOptions : byte
|
||||
[Tooltip("Jump acceleration in meters per second squared")]
|
||||
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")]
|
||||
[ReadOnly, SerializeField]
|
||||
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;
|
||||
public RuntimeData runtimeData;
|
||||
|
||||
#region Network Setup
|
||||
|
||||
@ -187,17 +250,16 @@ void Reset()
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
horizontal = 0f;
|
||||
vertical = 0f;
|
||||
turnSpeed = 0f;
|
||||
runtimeData.horizontal = 0f;
|
||||
runtimeData.vertical = 0f;
|
||||
runtimeData.turnSpeed = 0f;
|
||||
}
|
||||
|
||||
public override void OnStartAuthority()
|
||||
{
|
||||
// Calculate DPI-aware sensitivity
|
||||
float dpiScale = (Screen.dpi > 0) ? (Screen.dpi / BASE_DPI) : 1f;
|
||||
mouseSensitivity = turnAcceleration * dpiScale;
|
||||
//Debug.Log($"Screen DPI: {Screen.dpi}, DPI Scale: {dpiScale}, Adjusted Turn Acceleration: {turnAccelerationDPI}");
|
||||
runtimeData.mouseSensitivity = turnAcceleration * dpiScale;
|
||||
|
||||
SetCursor(controlOptions.HasFlag(ControlOptions.MouseSteer));
|
||||
|
||||
@ -215,22 +277,22 @@ public override void OnStopAuthority()
|
||||
public override void OnStartLocalPlayer()
|
||||
{
|
||||
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);
|
||||
|
||||
controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
runtimeData.controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnStopLocalPlayer()
|
||||
{
|
||||
if (controllerUI != null)
|
||||
Destroy(controllerUI);
|
||||
controllerUI = null;
|
||||
if (runtimeData.controllerUI != null)
|
||||
Destroy(runtimeData.controllerUI);
|
||||
runtimeData.controllerUI = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -255,12 +317,12 @@ void Update()
|
||||
|
||||
// Reset ground state
|
||||
if (characterController.isGrounded)
|
||||
groundState = GroundState.Grounded;
|
||||
else if (groundState != GroundState.Jumping)
|
||||
groundState = GroundState.Falling;
|
||||
runtimeData.groundState = GroundState.Grounded;
|
||||
else if (runtimeData.groundState != GroundState.Jumping)
|
||||
runtimeData.groundState = GroundState.Falling;
|
||||
|
||||
// Diagnostic velocity...FloorToInt for display purposes
|
||||
velocity = Vector3Int.FloorToInt(characterController.velocity);
|
||||
runtimeData.velocity = Vector3Int.FloorToInt(characterController.velocity);
|
||||
}
|
||||
|
||||
void SetCursor(bool locked)
|
||||
@ -284,8 +346,8 @@ void HandleOptions()
|
||||
{
|
||||
controlOptions ^= ControlOptions.ShowUI;
|
||||
|
||||
if (controllerUI != null)
|
||||
controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
if (runtimeData.controllerUI != null)
|
||||
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 no turn input and AutoRun is enabled, maintain the previous turn speed
|
||||
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)
|
||||
{
|
||||
// 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
|
||||
mouseInputX = Mathf.Clamp(mouseInputX, -1f, 1f);
|
||||
runtimeData.mouseInputX = Mathf.Clamp(runtimeData.mouseInputX, -1f, 1f);
|
||||
|
||||
// Calculate target turn speed
|
||||
float targetTurnSpeed = mouseInputX * maxTurnSpeed;
|
||||
float targetTurnSpeed = runtimeData.mouseInputX * maxTurnSpeed;
|
||||
|
||||
// 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
|
||||
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)
|
||||
{
|
||||
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;
|
||||
jumpSpeed = initialJumpSpeed;
|
||||
runtimeData.groundState = GroundState.Jumping;
|
||||
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
|
||||
float jumpProgress = (jumpSpeed - initialJumpSpeed) / (maxJumpSpeed - initialJumpSpeed);
|
||||
jumpSpeed += (jumpAcceleration * Mathf.Sqrt(1 - jumpProgress)) * deltaTime;
|
||||
float jumpProgress = (runtimeData.jumpSpeed - initialJumpSpeed) / (maxJumpSpeed - initialJumpSpeed);
|
||||
runtimeData.jumpSpeed += (jumpAcceleration * Mathf.Sqrt(1 - jumpProgress)) * deltaTime;
|
||||
}
|
||||
|
||||
if (jumpSpeed >= maxJumpSpeed)
|
||||
if (runtimeData.jumpSpeed >= maxJumpSpeed)
|
||||
{
|
||||
jumpSpeed = maxJumpSpeed;
|
||||
groundState = GroundState.Falling;
|
||||
runtimeData.jumpSpeed = maxJumpSpeed;
|
||||
runtimeData.groundState = GroundState.Falling;
|
||||
}
|
||||
}
|
||||
else if (groundState != GroundState.Grounded)
|
||||
else if (runtimeData.groundState != GroundState.Grounded)
|
||||
{
|
||||
groundState = GroundState.Falling;
|
||||
jumpSpeed = Mathf.Min(jumpSpeed, maxJumpSpeed);
|
||||
jumpSpeed += Physics.gravity.y * deltaTime;
|
||||
runtimeData.groundState = GroundState.Falling;
|
||||
runtimeData.jumpSpeed = Mathf.Min(runtimeData.jumpSpeed, maxJumpSpeed);
|
||||
runtimeData.jumpSpeed += Physics.gravity.y * deltaTime;
|
||||
}
|
||||
else
|
||||
// maintain small downward speed for when falling off ledges
|
||||
jumpSpeed = Physics.gravity.y * deltaTime;
|
||||
runtimeData.jumpSpeed = Physics.gravity.y * deltaTime;
|
||||
}
|
||||
|
||||
void HandleMove(float deltaTime)
|
||||
@ -376,39 +438,40 @@ void HandleMove(float deltaTime)
|
||||
if (targetMoveX == 0f)
|
||||
{
|
||||
if (!controlOptions.HasFlag(ControlOptions.AutoRun))
|
||||
horizontal = Mathf.MoveTowards(horizontal, targetMoveX, inputGravity * deltaTime);
|
||||
runtimeData.horizontal = Mathf.MoveTowards(runtimeData.horizontal, targetMoveX, inputGravity * deltaTime);
|
||||
}
|
||||
else
|
||||
horizontal = Mathf.MoveTowards(horizontal, targetMoveX, inputSensitivity * deltaTime);
|
||||
runtimeData.horizontal = Mathf.MoveTowards(runtimeData.horizontal, targetMoveX, inputSensitivity * deltaTime);
|
||||
|
||||
if (targetMoveZ == 0f)
|
||||
{
|
||||
if (!controlOptions.HasFlag(ControlOptions.AutoRun))
|
||||
vertical = Mathf.MoveTowards(vertical, targetMoveZ, inputGravity * deltaTime);
|
||||
runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputGravity * deltaTime);
|
||||
}
|
||||
else
|
||||
vertical = Mathf.MoveTowards(vertical, targetMoveZ, inputSensitivity * deltaTime);
|
||||
runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputSensitivity * deltaTime);
|
||||
}
|
||||
|
||||
void ApplyMove(float deltaTime)
|
||||
{
|
||||
// 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.
|
||||
direction = Vector3.ClampMagnitude(direction, 1f);
|
||||
runtimeData.direction = Vector3.ClampMagnitude(runtimeData.direction, 1f);
|
||||
|
||||
// Transforms direction from local space to world space.
|
||||
direction = transform.TransformDirection(direction);
|
||||
runtimeData.direction = transform.TransformDirection(runtimeData.direction);
|
||||
|
||||
// Multiply for desired ground speed.
|
||||
direction *= maxMoveSpeed;
|
||||
runtimeData.direction *= maxMoveSpeed;
|
||||
|
||||
// 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.
|
||||
characterController.Move(direction * deltaTime);
|
||||
characterController.Move(runtimeData.direction * deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace Mirror.Examples.Common.Controllers.Tank
|
||||
[DisallowMultipleComponent]
|
||||
public class TankControllerBase : NetworkBehaviour
|
||||
{
|
||||
public enum GroundState : byte { Jumping, Falling, Grounded }
|
||||
public enum GroundState : byte { Grounded, Jumping, Falling }
|
||||
|
||||
[Serializable]
|
||||
public struct MoveKeys
|
||||
@ -90,32 +90,81 @@ public enum ControlOptions : byte
|
||||
[Tooltip("Rotation acceleration in degrees per second squared")]
|
||||
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")]
|
||||
[ReadOnly, SerializeField]
|
||||
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;
|
||||
public RuntimeData runtimeData;
|
||||
|
||||
#region Network Setup
|
||||
|
||||
@ -164,15 +213,13 @@ protected virtual void Reset()
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
horizontal = 0f;
|
||||
vertical = 0f;
|
||||
turnSpeed = 0f;
|
||||
runtimeData.horizontal = 0f;
|
||||
runtimeData.vertical = 0f;
|
||||
runtimeData.turnSpeed = 0f;
|
||||
}
|
||||
|
||||
public override void OnStartAuthority()
|
||||
{
|
||||
// capsuleCollider and characterController are mutually exclusive
|
||||
// Having both enabled would double fire triggers and other collisions
|
||||
characterController.enabled = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
@ -180,31 +227,28 @@ public override void OnStartAuthority()
|
||||
public override void OnStopAuthority()
|
||||
{
|
||||
this.enabled = false;
|
||||
|
||||
// capsuleCollider and characterController are mutually exclusive
|
||||
// Having both enabled would double fire triggers and other collisions
|
||||
characterController.enabled = false;
|
||||
}
|
||||
|
||||
public override void OnStartLocalPlayer()
|
||||
{
|
||||
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);
|
||||
|
||||
controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
runtimeData.controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnStopLocalPlayer()
|
||||
{
|
||||
if (controllerUI != null)
|
||||
Destroy(controllerUI);
|
||||
controllerUI = null;
|
||||
if (runtimeData.controllerUI != null)
|
||||
Destroy(runtimeData.controllerUI);
|
||||
runtimeData.controllerUI = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -223,12 +267,12 @@ void Update()
|
||||
|
||||
// Reset ground state
|
||||
if (characterController.isGrounded)
|
||||
groundState = GroundState.Grounded;
|
||||
else if (groundState != GroundState.Jumping)
|
||||
groundState = GroundState.Falling;
|
||||
runtimeData.groundState = GroundState.Grounded;
|
||||
else if (runtimeData.groundState != GroundState.Jumping)
|
||||
runtimeData.groundState = GroundState.Falling;
|
||||
|
||||
// Diagnostic velocity...FloorToInt for display purposes
|
||||
velocity = Vector3Int.FloorToInt(characterController.velocity);
|
||||
runtimeData.velocity = Vector3Int.FloorToInt(characterController.velocity);
|
||||
}
|
||||
|
||||
void HandleOptions()
|
||||
@ -240,8 +284,8 @@ void HandleOptions()
|
||||
{
|
||||
controlOptions ^= ControlOptions.ShowUI;
|
||||
|
||||
if (controllerUI != null)
|
||||
controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
if (runtimeData.controllerUI != null)
|
||||
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 no turn input and AutoRun is enabled, maintain the previous turn speed
|
||||
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)
|
||||
@ -277,39 +321,39 @@ void HandleMove(float deltaTime)
|
||||
if (targetMoveX == 0f)
|
||||
{
|
||||
if (!controlOptions.HasFlag(ControlOptions.AutoRun))
|
||||
horizontal = Mathf.MoveTowards(horizontal, targetMoveX, inputGravity * deltaTime);
|
||||
runtimeData.horizontal = Mathf.MoveTowards(runtimeData.horizontal, targetMoveX, inputGravity * deltaTime);
|
||||
}
|
||||
else
|
||||
horizontal = Mathf.MoveTowards(horizontal, targetMoveX, inputSensitivity * deltaTime);
|
||||
runtimeData.horizontal = Mathf.MoveTowards(runtimeData.horizontal, targetMoveX, inputSensitivity * deltaTime);
|
||||
|
||||
if (targetMoveZ == 0f)
|
||||
{
|
||||
if (!controlOptions.HasFlag(ControlOptions.AutoRun))
|
||||
vertical = Mathf.MoveTowards(vertical, targetMoveZ, inputGravity * deltaTime);
|
||||
runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputGravity * deltaTime);
|
||||
}
|
||||
else
|
||||
vertical = Mathf.MoveTowards(vertical, targetMoveZ, inputSensitivity * deltaTime);
|
||||
runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputSensitivity * deltaTime);
|
||||
}
|
||||
|
||||
void ApplyMove(float deltaTime)
|
||||
{
|
||||
// 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.
|
||||
direction = Vector3.ClampMagnitude(direction, 1f);
|
||||
runtimeData.direction = Vector3.ClampMagnitude(runtimeData.direction, 1f);
|
||||
|
||||
// Transforms direction from local space to world space.
|
||||
direction = transform.TransformDirection(direction);
|
||||
runtimeData.direction = transform.TransformDirection(runtimeData.direction);
|
||||
|
||||
// Multiply for desired ground speed.
|
||||
direction *= maxMoveSpeed;
|
||||
runtimeData.direction *= maxMoveSpeed;
|
||||
|
||||
// Add gravity in case we drove off a cliff.
|
||||
direction += Physics.gravity;
|
||||
runtimeData.direction += Physics.gravity;
|
||||
|
||||
// Finally move the character.
|
||||
characterController.Move(direction * deltaTime);
|
||||
characterController.Move(runtimeData.direction * deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ namespace Mirror.Examples.Common.Controllers.Tank
|
||||
[DisallowMultipleComponent]
|
||||
public class TankTurretBase : NetworkBehaviour
|
||||
{
|
||||
const float BASE_DPI = 96f;
|
||||
|
||||
[Serializable]
|
||||
public struct OptionsKeys
|
||||
{
|
||||
@ -31,8 +33,6 @@ public struct OtherKeys
|
||||
public KeyCode Shoot;
|
||||
}
|
||||
|
||||
const float BASE_DPI = 96f;
|
||||
|
||||
[Flags]
|
||||
public enum ControlOptions : byte
|
||||
{
|
||||
@ -98,7 +98,7 @@ public enum ControlOptions : byte
|
||||
[Range(0, 300f)]
|
||||
[Tooltip("Max Rotation in degrees per second")]
|
||||
public float maxTurretSpeed = 250f;
|
||||
[Range(0, 10f)]
|
||||
[Range(0, 30f)]
|
||||
[Tooltip("Rotation acceleration in degrees per second squared")]
|
||||
public float turretAcceleration = 10f;
|
||||
|
||||
@ -116,33 +116,69 @@ public enum ControlOptions : byte
|
||||
[Tooltip("Pitch acceleration in degrees per second squared")]
|
||||
public float pitchAcceleration = 3f;
|
||||
|
||||
[Header("Diagnostics")]
|
||||
[ReadOnly, SerializeField, Range(-1f, 1f)]
|
||||
float mouseInputX;
|
||||
[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)
|
||||
// Runtime data in a struct so it can be folded up in inspector
|
||||
[Serializable]
|
||||
public struct RuntimeData
|
||||
{
|
||||
if (cachedMaterial == null)
|
||||
cachedMaterial = playerObject.GetComponent<Renderer>().material;
|
||||
[ReadOnly, SerializeField, Range(-300f, 300f)] float _turretSpeed;
|
||||
[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;
|
||||
playerObject.SetActive(newColor != Color.black);
|
||||
#region Properties
|
||||
|
||||
public float mouseInputX
|
||||
{
|
||||
get => _mouseInputX;
|
||||
internal set => _mouseInputX = value;
|
||||
}
|
||||
|
||||
#region Unity Callbacks
|
||||
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
|
||||
}
|
||||
|
||||
[Header("Diagnostics")]
|
||||
public RuntimeData runtimeData;
|
||||
|
||||
#region Network Setup
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
@ -160,7 +196,7 @@ protected virtual void Reset()
|
||||
animator = GetComponentInChildren<Animator>();
|
||||
|
||||
// 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".
|
||||
// They will be several levels deep in the hierarchy.
|
||||
@ -215,6 +251,43 @@ Transform FindDeepChild(Transform aParent, string aName)
|
||||
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
|
||||
|
||||
void Update()
|
||||
@ -232,6 +305,15 @@ void Update()
|
||||
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)
|
||||
{
|
||||
Cursor.lockState = locked ? CursorLockMode.Locked : CursorLockMode.None;
|
||||
@ -253,8 +335,8 @@ void HandleOptions()
|
||||
{
|
||||
controlOptions ^= ControlOptions.ShowUI;
|
||||
|
||||
if (turretUI != null)
|
||||
turretUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
if (runtimeData.turretUI != null)
|
||||
runtimeData.turretUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,28 +350,28 @@ void HandleTurning(float deltaTime)
|
||||
if (moveKeys.TurnRight != KeyCode.None && Input.GetKey(moveKeys.TurnRight))
|
||||
targetTurnSpeed += maxTurretSpeed;
|
||||
|
||||
turretSpeed = Mathf.MoveTowards(turretSpeed, targetTurnSpeed, turretAcceleration * maxTurretSpeed * deltaTime);
|
||||
turret.Rotate(0f, turretSpeed * deltaTime, 0f);
|
||||
runtimeData.turretSpeed = Mathf.MoveTowards(runtimeData.turretSpeed, targetTurnSpeed, turretAcceleration * maxTurretSpeed * deltaTime);
|
||||
turret.Rotate(0f, runtimeData.turretSpeed * deltaTime, 0f);
|
||||
}
|
||||
|
||||
void HandleMouseTurret(float deltaTime)
|
||||
{
|
||||
// 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
|
||||
mouseInputX = Mathf.Clamp(mouseInputX, -1f, 1f);
|
||||
runtimeData.mouseInputX = Mathf.Clamp(runtimeData.mouseInputX, -1f, 1f);
|
||||
|
||||
// Calculate target turn speed
|
||||
float targetTurnSpeed = mouseInputX * maxTurretSpeed;
|
||||
float targetTurnSpeed = runtimeData.mouseInputX * maxTurretSpeed;
|
||||
|
||||
// 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
|
||||
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)
|
||||
@ -310,19 +392,19 @@ void HandlePitch(float deltaTime)
|
||||
inputDetected = true;
|
||||
}
|
||||
|
||||
pitchSpeed = Mathf.MoveTowards(pitchSpeed, targetPitchSpeed, pitchAcceleration * maxPitchSpeed * deltaTime);
|
||||
runtimeData.pitchSpeed = Mathf.MoveTowards(runtimeData.pitchSpeed, targetPitchSpeed, pitchAcceleration * maxPitchSpeed * deltaTime);
|
||||
|
||||
// Apply pitch rotation
|
||||
pitchAngle += pitchSpeed * deltaTime;
|
||||
pitchAngle = Mathf.Clamp(pitchAngle, -maxPitchUpAngle, maxPitchDownAngle);
|
||||
runtimeData.pitchAngle += runtimeData.pitchSpeed * deltaTime;
|
||||
runtimeData.pitchAngle = Mathf.Clamp(runtimeData.pitchAngle, -maxPitchUpAngle, maxPitchDownAngle);
|
||||
|
||||
// Return to -90 when no input
|
||||
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
|
||||
// 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
|
||||
@ -336,7 +418,7 @@ void HandleShooting()
|
||||
}
|
||||
}
|
||||
|
||||
bool CanShoot => NetworkTime.time >= lastShotTime + cooldownTime;
|
||||
bool CanShoot => NetworkTime.time >= runtimeData.lastShotTime + cooldownTime;
|
||||
|
||||
[Command]
|
||||
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.
|
||||
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
|
||||
// - Barrel (with Collider)
|
||||
// - BarrelEnd
|
||||
// - ProjectileMount
|
||||
// projectileMount.transform.parent.parent is the Barrel object with the Collider
|
||||
|
||||
if (isServerOnly)
|
||||
{
|
||||
@ -373,7 +455,7 @@ void DoShoot()
|
||||
Physics.IgnoreCollision(go.GetComponent<Collider>(), projectileMount.transform.parent.parent.GetComponent<Collider>());
|
||||
|
||||
// Update the last shot time
|
||||
lastShotTime = NetworkTime.time;
|
||||
runtimeData.lastShotTime = NetworkTime.time;
|
||||
}
|
||||
else if (isServer)
|
||||
{
|
||||
@ -383,7 +465,7 @@ void DoShoot()
|
||||
Physics.IgnoreCollision(go.GetComponent<Collider>(), projectileMount.transform.parent.parent.GetComponent<Collider>());
|
||||
|
||||
// Update the last shot time
|
||||
lastShotTime = NetworkTime.time;
|
||||
runtimeData.lastShotTime = NetworkTime.time;
|
||||
}
|
||||
|
||||
if (isClientOnly)
|
||||
@ -394,54 +476,10 @@ void DoShoot()
|
||||
Physics.IgnoreCollision(go.GetComponent<Collider>(), projectileMount.transform.parent.parent.GetComponent<Collider>());
|
||||
|
||||
// Update the last shot time
|
||||
lastShotTime = NetworkTime.time;
|
||||
runtimeData.lastShotTime = NetworkTime.time;
|
||||
}
|
||||
}
|
||||
|
||||
#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
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,8 @@ namespace Mirror.Examples.Common.Controllers.Tank
|
||||
public class TankTurretReliable : TankTurretBase
|
||||
{
|
||||
[Header("Components")]
|
||||
public NetworkTransformReliable turretNTR;
|
||||
public NetworkTransformReliable barrelNTR;
|
||||
public NetworkTransformReliable turretNetworkTransform;
|
||||
public NetworkTransformReliable barrelNetworkTransform;
|
||||
|
||||
protected override void Reset()
|
||||
{
|
||||
@ -22,41 +22,39 @@ protected override void Reset()
|
||||
|
||||
if (NTs.Length < 2)
|
||||
{
|
||||
turretNTR = gameObject.AddComponent<NetworkTransformReliable>();
|
||||
turretNTR.transform.SetSiblingIndex(NTs[0].transform.GetSiblingIndex() + 1);
|
||||
turretNetworkTransform = gameObject.AddComponent<NetworkTransformReliable>();
|
||||
turretNetworkTransform.transform.SetSiblingIndex(NTs[0].transform.GetSiblingIndex() + 1);
|
||||
NTs = GetComponents<NetworkTransformReliable>();
|
||||
}
|
||||
else
|
||||
turretNTR = NTs[1];
|
||||
turretNetworkTransform = NTs[1];
|
||||
|
||||
// Ensure SyncDirection is Client to Server
|
||||
turretNTR.syncDirection = SyncDirection.ClientToServer;
|
||||
turretNTR.syncPosition = false;
|
||||
// Ensure syncDirection is Client to Server
|
||||
turretNetworkTransform.syncDirection = SyncDirection.ClientToServer;
|
||||
|
||||
// Set SyncPosition to false because we only want to sync rotation
|
||||
//turretNTR.syncPosition = false;
|
||||
// Set syncPosition to false because we only want to sync rotation
|
||||
turretNetworkTransform.syncPosition = false;
|
||||
|
||||
if (base.turret != null)
|
||||
turretNTR.target = turret;
|
||||
turretNetworkTransform.target = turret;
|
||||
|
||||
if (NTs.Length < 3)
|
||||
{
|
||||
barrelNTR = gameObject.AddComponent<NetworkTransformReliable>();
|
||||
barrelNTR.transform.SetSiblingIndex(NTs[1].transform.GetSiblingIndex() + 1);
|
||||
barrelNetworkTransform = gameObject.AddComponent<NetworkTransformReliable>();
|
||||
barrelNetworkTransform.transform.SetSiblingIndex(NTs[1].transform.GetSiblingIndex() + 1);
|
||||
NTs = GetComponents<NetworkTransformReliable>();
|
||||
}
|
||||
else
|
||||
barrelNTR = NTs[2];
|
||||
barrelNetworkTransform = NTs[2];
|
||||
|
||||
// Ensure SyncDirection is Client to Server
|
||||
barrelNTR.syncDirection = SyncDirection.ClientToServer;
|
||||
barrelNTR.syncPosition = false;
|
||||
// Ensure syncDirection is Client to Server
|
||||
barrelNetworkTransform.syncDirection = SyncDirection.ClientToServer;
|
||||
|
||||
// Set SyncPosition to false because we only want to sync rotation
|
||||
//barrelNTR.syncPosition = false;
|
||||
// Set syncPosition to false because we only want to sync rotation
|
||||
barrelNetworkTransform.syncPosition = false;
|
||||
|
||||
if (barrel != null)
|
||||
barrelNTR.target = barrel;
|
||||
barrelNetworkTransform.target = barrel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,8 @@ namespace Mirror.Examples.Common.Controllers.Tank
|
||||
public class TankTurretUnreliable : TankTurretBase
|
||||
{
|
||||
[Header("Components")]
|
||||
public NetworkTransformUnreliable turretNTR;
|
||||
public NetworkTransformUnreliable barrelNTR;
|
||||
public NetworkTransformUnreliable turretNetworkTransform;
|
||||
public NetworkTransformUnreliable barrelNetworkTransform;
|
||||
|
||||
protected override void Reset()
|
||||
{
|
||||
@ -22,41 +22,39 @@ protected override void Reset()
|
||||
|
||||
if (NTs.Length < 2)
|
||||
{
|
||||
turretNTR = gameObject.AddComponent<NetworkTransformUnreliable>();
|
||||
turretNTR.transform.SetSiblingIndex(NTs[0].transform.GetSiblingIndex() + 1);
|
||||
turretNetworkTransform = gameObject.AddComponent<NetworkTransformUnreliable>();
|
||||
turretNetworkTransform.transform.SetSiblingIndex(NTs[0].transform.GetSiblingIndex() + 1);
|
||||
NTs = GetComponents<NetworkTransformUnreliable>();
|
||||
}
|
||||
else
|
||||
turretNTR = NTs[1];
|
||||
turretNetworkTransform = NTs[1];
|
||||
|
||||
// Ensure SyncDirection is Client to Server
|
||||
turretNTR.syncDirection = SyncDirection.ClientToServer;
|
||||
turretNTR.syncPosition = false;
|
||||
// Ensure syncDirection is Client to Server
|
||||
turretNetworkTransform.syncDirection = SyncDirection.ClientToServer;
|
||||
|
||||
// Set SyncPosition to false because we only want to sync rotation
|
||||
//turretNTR.syncPosition = false;
|
||||
// Set syncPosition to false because we only want to sync rotation
|
||||
turretNetworkTransform.syncPosition = false;
|
||||
|
||||
if (base.turret != null)
|
||||
turretNTR.target = turret;
|
||||
turretNetworkTransform.target = turret;
|
||||
|
||||
if (NTs.Length < 3)
|
||||
{
|
||||
barrelNTR = gameObject.AddComponent<NetworkTransformUnreliable>();
|
||||
barrelNTR.transform.SetSiblingIndex(NTs[1].transform.GetSiblingIndex() + 1);
|
||||
barrelNetworkTransform = gameObject.AddComponent<NetworkTransformUnreliable>();
|
||||
barrelNetworkTransform.transform.SetSiblingIndex(NTs[1].transform.GetSiblingIndex() + 1);
|
||||
NTs = GetComponents<NetworkTransformUnreliable>();
|
||||
}
|
||||
else
|
||||
barrelNTR = NTs[2];
|
||||
barrelNetworkTransform = NTs[2];
|
||||
|
||||
// Ensure SyncDirection is Client to Server
|
||||
barrelNTR.syncDirection = SyncDirection.ClientToServer;
|
||||
barrelNTR.syncPosition = false;
|
||||
// Ensure syncDirection is Client to Server
|
||||
barrelNetworkTransform.syncDirection = SyncDirection.ClientToServer;
|
||||
|
||||
// Set SyncPosition to false because we only want to sync rotation
|
||||
//barrelNTR.syncPosition = false;
|
||||
// Set syncPosition to false because we only want to sync rotation
|
||||
barrelNetworkTransform.syncPosition = false;
|
||||
|
||||
if (barrel != null)
|
||||
barrelNTR.target = barrel;
|
||||
barrelNetworkTransform.target = barrel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user