1
0
forked from cgvr/DeltaVR

deltavr multiplayer 2.0

This commit is contained in:
Toomas Tamm
2023-05-08 15:56:10 +03:00
parent 978809a002
commit 07b9b9e2f4
10937 changed files with 2968397 additions and 1521012 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cbdb43e6ec783e947b6ffce18f97dafa
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,85 @@
namespace UnityEngine.XR.Content.UI.Layout
{
/// <summary>
/// Makes the object this is attached to follow a target with a slight delay
/// </summary>
public class LazyFollow : MonoBehaviour
{
#pragma warning disable 649
[SerializeField]
[Tooltip("The object being followed.")]
Transform m_Target;
#pragma warning restore 649
[SerializeField]
[Tooltip("Whether to always follow or only when in-view.")]
bool m_FOV = false;
[SerializeField]
[Tooltip("Whether rotation is locked to the z-axis for can move in any direction.")]
bool m_ZRot = true;
[SerializeField]
[Tooltip("Adjusts the follow point from the target by this amount.")]
Vector3 m_TargetOffset = Vector3.forward;
Vector3 m_TargetLastPos;
Camera m_Camera;
public float smoothTime = 0.3F;
private Vector3 velocity = Vector3.zero;
Vector3 targetPosition => m_Target.position + m_Target.TransformVector(m_TargetOffset);
Quaternion targetRotation
{
get
{
if (!m_ZRot)
{
var eulerAngles = m_Target.eulerAngles;
eulerAngles = new Vector3(eulerAngles.x, eulerAngles.y, 0f);
return Quaternion.Euler(eulerAngles);
}
return m_Target.rotation;
}
}
void Awake()
{
if (m_Camera == null)
m_Camera = Camera.main;
// Default to main camera
if (m_Target == null)
if (m_Camera != null)
m_Target = m_Camera.transform;
transform.position = targetPosition;
transform.rotation = targetRotation;
}
void Start()
{
var targetPos = targetPosition;
m_TargetLastPos = targetPos;
}
void Update()
{
if (m_FOV)
{
Vector3 screenPoint = m_Camera.WorldToViewportPoint(this.gameObject.transform.position);
var inFov = screenPoint.z > 0f && screenPoint.x > 0f && screenPoint.x < 1f && screenPoint.y > 0f && screenPoint.y < 1f;
if (inFov)
return;
}
var targetPos = targetPosition;
if (m_TargetLastPos == targetPos)
return;
transform.position = Vector3.SmoothDamp(transform.position, targetPos, ref velocity, smoothTime);
m_TargetLastPos = targetPos;
}
}
}

View File

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

View File

@@ -0,0 +1,61 @@
namespace UnityEngine.XR.Content.UI.Layout
{
/// <summary>
/// Makes this object face a target smoothly and along specific axes
/// </summary>
public class TurnToFace : MonoBehaviour
{
#pragma warning disable 649
[SerializeField, Tooltip("Target to face towards. If not set, this will default to the main camera")]
Transform m_FaceTarget;
[SerializeField, Tooltip("Speed to turn")]
float m_TurnToFaceSpeed = 5f;
[SerializeField, Tooltip("Local rotation offset")]
Vector3 m_RotationOffset = Vector3.zero;
[SerializeField, Tooltip("If enabled, ignore the x axis when rotating")]
bool m_IgnoreX;
[SerializeField, Tooltip("If enabled, ignore the y axis when rotating")]
bool m_IgnoreY;
[SerializeField, Tooltip("If enabled, ignore the z axis when rotating")]
bool m_IgnoreZ;
#pragma warning restore 649
void Awake()
{
// Default to main camera
if (m_FaceTarget == null)
if (Camera.main != null)
m_FaceTarget = Camera.main.transform;
}
void Update()
{
if (m_FaceTarget != null)
{
var facePosition = m_FaceTarget.position;
var forward = facePosition - transform.position;
var targetRotation = forward.sqrMagnitude > float.Epsilon ? Quaternion.LookRotation(forward, Vector3.up) : Quaternion.identity;
targetRotation *= Quaternion.Euler(m_RotationOffset);
if (m_IgnoreX || m_IgnoreY || m_IgnoreZ)
{
var targetEuler = targetRotation.eulerAngles;
var currentEuler = transform.rotation.eulerAngles;
targetRotation = Quaternion.Euler
(
m_IgnoreX ? currentEuler.x : targetEuler.x,
m_IgnoreY ? currentEuler.y : targetEuler.y,
m_IgnoreZ ? currentEuler.z : targetEuler.z
);
}
var ease = 1f - Mathf.Exp(-m_TurnToFaceSpeed * Time.unscaledDeltaTime);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, ease);
}
}
}
}

