Initial Unity project commit
This commit is contained in:
@@ -0,0 +1,521 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
#if DOTWEEN
|
||||
using DG.Tweening;
|
||||
#endif
|
||||
|
||||
namespace Test_2022.CameraControl
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class CameraWaypointSwitcher : MonoBehaviour
|
||||
{
|
||||
public enum TransitionMode
|
||||
{
|
||||
Instant = 0,
|
||||
Tween = 1
|
||||
}
|
||||
|
||||
public enum PathMode
|
||||
{
|
||||
Loop = 0,
|
||||
PingPong = 1
|
||||
}
|
||||
|
||||
public enum ShakeMode
|
||||
{
|
||||
Off = 0,
|
||||
Handheld = 1,
|
||||
WhileMoving = 2
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct Waypoint
|
||||
{
|
||||
public Transform point;
|
||||
[Min(0f)] public float holdSeconds;
|
||||
}
|
||||
|
||||
[Header("Target")]
|
||||
[SerializeField] Transform target;
|
||||
[SerializeField] bool autoStartOnEnable = true;
|
||||
[SerializeField] bool useUnscaledTime;
|
||||
[SerializeField] bool restoreOnDisable = true;
|
||||
|
||||
[Header("Path")]
|
||||
[SerializeField] TransitionMode transitionMode = TransitionMode.Instant;
|
||||
[SerializeField] PathMode pathMode = PathMode.PingPong;
|
||||
[SerializeField] List<Waypoint> waypoints = new List<Waypoint>();
|
||||
[SerializeField, Min(0f)] float moveDurationSeconds = 1.2f;
|
||||
[SerializeField, Min(0f)] float defaultHoldSeconds = 1.5f;
|
||||
[SerializeField] AnimationCurve moveEase = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
|
||||
[SerializeField] bool startFromNearestWaypoint = true;
|
||||
|
||||
[Header("Shake")]
|
||||
[SerializeField] ShakeMode shakeMode = ShakeMode.Handheld;
|
||||
[SerializeField] bool createRigForShake = true;
|
||||
[SerializeField] bool shakePosition = true;
|
||||
[SerializeField] bool shakeRotation = true;
|
||||
[SerializeField, Min(0f)] float handheldPositionAmplitude = 0.015f;
|
||||
[SerializeField, Min(0f)] float handheldRotationAmplitude = 0.45f;
|
||||
[SerializeField, Min(0f)] float handheldFrequency = 0.35f;
|
||||
[SerializeField, Min(0f)] float handheldSmoothingSeconds = 0.12f;
|
||||
[SerializeField] bool handheldUseFixedSeed;
|
||||
[SerializeField] int handheldSeed = 12345;
|
||||
|
||||
[Header("Shake (While Moving)")]
|
||||
[SerializeField, Min(0f)] float shakeStrengthPosition = 0.02f;
|
||||
[SerializeField, Min(0f)] float shakeStrengthRotation = 0.25f;
|
||||
[SerializeField, Min(1)] int shakeVibrato = 8;
|
||||
[SerializeField, Range(0f, 180f)] float shakeRandomness = 25f;
|
||||
|
||||
Vector3 basePosition;
|
||||
Quaternion baseRotation;
|
||||
Transform rig;
|
||||
Transform shakeTransform;
|
||||
Vector3 shakeBaseLocalPosition;
|
||||
Quaternion shakeBaseLocalRotation;
|
||||
Vector3 shakePosOffset;
|
||||
Vector3 shakeRotOffset;
|
||||
float shakeSeedX;
|
||||
float shakeSeedY;
|
||||
float shakeSeedZ;
|
||||
float lastShakeTime;
|
||||
Coroutine cutRoutine;
|
||||
bool isPlaying;
|
||||
|
||||
#if DOTWEEN
|
||||
Sequence sequence;
|
||||
Tween shakePosTween;
|
||||
Tween shakeRotTween;
|
||||
#endif
|
||||
|
||||
public IList<Waypoint> Waypoints => waypoints;
|
||||
|
||||
void Reset()
|
||||
{
|
||||
var cam = Camera.main;
|
||||
if (cam != null)
|
||||
target = cam.transform;
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (autoStartOnEnable)
|
||||
StartPlayback();
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
StopPlayback(restoreOnDisable);
|
||||
}
|
||||
|
||||
public void StartPlayback()
|
||||
{
|
||||
if (target == null)
|
||||
return;
|
||||
if (waypoints == null || waypoints.Count == 0)
|
||||
return;
|
||||
|
||||
StopPlayback(false);
|
||||
CacheBasePose();
|
||||
SetupRigAndShakeTransforms();
|
||||
CacheShakeLocalBase();
|
||||
ResetHandheldState();
|
||||
|
||||
var moveTransform = rig != null ? rig : target;
|
||||
|
||||
var indices = BuildTraversalIndices(pathMode, waypoints.Count);
|
||||
if (indices.Count == 0)
|
||||
return;
|
||||
|
||||
if (startFromNearestWaypoint)
|
||||
RotateTraversalToNearest(indices, moveTransform.position);
|
||||
|
||||
isPlaying = true;
|
||||
|
||||
if (transitionMode == TransitionMode.Instant)
|
||||
{
|
||||
cutRoutine = StartCoroutine(RunCutLoop(moveTransform, indices));
|
||||
return;
|
||||
}
|
||||
|
||||
#if DOTWEEN
|
||||
sequence = DOTween.Sequence();
|
||||
sequence.SetUpdate(useUnscaledTime);
|
||||
sequence.SetAutoKill(false);
|
||||
|
||||
for (var i = 0; i < indices.Count; i++)
|
||||
{
|
||||
var waypointIndex = indices[i];
|
||||
var wp = waypoints[waypointIndex].point;
|
||||
if (wp == null)
|
||||
continue;
|
||||
|
||||
var move = moveTransform.DOMove(wp.position, moveDurationSeconds);
|
||||
move.SetEase(moveEase);
|
||||
|
||||
var rot = moveTransform.DORotateQuaternion(wp.rotation, moveDurationSeconds);
|
||||
rot.SetEase(moveEase);
|
||||
|
||||
sequence.Append(move);
|
||||
sequence.Join(rot);
|
||||
|
||||
if (shakeMode == ShakeMode.WhileMoving)
|
||||
sequence.Join(CreateOneShotShakeTween(moveDurationSeconds));
|
||||
|
||||
sequence.AppendInterval(GetHoldSeconds(waypointIndex));
|
||||
}
|
||||
|
||||
sequence.SetLoops(-1, LoopType.Restart);
|
||||
|
||||
sequence.Play();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void StopPlayback(bool restore)
|
||||
{
|
||||
isPlaying = false;
|
||||
|
||||
if (cutRoutine != null)
|
||||
{
|
||||
StopCoroutine(cutRoutine);
|
||||
cutRoutine = null;
|
||||
}
|
||||
|
||||
#if DOTWEEN
|
||||
if (sequence != null)
|
||||
{
|
||||
sequence.Kill(false);
|
||||
sequence = null;
|
||||
}
|
||||
|
||||
if (shakePosTween != null)
|
||||
{
|
||||
shakePosTween.Kill(false);
|
||||
shakePosTween = null;
|
||||
}
|
||||
|
||||
if (shakeRotTween != null)
|
||||
{
|
||||
shakeRotTween.Kill(false);
|
||||
shakeRotTween = null;
|
||||
}
|
||||
#endif
|
||||
|
||||
ResetShakeTransformLocal();
|
||||
|
||||
if (restore)
|
||||
RestoreBasePose();
|
||||
|
||||
TeardownRigIfCreated();
|
||||
}
|
||||
|
||||
public void ClearWaypoints()
|
||||
{
|
||||
waypoints.Clear();
|
||||
}
|
||||
|
||||
public bool AddWaypoint(Transform point, float holdSeconds = -1f)
|
||||
{
|
||||
if (point == null)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < waypoints.Count; i++)
|
||||
{
|
||||
if (waypoints[i].point == point)
|
||||
return false;
|
||||
}
|
||||
|
||||
waypoints.Add(new Waypoint
|
||||
{
|
||||
point = point,
|
||||
holdSeconds = holdSeconds
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
public int RemoveNullWaypoints()
|
||||
{
|
||||
return waypoints.RemoveAll(w => w.point == null);
|
||||
}
|
||||
|
||||
void CacheBasePose()
|
||||
{
|
||||
if (target == null)
|
||||
return;
|
||||
basePosition = target.position;
|
||||
baseRotation = target.rotation;
|
||||
}
|
||||
|
||||
void RestoreBasePose()
|
||||
{
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
target.position = basePosition;
|
||||
target.rotation = baseRotation;
|
||||
}
|
||||
|
||||
void SetupRigAndShakeTransforms()
|
||||
{
|
||||
rig = null;
|
||||
shakeTransform = target;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
if (!createRigForShake || shakeMode == ShakeMode.Off)
|
||||
return;
|
||||
|
||||
var rigGo = new GameObject(target.name + "_Rig");
|
||||
rigGo.transform.SetPositionAndRotation(target.position, target.rotation);
|
||||
rigGo.transform.SetParent(target.parent, true);
|
||||
rig = rigGo.transform;
|
||||
|
||||
target.SetParent(rig, true);
|
||||
shakeTransform = target;
|
||||
target.localPosition = Vector3.zero;
|
||||
target.localRotation = Quaternion.identity;
|
||||
}
|
||||
|
||||
void CacheShakeLocalBase()
|
||||
{
|
||||
if (shakeTransform == null)
|
||||
return;
|
||||
shakeBaseLocalPosition = shakeTransform.localPosition;
|
||||
shakeBaseLocalRotation = shakeTransform.localRotation;
|
||||
}
|
||||
|
||||
void ResetShakeTransformLocal()
|
||||
{
|
||||
if (shakeTransform == null)
|
||||
return;
|
||||
shakeTransform.localPosition = shakeBaseLocalPosition;
|
||||
shakeTransform.localRotation = shakeBaseLocalRotation;
|
||||
}
|
||||
|
||||
void ResetHandheldState()
|
||||
{
|
||||
shakePosOffset = Vector3.zero;
|
||||
shakeRotOffset = Vector3.zero;
|
||||
lastShakeTime = useUnscaledTime ? Time.unscaledTime : Time.time;
|
||||
|
||||
if (handheldUseFixedSeed)
|
||||
{
|
||||
shakeSeedX = handheldSeed * 0.0137f + 10.1f;
|
||||
shakeSeedY = handheldSeed * 0.0213f + 20.2f;
|
||||
shakeSeedZ = handheldSeed * 0.0341f + 30.3f;
|
||||
}
|
||||
else
|
||||
{
|
||||
shakeSeedX = UnityEngine.Random.value * 1000f;
|
||||
shakeSeedY = UnityEngine.Random.value * 1000f;
|
||||
shakeSeedZ = UnityEngine.Random.value * 1000f;
|
||||
}
|
||||
}
|
||||
|
||||
void TeardownRigIfCreated()
|
||||
{
|
||||
if (rig == null || target == null)
|
||||
{
|
||||
rig = null;
|
||||
shakeTransform = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.parent == rig)
|
||||
target.SetParent(rig.parent, true);
|
||||
|
||||
if (Application.isPlaying)
|
||||
Destroy(rig.gameObject);
|
||||
else
|
||||
DestroyImmediate(rig.gameObject);
|
||||
|
||||
rig = null;
|
||||
shakeTransform = null;
|
||||
}
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
if (!isPlaying)
|
||||
return;
|
||||
if (shakeMode != ShakeMode.Handheld)
|
||||
return;
|
||||
if (shakeTransform == null)
|
||||
return;
|
||||
if (!shakePosition && !shakeRotation)
|
||||
return;
|
||||
|
||||
var now = useUnscaledTime ? Time.unscaledTime : Time.time;
|
||||
var dt = Mathf.Max(0f, now - lastShakeTime);
|
||||
lastShakeTime = now;
|
||||
|
||||
var t = now * Mathf.Max(0f, handheldFrequency);
|
||||
|
||||
var nx = Mathf.PerlinNoise(shakeSeedX, t) * 2f - 1f;
|
||||
var ny = Mathf.PerlinNoise(shakeSeedY, t + 11.1f) * 2f - 1f;
|
||||
var nz = Mathf.PerlinNoise(shakeSeedZ, t + 22.2f) * 2f - 1f;
|
||||
|
||||
var targetPos = Vector3.zero;
|
||||
if (shakePosition && handheldPositionAmplitude > 0f)
|
||||
{
|
||||
targetPos = new Vector3(nx, ny * 0.7f, nz * 0.35f) * handheldPositionAmplitude;
|
||||
}
|
||||
|
||||
var targetRot = Vector3.zero;
|
||||
if (shakeRotation && handheldRotationAmplitude > 0f)
|
||||
{
|
||||
targetRot = new Vector3(ny * 0.75f, nx * 0.5f, nz) * handheldRotationAmplitude;
|
||||
}
|
||||
|
||||
var smooth = handheldSmoothingSeconds <= 0f ? 1f : 1f - Mathf.Exp(-dt / Mathf.Max(0.0001f, handheldSmoothingSeconds));
|
||||
shakePosOffset = Vector3.Lerp(shakePosOffset, targetPos, smooth);
|
||||
shakeRotOffset = Vector3.Lerp(shakeRotOffset, targetRot, smooth);
|
||||
|
||||
shakeTransform.localPosition = shakeBaseLocalPosition + shakePosOffset;
|
||||
shakeTransform.localRotation = shakeBaseLocalRotation * Quaternion.Euler(shakeRotOffset);
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator RunCutLoop(Transform moveTransform, List<int> indices)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
for (var i = 0; i < indices.Count; i++)
|
||||
{
|
||||
var waypointIndex = indices[i];
|
||||
if (waypointIndex < 0 || waypointIndex >= waypoints.Count)
|
||||
continue;
|
||||
|
||||
var wp = waypoints[waypointIndex].point;
|
||||
if (wp == null)
|
||||
continue;
|
||||
|
||||
moveTransform.SetPositionAndRotation(wp.position, wp.rotation);
|
||||
|
||||
var hold = Mathf.Max(0f, GetHoldSeconds(waypointIndex));
|
||||
if (hold > 0f)
|
||||
{
|
||||
if (useUnscaledTime)
|
||||
yield return new WaitForSecondsRealtime(hold);
|
||||
else
|
||||
yield return new WaitForSeconds(hold);
|
||||
}
|
||||
else
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float GetHoldSeconds(int waypointIndex)
|
||||
{
|
||||
if (waypointIndex < 0 || waypointIndex >= waypoints.Count)
|
||||
return defaultHoldSeconds;
|
||||
|
||||
var h = waypoints[waypointIndex].holdSeconds;
|
||||
return h > 0f ? h : defaultHoldSeconds;
|
||||
}
|
||||
|
||||
static List<int> BuildTraversalIndices(PathMode mode, int count)
|
||||
{
|
||||
var list = new List<int>();
|
||||
if (count <= 0)
|
||||
return list;
|
||||
|
||||
if (count == 1)
|
||||
{
|
||||
list.Add(0);
|
||||
return list;
|
||||
}
|
||||
|
||||
if (mode == PathMode.Loop)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
list.Add(i);
|
||||
return list;
|
||||
}
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
list.Add(i);
|
||||
for (var i = count - 2; i >= 1; i--)
|
||||
list.Add(i);
|
||||
return list;
|
||||
}
|
||||
|
||||
void RotateTraversalToNearest(List<int> indices, Vector3 currentPosition)
|
||||
{
|
||||
if (indices == null || indices.Count == 0)
|
||||
return;
|
||||
|
||||
var best = 0;
|
||||
var bestDist = float.PositiveInfinity;
|
||||
|
||||
for (var i = 0; i < indices.Count; i++)
|
||||
{
|
||||
var wi = indices[i];
|
||||
if (wi < 0 || wi >= waypoints.Count)
|
||||
continue;
|
||||
var p = waypoints[wi].point;
|
||||
if (p == null)
|
||||
continue;
|
||||
var d = (p.position - currentPosition).sqrMagnitude;
|
||||
if (d < bestDist)
|
||||
{
|
||||
bestDist = d;
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (best <= 0)
|
||||
return;
|
||||
|
||||
var rotated = new List<int>(indices.Count);
|
||||
for (var i = best; i < indices.Count; i++)
|
||||
rotated.Add(indices[i]);
|
||||
for (var i = 0; i < best; i++)
|
||||
rotated.Add(indices[i]);
|
||||
|
||||
indices.Clear();
|
||||
indices.AddRange(rotated);
|
||||
}
|
||||
|
||||
#if DOTWEEN
|
||||
Tween CreateOneShotShakeTween(float duration)
|
||||
{
|
||||
if (shakeTransform == null)
|
||||
return null;
|
||||
|
||||
var seq = DOTween.Sequence();
|
||||
seq.SetUpdate(useUnscaledTime);
|
||||
|
||||
if (shakePosition && shakeStrengthPosition > 0f)
|
||||
{
|
||||
var t = shakeTransform.DOShakePosition(
|
||||
duration,
|
||||
shakeStrengthPosition,
|
||||
shakeVibrato,
|
||||
shakeRandomness,
|
||||
false,
|
||||
true
|
||||
);
|
||||
seq.Join(t);
|
||||
}
|
||||
|
||||
if (shakeRotation && shakeStrengthRotation > 0f)
|
||||
{
|
||||
var t = shakeTransform.DOShakeRotation(
|
||||
duration,
|
||||
shakeStrengthRotation,
|
||||
shakeVibrato,
|
||||
shakeRandomness,
|
||||
true
|
||||
);
|
||||
seq.Join(t);
|
||||
}
|
||||
|
||||
return seq;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 03105c644beb8724b86cea4ec224b18e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,406 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
#if DOTWEEN
|
||||
using DG.Tweening;
|
||||
#endif
|
||||
|
||||
namespace Test_2022.CameraControl
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class CameraWaypointSwitcherTweenedBackup : MonoBehaviour
|
||||
{
|
||||
public enum PathMode
|
||||
{
|
||||
Loop = 0,
|
||||
PingPong = 1
|
||||
}
|
||||
|
||||
public enum ShakeMode
|
||||
{
|
||||
Off = 0,
|
||||
Continuous = 1,
|
||||
WhileMoving = 2
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct Waypoint
|
||||
{
|
||||
public Transform point;
|
||||
[Min(0f)] public float holdSeconds;
|
||||
}
|
||||
|
||||
[Header("Target")]
|
||||
[SerializeField] Transform target;
|
||||
[SerializeField] bool autoStartOnEnable = true;
|
||||
[SerializeField] bool useUnscaledTime;
|
||||
[SerializeField] bool restoreOnDisable = true;
|
||||
|
||||
[Header("Path")]
|
||||
[SerializeField] PathMode pathMode = PathMode.PingPong;
|
||||
[SerializeField] List<Waypoint> waypoints = new List<Waypoint>();
|
||||
[SerializeField, Min(0f)] float moveDurationSeconds = 1.2f;
|
||||
[SerializeField, Min(0f)] float defaultHoldSeconds = 1.5f;
|
||||
[SerializeField] AnimationCurve moveEase = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
|
||||
[SerializeField] bool startFromNearestWaypoint = true;
|
||||
|
||||
[Header("Shake")]
|
||||
[SerializeField] ShakeMode shakeMode = ShakeMode.Continuous;
|
||||
[SerializeField] bool createRigForShake = true;
|
||||
[SerializeField] bool shakePosition = true;
|
||||
[SerializeField] bool shakeRotation = true;
|
||||
[SerializeField, Min(0f)] float shakeStrengthPosition = 0.06f;
|
||||
[SerializeField, Min(0f)] float shakeStrengthRotation = 0.65f;
|
||||
[SerializeField, Min(1)] int shakeVibrato = 12;
|
||||
[SerializeField, Range(0f, 180f)] float shakeRandomness = 90f;
|
||||
|
||||
Vector3 basePosition;
|
||||
Quaternion baseRotation;
|
||||
Transform rig;
|
||||
Transform shakeTransform;
|
||||
|
||||
#if DOTWEEN
|
||||
Sequence sequence;
|
||||
Tween shakePosTween;
|
||||
Tween shakeRotTween;
|
||||
#endif
|
||||
|
||||
public IList<Waypoint> Waypoints => waypoints;
|
||||
|
||||
void Reset()
|
||||
{
|
||||
var cam = Camera.main;
|
||||
if (cam != null)
|
||||
target = cam.transform;
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (autoStartOnEnable)
|
||||
StartPlayback();
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
StopPlayback(restoreOnDisable);
|
||||
}
|
||||
|
||||
public void StartPlayback()
|
||||
{
|
||||
#if DOTWEEN
|
||||
if (target == null)
|
||||
return;
|
||||
if (waypoints == null || waypoints.Count == 0)
|
||||
return;
|
||||
|
||||
StopPlayback(false);
|
||||
CacheBasePose();
|
||||
SetupRigAndShakeTransforms();
|
||||
|
||||
var moveTransform = rig != null ? rig : target;
|
||||
|
||||
var indices = BuildTraversalIndices(pathMode, waypoints.Count);
|
||||
if (indices.Count == 0)
|
||||
return;
|
||||
|
||||
if (startFromNearestWaypoint)
|
||||
RotateTraversalToNearest(indices, moveTransform.position);
|
||||
|
||||
sequence = DOTween.Sequence();
|
||||
sequence.SetUpdate(useUnscaledTime);
|
||||
sequence.SetAutoKill(false);
|
||||
|
||||
for (var i = 0; i < indices.Count; i++)
|
||||
{
|
||||
var wp = waypoints[indices[i]].point;
|
||||
if (wp == null)
|
||||
continue;
|
||||
|
||||
var move = moveTransform.DOMove(wp.position, moveDurationSeconds);
|
||||
move.SetEase(moveEase);
|
||||
|
||||
var rot = moveTransform.DORotateQuaternion(wp.rotation, moveDurationSeconds);
|
||||
rot.SetEase(moveEase);
|
||||
|
||||
sequence.Append(move);
|
||||
sequence.Join(rot);
|
||||
|
||||
if (shakeMode == ShakeMode.WhileMoving)
|
||||
sequence.Join(CreateOneShotShakeTween(moveDurationSeconds));
|
||||
|
||||
sequence.AppendInterval(GetHoldSeconds(indices[i]));
|
||||
}
|
||||
|
||||
sequence.SetLoops(-1, LoopType.Restart);
|
||||
|
||||
if (shakeMode == ShakeMode.Continuous)
|
||||
StartContinuousShake();
|
||||
|
||||
sequence.Play();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void StopPlayback(bool restore)
|
||||
{
|
||||
#if DOTWEEN
|
||||
if (sequence != null)
|
||||
{
|
||||
sequence.Kill(false);
|
||||
sequence = null;
|
||||
}
|
||||
|
||||
if (shakePosTween != null)
|
||||
{
|
||||
shakePosTween.Kill(false);
|
||||
shakePosTween = null;
|
||||
}
|
||||
|
||||
if (shakeRotTween != null)
|
||||
{
|
||||
shakeRotTween.Kill(false);
|
||||
shakeRotTween = null;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (restore)
|
||||
RestoreBasePose();
|
||||
|
||||
TeardownRigIfCreated();
|
||||
}
|
||||
|
||||
public void ClearWaypoints()
|
||||
{
|
||||
waypoints.Clear();
|
||||
}
|
||||
|
||||
public bool AddWaypoint(Transform point, float holdSeconds = -1f)
|
||||
{
|
||||
if (point == null)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < waypoints.Count; i++)
|
||||
{
|
||||
if (waypoints[i].point == point)
|
||||
return false;
|
||||
}
|
||||
|
||||
waypoints.Add(new Waypoint
|
||||
{
|
||||
point = point,
|
||||
holdSeconds = holdSeconds
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
public int RemoveNullWaypoints()
|
||||
{
|
||||
return waypoints.RemoveAll(w => w.point == null);
|
||||
}
|
||||
|
||||
void CacheBasePose()
|
||||
{
|
||||
if (target == null)
|
||||
return;
|
||||
basePosition = target.position;
|
||||
baseRotation = target.rotation;
|
||||
}
|
||||
|
||||
void RestoreBasePose()
|
||||
{
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
target.position = basePosition;
|
||||
target.rotation = baseRotation;
|
||||
}
|
||||
|
||||
void SetupRigAndShakeTransforms()
|
||||
{
|
||||
rig = null;
|
||||
shakeTransform = target;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
if (!createRigForShake || shakeMode == ShakeMode.Off)
|
||||
return;
|
||||
|
||||
var rigGo = new GameObject(target.name + "_Rig");
|
||||
rigGo.transform.SetPositionAndRotation(target.position, target.rotation);
|
||||
rigGo.transform.SetParent(target.parent, true);
|
||||
rig = rigGo.transform;
|
||||
|
||||
target.SetParent(rig, true);
|
||||
shakeTransform = target;
|
||||
target.localPosition = Vector3.zero;
|
||||
target.localRotation = Quaternion.identity;
|
||||
}
|
||||
|
||||
void TeardownRigIfCreated()
|
||||
{
|
||||
if (rig == null || target == null)
|
||||
{
|
||||
rig = null;
|
||||
shakeTransform = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.parent == rig)
|
||||
target.SetParent(rig.parent, true);
|
||||
|
||||
if (Application.isPlaying)
|
||||
Destroy(rig.gameObject);
|
||||
else
|
||||
DestroyImmediate(rig.gameObject);
|
||||
|
||||
rig = null;
|
||||
shakeTransform = null;
|
||||
}
|
||||
|
||||
float GetHoldSeconds(int waypointIndex)
|
||||
{
|
||||
if (waypointIndex < 0 || waypointIndex >= waypoints.Count)
|
||||
return defaultHoldSeconds;
|
||||
|
||||
var h = waypoints[waypointIndex].holdSeconds;
|
||||
return h > 0f ? h : defaultHoldSeconds;
|
||||
}
|
||||
|
||||
static List<int> BuildTraversalIndices(PathMode mode, int count)
|
||||
{
|
||||
var list = new List<int>();
|
||||
if (count <= 0)
|
||||
return list;
|
||||
|
||||
if (count == 1)
|
||||
{
|
||||
list.Add(0);
|
||||
return list;
|
||||
}
|
||||
|
||||
if (mode == PathMode.Loop)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
list.Add(i);
|
||||
return list;
|
||||
}
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
list.Add(i);
|
||||
for (var i = count - 2; i >= 1; i--)
|
||||
list.Add(i);
|
||||
return list;
|
||||
}
|
||||
|
||||
void RotateTraversalToNearest(List<int> indices, Vector3 currentPosition)
|
||||
{
|
||||
if (indices == null || indices.Count == 0)
|
||||
return;
|
||||
|
||||
var best = 0;
|
||||
var bestDist = float.PositiveInfinity;
|
||||
|
||||
for (var i = 0; i < indices.Count; i++)
|
||||
{
|
||||
var wi = indices[i];
|
||||
if (wi < 0 || wi >= waypoints.Count)
|
||||
continue;
|
||||
var p = waypoints[wi].point;
|
||||
if (p == null)
|
||||
continue;
|
||||
var d = (p.position - currentPosition).sqrMagnitude;
|
||||
if (d < bestDist)
|
||||
{
|
||||
bestDist = d;
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (best <= 0)
|
||||
return;
|
||||
|
||||
var rotated = new List<int>(indices.Count);
|
||||
for (var i = best; i < indices.Count; i++)
|
||||
rotated.Add(indices[i]);
|
||||
for (var i = 0; i < best; i++)
|
||||
rotated.Add(indices[i]);
|
||||
|
||||
indices.Clear();
|
||||
indices.AddRange(rotated);
|
||||
}
|
||||
|
||||
#if DOTWEEN
|
||||
Tween CreateOneShotShakeTween(float duration)
|
||||
{
|
||||
if (shakeTransform == null)
|
||||
return null;
|
||||
|
||||
var seq = DOTween.Sequence();
|
||||
seq.SetUpdate(useUnscaledTime);
|
||||
|
||||
if (shakePosition && shakeStrengthPosition > 0f)
|
||||
{
|
||||
var t = shakeTransform.DOShakePosition(
|
||||
duration,
|
||||
shakeStrengthPosition,
|
||||
shakeVibrato,
|
||||
shakeRandomness,
|
||||
false,
|
||||
true
|
||||
);
|
||||
seq.Join(t);
|
||||
}
|
||||
|
||||
if (shakeRotation && shakeStrengthRotation > 0f)
|
||||
{
|
||||
var t = shakeTransform.DOShakeRotation(
|
||||
duration,
|
||||
shakeStrengthRotation,
|
||||
shakeVibrato,
|
||||
shakeRandomness,
|
||||
true
|
||||
);
|
||||
seq.Join(t);
|
||||
}
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
void StartContinuousShake()
|
||||
{
|
||||
if (shakeTransform == null)
|
||||
return;
|
||||
|
||||
var duration = Mathf.Max(0.5f, moveDurationSeconds + defaultHoldSeconds);
|
||||
if (shakePosition && shakeStrengthPosition > 0f)
|
||||
{
|
||||
shakePosTween = shakeTransform.DOShakePosition(
|
||||
duration,
|
||||
shakeStrengthPosition,
|
||||
shakeVibrato,
|
||||
shakeRandomness,
|
||||
false,
|
||||
true
|
||||
)
|
||||
.SetUpdate(useUnscaledTime)
|
||||
.SetLoops(-1, LoopType.Restart);
|
||||
}
|
||||
|
||||
if (shakeRotation && shakeStrengthRotation > 0f)
|
||||
{
|
||||
shakeRotTween = shakeTransform.DOShakeRotation(
|
||||
duration,
|
||||
shakeStrengthRotation,
|
||||
shakeVibrato,
|
||||
shakeRandomness,
|
||||
true
|
||||
)
|
||||
.SetUpdate(useUnscaledTime)
|
||||
.SetLoops(-1, LoopType.Restart);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c6d753c21b4298e47980bc9cbb200e38
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0284fbf29b19c2a44865a3082d62d427
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,209 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using Test_2022.CameraControl;
|
||||
|
||||
[CustomEditor(typeof(CameraWaypointSwitcher))]
|
||||
[CanEditMultipleObjects]
|
||||
public sealed class CameraWaypointSwitcherEditor : Editor
|
||||
{
|
||||
SerializedProperty targetProp;
|
||||
SerializedProperty waypointsProp;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
targetProp = serializedObject.FindProperty("target");
|
||||
waypointsProp = serializedObject.FindProperty("waypoints");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
DrawDefaultInspector();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
EditorGUILayout.Space(10);
|
||||
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
|
||||
{
|
||||
EditorGUILayout.LabelField("Tools", EditorStyles.boldLabel);
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("Use Main Camera"))
|
||||
UseMainCamera();
|
||||
|
||||
if (GUILayout.Button("Create Point From Target"))
|
||||
CreatePointFromTarget();
|
||||
}
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("Add Selected"))
|
||||
AddSelected(false);
|
||||
|
||||
if (GUILayout.Button("Add Selected (+Children)"))
|
||||
AddSelected(true);
|
||||
}
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("Remove Nulls"))
|
||||
RemoveNulls();
|
||||
|
||||
if (GUILayout.Button("Clear Points"))
|
||||
ClearPoints();
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(10);
|
||||
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
|
||||
{
|
||||
EditorGUILayout.LabelField("Runtime", EditorStyles.boldLabel);
|
||||
|
||||
if (!Application.isPlaying)
|
||||
EditorGUILayout.HelpBox("进入 Play 模式后才会执行点位切换与摇晃。Transition Mode = Instant 为瞬切,Tween 为平滑移动。Shake Mode = Handheld 为手持式平滑摇晃。", MessageType.Info);
|
||||
|
||||
using (new EditorGUI.DisabledScope(!Application.isPlaying))
|
||||
{
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("Start"))
|
||||
StartPlayback();
|
||||
|
||||
if (GUILayout.Button("Stop (Restore)"))
|
||||
StopPlayback(true);
|
||||
|
||||
if (GUILayout.Button("Stop (No Restore)"))
|
||||
StopPlayback(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UseMainCamera()
|
||||
{
|
||||
var cam = Camera.main;
|
||||
if (cam == null)
|
||||
return;
|
||||
|
||||
foreach (var t in targets)
|
||||
{
|
||||
Undo.RecordObject(t, "Assign Main Camera");
|
||||
var sw = (CameraWaypointSwitcher)t;
|
||||
var so = new SerializedObject(sw);
|
||||
so.FindProperty("target").objectReferenceValue = cam.transform;
|
||||
so.ApplyModifiedPropertiesWithoutUndo();
|
||||
EditorUtility.SetDirty(sw);
|
||||
MarkSceneDirty(sw);
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
}
|
||||
|
||||
void CreatePointFromTarget()
|
||||
{
|
||||
if (targetProp == null)
|
||||
return;
|
||||
|
||||
var target = targetProp.objectReferenceValue as Transform;
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
var go = new GameObject("CamPoint");
|
||||
Undo.RegisterCreatedObjectUndo(go, "Create Camera Point");
|
||||
go.transform.SetPositionAndRotation(target.position, target.rotation);
|
||||
|
||||
AddPointToAll(go.transform, -1f);
|
||||
Selection.activeGameObject = go;
|
||||
}
|
||||
|
||||
void AddSelected(bool includeChildren)
|
||||
{
|
||||
var selected = Selection.gameObjects;
|
||||
if (selected == null || selected.Length == 0)
|
||||
return;
|
||||
|
||||
var points = new List<Transform>();
|
||||
for (var i = 0; i < selected.Length; i++)
|
||||
{
|
||||
var go = selected[i];
|
||||
if (go == null)
|
||||
continue;
|
||||
|
||||
if (includeChildren)
|
||||
points.AddRange(go.GetComponentsInChildren<Transform>(true));
|
||||
else
|
||||
points.Add(go.transform);
|
||||
}
|
||||
|
||||
for (var i = 0; i < points.Count; i++)
|
||||
AddPointToAll(points[i], -1f);
|
||||
}
|
||||
|
||||
void AddPointToAll(Transform point, float holdSeconds)
|
||||
{
|
||||
if (point == null)
|
||||
return;
|
||||
|
||||
foreach (var t in targets)
|
||||
{
|
||||
var sw = (CameraWaypointSwitcher)t;
|
||||
Undo.RecordObject(sw, "Add Waypoint");
|
||||
sw.AddWaypoint(point, holdSeconds);
|
||||
EditorUtility.SetDirty(sw);
|
||||
MarkSceneDirty(sw);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveNulls()
|
||||
{
|
||||
foreach (var t in targets)
|
||||
{
|
||||
var sw = (CameraWaypointSwitcher)t;
|
||||
Undo.RecordObject(sw, "Remove Null Waypoints");
|
||||
sw.RemoveNullWaypoints();
|
||||
EditorUtility.SetDirty(sw);
|
||||
MarkSceneDirty(sw);
|
||||
}
|
||||
}
|
||||
|
||||
void ClearPoints()
|
||||
{
|
||||
foreach (var t in targets)
|
||||
{
|
||||
var sw = (CameraWaypointSwitcher)t;
|
||||
Undo.RecordObject(sw, "Clear Waypoints");
|
||||
sw.ClearWaypoints();
|
||||
EditorUtility.SetDirty(sw);
|
||||
MarkSceneDirty(sw);
|
||||
}
|
||||
}
|
||||
|
||||
void StartPlayback()
|
||||
{
|
||||
foreach (var t in targets)
|
||||
{
|
||||
var sw = (CameraWaypointSwitcher)t;
|
||||
sw.StartPlayback();
|
||||
}
|
||||
}
|
||||
|
||||
void StopPlayback(bool restore)
|
||||
{
|
||||
foreach (var t in targets)
|
||||
{
|
||||
var sw = (CameraWaypointSwitcher)t;
|
||||
sw.StopPlayback(restore);
|
||||
}
|
||||
}
|
||||
|
||||
static void MarkSceneDirty(CameraWaypointSwitcher sw)
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
return;
|
||||
if (!sw.gameObject.scene.IsValid())
|
||||
return;
|
||||
EditorSceneManager.MarkSceneDirty(sw.gameObject.scene);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 523a055464a634349bc2aad4daf8faf1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user