Added Observation Scene to show the Replay
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
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" });
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8861e25f312f0a4ea14b26b40531fa4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 512059070b04a4ca6b07b64f00733c27
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Cinemachine.Samples
|
||||
{
|
||||
/// <summary>
|
||||
/// An example add-on module for Cinemachine Virtual Camera for controlling
|
||||
/// the FadeOut shader included in our example package.
|
||||
/// </summary>
|
||||
[ExecuteAlways]
|
||||
public class CinemachineFadeOutShaderController : 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 28e3f754a40cc487cac0f59efb1118db
|
||||
timeCreated: 1611253266
|
||||
@@ -0,0 +1,57 @@
|
||||
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 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84bbe8a9281f49f9801ffc0203bee6df
|
||||
timeCreated: 1682089493
|
||||
@@ -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(1, PositionDamping, Time.deltaTime);
|
||||
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(1, RotationDamping, Time.deltaTime);
|
||||
var srcUp = transform.up;
|
||||
var dstUp = Target.up;
|
||||
var axis = Vector3.Cross(srcUp, dstUp);
|
||||
if (axis.sqrMagnitude > 0.001f)
|
||||
{
|
||||
var angle = UnityVectorExtensions.SignedAngle(srcUp, dstUp, axis) * t;
|
||||
rot = Quaternion.AngleAxis(angle, axis) * rot;
|
||||
}
|
||||
transform.SetPositionAndRotation(pos, rot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a51048181b52dd044a17566021c7c163
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae1b182fbad6e0f4fbf3a51e56784830
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,96 @@
|
||||
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()
|
||||
{
|
||||
Pan.Validate();
|
||||
Tilt.Range.x = Mathf.Clamp(Tilt.Range.x, -90, 90);
|
||||
Tilt.Range.y = Mathf.Clamp(Tilt.Range.y, -90, 90);
|
||||
Tilt.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e1d63f642ebbdb4eb38a57a250ab00d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eed4d084bee9a604a9bd6dcfe5987fa4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 703a8ce5f266d408ca92818e87835af3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6aefb4603cbd49d4691e1aea49f45b00
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f6129fefa8c2eb41a2b757c190472af
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a508fe0a084bc46889cc2017b857a97a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,41 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace Unity.Cinemachine.Samples
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d73e77c46508cf441ad464dc058f7a86
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+43
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af2f2697aa2d246eaa1bbac01e1cff23
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d02e38cc82ce35043b04296eeaff6f29
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+17
@@ -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;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ba786fa8607649dab5568a9a20a8906
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29d5b71c25a12441dbb6f920533a92df
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7950c6f52d74a4772bb69c03fe19a19b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be49d5e8cf7024d6b8f8d5b9872c0f91
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+29
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2ffe8fe055874f88a4f26bff5ebce0b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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 (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1389666f22088e47a6ca8ccd8d0b78e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,78 @@
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 45a1d5f246db83d4e9db796e9e19282e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5232368c753c54e419e00665fe96a8e1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be929e8436c3caf46b4ef616ef0e824e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,384 @@
|
||||
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" });
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
[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;
|
||||
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 (Physics.Raycast(pos + up * kExtraHeight, -up, out var hit,
|
||||
max + kExtraHeight, GroundLayers, QueryTriggerInteraction.Ignore))
|
||||
return hit.distance - kExtraHeight;
|
||||
return max + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 297aaca66939f724fbbc8f0c485168bc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a88ade7667a42594ba2c435a7d3b2307
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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 (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 && 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
|
||||
&& 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(1, damping, Time.deltaTime);
|
||||
var fwd = tr.forward.ProjectOntoPlane(desiredUp);
|
||||
if (fwd.sqrMagnitude > 0.0001f)
|
||||
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 (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5810a0e64264ad44499c7ec07b601026
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,115 @@
|
||||
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()
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b9a98e5e4b17784592b9725e8cfc063
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4af788c5ab01a4f658517e19cb276ec5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+44
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ffbcba0a55c544a181ba9b409a505fa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d7e5e287b7c6413086331f81c3a8de2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5498592a73332894897637bff40489e2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user