View File

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

View File

@@ -0,0 +1,90 @@
namespace UnityEngine.XR.Content.UI.Layout
{
/// <summary>
/// Component that controls the scale of the GameObject based on the distance from the main camera.
/// The distance is divided into zones, and the transform scale is smoothly transitioned when the object moves into a different zone.
/// The size of the zones are relative to the viewer's scale.
/// </summary>
public class ZoneScale : MonoBehaviour
{
const float k_SmoothTime = 0.3f;
#pragma warning disable 649
[SerializeField]
bool m_Clamp;
[SerializeField]
float m_ClampMax = 10f;
[SerializeField]
float m_ClampMin = 1f;
[SerializeField]
float m_ZoneSize = 0.2f;
[SerializeField]
float m_DefaultScale = 1f;
#pragma warning restore 649
float m_YVelocity;
float m_LastScale = 1.0f;
bool m_Snap;
Transform m_MainCameraTransform;
int m_CurrentZone;
//IProvidesViewerScale IFunctionalitySubscriber<IProvidesViewerScale>.provider { get; set; }
/// <summary>
/// Skip the transition interpolation to the target scale immediately
/// </summary>
public void Snap()
{
m_Snap = true;
SetScaleForCurrentDistance();
}
void OnEnable()
{
var mainCamera = Camera.main;
if (mainCamera != null)
m_MainCameraTransform = mainCamera.transform;
Snap();
}
void LateUpdate()
{
SetScaleForCurrentDistance();
}
void SetScaleForCurrentDistance()
{
if (m_MainCameraTransform == null)
return;
var cameraPosition = m_MainCameraTransform.position;
var deltaToCamera = cameraPosition - transform.position;
var adjustedDistance = deltaToCamera.magnitude;
var scaledZoneSize = m_ZoneSize * Camera.main.transform.lossyScale.x;
var zone = Mathf.CeilToInt(adjustedDistance / scaledZoneSize);
var bufferSize = scaledZoneSize * 0.5f;
if (adjustedDistance > m_CurrentZone * scaledZoneSize + bufferSize ||
adjustedDistance < m_CurrentZone * scaledZoneSize - bufferSize)
{
m_CurrentZone = zone;
}
var targetScale = m_CurrentZone * scaledZoneSize;
var newScale = m_Snap ? targetScale : Mathf.SmoothDamp(m_LastScale, targetScale, ref m_YVelocity, k_SmoothTime);
if (m_Snap)
m_Snap = false;
if (m_Clamp)
newScale = Mathf.Clamp(newScale, m_ClampMin, m_ClampMax);
transform.localScale = Vector3.one * (newScale * m_DefaultScale);
m_LastScale = newScale;
}
}
}

View File

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

View File

