diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs index a6592068d..1655ca5af 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs @@ -291,6 +291,7 @@ internal class IconLookup : IIconLookup public Texture2D Lookup(T mb, IconSize size = IconSize.Large, IconColor color = IconColor.Gray) where T : class { switch (mb) { + case BallShotComponent _: return Icons.BallRoller(size, color); case BallComponent _: return Icons.Ball(size, color); case BallRollerComponent _: return Icons.BallRoller(size, color); case BumperComponent _: return Icons.Bumper(size, color); @@ -330,6 +331,7 @@ public Texture2D Lookup(T mb, IconSize size = IconSize.Large, IconColor color public void DisableGizmoIcons() { + Icons.DisableGizmo(); Icons.DisableGizmo(); Icons.DisableGizmo(); Icons.DisableGizmo(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Common/Handles2.cs b/VisualPinball.Unity/VisualPinball.Unity/Common/Handles2.cs new file mode 100644 index 000000000..f4650ad30 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Common/Handles2.cs @@ -0,0 +1,40 @@ +// Visual Pinball Engine +// Copyright (C) 2023 freezy and VPE Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#if UNITY_EDITOR + +using UnityEditor; +using UnityEngine; + +namespace VisualPinball.Unity +{ + public static class Handles2 + { + public static void DrawArrow(Vector3 from, Vector3 to, float width = 1f, float arrowHeadLength = 0.025f, float arrowHeadAngle = 20.0f, bool bothSides = false) + { + var direction = to - from; + var right = Quaternion.LookRotation(direction) * Quaternion.Euler(0, 180 + arrowHeadAngle, 0) * new Vector3(0, 0, 1); + var left = Quaternion.LookRotation(direction) * Quaternion.Euler(0, 180 - arrowHeadAngle, 0) * new Vector3(0, 0, 1); + Handles.DrawAAPolyLine(width, from, to); + Handles.DrawAAPolyLine(width, to + right * arrowHeadLength, to, to + left * arrowHeadLength); + if (bothSides) { + Handles.DrawAAPolyLine(width, from - right * arrowHeadLength, from, from - left * arrowHeadLength); + } + } + } +} + +#endif diff --git a/VisualPinball.Unity/VisualPinball.Unity/Common/Handles2.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Common/Handles2.cs.meta new file mode 100644 index 000000000..eedbc83ea --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Common/Handles2.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c4984fa3344e4254a63ce13e77799178 +timeCreated: 1745010982 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/BallDebugComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/BallDebugComponent.cs new file mode 100644 index 000000000..f0ae24471 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/BallDebugComponent.cs @@ -0,0 +1,56 @@ +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.InputSystem; + +namespace VisualPinball.Unity +{ + public abstract class BallDebugComponent : MonoBehaviour + { + protected PhysicsEngine _physicsEngine; + protected PlayfieldComponent _playfield; + protected Player _player; + protected Matrix4x4 _ltw; + protected Matrix4x4 _wtl; + + protected Plane _playfieldPlane; + + protected int _ballId = 0; + + private void Awake() + { + _physicsEngine = GetComponentInParent(); + _playfield = GetComponentInParent(); + _player = GetComponentInParent(); + + _ltw = Physics.VpxToWorld; + _wtl = Physics.WorldToVpx; + + var p1 = _ltw.MultiplyPoint(new Vector3(-100f, 100f, 0)); + var p2 = _ltw.MultiplyPoint(new Vector3(100f, 100f, 0)); + var p3 = _ltw.MultiplyPoint(new Vector3(100f, -100f, 0)); + _playfieldPlane.Set3Points(p1, p2, p3); + } + + + protected bool GetCursorPositionOnPlayfield(out float3 vpxPos, out float3 worldPos) + { + vpxPos = float3.zero; + worldPos = float3.zero; + if (!Camera.main) { + return false; + } + + var mouseOnScreenPos = Mouse.current.position.ReadValue(); + var ray = Camera.main.ScreenPointToRay(mouseOnScreenPos); + + if (_playfieldPlane.Raycast(ray, out var enter)) { + worldPos = _playfield.transform.localToWorldMatrix.inverse.MultiplyPoint(ray.GetPoint(enter)); + vpxPos = _wtl.MultiplyPoint(worldPos); + + // todo check playfield bounds + return true; + } + return false; + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/BallDebugComponent.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/BallDebugComponent.cs.meta new file mode 100644 index 000000000..fca50ad78 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/BallDebugComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: dfe9af57cee842449d63397ffed037bc +timeCreated: 1745065405 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/BallRollerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/BallRollerComponent.cs index 2a0066186..d8e2b99c5 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/BallRollerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/BallRollerComponent.cs @@ -21,7 +21,7 @@ namespace VisualPinball.Unity { [PackAs("BallRoller")] - public class BallRollerComponent : MonoBehaviour, IPackable + public class BallRollerComponent : BallDebugComponent, IPackable { #region Packaging @@ -35,68 +35,26 @@ public void UnpackReferences(byte[] bytes, Transform root, PackagedRefs refs, Pa #endregion - private PhysicsEngine _physicsEngine; - private PlayfieldComponent _playfield; - private Matrix4x4 _ltw; - private Matrix4x4 _wtl; - - private Plane _playfieldPlane; - - private int _ballId = 0; - - private void Awake() - { - _playfield = GetComponentInChildren(); - - _ltw = Physics.VpxToWorld; - _wtl = Physics.WorldToVpx; - - var p1 = _ltw.MultiplyPoint(new Vector3(-100f, 100f, 0)); - var p2 = _ltw.MultiplyPoint(new Vector3(100f, 100f, 0)); - var p3 = _ltw.MultiplyPoint(new Vector3(100f, -100f, 0)); - _playfieldPlane.Set3Points(p1, p2, p3); - _physicsEngine = GetComponentInChildren(); - } - private void Update() { - if (Camera.main == null || _playfield == null) { + if (!Camera.main || !_playfield || !_player) { return; } // find nearest ball if (Mouse.current.middleButton.wasPressedThisFrame) { - if (GetCursorPositionOnPlayfield(out var mousePosition)) { - var nearestDistance = float.PositiveInfinity; - BallState nearestBall = default; - var ballFound = false; - - using (var enumerator = _physicsEngine.Balls.GetEnumerator()) { - while (enumerator.MoveNext()) { - var ball = enumerator.Current.Value; - - if (ball.IsFrozen) { - continue; - } - var distance = math.distance(mousePosition, ball.Position.xy); - if (distance < nearestDistance) { - nearestDistance = distance; - nearestBall = ball; - ballFound = true; - _ballId = ball.Id; - } - } - } + if (GetCursorPositionOnPlayfield(out var mousePosition, out var _)) { - if (ballFound) { - UpdateBall(ref nearestBall, mousePosition); + if (_player.BallManager.FindNearest(mousePosition.xy, out var nearestBall)) { + _ballId = nearestBall.Id; + UpdateBall(ref nearestBall, mousePosition.xy); } } } else if (Mouse.current.middleButton.isPressed && _ballId != 0) { - if (GetCursorPositionOnPlayfield(out var mousePosition)) { + if (GetCursorPositionOnPlayfield(out var mousePosition, out var _)) { ref var ball = ref _physicsEngine.BallState(_ballId); - UpdateBall(ref ball, mousePosition); + UpdateBall(ref ball, mousePosition.xy); } } @@ -112,28 +70,5 @@ private void UpdateBall(ref BallState ballState, float2 position) ballState.ManualControl = true; ballState.ManualPosition = position; } - - private bool GetCursorPositionOnPlayfield(out float2 position) - { - if (Camera.main == null) { - position = float2.zero; - return false; - } - - var mouseOnScreenPos = Mouse.current.position.ReadValue(); - var ray = Camera.main.ScreenPointToRay(mouseOnScreenPos); - - if (_playfieldPlane.Raycast(ray, out var enter)) { - var playfieldPosWorld = _playfield.transform.localToWorldMatrix.inverse.MultiplyPoint(ray.GetPoint(enter)); - var playfieldPosLocal = _wtl.MultiplyPoint(playfieldPosWorld); - - position = new float2(playfieldPosLocal.x, playfieldPosLocal.y); - - // todo check playfield bounds - return true; - } - position = float2.zero; - return false; - } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/BallShotComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/BallShotComponent.cs new file mode 100644 index 000000000..103a6c965 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/BallShotComponent.cs @@ -0,0 +1,184 @@ +// Visual Pinball Engine +// Copyright (C) 2023 freezy and VPE Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.InputSystem; +using VisualPinball.Engine.Common; + +namespace VisualPinball.Unity +{ + public class BallShotComponent : BallDebugComponent + { + bool _activated = false; //!< DebugShot mode activation flag + bool _mouseDown; //!< mouse button down flag + + public float ForceMultiplier = 0.2F; + + float m_elasped = 0f; //!< elapsed time since last click + + private const float GizmoHeight = 0.025f; + private const string NameParent = "Current"; + private const string NameStart = "StartGizmo"; + private const string NameEnd = "EndGizmo"; + private const string NameDirection = "DirectionGizmo"; + + private void Start() + { + CreateShotGizmos(NameParent); + + //for (int i = 1; i <= 12; i++) + //{ + // CreateShot("F" + i); + //} + + SetVisible(false); + } + + private void SetVisible(bool b, GameObject o = null) + { + if (o == null) { + o = gameObject; + } + + if (o.GetComponent() != null) { + o.GetComponent().enabled = b; + } + + for (var i = 0; i < o.transform.childCount; i++) { + SetVisible(b, o.transform.GetChild(i).gameObject); + } + } + + private void CreateShotGizmos(string parent) + { + // If already loaded, to not modify + if (transform.Find(parent) != null) { + return; + } + + // Father object + var plane = GameObject.CreatePrimitive(PrimitiveType.Plane); + Destroy(plane.GetComponent()); + Destroy(plane.GetComponent()); + plane.name = parent; + plane.transform.SetParent(transform, false); + + // Shot start + var point1 = GameObject.CreatePrimitive(PrimitiveType.Sphere); + Destroy(point1.GetComponent()); + point1.name = NameStart; + point1.transform.parent = plane.transform; + point1.transform.localScale = Vector3.one * 0.027f; // ball size = 27mm + + // Shot direction end + var point2 = GameObject.CreatePrimitive(PrimitiveType.Sphere); + Destroy(point2.GetComponent()); + point2.name = NameEnd; + point2.transform.parent = plane.transform; + point2.transform.localScale = Vector3.one * 0.027f; + + var line = GameObject.CreatePrimitive(PrimitiveType.Cylinder); + Destroy(line.GetComponent()); + line.name = NameDirection; + line.transform.parent = plane.transform; + line.transform.localScale = new Vector3(0.005f, 1f, 0.005f); + } + + private void SetShotStart(string parent, float3 p) + { + transform.Find($"{parent}/{NameStart}").localPosition = new float3(p.x, GizmoHeight, p.z); // Sets the fathers position (start is the reference) + } + + private void SetShotEnd(string parent, float3 p) + { + p = new float3(p.x, GizmoHeight, p.z); + transform.Find($"{parent}/{NameEnd}").localPosition = p; // Sets the direction's end position + + // Set the shooting line + var ps = transform.Find($"{parent}/{NameStart}").localPosition; + var pe = (Vector3)p; + var dir = transform.Find($"{parent}/{NameDirection}"); + var length = (pe - ps).magnitude; + dir.localPosition = (pe + ps) * 0.5f; + dir.localScale = new Vector3(0.005f, length * 0.5f, 0.005f);// *Globals.g_Scale; + dir.LookAt(_playfield.transform.localToWorldMatrix.MultiplyPoint(pe)); + dir.Rotate(90.0f, 0.0f, 0.0f); + } + + private void Update() + { + if (!Camera.main || !_playfield || !_player) { + return; + } + + if (Mouse.current.middleButton.wasPressedThisFrame || _mouseDown) { + if (!_mouseDown) { // just clicked (set start) + m_elasped = 0f; + if (GetCursorPositionOnPlayfield(out var vpxPos, out var worldPos)) { + SetShotStart(NameParent, worldPos); + } + + if (!_activated) { + SetVisible(true, transform.Find(NameParent).gameObject); + } + + } else { + m_elasped += Time.deltaTime; + if (GetCursorPositionOnPlayfield(out var pos, out var worldPos)) { + SetShotEnd(NameParent, worldPos); + // SetShotForce (NameParent,50f);//1f/m_elasped); + } + } + _mouseDown = true; + } + + if (Mouse.current.middleButton.wasReleasedThisFrame) { + if (_mouseDown) { // just released + LaunchShot(NameParent); + if (!_activated) { + SetVisible(false, transform.Find(NameParent).gameObject); + } + } + _mouseDown = false; + } + + if (Keyboard.current.spaceKey.wasPressedThisFrame) { + LaunchShot(NameParent); + } + } + + private void LaunchShot(string n) + { + var ps = _wtl.MultiplyPoint(transform.Find($"{n}/{NameStart}").localPosition); + var pe = _wtl.MultiplyPoint(transform.Find($"{n}/{NameEnd}").localPosition); + + var dir = pe - ps; + var mag = dir.magnitude; // To reuse magnitude for force + + var angle = mag > Mathf.Epsilon + ? Vector3.SignedAngle(Vector3.up, dir/mag, Vector3.forward) + 180F + : 0F; + + if (!Keyboard.current.leftCtrlKey.isPressed) { + if (_player.BallManager.FindNearest(new float2(ps.x, ps.y), out var nearestBall)) { + _player.BallManager.DestroyBall(nearestBall.Id); + } + } + _player.BallManager.CreateBall(new DebugBallCreator(ps.x, ps.y, PhysicsConstants.PhysSkin, angle, mag * ForceMultiplier)); + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/BallShotComponent.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/BallShotComponent.cs.meta new file mode 100644 index 000000000..b8ce274f7 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/BallShotComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: edcb711c769cb9046b46430fa4447c1f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs index ab689024e..4f80fa1d9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs @@ -86,6 +86,15 @@ public static class Physics public static float4x4 GetLocalToPlayfieldMatrixInVpx(this float4x4 localToWorld, float4x4 worldToPlayfield) => math.mul(math.mul(WorldToVpx, math.mul(worldToPlayfield, localToWorld)), VpxToWorld); + public static float4x4 GetLocalToPlayfieldMatrixInVpx(this Transform itemTransform) + { + var playfieldComp = itemTransform.GetComponentInParent(); + var playfieldToWorld = playfieldComp ? (float4x4)playfieldComp.transform.localToWorldMatrix : float4x4.identity; + var worldToPlayfield = playfieldComp ? (float4x4)playfieldComp.transform.worldToLocalMatrix : float4x4.identity; + var localToPlayfieldMatrixInVpx = GetLocalToPlayfieldMatrixInVpx(itemTransform.localToWorldMatrix, worldToPlayfield); + return math.mul(math.mul(playfieldToWorld, VpxToWorld), localToPlayfieldMatrixInVpx); + } + #endregion #region Translation diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallManager.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallManager.cs index 9d25dd573..e12a128a3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallManager.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallManager.cs @@ -15,6 +15,8 @@ // along with this program. If not, see . using System; +using Unity.Collections; +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.Game; using Object = UnityEngine.Object; @@ -73,5 +75,28 @@ public void DestroyBall(int ballId) // destroy game object Object.DestroyImmediate(ballTransform.gameObject); } + + public bool FindNearest(float2 fromPosition, out BallState nearestBall) + { + var nearestDistance = float.PositiveInfinity; + nearestBall = default; + var ballFound = false; + + using var enumerator = _physicsEngine.Balls.GetEnumerator(); + while (enumerator.MoveNext()) { + var ball = enumerator.Current.Value; + + if (ball.IsFrozen) { + continue; + } + var distance = math.distance(fromPosition, ball.Position.xy); + if (distance < nearestDistance) { + nearestDistance = distance; + nearestBall = ball; + ballFound = true; + } + } + return ballFound; + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/SwitchAnimationComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/SwitchAnimationComponent.cs new file mode 100644 index 000000000..df769c3a1 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/SwitchAnimationComponent.cs @@ -0,0 +1,222 @@ +// Visual Pinball Engine +// Copyright (C) 2023 freezy and VPE Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// ReSharper disable InconsistentNaming + +#if UNITY_EDITOR +using UnityEditor; +#endif +using System.Collections; +using NLog; +using Unity.Mathematics; +using UnityEngine; +using VisualPinball.Engine.VPT.Trigger; +using Logger = NLog.Logger; + + +namespace VisualPinball.Unity +{ + //[PackAs("SwitchAnimation")] + [AddComponentMenu("Pinball/Animation/Switch Animation")] + public class SwitchAnimationComponent : AnimationComponent//, IPackable + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + #region Data + + public float StartAngle { + get => transform.localEulerAngles.x > 180 ? transform.localEulerAngles.x - 360 : transform.localEulerAngles.x; + set => transform.SetLocalXRotation(math.radians(value)); + } + + [Range(-180f, 180f)] + [Tooltip("Angle of the switch bracket in end position (closed)")] + public float EndAngle; + + public AnimationCurve ForwardsAnimationCurve = AnimationCurve.EaseInOut(0, 0, 1f, 1); + public AnimationCurve BackwardsAnimationCurve = AnimationCurve.EaseInOut(0, 0, 1f, 1); + public float BackwardsAnimationDurationSeconds = 0.3f; + + private float _startAngle; + private float _currentAngle; + private bool _ballInside; + private int _ballId; + private float _yEnter; + private float _yExit; + private Coroutine _animateBackwardsCoroutine; + + private TriggerComponent _triggerComp; + private PhysicsEngine _physicsEngine; + + #endregion + + private void Start() + { + _startAngle = StartAngle; + _currentAngle = _startAngle; + _triggerComp = GetComponentInParent(); + if (!_triggerComp) { + Logger.Warn($"{name}: No Trigger Component found in parent. Animation will not work."); + return; + } + _physicsEngine = _triggerComp.GetComponentInParent(); + if (!_physicsEngine) { + Logger.Warn($"{name}: No Physics Engine found in parent. Animation will not work."); + return; + } + + _yEnter = _triggerComp.DragPoints[0].Center.Y; + _yExit = _triggerComp.DragPoints[1].Center.Y; + + _triggerComp.TriggerApi.Hit += OnHit; + _triggerComp.TriggerApi.UnHit += UnHit; + } + + private void OnHit(object sender, HitEventArgs e) + { + if (_ballInside) { + // ignore other balls + return; + } + if (_animateBackwardsCoroutine != null) { + StopCoroutine(_animateBackwardsCoroutine); + _animateBackwardsCoroutine = null; + } + + _ballInside = true; + _ballId = e.BallId; + } + + private void Update() + { + if (!_ballInside) { + // nothing to animate + return; + } + + var ballTransform = _physicsEngine.GetTransform(_ballId); + var ballLocalToWorld = (float4x4)ballTransform.localToWorldMatrix; + var transformWithinParent = ballLocalToWorld.GetLocalToPlayfieldMatrixInVpx(_triggerComp.transform.worldToLocalMatrix); + var localVpxPos = transformWithinParent.MultiplyPoint(ballTransform.position); + + var yPos = ForwardsAnimationCurve.Evaluate(math.unlerp(_yEnter, _yExit, localVpxPos.y)); // yPos is between 0 and 1, depending on where localVpxPos.y is + _currentAngle = math.clamp(math.lerp(_startAngle, EndAngle, yPos), _startAngle, EndAngle); + + transform.SetLocalXRotation(math.radians(_currentAngle)); + } + + private void UnHit(object sender, HitEventArgs e) + { + if (e.BallId != _ballId) { + // ignore other balls + return; + } + _ballId = 0; + _ballInside = false; + + if (_animateBackwardsCoroutine != null) { + StopCoroutine(_animateBackwardsCoroutine); + } + _animateBackwardsCoroutine = StartCoroutine(AnimateBackwards()); + } + + private IEnumerator AnimateBackwards() + { + // rotate from _currentAngle to _startAngle + var from = _currentAngle; + var to = _startAngle; + var d = to - from; + + var t = 0f; + while (t < BackwardsAnimationDurationSeconds) { + var f = BackwardsAnimationCurve.Evaluate(t / BackwardsAnimationDurationSeconds); + _currentAngle = from + f * d; + transform.SetLocalXRotation(math.radians(_currentAngle)); + t += Time.deltaTime; + yield return null; // wait one frame + } + + // finally, snap to the curve's final value + transform.SetLocalXRotation(math.radians(to)); + _animateBackwardsCoroutine = null; + } + + private void OnDestroy() + { + if (_triggerComp) { + _triggerComp.TriggerApi.Hit -= OnHit; + _triggerComp.TriggerApi.UnHit -= UnHit; + } + } + + // #region Packaging + // + // public byte[] Pack() => TriggerAnimationPackable.Pack(this); + // + // public byte[] PackReferences(Transform root, PackagedRefs refs, PackagedFiles files) => null; + // + // public void Unpack(byte[] bytes) => TriggerAnimationPackable.Unpack(bytes, this); + // + // public void UnpackReferences(byte[] data, Transform root, PackagedRefs refs, PackagedFiles files) { } + // + // #endregion + +#if UNITY_EDITOR + + private void OnDrawGizmosSelected() + { + + var triggerComp = GetComponentInParent(); + var collComp = GetComponentInParent(); + if (!triggerComp || triggerComp.DragPoints is not { Length: 4 } || !collComp) { + return; + } + + var dp0 = triggerComp.DragPoints[0].Center.ToUnityVector3(); + var dp1 = triggerComp.DragPoints[1].Center.ToUnityVector3(); + var dp3 = triggerComp.DragPoints[3].Center.ToUnityVector3(); + + var dx = dp3.x - dp0.x; + var h = collComp.HitHeight; + + var entryRect = new[] { + dp0, + new Vector3(dp0.x, dp0.y, dp0.z + h), + new Vector3(dp0.x + dx, dp0.y, dp0.z + h), + new Vector3(dp0.x + dx, dp0.y, dp0.z), + dp0 + }; + + var exitRect = new[] { + dp1, + new Vector3(dp1.x, dp1.y, dp1.z + h), + new Vector3(dp1.x + dx, dp1.y, dp1.z + h), + new Vector3(dp1.x + dx, dp1.y, dp1.z), + dp1 + }; + + Handles.matrix = triggerComp.transform.GetLocalToPlayfieldMatrixInVpx(); + Handles.color = Color.gray; + + for (var i = 0; i < 4; i++) { + Handles2.DrawArrow(entryRect[i], exitRect[i], 3, 10); + } + Handles.DrawAAPolyLine(5, entryRect); + Handles.DrawAAPolyLine(5, exitRect); + } +#endif + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/SwitchAnimationComponent.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/SwitchAnimationComponent.cs.meta new file mode 100644 index 000000000..ac4cefb6a --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/SwitchAnimationComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8df7b558b1f640809ed31dc5e5ff9ca2 +timeCreated: 1744981170 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs index 2696d2e6a..d72e7e4ec 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs @@ -70,7 +70,7 @@ public float Rotation { } [SerializeField] - private DragPointData[] _dragPoints; + private DragPointData[] _dragPoints = { new(-50f, -50f), new(-50f, 50f), new(50f, 50f), new(50f, -50f) }; public DragPointData[] DragPoints { get => _dragPoints; set => _dragPoints = value; } #endregion