aha
This commit is contained in:
180
Assets/Mirror/Examples/_Common/Controllers/ControllerUIBase.cs
Normal file
180
Assets/Mirror/Examples/_Common/Controllers/ControllerUIBase.cs
Normal file
@ -0,0 +1,180 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers
|
||||
{
|
||||
[AddComponentMenu("")]
|
||||
[DisallowMultipleComponent]
|
||||
public class ControllerUIBase : MonoBehaviour
|
||||
{
|
||||
|
||||
// Returns a string representation of a KeyCode that is more suitable
|
||||
// for display in the UI than KeyCode.ToString() for "named" keys.
|
||||
internal string GetKeyText(KeyCode key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case KeyCode.None:
|
||||
return "";
|
||||
|
||||
case KeyCode.Escape:
|
||||
return "Esc";
|
||||
case KeyCode.BackQuote:
|
||||
return "`";
|
||||
case KeyCode.Tilde:
|
||||
return "~";
|
||||
|
||||
// number keys
|
||||
case KeyCode.Alpha1:
|
||||
return "1";
|
||||
case KeyCode.Alpha2:
|
||||
return "2";
|
||||
case KeyCode.Alpha3:
|
||||
return "3";
|
||||
case KeyCode.Alpha4:
|
||||
return "4";
|
||||
case KeyCode.Alpha5:
|
||||
return "5";
|
||||
case KeyCode.Alpha6:
|
||||
return "6";
|
||||
case KeyCode.Alpha7:
|
||||
return "7";
|
||||
case KeyCode.Alpha8:
|
||||
return "8";
|
||||
case KeyCode.Alpha9:
|
||||
return "9";
|
||||
case KeyCode.Alpha0:
|
||||
return "0";
|
||||
|
||||
// punctuation keys
|
||||
case KeyCode.Exclaim:
|
||||
return "!";
|
||||
case KeyCode.At:
|
||||
return "@";
|
||||
case KeyCode.Hash:
|
||||
return "#";
|
||||
case KeyCode.Dollar:
|
||||
return "$";
|
||||
case KeyCode.Percent:
|
||||
return "%";
|
||||
case KeyCode.Caret:
|
||||
return "^";
|
||||
case KeyCode.Ampersand:
|
||||
return "&";
|
||||
case KeyCode.Asterisk:
|
||||
return "*";
|
||||
case KeyCode.LeftParen:
|
||||
return "(";
|
||||
case KeyCode.RightParen:
|
||||
return ")";
|
||||
|
||||
case KeyCode.Minus:
|
||||
return "-";
|
||||
case KeyCode.Underscore:
|
||||
return "_";
|
||||
case KeyCode.Plus:
|
||||
return "+";
|
||||
case KeyCode.Equals:
|
||||
return "=";
|
||||
case KeyCode.Backspace:
|
||||
return "Back";
|
||||
|
||||
case KeyCode.LeftBracket:
|
||||
return "[";
|
||||
case KeyCode.LeftCurlyBracket:
|
||||
return "{";
|
||||
case KeyCode.RightBracket:
|
||||
return "]";
|
||||
case KeyCode.RightCurlyBracket:
|
||||
return "}";
|
||||
case KeyCode.Pipe:
|
||||
return "|";
|
||||
case KeyCode.Backslash:
|
||||
return "\\";
|
||||
|
||||
case KeyCode.Semicolon:
|
||||
return ";";
|
||||
case KeyCode.Colon:
|
||||
return ":";
|
||||
|
||||
case KeyCode.Quote:
|
||||
return "'";
|
||||
case KeyCode.DoubleQuote:
|
||||
return "\"";
|
||||
case KeyCode.Return:
|
||||
return "\u23CE";
|
||||
|
||||
case KeyCode.Comma:
|
||||
return ",";
|
||||
case KeyCode.Less:
|
||||
return "<";
|
||||
case KeyCode.Period:
|
||||
return ".";
|
||||
case KeyCode.Greater:
|
||||
return ">";
|
||||
case KeyCode.Slash:
|
||||
return "/";
|
||||
case KeyCode.Question:
|
||||
return "?";
|
||||
|
||||
// arrow keys
|
||||
case KeyCode.UpArrow:
|
||||
return "\u25B2";
|
||||
case KeyCode.LeftArrow:
|
||||
return "\u25C4";
|
||||
case KeyCode.DownArrow:
|
||||
return "\u25BC";
|
||||
case KeyCode.RightArrow:
|
||||
return "\u25BA";
|
||||
|
||||
// special keys
|
||||
case KeyCode.PageUp:
|
||||
return "Page\nUp";
|
||||
case KeyCode.PageDown:
|
||||
return "Page\nDown";
|
||||
case KeyCode.Insert:
|
||||
return "Ins";
|
||||
case KeyCode.Delete:
|
||||
return "Del";
|
||||
|
||||
// num pad keys
|
||||
case KeyCode.Keypad1:
|
||||
return "Pad\n1";
|
||||
case KeyCode.Keypad2:
|
||||
return "Pad\n2";
|
||||
case KeyCode.Keypad3:
|
||||
return "Pad\n3";
|
||||
case KeyCode.Keypad4:
|
||||
return "Pad\n4";
|
||||
case KeyCode.Keypad5:
|
||||
return "Pad\n5";
|
||||
case KeyCode.Keypad6:
|
||||
return "Pad\n6";
|
||||
case KeyCode.Keypad7:
|
||||
return "Pad\n7";
|
||||
case KeyCode.Keypad8:
|
||||
return "Pad\n8";
|
||||
case KeyCode.Keypad9:
|
||||
return "Pad\n9";
|
||||
case KeyCode.Keypad0:
|
||||
return "Pad\n0";
|
||||
case KeyCode.KeypadDivide:
|
||||
return "Pad\n/";
|
||||
case KeyCode.KeypadMultiply:
|
||||
return "Pad\n*";
|
||||
case KeyCode.KeypadMinus:
|
||||
return "Pad\n-";
|
||||
case KeyCode.KeypadPlus:
|
||||
return "Pad\n+";
|
||||
case KeyCode.KeypadEquals:
|
||||
return "Pad\n=";
|
||||
case KeyCode.KeypadPeriod:
|
||||
return "Pad\n.";
|
||||
case KeyCode.KeypadEnter:
|
||||
return "Pad\n\u23CE";
|
||||
|
||||
default:
|
||||
return key.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b8c728d262078147bf398cd80ff4b7d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/ControllerUIBase.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f7c2b0cb9aa8454a837825241e3bc0e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,575 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Flyer
|
||||
{
|
||||
[AddComponentMenu("")]
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
[RequireComponent(typeof(CapsuleCollider))]
|
||||
[RequireComponent(typeof(CharacterController))]
|
||||
[RequireComponent(typeof(NetworkIdentity))]
|
||||
[DisallowMultipleComponent]
|
||||
public class FlyerControllerBase : NetworkBehaviour
|
||||
{
|
||||
const float BASE_DPI = 96f;
|
||||
|
||||
[Serializable]
|
||||
public struct OptionsKeys
|
||||
{
|
||||
public KeyCode MouseSteer;
|
||||
public KeyCode AutoRun;
|
||||
public KeyCode ToggleUI;
|
||||
}
|
||||
|
||||
public enum GroundState : byte { Grounded, Jumping, Falling }
|
||||
|
||||
[Serializable]
|
||||
public struct MoveKeys
|
||||
{
|
||||
public KeyCode Forward;
|
||||
public KeyCode Back;
|
||||
public KeyCode StrafeLeft;
|
||||
public KeyCode StrafeRight;
|
||||
public KeyCode TurnLeft;
|
||||
public KeyCode TurnRight;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct FlightKeys
|
||||
{
|
||||
public KeyCode PitchDown;
|
||||
public KeyCode PitchUp;
|
||||
public KeyCode RollLeft;
|
||||
public KeyCode RollRight;
|
||||
public KeyCode AutoLevel;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ControlOptions : byte
|
||||
{
|
||||
None,
|
||||
MouseSteer = 1 << 0,
|
||||
AutoRun = 1 << 1,
|
||||
AutoLevel = 1 << 2,
|
||||
ShowUI = 1 << 3
|
||||
}
|
||||
|
||||
[Header("Avatar Components")]
|
||||
public CapsuleCollider capsuleCollider;
|
||||
public CharacterController characterController;
|
||||
|
||||
[Header("User Interface")]
|
||||
public GameObject ControllerUIPrefab;
|
||||
|
||||
[Header("Configuration")]
|
||||
[SerializeField]
|
||||
public MoveKeys moveKeys = new MoveKeys
|
||||
{
|
||||
Forward = KeyCode.W,
|
||||
Back = KeyCode.S,
|
||||
StrafeLeft = KeyCode.A,
|
||||
StrafeRight = KeyCode.D,
|
||||
TurnLeft = KeyCode.Q,
|
||||
TurnRight = KeyCode.E
|
||||
};
|
||||
|
||||
[SerializeField]
|
||||
public FlightKeys flightKeys = new FlightKeys
|
||||
{
|
||||
PitchDown = KeyCode.UpArrow,
|
||||
PitchUp = KeyCode.DownArrow,
|
||||
RollLeft = KeyCode.LeftArrow,
|
||||
RollRight = KeyCode.RightArrow,
|
||||
AutoLevel = KeyCode.L
|
||||
};
|
||||
|
||||
[SerializeField]
|
||||
public OptionsKeys optionsKeys = new OptionsKeys
|
||||
{
|
||||
MouseSteer = KeyCode.M,
|
||||
AutoRun = KeyCode.R,
|
||||
ToggleUI = KeyCode.U
|
||||
};
|
||||
|
||||
[Space(5)]
|
||||
public ControlOptions controlOptions = ControlOptions.AutoLevel | ControlOptions.ShowUI;
|
||||
|
||||
[Header("Movement")]
|
||||
[Range(0, 20)]
|
||||
[FormerlySerializedAs("moveSpeedMultiplier")]
|
||||
[Tooltip("Speed in meters per second")]
|
||||
public float maxMoveSpeed = 8f;
|
||||
|
||||
// Replacement for Sensitvity from Input Settings.
|
||||
[Range(0, 10f)]
|
||||
[Tooltip("Sensitivity factors into accelleration")]
|
||||
public float inputSensitivity = 2f;
|
||||
|
||||
// Replacement for Gravity from Input Settings.
|
||||
[Range(0, 10f)]
|
||||
[Tooltip("Gravity factors into decelleration")]
|
||||
public float inputGravity = 2f;
|
||||
|
||||
[Header("Turning")]
|
||||
[Range(0, 300f)]
|
||||
[Tooltip("Max Rotation in degrees per second")]
|
||||
public float maxTurnSpeed = 100f;
|
||||
[Range(0, 10f)]
|
||||
[FormerlySerializedAs("turnDelta")]
|
||||
[Tooltip("Rotation acceleration in degrees per second squared")]
|
||||
public float turnAcceleration = 3f;
|
||||
|
||||
[Header("Pitch")]
|
||||
[Range(0, 180f)]
|
||||
[Tooltip("Max Pitch in degrees per second")]
|
||||
public float maxPitchSpeed = 30f;
|
||||
[Range(0, 180f)]
|
||||
[Tooltip("Max Pitch in degrees")]
|
||||
public float maxPitchUpAngle = 20f;
|
||||
[Range(0, 180f)]
|
||||
[Tooltip("Max Pitch in degrees")]
|
||||
public float maxPitchDownAngle = 45f;
|
||||
[Range(0, 10f)]
|
||||
[Tooltip("Pitch acceleration in degrees per second squared")]
|
||||
public float pitchAcceleration = 3f;
|
||||
|
||||
[Header("Roll")]
|
||||
[Range(0, 180f)]
|
||||
[Tooltip("Max Roll in degrees per second")]
|
||||
public float maxRollSpeed = 30f;
|
||||
[Range(0, 180f)]
|
||||
[Tooltip("Max Roll in degrees")]
|
||||
public float maxRollAngle = 45f;
|
||||
[Range(0, 10f)]
|
||||
[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")]
|
||||
public RuntimeData runtimeData;
|
||||
|
||||
#region Network Setup
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
// Skip if Editor is in Play mode
|
||||
if (Application.isPlaying) return;
|
||||
|
||||
base.OnValidate();
|
||||
Reset();
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
if (capsuleCollider == null)
|
||||
capsuleCollider = GetComponent<CapsuleCollider>();
|
||||
|
||||
// Enable by default...it will be disabled when characterController is enabled
|
||||
capsuleCollider.enabled = true;
|
||||
|
||||
if (characterController == null)
|
||||
characterController = GetComponent<CharacterController>();
|
||||
|
||||
// Override CharacterController default values
|
||||
characterController.enabled = false;
|
||||
characterController.skinWidth = 0.02f;
|
||||
characterController.minMoveDistance = 0f;
|
||||
|
||||
GetComponent<Rigidbody>().isKinematic = true;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// For convenience in the examples, we use the GUID of the FlyerControllerUI
|
||||
// to find the correct prefab in the Mirror/Examples/_Common/Controllers folder.
|
||||
// This avoids conflicts with user-created prefabs that may have the same name
|
||||
// and avoids polluting the user's project with Resources.
|
||||
// This is not recommended for production code...use Resources.Load or AssetBundles instead.
|
||||
if (ControllerUIPrefab == null)
|
||||
{
|
||||
string path = UnityEditor.AssetDatabase.GUIDToAssetPath("493615025d304c144bacfb91f6aac90e");
|
||||
ControllerUIPrefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||
}
|
||||
#endif
|
||||
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
public override void OnStartAuthority()
|
||||
{
|
||||
// Calculate DPI-aware sensitivity
|
||||
float dpiScale = (Screen.dpi > 0) ? (Screen.dpi / BASE_DPI) : 1f;
|
||||
runtimeData.mouseSensitivity = turnAcceleration * dpiScale;
|
||||
|
||||
SetCursor(controlOptions.HasFlag(ControlOptions.MouseSteer));
|
||||
|
||||
// capsuleCollider and characterController are mutually exclusive
|
||||
// Having both enabled would double fire triggers and other collisions
|
||||
capsuleCollider.enabled = false;
|
||||
characterController.enabled = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public override void OnStopAuthority()
|
||||
{
|
||||
this.enabled = false;
|
||||
|
||||
// capsuleCollider and characterController are mutually exclusive
|
||||
// Having both enabled would double fire triggers and other collisions
|
||||
capsuleCollider.enabled = true;
|
||||
characterController.enabled = false;
|
||||
|
||||
SetCursor(false);
|
||||
}
|
||||
|
||||
public override void OnStartLocalPlayer()
|
||||
{
|
||||
if (ControllerUIPrefab != null)
|
||||
runtimeData.controllerUI = Instantiate(ControllerUIPrefab);
|
||||
|
||||
if (runtimeData.controllerUI != null)
|
||||
{
|
||||
if (runtimeData.controllerUI.TryGetComponent(out FlyerControllerUI canvasControlPanel))
|
||||
canvasControlPanel.Refresh(moveKeys, flightKeys, optionsKeys);
|
||||
|
||||
runtimeData.controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnStopLocalPlayer()
|
||||
{
|
||||
if (runtimeData.controllerUI != null)
|
||||
Destroy(runtimeData.controllerUI);
|
||||
runtimeData.controllerUI = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (!Application.isFocused)
|
||||
return;
|
||||
|
||||
if (!characterController.enabled)
|
||||
return;
|
||||
|
||||
float deltaTime = Time.deltaTime;
|
||||
|
||||
HandleOptions();
|
||||
|
||||
if (controlOptions.HasFlag(ControlOptions.MouseSteer))
|
||||
HandleMouseSteer(deltaTime);
|
||||
else
|
||||
HandleTurning(deltaTime);
|
||||
|
||||
HandlePitch(deltaTime);
|
||||
HandleRoll(deltaTime);
|
||||
HandleMove(deltaTime);
|
||||
ApplyMove(deltaTime);
|
||||
|
||||
// Reset ground state
|
||||
if (characterController.isGrounded)
|
||||
runtimeData.groundState = GroundState.Grounded;
|
||||
else if (runtimeData.groundState != GroundState.Jumping)
|
||||
runtimeData.groundState = GroundState.Falling;
|
||||
|
||||
// Diagnostic velocity...FloorToInt for display purposes
|
||||
runtimeData.velocity = Vector3Int.FloorToInt(characterController.velocity);
|
||||
}
|
||||
|
||||
void SetCursor(bool locked)
|
||||
{
|
||||
Cursor.lockState = locked ? CursorLockMode.Locked : CursorLockMode.None;
|
||||
Cursor.visible = !locked;
|
||||
}
|
||||
|
||||
void HandleOptions()
|
||||
{
|
||||
if (optionsKeys.MouseSteer != KeyCode.None && Input.GetKeyUp(optionsKeys.MouseSteer))
|
||||
{
|
||||
controlOptions ^= ControlOptions.MouseSteer;
|
||||
SetCursor(controlOptions.HasFlag(ControlOptions.MouseSteer));
|
||||
}
|
||||
|
||||
if (optionsKeys.AutoRun != KeyCode.None && Input.GetKeyUp(optionsKeys.AutoRun))
|
||||
controlOptions ^= ControlOptions.AutoRun;
|
||||
|
||||
if (optionsKeys.ToggleUI != KeyCode.None && Input.GetKeyUp(optionsKeys.ToggleUI))
|
||||
{
|
||||
controlOptions ^= ControlOptions.ShowUI;
|
||||
|
||||
if (runtimeData.controllerUI != null)
|
||||
runtimeData.controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
}
|
||||
|
||||
if (flightKeys.AutoLevel != KeyCode.None && Input.GetKeyUp(flightKeys.AutoLevel))
|
||||
controlOptions ^= ControlOptions.AutoLevel;
|
||||
}
|
||||
|
||||
// Turning works while airborne...feature?
|
||||
void HandleTurning(float deltaTime)
|
||||
{
|
||||
float targetTurnSpeed = 0f;
|
||||
|
||||
// Q and E cancel each other out, reducing targetTurnSpeed to zero.
|
||||
if (moveKeys.TurnLeft != KeyCode.None && Input.GetKey(moveKeys.TurnLeft))
|
||||
targetTurnSpeed -= maxTurnSpeed;
|
||||
if (moveKeys.TurnRight != KeyCode.None && Input.GetKey(moveKeys.TurnRight))
|
||||
targetTurnSpeed += maxTurnSpeed;
|
||||
|
||||
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
|
||||
runtimeData.mouseInputX += Input.GetAxisRaw("Mouse X") * runtimeData.mouseSensitivity;
|
||||
|
||||
// Clamp the accumulator to simulate key press behavior
|
||||
runtimeData.mouseInputX = Mathf.Clamp(runtimeData.mouseInputX, -1f, 1f);
|
||||
|
||||
// Calculate target turn speed
|
||||
float targetTurnSpeed = runtimeData.mouseInputX * maxTurnSpeed;
|
||||
|
||||
// Use the same acceleration logic as HandleTurning
|
||||
runtimeData.turnSpeed = Mathf.MoveTowards(runtimeData.turnSpeed, targetTurnSpeed, runtimeData.mouseSensitivity * maxTurnSpeed * deltaTime);
|
||||
|
||||
// Apply rotation
|
||||
transform.Rotate(0f, runtimeData.turnSpeed * deltaTime, 0f);
|
||||
|
||||
runtimeData.mouseInputX = Mathf.MoveTowards(runtimeData.mouseInputX, 0f, runtimeData.mouseSensitivity * deltaTime);
|
||||
}
|
||||
|
||||
void HandlePitch(float deltaTime)
|
||||
{
|
||||
float targetPitchSpeed = 0f;
|
||||
bool inputDetected = false;
|
||||
|
||||
// Up and Down arrows for pitch
|
||||
if (flightKeys.PitchUp != KeyCode.None && Input.GetKey(flightKeys.PitchUp))
|
||||
{
|
||||
targetPitchSpeed -= maxPitchSpeed;
|
||||
inputDetected = true;
|
||||
}
|
||||
|
||||
if (flightKeys.PitchDown != KeyCode.None && Input.GetKey(flightKeys.PitchDown))
|
||||
{
|
||||
targetPitchSpeed += maxPitchSpeed;
|
||||
inputDetected = true;
|
||||
}
|
||||
|
||||
runtimeData.pitchSpeed = Mathf.MoveTowards(runtimeData.pitchSpeed, targetPitchSpeed, pitchAcceleration * maxPitchSpeed * deltaTime);
|
||||
|
||||
// Apply pitch rotation
|
||||
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))
|
||||
runtimeData.pitchAngle = Mathf.MoveTowards(runtimeData.pitchAngle, 0f, maxPitchSpeed * deltaTime);
|
||||
|
||||
ApplyRotation();
|
||||
}
|
||||
|
||||
void HandleRoll(float deltaTime)
|
||||
{
|
||||
float targetRollSpeed = 0f;
|
||||
bool inputDetected = false;
|
||||
|
||||
// Left and Right arrows for roll
|
||||
if (flightKeys.RollRight != KeyCode.None && Input.GetKey(flightKeys.RollRight))
|
||||
{
|
||||
targetRollSpeed -= maxRollSpeed;
|
||||
inputDetected = true;
|
||||
}
|
||||
|
||||
if (flightKeys.RollLeft != KeyCode.None && Input.GetKey(flightKeys.RollLeft))
|
||||
{
|
||||
targetRollSpeed += maxRollSpeed;
|
||||
inputDetected = true;
|
||||
}
|
||||
|
||||
runtimeData.rollSpeed = Mathf.MoveTowards(runtimeData.rollSpeed, targetRollSpeed, rollAcceleration * maxRollSpeed * deltaTime);
|
||||
|
||||
// Apply roll rotation
|
||||
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))
|
||||
runtimeData.rollAngle = Mathf.MoveTowards(runtimeData.rollAngle, 0f, maxRollSpeed * deltaTime);
|
||||
|
||||
ApplyRotation();
|
||||
}
|
||||
|
||||
void ApplyRotation()
|
||||
{
|
||||
// Get the current yaw (Y-axis rotation)
|
||||
float currentYaw = transform.localRotation.eulerAngles.y;
|
||||
|
||||
// Apply all rotations
|
||||
transform.localRotation = Quaternion.Euler(runtimeData.pitchAngle, currentYaw, runtimeData.rollAngle);
|
||||
}
|
||||
|
||||
void HandleMove(float deltaTime)
|
||||
{
|
||||
// Initialize target movement variables
|
||||
float targetMoveX = 0f;
|
||||
float targetMoveZ = 0f;
|
||||
|
||||
// Check for WASD key presses and adjust target movement variables accordingly
|
||||
if (moveKeys.Forward != KeyCode.None && Input.GetKey(moveKeys.Forward)) targetMoveZ = 1f;
|
||||
if (moveKeys.Back != KeyCode.None && Input.GetKey(moveKeys.Back)) targetMoveZ = -1f;
|
||||
if (moveKeys.StrafeLeft != KeyCode.None && Input.GetKey(moveKeys.StrafeLeft)) targetMoveX = -1f;
|
||||
if (moveKeys.StrafeRight != KeyCode.None && Input.GetKey(moveKeys.StrafeRight)) targetMoveX = 1f;
|
||||
|
||||
if (targetMoveX == 0f)
|
||||
{
|
||||
if (!controlOptions.HasFlag(ControlOptions.AutoRun))
|
||||
runtimeData.horizontal = Mathf.MoveTowards(runtimeData.horizontal, targetMoveX, inputGravity * deltaTime);
|
||||
}
|
||||
else
|
||||
runtimeData.horizontal = Mathf.MoveTowards(runtimeData.horizontal, targetMoveX, inputSensitivity * deltaTime);
|
||||
|
||||
if (targetMoveZ == 0f)
|
||||
{
|
||||
if (!controlOptions.HasFlag(ControlOptions.AutoRun))
|
||||
runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputGravity * deltaTime);
|
||||
}
|
||||
else
|
||||
runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputSensitivity * deltaTime);
|
||||
}
|
||||
|
||||
void ApplyMove(float deltaTime)
|
||||
{
|
||||
// Create initial direction vector without jumpSpeed (y-axis).
|
||||
runtimeData.direction = new Vector3(runtimeData.horizontal, 0f, runtimeData.vertical);
|
||||
|
||||
// Clamp so diagonal strafing isn't a speed advantage.
|
||||
runtimeData.direction = Vector3.ClampMagnitude(runtimeData.direction, 1f);
|
||||
|
||||
// Transforms direction from local space to world space.
|
||||
runtimeData.direction = transform.TransformDirection(runtimeData.direction);
|
||||
|
||||
// Multiply for desired ground speed.
|
||||
runtimeData.direction *= maxMoveSpeed;
|
||||
|
||||
// Finally move the character.
|
||||
characterController.Move(runtimeData.direction * deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7616003f8b749c43943908c2daa4e5d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/FlyerController/FlyerControllerBase.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,8 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Flyer
|
||||
{
|
||||
[AddComponentMenu("Network/Flyer Controller (Reliable)")]
|
||||
[RequireComponent(typeof(NetworkTransformReliable))]
|
||||
public class FlyerControllerReliable : FlyerControllerBase { }
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea5cb2c21c7b53847b5d39d172f84080
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/FlyerController/FlyerControllerReliable.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Flyer
|
||||
{
|
||||
[AddComponentMenu("")]
|
||||
[DisallowMultipleComponent]
|
||||
public class FlyerControllerUI : ControllerUIBase
|
||||
{
|
||||
[Serializable]
|
||||
public struct MoveTexts
|
||||
{
|
||||
public Text keyTextTurnLeft;
|
||||
public Text keyTextForward;
|
||||
public Text keyTextTurnRight;
|
||||
public Text keyTextStrafeLeft;
|
||||
public Text keyTextBack;
|
||||
public Text keyTextStrafeRight;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct FlightTexts
|
||||
{
|
||||
public Text keyTextPitchDown;
|
||||
public Text keyTextPitchUp;
|
||||
public Text keyTextRollLeft;
|
||||
public Text keyTextRollRight;
|
||||
public Text keyTextAutoLevel;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct OptionsTexts
|
||||
{
|
||||
public Text keyTextMouseSteer;
|
||||
public Text keyTextAutoRun;
|
||||
public Text keyTextToggleUI;
|
||||
}
|
||||
|
||||
[SerializeField] MoveTexts moveTexts;
|
||||
[SerializeField] FlightTexts flightTexts;
|
||||
[SerializeField] OptionsTexts optionsTexts;
|
||||
|
||||
public void Refresh(FlyerControllerBase.MoveKeys moveKeys, FlyerControllerBase.FlightKeys flightKeys, FlyerControllerBase.OptionsKeys optionsKeys)
|
||||
{
|
||||
// Movement Keys
|
||||
moveTexts.keyTextTurnLeft.text = GetKeyText(moveKeys.TurnLeft);
|
||||
moveTexts.keyTextForward.text = GetKeyText(moveKeys.Forward);
|
||||
moveTexts.keyTextTurnRight.text = GetKeyText(moveKeys.TurnRight);
|
||||
moveTexts.keyTextStrafeLeft.text = GetKeyText(moveKeys.StrafeLeft);
|
||||
moveTexts.keyTextBack.text = GetKeyText(moveKeys.Back);
|
||||
moveTexts.keyTextStrafeRight.text = GetKeyText(moveKeys.StrafeRight);
|
||||
|
||||
// Flight Keys
|
||||
flightTexts.keyTextPitchDown.text = GetKeyText(flightKeys.PitchDown);
|
||||
flightTexts.keyTextPitchUp.text = GetKeyText(flightKeys.PitchUp);
|
||||
flightTexts.keyTextRollLeft.text = GetKeyText(flightKeys.RollLeft);
|
||||
flightTexts.keyTextRollRight.text = GetKeyText(flightKeys.RollRight);
|
||||
flightTexts.keyTextAutoLevel.text = GetKeyText(flightKeys.AutoLevel);
|
||||
|
||||
// Options Keys
|
||||
optionsTexts.keyTextMouseSteer.text = GetKeyText(optionsKeys.MouseSteer);
|
||||
optionsTexts.keyTextAutoRun.text = GetKeyText(optionsKeys.AutoRun);
|
||||
optionsTexts.keyTextToggleUI.text = GetKeyText(optionsKeys.ToggleUI);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 46a320327396d584faba2bffc73278ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/FlyerController/FlyerControllerUI.cs
|
||||
uploadId: 736421
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 493615025d304c144bacfb91f6aac90e
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/FlyerController/FlyerControllerUI.prefab
|
||||
uploadId: 736421
|
@ -0,0 +1,8 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Flyer
|
||||
{
|
||||
[AddComponentMenu("Network/Flyer Controller (Unreliable)")]
|
||||
[RequireComponent(typeof(NetworkTransformUnreliable))]
|
||||
public class FlyerControllerUnreliable : FlyerControllerBase { }
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: acd5d4cdfcd42424d9d1fae8f79611e8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/FlyerController/FlyerControllerUnreliable.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abc9f9284fe9a1a4ab4deac2e4b504ec
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,477 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Player
|
||||
{
|
||||
[AddComponentMenu("")]
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
[RequireComponent(typeof(CapsuleCollider))]
|
||||
[RequireComponent(typeof(CharacterController))]
|
||||
[RequireComponent(typeof(NetworkIdentity))]
|
||||
[DisallowMultipleComponent]
|
||||
public class PlayerControllerBase : NetworkBehaviour
|
||||
{
|
||||
const float BASE_DPI = 96f;
|
||||
|
||||
public enum GroundState : byte { Grounded, Jumping, Falling }
|
||||
|
||||
[Serializable]
|
||||
public struct MoveKeys
|
||||
{
|
||||
public KeyCode Forward;
|
||||
public KeyCode Back;
|
||||
public KeyCode StrafeLeft;
|
||||
public KeyCode StrafeRight;
|
||||
public KeyCode TurnLeft;
|
||||
public KeyCode TurnRight;
|
||||
public KeyCode Jump;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct OptionsKeys
|
||||
{
|
||||
public KeyCode MouseSteer;
|
||||
public KeyCode AutoRun;
|
||||
public KeyCode ToggleUI;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ControlOptions : byte
|
||||
{
|
||||
None,
|
||||
MouseSteer = 1 << 0,
|
||||
AutoRun = 1 << 1,
|
||||
ShowUI = 1 << 2
|
||||
}
|
||||
|
||||
[Header("Avatar Components")]
|
||||
public CharacterController characterController;
|
||||
|
||||
[Header("User Interface")]
|
||||
public GameObject ControllerUIPrefab;
|
||||
|
||||
[Header("Configuration")]
|
||||
[SerializeField]
|
||||
public MoveKeys moveKeys = new MoveKeys
|
||||
{
|
||||
Forward = KeyCode.W,
|
||||
Back = KeyCode.S,
|
||||
StrafeLeft = KeyCode.A,
|
||||
StrafeRight = KeyCode.D,
|
||||
TurnLeft = KeyCode.Q,
|
||||
TurnRight = KeyCode.E,
|
||||
Jump = KeyCode.Space,
|
||||
};
|
||||
|
||||
[SerializeField]
|
||||
public OptionsKeys optionsKeys = new OptionsKeys
|
||||
{
|
||||
MouseSteer = KeyCode.M,
|
||||
AutoRun = KeyCode.R,
|
||||
ToggleUI = KeyCode.U
|
||||
};
|
||||
|
||||
[Space(5)]
|
||||
public ControlOptions controlOptions = ControlOptions.ShowUI;
|
||||
|
||||
[Header("Movement")]
|
||||
[Range(0, 20)]
|
||||
[FormerlySerializedAs("moveSpeedMultiplier")]
|
||||
[Tooltip("Speed in meters per second")]
|
||||
public float maxMoveSpeed = 8f;
|
||||
|
||||
// Replacement for Sensitvity from Input Settings.
|
||||
[Range(0, 10f)]
|
||||
[Tooltip("Sensitivity factors into accelleration")]
|
||||
public float inputSensitivity = 2f;
|
||||
|
||||
// Replacement for Gravity from Input Settings.
|
||||
[Range(0, 10f)]
|
||||
[Tooltip("Gravity factors into decelleration")]
|
||||
public float inputGravity = 2f;
|
||||
|
||||
[Header("Turning")]
|
||||
[Range(0, 300f)]
|
||||
[Tooltip("Max Rotation in degrees per second")]
|
||||
public float maxTurnSpeed = 100f;
|
||||
[Range(0, 10f)]
|
||||
[FormerlySerializedAs("turnDelta")]
|
||||
[Tooltip("Rotation acceleration in degrees per second squared")]
|
||||
public float turnAcceleration = 3f;
|
||||
|
||||
[Header("Jumping")]
|
||||
[Range(0, 10f)]
|
||||
[Tooltip("Initial jump speed in meters per second")]
|
||||
public float initialJumpSpeed = 2.5f;
|
||||
[Range(0, 10f)]
|
||||
[Tooltip("Maximum jump speed in meters per second")]
|
||||
public float maxJumpSpeed = 3.5f;
|
||||
[Range(0, 10f)]
|
||||
[FormerlySerializedAs("jumpDelta")]
|
||||
[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")]
|
||||
public RuntimeData runtimeData;
|
||||
|
||||
#region Network Setup
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
// Skip if Editor is in Play mode
|
||||
if (Application.isPlaying) return;
|
||||
|
||||
base.OnValidate();
|
||||
Reset();
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
if (characterController == null)
|
||||
characterController = GetComponent<CharacterController>();
|
||||
|
||||
// Override CharacterController default values
|
||||
characterController.enabled = false;
|
||||
characterController.skinWidth = 0.02f;
|
||||
characterController.minMoveDistance = 0f;
|
||||
|
||||
GetComponent<Rigidbody>().isKinematic = true;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// For convenience in the examples, we use the GUID of the PlayerControllerUI
|
||||
// to find the correct prefab in the Mirror/Examples/_Common/Controllers folder.
|
||||
// This avoids conflicts with user-created prefabs that may have the same name
|
||||
// and avoids polluting the user's project with Resources.
|
||||
// This is not recommended for production code...use Resources.Load or AssetBundles instead.
|
||||
if (ControllerUIPrefab == null)
|
||||
{
|
||||
string path = UnityEditor.AssetDatabase.GUIDToAssetPath("7beee247444994f0281dadde274cc4af");
|
||||
ControllerUIPrefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||
}
|
||||
#endif
|
||||
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
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;
|
||||
runtimeData.mouseSensitivity = turnAcceleration * dpiScale;
|
||||
|
||||
SetCursor(controlOptions.HasFlag(ControlOptions.MouseSteer));
|
||||
|
||||
characterController.enabled = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public override void OnStopAuthority()
|
||||
{
|
||||
this.enabled = false;
|
||||
characterController.enabled = false;
|
||||
SetCursor(false);
|
||||
}
|
||||
|
||||
public override void OnStartLocalPlayer()
|
||||
{
|
||||
if (ControllerUIPrefab != null)
|
||||
runtimeData.controllerUI = Instantiate(ControllerUIPrefab);
|
||||
|
||||
if (runtimeData.controllerUI != null)
|
||||
{
|
||||
if (runtimeData.controllerUI.TryGetComponent(out PlayerControllerUI canvasControlPanel))
|
||||
canvasControlPanel.Refresh(moveKeys, optionsKeys);
|
||||
|
||||
runtimeData.controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnStopLocalPlayer()
|
||||
{
|
||||
if (runtimeData.controllerUI != null)
|
||||
Destroy(runtimeData.controllerUI);
|
||||
runtimeData.controllerUI = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (!characterController.enabled)
|
||||
return;
|
||||
|
||||
float deltaTime = Time.deltaTime;
|
||||
|
||||
HandleOptions();
|
||||
|
||||
if (controlOptions.HasFlag(ControlOptions.MouseSteer))
|
||||
HandleMouseSteer(deltaTime);
|
||||
else
|
||||
HandleTurning(deltaTime);
|
||||
|
||||
HandleJumping(deltaTime);
|
||||
HandleMove(deltaTime);
|
||||
ApplyMove(deltaTime);
|
||||
|
||||
// Reset ground state
|
||||
if (characterController.isGrounded)
|
||||
runtimeData.groundState = GroundState.Grounded;
|
||||
else if (runtimeData.groundState != GroundState.Jumping)
|
||||
runtimeData.groundState = GroundState.Falling;
|
||||
|
||||
// Diagnostic velocity...FloorToInt for display purposes
|
||||
runtimeData.velocity = Vector3Int.FloorToInt(characterController.velocity);
|
||||
}
|
||||
|
||||
void SetCursor(bool locked)
|
||||
{
|
||||
Cursor.lockState = locked ? CursorLockMode.Locked : CursorLockMode.None;
|
||||
Cursor.visible = !locked;
|
||||
}
|
||||
|
||||
void HandleOptions()
|
||||
{
|
||||
if (optionsKeys.MouseSteer != KeyCode.None && Input.GetKeyUp(optionsKeys.MouseSteer))
|
||||
{
|
||||
controlOptions ^= ControlOptions.MouseSteer;
|
||||
SetCursor(controlOptions.HasFlag(ControlOptions.MouseSteer));
|
||||
}
|
||||
|
||||
if (optionsKeys.AutoRun != KeyCode.None && Input.GetKeyUp(optionsKeys.AutoRun))
|
||||
controlOptions ^= ControlOptions.AutoRun;
|
||||
|
||||
if (optionsKeys.ToggleUI != KeyCode.None && Input.GetKeyUp(optionsKeys.ToggleUI))
|
||||
{
|
||||
controlOptions ^= ControlOptions.ShowUI;
|
||||
|
||||
if (runtimeData.controllerUI != null)
|
||||
runtimeData.controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
}
|
||||
}
|
||||
|
||||
// Turning works while airborne...feature?
|
||||
void HandleTurning(float deltaTime)
|
||||
{
|
||||
float targetTurnSpeed = 0f;
|
||||
|
||||
// TurnLeft and TurnRight cancel each other out, reducing targetTurnSpeed to zero.
|
||||
if (moveKeys.TurnLeft != KeyCode.None && Input.GetKey(moveKeys.TurnLeft))
|
||||
targetTurnSpeed -= maxTurnSpeed;
|
||||
if (moveKeys.TurnRight != KeyCode.None && Input.GetKey(moveKeys.TurnRight))
|
||||
targetTurnSpeed += maxTurnSpeed;
|
||||
|
||||
// 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))
|
||||
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
|
||||
runtimeData.mouseInputX += Input.GetAxisRaw("Mouse X") * runtimeData.mouseSensitivity;
|
||||
|
||||
// Clamp the accumulator to simulate key press behavior
|
||||
runtimeData.mouseInputX = Mathf.Clamp(runtimeData.mouseInputX, -1f, 1f);
|
||||
|
||||
// Calculate target turn speed
|
||||
float targetTurnSpeed = runtimeData.mouseInputX * maxTurnSpeed;
|
||||
|
||||
// Use the same acceleration logic as HandleTurning
|
||||
runtimeData.turnSpeed = Mathf.MoveTowards(runtimeData.turnSpeed, targetTurnSpeed, runtimeData.mouseSensitivity * maxTurnSpeed * deltaTime);
|
||||
|
||||
// Apply rotation
|
||||
transform.Rotate(0f, runtimeData.turnSpeed * deltaTime, 0f);
|
||||
|
||||
runtimeData.mouseInputX = Mathf.MoveTowards(runtimeData.mouseInputX, 0f, runtimeData.mouseSensitivity * deltaTime);
|
||||
}
|
||||
|
||||
void HandleJumping(float deltaTime)
|
||||
{
|
||||
if (runtimeData.groundState != GroundState.Falling && moveKeys.Jump != KeyCode.None && Input.GetKey(moveKeys.Jump))
|
||||
{
|
||||
if (runtimeData.groundState != GroundState.Jumping)
|
||||
{
|
||||
runtimeData.groundState = GroundState.Jumping;
|
||||
runtimeData.jumpSpeed = initialJumpSpeed;
|
||||
}
|
||||
else if (runtimeData.jumpSpeed < maxJumpSpeed)
|
||||
{
|
||||
// Increase jumpSpeed using a square root function for a fast start and slow finish
|
||||
float jumpProgress = (runtimeData.jumpSpeed - initialJumpSpeed) / (maxJumpSpeed - initialJumpSpeed);
|
||||
runtimeData.jumpSpeed += (jumpAcceleration * Mathf.Sqrt(1 - jumpProgress)) * deltaTime;
|
||||
}
|
||||
|
||||
if (runtimeData.jumpSpeed >= maxJumpSpeed)
|
||||
{
|
||||
runtimeData.jumpSpeed = maxJumpSpeed;
|
||||
runtimeData.groundState = GroundState.Falling;
|
||||
}
|
||||
}
|
||||
else if (runtimeData.groundState != GroundState.Grounded)
|
||||
{
|
||||
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
|
||||
runtimeData.jumpSpeed = Physics.gravity.y * deltaTime;
|
||||
}
|
||||
|
||||
void HandleMove(float deltaTime)
|
||||
{
|
||||
// Initialize target movement variables
|
||||
float targetMoveX = 0f;
|
||||
float targetMoveZ = 0f;
|
||||
|
||||
// Check for WASD key presses and adjust target movement variables accordingly
|
||||
if (moveKeys.Forward != KeyCode.None && Input.GetKey(moveKeys.Forward)) targetMoveZ = 1f;
|
||||
if (moveKeys.Back != KeyCode.None && Input.GetKey(moveKeys.Back)) targetMoveZ = -1f;
|
||||
if (moveKeys.StrafeLeft != KeyCode.None && Input.GetKey(moveKeys.StrafeLeft)) targetMoveX = -1f;
|
||||
if (moveKeys.StrafeRight != KeyCode.None && Input.GetKey(moveKeys.StrafeRight)) targetMoveX = 1f;
|
||||
|
||||
if (targetMoveX == 0f)
|
||||
{
|
||||
if (!controlOptions.HasFlag(ControlOptions.AutoRun))
|
||||
runtimeData.horizontal = Mathf.MoveTowards(runtimeData.horizontal, targetMoveX, inputGravity * deltaTime);
|
||||
}
|
||||
else
|
||||
runtimeData.horizontal = Mathf.MoveTowards(runtimeData.horizontal, targetMoveX, inputSensitivity * deltaTime);
|
||||
|
||||
if (targetMoveZ == 0f)
|
||||
{
|
||||
if (!controlOptions.HasFlag(ControlOptions.AutoRun))
|
||||
runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputGravity * deltaTime);
|
||||
}
|
||||
else
|
||||
runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputSensitivity * deltaTime);
|
||||
}
|
||||
|
||||
void ApplyMove(float deltaTime)
|
||||
{
|
||||
// Create initial direction vector without jumpSpeed (y-axis).
|
||||
runtimeData.direction = new Vector3(runtimeData.horizontal, 0f, runtimeData.vertical);
|
||||
|
||||
// Clamp so diagonal strafing isn't a speed advantage.
|
||||
runtimeData.direction = Vector3.ClampMagnitude(runtimeData.direction, 1f);
|
||||
|
||||
// Transforms direction from local space to world space.
|
||||
runtimeData.direction = transform.TransformDirection(runtimeData.direction);
|
||||
|
||||
// Multiply for desired ground speed.
|
||||
runtimeData.direction *= maxMoveSpeed;
|
||||
|
||||
// Add jumpSpeed to direction as last step.
|
||||
//runtimeData.direction.y = runtimeData.jumpSpeed;
|
||||
runtimeData.direction = new Vector3(runtimeData.direction.x, runtimeData.jumpSpeed, runtimeData.direction.z);
|
||||
|
||||
// Finally move the character.
|
||||
characterController.Move(runtimeData.direction * deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 22ac0cecaad969041bfd6a297fc5f2cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/PlayerController/PlayerControllerBase.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,8 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Player
|
||||
{
|
||||
[AddComponentMenu("Network/Player Controller (Hybrid)")]
|
||||
[RequireComponent(typeof(NetworkTransformHybrid))]
|
||||
public class PlayerControllerHybrid : PlayerControllerBase { }
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 442f181dd98a15148978a961afda8f94
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/PlayerController/PlayerControllerHybrid.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,8 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Player
|
||||
{
|
||||
[AddComponentMenu("Network/Player Controller (Reliable)")]
|
||||
[RequireComponent(typeof(NetworkTransformReliable))]
|
||||
public class PlayerControllerReliable : PlayerControllerBase { }
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 497221e839119e34b897d6c497cbc8e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/PlayerController/PlayerControllerReliable.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Player
|
||||
{
|
||||
[AddComponentMenu("")]
|
||||
[DisallowMultipleComponent]
|
||||
public class PlayerControllerUI : ControllerUIBase
|
||||
{
|
||||
[Serializable]
|
||||
public struct MoveTexts
|
||||
{
|
||||
public Text keyTextTurnLeft;
|
||||
public Text keyTextForward;
|
||||
public Text keyTextTurnRight;
|
||||
public Text keyTextStrafeLeft;
|
||||
public Text keyTextBack;
|
||||
public Text keyTextStrafeRight;
|
||||
public Text keyTextJump;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct OptionsTexts
|
||||
{
|
||||
public Text keyTextMouseSteer;
|
||||
public Text keyTextAutoRun;
|
||||
public Text keyTextToggleUI;
|
||||
}
|
||||
|
||||
[SerializeField] MoveTexts moveTexts;
|
||||
[SerializeField] OptionsTexts optionsTexts;
|
||||
|
||||
public void Refresh(PlayerControllerBase.MoveKeys moveKeys, PlayerControllerBase.OptionsKeys optionsKeys)
|
||||
{
|
||||
// Movement Keys
|
||||
moveTexts.keyTextTurnLeft.text = GetKeyText(moveKeys.TurnLeft);
|
||||
moveTexts.keyTextForward.text = GetKeyText(moveKeys.Forward);
|
||||
moveTexts.keyTextTurnRight.text = GetKeyText(moveKeys.TurnRight);
|
||||
moveTexts.keyTextStrafeLeft.text = GetKeyText(moveKeys.StrafeLeft);
|
||||
moveTexts.keyTextBack.text = GetKeyText(moveKeys.Back);
|
||||
moveTexts.keyTextStrafeRight.text = GetKeyText(moveKeys.StrafeRight);
|
||||
moveTexts.keyTextJump.text = GetKeyText(moveKeys.Jump);
|
||||
|
||||
// Options Keys
|
||||
optionsTexts.keyTextMouseSteer.text = GetKeyText(optionsKeys.MouseSteer);
|
||||
optionsTexts.keyTextAutoRun.text = GetKeyText(optionsKeys.AutoRun);
|
||||
optionsTexts.keyTextToggleUI.text = GetKeyText(optionsKeys.ToggleUI);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5548caf5abf51449f98ab54971eeff29
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/PlayerController/PlayerControllerUI.cs
|
||||
uploadId: 736421
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7beee247444994f0281dadde274cc4af
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/PlayerController/PlayerControllerUI.prefab
|
||||
uploadId: 736421
|
@ -0,0 +1,8 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Player
|
||||
{
|
||||
[AddComponentMenu("Network/Player Controller (Unreliable)")]
|
||||
[RequireComponent(typeof(NetworkTransformUnreliable))]
|
||||
public class PlayerControllerUnreliable : PlayerControllerBase { }
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f81b59082839c2e40938767457bb91ae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/PlayerController/PlayerControllerUnreliable.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42188a14ef528ec489da28afb66db6fa
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,511 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Player
|
||||
{
|
||||
[AddComponentMenu("")]
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
[RequireComponent(typeof(CapsuleCollider))]
|
||||
[RequireComponent(typeof(NetworkIdentity))]
|
||||
[DisallowMultipleComponent]
|
||||
public class PlayerControllerRBBase : NetworkBehaviour
|
||||
{
|
||||
const float BASE_DPI = 96f;
|
||||
|
||||
public enum GroundState : byte { Grounded, Jumping, Falling }
|
||||
|
||||
[Serializable]
|
||||
public struct MoveKeys
|
||||
{
|
||||
public KeyCode Forward;
|
||||
public KeyCode Back;
|
||||
public KeyCode StrafeLeft;
|
||||
public KeyCode StrafeRight;
|
||||
public KeyCode TurnLeft;
|
||||
public KeyCode TurnRight;
|
||||
public KeyCode Jump;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct OptionsKeys
|
||||
{
|
||||
public KeyCode MouseSteer;
|
||||
public KeyCode AutoRun;
|
||||
public KeyCode ToggleUI;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ControlOptions : byte
|
||||
{
|
||||
None,
|
||||
MouseSteer = 1 << 0,
|
||||
AutoRun = 1 << 1,
|
||||
ShowUI = 1 << 2
|
||||
}
|
||||
|
||||
[Header("Avatar Components")]
|
||||
public Rigidbody rigidBody;
|
||||
public CapsuleCollider capsuleCollider;
|
||||
|
||||
[Header("User Interface")]
|
||||
public GameObject ControllerUIPrefab;
|
||||
|
||||
[Header("Configuration")]
|
||||
[SerializeField]
|
||||
public MoveKeys moveKeys = new MoveKeys
|
||||
{
|
||||
Forward = KeyCode.W,
|
||||
Back = KeyCode.S,
|
||||
StrafeLeft = KeyCode.A,
|
||||
StrafeRight = KeyCode.D,
|
||||
TurnLeft = KeyCode.Q,
|
||||
TurnRight = KeyCode.E,
|
||||
Jump = KeyCode.Space,
|
||||
};
|
||||
|
||||
[SerializeField]
|
||||
public OptionsKeys optionsKeys = new OptionsKeys
|
||||
{
|
||||
MouseSteer = KeyCode.M,
|
||||
AutoRun = KeyCode.R,
|
||||
ToggleUI = KeyCode.U
|
||||
};
|
||||
|
||||
[Space(5)]
|
||||
public ControlOptions controlOptions = ControlOptions.ShowUI;
|
||||
|
||||
[Header("Movement")]
|
||||
[Range(0, 20)]
|
||||
[FormerlySerializedAs("moveSpeedMultiplier")]
|
||||
[Tooltip("Speed in meters per second")]
|
||||
public float maxMoveSpeed = 8f;
|
||||
|
||||
// Replacement for Sensitvity from Input Settings.
|
||||
[Range(0, 10f)]
|
||||
[Tooltip("Sensitivity factors into accelleration")]
|
||||
public float inputSensitivity = 2f;
|
||||
|
||||
// Replacement for Gravity from Input Settings.
|
||||
[Range(0, 10f)]
|
||||
[Tooltip("Gravity factors into decelleration")]
|
||||
public float inputGravity = 2f;
|
||||
|
||||
[Header("Turning")]
|
||||
[Range(0, 300f)]
|
||||
[Tooltip("Max Rotation in degrees per second")]
|
||||
public float maxTurnSpeed = 100f;
|
||||
[Range(0, 10f)]
|
||||
[FormerlySerializedAs("turnDelta")]
|
||||
[Tooltip("Rotation acceleration in degrees per second squared")]
|
||||
public float turnAcceleration = 3f;
|
||||
|
||||
[Header("Jumping")]
|
||||
[Range(0, 10f)]
|
||||
[Tooltip("Initial jump speed in meters per second")]
|
||||
public float initialJumpSpeed = 2.5f;
|
||||
[Range(0, 10f)]
|
||||
[Tooltip("Maximum jump speed in meters per second")]
|
||||
public float maxJumpSpeed = 3.5f;
|
||||
[Range(0, 10f)]
|
||||
[FormerlySerializedAs("jumpDelta")]
|
||||
[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")]
|
||||
public RuntimeData runtimeData;
|
||||
|
||||
#region Network Setup
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
// Skip if Editor is in Play mode
|
||||
if (Application.isPlaying) return;
|
||||
|
||||
base.OnValidate();
|
||||
Reset();
|
||||
}
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
if (rigidBody == null)
|
||||
rigidBody = GetComponent<Rigidbody>();
|
||||
if (capsuleCollider == null)
|
||||
capsuleCollider = GetComponent<CapsuleCollider>();
|
||||
|
||||
// Configure Rigidbody
|
||||
rigidBody.useGravity = true;
|
||||
rigidBody.interpolation = RigidbodyInterpolation.None;
|
||||
rigidBody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
|
||||
rigidBody.isKinematic = true;
|
||||
|
||||
// Freeze rotation on X and Z axes, but allow rotation on Y axis
|
||||
rigidBody.constraints = RigidbodyConstraints.FreezeRotation;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// For convenience in the examples, we use the GUID of the PlayerControllerUI
|
||||
// to find the correct prefab in the Mirror/Examples/_Common/Controllers folder.
|
||||
// This avoids conflicts with user-created prefabs that may have the same name
|
||||
// and avoids polluting the user's project with Resources.
|
||||
// This is not recommended for production code...use Resources.Load or AssetBundles instead.
|
||||
if (ControllerUIPrefab == null)
|
||||
{
|
||||
string path = UnityEditor.AssetDatabase.GUIDToAssetPath("5caaf0d5754a64f4080f0c8b55c0b03d");
|
||||
ControllerUIPrefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||
}
|
||||
#endif
|
||||
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
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;
|
||||
runtimeData.mouseSensitivity = turnAcceleration * dpiScale;
|
||||
|
||||
SetCursor(controlOptions.HasFlag(ControlOptions.MouseSteer));
|
||||
|
||||
rigidBody.isKinematic = false;
|
||||
rigidBody.collisionDetectionMode = CollisionDetectionMode.Continuous;
|
||||
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public override void OnStopAuthority()
|
||||
{
|
||||
this.enabled = false;
|
||||
|
||||
rigidBody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
|
||||
rigidBody.isKinematic = true;
|
||||
|
||||
SetCursor(false);
|
||||
}
|
||||
|
||||
public override void OnStartLocalPlayer()
|
||||
{
|
||||
if (ControllerUIPrefab != null)
|
||||
runtimeData.controllerUI = Instantiate(ControllerUIPrefab);
|
||||
|
||||
if (runtimeData.controllerUI != null)
|
||||
{
|
||||
if (runtimeData.controllerUI.TryGetComponent(out PlayerControllerRBUI canvasControlPanel))
|
||||
canvasControlPanel.Refresh(moveKeys, optionsKeys);
|
||||
|
||||
runtimeData.controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnStopLocalPlayer()
|
||||
{
|
||||
if (runtimeData.controllerUI != null)
|
||||
Destroy(runtimeData.controllerUI);
|
||||
|
||||
runtimeData.controllerUI = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
void Start()
|
||||
{
|
||||
Application.targetFrameRate = NetworkManager.singleton.sendRate;
|
||||
Time.fixedDeltaTime = 1f / NetworkManager.singleton.sendRate;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
HandleOptions();
|
||||
|
||||
float deltaTime = Time.deltaTime;
|
||||
|
||||
if (controlOptions.HasFlag(ControlOptions.MouseSteer))
|
||||
HandleMouseSteer(deltaTime);
|
||||
else
|
||||
HandleTurning(deltaTime);
|
||||
|
||||
HandleJumping(deltaTime);
|
||||
HandleMove(deltaTime);
|
||||
}
|
||||
|
||||
void FixedUpdate()
|
||||
{
|
||||
float fixedDeltaTime = Time.fixedDeltaTime;
|
||||
ApplyMove(fixedDeltaTime);
|
||||
|
||||
// Update ground state
|
||||
bool isGrounded = Physics.Raycast(transform.position, Vector3.down, capsuleCollider.height / 2 + 0.1f);
|
||||
if (isGrounded)
|
||||
runtimeData.groundState = GroundState.Grounded;
|
||||
else if (runtimeData.groundState != GroundState.Jumping)
|
||||
runtimeData.groundState = GroundState.Falling;
|
||||
|
||||
// Update velocity for diagnostics
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
runtimeData.velocity = Vector3Int.FloorToInt(rigidBody.linearVelocity);
|
||||
#else
|
||||
runtimeData.velocity = Vector3Int.FloorToInt(rigidBody.velocity);
|
||||
#endif
|
||||
}
|
||||
|
||||
void HandleOptions()
|
||||
{
|
||||
if (optionsKeys.MouseSteer != KeyCode.None && Input.GetKeyUp(optionsKeys.MouseSteer))
|
||||
{
|
||||
controlOptions ^= ControlOptions.MouseSteer;
|
||||
SetCursor(controlOptions.HasFlag(ControlOptions.MouseSteer));
|
||||
}
|
||||
|
||||
if (optionsKeys.AutoRun != KeyCode.None && Input.GetKeyUp(optionsKeys.AutoRun))
|
||||
controlOptions ^= ControlOptions.AutoRun;
|
||||
|
||||
if (optionsKeys.ToggleUI != KeyCode.None && Input.GetKeyUp(optionsKeys.ToggleUI))
|
||||
{
|
||||
controlOptions ^= ControlOptions.ShowUI;
|
||||
|
||||
if (runtimeData.controllerUI != null)
|
||||
runtimeData.controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
}
|
||||
}
|
||||
|
||||
void SetCursor(bool locked)
|
||||
{
|
||||
Cursor.lockState = locked ? CursorLockMode.Locked : CursorLockMode.None;
|
||||
Cursor.visible = !locked;
|
||||
}
|
||||
|
||||
// Turning works while airborne...feature?
|
||||
void HandleTurning(float deltaTime)
|
||||
{
|
||||
float targetTurnSpeed = 0f;
|
||||
|
||||
// TurnLeft and TurnRight cancel each other out, reducing targetTurnSpeed to zero.
|
||||
if (moveKeys.TurnLeft != KeyCode.None && Input.GetKey(moveKeys.TurnLeft))
|
||||
targetTurnSpeed -= maxTurnSpeed;
|
||||
if (moveKeys.TurnRight != KeyCode.None && Input.GetKey(moveKeys.TurnRight))
|
||||
targetTurnSpeed += maxTurnSpeed;
|
||||
|
||||
// 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))
|
||||
runtimeData.turnSpeed = Mathf.MoveTowards(runtimeData.turnSpeed, targetTurnSpeed, turnAcceleration * maxTurnSpeed * deltaTime);
|
||||
|
||||
//transform.Rotate(0f, runtimeData.turnSpeed * fixedDeltaTime, 0f);
|
||||
transform.Rotate(transform.up, runtimeData.turnSpeed * deltaTime, Space.World);
|
||||
}
|
||||
|
||||
void HandleMouseSteer(float deltaTime)
|
||||
{
|
||||
// Accumulate mouse input over time
|
||||
runtimeData.mouseInputX += Input.GetAxisRaw("Mouse X") * runtimeData.mouseSensitivity;
|
||||
|
||||
// Clamp the accumulator to simulate key press behavior
|
||||
runtimeData.mouseInputX = Mathf.Clamp(runtimeData.mouseInputX, -1f, 1f);
|
||||
|
||||
// Calculate target turn speed
|
||||
float targetTurnSpeed = runtimeData.mouseInputX * maxTurnSpeed;
|
||||
|
||||
// Use the same acceleration logic as HandleTurning
|
||||
runtimeData.turnSpeed = Mathf.MoveTowards(runtimeData.turnSpeed, targetTurnSpeed, runtimeData.mouseSensitivity * maxTurnSpeed * deltaTime);
|
||||
|
||||
// Apply rotation
|
||||
//transform.Rotate(0f, runtimeData.turnSpeed * fixedDeltaTime, 0f);
|
||||
transform.Rotate(transform.up, runtimeData.turnSpeed * deltaTime, Space.World);
|
||||
|
||||
runtimeData.mouseInputX = Mathf.MoveTowards(runtimeData.mouseInputX, 0f, runtimeData.mouseSensitivity * deltaTime);
|
||||
}
|
||||
|
||||
void HandleJumping(float deltaTime)
|
||||
{
|
||||
if (runtimeData.groundState != GroundState.Falling && moveKeys.Jump != KeyCode.None && Input.GetKey(moveKeys.Jump))
|
||||
{
|
||||
if (runtimeData.groundState != GroundState.Jumping)
|
||||
{
|
||||
runtimeData.groundState = GroundState.Jumping;
|
||||
runtimeData.jumpSpeed = initialJumpSpeed;
|
||||
}
|
||||
else if (runtimeData.jumpSpeed < maxJumpSpeed)
|
||||
{
|
||||
// Increase jumpSpeed using a square root function for a fast start and slow finish
|
||||
float jumpProgress = (runtimeData.jumpSpeed - initialJumpSpeed) / (maxJumpSpeed - initialJumpSpeed);
|
||||
runtimeData.jumpSpeed += (jumpAcceleration * Mathf.Sqrt(1 - jumpProgress)) * deltaTime;
|
||||
}
|
||||
|
||||
if (runtimeData.jumpSpeed >= maxJumpSpeed)
|
||||
{
|
||||
runtimeData.jumpSpeed = maxJumpSpeed;
|
||||
runtimeData.groundState = GroundState.Falling;
|
||||
}
|
||||
}
|
||||
else if (runtimeData.groundState != GroundState.Grounded)
|
||||
{
|
||||
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
|
||||
runtimeData.jumpSpeed = Physics.gravity.y * deltaTime;
|
||||
}
|
||||
|
||||
void HandleMove(float deltaTime)
|
||||
{
|
||||
// Initialize target movement variables
|
||||
float targetMoveX = 0f;
|
||||
float targetMoveZ = 0f;
|
||||
|
||||
// Check for WASD key presses and adjust target movement variables accordingly
|
||||
if (moveKeys.Forward != KeyCode.None && Input.GetKey(moveKeys.Forward)) targetMoveZ = 1f;
|
||||
if (moveKeys.Back != KeyCode.None && Input.GetKey(moveKeys.Back)) targetMoveZ = -1f;
|
||||
if (moveKeys.StrafeLeft != KeyCode.None && Input.GetKey(moveKeys.StrafeLeft)) targetMoveX = -1f;
|
||||
if (moveKeys.StrafeRight != KeyCode.None && Input.GetKey(moveKeys.StrafeRight)) targetMoveX = 1f;
|
||||
|
||||
if (targetMoveX == 0f)
|
||||
{
|
||||
if (!controlOptions.HasFlag(ControlOptions.AutoRun))
|
||||
runtimeData.horizontal = Mathf.MoveTowards(runtimeData.horizontal, targetMoveX, inputGravity * deltaTime);
|
||||
}
|
||||
else
|
||||
runtimeData.horizontal = Mathf.MoveTowards(runtimeData.horizontal, targetMoveX, inputSensitivity * deltaTime);
|
||||
|
||||
if (targetMoveZ == 0f)
|
||||
{
|
||||
if (!controlOptions.HasFlag(ControlOptions.AutoRun))
|
||||
runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputGravity * deltaTime);
|
||||
}
|
||||
else
|
||||
runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputSensitivity * deltaTime);
|
||||
}
|
||||
|
||||
void ApplyMove(float fixedDeltaTime)
|
||||
{
|
||||
// Handle horizontal movement
|
||||
runtimeData.direction = new Vector3(runtimeData.horizontal, 0f, runtimeData.vertical);
|
||||
runtimeData.direction = Vector3.ClampMagnitude(runtimeData.direction, 1f);
|
||||
runtimeData.direction = transform.TransformDirection(runtimeData.direction);
|
||||
runtimeData.direction *= maxMoveSpeed;
|
||||
|
||||
// Apply horizontal movement
|
||||
rigidBody.MovePosition(rigidBody.position + runtimeData.direction * fixedDeltaTime);
|
||||
|
||||
// Handle vertical movement (jumping and gravity)
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
Vector3 verticalMovement = rigidBody.linearVelocity;
|
||||
#else
|
||||
Vector3 verticalMovement = rigidBody.velocity;
|
||||
#endif
|
||||
verticalMovement.y = runtimeData.jumpSpeed;
|
||||
|
||||
// Apply gravity
|
||||
if (runtimeData.groundState != GroundState.Grounded)
|
||||
verticalMovement.y += Physics.gravity.y * fixedDeltaTime;
|
||||
|
||||
// Apply vertical movement
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
rigidBody.linearVelocity = new Vector3(rigidBody.linearVelocity.x, verticalMovement.y, rigidBody.linearVelocity.z);
|
||||
#else
|
||||
rigidBody.velocity = new Vector3(rigidBody.velocity.x, verticalMovement.y, rigidBody.velocity.z);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d6d426b831ca7c43a7ebc82d324dbb6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/PlayerControllerRB/PlayerControllerRBBase.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,23 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Player
|
||||
{
|
||||
[AddComponentMenu("Network/Player Controller RB (Hybrid)")]
|
||||
[RequireComponent(typeof(NetworkTransformHybrid))]
|
||||
public class PlayerControllerRBHybrid : PlayerControllerRBBase
|
||||
{
|
||||
protected override void OnValidate()
|
||||
{
|
||||
if (Application.isPlaying) return;
|
||||
base.OnValidate();
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
GetComponent<NetworkTransformHybrid>().useFixedUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 56a0c5bf67ad39b42a0faee0585bab6a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/PlayerControllerRB/PlayerControllerRBHybrid.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,23 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Player
|
||||
{
|
||||
[AddComponentMenu("Network/Player Controller RB (Reliable)")]
|
||||
[RequireComponent(typeof(NetworkTransformReliable))]
|
||||
public class PlayerControllerRBReliable : PlayerControllerRBBase
|
||||
{
|
||||
protected override void OnValidate()
|
||||
{
|
||||
if (Application.isPlaying) return;
|
||||
base.OnValidate();
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
GetComponent<NetworkTransformReliable>().useFixedUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0336d1bd689de45418c08c76ae66e503
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/PlayerControllerRB/PlayerControllerRBReliable.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Player
|
||||
{
|
||||
[AddComponentMenu("")]
|
||||
[DisallowMultipleComponent]
|
||||
public class PlayerControllerRBUI : ControllerUIBase
|
||||
{
|
||||
[Serializable]
|
||||
public struct MoveTexts
|
||||
{
|
||||
public Text keyTextTurnLeft;
|
||||
public Text keyTextForward;
|
||||
public Text keyTextTurnRight;
|
||||
public Text keyTextStrafeLeft;
|
||||
public Text keyTextBack;
|
||||
public Text keyTextStrafeRight;
|
||||
public Text keyTextJump;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct OptionsTexts
|
||||
{
|
||||
public Text keyTextMouseSteer;
|
||||
public Text keyTextAutoRun;
|
||||
public Text keyTextToggleUI;
|
||||
}
|
||||
|
||||
[SerializeField] MoveTexts moveTexts;
|
||||
[SerializeField] OptionsTexts optionsTexts;
|
||||
|
||||
public void Refresh(PlayerControllerRBBase.MoveKeys moveKeys, PlayerControllerRBBase.OptionsKeys optionsKeys)
|
||||
{
|
||||
// Movement Keys
|
||||
moveTexts.keyTextTurnLeft.text = GetKeyText(moveKeys.TurnLeft);
|
||||
moveTexts.keyTextForward.text = GetKeyText(moveKeys.Forward);
|
||||
moveTexts.keyTextTurnRight.text = GetKeyText(moveKeys.TurnRight);
|
||||
moveTexts.keyTextStrafeLeft.text = GetKeyText(moveKeys.StrafeLeft);
|
||||
moveTexts.keyTextBack.text = GetKeyText(moveKeys.Back);
|
||||
moveTexts.keyTextStrafeRight.text = GetKeyText(moveKeys.StrafeRight);
|
||||
moveTexts.keyTextJump.text = GetKeyText(moveKeys.Jump);
|
||||
|
||||
// Options Keys
|
||||
optionsTexts.keyTextMouseSteer.text = GetKeyText(optionsKeys.MouseSteer);
|
||||
optionsTexts.keyTextAutoRun.text = GetKeyText(optionsKeys.AutoRun);
|
||||
optionsTexts.keyTextToggleUI.text = GetKeyText(optionsKeys.ToggleUI);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b3ad79b59b8ecf4691d945429efae18
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/PlayerControllerRB/PlayerControllerRBUI.cs
|
||||
uploadId: 736421
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5caaf0d5754a64f4080f0c8b55c0b03d
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/PlayerControllerRB/PlayerControllerRBUI.prefab
|
||||
uploadId: 736421
|
@ -0,0 +1,23 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Player
|
||||
{
|
||||
[AddComponentMenu("Network/Player Controller RB (Unreliable)")]
|
||||
[RequireComponent(typeof(NetworkTransformUnreliable))]
|
||||
public class PlayerControllerRBUnreliable : PlayerControllerRBBase
|
||||
{
|
||||
protected override void OnValidate()
|
||||
{
|
||||
if (Application.isPlaying) return;
|
||||
base.OnValidate();
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
GetComponent<NetworkTransformUnreliable>().useFixedUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60cbbdae6551c7b4395e1bd09e2ff3ea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/PlayerControllerRB/PlayerControllerRBUnreliable.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 900b1db22b186f84cba696f403f120e2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,336 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Tank
|
||||
{
|
||||
[AddComponentMenu("")]
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
[RequireComponent(typeof(CharacterController))]
|
||||
[RequireComponent(typeof(NetworkIdentity))]
|
||||
[RequireComponent(typeof(TankHealth))]
|
||||
[DisallowMultipleComponent]
|
||||
public class TankControllerBase : NetworkBehaviour
|
||||
{
|
||||
public enum GroundState : byte { Grounded, Falling }
|
||||
|
||||
[Serializable]
|
||||
public struct MoveKeys
|
||||
{
|
||||
public KeyCode Forward;
|
||||
public KeyCode Back;
|
||||
public KeyCode TurnLeft;
|
||||
public KeyCode TurnRight;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct OptionsKeys
|
||||
{
|
||||
public KeyCode AutoRun;
|
||||
public KeyCode ToggleUI;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ControlOptions : byte
|
||||
{
|
||||
None,
|
||||
AutoRun = 1 << 0,
|
||||
ShowUI = 1 << 1
|
||||
}
|
||||
|
||||
[Header("Components")]
|
||||
public BoxCollider boxCollider;
|
||||
public CharacterController characterController;
|
||||
|
||||
[Header("User Interface")]
|
||||
public GameObject ControllerUIPrefab;
|
||||
|
||||
[Header("Configuration")]
|
||||
[SerializeField]
|
||||
public MoveKeys moveKeys = new MoveKeys
|
||||
{
|
||||
Forward = KeyCode.W,
|
||||
Back = KeyCode.S,
|
||||
TurnLeft = KeyCode.A,
|
||||
TurnRight = KeyCode.D,
|
||||
};
|
||||
|
||||
[SerializeField]
|
||||
public OptionsKeys optionsKeys = new OptionsKeys
|
||||
{
|
||||
AutoRun = KeyCode.R,
|
||||
ToggleUI = KeyCode.U
|
||||
};
|
||||
|
||||
[Space(5)]
|
||||
public ControlOptions controlOptions = ControlOptions.ShowUI;
|
||||
|
||||
[Header("Movement")]
|
||||
[Range(0, 20)]
|
||||
[FormerlySerializedAs("moveSpeedMultiplier")]
|
||||
[Tooltip("Speed in meters per second")]
|
||||
public float maxMoveSpeed = 8f;
|
||||
|
||||
// Replacement for Sensitvity from Input Settings.
|
||||
[Range(0, 10f)]
|
||||
[Tooltip("Sensitivity factors into accelleration")]
|
||||
public float inputSensitivity = 2f;
|
||||
|
||||
// Replacement for Gravity from Input Settings.
|
||||
[Range(0, 10f)]
|
||||
[Tooltip("Gravity factors into decelleration")]
|
||||
public float inputGravity = 2f;
|
||||
|
||||
[Header("Turning")]
|
||||
[Range(0, 300f)]
|
||||
[Tooltip("Max Rotation in degrees per second")]
|
||||
public float maxTurnSpeed = 100f;
|
||||
[Range(0, 10f)]
|
||||
[FormerlySerializedAs("turnDelta")]
|
||||
[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 _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 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")]
|
||||
public RuntimeData runtimeData;
|
||||
|
||||
#region Network Setup
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
// Skip if Editor is in Play mode
|
||||
if (Application.isPlaying) return;
|
||||
|
||||
base.OnValidate();
|
||||
Reset();
|
||||
}
|
||||
|
||||
protected virtual void Reset()
|
||||
{
|
||||
if (boxCollider == null)
|
||||
boxCollider = GetComponentInChildren<BoxCollider>();
|
||||
|
||||
// Enable by default...it will be disabled when characterController is enabled
|
||||
boxCollider.enabled = true;
|
||||
|
||||
if (characterController == null)
|
||||
characterController = GetComponent<CharacterController>();
|
||||
|
||||
// Override CharacterController default values
|
||||
characterController.enabled = false;
|
||||
characterController.skinWidth = 0.02f;
|
||||
characterController.minMoveDistance = 0f;
|
||||
|
||||
GetComponent<Rigidbody>().isKinematic = true;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// For convenience in the examples, we use the GUID of the TankControllerUI prefab
|
||||
// to find the correct prefab in the Mirror/Examples/_Common/Controllers folder.
|
||||
// This avoids conflicts with user-created prefabs that may have the same name
|
||||
// and avoids polluting the user's project with Resources.
|
||||
// This is not recommended for production code...use Resources.Load or AssetBundles instead.
|
||||
if (ControllerUIPrefab == null)
|
||||
{
|
||||
string path = UnityEditor.AssetDatabase.GUIDToAssetPath("e64b14552402f6745a7f0aca6237fae2");
|
||||
ControllerUIPrefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||
}
|
||||
#endif
|
||||
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
runtimeData.vertical = 0f;
|
||||
runtimeData.turnSpeed = 0f;
|
||||
}
|
||||
|
||||
public override void OnStartAuthority()
|
||||
{
|
||||
characterController.enabled = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public override void OnStopAuthority()
|
||||
{
|
||||
this.enabled = false;
|
||||
characterController.enabled = false;
|
||||
}
|
||||
|
||||
public override void OnStartLocalPlayer()
|
||||
{
|
||||
if (ControllerUIPrefab != null)
|
||||
runtimeData.controllerUI = Instantiate(ControllerUIPrefab);
|
||||
|
||||
if (runtimeData.controllerUI != null)
|
||||
{
|
||||
if (runtimeData.controllerUI.TryGetComponent(out TankControllerUI canvasControlPanel))
|
||||
canvasControlPanel.Refresh(moveKeys, optionsKeys);
|
||||
|
||||
runtimeData.controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnStopLocalPlayer()
|
||||
{
|
||||
if (runtimeData.controllerUI != null)
|
||||
Destroy(runtimeData.controllerUI);
|
||||
runtimeData.controllerUI = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (!characterController.enabled)
|
||||
return;
|
||||
|
||||
float deltaTime = Time.deltaTime;
|
||||
|
||||
HandleOptions();
|
||||
HandleTurning(deltaTime);
|
||||
HandleMove(deltaTime);
|
||||
ApplyMove(deltaTime);
|
||||
|
||||
// Reset ground state
|
||||
runtimeData.groundState = characterController.isGrounded ? GroundState.Grounded : GroundState.Falling;
|
||||
|
||||
// Diagnostic velocity...FloorToInt for display purposes
|
||||
runtimeData.velocity = Vector3Int.FloorToInt(characterController.velocity);
|
||||
}
|
||||
|
||||
void HandleOptions()
|
||||
{
|
||||
if (optionsKeys.AutoRun != KeyCode.None && Input.GetKeyUp(optionsKeys.AutoRun))
|
||||
controlOptions ^= ControlOptions.AutoRun;
|
||||
|
||||
if (optionsKeys.ToggleUI != KeyCode.None && Input.GetKeyUp(optionsKeys.ToggleUI))
|
||||
{
|
||||
controlOptions ^= ControlOptions.ShowUI;
|
||||
|
||||
if (runtimeData.controllerUI != null)
|
||||
runtimeData.controllerUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
}
|
||||
}
|
||||
|
||||
// Turning works while airborne...feature?
|
||||
void HandleTurning(float deltaTime)
|
||||
{
|
||||
float targetTurnSpeed = 0f;
|
||||
|
||||
// TurnLeft and TurnRight cancel each other out, reducing targetTurnSpeed to zero.
|
||||
if (moveKeys.TurnLeft != KeyCode.None && Input.GetKey(moveKeys.TurnLeft))
|
||||
targetTurnSpeed -= maxTurnSpeed;
|
||||
if (moveKeys.TurnRight != KeyCode.None && Input.GetKey(moveKeys.TurnRight))
|
||||
targetTurnSpeed += maxTurnSpeed;
|
||||
|
||||
// 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))
|
||||
runtimeData.turnSpeed = Mathf.MoveTowards(runtimeData.turnSpeed, targetTurnSpeed, turnAcceleration * maxTurnSpeed * deltaTime);
|
||||
|
||||
transform.Rotate(0f, runtimeData.turnSpeed * deltaTime, 0f);
|
||||
}
|
||||
|
||||
void HandleMove(float deltaTime)
|
||||
{
|
||||
// Initialize target movement variables
|
||||
float targetMoveZ = 0f;
|
||||
|
||||
// Check for WASD key presses and adjust target movement variables accordingly
|
||||
if (moveKeys.Forward != KeyCode.None && Input.GetKey(moveKeys.Forward)) targetMoveZ = 1f;
|
||||
if (moveKeys.Back != KeyCode.None && Input.GetKey(moveKeys.Back)) targetMoveZ = -1f;
|
||||
|
||||
if (targetMoveZ == 0f)
|
||||
{
|
||||
if (!controlOptions.HasFlag(ControlOptions.AutoRun))
|
||||
runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputGravity * deltaTime);
|
||||
}
|
||||
else
|
||||
runtimeData.vertical = Mathf.MoveTowards(runtimeData.vertical, targetMoveZ, inputSensitivity * deltaTime);
|
||||
}
|
||||
|
||||
void ApplyMove(float deltaTime)
|
||||
{
|
||||
// Create initial direction vector (z-axis only)
|
||||
runtimeData.direction = new Vector3(0f, 0f, runtimeData.vertical);
|
||||
|
||||
// Transforms direction from local space to world space.
|
||||
runtimeData.direction = transform.TransformDirection(runtimeData.direction);
|
||||
|
||||
// Multiply for desired ground speed.
|
||||
runtimeData.direction *= maxMoveSpeed;
|
||||
|
||||
// Add gravity in case we drove off a cliff.
|
||||
runtimeData.direction += Physics.gravity;
|
||||
|
||||
// Finally move the character.
|
||||
characterController.Move(runtimeData.direction * deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 047024b2ae9afb74485837a482ea4175
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/TankController/TankControllerBase.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,8 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Tank
|
||||
{
|
||||
[AddComponentMenu("Network/Tank Controller (Hybrid)")]
|
||||
[RequireComponent(typeof(NetworkTransformHybrid))]
|
||||
public class TankControllerHybrid : TankControllerBase { }
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 74a242534e86bfe409459946bee3afd0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/TankController/TankControllerHybrid.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,8 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Tank
|
||||
{
|
||||
[AddComponentMenu("Network/Tank Controller (Reliable)")]
|
||||
[RequireComponent(typeof(NetworkTransformReliable))]
|
||||
public class TankControllerReliable : TankControllerBase { }
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 275b33356ccf3ad4f96b0084f0128272
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/TankController/TankControllerReliable.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Tank
|
||||
{
|
||||
[AddComponentMenu("")]
|
||||
[DisallowMultipleComponent]
|
||||
public class TankControllerUI : ControllerUIBase
|
||||
{
|
||||
[Serializable]
|
||||
public struct MoveTexts
|
||||
{
|
||||
public Text keyTextTurnLeft;
|
||||
public Text keyTextForward;
|
||||
public Text keyTextTurnRight;
|
||||
public Text keyTextBack;
|
||||
public Text keyTextShoot;
|
||||
}
|
||||
|
||||
public struct OtherTexts
|
||||
{
|
||||
public Text keyTextShoot;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct OptionsTexts
|
||||
{
|
||||
public Text keyTextMouseLock;
|
||||
public Text keyTextAutoRun;
|
||||
public Text keyTextToggleUI;
|
||||
}
|
||||
|
||||
[SerializeField] MoveTexts moveTexts;
|
||||
[SerializeField] OtherTexts otherKeys;
|
||||
[SerializeField] OptionsTexts optionsTexts;
|
||||
|
||||
public void Refresh(TankControllerBase.MoveKeys moveKeys, TankControllerBase.OptionsKeys optionsKeys)
|
||||
{
|
||||
// Movement Keys
|
||||
moveTexts.keyTextTurnLeft.text = GetKeyText(moveKeys.TurnLeft);
|
||||
moveTexts.keyTextForward.text = GetKeyText(moveKeys.Forward);
|
||||
moveTexts.keyTextTurnRight.text = GetKeyText(moveKeys.TurnRight);
|
||||
moveTexts.keyTextBack.text = GetKeyText(moveKeys.Back);
|
||||
|
||||
//// Other Keys
|
||||
//moveTexts.keyTextShoot.text = GetKeyText(otherKeys.Shoot);
|
||||
|
||||
// Options Keys
|
||||
optionsTexts.keyTextAutoRun.text = GetKeyText(optionsKeys.AutoRun);
|
||||
optionsTexts.keyTextToggleUI.text = GetKeyText(optionsKeys.ToggleUI);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 55bd8eba9aaa3104d85d885bfa7f0437
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/TankController/TankControllerUI.cs
|
||||
uploadId: 736421
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e64b14552402f6745a7f0aca6237fae2
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/TankController/TankControllerUI.prefab
|
||||
uploadId: 736421
|
@ -0,0 +1,8 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Tank
|
||||
{
|
||||
[AddComponentMenu("Network/Tank Controller (Unreliable)")]
|
||||
[RequireComponent(typeof(NetworkTransformUnreliable))]
|
||||
public class TankControllerUnreliable : TankControllerBase { }
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54174134d990edc4b8d3ba4242a9a61a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/TankController/TankControllerUnreliable.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,83 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Tank
|
||||
{
|
||||
[AddComponentMenu("")]
|
||||
[DisallowMultipleComponent]
|
||||
public class TankHealth : NetworkBehaviour
|
||||
{
|
||||
[Header("Components")]
|
||||
public TextMesh healthBar;
|
||||
|
||||
[Header("Stats")]
|
||||
public byte maxHealth = 5;
|
||||
[SyncVar(hook = nameof(OnHealthChanged))]
|
||||
public byte health = 5;
|
||||
|
||||
[Header("Respawn")]
|
||||
public bool respawn = true;
|
||||
public byte respawnTime = 3;
|
||||
|
||||
void OnHealthChanged(byte oldHealth, byte newHealth)
|
||||
{
|
||||
healthBar.text = new string('-', newHealth);
|
||||
|
||||
if (newHealth >= maxHealth)
|
||||
healthBar.color = Color.green;
|
||||
if (newHealth < 4)
|
||||
healthBar.color = Color.yellow;
|
||||
if (newHealth < 2)
|
||||
healthBar.color = Color.red;
|
||||
if (newHealth < 1)
|
||||
healthBar.color = Color.black;
|
||||
}
|
||||
|
||||
#region Unity Callbacks
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
// Skip if Editor is in Play mode
|
||||
if (Application.isPlaying) return;
|
||||
|
||||
base.OnValidate();
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
if (healthBar == null)
|
||||
healthBar = transform.Find("HealthBar").GetComponent<TextMesh>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override void OnStartServer()
|
||||
{
|
||||
health = maxHealth;
|
||||
}
|
||||
|
||||
[ServerCallback]
|
||||
public void TakeDamage(byte damage)
|
||||
{
|
||||
// Skip if health is already 0
|
||||
if (health == 0) return;
|
||||
|
||||
if (damage > health)
|
||||
health = 0;
|
||||
else
|
||||
health -= damage;
|
||||
|
||||
if (health == 0)
|
||||
{
|
||||
if (connectionToClient != null)
|
||||
Respawn.RespawnPlayer(respawn, respawnTime, connectionToClient);
|
||||
else if (netIdentity.sceneId != 0)
|
||||
NetworkServer.UnSpawn(gameObject);
|
||||
else
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a98fd0a5ae4916d48bdcdb0e8c99a5cc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/TankController/TankHealth.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,468 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Tank
|
||||
{
|
||||
[AddComponentMenu("")]
|
||||
[RequireComponent(typeof(NetworkIdentity))]
|
||||
[DisallowMultipleComponent]
|
||||
public class TankTurretBase : NetworkBehaviour
|
||||
{
|
||||
const float BASE_DPI = 96f;
|
||||
|
||||
[Serializable]
|
||||
public struct OptionsKeys
|
||||
{
|
||||
public KeyCode MouseLock;
|
||||
public KeyCode AutoLevel;
|
||||
public KeyCode ToggleUI;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct MoveKeys
|
||||
{
|
||||
public KeyCode PitchUp;
|
||||
public KeyCode PitchDown;
|
||||
public KeyCode TurnLeft;
|
||||
public KeyCode TurnRight;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct OtherKeys
|
||||
{
|
||||
public KeyCode Shoot;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ControlOptions : byte
|
||||
{
|
||||
None,
|
||||
MouseLock = 1 << 0,
|
||||
AutoLevel = 1 << 1,
|
||||
ShowUI = 1 << 2
|
||||
}
|
||||
|
||||
// Unity clones the material when GetComponent<Renderer>().material is called
|
||||
// Cache it here and destroy it in OnDestroy to prevent a memory leak
|
||||
Material cachedMaterial;
|
||||
|
||||
[Header("Prefabs")]
|
||||
public GameObject turretUIPrefab;
|
||||
public GameObject projectilePrefab;
|
||||
|
||||
[Header("Components")]
|
||||
public Animator animator;
|
||||
public Transform turret;
|
||||
public Transform barrel;
|
||||
public Transform projectileMount;
|
||||
public CapsuleCollider barrelCollider;
|
||||
|
||||
[Header("Seated Player")]
|
||||
public GameObject playerObject;
|
||||
|
||||
[SyncVar(hook = nameof(OnPlayerColorChanged))]
|
||||
public Color32 playerColor = Color.black;
|
||||
|
||||
[Header("Configuration")]
|
||||
[SerializeField]
|
||||
public MoveKeys moveKeys = new MoveKeys
|
||||
{
|
||||
PitchUp = KeyCode.UpArrow,
|
||||
PitchDown = KeyCode.DownArrow,
|
||||
TurnLeft = KeyCode.LeftArrow,
|
||||
TurnRight = KeyCode.RightArrow
|
||||
};
|
||||
|
||||
[SerializeField]
|
||||
public OtherKeys otherKeys = new OtherKeys
|
||||
{
|
||||
Shoot = KeyCode.Space
|
||||
};
|
||||
|
||||
[SerializeField]
|
||||
public OptionsKeys optionsKeys = new OptionsKeys
|
||||
{
|
||||
MouseLock = KeyCode.M,
|
||||
AutoLevel = KeyCode.L,
|
||||
ToggleUI = KeyCode.U
|
||||
};
|
||||
|
||||
[Space(5)]
|
||||
public ControlOptions controlOptions = ControlOptions.AutoLevel | ControlOptions.ShowUI;
|
||||
|
||||
[Header("Shooting")]
|
||||
[Tooltip("Cooldown time in seconds")]
|
||||
[Range(0, 10)]
|
||||
public byte cooldownTime = 1;
|
||||
|
||||
[Header("Turret")]
|
||||
[Range(0, 300f)]
|
||||
[Tooltip("Max Rotation in degrees per second")]
|
||||
public float maxTurretSpeed = 250f;
|
||||
[Range(0, 30f)]
|
||||
[Tooltip("Rotation acceleration in degrees per second squared")]
|
||||
public float turretAcceleration = 10f;
|
||||
|
||||
[Header("Barrel")]
|
||||
[Range(0, 180f)]
|
||||
[Tooltip("Max Pitch in degrees per second")]
|
||||
public float maxPitchSpeed = 30f;
|
||||
[Range(0, 40f)]
|
||||
[Tooltip("Max Pitch in degrees")]
|
||||
public float maxPitchUpAngle = 25f;
|
||||
[Range(0, 20f)]
|
||||
[Tooltip("Max Pitch in degrees")]
|
||||
public float maxPitchDownAngle = 0f;
|
||||
[Range(0, 10f)]
|
||||
[Tooltip("Pitch acceleration in degrees per second squared")]
|
||||
public float pitchAcceleration = 3f;
|
||||
|
||||
// Runtime data in a struct so it can be folded up in inspector
|
||||
[Serializable]
|
||||
public struct RuntimeData
|
||||
{
|
||||
[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;
|
||||
|
||||
#region Properties
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
[Header("Diagnostics")]
|
||||
public RuntimeData runtimeData;
|
||||
|
||||
#region Network Setup
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
// Skip if Editor is in Play mode
|
||||
if (Application.isPlaying) return;
|
||||
|
||||
base.OnValidate();
|
||||
Reset();
|
||||
}
|
||||
|
||||
// NOTE: Do not put objects in DontDestroyOnLoad (DDOL) in Awake. You can do that in Start instead.
|
||||
protected virtual void Reset()
|
||||
{
|
||||
// Ensure syncDirection is Client to Server
|
||||
syncDirection = SyncDirection.ClientToServer;
|
||||
|
||||
if (animator == null)
|
||||
animator = GetComponentInChildren<Animator>();
|
||||
|
||||
// Set default...this may be modified based on DPI at runtime
|
||||
runtimeData.mouseSensitivity = turretAcceleration;
|
||||
|
||||
// Do a recursive search for a children named "Turret" and "ProjectileMount".
|
||||
// They will be several levels deep in the hierarchy.
|
||||
if (turret == null)
|
||||
turret = FindDeepChild(transform, "Turret");
|
||||
|
||||
if (barrel == null)
|
||||
barrel = FindDeepChild(turret, "Barrel");
|
||||
|
||||
if (barrelCollider == null)
|
||||
barrelCollider = barrel.GetComponent<CapsuleCollider>();
|
||||
|
||||
if (projectileMount == null)
|
||||
projectileMount = FindDeepChild(turret, "ProjectileMount");
|
||||
|
||||
if (playerObject == null)
|
||||
playerObject = FindDeepChild(turret, "SeatedPlayer").gameObject;
|
||||
|
||||
// tranform.Find will fail - must do recursive search
|
||||
Transform FindDeepChild(Transform aParent, string aName)
|
||||
{
|
||||
var result = aParent.Find(aName);
|
||||
if (result != null)
|
||||
return result;
|
||||
|
||||
foreach (Transform child in aParent)
|
||||
{
|
||||
result = FindDeepChild(child, aName);
|
||||
if (result != null)
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// For convenience in the examples, we use the GUID of the Projectile prefab
|
||||
// to find the correct prefab in the Mirror/Examples/_Common/Controllers folder.
|
||||
// This avoids conflicts with user-created prefabs that may have the same name
|
||||
// and avoids polluting the user's project with Resources.
|
||||
// This is not recommended for production code...use Resources.Load or AssetBundles instead.
|
||||
if (turretUIPrefab == null)
|
||||
{
|
||||
string path = UnityEditor.AssetDatabase.GUIDToAssetPath("4d16730f7a8ba0a419530d1156d25080");
|
||||
turretUIPrefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||
}
|
||||
|
||||
if (projectilePrefab == null)
|
||||
{
|
||||
string path = UnityEditor.AssetDatabase.GUIDToAssetPath("aec853915cd4f4477ba1532b5fe05488");
|
||||
projectilePrefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||
}
|
||||
#endif
|
||||
|
||||
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()
|
||||
{
|
||||
float deltaTime = Time.deltaTime;
|
||||
|
||||
HandleOptions();
|
||||
HandlePitch(deltaTime);
|
||||
|
||||
if (controlOptions.HasFlag(ControlOptions.MouseLock))
|
||||
HandleMouseTurret(deltaTime);
|
||||
else
|
||||
HandleTurning(deltaTime);
|
||||
|
||||
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;
|
||||
Cursor.visible = !locked;
|
||||
}
|
||||
|
||||
void HandleOptions()
|
||||
{
|
||||
if (optionsKeys.MouseLock != KeyCode.None && Input.GetKeyUp(optionsKeys.MouseLock))
|
||||
{
|
||||
controlOptions ^= ControlOptions.MouseLock;
|
||||
SetCursor(controlOptions.HasFlag(ControlOptions.MouseLock));
|
||||
}
|
||||
|
||||
if (optionsKeys.AutoLevel != KeyCode.None && Input.GetKeyUp(optionsKeys.AutoLevel))
|
||||
controlOptions ^= ControlOptions.AutoLevel;
|
||||
|
||||
if (optionsKeys.ToggleUI != KeyCode.None && Input.GetKeyUp(optionsKeys.ToggleUI))
|
||||
{
|
||||
controlOptions ^= ControlOptions.ShowUI;
|
||||
|
||||
if (runtimeData.turretUI != null)
|
||||
runtimeData.turretUI.SetActive(controlOptions.HasFlag(ControlOptions.ShowUI));
|
||||
}
|
||||
}
|
||||
|
||||
void HandleTurning(float deltaTime)
|
||||
{
|
||||
float targetTurnSpeed = 0f;
|
||||
|
||||
// TurnLeft and TurnRight cancel each other out, reducing targetTurnSpeed to zero.
|
||||
if (moveKeys.TurnLeft != KeyCode.None && Input.GetKey(moveKeys.TurnLeft))
|
||||
targetTurnSpeed -= maxTurretSpeed;
|
||||
if (moveKeys.TurnRight != KeyCode.None && Input.GetKey(moveKeys.TurnRight))
|
||||
targetTurnSpeed += maxTurretSpeed;
|
||||
|
||||
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
|
||||
runtimeData.mouseInputX += Input.GetAxisRaw("Mouse X") * runtimeData.mouseSensitivity;
|
||||
|
||||
// Clamp the accumulator to simulate key press behavior
|
||||
runtimeData.mouseInputX = Mathf.Clamp(runtimeData.mouseInputX, -1f, 1f);
|
||||
|
||||
// Calculate target turn speed
|
||||
float targetTurnSpeed = runtimeData.mouseInputX * maxTurretSpeed;
|
||||
|
||||
// Use the same acceleration logic as HandleTurning
|
||||
runtimeData.turretSpeed = Mathf.MoveTowards(runtimeData.turretSpeed, targetTurnSpeed, runtimeData.mouseSensitivity * maxTurretSpeed * deltaTime);
|
||||
|
||||
// Apply rotation
|
||||
turret.Rotate(0f, runtimeData.turretSpeed * deltaTime, 0f);
|
||||
|
||||
runtimeData.mouseInputX = Mathf.MoveTowards(runtimeData.mouseInputX, 0f, runtimeData.mouseSensitivity * deltaTime);
|
||||
}
|
||||
|
||||
void HandlePitch(float deltaTime)
|
||||
{
|
||||
float targetPitchSpeed = 0f;
|
||||
bool inputDetected = false;
|
||||
|
||||
// Up and Down arrows for pitch
|
||||
if (moveKeys.PitchUp != KeyCode.None && Input.GetKey(moveKeys.PitchUp))
|
||||
{
|
||||
targetPitchSpeed -= maxPitchSpeed;
|
||||
inputDetected = true;
|
||||
}
|
||||
|
||||
if (moveKeys.PitchDown != KeyCode.None && Input.GetKey(moveKeys.PitchDown))
|
||||
{
|
||||
targetPitchSpeed += maxPitchSpeed;
|
||||
inputDetected = true;
|
||||
}
|
||||
|
||||
runtimeData.pitchSpeed = Mathf.MoveTowards(runtimeData.pitchSpeed, targetPitchSpeed, pitchAcceleration * maxPitchSpeed * deltaTime);
|
||||
|
||||
// Apply pitch rotation
|
||||
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))
|
||||
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 + runtimeData.pitchAngle, 0f, 180f);
|
||||
}
|
||||
|
||||
#region Shooting
|
||||
|
||||
bool CanShoot => NetworkTime.time >= runtimeData.lastShotTime + cooldownTime;
|
||||
|
||||
void HandleShooting()
|
||||
{
|
||||
if (CanShoot && otherKeys.Shoot != KeyCode.None && Input.GetKeyUp(otherKeys.Shoot))
|
||||
{
|
||||
CmdShoot();
|
||||
if (!isServer) DoShoot();
|
||||
}
|
||||
}
|
||||
|
||||
[Command]
|
||||
void CmdShoot()
|
||||
{
|
||||
if (!CanShoot) return;
|
||||
|
||||
//Debug.Log("CmdShoot");
|
||||
RpcShoot();
|
||||
DoShoot();
|
||||
}
|
||||
|
||||
[ClientRpc(includeOwner = false)]
|
||||
void RpcShoot()
|
||||
{
|
||||
//Debug.Log("RpcShoot");
|
||||
if (!isServer) DoShoot();
|
||||
}
|
||||
|
||||
void DoShoot()
|
||||
{
|
||||
//Debug.Log($"DoShoot isServerOnly:{isServerOnly} | isServer:{isServer} | isClientOnly:{isClientOnly}");
|
||||
|
||||
// Turret
|
||||
// - Barrel (with Collider)
|
||||
// - BarrelEnd
|
||||
// - ProjectileMount
|
||||
|
||||
// Locally instantiate the projectile at the end of the barrel
|
||||
GameObject go = Instantiate(projectilePrefab, projectileMount.position, projectileMount.rotation);
|
||||
|
||||
// Ignore collision between the projectile and the barrel collider
|
||||
Physics.IgnoreCollision(go.GetComponent<Collider>(), barrelCollider);
|
||||
|
||||
// Update the last shot time
|
||||
runtimeData.lastShotTime = NetworkTime.time;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba67fa30a5212ed4e964609050cdc3f5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/TankController/TankTurretBase.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,60 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Tank
|
||||
{
|
||||
[AddComponentMenu("Network/Tank Turret (Hybrid)")]
|
||||
[RequireComponent(typeof(TankControllerHybrid))]
|
||||
[RequireComponent(typeof(NetworkTransformHybrid))]
|
||||
public class TankTurretHybrid : TankTurretBase
|
||||
{
|
||||
[Header("Network Transforms")]
|
||||
public NetworkTransformHybrid turretNetworkTransform;
|
||||
public NetworkTransformHybrid barrelNetworkTransform;
|
||||
|
||||
protected override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
|
||||
// The base Tank uses the first NetworkTransformHybrid for the tank body
|
||||
// Add additional NetworkTransformHybrid components for the turret and barrel
|
||||
// Set SyncPosition to false because we only want to sync rotation
|
||||
NetworkTransformHybrid[] NTs = GetComponents<NetworkTransformHybrid>();
|
||||
|
||||
if (NTs.Length < 2)
|
||||
{
|
||||
turretNetworkTransform = gameObject.AddComponent<NetworkTransformHybrid>();
|
||||
turretNetworkTransform.transform.SetSiblingIndex(NTs[0].transform.GetSiblingIndex() + 1);
|
||||
NTs = GetComponents<NetworkTransformHybrid>();
|
||||
}
|
||||
else
|
||||
turretNetworkTransform = NTs[1];
|
||||
|
||||
// Ensure syncDirection is Client to Server
|
||||
turretNetworkTransform.syncDirection = SyncDirection.ClientToServer;
|
||||
|
||||
// Set syncPosition to false because we only want to sync rotation
|
||||
turretNetworkTransform.syncPosition = false;
|
||||
|
||||
if (base.turret != null)
|
||||
turretNetworkTransform.target = turret;
|
||||
|
||||
if (NTs.Length < 3)
|
||||
{
|
||||
barrelNetworkTransform = gameObject.AddComponent<NetworkTransformHybrid>();
|
||||
barrelNetworkTransform.transform.SetSiblingIndex(NTs[1].transform.GetSiblingIndex() + 1);
|
||||
NTs = GetComponents<NetworkTransformHybrid>();
|
||||
}
|
||||
else
|
||||
barrelNetworkTransform = NTs[2];
|
||||
|
||||
// Ensure syncDirection is Client to Server
|
||||
barrelNetworkTransform.syncDirection = SyncDirection.ClientToServer;
|
||||
|
||||
// Set syncPosition to false because we only want to sync rotation
|
||||
barrelNetworkTransform.syncPosition = false;
|
||||
|
||||
if (barrel != null)
|
||||
barrelNetworkTransform.target = barrel;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d03cccae3cfa1842968563fa2248e35
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/TankController/TankTurretHybrid.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,60 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Tank
|
||||
{
|
||||
[AddComponentMenu("Network/Tank Turret (Reliable)")]
|
||||
[RequireComponent(typeof(TankControllerReliable))]
|
||||
[RequireComponent(typeof(NetworkTransformReliable))]
|
||||
public class TankTurretReliable : TankTurretBase
|
||||
{
|
||||
[Header("Network Transforms")]
|
||||
public NetworkTransformReliable turretNetworkTransform;
|
||||
public NetworkTransformReliable barrelNetworkTransform;
|
||||
|
||||
protected override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
|
||||
// The base Tank uses the first NetworkTransformReliable for the tank body
|
||||
// Add additional NetworkTransformReliable components for the turret and barrel
|
||||
// Set SyncPosition to false because we only want to sync rotation
|
||||
NetworkTransformReliable[] NTs = GetComponents<NetworkTransformReliable>();
|
||||
|
||||
if (NTs.Length < 2)
|
||||
{
|
||||
turretNetworkTransform = gameObject.AddComponent<NetworkTransformReliable>();
|
||||
turretNetworkTransform.transform.SetSiblingIndex(NTs[0].transform.GetSiblingIndex() + 1);
|
||||
NTs = GetComponents<NetworkTransformReliable>();
|
||||
}
|
||||
else
|
||||
turretNetworkTransform = NTs[1];
|
||||
|
||||
// Ensure syncDirection is Client to Server
|
||||
turretNetworkTransform.syncDirection = SyncDirection.ClientToServer;
|
||||
|
||||
// Set syncPosition to false because we only want to sync rotation
|
||||
turretNetworkTransform.syncPosition = false;
|
||||
|
||||
if (base.turret != null)
|
||||
turretNetworkTransform.target = turret;
|
||||
|
||||
if (NTs.Length < 3)
|
||||
{
|
||||
barrelNetworkTransform = gameObject.AddComponent<NetworkTransformReliable>();
|
||||
barrelNetworkTransform.transform.SetSiblingIndex(NTs[1].transform.GetSiblingIndex() + 1);
|
||||
NTs = GetComponents<NetworkTransformReliable>();
|
||||
}
|
||||
else
|
||||
barrelNetworkTransform = NTs[2];
|
||||
|
||||
// Ensure syncDirection is Client to Server
|
||||
barrelNetworkTransform.syncDirection = SyncDirection.ClientToServer;
|
||||
|
||||
// Set syncPosition to false because we only want to sync rotation
|
||||
barrelNetworkTransform.syncPosition = false;
|
||||
|
||||
if (barrel != null)
|
||||
barrelNetworkTransform.target = barrel;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fbb0b0f4ce2fede4f95abe4cf1737365
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/TankController/TankTurretReliable.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,60 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Tank
|
||||
{
|
||||
[AddComponentMenu("Network/Tank Turret (Unreliable)")]
|
||||
[RequireComponent(typeof(TankControllerUnreliable))]
|
||||
[RequireComponent(typeof(NetworkTransformUnreliable))]
|
||||
public class TankTurretUnreliable : TankTurretBase
|
||||
{
|
||||
[Header("Network Transforms")]
|
||||
public NetworkTransformUnreliable turretNetworkTransform;
|
||||
public NetworkTransformUnreliable barrelNetworkTransform;
|
||||
|
||||
protected override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
|
||||
// The base Tank uses the first NetworkTransformReliable for the tank body
|
||||
// Add additional NetworkTransformReliable components for the turret and barrel
|
||||
// Set SyncPosition to false because we only want to sync rotation
|
||||
NetworkTransformUnreliable[] NTs = GetComponents<NetworkTransformUnreliable>();
|
||||
|
||||
if (NTs.Length < 2)
|
||||
{
|
||||
turretNetworkTransform = gameObject.AddComponent<NetworkTransformUnreliable>();
|
||||
turretNetworkTransform.transform.SetSiblingIndex(NTs[0].transform.GetSiblingIndex() + 1);
|
||||
NTs = GetComponents<NetworkTransformUnreliable>();
|
||||
}
|
||||
else
|
||||
turretNetworkTransform = NTs[1];
|
||||
|
||||
// Ensure syncDirection is Client to Server
|
||||
turretNetworkTransform.syncDirection = SyncDirection.ClientToServer;
|
||||
|
||||
// Set syncPosition to false because we only want to sync rotation
|
||||
turretNetworkTransform.syncPosition = false;
|
||||
|
||||
if (base.turret != null)
|
||||
turretNetworkTransform.target = turret;
|
||||
|
||||
if (NTs.Length < 3)
|
||||
{
|
||||
barrelNetworkTransform = gameObject.AddComponent<NetworkTransformUnreliable>();
|
||||
barrelNetworkTransform.transform.SetSiblingIndex(NTs[1].transform.GetSiblingIndex() + 1);
|
||||
NTs = GetComponents<NetworkTransformUnreliable>();
|
||||
}
|
||||
else
|
||||
barrelNetworkTransform = NTs[2];
|
||||
|
||||
// Ensure syncDirection is Client to Server
|
||||
barrelNetworkTransform.syncDirection = SyncDirection.ClientToServer;
|
||||
|
||||
// Set syncPosition to false because we only want to sync rotation
|
||||
barrelNetworkTransform.syncPosition = false;
|
||||
|
||||
if (barrel != null)
|
||||
barrelNetworkTransform.target = barrel;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2cd78b1021803ce4fa7c95ca67f19aec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/TankController/TankTurretUnreliable.cs
|
||||
uploadId: 736421
|
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Mirror.Examples.Common.Controllers.Tank
|
||||
{
|
||||
[AddComponentMenu("")]
|
||||
[DisallowMultipleComponent]
|
||||
public class TurretUI : ControllerUIBase
|
||||
{
|
||||
[Serializable]
|
||||
public struct MoveTexts
|
||||
{
|
||||
public Text keyTextPitchUp;
|
||||
public Text keyTextPitchDown;
|
||||
public Text keyTextTurnLeft;
|
||||
public Text keyTextTurnRight;
|
||||
}
|
||||
|
||||
[SerializeField] MoveTexts moveTexts;
|
||||
|
||||
public void Refresh(TankTurretBase.MoveKeys moveKeys, TankTurretBase.OptionsKeys optionsKeys)
|
||||
{
|
||||
// Movement Keys
|
||||
moveTexts.keyTextPitchUp.text = GetKeyText(moveKeys.PitchUp);
|
||||
moveTexts.keyTextPitchDown.text = GetKeyText(moveKeys.PitchDown);
|
||||
moveTexts.keyTextTurnLeft.text = GetKeyText(moveKeys.TurnLeft);
|
||||
moveTexts.keyTextTurnRight.text = GetKeyText(moveKeys.TurnRight);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 727f1d4c7bded434bb0a729ba0dcbc51
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/TankController/TurretUI.cs
|
||||
uploadId: 736421
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d16730f7a8ba0a419530d1156d25080
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 129321
|
||||
packageName: Mirror
|
||||
packageVersion: 96.0.1
|
||||
assetPath: Assets/Mirror/Examples/_Common/Controllers/TankController/TurretUI.prefab
|
||||
uploadId: 736421
|
Reference in New Issue
Block a user