@@ -0,0 +1,147 @@
using System;
namespace UnityEngine.XR.Content.Walkthrough
{
/// <summary>
/// Defines a walkthrough - a series of steps gated by triggers
/// </summary>
public class Walkthrough : MonoBehaviour
{
#pragma warning disable 649
[SerializeField]
[Tooltip("The name of this walkthrough - for reference by UI")]
string m_WalkthroughName;
[SerializeField]
[Tooltip("All of the steps this walkthrough requires, in order")]
WalkthroughStep[] m_Steps;
[SerializeField]
GameObject m_Waypoint;
[SerializeField]
GameObject m_WaypointLink;
[SerializeField]
bool m_LoopOnComplete = false;
#pragma warning restore 649
/// <summary>
/// The name of the walkthrough experience
/// </summary>
public string walkthroughName => m_WalkthroughName;
/// <summary>
/// All of the steps this walkthrough requires, in order
/// </summary>
public WalkthroughStep[] steps => m_Steps;
/// <summary>
/// The currently active step of the walkthrough
/// </summary>
public int currentStep { get; private set; } = 0;
/// <summary>
/// Event that is raised whenever the state of the walkthrough has changed.
/// </summary>
public Action walkthroughChangedCallback;
/// <summary>
/// Shifts to another step of the walkthrough
/// </summary>
/// <param name="stepIndex">The step to make active</param>
/// <param name="autoProgressIfComplete">If true, allows for skipping to the subsequent step if the current one is already complete.</param>
public void SkipToStep(int stepIndex, bool autoProgressIfComplete)
{
// Ignore invalid indices and no-ops
if (stepIndex < 0 || stepIndex >= m_Steps.Length || stepIndex == currentStep)
return;
// If any steps between our current step and the next are incomplete and block progression, we do not allow skipping to occur. This prevents
// problems like skipping to a step where relocalization has not yet occurred.
if (stepIndex > currentStep)
{
for (var testStepIndex = currentStep; testStepIndex < stepIndex; testStepIndex++)
{
var testStep = m_Steps[testStepIndex];
if (!testStep.canSkip)
{
Debug.LogWarning($"Can't skip past incomplete step {testStep.name}");
walkthroughChangedCallback?.Invoke();
return;
}
}
}
// If a valid step is already being displayed, set it back to inactive now
if (currentStep >= 0 && currentStep < m_Steps.Length)
m_Steps[currentStep].CancelStep();
currentStep = stepIndex;
m_Steps[currentStep].StartStep(OnStepComplete, autoProgressIfComplete);
walkthroughChangedCallback?.Invoke();
}
public void SkipToStep(int stepIndex)
{
SkipToStep(stepIndex, false);
}
void Awake()
{
int stepIndex = 1;
// We ensure each walkthrough step is ready to work (as we can't ensure components are waking in a determined order), then start the first step
foreach (var step in m_Steps)
{
step.Initialize();
var waypoint = Instantiate(m_Waypoint, step.gameObject.transform);
waypoint.transform.localPosition = Vector3.zero;
waypoint.transform.rotation = Quaternion.identity;
var waypointText = waypoint.GetComponentInChildren<TMPro.TMP_Text>();
waypointText.text = stepIndex.ToString();
step.waypoint = waypoint;
if (stepIndex > 1)
{
var link = Instantiate(m_WaypointLink, step.gameObject.transform);
link.transform.localPosition = Vector3.zero;
link.transform.rotation = Quaternion.identity;
var linkCurve = link.GetComponentInChildren<WaypointCurve>();
linkCurve.start = m_Steps[stepIndex - 2].gameObject.transform.position;
linkCurve.end = m_Steps[stepIndex - 1].gameObject.transform.position;
step.link = link;
}
stepIndex++;
}
if (m_Steps != null && m_Steps.Length > 0)
m_Steps[currentStep].StartStep(OnStepComplete);
walkthroughChangedCallback?.Invoke();
}
void OnStepComplete(bool autoProgress)
{
// We still call the changed callback even if we are not auto-progressing, as some UI may want to update labels or controls
if (!autoProgress)
{
walkthroughChangedCallback?.Invoke();
return;
}
// If we are auto-progressing, increment the step index and start the process again
currentStep++;
if (m_LoopOnComplete && currentStep >= m_Steps.Length)
currentStep = 0;
if (m_Steps == null || currentStep >= m_Steps.Length)
return;
m_Steps[currentStep].StartStep(OnStepComplete);
walkthroughChangedCallback?.Invoke();
}
}
}

View File

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

View File

