This commit is contained in:
2025-06-16 15:14:23 +02:00
committed by devbeni
parent 60fe4620ff
commit 4ff561284f
3174 changed files with 428263 additions and 0 deletions

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -0,0 +1,8 @@
using UnityEngine;
namespace Mirror.Examples.Common.Controllers.Tank
{
[AddComponentMenu("Network/Tank Controller (Hybrid)")]
[RequireComponent(typeof(NetworkTransformHybrid))]
public class TankControllerHybrid : TankControllerBase { }
}

View File

@ -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

View File

@ -0,0 +1,8 @@
using UnityEngine;
namespace Mirror.Examples.Common.Controllers.Tank
{
[AddComponentMenu("Network/Tank Controller (Reliable)")]
[RequireComponent(typeof(NetworkTransformReliable))]
public class TankControllerReliable : TankControllerBase { }
}

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,8 @@
using UnityEngine;
namespace Mirror.Examples.Common.Controllers.Tank
{
[AddComponentMenu("Network/Tank Controller (Unreliable)")]
[RequireComponent(typeof(NetworkTransformUnreliable))]
public class TankControllerUnreliable : TankControllerBase { }
}

View File

@ -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

View File

@ -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);
}
}
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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