Add free-camera mode with player-equivalent collision
Introduce a free-fly camera as the next entry in the Tab mode cycle (Player Control -> Paint -> Free Cam): - FreeCamController: dedicated CinemachineCamera plus a CharacterController mirroring the player capsule (radius 0.1, height 1.3, slope 59, step 0.15) so the free camera collides with walls/ceiling/floor exactly like the player. WASD/Q-E (and Space/Ctrl) fly relative to view; mouse looks. - FreeCamMode: plain-C# GameMode mirroring PaintMode, with an Exit() that restores camera priority and re-enables player control + camera input. - GameModeManager: register freeCam in the Tab cycle (gated like player). - Scene: FreeCamera object wired, Main Camera custom blends (0.4s) for the vcam priority hand-off. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
0edc59555a
commit
a3ef7055b4
10 files changed
+472
-19
No files matched your search
@@ -0,0 +1,26 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 36baaa8bdcb9d8b49b9199833965d2c3, type: 3}
|
||||
m_Name: Main Camera Custom Blends
|
||||
m_EditorClassIdentifier: Unity.Cinemachine::Unity.Cinemachine.CinemachineBlenderSettings
|
||||
CustomBlends:
|
||||
- From: '**ANY CAMERA**'
|
||||
To: '**ANY CAMERA**'
|
||||
Blend:
|
||||
Style: 1
|
||||
Time: 0.4
|
||||
CustomCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve: []
|
||||
m_PreInfinity: 0
|
||||
m_PostInfinity: 0
|
||||
m_RotationOrder: 0
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 47b57a7fe51fd154e975311ad293878a
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -19,3 +19,4 @@ MonoBehaviour:
|
||||
runClip: {fileID: 0}
|
||||
runClipSpeed: 1
|
||||
ceilingYOffset: 0.84
|
||||
cameraTargetYOffset: -0.5
|
||||
@@ -19,3 +19,4 @@ MonoBehaviour:
|
||||
runClip: {fileID: 0}
|
||||
runClipSpeed: 1
|
||||
ceilingYOffset: 1
|
||||
cameraTargetYOffset: -0.55
|
||||
+186
-18
@@ -1561,7 +1561,7 @@ MonoBehaviour:
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
CustomBlends: {fileID: 0}
|
||||
CustomBlends: {fileID: 11400000, guid: 47b57a7fe51fd154e975311ad293878a, type: 2}
|
||||
--- !u!1 &347639556
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -1750,7 +1750,7 @@ Light:
|
||||
m_Type: 2
|
||||
m_Resolution: -1
|
||||
m_CustomResolution: -1
|
||||
m_Strength: 1
|
||||
m_Strength: 0.9
|
||||
m_Bias: 0.05
|
||||
m_NormalBias: 0.4
|
||||
m_NearPlane: 0.2
|
||||
@@ -2559,6 +2559,19 @@ PrefabInstance:
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: ac1cc01cb5dbf5d4f869c403b38f8a21, type: 3}
|
||||
--- !u!134 &610463647
|
||||
PhysicsMaterial:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
serializedVersion: 2
|
||||
m_DynamicFriction: 0.6
|
||||
m_StaticFriction: 0.6
|
||||
m_Bounciness: 0
|
||||
m_FrictionCombine: 0
|
||||
m_BounceCombine: 0
|
||||
--- !u!1001 &626957352
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -3020,19 +3033,6 @@ Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 4922444305696846, guid: ac1cc01cb5dbf5d4f869c403b38f8a21, type: 3}
|
||||
m_PrefabInstance: {fileID: 696901729}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!134 &699178043
|
||||
PhysicsMaterial:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
serializedVersion: 2
|
||||
m_DynamicFriction: 0.6
|
||||
m_StaticFriction: 0.6
|
||||
m_Bounciness: 0
|
||||
m_FrictionCombine: 0
|
||||
m_BounceCombine: 0
|
||||
--- !u!1001 &724408611
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -4571,7 +4571,7 @@ CharacterController:
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1141039638}
|
||||
m_Material: {fileID: 699178043}
|
||||
m_Material: {fileID: 1671101580}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
@@ -4979,6 +4979,155 @@ Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 415460, guid: ab99da1b8f76c44549a399bcdd9c8caa, type: 3}
|
||||
m_PrefabInstance: {fileID: 1253165018}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!1 &1260370184 stripped
|
||||
GameObject:
|
||||
m_CorrespondingSourceObject: {fileID: 133438, guid: 6f95ff39b42c3414d9a7ced2eea58274, type: 3}
|
||||
m_PrefabInstance: {fileID: 1860487067}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!64 &1260370189
|
||||
MeshCollider:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1260370184}
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_IsTrigger: 0
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
serializedVersion: 5
|
||||
m_Convex: 0
|
||||
m_CookingOptions: 30
|
||||
m_Mesh: {fileID: 4300000, guid: 6bb1e412df9cd4efd8941377ebabb1be, type: 3}
|
||||
--- !u!1 &1282734303
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1282734307}
|
||||
- component: {fileID: 1282734306}
|
||||
- component: {fileID: 1282734305}
|
||||
- component: {fileID: 1282734304}
|
||||
m_Layer: 0
|
||||
m_Name: FreeCamera
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1282734304
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1282734303}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 5ca7cff7542b53b498f0a9834786b9e0, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::FreeCamController
|
||||
moveSpeed: 6
|
||||
sprintMultiplier: 3
|
||||
horizontalSensitivity: 0.1
|
||||
verticalSensitivity: 0.07
|
||||
invertY: 0
|
||||
maxPitch: 89
|
||||
activePriority: 100
|
||||
inactivePriority: -100
|
||||
--- !u!143 &1282734305
|
||||
CharacterController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1282734303}
|
||||
m_Material: {fileID: 610463647}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
serializedVersion: 3
|
||||
m_Height: 1.3
|
||||
m_Radius: 0.1
|
||||
m_SlopeLimit: 59
|
||||
m_StepOffset: 0.15
|
||||
m_SkinWidth: 0.01
|
||||
m_MinMoveDistance: 0.001
|
||||
m_Center: {x: 0, y: -0.09, z: 0}
|
||||
--- !u!114 &1282734306
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1282734303}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f9dfa5b682dcd46bda6128250e975f58, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.Cinemachine::Unity.Cinemachine.CinemachineCamera
|
||||
Priority:
|
||||
Enabled: 1
|
||||
m_Value: -100
|
||||
OutputChannel: 1
|
||||
StandbyUpdate: 2
|
||||
m_StreamingVersion: 20241001
|
||||
m_LegacyPriority: 0
|
||||
Target:
|
||||
TrackingTarget: {fileID: 0}
|
||||
LookAtTarget: {fileID: 0}
|
||||
CustomLookAtTarget: 0
|
||||
Lens:
|
||||
FieldOfView: 40
|
||||
OrthographicSize: 10
|
||||
NearClipPlane: 0.1
|
||||
FarClipPlane: 5000
|
||||
Dutch: 0
|
||||
ModeOverride: 0
|
||||
PhysicalProperties:
|
||||
GateFit: 2
|
||||
SensorSize: {x: 21.946, y: 16.002}
|
||||
LensShift: {x: 0, y: 0}
|
||||
FocusDistance: 10
|
||||
Iso: 200
|
||||
ShutterSpeed: 0.005
|
||||
Aperture: 16
|
||||
BladeCount: 5
|
||||
Curvature: {x: 2, y: 11}
|
||||
BarrelClipping: 0.25
|
||||
Anamorphism: 0
|
||||
BlendHint: 0
|
||||
--- !u!4 &1282734307
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1282734303}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 1, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1001 &1317768121
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -5768,6 +5917,7 @@ MonoBehaviour:
|
||||
player: {fileID: 1141039642}
|
||||
cameraInput: {fileID: 172071431}
|
||||
painter: {fileID: 0}
|
||||
freeCam: {fileID: 1282734304}
|
||||
switchKey: 3
|
||||
startModeIndex: 0
|
||||
--- !u!4 &1516578028
|
||||
@@ -6264,6 +6414,19 @@ MeshCollider:
|
||||
m_Convex: 0
|
||||
m_CookingOptions: 30
|
||||
m_Mesh: {fileID: 4300000, guid: 0727ba69b779d42a0978a362226fad3e, type: 3}
|
||||
--- !u!134 &1671101580
|
||||
PhysicsMaterial:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: ' (Instance)'
|
||||
serializedVersion: 2
|
||||
m_DynamicFriction: 0.6
|
||||
m_StaticFriction: 0.6
|
||||
m_Bounciness: 0
|
||||
m_FrictionCombine: 0
|
||||
m_BounceCombine: 0
|
||||
--- !u!1 &1680244256
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -7029,10 +7192,14 @@ PrefabInstance:
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedComponents:
|
||||
- {fileID: 6582054, guid: 6f95ff39b42c3414d9a7ced2eea58274, type: 3}
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_AddedComponents:
|
||||
- targetCorrespondingSourceObject: {fileID: 133438, guid: 6f95ff39b42c3414d9a7ced2eea58274, type: 3}
|
||||
insertIndex: -1
|
||||
addedObject: {fileID: 1260370189}
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 6f95ff39b42c3414d9a7ced2eea58274, type: 3}
|
||||
--- !u!4 &1860487068 stripped
|
||||
Transform:
|
||||
@@ -7836,3 +8003,4 @@ SceneRoots:
|
||||
- {fileID: 686187287}
|
||||
- {fileID: 489277934}
|
||||
- {fileID: 347639557}
|
||||
- {fileID: 1282734307}
|
||||
@@ -0,0 +1,194 @@
|
||||
using UnityEngine;
|
||||
using Unity.Cinemachine;
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
using UnityEngine.InputSystem;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Free-flying spectator camera. Drives its OWN dedicated <see cref="CinemachineCamera"/> by moving
|
||||
/// this transform with WASD (relative to where it looks), Q/E (or Space/Left Ctrl) for down/up
|
||||
/// and the mouse for look. Collision is a <see cref="CharacterController"/> capsule sized like the
|
||||
/// player's, so the freecam slides along world geometry instead of clipping through walls/floors.
|
||||
///
|
||||
/// Activation is driven by <see cref="FreeCamMode"/> via <see cref="SetActive"/>: it raises the vcam
|
||||
/// priority so the CinemachineBrain blends to it, snaps the rig to the current view (no jump) and
|
||||
/// locks the cursor. While inactive the vcam rests at a low priority and this component does nothing.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(CharacterController))]
|
||||
[DisallowMultipleComponent]
|
||||
public class FreeCamController : MonoBehaviour
|
||||
{
|
||||
[Header("Flight")]
|
||||
[Tooltip("Base flight speed (units/second).")]
|
||||
public float moveSpeed = 6f;
|
||||
|
||||
[Tooltip("Speed multiplier while the sprint key (Left Shift) is held.")]
|
||||
public float sprintMultiplier = 3f;
|
||||
|
||||
[Header("Look")]
|
||||
[Tooltip("Horizontal look sensitivity (degrees of yaw per pixel).")]
|
||||
public float horizontalSensitivity = 0.2f;
|
||||
|
||||
[Tooltip("Vertical look sensitivity (degrees of pitch per pixel).")]
|
||||
public float verticalSensitivity = 0.15f;
|
||||
|
||||
[Tooltip("Invert vertical look (mouse up tilts the camera down).")]
|
||||
public bool invertY = false;
|
||||
|
||||
[Tooltip("Pitch is clamped to +/- this angle (degrees) so the camera never flips over.")]
|
||||
[Range(1f, 89.9f)]
|
||||
public float maxPitch = 89f;
|
||||
|
||||
[Header("Camera")]
|
||||
[Tooltip("Priority the dedicated CinemachineCamera takes while active (must beat the gameplay camera).")]
|
||||
public int activePriority = 100;
|
||||
|
||||
[Tooltip("Priority the dedicated CinemachineCamera rests at while inactive (must lose to the gameplay camera).")]
|
||||
public int inactivePriority = -100;
|
||||
|
||||
CharacterController _controller;
|
||||
CinemachineCamera _vcam;
|
||||
float _yaw;
|
||||
float _pitch;
|
||||
bool _ignoredPlayer;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_controller = GetComponent<CharacterController>();
|
||||
_vcam = GetComponent<CinemachineCamera>();
|
||||
if (!_vcam)
|
||||
Debug.LogError("[FreeCamController] No CinemachineCamera on this GameObject; freecam cannot go live.", this);
|
||||
else
|
||||
_vcam.Priority = inactivePriority;
|
||||
|
||||
// Dormant until FreeCamMode enters; nothing reads input or moves the rig meanwhile.
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
/// <summary>Enter/leave freecam. Raises/lowers the vcam priority, snaps to the live view and
|
||||
/// manages the cursor. Called by <see cref="FreeCamMode"/>.</summary>
|
||||
public void SetActive(bool active)
|
||||
{
|
||||
if (_vcam)
|
||||
_vcam.Priority = active ? activePriority : inactivePriority;
|
||||
|
||||
if (active)
|
||||
{
|
||||
IgnorePlayerCollision();
|
||||
SyncToCurrentView();
|
||||
SetCursorLocked(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetCursorLocked(false);
|
||||
}
|
||||
|
||||
enabled = active;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
LookTick();
|
||||
MoveTick();
|
||||
}
|
||||
|
||||
void LookTick()
|
||||
{
|
||||
Vector2 delta = ReadMouseDelta();
|
||||
_yaw += delta.x * horizontalSensitivity;
|
||||
float vs = verticalSensitivity * (invertY ? 1f : -1f);
|
||||
_pitch = Mathf.Clamp(_pitch + delta.y * vs, -maxPitch, maxPitch);
|
||||
// Rotation is unconstrained by the CharacterController; only position is.
|
||||
transform.rotation = Quaternion.Euler(_pitch, _yaw, 0f);
|
||||
}
|
||||
|
||||
void MoveTick()
|
||||
{
|
||||
Vector3 dir = ReadMoveDirection();
|
||||
if (dir.sqrMagnitude > 1f)
|
||||
dir.Normalize();
|
||||
|
||||
float speed = moveSpeed * (SprintHeld() ? sprintMultiplier : 1f);
|
||||
_controller.Move(dir * (speed * Time.deltaTime));
|
||||
}
|
||||
|
||||
/// <summary>Place the rig at the currently rendered view so going live produces no visible jump.</summary>
|
||||
void SyncToCurrentView()
|
||||
{
|
||||
var cam = Camera.main;
|
||||
if (!cam) return;
|
||||
|
||||
// CharacterController forbids direct transform writes while enabled, so toggle it for the teleport.
|
||||
bool wasEnabled = _controller.enabled;
|
||||
_controller.enabled = false;
|
||||
transform.SetPositionAndRotation(cam.transform.position, cam.transform.rotation);
|
||||
_controller.enabled = wasEnabled;
|
||||
|
||||
Vector3 e = transform.rotation.eulerAngles;
|
||||
_yaw = e.y;
|
||||
float p = e.x > 180f ? e.x - 360f : e.x;
|
||||
_pitch = Mathf.Clamp(p, -maxPitch, maxPitch);
|
||||
}
|
||||
|
||||
/// <summary>Stop the freecam capsule from being blocked by the (frozen) player's capsule.</summary>
|
||||
void IgnorePlayerCollision()
|
||||
{
|
||||
if (_ignoredPlayer) return;
|
||||
_ignoredPlayer = true;
|
||||
|
||||
var player = FindFirstObjectByType<PlayerCharacterController>();
|
||||
if (player)
|
||||
{
|
||||
var playerCC = player.GetComponent<CharacterController>();
|
||||
if (playerCC)
|
||||
Physics.IgnoreCollision(_controller, playerCC, true);
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 ReadMoveDirection()
|
||||
{
|
||||
Vector3 dir = Vector3.zero;
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
var kb = Keyboard.current;
|
||||
if (kb != null)
|
||||
{
|
||||
if (kb.wKey.isPressed) dir += transform.forward;
|
||||
if (kb.sKey.isPressed) dir -= transform.forward;
|
||||
if (kb.dKey.isPressed) dir += transform.right;
|
||||
if (kb.aKey.isPressed) dir -= transform.right;
|
||||
if (kb.eKey.isPressed || kb.spaceKey.isPressed) dir += Vector3.up;
|
||||
if (kb.qKey.isPressed || kb.leftCtrlKey.isPressed) dir -= Vector3.up;
|
||||
}
|
||||
#else
|
||||
dir += transform.forward * Input.GetAxisRaw("Vertical");
|
||||
dir += transform.right * Input.GetAxisRaw("Horizontal");
|
||||
if (Input.GetKey(KeyCode.E) || Input.GetKey(KeyCode.Space)) dir += Vector3.up;
|
||||
if (Input.GetKey(KeyCode.Q) || Input.GetKey(KeyCode.LeftControl)) dir -= Vector3.up;
|
||||
#endif
|
||||
return dir;
|
||||
}
|
||||
|
||||
bool SprintHeld()
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
return Keyboard.current != null && Keyboard.current.leftShiftKey.isPressed;
|
||||
#else
|
||||
return Input.GetKey(KeyCode.LeftShift);
|
||||
#endif
|
||||
}
|
||||
|
||||
Vector2 ReadMouseDelta()
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
return Mouse.current != null ? Mouse.current.delta.ReadValue() : Vector2.zero;
|
||||
#else
|
||||
return new Vector2(Input.GetAxisRaw("Mouse X"), Input.GetAxisRaw("Mouse Y")) * 10f;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void SetCursorLocked(bool locked)
|
||||
{
|
||||
Cursor.lockState = locked ? CursorLockMode.Locked : CursorLockMode.None;
|
||||
Cursor.visible = !locked;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ca7cff7542b53b498f0a9834786b9e0
|
||||
@@ -0,0 +1,41 @@
|
||||
/// <summary>
|
||||
/// Free-fly camera mode. The player is frozen in place, painting is off, and a dedicated
|
||||
/// freecam (<see cref="FreeCamController"/> + its own CinemachineCamera) takes over: fly the
|
||||
/// camera through the map with WASD/Q/E and the mouse, colliding with world geometry like the
|
||||
/// player. The orbit camera input is disabled while here so it doesn't fight for the cursor.
|
||||
/// </summary>
|
||||
public class FreeCamMode : GameMode
|
||||
{
|
||||
public override string DisplayName => "Free Cam";
|
||||
|
||||
public override void Enter()
|
||||
{
|
||||
// Keep the player component alive (gravity/grounding) but reject input, like Paint mode.
|
||||
if (Manager.Player)
|
||||
{
|
||||
Manager.Player.enabled = true;
|
||||
Manager.Player.ControlEnabled = false;
|
||||
}
|
||||
|
||||
if (Manager.Painter)
|
||||
Manager.Painter.PaintingEnabled = false;
|
||||
|
||||
// The orbit rig would otherwise keep grabbing the cursor and reading the mouse every frame.
|
||||
if (Manager.CameraInput)
|
||||
Manager.CameraInput.enabled = false;
|
||||
|
||||
if (Manager.FreeCam)
|
||||
Manager.FreeCam.SetActive(true);
|
||||
}
|
||||
|
||||
public override void Exit()
|
||||
{
|
||||
// Modes are Enter-only, so freecam must undo its own camera takeover here; the next mode's
|
||||
// Enter restores the player/painter/look-mode state.
|
||||
if (Manager.FreeCam)
|
||||
Manager.FreeCam.SetActive(false);
|
||||
|
||||
if (Manager.CameraInput)
|
||||
Manager.CameraInput.enabled = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a221ef0b363824540993c714c0fc7385
|
||||
@@ -5,7 +5,7 @@ using UnityEngine.InputSystem;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Switches the game between high-level modes (Player Control, Paint). The switch
|
||||
/// Switches the game between high-level modes (Player Control, Paint, Free Cam). The switch
|
||||
/// key (Tab) cycles to the next registered mode. Register additional modes in
|
||||
/// <see cref="BuildModes"/>. Attach to a single, persistent scene GameObject.
|
||||
/// </summary>
|
||||
@@ -22,6 +22,9 @@ public class GameModeManager : MonoBehaviour
|
||||
[Tooltip("Mesh painter. Empty = found automatically.")]
|
||||
public MeshPainter painter;
|
||||
|
||||
[Tooltip("Free-fly spectator camera. Empty = found automatically. Absent = Free Cam is left out of the cycle.")]
|
||||
public FreeCamController freeCam;
|
||||
|
||||
//[Tooltip("Insect / surface-walk controller. Empty = found automatically.")]
|
||||
//public InsectController insect;
|
||||
|
||||
@@ -39,6 +42,7 @@ public class GameModeManager : MonoBehaviour
|
||||
public PlayerCharacterController Player => player;
|
||||
public ThirdPersonOrbitInput CameraInput => cameraInput;
|
||||
public MeshPainter Painter => painter;
|
||||
public FreeCamController FreeCam => freeCam;
|
||||
//public InsectController Insect => insect;
|
||||
|
||||
/// <summary>Raised after a mode switch finishes (the new mode's Enter has already run).
|
||||
@@ -57,6 +61,8 @@ public class GameModeManager : MonoBehaviour
|
||||
if (!player) player = FindFirstObjectByType<PlayerCharacterController>();
|
||||
if (!cameraInput) cameraInput = FindFirstObjectByType<ThirdPersonOrbitInput>();
|
||||
if (!painter) painter = FindFirstObjectByType<MeshPainter>();
|
||||
// Include inactive so a freecam rig that starts disabled is still found and wired.
|
||||
if (!freeCam) freeCam = FindFirstObjectByType<FreeCamController>(FindObjectsInactive.Include);
|
||||
//if (!insect) insect = FindFirstObjectByType<InsectController>();
|
||||
|
||||
BuildModes();
|
||||
@@ -80,6 +86,10 @@ public class GameModeManager : MonoBehaviour
|
||||
|
||||
_modes.Add(new PaintMode());
|
||||
|
||||
// Free Cam only joins the cycle when a freecam rig is present, so scenes without one are unchanged.
|
||||
if (freeCam)
|
||||
_modes.Add(new FreeCamMode());
|
||||
|
||||
// Insect mode only joins the Tab cycle when a surface walker is present, so a scene
|
||||
// without one behaves exactly as before (no dead, player-freezing mode in the cycle).
|
||||
//if (insect)
|
||||
|
||||
Reference in new issue
Block a user