@@ -0,0 +1,288 @@
using System;
using System.Collections.Generic;
using UnityEngine.Events;
using UnityEngine.XR.Interaction.Toolkit;
namespace UnityEngine.XR.Content.Walkthrough
{
/// <summary>
/// Contains information needed to process one step of a walkthrough.
/// </summary>
public class WalkthroughStep : MonoBehaviour
{
/// Local method use only -- created here to reduce garbage collection. Collections must be cleared before use
static readonly List<WalkthroughTrigger> s_TriggersToRemove = new List<WalkthroughTrigger>();
[SerializeField]
[Tooltip("Camera target to reposition user.")]
GameObject m_CameraTarget;
[SerializeField]
[Tooltip("The Teleportation Provider used to reposition the user. Usually a component on the XR Origin.")]
TeleportationProvider m_TeleportationProvider;
[SerializeField]
[Tooltip("Optional audio source for voiceover")]
AudioSource m_AudioSource;
[SerializeField]
[Tooltip("Objects to enable when this step is active.")]
List<GameObject> m_Visuals = new List<GameObject>();
#pragma warning disable 649
[SerializeField]
[Tooltip("Actions to call when the step starts.")]
UnityEvent m_OnStepBegin;
[SerializeField]
[Tooltip("Actions to call when the step completes.")]
UnityEvent m_OnStepComplete;
[SerializeField]
[Tooltip("The purpose of this step.")]
string m_Description;
#pragma warning restore 649
[SerializeField]
[Tooltip("If true, this step cannot be skipped until completed at least once.")]
bool m_BlockUntilComplete;
[SerializeField]
[Tooltip("If true, this step will automatically progress when complete - unless explicitly skipped to.")]
bool m_AutoProgressOnComplete = true;
bool m_Started;
bool m_AutoProgressEnabled = true;
bool m_StepInvoked;
Action<bool> m_OnComplete;
GameObject m_Waypoint;
GameObject m_Link;
List<WalkthroughTrigger> m_Triggers = new List<WalkthroughTrigger>();
List<WalkthroughTrigger> m_RemainingTriggers = new List<WalkthroughTrigger>();
/// <summary>
/// The purpose of this step. Appends a (Complete) if complete and normally has triggers.
/// </summary>
public string description => $"{m_Description}{(completed && m_Triggers.Count > 0 ? " (Complete)" : "") }";
public GameObject waypoint
{
get => m_Waypoint;
set => m_Waypoint = value;
}
public GameObject link
{
get => m_Link;
set => m_Link = value;
}
/// <summary>
/// Ensures the step visuals are hidden until active and that all triggers are accounted for
/// </summary>
public void Initialize()
{
if (!m_Started)
SetVisualsState(false);
GetComponents(m_Triggers);
}
/// <summary>
/// Returns true if this step does not currently have any triggers remaining to fire
/// </summary>
public bool canProgress => (!m_BlockUntilComplete || (m_RemainingTriggers.Count == 0));
/// <summary>
/// Returns true if this step does not block, or has been completed at least once.
/// </summary>
public bool canSkip => (!m_BlockUntilComplete || completed);
/// <summary>
/// True if this step's triggers have been activated at least once
/// </summary>
public bool completed { get; private set; }
/// <summary>
/// Makes this step and its triggers the active focus of a walkthrough
/// </summary>
/// <param name="onComplete">Callback to fire when this step's triggers are complete</param>
/// <param name="allowAutoProgress">If this step is allow to auto-progress during this activation</param>
public void StartStep(Action<bool> onComplete, bool allowAutoProgress = true)
{
if (m_Started)
return;
// Autoprogression is enabled only if the step AND walkthrough allow it
m_AutoProgressEnabled = allowAutoProgress && m_AutoProgressOnComplete;
SetVisualsState(true);
SetAudioSource(true);
if (m_Waypoint != null)
{
m_Waypoint.SetActive(false);
}
if (m_CameraTarget != null && m_TeleportationProvider != null)
{
SetCameraPosition();
}
m_OnComplete = onComplete;
m_Started = true;
if (m_Triggers.Count == 0 && m_AutoProgressEnabled)
{
CompleteStep();
return;
}
foreach (var currentTrigger in m_Triggers)
{
if (currentTrigger.ResetTrigger())
m_RemainingTriggers.Add(currentTrigger);
}
if (m_RemainingTriggers.Count == 0)
{
CompleteStep();
return;
}
if (m_RemainingTriggers.Count > 0)
m_AutoProgressEnabled = m_AutoProgressOnComplete;
}
/// <summary>
/// Ends this step being the focus of the current walkthrough
/// </summary>
public void CancelStep()
{
SetVisualsState(false);
SetAudioSource(false);
if (m_Waypoint != null)
{
m_Waypoint.SetActive(true);
}
if (!m_Started)
return;
m_OnComplete = null;
m_Started = false;
m_RemainingTriggers.Clear();
}
void CompleteStep()
{
if (!m_Started)
return;
completed = true;
m_OnComplete?.Invoke(m_AutoProgressEnabled);
m_OnComplete = null;
m_Started = false;
// We disable visuals if the the next step is being activated
if (m_AutoProgressEnabled)
{
SetVisualsState(false);
SetAudioSource(false);
if (m_Waypoint != null)
{
m_Waypoint.SetActive(true);
}
}
m_RemainingTriggers.Clear();
}
void Update()
{
// If this step is running, check remaining triggers. Any triggers that are now met get removed.
// If there are no triggers left, then the step is complete.
if (!m_Started)
return;
if (m_RemainingTriggers.Count == 0)
return;
s_TriggersToRemove.Clear();
foreach (var currentTrigger in m_RemainingTriggers)
{
if (currentTrigger.Check())
s_TriggersToRemove.Add(currentTrigger);
}
foreach (var toRemove in s_TriggersToRemove)
{
m_RemainingTriggers.Remove(toRemove);
}
s_TriggersToRemove.Clear();
if (m_RemainingTriggers.Count == 0)
{
CompleteStep();
return;
}
}
void SetVisualsState(bool enabled)
{
if (m_Visuals != null)
{
foreach (var currentVisual in m_Visuals)
{
if (currentVisual != null)
currentVisual.SetActive(enabled);
}
}
if (m_StepInvoked == enabled)
return;
m_StepInvoked = enabled;
if (enabled && m_OnStepBegin != null)
m_OnStepBegin.Invoke();
if (!enabled && m_OnStepComplete != null)
m_OnStepComplete.Invoke();
}
void SetAudioSource(bool enabled)
{
if (m_AudioSource != null)
{
if (enabled)
{
m_AudioSource.Play();
}
else
{
m_AudioSource.Stop();
}
}
}
void SetCameraPosition()
{
TeleportRequest request = new TeleportRequest()
{
requestTime = Time.time,
matchOrientation = MatchOrientation.TargetUpAndForward,
destinationPosition = m_CameraTarget.transform.position,
destinationRotation = m_CameraTarget.transform.rotation
};
m_TeleportationProvider.QueueTeleportRequest(request);
}
}
}

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cfb30d95353445d468ef3170b5dc0812
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,67 @@
using System;
using UnityEngine.UI;
namespace UnityEngine.XR.Content.Walkthrough
{
/// <summary>
/// Trigger that, when active, waits for a UI button to be pressed
/// </summary>
internal class ButtonPressTrigger : WalkthroughTrigger
{
#pragma warning disable 649
[SerializeField]
[Tooltip("The UI button that when pressed, will allow this trigger to pass.")]
Button m_ButtonToPress;
[SerializeField]
[Tooltip("Allow pressing of the button switch the step of the tutorial")]
bool m_SwitchContext = true;
#pragma warning restore 649
bool m_Triggered = false;
void Start()
{
if (m_ButtonToPress == null)
return;
m_ButtonToPress.onClick.RemoveListener(ButtonPressHandler);
m_ButtonToPress.onClick.AddListener(ButtonPressHandler);
}
public override bool ResetTrigger()
{
m_Triggered = false;
if (m_ButtonToPress == null)
return false;
m_ButtonToPress.onClick.RemoveListener(ButtonPressHandler);
m_ButtonToPress.onClick.AddListener(ButtonPressHandler);
return true;
}
public override bool Check()
{
return m_Triggered;
}
void ButtonPressHandler()
{
// Attempt to switch to this step if this button is not part of the current step
if (m_SwitchContext)
{
var parent = GetComponentInParent<WalkthroughStep>();
var walkthrough = GetComponentInParent<Walkthrough>();
if (parent != null && walkthrough != null)
{
var steps = walkthrough.steps;
var stepIndex = Array.IndexOf(steps, parent);
if (stepIndex != walkthrough.currentStep)
walkthrough.SkipToStep(stepIndex);
}
}
m_Triggered = true;
}
}
}

View File

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

View File

@@ -0,0 +1,20 @@
namespace UnityEngine.XR.Content.Walkthrough
{
/// <summary>
/// Base class for all triggers used by walkthrough steps.
/// </summary>
abstract public class WalkthroughTrigger : MonoBehaviour
{
/// <summary>
/// Attempts to return a trigger to a state where it can be activated again
/// </summary>
/// <returns>False if this trigger cannot be reset or would automatically fire</returns>
public abstract bool ResetTrigger();
/// <summary>
/// Checks if this trigger's pass/fail condition is active
/// </summary>
/// <returns>True if this trigger is no longer blocking the current walkthrough step</returns>
public abstract bool Check();
}
}

View File

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

View File

@@ -0,0 +1,136 @@
namespace UnityEngine.XR.Content.Walkthrough
{
/// <summary>
/// Manipulates a line renderer to draw a curved path between two locations
/// </summary>
[RequireComponent(typeof(LineRenderer))]
public class WaypointCurve : MonoBehaviour
{
#pragma warning disable 649
[SerializeField]
[Tooltip("Where the curved path should begin.")]
Transform m_StartPoint;
[SerializeField]
[Tooltip("Where the curved path should complete.")]
Transform m_EndPoint;
[SerializeField]
[Tooltip("Bends the first control point of the path.")]
float m_CurveFactorStart = 1.0f;
[SerializeField]
[Tooltip("Bends the last control point of the path.")]
float m_CurveFactorEnd = 1.0f;
[SerializeField]
[Tooltip("Enable to make the path animate colors.")]
bool m_Animate = false;
[SerializeField]
[Tooltip("How quickly to animate the path.")]
float m_AnimSpeed = 0.25f;
#pragma warning restore 649
Vector3[] m_ControlPoints = new Vector3[4];
LineRenderer m_LineRenderer;
int m_CurveCount = 0;
int m_LayerOrder = 0;
int m_SegmentCount = 50;
float m_Time = 0.0f;
public Vector3 start
{
get => m_StartPoint.position;
set => m_StartPoint.position = value;
}
public Vector3 end
{
get => m_EndPoint.position;
set => m_EndPoint.position = value;
}
void Start()
{
if (!m_LineRenderer)
{
m_LineRenderer = GetComponent<LineRenderer>();
}
m_LineRenderer.sortingLayerID = m_LayerOrder;
m_CurveCount = (int)4 / 3;
}
void Update()
{
DrawCurve();
if (m_Animate) { AnimateCurve(); }
}
void DrawCurve()
{
var dist = Mathf.Clamp(Vector3.Distance(m_StartPoint.position, m_EndPoint.position), 0f, 1f);
m_ControlPoints[0] = m_StartPoint.position;
m_ControlPoints[1] = m_StartPoint.position + m_StartPoint.right * (dist * m_CurveFactorStart);
m_ControlPoints[2] = m_EndPoint.position - m_EndPoint.right * (dist * m_CurveFactorEnd);
m_ControlPoints[3] = m_EndPoint.position;
for (int j = 0; j < m_CurveCount; j++)
{
for (int i = 1; i <= m_SegmentCount; i++)
{
float t = i / (float)m_SegmentCount;
int nodeIndex = j * 3;
Vector3 pixel = CalculateCubicBezierPoint(t, m_ControlPoints[nodeIndex], m_ControlPoints[nodeIndex + 1], m_ControlPoints[nodeIndex + 2], m_ControlPoints[nodeIndex + 3]);
m_LineRenderer.positionCount = (((j * m_SegmentCount) + i));
m_LineRenderer.SetPosition((j * m_SegmentCount) + (i - 1), pixel);
}
}
}
Vector3 CalculateCubicBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
{
float u = 1 - t;
float tt = t * t;
float uu = u * u;
float uuu = uu * u;
float ttt = tt * t;
Vector3 p = uuu * p0;
p += 3 * uu * t * p1;
p += 3 * u * tt * p2;
p += ttt * p3;
return p;
}
void AnimateCurve()
{
Gradient newGrad = new Gradient();
GradientColorKey[] colorKeys = new GradientColorKey[1];
GradientAlphaKey[] alphaKeys = new GradientAlphaKey[2];
GradientColorKey colorKey = new GradientColorKey(new Color(0.1254902f, 0.5882353f, 0.9529412f), 0f);
colorKeys[0] = colorKey;
GradientAlphaKey alphaKeyStart1 = new GradientAlphaKey(.25f, m_Time);
GradientAlphaKey alphaKeyStart = new GradientAlphaKey(.25f, m_Time);
GradientAlphaKey alphaKeyEnd = new GradientAlphaKey(1f, 1f);
alphaKeys[0] = alphaKeyStart;
alphaKeys[1] = alphaKeyEnd;
newGrad.SetKeys(colorKeys, alphaKeys);
newGrad.mode = GradientMode.Blend;
m_LineRenderer.colorGradient = newGrad;
m_Time += (Time.deltaTime * m_AnimSpeed);
if (m_Time >= 1f)
{
m_Time = 0f;
}
}
}
}

View File

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