Initial project commit

This commit is contained in:
2026-01-08 16:50:20 +00:00
commit f0c5a8b267
29596 changed files with 4861782 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
using UnityEngine;
using System.Collections.Generic;
namespace Unity.Cinemachine.Samples
{
/// <summary>
/// This is a custom camera manager that selects between an aiming camera child and a
/// non-aiming camera child, depending on the value of some user input.
///
/// The Aiming child is expected to have ThirdPersonFollow and ThirdPersonAim components,
/// and to have a player as its Follow target. The player is expected to have a
/// SimplePlayerAimController behaviour on one of its children, to decouple aiminag and
/// player rotation.
/// </summary>
[ExecuteAlways]
public class AimCameraRig : CinemachineCameraManagerBase, Unity.Cinemachine.IInputAxisOwner
{
public InputAxis AimMode = InputAxis.DefaultMomentary;
SimplePlayerAimController AimController;
CinemachineVirtualCameraBase AimCamera;
CinemachineVirtualCameraBase FreeCamera;
bool IsAiming => AimMode.Value > 0.5f;
/// Report the available input axes to the input axis controller.
/// We use the Input Axis Controller because it works with both the Input package
/// and the Legacy input system. This is sample code and we
/// want it to work everywhere.
void IInputAxisOwner.GetInputAxes(List<IInputAxisOwner.AxisDescriptor> axes)
{
axes.Add(new () { DrivenAxis = () => ref AimMode, Name = "Aim" });
}
void OnValidate() => AimMode.Validate();
protected override void Start()
{
base.Start();
// Find the player and the aiming camera.
// We expect to have one camera with a CinemachineThirdPersonAim component
// whose Follow target is a player with a SimplePlayerAimController child.
for (int i = 0; i < ChildCameras.Count; ++i)
{
var cam = ChildCameras[i];
if (!cam.isActiveAndEnabled)
continue;
if (AimCamera == null
&& cam.TryGetComponent<CinemachineThirdPersonAim>(out var aim)
&& aim.NoiseCancellation)
{
AimCamera = cam;
var player = AimCamera.Follow;
if (player != null)
AimController = player.GetComponentInChildren<SimplePlayerAimController>();
}
else if (FreeCamera == null)
FreeCamera = cam;
}
if (AimCamera == null)
Debug.LogError("AimCameraRig: no valid CinemachineThirdPersonAim camera found among children");
if (AimController == null)
Debug.LogError("AimCameraRig: no valid SimplePlayerAimController target found");
if (FreeCamera == null)
Debug.LogError("AimCameraRig: no valid non-aiming camera found among children");
}
protected override CinemachineVirtualCameraBase ChooseCurrentCamera(Vector3 worldUp, float deltaTime)
{
var oldCam = (CinemachineVirtualCameraBase)LiveChild;
var newCam = IsAiming ? AimCamera : FreeCamera;
if (AimController != null && oldCam != newCam)
{
// Set the mode of the player aim controller.
// We want the player rotation to be copuled to the camera when aiming, otherwise not.
AimController.PlayerRotation = IsAiming
? SimplePlayerAimController.CouplingMode.Coupled
: SimplePlayerAimController.CouplingMode.Decoupled;
AimController.RecenterPlayer();
}
return newCam;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c8861e25f312f0a4ea14b26b40531fa4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,78 @@
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
/// <summary>
/// When there is an active ThirdPersonFollow camera with noise cancellation,
/// the position of this object is the aim target for the ThirdPersonAim camera.
/// </summary>
public class AimTargetManager : MonoBehaviour
{
[Tooltip("This canvas will be enabled when there is a 3rdPersoAim camera active")]
public Canvas ReticleCanvas;
[Tooltip("If non-null, this target will pe positioned on the screen over the actual aim target")]
public RectTransform AimTargetIndicator;
bool m_HaveAimTarget;
// We add a CameraUpdatedEvent listener so that we are guaranteed to update after the
// Brain has positioned the camera
void OnEnable() => CinemachineCore.CameraUpdatedEvent.AddListener(SetAimTarget);
void OnDisable() => CinemachineCore.CameraUpdatedEvent.RemoveListener(SetAimTarget);
// This is called after the Brain has positioned the camera. If the camera has a
// ThirdPersonAim component with noise cancellation, then we set the aim target
// position to be precisely what the camera is indicating onscreen.
// Otherwise, we disable the reticle and the aim target indicator.
void SetAimTarget(CinemachineBrain brain)
{
m_HaveAimTarget = false;
if (brain == null || brain.OutputCamera == null)
CinemachineCore.CameraUpdatedEvent.RemoveListener(SetAimTarget);
else
{
CinemachineCamera liveCam;
if (brain.ActiveVirtualCamera is CinemachineCameraManagerBase managerCam)
liveCam = managerCam.LiveChild as CinemachineCamera;
else
liveCam = brain.ActiveVirtualCamera as CinemachineCamera;
if (liveCam != null)
{
if (liveCam.TryGetComponent<CinemachineThirdPersonAim>(out var aim) && aim.enabled)
{
// Set the worldspace aim target position so that we can know what gets hit
m_HaveAimTarget = aim.NoiseCancellation;
transform.position = aim.AimTarget;
// Set the screen-space hit target indicator position
if (AimTargetIndicator != null)
AimTargetIndicator.position = brain.OutputCamera.WorldToScreenPoint(transform.position);
}
}
}
if (ReticleCanvas != null)
ReticleCanvas.enabled = m_HaveAimTarget;
}
/// <summary>
/// Called by the player's shooting object to get the aim direction override, in case
/// there is an active ThirdPersonFollow camera with noise cancellation.
/// </summary>
/// <param name="firingOrigin">Where the firing will come from.</param>
/// <param name="firingDirection">The intended firing direction.</param>
/// <returns>The direction in which to fire</returns>
public Vector3 GetAimDirection(Vector3 firingOrigin, Vector3 firingDirection)
{
if (m_HaveAimTarget)
{
var dir = transform.position - firingOrigin;
var len = dir.magnitude;
if (len > 0.0001f)
return dir / len;
}
return firingDirection;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 512059070b04a4ca6b07b64f00733c27
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,58 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
namespace Unity.Cinemachine.Samples
{
public class CursorLockManager : MonoBehaviour, Unity.Cinemachine.IInputAxisOwner
{
public InputAxis CursorLock = InputAxis.DefaultMomentary;
public UnityEvent OnCursorLocked = new ();
public UnityEvent OnCursorUnlocked = new ();
bool m_IsTriggered;
public void GetInputAxes(List<IInputAxisOwner.AxisDescriptor> axes)
{
axes.Add(new()
{
DrivenAxis = () => ref CursorLock, Name = "CursorLock",
Hint = IInputAxisOwner.AxisDescriptor.Hints.X
});
}
void OnValidate() => CursorLock.Validate();
void OnEnable() => UnlockCursor();
void OnDisable() => UnlockCursor();
void Update()
{
if (CursorLock.Value == 0)
m_IsTriggered = false;
else if (!m_IsTriggered)
{
m_IsTriggered = true;
if (Cursor.lockState == CursorLockMode.None)
LockCursor();
else
UnlockCursor();
}
}
public void LockCursor()
{
if (enabled)
{
Cursor.lockState = CursorLockMode.Locked;
OnCursorLocked.Invoke();
}
}
public void UnlockCursor()
{
Cursor.lockState = CursorLockMode.None;
OnCursorUnlocked.Invoke();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 84bbe8a9281f49f9801ffc0203bee6df
timeCreated: 1682089493

View File

@@ -0,0 +1,48 @@
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
/// <summary>
/// Will match a GameObject's position and rotation to a target's position
/// and rotation, with damping
/// </summary>
[ExecuteAlways]
public class DampedTracker : MonoBehaviour
{
[Tooltip("The target to track")]
public Transform Target;
[Tooltip("How fast the GameObject moves to the target position")]
public float PositionDamping = 1;
[Tooltip("How fast the rotation aligns with target rotation")]
public float RotationDamping = 1;
void OnEnable()
{
if (Target != null)
transform.SetPositionAndRotation(Target.position, Target.rotation);
}
void LateUpdate()
{
if (Target != null)
{
// Match the player's position
float t = Damper.Damp(100, PositionDamping, Time.deltaTime) * 0.01f;
var pos = Vector3.Lerp(transform.position, Target.position, t);
// Rotate my transform to make my up match the target's up
var rot = transform.rotation;
t = Damper.Damp(100, RotationDamping, Time.deltaTime) * 0.01f;
var srcUp = transform.up;
var dstUp = Target.up;
var axis = Vector3.Cross(srcUp, dstUp);
if (axis.sqrMagnitude > 0.000001f)
{
var angle = UnityVectorExtensions.SignedAngle(srcUp, dstUp, axis) * t;
rot = Quaternion.AngleAxis(angle, axis) * rot;
}
transform.SetPositionAndRotation(pos, rot);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a51048181b52dd044a17566021c7c163
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,72 @@
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
public class EarlyLookAtCustomBlender : MonoBehaviour, CinemachineBlend.IBlender
{
// CinemachineBlend.IBlender implementation:
// This method is free to blend the states any way it likes.
// In this case, we do a default blend then override the rotation to make
// it happen at the beginning of the blend.
public CameraState GetIntermediateState(ICinemachineCamera CamA, ICinemachineCamera CamB, float t)
{
var stateA = CamA.State;
var stateB = CamB.State;
// Standard blend - first we disable cylindrical position
stateA.BlendHint &= ~CameraState.BlendHints.CylindricalPositionBlend;
stateB.BlendHint &= ~CameraState.BlendHints.CylindricalPositionBlend;
var state = CameraState.Lerp(stateA, stateB, t);
// Override the rotation blend: look directly at the new target
// at the start of the blend
const float kFinishRotatingAt = 0.2f;
var rotB = Quaternion.LookRotation(
stateB.ReferenceLookAt - state.RawPosition, state.ReferenceUp);
state.RawOrientation = Quaternion.Slerp(
stateA.RawOrientation, rotB, Damper.Damp(1, kFinishRotatingAt, t));
return state;
}
void OnEnable() => CinemachineCore.GetCustomBlender += GetCustomBlender;
void OnDisable() => CinemachineCore.GetCustomBlender -= GetCustomBlender;
// CinemachineCore.GetCustomBlender handler
CinemachineBlend.IBlender GetCustomBlender(ICinemachineCamera camA, ICinemachineCamera camB)
{
// Override the blender with a custom blender if the game state demands it
if (m_UseCustomBlend)
return this;
// Use default blender
return null;
}
// The remainder of this code is demo-specific implementation
bool m_UseCustomBlend;
// Callback for UX button
public void DefaultBlend()
{
m_UseCustomBlend = false;
ChangeCamera();
}
// Callback for UX button
public void CustomBlend()
{
m_UseCustomBlend = true;
ChangeCamera();
}
void ChangeCamera()
{
// Cycle through all the virtual cameras, assuming that they all have the same priority.
// Prioritize the least-recently used one.
int numCameras = CinemachineCore.VirtualCameraCount;
CinemachineCore.GetVirtualCamera(numCameras - 1).Prioritize();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 16eca234a25d87f4e9a54ac550e684e9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,68 @@
using System;
using UnityEngine;
using UnityEngine.UI;
namespace Unity.Cinemachine.Samples
{
/// <summary>
/// Reticle control for when the aiming is inaccurate. Inaccuracy is shown by pulling apart the aim reticle.
/// </summary>
public class ExpandingAimReticle : MonoBehaviour
{
[Tooltip("Maximum radius of the aim reticle, when aiming is inaccurate. ")]
[Vector2AsRange]
public Vector2 RadiusRange;
[Tooltip("The time is takes for the aim reticle to adjust, when inaccurate.")]
[Range(0, 1f)]
public float BlendTime;
[Tooltip("Top piece of the aim reticle.")]
public Image Top;
[Tooltip("Bottom piece of the aim reticle.")]
public Image Bottom;
[Tooltip("Left piece of the aim reticle.")]
public Image Left;
[Tooltip("Right piece of the aim reticle.")]
public Image Right;
[Tooltip("This 2D object will be positioned in the game view over the raycast hit point, if any, "
+ "or will remain in the center of the screen if no hit point is detected. "
+ "May be null, in which case no on-screen indicator will appear.")]
public RectTransform AimTargetReticle;
float m_BlendVelocity;
float m_CurrentRadius;
void OnValidate()
{
RadiusRange.x = Mathf.Clamp(RadiusRange.x, 0, 100);
RadiusRange.y = Mathf.Clamp(RadiusRange.y, RadiusRange.x, 100);
}
void Reset()
{
RadiusRange = new Vector2(2.5f, 40f);
BlendTime = 0.05f;
}
void Update()
{
var screenCenterPoint = new Vector2(Screen.width * 0.5f, Screen.height * 0.5f);
float distanceFromCenter = 0;
if (AimTargetReticle != null)
{
var hitPoint = (Vector2)AimTargetReticle.position;
distanceFromCenter = (screenCenterPoint - hitPoint).magnitude;
}
m_CurrentRadius = Mathf.SmoothDamp(m_CurrentRadius, distanceFromCenter * 2f, ref m_BlendVelocity, BlendTime);
m_CurrentRadius = Mathf.Clamp(m_CurrentRadius, RadiusRange.x, RadiusRange.y);
Left.rectTransform.position = screenCenterPoint + (Vector2.left * m_CurrentRadius);
Right.rectTransform.position = screenCenterPoint + (Vector2.right * m_CurrentRadius);
Top.rectTransform.position = screenCenterPoint + (Vector2.up * m_CurrentRadius);
Bottom.rectTransform.position = screenCenterPoint + (Vector2.down * m_CurrentRadius);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ae1b182fbad6e0f4fbf3a51e56784830
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,75 @@
using System;
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
/// <summary>
/// An example add-on module for Cinemachine Camera for controlling
/// the FadeOut shader included in our example package.
/// </summary>
[ExecuteAlways]
public class FadeOutShaderController : CinemachineExtension
{
/// <summary>Radius of the look at target.</summary>
[Tooltip("Radius of the look at target.")]
public float LookAtTargetRadius = 1;
/// <summary>
/// The range is defined from the camera (x value) towards the look direction (y value) within which objects
/// with FadeOut material are going to be transparent.
/// The strength of the transparency depends on how far the objects are from the camera and the FadeOut range.
/// </summary>
[Tooltip("The range is defined from the camera (x value) towards the look direction (y value) within which " +
"objects with FadeOut material are going to be transparent. \nThe strength of the transparency depends " +
"on how far the objects are from the camera and the FadeOut range.")]
[MinMaxRangeSlider(0, 20)]
public Vector2 FadeOutRange = new (0f, 10f);
/// <summary>
/// If true, m_MaxDistance will be set to
/// distance between this virtual camera and LookAt target minus m_LookAtTargetRadius.
/// </summary>
[Tooltip("If true, MaxDistance will be set to " +
"distance between this virtual camera and LookAt target minus LookAtTargetRadius.")]
public bool MaxDistanceControlledByCamera = true;
/// <summary>Material using the FadeOut shader.</summary>
[Tooltip("Material using the FadeOut shader.")]
public Material FadeOutMaterial;
static readonly int k_MaxDistanceID = Shader.PropertyToID("_MaxDistance");
static readonly int k_MinDistanceID = Shader.PropertyToID("_MinDistance");
/// <summary>Updates FadeOut shader on the specified FadeOutMaterial.</summary>
/// <param name="vcam">The virtual camera being processed</param>
/// <param name="stage">The current pipeline stage</param>
/// <param name="state">The current virtual camera state</param>
/// <param name="deltaTime">The current applicable deltaTime</param>
protected override void PostPipelineStageCallback(
CinemachineVirtualCameraBase vcam,
CinemachineCore.Stage stage, ref CameraState state, float deltaTime)
{
if (stage == CinemachineCore.Stage.Finalize)
{
if (FadeOutMaterial == null ||
!FadeOutMaterial.HasProperty(k_MaxDistanceID) ||
!FadeOutMaterial.HasProperty(k_MinDistanceID))
return;
if (MaxDistanceControlledByCamera && vcam.LookAt != null)
FadeOutRange.y = Vector3.Distance(vcam.transform.position, vcam.LookAt.position) - LookAtTargetRadius;
FadeOutMaterial.SetFloat(k_MinDistanceID, FadeOutRange.x);
FadeOutMaterial.SetFloat(k_MaxDistanceID, FadeOutRange.y);
}
}
void OnValidate()
{
LookAtTargetRadius = Math.Max(0, LookAtTargetRadius);
FadeOutRange.x = Math.Max(0, FadeOutRange.x);
FadeOutRange.y = Math.Max(0, FadeOutRange.y);
FadeOutRange.y = Math.Max(FadeOutRange.x, FadeOutRange.y);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 28e3f754a40cc487cac0f59efb1118db
timeCreated: 1611253266

View File

@@ -0,0 +1,100 @@
using System.Collections.Generic;
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
/// <summary>
/// This behaviour makes a GameObject fly around in response to user input.
/// Movement is relative to the GameObject's local axes.
///
/// </summary>
public class FlyAround : MonoBehaviour, Unity.Cinemachine.IInputAxisOwner
{
[Tooltip("Speed when moving")]
public float Speed = 10;
[Tooltip("Speed multiplier when sprinting")]
public float SprintMultiplier = 4;
[Header("Input Axes")]
[Tooltip("X Axis movement. Value is -1..1. Controls the sideways movement")]
public InputAxis Sideways = InputAxis.DefaultMomentary;
[Tooltip("Y Axis movement. Value is -1..1. Controls the vertical movement")]
public InputAxis UpDown = InputAxis.DefaultMomentary;
[Tooltip("Z Axis movement. Value is -1..1. Controls the forward movement")]
public InputAxis Forward = InputAxis.DefaultMomentary;
[Tooltip("Horizontal rotation. Value is -1..1.")]
public InputAxis Pan = DefaultPan;
[Tooltip("Vertical rotation. Value is -1..1.")]
public InputAxis Tilt = DefaultTilt;
[Tooltip("Sprint movement. Value is 0 or 1. If 1, then is sprinting")]
public InputAxis Sprint = InputAxis.DefaultMomentary;
static InputAxis DefaultPan => new ()
{ Value = 0, Range = new Vector2(-180, 180), Wrap = true, Center = 0, Restrictions = InputAxis.RestrictionFlags.NoRecentering };
static InputAxis DefaultTilt => new ()
{ Value = 0, Range = new Vector2(-70, 70), Wrap = false, Center = 0, Restrictions = InputAxis.RestrictionFlags.NoRecentering };
/// Report the available input axes to the input axis controller.
/// We use the Input Axis Controller because it works with both the Input package
/// and the Legacy input system. This is sample code and we
/// want it to work everywhere.
void IInputAxisOwner.GetInputAxes(List<IInputAxisOwner.AxisDescriptor> axes)
{
axes.Add(new () { DrivenAxis = () => ref Sideways, Name = "Move X", Hint = IInputAxisOwner.AxisDescriptor.Hints.X });
axes.Add(new () { DrivenAxis = () => ref Forward, Name = "Forward" });
axes.Add(new () { DrivenAxis = () => ref UpDown, Name = "Move Y", Hint = IInputAxisOwner.AxisDescriptor.Hints.Y });
axes.Add(new () { DrivenAxis = () => ref Pan, Name = "Look X (Pan)", Hint = IInputAxisOwner.AxisDescriptor.Hints.X });
axes.Add(new () { DrivenAxis = () => ref Tilt, Name = "Look Y (Tilt)", Hint = IInputAxisOwner.AxisDescriptor.Hints.Y });
axes.Add(new () { DrivenAxis = () => ref Sprint, Name = "Sprint" });
}
void OnValidate()
{
Sideways.Validate();
UpDown.Validate();
Forward.Validate();
Pan.Validate();
Tilt.Range.x = Mathf.Clamp(Tilt.Range.x, -90, 90);
Tilt.Range.y = Mathf.Clamp(Tilt.Range.y, -90, 90);
Tilt.Validate();
Sprint.Validate();
}
void Reset()
{
Speed = 10;
SprintMultiplier = 4;
Sideways = InputAxis.DefaultMomentary;
Forward = InputAxis.DefaultMomentary;
UpDown = InputAxis.DefaultMomentary;
Pan = DefaultPan;
Tilt = DefaultTilt;
Sprint = InputAxis.DefaultMomentary;
}
void OnEnable()
{
// Take rotation from the transform
var euler = transform.rotation.eulerAngles;
Pan.Value = euler.y;
Tilt.Value = euler.x;
}
void Update()
{
// Calculate the move direction and speed based on input
var rot = Quaternion.Euler(Tilt.Value, Pan.Value, 0);
var movement = rot * new Vector3(Sideways.Value, UpDown.Value, Forward.Value);
var speed = Sprint.Value < 0.01f ? Speed : Speed * SprintMultiplier;
// Apply motion
transform.SetPositionAndRotation(transform.position + speed * Time.deltaTime * movement, rot);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1e1d63f642ebbdb4eb38a57a250ab00d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,16 @@
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
public class LockPosZ : MonoBehaviour
{
public float ZPosiion;
void LateUpdate()
{
var pos = transform.position;
pos.z = ZPosiion;
transform.position = pos;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eed4d084bee9a604a9bd6dcfe5987fa4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,51 @@
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
/// <summary>
/// Orients the GameObject that this script is attached in such a way that it always faces the Target.
/// </summary>
[ExecuteAlways]
public class LookAtTarget : MonoBehaviour
{
[Tooltip("Target to look at.")]
public Transform Target;
[Tooltip("Lock rotation along the x axis to the initial value.")]
public bool LockRotationX;
[Tooltip("Lock rotation along the y axis to the initial value.")]
public bool LockRotationY;
[Tooltip("Lock rotation along the z axis to the initial value.")]
public bool LockRotationZ;
Vector3 m_Rotation;
void OnEnable()
{
m_Rotation = transform.rotation.eulerAngles;
}
void Reset()
{
m_Rotation = transform.rotation.eulerAngles;
}
void Update()
{
if (Target != null)
{
var direction = Target.position - transform.position;
transform.rotation = Quaternion.LookRotation(direction);
if (LockRotationX || LockRotationY || LockRotationZ)
{
var euler = transform.rotation.eulerAngles;
euler.x = LockRotationX ? m_Rotation.x : euler.x;
euler.y = LockRotationY ? m_Rotation.y : euler.y;
euler.z = LockRotationZ ? m_Rotation.z : euler.z;
transform.rotation = Quaternion.Euler(euler);
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 703a8ce5f266d408ca92818e87835af3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,22 @@
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
[ExecuteInEditMode]
public class Magnet : MonoBehaviour
{
[Range(0.1f, 50.0f)]
public float Strength = 5.0f;
[Range(0.1f, 50.0f)]
public float Range = 5.0f;
public Transform RangeVisualizer;
void Update()
{
if (RangeVisualizer != null)
RangeVisualizer.localScale = new Vector3(Range * 2.0f, Range * 2.0f, 1);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6aefb4603cbd49d4691e1aea49f45b00
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,29 @@
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
public class MagnetGroupController : MonoBehaviour
{
public CinemachineTargetGroup TargetGroup;
void Update()
{
if (TargetGroup == null || TargetGroup.IsEmpty)
return;
// We assume that the player is at group index 0
var targets = TargetGroup.Targets;
var playerPos = targets[0].Object.position;
for (int i = 1; i < targets.Count; ++i)
{
var t = targets[i];
if (t.Object != null && t.Object.TryGetComponent<Magnet>(out var magnet))
{
// Reduce the member weight as the distance from player increases
var distance = (playerPos - t.Object.position).magnitude;
t.Weight = magnet.Strength * Mathf.Max(0, 1 - distance / magnet.Range);
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0f6129fefa8c2eb41a2b757c190472af
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,36 @@
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
[RequireComponent(typeof(CinemachineMixingCamera))]
public class MixCamerasBasedOnSpeed : MonoBehaviour
{
[Tooltip("At and above this speed, the second camera is going to be in control completely.")]
public float MaxSpeed;
public Rigidbody Rigidbody;
CinemachineMixingCamera m_Mixer;
void Start()
{
m_Mixer = GetComponent<CinemachineMixingCamera>();
}
void OnValidate()
{
MaxSpeed = Mathf.Max(1, MaxSpeed);
}
void FixedUpdate()
{
if (Rigidbody == null)
return;
#if UNITY_2023_3_OR_NEWER
var t = Mathf.Clamp01(Rigidbody.linearVelocity.magnitude / MaxSpeed);
#else
var t = Mathf.Clamp01(Rigidbody.velocity.magnitude / MaxSpeed);
#endif
m_Mixer.Weight0 = 1 - t;
m_Mixer.Weight1 = t;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a508fe0a084bc46889cc2017b857a97a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,110 @@
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
/// <summary>
/// This behaviour implements a custom algorithm to provide smooth blends between perspective
/// and ortho cameras. Drop it into your scene, and while enabled it will override the default
/// blending algorithms when appropriate.
///
/// Specifically, if one camera is perspective and the other is orthographic, and if the orthographic
/// camera has a LookAt target, the custom blend will be used.
///
/// Because it's not possible to lerp between perspective and ortho cameras, we approximate the ortho
/// camera with a perspective camera placed far away with a small fov, and blend to that. Afterwards,
/// we simply cut to the ortho camera.
///
/// This class illustrates the use of the CinemachineCore.GetCustomBlender hook, and shows how to
/// implement the CinemachineBlend.IBlender interface.
/// </summary>
[ExecuteAlways]
public class PerspectiveToOrthoCustomBlender : MonoBehaviour, CinemachineBlend.IBlender
{
[Tooltip("Minimum distance at which to place the perspective camera which will mimic the orthographic one. \n"
+ "Changing this distance may affect the feel of the blend: a large distance will produce a better approximation "
+ "of the ortho camera, but will also make the FOV change happen more quickly at the start of the blend. \n"
+ "Keep this distance as small as you can tolerate, to avoid precision errors which can be present at "
+ "large camera distances.")]
public float FakeOrthoCameraDistance = 100;
void OnEnable() => CinemachineCore.GetCustomBlender += GetCustomBlender;
void OnDisable() => CinemachineCore.GetCustomBlender -= GetCustomBlender;
// CinemachineCore.GetCustomBlender handler
CinemachineBlend.IBlender GetCustomBlender(ICinemachineCamera camA, ICinemachineCamera camB)
{
// Use the custom blender if and only if we're transitioning between ortho and perspective cameras
if (camA != null && camB != null)
{
var stateA = camA.State;
var stateB = camB.State;
if (IsBlendToOrthoCandidate(ref stateA, ref stateB))
return this;
}
// Use default blender
return null;
}
// CinemachineBlend.IBlender implementation
public CameraState GetIntermediateState(ICinemachineCamera camA, ICinemachineCamera camB, float t)
{
var stateA = camA.State;
var stateB = camB.State;
// This can happen if we're blending intermediate states due to interrupted blend
if (!IsBlendToOrthoCandidate(ref stateA, ref stateB))
return CameraState.Lerp(stateA, stateB, t);
if (!stateA.Lens.Orthographic)
return BlendToOrtho(ref stateA, ref stateB, t);
return BlendToOrtho(ref stateB, ref stateA, 1-t);
}
bool IsBlendToOrthoCandidate(ref CameraState stateA, ref CameraState stateB)
{
bool orthoA = stateA.Lens.Orthographic;
bool orthoB = stateB.Lens.Orthographic;
// A lookAt target is required on the ortho camera in order to establish the mimic fov
return orthoA != orthoB && ((orthoA && stateA.HasLookAt()) || (orthoB && stateB.HasLookAt()));
}
// Replaces stateB with a fake ortho camera which is a far-away perspective camera with a small fov
CameraState BlendToOrtho(ref CameraState stateA, ref CameraState stateB, float t)
{
var lensB = stateB.Lens;
var orthoSize = lensB.OrthographicSize;
var lookAt = stateB.ReferenceLookAt;
if (!stateA.HasLookAt())
stateA.ReferenceLookAt = lookAt;
var distanceFromTarget = Vector3.Distance(lookAt, stateB.GetCorrectedPosition());
// We want it to be far compared to the ortho size
var extraDistance = Mathf.Max(0, Mathf.Max(FakeOrthoCameraDistance, orthoSize * 20) - distanceFromTarget);
var rotB = stateB.GetFinalOrientation();
stateB.RawPosition = stateB.GetCorrectedPosition() + rotB * Vector3.back * extraDistance;
stateB.PositionCorrection = Vector3.zero;
stateB.ReferenceUp = rotB * Vector3.up;
// Force a spherical position algorithm
stateB.BlendHint |= CameraState.BlendHints.SphericalPositionBlend;
// The fov should be such as to produce the ortho size at the target's position
var lens = stateA.Lens;
lens.FieldOfView = 2f * Mathf.Atan(orthoSize / (extraDistance + distanceFromTarget)) * Mathf.Rad2Deg;
// Lerp the clip planes to reduce popping
lens.NearClipPlane = Mathf.Max(lens.NearClipPlane, extraDistance + lensB.NearClipPlane);
lens.FarClipPlane = extraDistance + lensB.FarClipPlane;
stateB.Lens = lens;
// We square t to spend more time at the start of the blend, producing a smoother result
// when the fake ortho camera is far away. This could potentially be tweaked.
return CameraState.Lerp(stateA, stateB, t * t);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 51f0a49e6b834fa47872db1fbc7bcbdb

View File

@@ -0,0 +1,43 @@
using System;
using UnityEngine;
using UnityEngine.Splines;
namespace Unity.Cinemachine.Samples
{
[Serializable]
public class RandomizedDollySpeed : SplineAutoDolly.ISplineAutoDolly
{
[Tooltip("Minimum speed the cart can travel")]
public float MinSpeed = 2;
[Tooltip("Maximum speed the cart can travel")]
public float MaxSpeed = 10;
[Tooltip("How quickly the cart can change speed")]
public float Acceleration = 1;
float m_Speed;
float m_TargetSpeed;
void SplineAutoDolly.ISplineAutoDolly.Validate() => MaxSpeed = Mathf.Max(MaxSpeed, MinSpeed);
void SplineAutoDolly.ISplineAutoDolly.Reset() => m_Speed = m_TargetSpeed = (MinSpeed + MaxSpeed) / 2;
bool SplineAutoDolly.ISplineAutoDolly.RequiresTrackingTarget => false;
public float GetSplinePosition(
MonoBehaviour sender, Transform target,
SplineContainer spline, float currentPosition,
PathIndexUnit positionUnits, float deltaTime)
{
if (Application.isPlaying && deltaTime > 0)
{
if (Mathf.Abs(m_Speed - m_TargetSpeed) < 0.01f)
m_TargetSpeed = UnityEngine.Random.Range(MinSpeed, MaxSpeed);
if (m_Speed < m_TargetSpeed)
m_Speed = Mathf.Min(m_TargetSpeed, m_Speed + Acceleration * deltaTime);
if (m_Speed > m_TargetSpeed)
m_Speed = Mathf.Max(m_TargetSpeed, m_Speed - Acceleration * deltaTime);
return currentPosition + m_Speed * deltaTime;
}
return currentPosition;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d73e77c46508cf441ad464dc058f7a86
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,43 @@
using System;
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
[RequireComponent(typeof(CinemachineTargetGroup))]
public class ReduceGroupWeightWhenBelowFloor : MonoBehaviour
{
[Tooltip("The platform from which LoseSightAtRange is calculated")]
public Transform Floor;
[Tooltip("The weight of a transform in the target group is 1 when above the Floor. When a transform is " +
"below the Floor, then its weight decreases based on the distance between the transform and the " +
"Floor and it reaches 0 at LoseSightAtRange. If you set this value to 0, then the transform is removed " +
"instantly when below the Floor.")]
[Range(0, 30)]
public float LoseSightAtRange = 20;
CinemachineTargetGroup m_TargetGroup;
void Awake()
{
m_TargetGroup = GetComponent<CinemachineTargetGroup>();
}
void Update()
{
// iterate through each target in the targetGroup
var floor = Floor.position.y;
for (int i = 0; i < m_TargetGroup.Targets.Count; ++i)
{
var target = m_TargetGroup.Targets[i];
// calculate the distance between target and the Floor along the Y axis
var distanceBelow = floor - target.Object.position.y;
// weight goes to 0 if it's farther below than LoseSightAtRange
var weight = Mathf.Clamp(1 - distanceBelow / Mathf.Max(0.001f, LoseSightAtRange), 0, 1);
target.Weight = weight;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: af2f2697aa2d246eaa1bbac01e1cff23
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,31 @@
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
/// <summary>This behaviour works in conjunction with PlayerOnSurface to keep the player
/// parented to the surface it's standing on. This is useful to prevent sliding
/// when the surface is in motion</summary>
[RequireComponent(typeof(SimplePlayerOnSurface))]
public class ReparentPlayerToSurface : MonoBehaviour
{
SimplePlayerOnSurface m_PlayerOnSurface;
void OnEnable()
{
if (TryGetComponent(out m_PlayerOnSurface))
m_PlayerOnSurface.SurfaceChanged.AddListener(OnSurfaceChanged);
}
void OnDisable()
{
if (m_PlayerOnSurface != null)
m_PlayerOnSurface.SurfaceChanged.RemoveListener(OnSurfaceChanged);
}
// newSurface may be null, in which case we should just uparent the player
void OnSurfaceChanged(Collider newSurface)
{
transform.SetParent(newSurface == null ? null : newSurface.transform, true);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d02e38cc82ce35043b04296eeaff6f29
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,17 @@
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
/// <summary>
/// Helper class to set the interpolation of a rigidbody.
/// </summary>
[RequireComponent(typeof(Rigidbody))]
public class RigidbodyInterpolationSetter : MonoBehaviour
{
Rigidbody m_Rigidbody;
void Start() => m_Rigidbody = GetComponent<Rigidbody>();
public void SetInterpolation(bool on) =>
m_Rigidbody.interpolation = on ? RigidbodyInterpolation.Interpolate : RigidbodyInterpolation.None;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2ba786fa8607649dab5568a9a20a8906
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,67 @@
using UnityEngine;
using UnityEngine.Splines;
namespace Unity.Cinemachine.Samples
{
[RequireComponent(typeof(CinemachineSplineCart))]
public class RunnerController : MonoBehaviour
{
public float WaitTimeAtStart = 1;
CinemachineSplineCart m_Cart;
static bool s_LeaderWasSlowed;
bool m_IsTired;
float m_StartTime;
void OnEnable()
{
m_Cart = GetComponent<CinemachineSplineCart>();
ResetRace();
}
void Update()
{
m_Cart.AutomaticDolly.Enabled = Time.time > m_StartTime + WaitTimeAtStart;
// Slow down leader to improve chances of at least 1 takeover per run
if (!s_LeaderWasSlowed)
{
if (m_Cart.Spline.Spline.ConvertIndexUnit(
m_Cart.SplinePosition, m_Cart.PositionUnits, PathIndexUnit.Normalized) > 0.5f)
{
s_LeaderWasSlowed = true;
if (m_Cart.AutomaticDolly.Method is RandomizedDollySpeed speedControl)
{
// Leader is tired!
speedControl.MinSpeed /= 2;
speedControl.MaxSpeed /= 2;
m_IsTired = true;
}
}
}
}
// This is called by the "Restart Race" UX button.
public void ResetRace()
{
// Reset position to start
m_Cart.SplinePosition = 0;
m_StartTime = Time.time;
m_Cart.AutomaticDolly.Enabled = false;
// Reset slowdown mechanism
if (m_Cart.AutomaticDolly.Method is RandomizedDollySpeed speedControl)
{
m_Cart.AutomaticDolly.Method.Reset();
if (m_IsTired)
{
// Restore the speed, leader is no longer tired
speedControl.MinSpeed *= 2;
speedControl.MaxSpeed *= 2;
}
}
m_IsTired = false;
s_LeaderWasSlowed = false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 29d5b71c25a12441dbb6f920533a92df
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UIElements;
namespace Unity.Cinemachine.Samples
{
/// <summary>
/// Runtime UI script that can create toggles and buttons.
/// </summary>
[RequireComponent(typeof(UIDocument))]
public class SamplesDynamicUI : MonoBehaviour
{
[Serializable]
public class Item
{
[Tooltip("Text displayed on button")]
public string Name = "Text";
[Serializable]
public class ToggleSettings
{
public bool Enabled;
[Tooltip("Initial value of the toggle")]
public bool Value;
[Tooltip("Event sent when toggle button is clicked")]
public UnityEvent<bool> OnValueChanged = new();
}
[Tooltip("Set this to true to create a toggle button")]
[FoldoutWithEnabledButton]
public ToggleSettings IsToggle = new();
[Tooltip("Event sent when button is clicked")]
public UnityEvent OnClick = new();
}
[Tooltip("The buttons to be displayed")]
public List<Item> Buttons = new();
VisualElement m_Root;
readonly List<VisualElement> m_DynamicElements = new ();
void OnEnable()
{
var uiDocument = GetComponent<UIDocument>();
m_Root = uiDocument.rootVisualElement.Q("TogglesAndButtons"); // Should be justified - flex grow 1
if (m_Root == null)
{
Debug.LogError("Cannot find TogglesAndButtons. Is the source asset set in the UIDocument?");
return;
}
for (int i = 0; i < Buttons.Count; ++i)
{
var item = Buttons[i];
if (item.IsToggle.Enabled)
{
var toggle = new Toggle
{
label = item.Name,
value = item.IsToggle.Value,
focusable = false,
};
toggle.AddToClassList("dynamicToggle");
toggle.RegisterValueChangedCallback(e => item.IsToggle.OnValueChanged.Invoke(e.newValue));
m_Root.Add(toggle);
m_DynamicElements.Add(toggle);
}
else
{
var button = new Button
{
text = item.Name,
focusable = false
};
button.AddToClassList("dynamicButton");
button.clickable.clicked += item.OnClick.Invoke;
m_Root.Add(button);
m_DynamicElements.Add(button);
}
}
m_Root.visible = Buttons.Count > 0;
}
void OnDisable()
{
for (int i = 0; i < m_DynamicElements.Count; ++i)
m_Root.Remove(m_DynamicElements[i]);
m_DynamicElements.Clear();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7950c6f52d74a4772bb69c03fe19a19b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,82 @@
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UIElements;
namespace Unity.Cinemachine.Samples
{
/// <summary>
/// Displays a button in the game view that will bring up a window with scrollable text.
/// </summary>
[RequireComponent(typeof(UIDocument))]
public class SamplesHelpUI : MonoBehaviour
{
public bool VisibleAtStart;
public string HelpTitle;
[TextArea(minLines: 10, maxLines: 50)]
public string HelpText;
[Tooltip("Event sent when the help window is dismissed")]
public UnityEvent OnHelpDismissed = new ();
VisualElement m_HelpBox;
Button m_HelpButton, m_CloseButton;
void OnEnable()
{
var uiDocument = GetComponent<UIDocument>();
m_HelpButton = uiDocument.rootVisualElement.Q("HelpButton") as Button;
if (m_HelpButton == null)
{
Debug.LogError("Cannot find HelpToggleBox. Is the source asset set in the UIDocument?");
return;
}
m_HelpButton.RegisterCallback(new EventCallback<ClickEvent>(OpenHelpBox));
m_HelpBox = uiDocument.rootVisualElement.Q("HelpTextBox");
if (uiDocument.rootVisualElement.Q("HelpTextBox__Title") is Label helpTitle)
helpTitle.text = string.IsNullOrEmpty(HelpTitle) ? SceneManager.GetActiveScene().name : HelpTitle;
if (uiDocument.rootVisualElement.Q("HelpTextBox__ScrollView__Label") is Label helpLabel)
helpLabel.text = HelpText;
m_CloseButton = uiDocument.rootVisualElement.Q("HelpTextBox__CloseButton") as Button;
if (m_CloseButton == null)
{
Debug.LogError("Cannot find HelpTextBox__CloseButton. Is the source asset set in the UIDocument?");
return;
}
m_CloseButton.RegisterCallback<ClickEvent>(CloseHelpBox);
m_HelpBox.visible = VisibleAtStart;
m_HelpButton.visible = !VisibleAtStart;
}
void OnDisable()
{
CloseHelpBox(null);
m_HelpButton.UnregisterCallback(new EventCallback<ClickEvent>(OpenHelpBox));
m_CloseButton.UnregisterCallback(new EventCallback<ClickEvent>(CloseHelpBox));
m_HelpButton.visible = false;
}
void OpenHelpBox(ClickEvent click)
{
if (m_HelpButton != null)
m_HelpButton.visible = false;
if (m_HelpBox != null)
m_HelpBox.visible = true;
}
void CloseHelpBox(ClickEvent click)
{
if (m_HelpButton != null)
m_HelpButton.visible = true;
if (m_HelpBox != null)
m_HelpBox.visible = false;
VisibleAtStart = false;
OnHelpDismissed.Invoke();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: be49d5e8cf7024d6b8f8d5b9872c0f91
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,29 @@
using UnityEngine.Splines;
namespace Unity.Cinemachine.Samples
{
/// <summary>
/// Calculates shot quality based on how far the Follow target has advanced
/// with its CinemachineSplineCart.
/// </summary>
public class ShotQualityBasedOnSplineCartPosition : CinemachineExtension, IShotQualityEvaluator
{
protected override void PostPipelineStageCallback(
CinemachineVirtualCameraBase vcam, CinemachineCore.Stage stage,
ref CameraState state, float deltaTime)
{
if (stage == CinemachineCore.Stage.Finalize)
{
state.ShotQuality = 0;
if (vcam.Follow != null
&& vcam.Follow.TryGetComponent(out CinemachineSplineCart cart)
&& cart.Spline != null && cart.Spline.Spline != null)
{
// Shot quality is just the normalized distance along the path
state.ShotQuality = cart.Spline.Spline.ConvertIndexUnit(
cart.SplinePosition, cart.PositionUnits, PathIndexUnit.Normalized);
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c2ffe8fe055874f88a4f26bff5ebce0b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,67 @@
using System.Collections;
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
public class SimpleBullet : MonoBehaviour
{
public LayerMask CollisionLayers = 1;
public float Speed = 500;
public float Lifespan = 3;
[Tooltip("Stretch factor in the direction of motion while flying")]
public float Stretch = 6;
float m_Speed;
Vector3 m_SpawnPoint;
void OnValidate()
{
Speed = Mathf.Max(1, Speed);
Lifespan = Mathf.Max(0.2f, Lifespan);
}
void OnEnable()
{
m_Speed = Speed;
m_SpawnPoint = transform.position;
SetStretch(1);
StartCoroutine(DeactivateAfter());
}
void Update()
{
if (m_Speed > 0)
{
var t = transform;
if (UnityEngine.Physics.Raycast(
t.position, t.forward, out var hitInfo, m_Speed * Time.deltaTime, CollisionLayers,
QueryTriggerInteraction.Ignore))
{
t.position = hitInfo.point;
m_Speed = 0;
SetStretch(1);
}
var deltaPos = m_Speed * Time.deltaTime;
t.position += deltaPos * t.forward;
// Clamp the stretch to avoid the bullet stretching back past the spawn point.
// This code assumes that the bullet length is 1.
SetStretch(Mathf.Min(1 + deltaPos * Stretch, Vector3.Distance(t.position, m_SpawnPoint)));
}
}
void SetStretch(float stretch)
{
var scale = transform.localScale;
scale.z = stretch;
transform.localScale = scale;
}
IEnumerator DeactivateAfter()
{
yield return new WaitForSeconds(Lifespan);
gameObject.SetActive(false);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c1389666f22088e47a6ca8ccd8d0b78e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,85 @@
using System.Collections.Generic;
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
public class SimpleCarController : MonoBehaviour, Unity.Cinemachine.IInputAxisOwner
{
public float MotorStrength = 2000;
public float BrakeStrength = 5000;
public float MaxTurnAngle = 50;
public WheelCollider FrontLeftWheelCollider;
public WheelCollider FrontRightWheelCollider;
public WheelCollider RearLeftWheelCollider;
public WheelCollider RearRightWheelCollider;
public Transform FrontLeftWheel;
public Transform FrontRightWhee;
public Transform RearLeftWheel;
public Transform RearRightWheel;
[Header("Input Axes")]
[Tooltip("X Axis movement. Value is -1..1. Controls the turning amount")]
public InputAxis MoveX = InputAxis.DefaultMomentary;
[Tooltip("Z Axis movement. Value is -1..1. Controls the forward acceleration")]
public InputAxis MoveZ = InputAxis.DefaultMomentary;
[Tooltip("Braking. Value is 0 to 1. Controls the braking force")]
public InputAxis Brake = InputAxis.DefaultMomentary;
/// Report the available input axes to the input axis controller.
/// We use the Input Axis Controller because it works with both the Input package
/// and the Legacy input system. This is sample code and we
/// want it to work everywhere.
void IInputAxisOwner.GetInputAxes(List<IInputAxisOwner.AxisDescriptor> axes)
{
axes.Add(new () { DrivenAxis = () => ref MoveX, Name = "Move X", Hint = IInputAxisOwner.AxisDescriptor.Hints.X });
axes.Add(new () { DrivenAxis = () => ref MoveZ, Name = "Move Z", Hint = IInputAxisOwner.AxisDescriptor.Hints.Y });
axes.Add(new () { DrivenAxis = () => ref Brake, Name = "Brake" });
}
void OnValidate()
{
MoveX.Validate();
MoveZ.Validate();
Brake.Validate();
}
void FixedUpdate()
{
// Acceleration
var force = MotorStrength * MoveZ.Value;
FrontLeftWheelCollider.motorTorque = force;
FrontRightWheelCollider.motorTorque = force;
// Braking
force = BrakeStrength * Brake.Value;
FrontRightWheelCollider.brakeTorque = force;
FrontLeftWheelCollider.brakeTorque = force;
RearLeftWheelCollider.brakeTorque = force;
RearRightWheelCollider.brakeTorque = force;
if (Brake.Value > 0.99f)
MoveZ.Value = 0;
// Steering
force = MaxTurnAngle * MoveX.Value;
FrontLeftWheelCollider.steerAngle = force;
FrontRightWheelCollider.steerAngle = force;
// Place the wheels
UpdateWheel(FrontLeftWheelCollider, FrontLeftWheel);
UpdateWheel(FrontRightWheelCollider, FrontRightWhee);
UpdateWheel(RearRightWheelCollider, RearRightWheel);
UpdateWheel(RearLeftWheelCollider, RearLeftWheel);
}
void UpdateWheel(WheelCollider c, Transform t)
{
c.GetWorldPose(out var pos, out var rot);
t.SetPositionAndRotation(pos, rot);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 45a1d5f246db83d4e9db796e9e19282e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,185 @@
using System.Collections.Generic;
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
/// <summary>
/// This is an add-on for SimplePlayerController that controls the player's Aiming Core.
///
/// This component expects to be in a child object of a player that has a SimplePlayerController
/// behaviour. It works intimately with that component.
//
/// The purpose of the aiming core is to decouple the camera rotation from the player rotation.
/// Camera rotation is determined by the rotation of the player core GameObject, and this behaviour
/// provides input axes for controlling it. When the player core is used as the target for
/// a CinemachineCamera with a ThirdPersonFollow component, the camera will look along the core's
/// forward axis, and pivot around the core's origin.
///
/// The aiming core is also used to define the origin and direction of player shooting, if player
/// has that ability.
///
/// To implement player shooting, add a SimplePlayerShoot behaviour to this GameObject.
/// </summary>
public class SimplePlayerAimController : MonoBehaviour, Unity.Cinemachine.IInputAxisOwner
{
public enum CouplingMode { Coupled, CoupledWhenMoving, Decoupled }
[Tooltip("How the player's rotation is coupled to the camera's rotation. Three modes are available:\n"
+ "<b>Coupled</b>: The player rotates with the camera. Sideways movement will result in strafing.\n"
+ "<b>Coupled When Moving</b>: Camera can rotate freely around the player when the player is stationary, "
+ "but the player will rotate to face camera forward when it starts moving.\n"
+ "<b>Decoupled</b>: The player's rotation is independent of the camera's rotation.")]
public CouplingMode PlayerRotation;
[Tooltip("How fast the player rotates to face the camera direction when the player starts moving. "
+ "Only used when Player Rotation is Coupled When Moving.")]
public float RotationDamping = 0.2f;
[Tooltip("Horizontal Rotation. Value is in degrees, with 0 being centered.")]
public InputAxis HorizontalLook = new () { Range = new Vector2(-180, 180), Wrap = true, Recentering = InputAxis.RecenteringSettings.Default };
[Tooltip("Vertical Rotation. Value is in degrees, with 0 being centered.")]
public InputAxis VerticalLook = new () { Range = new Vector2(-70, 70), Recentering = InputAxis.RecenteringSettings.Default };
SimplePlayerControllerBase m_Controller;
Transform m_ControllerTransform; // cached for efficiency
Quaternion m_DesiredWorldRotation;
/// Report the available input axes to the input axis controller.
/// We use the Input Axis Controller because it works with both the Input package
/// and the Legacy input system. This is sample code and we
/// want it to work everywhere.
void IInputAxisOwner.GetInputAxes(List<IInputAxisOwner.AxisDescriptor> axes)
{
axes.Add(new () { DrivenAxis = () => ref HorizontalLook, Name = "Horizontal Look", Hint = IInputAxisOwner.AxisDescriptor.Hints.X });
axes.Add(new () { DrivenAxis = () => ref VerticalLook, Name = "Vertical Look", Hint = IInputAxisOwner.AxisDescriptor.Hints.Y });
}
void OnValidate()
{
HorizontalLook.Validate();
VerticalLook.Range.x = Mathf.Clamp(VerticalLook.Range.x, -90, 90);
VerticalLook.Range.y = Mathf.Clamp(VerticalLook.Range.y, -90, 90);
VerticalLook.Validate();
}
void OnEnable()
{
m_Controller = GetComponentInParent<SimplePlayerControllerBase>();
if (m_Controller == null)
Debug.LogError("SimplePlayerController not found on parent object");
else
{
m_Controller.PreUpdate -= UpdatePlayerRotation;
m_Controller.PreUpdate += UpdatePlayerRotation;
m_Controller.PostUpdate -= PostUpdate;
m_Controller.PostUpdate += PostUpdate;
m_ControllerTransform = m_Controller.transform;
}
}
void OnDisable()
{
if (m_Controller != null)
{
m_Controller.PreUpdate -= UpdatePlayerRotation;
m_Controller.PostUpdate -= PostUpdate;
m_ControllerTransform = null;
}
}
/// <summary>Recenters the player to match my rotation</summary>
/// <param name="damping">How long the recentering should take</param>
public void RecenterPlayer(float damping = 0)
{
if (m_ControllerTransform == null)
return;
// Get my rotation relative to parent
var rot = transform.localRotation.eulerAngles;
rot.y = NormalizeAngle(rot.y);
var delta = rot.y;
delta = Damper.Damp(delta, damping, Time.deltaTime);
// Rotate the parent towards me
m_ControllerTransform.rotation = Quaternion.AngleAxis(
delta, m_ControllerTransform.up) * m_ControllerTransform.rotation;
// Rotate me in the opposite direction
HorizontalLook.Value -= delta;
rot.y -= delta;
transform.localRotation = Quaternion.Euler(rot);
}
/// <summary>
/// Set my rotation to look in this direction, without changing player rotation.
/// Here we only set the axis values, we let the player controller do the actual rotation.
/// </summary>
/// <param name="worldspaceDirection">Direction to look in, in worldspace</param>
public void SetLookDirection(Vector3 worldspaceDirection)
{
if (m_ControllerTransform == null)
return;
var rot = (Quaternion.Inverse(m_ControllerTransform.rotation)
* Quaternion.LookRotation(worldspaceDirection, m_ControllerTransform.up)).eulerAngles;
HorizontalLook.Value = HorizontalLook.ClampValue(rot.y);
VerticalLook.Value = VerticalLook.ClampValue(NormalizeAngle(rot.x));
}
// This is called by the player controller before it updates its own rotation.
void UpdatePlayerRotation()
{
var t = transform;
t.localRotation = Quaternion.Euler(VerticalLook.Value, HorizontalLook.Value, 0);
m_DesiredWorldRotation = t.rotation;
switch (PlayerRotation)
{
case CouplingMode.Coupled:
{
m_Controller.SetStrafeMode(true);
RecenterPlayer();
break;
}
case CouplingMode.CoupledWhenMoving:
{
// If the player is moving, rotate its yaw to match the camera direction,
// otherwise let the camera orbit
m_Controller.SetStrafeMode(true);
if (m_Controller.IsMoving)
RecenterPlayer(RotationDamping);
break;
}
case CouplingMode.Decoupled:
{
m_Controller.SetStrafeMode(false);
break;
}
}
VerticalLook.UpdateRecentering(Time.deltaTime, VerticalLook.TrackValueChange());
HorizontalLook.UpdateRecentering(Time.deltaTime, HorizontalLook.TrackValueChange());
}
// Callback for player controller to update our rotation after it has updated its own.
void PostUpdate(Vector3 vel, float speed)
{
if (PlayerRotation == CouplingMode.Decoupled)
{
// After player has been rotated, we subtract any rotation change
// from our own transform, to maintain our world rotation
transform.rotation = m_DesiredWorldRotation;
var delta = (Quaternion.Inverse(m_ControllerTransform.rotation) * m_DesiredWorldRotation).eulerAngles;
VerticalLook.Value = NormalizeAngle(delta.x);
HorizontalLook.Value = NormalizeAngle(delta.y);
}
}
float NormalizeAngle(float angle)
{
while (angle > 180)
angle -= 360;
while (angle < -180)
angle += 360;
return angle;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5232368c753c54e419e00665fe96a8e1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,165 @@
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
/// <summary>
/// This is a behaviour whose job it is to drive animation based on the player's motion.
/// It is a sample implementation that you can modify or replace with your own. As shipped, it is
/// hardcoded to work specifically with the sample `CameronSimpleController` Animation controller, which
/// is set up with states that the SimplePlayerAnimator knows about. You can modify
/// this class to work with your own animation controller.
///
/// SimplePlayerAnimator works with or without a SimplePlayerControllerBase alongside.
/// Without one, it monitors the transform's position and drives the animation accordingly.
/// You can see it used like this in some of the sample scenes, such as RunningRace or ClearShot.
/// In this mode, is it unable to detect the player's grounded state, and so it always
/// assumes that the player is grounded.
///
/// When a SimplePlayerControllerBase is detected, the SimplePlayerAnimator installs callbacks
/// and expects to be driven by the SimplePlayerControllerBase using the STartJump, EndJump,
/// and PostUpdate callbacks.
/// </summary>
public class SimplePlayerAnimator : MonoBehaviour
{
[Tooltip("Tune this to the animation in the model: feet should not slide when walking at this speed")]
public float NormalWalkSpeed = 1.7f;
[Tooltip("Tune this to the animation in the model: feet should not slide when sprinting at this speed")]
public float NormalSprintSpeed = 5;
[Tooltip("Never speed up the sprint animation more than this, to avoid absurdly fast movement")]
public float MaxSprintScale = 1.4f;
[Tooltip("Scale factor for the overall speed of the jump animation")]
public float JumpAnimationScale = 0.65f;
SimplePlayerControllerBase m_Controller;
Vector3 m_PreviousPosition; // used if m_Controller == null or disabled
protected struct AnimationParams
{
public bool IsWalking;
public bool IsRunning;
public bool IsJumping;
public bool LandTriggered;
public bool JumpTriggered;
public Vector3 Direction; // normalized direction of motion
public float MotionScale; // scale factor for the animation speed
public float JumpScale; // scale factor for the jump animation
}
AnimationParams m_AnimationParams;
const float k_IdleThreshold = 0.2f;
public enum States { Idle, Walk, Run, Jump, RunJump }
/// <summary>Current state of the player</summary>
public States State
{
get
{
if (m_AnimationParams.IsJumping)
return m_AnimationParams.IsRunning ? States.RunJump : States.Jump;
if (m_AnimationParams.IsRunning)
return States.Run;
return m_AnimationParams.IsWalking ? States.Walk : States.Idle;
}
}
protected virtual void Start()
{
m_PreviousPosition = transform.position;
m_Controller = GetComponentInParent<SimplePlayerControllerBase>();
if (m_Controller != null)
{
// Install our callbacks to handle jump and animation based on velocity
m_Controller.StartJump += () => m_AnimationParams.JumpTriggered = true;
m_Controller.EndJump += () => m_AnimationParams.LandTriggered = true;
m_Controller.PostUpdate += (vel, jumpAnimationScale) => UpdateAnimationState(vel, jumpAnimationScale);
}
}
/// <summary>
/// LateUpdate is used to avoid having to worry about script execution order:
/// it can be assumed that the player has already been moved.
/// </summary>
protected virtual void LateUpdate()
{
// In no-controller mode, we monitor the player's motion and deduce the appropriate animation.
// We don't support jumping in this mode.
if (m_Controller == null || !m_Controller.enabled)
{
// Get velocity in player-local coords
var pos = transform.position;
var vel = Quaternion.Inverse(transform.rotation) * (pos - m_PreviousPosition) / Time.deltaTime;
m_PreviousPosition = pos;
UpdateAnimationState(vel, 1);
}
}
/// <summary>
/// Update the animation based on the player's velocity.
/// Override this to interact appropriately with your animation controller.
/// </summary>
/// <param name="vel">Player's velocity, in player-local coordinates.</param>
/// <param name="jumpAnimationScale">Scale factor to apply to the jump animation.
/// It can be used to slow down the jump animation for longer jumps.</param>
void UpdateAnimationState(Vector3 vel, float jumpAnimationScale)
{
vel.y = 0; // we don't consider vertical movement
var speed = vel.magnitude;
// Hysteresis reduction
bool isRunning = speed > NormalWalkSpeed * 2 + (m_AnimationParams.IsRunning ? -0.15f : 0.15f);
bool isWalking = !isRunning && speed > k_IdleThreshold + (m_AnimationParams.IsWalking ? -0.05f : 0.05f);
m_AnimationParams.IsWalking = isWalking;
m_AnimationParams.IsRunning = isRunning;
// Set the normalized direction of motion and scale the animation speed to match motion speed
m_AnimationParams.Direction = speed > k_IdleThreshold ? vel / speed : Vector3.zero;
m_AnimationParams.MotionScale = isWalking ? speed / NormalWalkSpeed : 1;
m_AnimationParams.JumpScale = JumpAnimationScale * jumpAnimationScale;
// We scale the sprint animation speed to loosely match the actual speed, but we cheat
// at the high end to avoid making the animation look ridiculous
if (isRunning)
m_AnimationParams.MotionScale = (speed < NormalSprintSpeed)
? speed / NormalSprintSpeed
: Mathf.Min(MaxSprintScale, 1 + (speed - NormalSprintSpeed) / (3 * NormalSprintSpeed));
UpdateAnimation(m_AnimationParams);
if (m_AnimationParams.JumpTriggered)
m_AnimationParams.IsJumping = true;
if (m_AnimationParams.LandTriggered)
m_AnimationParams.IsJumping = false;
m_AnimationParams.JumpTriggered = false;
m_AnimationParams.LandTriggered = false;
}
/// <summary>
/// Update the animation based on the player's state.
/// Override this to interact appropriately with your animation controller.
/// </summary>
protected virtual void UpdateAnimation(AnimationParams animationParams)
{
if (!TryGetComponent(out Animator animator))
{
Debug.LogError("SimplePlayerAnimator: An Animator component is required");
return;
}
animator.SetFloat("DirX", animationParams.Direction.x);
animator.SetFloat("DirZ", animationParams.Direction.z);
animator.SetFloat("MotionScale", animationParams.MotionScale);
animator.SetBool("Walking", animationParams.IsWalking);
animator.SetBool("Running", animationParams.IsRunning);
animator.SetFloat("JumpScale", animationParams.JumpScale);
if (m_AnimationParams.JumpTriggered)
animator.SetTrigger("Jump");
if (m_AnimationParams.LandTriggered)
animator.SetTrigger("Land");
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: be929e8436c3caf46b4ef616ef0e824e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,405 @@
using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEngine.Events;
namespace Unity.Cinemachine.Samples
{
/// <summary>
/// This is the base class for SimplePlayerController and SimplePlayerController2D.
/// You can also use it as a base class for your custom controllers.
/// It provides the following:
///
/// **Services:**
///
/// - 2D motion axes (MoveX and MoveZ)
/// - Jump button
/// - Sprint button
/// - API for strafe mode
///
/// **Actions:**
///
/// - PreUpdate - invoked at the beginning of `Update()`
/// - PostUpdate - invoked at the end of `Update()`
/// - StartJump - invoked when the player starts jumping
/// - EndJump - invoked when the player stops jumping
///
/// **Events:**
///
/// - Landed - invoked when the player lands on the ground
/// </summary>
public abstract class SimplePlayerControllerBase : MonoBehaviour, Unity.Cinemachine.IInputAxisOwner
{
[Tooltip("Ground speed when walking")]
public float Speed = 1f;
[Tooltip("Ground speed when sprinting")]
public float SprintSpeed = 4;
[Tooltip("Initial vertical speed when jumping")]
public float JumpSpeed = 4;
[Tooltip("Initial vertical speed when sprint-jumping")]
public float SprintJumpSpeed = 6;
public Action PreUpdate;
public Action<Vector3, float> PostUpdate;
public Action StartJump;
public Action EndJump;
[Header("Input Axes")]
[Tooltip("X Axis movement. Value is -1..1. Controls the sideways movement")]
public InputAxis MoveX = InputAxis.DefaultMomentary;
[Tooltip("Z Axis movement. Value is -1..1. Controls the forward movement")]
public InputAxis MoveZ = InputAxis.DefaultMomentary;
[Tooltip("Jump movement. Value is 0 or 1. Controls the vertical movement")]
public InputAxis Jump = InputAxis.DefaultMomentary;
[Tooltip("Sprint movement. Value is 0 or 1. If 1, then is sprinting")]
public InputAxis Sprint = InputAxis.DefaultMomentary;
[Header("Events")]
[Tooltip("This event is sent when the player lands after a jump.")]
public UnityEvent Landed = new ();
/// Report the available input axes to the input axis controller.
/// We use the Input Axis Controller because it works with both the Input package
/// and the Legacy input system. This is sample code and we
/// want it to work everywhere.
void IInputAxisOwner.GetInputAxes(List<IInputAxisOwner.AxisDescriptor> axes)
{
axes.Add(new () { DrivenAxis = () => ref MoveX, Name = "Move X", Hint = IInputAxisOwner.AxisDescriptor.Hints.X });
axes.Add(new () { DrivenAxis = () => ref MoveZ, Name = "Move Z", Hint = IInputAxisOwner.AxisDescriptor.Hints.Y });
axes.Add(new () { DrivenAxis = () => ref Jump, Name = "Jump" });
axes.Add(new () { DrivenAxis = () => ref Sprint, Name = "Sprint" });
}
protected virtual void OnValidate()
{
MoveX.Validate();
MoveZ.Validate();
Jump.Validate();
Sprint.Validate();
}
public virtual void SetStrafeMode(bool b) {}
public abstract bool IsMoving { get; }
}
/// <summary>
/// Building on top of SimplePlayerControllerBase, this is the 3D character controller.
/// It provides the following services and settings:
///
/// - Damping (applied to the player's velocity, and to the player's rotation)
/// - Strafe Mode
/// - Gravity
/// - Input Frames (which reference frame is used fo interpreting input: Camera, World, or Player)
/// - Ground Detection (using raycasts, or delegating to Character Controller)
/// - Camera Override (camera is used only for determining the input frame)
///
/// This behaviour should be attached to the player GameObject's root. It moves the GameObject's
/// transform. If the GameObject also has a Unity Character Controller component, the Simple Player
/// Controller delegates grounded state and movement to it. If the GameObject does not have a
/// Character Controller, the Simple Player Controller manages its own movement and does raycasts
/// to test for grounded state.
///
/// Simple Player Controller does its best to interpret User input in the context of the
/// selected reference frame. Generally, this works well, but in Camera mode, the user
/// may potentially transition from being upright relative to the camera to being inverted.
/// When this happens, there can be a discontinuity in the interpretation of the input.
/// The Simple Player Controller has an ad-hoc technique of resolving this discontinuity,
/// (you can see this in the code), but it is only used in this very specific situation.
/// </summary>
public class SimplePlayerController : SimplePlayerControllerBase, ITeleportable
{
[Tooltip("Transition duration (in seconds) when the player changes velocity or rotation.")]
public float Damping = 0.5f;
[Tooltip("Makes the player strafe when moving sideways, otherwise it turns to face the direction of motion.")]
public bool Strafe = false;
public enum ForwardModes { Camera, Player, World };
public enum UpModes { Player, World };
[Tooltip("Reference frame for the input controls:\n"
+ "<b>Camera</b>: Input forward is camera forward direction.\n"
+ "<b>Player</b>: Input forward is Player's forward direction.\n"
+ "<b>World</b>: Input forward is World forward direction.")]
public ForwardModes InputForward = ForwardModes.Camera;
[Tooltip("Up direction for computing motion:\n"
+ "<b>Player</b>: Move in the Player's local XZ plane.\n"
+ "<b>World</b>: Move in global XZ plane.")]
public UpModes UpMode = UpModes.World;
[Tooltip("If non-null, take the input frame from this camera instead of Camera.main. Useful for split-screen games.")]
public Camera CameraOverride;
[Tooltip("Layers to include in ground detection via Raycasts.")]
public LayerMask GroundLayers = 1;
[Tooltip("Force of gravity in the down direction (m/s^2)")]
public float Gravity = 10;
const float kDelayBeforeInferringJump = 0.3f;
float m_TimeLastGrounded = 0;
Vector3 m_CurrentVelocityXZ;
Vector3 m_LastInput;
float m_CurrentVelocityY;
bool m_IsSprinting;
bool m_IsJumping;
UnityEngine.CharacterController m_Controller; // optional
// These are part of a strategy to combat input gimbal lock when controlling a player
// that can move freely on surfaces that go upside-down relative to the camera.
// This is only used in the specific situation where the character is upside-down relative to the input frame,
// and the input directives become ambiguous.
// If the camera and input frame are travelling along with the player, then these are not used.
bool m_InTopHemisphere = true;
float m_TimeInHemisphere = 100;
Vector3 m_LastRawInput;
Quaternion m_Upsidedown = Quaternion.AngleAxis(180, Vector3.left);
public override void SetStrafeMode(bool b) => Strafe = b;
public override bool IsMoving => m_LastInput.sqrMagnitude > 0.01f;
public bool IsSprinting => m_IsSprinting;
public bool IsJumping => m_IsJumping;
public Camera Camera => CameraOverride == null ? Camera.main : CameraOverride;
public bool IsGrounded() => GetDistanceFromGround(transform.position, UpDirection, 10) < 0.01f;
// Note that m_Controller is an optional component: we'll use it if it's there.
void Start() => TryGetComponent(out m_Controller);
private void OnEnable()
{
m_CurrentVelocityY = 0;
m_IsSprinting = false;
m_IsJumping = false;
m_TimeLastGrounded = Time.time;
}
void Update()
{
PreUpdate?.Invoke();
// Process Jump and gravity
bool justLanded = ProcessJump();
// Get the reference frame for the input
var rawInput = new Vector3(MoveX.Value, 0, MoveZ.Value);
var inputFrame = GetInputFrame(Vector3.Dot(rawInput, m_LastRawInput) < 0.8f);
m_LastRawInput = rawInput;
// Read the input from the user and put it in the input frame
m_LastInput = inputFrame * rawInput;
if (m_LastInput.sqrMagnitude > 1)
m_LastInput.Normalize();
// Compute the new velocity and move the player, but only if not mid-jump
if (!m_IsJumping)
{
m_IsSprinting = Sprint.Value > 0.5f;
var desiredVelocity = m_LastInput * (m_IsSprinting ? SprintSpeed : Speed);
var damping = justLanded ? 0 : Damping;
if (Vector3.Angle(m_CurrentVelocityXZ, desiredVelocity) < 100)
m_CurrentVelocityXZ = Vector3.Slerp(
m_CurrentVelocityXZ, desiredVelocity,
Damper.Damp(1, damping, Time.deltaTime));
else
m_CurrentVelocityXZ += Damper.Damp(
desiredVelocity - m_CurrentVelocityXZ, damping, Time.deltaTime);
}
// Apply the position change
ApplyMotion();
// If not strafing, rotate the player to face movement direction
if (!Strafe && m_CurrentVelocityXZ.sqrMagnitude > 0.001f)
{
var fwd = inputFrame * Vector3.forward;
var qA = transform.rotation;
var qB = Quaternion.LookRotation(
(InputForward == ForwardModes.Player && Vector3.Dot(fwd, m_CurrentVelocityXZ) < 0)
? -m_CurrentVelocityXZ : m_CurrentVelocityXZ, UpDirection);
var damping = justLanded ? 0 : Damping;
transform.rotation = Quaternion.Slerp(qA, qB, Damper.Damp(1, damping, Time.deltaTime));
}
if (PostUpdate != null)
{
// Get local-space velocity
var vel = Quaternion.Inverse(transform.rotation) * m_CurrentVelocityXZ;
vel.y = m_CurrentVelocityY;
PostUpdate(vel, m_IsSprinting ? JumpSpeed / SprintJumpSpeed : 1);
}
}
Vector3 UpDirection => UpMode == UpModes.World ? Vector3.up : transform.up;
// Get the reference frame for the input. The idea is to map camera fwd/right
// to the player's XZ plane. There is some complexity here to avoid
// gimbal lock when the player is tilted 180 degrees relative to the input frame.
Quaternion GetInputFrame(bool inputDirectionChanged)
{
// Get the raw input frame, depending of forward mode setting
var frame = Quaternion.identity;
switch (InputForward)
{
case ForwardModes.Camera: frame = Camera.transform.rotation; break;
case ForwardModes.Player: return transform.rotation;
case ForwardModes.World: break;
}
// Map the raw input frame to something that makes sense as a direction for the player
var playerUp = transform.up;
var up = frame * Vector3.up;
// Is the player in the top or bottom hemisphere? This is needed to avoid gimbal lock,
// but only when the player is upside-down relative to the input frame.
const float BlendTime = 2f;
m_TimeInHemisphere += Time.deltaTime;
bool inTopHemisphere = Vector3.Dot(up, playerUp) >= 0;
if (inTopHemisphere != m_InTopHemisphere)
{
m_InTopHemisphere = inTopHemisphere;
m_TimeInHemisphere = Mathf.Max(0, BlendTime - m_TimeInHemisphere);
}
// If the player is untilted relative to the input frmae, then early-out with a simple LookRotation
var axis = Vector3.Cross(up, playerUp);
if (axis.sqrMagnitude < 0.001f && inTopHemisphere)
return frame;
// Player is tilted relative to input frame: tilt the input frame to match
var angle = UnityVectorExtensions.SignedAngle(up, playerUp, axis);
var frameA = Quaternion.AngleAxis(angle, axis) * frame;
// If the player is tilted, then we need to get tricky to avoid gimbal-lock
// when player is tilted 180 degrees. There is no perfect solution for this,
// we need to cheat it :/
Quaternion frameB = frameA;
if (!inTopHemisphere || m_TimeInHemisphere < BlendTime)
{
// Compute an alternative reference frame for the bottom hemisphere.
// The two reference frames are incompatible where they meet, especially
// when player up is pointing along the X axis of camera frame.
// There is no one reference frame that works for all player directions.
frameB = frame * m_Upsidedown;
var axisB = Vector3.Cross(frameB * Vector3.up, playerUp);
if (axisB.sqrMagnitude > 0.001f)
frameB = Quaternion.AngleAxis(180f - angle, axisB) * frameB;
}
// Blend timer force-expires when user changes input direction
if (inputDirectionChanged)
m_TimeInHemisphere = BlendTime;
// If we have been long enough in one hemisphere, then we can just use its reference frame
if (m_TimeInHemisphere >= BlendTime)
return inTopHemisphere ? frameA : frameB;
// Because frameA and frameB do not join seamlessly when player Up is along X axis,
// we blend them over a time in order to avoid degenerate spinning.
// This will produce weird movements occasionally, but it's the lesser of the evils.
if (inTopHemisphere)
return Quaternion.Slerp(frameB, frameA, m_TimeInHemisphere / BlendTime);
return Quaternion.Slerp(frameA, frameB, m_TimeInHemisphere / BlendTime);
}
bool ProcessJump()
{
bool justLanded = false;
var now = Time.time;
bool grounded = IsGrounded();
m_CurrentVelocityY -= Gravity * Time.deltaTime;
if (!m_IsJumping)
{
// Process jump command
if (grounded && Jump.Value > 0.01f)
{
m_IsJumping = true;
m_CurrentVelocityY = m_IsSprinting ? SprintJumpSpeed : JumpSpeed;
}
// If we are falling, assume the jump pose
if (!grounded && now - m_TimeLastGrounded > kDelayBeforeInferringJump)
m_IsJumping = true;
if (m_IsJumping)
{
StartJump?.Invoke();
grounded = false;
}
}
if (grounded)
{
m_TimeLastGrounded = Time.time;
m_CurrentVelocityY = 0;
// If we were jumping, complete the jump
if (m_IsJumping)
{
EndJump?.Invoke();
m_IsJumping = false;
justLanded = true;
Landed.Invoke();
}
}
return justLanded;
}
void ApplyMotion()
{
if (m_Controller != null)
m_Controller.Move((m_CurrentVelocityY * UpDirection + m_CurrentVelocityXZ) * Time.deltaTime);
else
{
var pos = transform.position + m_CurrentVelocityXZ * Time.deltaTime;
// Don't fall below ground
var up = UpDirection;
var altitude = GetDistanceFromGround(pos, up, 10);
if (altitude < 0 && m_CurrentVelocityY <= 0)
{
pos -= altitude * up;
m_CurrentVelocityY = 0;
}
else if (m_CurrentVelocityY < 0)
{
var dy = -m_CurrentVelocityY * Time.deltaTime;
if (dy > altitude)
{
pos -= altitude * up;
m_CurrentVelocityY = 0;
}
}
transform.position = pos + m_CurrentVelocityY * up * Time.deltaTime;
}
}
float GetDistanceFromGround(Vector3 pos, Vector3 up, float max)
{
float kExtraHeight = m_Controller == null ? 2 : 0; // start a little above the player in case it's moving down fast
if (UnityEngine.Physics.Raycast(pos + up * kExtraHeight, -up, out var hit,
max + kExtraHeight, GroundLayers, QueryTriggerInteraction.Ignore))
return hit.distance - kExtraHeight;
return max + 1;
}
// ITeleportable implementation
public void Teleport(Vector3 newPos, Quaternion newRot)
{
if (m_Controller != null)
m_Controller.enabled = false;
var rot = transform.rotation;
var rotDelta = newRot * Quaternion.Inverse(rot);
m_CurrentVelocityXZ = rotDelta * m_CurrentVelocityXZ;
transform.SetPositionAndRotation(newPos, newRot);
if (m_Controller != null)
m_Controller.enabled = true;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 297aaca66939f724fbbc8f0c485168bc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,108 @@
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
/// <summary>
/// This is a very basic 2D implementation of SimplePlayerControllerBase.
///
/// It requires a [Rigidbody2D](https://docs.unity3d.com/ScriptReference/Rigidbody2D.html) component
/// to be placed on the player GameObject. Because it works with a Rigidbody2D, motion control is
/// implemented in the `FixedUpdate()` method.
///
/// Ground detection only works if the player has a small trigger collider under its feet.
/// </summary>
[RequireComponent(typeof(Rigidbody2D))]
public class SimplePlayerController2D : SimplePlayerControllerBase
{
[Tooltip("Reference to the child object that holds the player's visible geometry. "
+ "'It is rotated to face the direction of motion")]
public Transform PlayerGeometry;
[Tooltip("Makes possible to influence the direction of motion while the character is "
+ "in the air. Otherwise, the more realistic rule that the feet must be touching the ground applies.")]
public bool MotionControlWhileInAir;
bool m_IsSprinting;
bool m_IsGrounded;
Rigidbody2D m_Rigidbody2D;
#if UNITY_6000_1_OR_NEWER
public override bool IsMoving => Mathf.Abs(m_Rigidbody2D.linearVelocity.x) > 0.01f;
#else
#pragma warning disable CS0618 // obsolete for 6000.0.0f11 and newer
public override bool IsMoving => Mathf.Abs(m_Rigidbody2D.velocity.x) > 0.01f;
#pragma warning restore CS0618
#endif
public bool IsSprinting => m_IsSprinting;
public bool IsJumping => !m_IsGrounded;
void Start() => TryGetComponent(out m_Rigidbody2D);
private void OnEnable()
{
m_IsGrounded = true;
m_IsSprinting = false;
}
void FixedUpdate()
{
PreUpdate?.Invoke();
#if UNITY_6000_1_OR_NEWER
var vel = m_Rigidbody2D.linearVelocity;
#else
#pragma warning disable CS0618 // obsolete for 6000.0.0f11 and newer
var vel = m_Rigidbody2D.velocity;
#pragma warning restore CS0618
#endif
// Compute the new velocity and move the player, but only if not mid-jump
if (m_IsGrounded || MotionControlWhileInAir)
{
// Read the input from the user
m_IsSprinting = Sprint.Value > 0.5f;
vel.x = MoveX.Value * (m_IsSprinting ? SprintSpeed : Speed);
if (m_IsGrounded && Mathf.Max(MoveZ.Value, Jump.Value) > 0.01f)
vel.y = m_IsSprinting ? SprintJumpSpeed : JumpSpeed;
}
// Rotate the player to face movement direction
if (PlayerGeometry.rotation != null)
{
if (vel.x > Speed * 0.5f)
PlayerGeometry.rotation = Quaternion.Euler(new Vector3(0, 90, 0));
if (vel.x < -Speed * 0.5f)
PlayerGeometry.rotation = Quaternion.Euler(new Vector3(0, -90, 0));
}
#if UNITY_6000_1_OR_NEWER
m_Rigidbody2D.linearVelocity = vel;
#else
#pragma warning disable CS0618 // obsolete for 6000.0.0f11 and newer
m_Rigidbody2D.velocity = vel;
#pragma warning restore CS0618
#endif
PostUpdate?.Invoke(
new Vector3(0, vel.y, Mathf.Abs(vel.x)),
m_IsSprinting ? JumpSpeed / SprintJumpSpeed : 1);
}
// Ground detection only works if the player has a small trigger collider under its feet
void OnTriggerEnter2D(Collider2D collision)
{
if (!collision.isTrigger && !m_IsGrounded)
{
EndJump?.Invoke();
Landed.Invoke();
m_IsGrounded = true;
}
}
void OnTriggerExit2D(Collider2D collision)
{
if (!collision.isTrigger && m_IsGrounded)
{
StartJump?.Invoke();
m_IsGrounded = false;
}
}
void OnTriggerStay2D(Collider2D collision) => OnTriggerEnter2D(collision);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a88ade7667a42594ba2c435a7d3b2307
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,237 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
namespace Unity.Cinemachine.Samples
{
/// <summary>
/// This behaviour keeps a player upright on surfaces. It can be used to make the player walk
/// on walls and ceilings or on the surfaces of arbitrary meshes. It rotates the player
/// up to match the surface normal. This script assumes that the pivot point of the player is at the bottom.
///
/// Raycasts are used to detect walkable surfaces.
///
/// When using this component, SimpleSplayerController's Up Mode should be set to _Player_, and it
/// should not have a Character Controller component, as that does not play nicely with
/// nonstandard Up directions.
///
/// Also, when CinemachineCameras are being used to track the character, the
/// [CinemachineBrain](CinemachineBrain.md)'s World Up Override setting should be set to the Player,
/// so that the Camera's Up matches the Player's Up.
/// </summary>
public class SimplePlayerOnSurface : MonoBehaviour
{
[Tooltip("How fast the player rotates to match the surface normal")]
public float RotationDamping = 0.2f;
[Tooltip("What layers to consider as ground")]
public LayerMask GroundLayers = 1;
[Tooltip("How far to raycast when checking for ground")]
public float MaxRaycastDistance = 5;
[Tooltip("The approximate height of the player. Used to compute where raycasts begin")]
public float PlayerHeight = 1;
[Tooltip("If enabled, then player will fall towards the nearest surface when in free fall")]
public bool FreeFallRecovery;
[Header("Events")]
[Tooltip("This event is sent when the player moves from one surface to another.")]
public UnityEvent<Collider> SurfaceChanged = new ();
Vector3 m_PreviousGroundPoint;
Vector3 m_PreviousPosition;
Collider m_CurrentSurface;
float m_FreeFallRaycastAngle = 0;
public bool PreviousSateIsValid { get; set; }
void OnEnable() => PreviousSateIsValid = false;
void OnValidate()
{
RotationDamping = Mathf.Max(0, RotationDamping);
MaxRaycastDistance = Mathf.Max(PlayerHeight * 0.5f, MaxRaycastDistance);
PlayerHeight = Mathf.Max(0, PlayerHeight);
}
// Rotate the player to match the normal of the surface it's standing on
void LateUpdate()
{
var tr = transform;
var desiredUp = tr.up;
var down = -desiredUp;
var damping = RotationDamping;
var originOffset = 0.25f * PlayerHeight * desiredUp;
var downRaycastOrigin = tr.position + originOffset;
var fwdRaycastOrigin = tr.position + 2 * originOffset;
var playerRadius = 0.25f * PlayerHeight; // Approximate player radius - can convert to a parameter if needed
if (!PreviousSateIsValid)
{
m_PreviousPosition = fwdRaycastOrigin;
m_PreviousGroundPoint = downRaycastOrigin;
}
// Find the direction of motion and speed
var motionDir = fwdRaycastOrigin - m_PreviousPosition;
var motionLen = motionDir.magnitude;
if (motionLen < 0.0001f)
motionDir = tr.forward;
else
motionDir /= motionLen;
// Check whether we have walked into a surface
bool haveHit = false;
if (UnityEngine.Physics.Raycast(m_PreviousPosition, motionDir, out var hit,
motionLen + playerRadius, GroundLayers, QueryTriggerInteraction.Ignore))
{
haveHit = true;
desiredUp = CaptureUpDirection(hit);
}
var raycastLength = Mathf.Max(MaxRaycastDistance, PreviousSateIsValid
? (m_PreviousGroundPoint - downRaycastOrigin).magnitude + PlayerHeight : MaxRaycastDistance);
if (!haveHit && UnityEngine.Physics.Raycast(downRaycastOrigin, down, out hit,
raycastLength, GroundLayers, QueryTriggerInteraction.Ignore))
{
haveHit = true;
desiredUp = CaptureUpDirection(hit);
}
// If nothing is directly under our feet, try to find a surface in the direction
// where we came from. This handles the case of sudden convex direction changes in the floor
// (e.g. going around the lip of a surface)
if (!haveHit && PreviousSateIsValid
&& UnityEngine.Physics.Raycast(downRaycastOrigin, m_PreviousGroundPoint - downRaycastOrigin, out hit,
MaxRaycastDistance, GroundLayers, QueryTriggerInteraction.Ignore))
{
haveHit = true;
desiredUp = CaptureUpDirection(hit);
}
// If we don't have a hit by now, we're in free fall
if (haveHit)
m_FreeFallRaycastAngle = 0;
else
{
SetCurrentSurface(null);
if (FreeFallRecovery
&& Vector3.Dot(motionDir, desiredUp) <= 0
&& FindNearestSurface(downRaycastOrigin, raycastLength, out var surfacePoint))
{
desiredUp = (downRaycastOrigin - surfacePoint).normalized;
damping = 0;
if (!PreviousSateIsValid)
m_PreviousGroundPoint = downRaycastOrigin - motionDir;
}
}
// Rotate to match the desired up direction
float t = Damper.Damp(100, damping, Time.deltaTime) * 0.01f;
var fwd = tr.forward.ProjectOntoPlane(desiredUp);
if (fwd.sqrMagnitude > 0.000001f)
tr.rotation = Quaternion.Slerp(tr.rotation, Quaternion.LookRotation(fwd, desiredUp), t);
else
{
// Rotating 90 degrees - can't preserve the forward
var axis = Vector3.Cross(tr.up, desiredUp);
var angle = UnityVectorExtensions.SignedAngle(tr.up, desiredUp, axis);
var rot = Quaternion.Slerp(Quaternion.identity, Quaternion.AngleAxis(angle, axis), t);
tr.rotation = rot * tr.rotation;
}
m_PreviousPosition = fwdRaycastOrigin;
PreviousSateIsValid = true;
}
Vector3 CaptureUpDirection(RaycastHit hit)
{
m_PreviousGroundPoint = hit.point; // Capture the last point where there was ground under our feet
SetCurrentSurface(hit.collider); // Capture the current ground surface
return SmoothedNormal(hit);
}
void SetCurrentSurface(Collider surface)
{
// If the surface has changed, send an event
if (surface != m_CurrentSurface)
{
m_CurrentSurface = surface;
SurfaceChanged.Invoke(m_CurrentSurface);
}
}
bool FindNearestSurface(Vector3 playerPos, float raycastLength, out Vector3 surfacePoint)
{
surfacePoint = playerPos - transform.up; // default is to continue falling down
// Starting at the bottom, we'll spread out a number of horizontal sweeps over several frames
const float kVerticalStep = 10.0f;
if (m_FreeFallRaycastAngle == 0 || m_FreeFallRaycastAngle > 180 - kVerticalStep)
m_FreeFallRaycastAngle = kVerticalStep / 2 + Time.frameCount % kVerticalStep;
else
m_FreeFallRaycastAngle += kVerticalStep;
// We'll do a horizontal sweep at this angle to find the nearest surface
var up = transform.up;
var dir = Quaternion.AngleAxis(m_FreeFallRaycastAngle, transform.right) * -up;
const float kHorizontalalSteps = 12;
const float kHorizontalStepSize = 360.0f / kHorizontalalSteps;
dir = Quaternion.AngleAxis(Time.frameCount % (int)kHorizontalStepSize, -up) * dir;
float nearestDistance = float.MaxValue;
var rotStep = Quaternion.AngleAxis(kHorizontalStepSize, -up);
for (int i = 0; i < kHorizontalalSteps; ++i, dir = rotStep * dir)
{
//Debug.DrawLine(playerPos, playerPos + dir * raycastLength, Color.yellow, 1);
if (UnityEngine.Physics.Raycast(playerPos, dir, out var hit,
raycastLength, GroundLayers, QueryTriggerInteraction.Ignore))
{
if (hit.distance < nearestDistance)
{
nearestDistance = hit.distance;
surfacePoint = hit.point;
}
}
}
return nearestDistance != float.MaxValue;
}
// This code smooths the normals of a mesh so that they don't change abruptly.
// We cache the mesh data for efficiency to reduce allocations.
struct MeshCache
{
public MeshCollider Mesh;
public Vector3[] Normals;
public int[] Indices;
}
List<MeshCache> m_MeshCacheList = new();
const int kMaxMeshCacheSize = 5;
MeshCache GetMeshCache(MeshCollider collider)
{
for (int i = 0; i < m_MeshCacheList.Count; ++i)
if (m_MeshCacheList[i].Mesh == collider)
return m_MeshCacheList[i];
if (m_MeshCacheList.Count >= kMaxMeshCacheSize)
m_MeshCacheList.RemoveAt(0); // discard oldest
var m = collider.sharedMesh;
var mc = new MeshCache { Mesh = collider, Normals = m.normals, Indices = m.triangles };
m_MeshCacheList.Add(mc);
return mc;
}
Vector3 SmoothedNormal(RaycastHit hit)
{
var mc = hit.collider as MeshCollider;
if (mc == null)
return hit.normal;
var m = GetMeshCache(mc);
var n0 = m.Normals[m.Indices[hit.triangleIndex*3 + 0]];
var n1 = m.Normals[m.Indices[hit.triangleIndex*3 + 1]];
var n2 = m.Normals[m.Indices[hit.triangleIndex*3 + 2]];
var b = hit.barycentricCoordinate;
var localNormal = (b[0] * n0 + b[1] * n1 + b[2] * n2).normalized;
return mc.transform.TransformDirection(localNormal);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5810a0e64264ad44499c7ec07b601026
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,116 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
namespace Unity.Cinemachine.Samples
{
/// <summary>
/// This component manages player shooting. It is expected to be on the player object,
/// or on a child SimplePlayerAimController object of the player.
///
/// If an AimTargetManager is specified, then the behaviour aims at that target.
/// Otherwise, the behaviour aims in the forward direction of the player object,
/// or of the SimplePlayerAimController object if it exists and is not decoupled
/// from the player rotation.
/// </summary>
class SimplePlayerShoot : MonoBehaviour, Unity.Cinemachine.IInputAxisOwner
{
[Tooltip("The bullet prefab to instantiate when firing")]
public GameObject BulletPrefab;
[Tooltip("Maximum bullets per second")]
public float MaxBulletsPerSec = 10;
[Tooltip("Input Axis for firing. Value is 0 or 1")]
public InputAxis Fire = InputAxis.DefaultMomentary;
[Tooltip("Target to Aim towards. If null, the aim is defined by the forward vector of this gameObject.")]
public AimTargetManager AimTargetManager;
[Tooltip("Event that's triggered when firing.")]
public UnityEvent FireEvent;
float m_LastFireTime;
SimplePlayerAimController AimController;
// We pool the bullets for improved performance
readonly List<GameObject> m_BulletPool = new ();
/// Report the available input axes to the input axis controller.
/// We use the Input Axis Controller because it works with both the Input package
/// and the Legacy input system. This is sample code and we
/// want it to work everywhere.
void IInputAxisOwner.GetInputAxes(List<IInputAxisOwner.AxisDescriptor> axes)
{
axes.Add(new () { DrivenAxis = () => ref Fire, Name = "Fire" });
}
void OnValidate()
{
Fire.Validate();
MaxBulletsPerSec = Mathf.Max(1, MaxBulletsPerSec);
}
void Start()
{
TryGetComponent(out AimController);
}
void Update()
{
var now = Time.time;
bool fireNow = BulletPrefab != null
&& now - m_LastFireTime > 1 / MaxBulletsPerSec
&& Fire.Value > 0.1f;
// Get the firing direction. Special case: if there is a decoupled AimController,
// firing direction is character forward, not AimController forward.
var fwd = transform.forward;
bool decoupled = AimController != null
&& AimController.PlayerRotation == SimplePlayerAimController.CouplingMode.Decoupled;
if (decoupled)
fwd = transform.parent.forward;
// Face the firing direction if appropriate
if (AimController != null && !decoupled)
{
var rotationTime = AimController.RotationDamping;
if (fireNow || now - m_LastFireTime <= rotationTime)
AimController.RecenterPlayer(rotationTime);
}
// Fire the bullet
if (fireNow)
{
m_LastFireTime = now;
if (AimTargetManager != null)
fwd = AimTargetManager.GetAimDirection(transform.position, fwd).normalized;
var pos = transform.position + fwd;
var rot = Quaternion.LookRotation(fwd, transform.up);
// Because creating and destroying GameObjects is costly, we pool them and recycle
// the deactivated ones. The bullets deactivate themselves after a time.
GameObject bullet = null;
for (var i = 0; bullet == null && i < m_BulletPool.Count; ++i) // Look in the pool if one is available
{
if (!m_BulletPool[i].activeInHierarchy)
{
bullet = m_BulletPool[i];
bullet.transform.SetPositionAndRotation(pos, rot);
m_BulletPool.Remove(bullet);
}
}
// Instantiate a new bullet if none are found in the pool
if (bullet == null)
bullet = Instantiate(BulletPrefab, pos, rot);
// Off it goes!
m_BulletPool.Add(bullet);
bullet.SetActive(true);
FireEvent.Invoke();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8b9a98e5e4b17784592b9725e8cfc063
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,29 @@
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
public class SpawnInRadius : MonoBehaviour
{
public GameObject Prefab;
public float Radius = 40;
public float Amount = 200;
public bool DoIt;
void Update()
{
if (DoIt && Prefab != null)
{
var spawner = transform;
for (int i = 0; i < Amount; ++i)
{
var a = Random.Range(0, 360);
var pos = new Vector3(Mathf.Cos(a), 0, Mathf.Sin(a));
pos = spawner.position + pos * (Mathf.Sqrt(Random.Range(0.0f, 1.0f)) * Radius);
Instantiate(Prefab, pos, spawner.rotation, spawner.parent);
}
}
DoIt = false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4af788c5ab01a4f658517e19cb276ec5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,115 @@
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
// Interface for behaviours implementing teleportation
public interface ITeleportable
{
// Teleport the object to a new worldspace location and rotation
public void Teleport(Vector3 newPos, Quaternion newRot);
}
// This class will teleport an object and the main CinemachineCamera tracking it to a target Teleporter object.
// This class co-operates with the ITeleportables interface which implements the teleportation.
public class Teleporter : MonoBehaviour
{
// The target portal to which the player will be teleported
public Teleporter TargetPortal;
// Sets the teleported in receivce-only mode, which prevents received
// objects from immediately being teleported out
public bool ReceiveOnlyMode { get; set; }
// Teleport an object to the target portal
void TeleportToTarget(Transform player)
{
// If a target portal is not specified, choose one at random
var targetPortal = TargetPortal;
if (targetPortal == null)
{
var allPortals = FindObjectsByType<Teleporter>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
if (allPortals.Length < 2)
return; // no other portals
// Choose a random one
var index = Random.Range(0, allPortals.Length);
if (allPortals[index] == this && --index < 0)
index = allPortals.Length - 1;
targetPortal = allPortals[index];
}
targetPortal.ReceiveObjectFromPortal(player, transform);
}
// Receive an object from another portal
void ReceiveObjectFromPortal(Transform player, Transform srcPortal)
{
// Deactivate so we can receive player without re-teleporting
ReceiveOnlyMode = true;
var pivot = srcPortal.position;
var rotDelta = Quaternion.FromToRotation(srcPortal.forward, transform.forward);
var posDelta = transform.position - pivot;
// Teleport the player. Either set the new pos/rot directly, or if a ITeleportable
// is present, get it to do the teleportation.
player.GetPositionAndRotation(out var playerPos, out var playerRot);
var newPlayerPos = RotateAround(playerPos, pivot, rotDelta) + posDelta;
var newPlayerRot = rotDelta * playerRot;
if (player.TryGetComponent(out ITeleportable teleportable))
teleportable.Teleport(newPlayerPos, newPlayerRot);
else
player.SetPositionAndRotation(newPlayerPos, newPlayerRot);
// Teleport the camera. This implementation only works for cameras which directly target the player.
// If your camera target is a child of player, then you will need to do some extra work here
// to find the appropriate target.
var cameraTarget = player;
// Now we iterate all active cameras targeting the player, teleporting each one.
for (int i = 0; i < CinemachineCore.VirtualCameraCount; ++i)
{
var cam = CinemachineCore.GetVirtualCamera(i);
if (cam.Follow != cameraTarget)
continue;
// Note that we don't use the camera's transform since that doesn't always reflect the
// actual position and rotation. Instead, we snapshot the VirtualCamera's State member.
var state = cam.State;
// This call will seamlessly teleport the camera based on the player's change of position,
// but it will not handle a change of rotation
cam.OnTargetObjectWarped(cameraTarget, newPlayerPos - playerPos);
// If the player-camera combo is not only being translated but also rotated by the portal,
// we need to do some additional work to tell the CinemachineCamera to rotate its state
// and teleport seamlessly.
if (rotDelta != Quaternion.identity)
{
// Here we grab the camera's original position and put it through the same teleportation
// as the player, preserving the relationship between camera and player.
var camPos = state.GetFinalPosition();
var camRot = state.GetFinalOrientation();
var newCamPos = RotateAround(camPos, pivot, rotDelta) + posDelta;
var newCamRot = rotDelta * camRot;
// Now force the CinemachineCamera to be at the desired position and rotation. This will
// also manipulate the internal camera state, so that no spurious damping or gitching will occur
cam.ForceCameraPosition(newCamPos, newCamRot);
}
}
// This is a helper function to rotate a point around a pivot using a quaternion rotation
static Vector3 RotateAround(Vector3 p, Vector3 pivot, Quaternion rot) => rot * (p - pivot) + pivot;
}
// When something enters the trigger zone, we teleport it
void OnTriggerEnter(Collider other)
{
if (!ReceiveOnlyMode)
TeleportToTarget(other.transform);
}
// After receiving an object, we can't teleport it away until after it leaves the trigger zone
void OnTriggerExit(Collider other) => ReceiveOnlyMode = false;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 31da745dcfdedca4caed18d398713609

View File

@@ -0,0 +1,44 @@
using System.Collections.Generic;
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
public class ThirdPersonFollowCameraSideSwapper : MonoBehaviour
{
[Tooltip("How long the shoulder swap will take")]
public float Damping;
List<CinemachineThirdPersonFollow> m_ThirdPersonFollows = new();
float m_SwapDirection;
void OnEnable()
{
GetComponentsInChildren(true, m_ThirdPersonFollows);
}
void Update()
{
bool allDone = true;
if (m_SwapDirection != 0)
{
float swapTarget = m_SwapDirection > 0 ? 1 : 0;
for (int i = 0; i < m_ThirdPersonFollows.Count; ++i)
{
m_ThirdPersonFollows[i].CameraSide +=
Damper.Damp(swapTarget - m_ThirdPersonFollows[i].CameraSide, Damping, Time.deltaTime);
if (Mathf.Abs(m_ThirdPersonFollows[i].CameraSide - swapTarget) > UnityVectorExtensions.Epsilon)
allDone = false;
}
}
if (allDone)
m_SwapDirection = 0;
}
public void Swap()
{
m_SwapDirection *= -1;
for (int i = 0; m_SwapDirection == 0 && i < m_ThirdPersonFollows.Count; ++i)
m_SwapDirection = m_ThirdPersonFollows[i].CameraSide > 0.5f ? -1 : 1;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2ffbcba0a55c544a181ba9b409a505fa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
public class TurnAroundPlayer : MonoBehaviour
{
public GameObject Player;
SimplePlayerController m_PlayerController;
void Start()
{
m_PlayerController = Player.GetComponent<SimplePlayerController>();
}
void OnTriggerEnter(Collider other)
{
if (other.CompareTag(Player.tag))
m_PlayerController.MoveZ.Value *= -1f; // flip direction
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5d7e5e287b7c6413086331f81c3a8de2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,45 @@
using UnityEngine;
namespace Unity.Cinemachine.Samples
{
/// <summary>
/// Implements continuous motion by wrapping the position around a range.
/// </summary>
public class WrapAround : MonoBehaviour
{
public enum AxisSelection { XAxis = 0, YAxis = 1, ZAxis = 2 };
public AxisSelection Axis = AxisSelection.ZAxis;
public float MinRange;
public float MaxRange;
private void OnValidate()
{
MaxRange = Mathf.Max(MinRange, MaxRange);
}
void LateUpdate()
{
// Wrap the axis around the range
var pos = transform.position;
var newPos = pos;
if (newPos[(int)Axis] < MinRange)
newPos[(int)Axis] += MaxRange - MinRange;
if (newPos[(int)Axis] > MaxRange)
newPos[(int)Axis] += MinRange - MaxRange;
var delta = newPos - pos;
if (!delta.AlmostZero())
{
transform.position = newPos;
// Handle objects driven by a Rigidbody.
// We don't use Rigidbody.MovePosition() because it's a warp and we want to bypass interpolation.
if (TryGetComponent<Rigidbody>(out var rb))
rb.position = newPos;
// Notify any CinemachineCameras that are targeting this object
CinemachineCore.OnTargetObjectWarped(transform, delta);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5498592a73332894897637bff40489e2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: