From c77c2bf15cab9a2f735754310799664dfc3756c2 Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 28 Sep 2021 23:59:29 +0200 Subject: [PATCH 01/15] surface: Fix defaults. --- .../VPT/Surface/SurfaceData.cs | 4 +- .../Assets/Resources/Prefabs/Surface.prefab | 66 ++++--------------- .../Physics/Collider/LineSlingshotCollider.cs | 4 +- 3 files changed, 16 insertions(+), 58 deletions(-) diff --git a/VisualPinball.Engine/VPT/Surface/SurfaceData.cs b/VisualPinball.Engine/VPT/Surface/SurfaceData.cs index 0da64cdee..d584ae742 100644 --- a/VisualPinball.Engine/VPT/Surface/SurfaceData.cs +++ b/VisualPinball.Engine/VPT/Surface/SurfaceData.cs @@ -97,13 +97,13 @@ public class SurfaceData : ItemData, ISurfaceData public bool SlingshotAnimation = true; [BiffFloat("ELAS", Pos = 20)] - public float Elasticity; + public float Elasticity = 0.3f; [BiffFloat("ELFO", Pos = 21, WasAddedInVp107 = true)] public float ElasticityFalloff; [BiffFloat("WFCT", Pos = 22)] - public float Friction; + public float Friction = 0.3f; [BiffFloat("WSCT", Pos = 23)] public float Scatter; diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Surface.prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Surface.prefab index 2caa6b145..c38bb28aa 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/Surface.prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Surface.prefab @@ -44,7 +44,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: ed6ad111d870e3b43a828b6086c08607, type: 3} m_Name: m_EditorClassIdentifier: - _meshCreated: 1 --- !u!33 &6530115296024031604 MeshFilter: m_ObjectHideFlags: 0 @@ -100,54 +99,13 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: b8b3e83ae5f0a1e41a2dfdfaa251caf7, type: 3} m_Name: m_EditorClassIdentifier: - _data: - StoragePrefix: 0 - StorageIndex: 0 - IsLocked: 0 - EditorLayerName: - EditorLayerVisibility: 0 - Name: - HitEvent: 0 - IsDroppable: 0 - IsFlipbook: 0 - IsBottomSolid: 0 - IsCollidable: 0 - Threshold: 0 - Image: - SideImage: - SideMaterial: - TopMaterial: - PhysicsMaterial: - SlingShotMaterial: - HeightBottom: 0 - HeightTop: 0 - Inner: 0 - DisplayTexture: 0 - SlingshotForce: 0 - SlingshotThreshold: 0 - SlingshotAnimation: 0 - Elasticity: 0 - ElasticityFalloff: 0 - Friction: 0 - Scatter: 0 - IsTopBottomVisible: 0 - OverwritePhysics: 0 - DisableLightingTop: 0 - DisableLightingBelow: 0 - IsSideVisible: 0 - IsReflectionEnabled: 0 - DragPoints: [] - IsTimerEnabled: 0 - TimerInterval: 0 - Points: 0 - IsDisabled: 0 - IsDroppable: 0 - IsFlipbook: 0 - IsBottomSolid: 0 - HeightBottom: 0 + _isLocked: 0 + _editorLayer: 0 + _editorLayerName: + _editorLayerVisibility: 1 HeightTop: 50 - Inner: 1 - DragPoints: [] + HeightBottom: 0 + _dragPoints: [] --- !u!114 &7807490122124131058 MonoBehaviour: m_ObjectHideFlags: 0 @@ -161,18 +119,17 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: PhysicsMaterial: {fileID: 0} - ShowColliderMesh: 0 + ShowColliderMesh: 1 ShowAabbs: 0 HitEvent: 0 Threshold: 2 - SlingShotMaterial: {fileID: 0} - SlingshotForce: 80 + IsBottomSolid: 0 SlingshotThreshold: 0 - SlingshotAnimation: 1 + SlingshotForce: 80 OverwritePhysics: 1 - Elasticity: 0 + Elasticity: 0.3 ElasticityFalloff: 0 - Friction: 0 + Friction: 0.3 Scatter: 0 --- !u!114 &-3256406434667294810 MonoBehaviour: @@ -231,7 +188,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 75abfb7d4ccc1b14bb10b53cb3f60574, type: 3} m_Name: m_EditorClassIdentifier: - _meshCreated: 1 --- !u!33 &6035808683100513115 MeshFilter: m_ObjectHideFlags: 0 diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs index bcc7ea8b7..ccfe19392 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs @@ -30,10 +30,12 @@ internal struct LineSlingshotCollider : ICollider public readonly float2 V1; public readonly float2 V2; + public float2 Normal; - private float _length; public readonly float ZLow; public readonly float ZHigh; + private float _length; + private readonly float _force; public ColliderBounds Bounds => new ColliderBounds(_header.Entity, _header.Id, new Aabb( From a039b113ff0ed0b17782143fb5e6666d01b4adcc Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 29 Sep 2021 00:16:21 +0200 Subject: [PATCH 02/15] editor: Fix locked status after play. --- .../VisualPinball.Unity.Editor/VPT/ItemInspector.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs index 9418e99b6..ff7debd3e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs @@ -37,6 +37,8 @@ public abstract class ItemInspector : UnityEditor.Editor private bool _transformsDirty; private bool _visibilityDirty; + private SerializedProperty _isLockedProperty; + #region Unity Events protected virtual void OnEnable() @@ -45,6 +47,8 @@ protected virtual void OnEnable() TableComponent = (target as MonoBehaviour)?.gameObject.GetComponentInParent(); PlayfieldComponent = (target as MonoBehaviour)?.gameObject.GetComponentInParent(); + + _isLockedProperty = serializedObject.FindProperty("_isLocked"); } protected virtual void OnDisable() @@ -232,16 +236,13 @@ protected void MeshDropdownProperty(string label, SerializedProperty meshProp, s protected void OnPreInspectorGUI() { - if (!(target is IMainRenderableComponent item)) { + if (!(target is IMainRenderableComponent)) { return; } EditorGUI.BeginChangeCheck(); - var newLock = EditorGUILayout.Toggle("IsLocked", item.IsLocked); - if (EditorGUI.EndChangeCheck()) - { - FinishEdit("IsLocked"); - item.IsLocked = newLock; + PropertyField(_isLockedProperty, "Locked"); + if (EditorGUI.EndChangeCheck()) { SceneView.RepaintAll(); } } From eeec79b696e7b2a118dc6f09310703d5c31f5f6e Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 29 Sep 2021 00:26:29 +0200 Subject: [PATCH 03/15] sling: Add component and inspector. --- .../VPT/Surface/SlingshotInspector.cs | 58 +++++++++ .../VPT/Surface/SlingshotInspector.cs.meta | 11 ++ .../VPT/Surface/SlingshotComponent.cs | 114 ++++++++++++++++++ .../VPT/Surface/SlingshotComponent.cs.meta | 11 ++ 4 files changed, 194 insertions(+) create mode 100644 VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs create mode 100644 VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs.meta create mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs create mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs new file mode 100644 index 000000000..ecd75d63e --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs @@ -0,0 +1,58 @@ +// Visual Pinball Engine +// Copyright (C) 2021 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 UnityEditor; +using UnityEngine; + +namespace VisualPinball.Unity.Editor +{ + [CustomEditor(typeof(SlingshotComponent))] + public class SlingshotInspector : ItemInspector + { + private SerializedProperty _surfaceProperty; + private SerializedProperty _rubberOnProperty; + private SerializedProperty _rubberOffProperty; + protected override MonoBehaviour UndoTarget => target as MonoBehaviour; + + protected override void OnEnable() + { + base.OnEnable(); + + _surfaceProperty = serializedObject.FindProperty(nameof(SlingshotComponent.SlingshotSurface)); + _rubberOnProperty = serializedObject.FindProperty(nameof(SlingshotComponent.RubberOn)); + _rubberOffProperty = serializedObject.FindProperty(nameof(SlingshotComponent.RubberOff)); + } + + public override void OnInspectorGUI() + { + // if (HasErrors()) { + // return; + // } + + BeginEditing(); + + OnPreInspectorGUI(); + + PropertyField(_surfaceProperty, "Collider Surface"); + PropertyField(_rubberOffProperty, "Rubber Off", true); + PropertyField(_rubberOnProperty, "Rubber On", true); + + base.OnInspectorGUI(); + + EndEditing(); + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs.meta b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs.meta new file mode 100644 index 000000000..2ccd9e843 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 083d1d50400ed5d428067a9dfb9adc3a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs new file mode 100644 index 000000000..961a28b2f --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs @@ -0,0 +1,114 @@ +// Visual Pinball Engine +// Copyright (C) 2021 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 + +using Unity.Entities; +using UnityEngine; + +namespace VisualPinball.Unity +{ + public class SlingshotComponent : MonoBehaviour, IMeshComponent, IMainRenderableComponent + { + public SurfaceColliderComponent SlingshotSurface; + public RubberComponent RubberOn; + public RubberComponent RubberOff; + + [SerializeField] private bool _isLocked; + + #region IMeshComponent + + public IMainRenderableComponent MainRenderableComponent => this; + + public void RebuildMeshes() + { + // check renderers + var mf = GetComponent(); + var smr = GetComponent(); + if (!smr || !mf) { + Debug.LogWarning("Mesh filter or skinned mesh renderer not found."); + return; + } + + // no rubbers, no mesh + if (RubberOn == null || RubberOff == null) { + Debug.LogWarning("Rubber references not set."); + return; + } + var m0 = RubberOff.GetComponent(); + var m1 = RubberOn.GetComponent(); + if (m0 == null || m1 == null || m0.sharedMesh == null || m1.sharedMesh == null) { + Debug.LogWarning("Rubber references not found."); + return; + } + + if (m0.sharedMesh.vertices.Length != m1.sharedMesh.vertices.Length) { + Debug.LogWarning($"Rubber vertices vary ({m0.sharedMesh.vertices.Length} vs {m1.sharedMesh.vertices.Length})."); + return; + } + + var mesh0 = m0.sharedMesh; + var mesh1 = m1.sharedMesh; + var triangles = mesh0.triangles; + var vertices0 = mesh0.vertices; + var normals0 = mesh0.normals; + var vertices1 = mesh1.vertices; + var normals1 = mesh1.normals; + + var mesh = new Mesh { name = "Slingshot Mesh" }; + mesh.vertices = vertices0; + mesh.normals = normals0; + mesh.triangles = triangles; + + var deltaVertices = new Vector3[vertices0.Length]; + var deltaNormals = new Vector3[vertices0.Length]; + for (var i = 0; i < vertices0.Length; i++) { + deltaVertices[i] = vertices0[i] - vertices1[i]; + deltaNormals[i] = normals0[i] - normals1[i]; + } + mesh.AddBlendShapeFrame("slingshot", 1, deltaVertices, deltaNormals, null); + + mf.sharedMesh = mesh; + smr.sharedMesh = mesh; + } + + #endregion + + #region IMainRenderableComponent + + public bool IsLocked { get => _isLocked; set => _isLocked = value; } + public bool CanBeTransformed => false; + public string ItemName => "Slingshot"; + public Entity Entity { get; set; } + + public void UpdateTransforms() { } + public void UpdateVisibility() { } + + public ItemDataTransformType EditorPositionType => ItemDataTransformType.None; + public Vector3 GetEditorPosition() => Vector3.zero; + public void SetEditorPosition(Vector3 pos) { } + + public ItemDataTransformType EditorRotationType => ItemDataTransformType.None; + public Vector3 GetEditorRotation() => Vector3.zero; + public void SetEditorRotation(Vector3 pos) { } + + public ItemDataTransformType EditorScaleType => ItemDataTransformType.None; + public Vector3 GetEditorScale() => Vector3.one; + public void SetEditorScale(Vector3 pos) { } + + #endregion + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs.meta new file mode 100644 index 000000000..a4e746f87 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8840a3e9d44a5a443a20087784eae747 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From a8a6467903ac17c3ee72dc39b99a15108b52202f Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 29 Sep 2021 23:45:31 +0200 Subject: [PATCH 04/15] sling: Properly create geometry. --- VisualPinball.Engine/Math/DragPointData.cs | 22 +++++ .../VPT/Surface/SlingshotInspector.cs | 17 +++- .../VPT/Surface/SlingshotComponent.cs | 92 +++++++++++-------- 3 files changed, 91 insertions(+), 40 deletions(-) diff --git a/VisualPinball.Engine/Math/DragPointData.cs b/VisualPinball.Engine/Math/DragPointData.cs index 28f9f2aaa..da7089fc2 100644 --- a/VisualPinball.Engine/Math/DragPointData.cs +++ b/VisualPinball.Engine/Math/DragPointData.cs @@ -20,6 +20,7 @@ // ReSharper disable FieldCanBeMadeReadOnly.Global // ReSharper disable ConvertToConstant.Global // ReSharper disable CompareOfFloatsByEqualityOperator +// ReSharper disable InconsistentNaming #endregion using System; @@ -72,6 +73,20 @@ public override string ToString() return $"DragPoint({Center.X}/{Center.Y}/{Center.Z}, {(IsSmooth ? "S" : "")}{(IsSlingshot ? "SS" : "")}{(HasAutoTexture ? "A" : "")})"; } + public DragPointData Lerp(DragPointData dp, float pos) + { + return new DragPointData(Center + pos * (dp.Center - Center)) { + IsSmooth = dp.IsSmooth, + IsSlingshot = dp.IsSlingshot, + HasAutoTexture = dp.HasAutoTexture, + TextureCoord = dp.TextureCoord, + IsLocked = dp.IsLocked, + EditorLayer = dp.EditorLayer, + EditorLayerName = dp.EditorLayerName, + EditorLayerVisibility = EditorLayerVisibility + }; + } + #region BIFF static DragPointData() @@ -79,6 +94,12 @@ static DragPointData() Init(typeof(DragPointData), Attributes); } + public DragPointData(Vertex3D center) : base(null) + { + Center = center; + HasAutoTexture = true; + } + public DragPointData(float x, float y) : base(null) { Center = new Vertex3D(x, y, 0f); @@ -112,5 +133,6 @@ public override void Write(BinaryWriter writer, HashWriter hashWriter) private static readonly Dictionary> Attributes = new Dictionary>(); #endregion + } } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs index ecd75d63e..a0ebff9a8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs @@ -22,18 +22,24 @@ namespace VisualPinball.Unity.Editor [CustomEditor(typeof(SlingshotComponent))] public class SlingshotInspector : ItemInspector { + private SlingshotComponent _slingShot; + private SerializedProperty _surfaceProperty; - private SerializedProperty _rubberOnProperty; private SerializedProperty _rubberOffProperty; + private SerializedProperty _rubberOnProperty; + protected override MonoBehaviour UndoTarget => target as MonoBehaviour; protected override void OnEnable() { base.OnEnable(); + _slingShot = target as SlingshotComponent; + _surfaceProperty = serializedObject.FindProperty(nameof(SlingshotComponent.SlingshotSurface)); - _rubberOnProperty = serializedObject.FindProperty(nameof(SlingshotComponent.RubberOn)); _rubberOffProperty = serializedObject.FindProperty(nameof(SlingshotComponent.RubberOff)); + _rubberOnProperty = serializedObject.FindProperty(nameof(SlingshotComponent.RubberOn)); + } public override void OnInspectorGUI() @@ -50,6 +56,13 @@ public override void OnInspectorGUI() PropertyField(_rubberOffProperty, "Rubber Off", true); PropertyField(_rubberOnProperty, "Rubber On", true); + EditorGUI.BeginChangeCheck(); + var pos = EditorGUILayout.Slider("Test", _slingShot.Position, 0f, 1f); + if (EditorGUI.EndChangeCheck()) { + _slingShot.Position = pos; + _slingShot.RebuildMeshes(); + } + base.OnInspectorGUI(); EndEditing(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs index 961a28b2f..4222e2ff8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs @@ -16,73 +16,60 @@ // ReSharper disable InconsistentNaming +using System; using Unity.Entities; using UnityEngine; +using VisualPinball.Engine.Math; +using VisualPinball.Engine.VPT.Rubber; namespace VisualPinball.Unity { - public class SlingshotComponent : MonoBehaviour, IMeshComponent, IMainRenderableComponent + public class SlingshotComponent : MonoBehaviour, IMeshComponent, IMainRenderableComponent, IRubberData { public SurfaceColliderComponent SlingshotSurface; public RubberComponent RubberOn; public RubberComponent RubberOff; + [NonSerialized] public float Position; [SerializeField] private bool _isLocked; + #region IRubberData + + public DragPointData[] DragPoints => DragPointsAt(Position); + public int Thickness => RubberOff.GetComponent()?.Thickness ?? 8; + public float Height => RubberOff.GetComponent()?.Height ?? 25f; + public float RotX => RubberOff.GetComponent()?.RotX ?? 0; + public float RotY => RubberOff.GetComponent()?.RotY ?? 0; + public float RotZ => RubberOff.GetComponent()?.RotZ ?? 0; + + #endregion + #region IMeshComponent public IMainRenderableComponent MainRenderableComponent => this; public void RebuildMeshes() { - // check renderers var mf = GetComponent(); - var smr = GetComponent(); - if (!smr || !mf) { - Debug.LogWarning("Mesh filter or skinned mesh renderer not found."); + if (!mf) { + Debug.LogWarning("Mesh filter or renderer not found."); return; } - // no rubbers, no mesh - if (RubberOn == null || RubberOff == null) { - Debug.LogWarning("Rubber references not set."); + if (!RubberOff) { return; } - var m0 = RubberOff.GetComponent(); - var m1 = RubberOn.GetComponent(); - if (m0 == null || m1 == null || m0.sharedMesh == null || m1.sharedMesh == null) { - Debug.LogWarning("Rubber references not found."); + var pf = GetComponentInParent(); + var r0 = RubberOff.GetComponent(); + if (!r0 || !pf) { return; } - if (m0.sharedMesh.vertices.Length != m1.sharedMesh.vertices.Length) { - Debug.LogWarning($"Rubber vertices vary ({m0.sharedMesh.vertices.Length} vs {m1.sharedMesh.vertices.Length})."); - return; - } - - var mesh0 = m0.sharedMesh; - var mesh1 = m1.sharedMesh; - var triangles = mesh0.triangles; - var vertices0 = mesh0.vertices; - var normals0 = mesh0.normals; - var vertices1 = mesh1.vertices; - var normals1 = mesh1.normals; - - var mesh = new Mesh { name = "Slingshot Mesh" }; - mesh.vertices = vertices0; - mesh.normals = normals0; - mesh.triangles = triangles; - - var deltaVertices = new Vector3[vertices0.Length]; - var deltaNormals = new Vector3[vertices0.Length]; - for (var i = 0; i < vertices0.Length; i++) { - deltaVertices[i] = vertices0[i] - vertices1[i]; - deltaNormals[i] = normals0[i] - normals1[i]; - } - mesh.AddBlendShapeFrame("slingshot", 1, deltaVertices, deltaNormals, null); + var mesh = new RubberMeshGenerator(this) + .GetTransformedMesh(pf.PlayfieldHeight, r0.Height, pf.PlayfieldDetailLevel) + .ToUnityMesh(); mf.sharedMesh = mesh; - smr.sharedMesh = mesh; } #endregion @@ -110,5 +97,34 @@ public void SetEditorRotation(Vector3 pos) { } public void SetEditorScale(Vector3 pos) { } #endregion + + private DragPointData[] DragPointsAt(float pos) + { + if (RubberOn == null || RubberOff == null) { + Debug.LogWarning("Rubber references not set."); + return Array.Empty(); + } + var r0 = RubberOff.GetComponent(); + var r1 = RubberOn.GetComponent(); + if (r0 == null || r1 == null || r0.DragPoints == null || r1.DragPoints == null) { + Debug.LogWarning("Rubber references not found or drag points not set."); + return Array.Empty(); + } + + var dp0 = r0.DragPoints; + var dp1 = r1.DragPoints; + + if (dp0.Length != dp1.Length) { + Debug.LogWarning($"Drag point number varies ({dp0.Length} vs {dp1.Length}.)."); + return Array.Empty(); + } + + var dp = new DragPointData[dp0.Length]; + for (var i = 0; i < dp.Length; i++) { + dp[i] = dp0[i].Lerp(dp1[i], pos); + } + + return dp; + } } } From 8a113fe11dd09de7a23c2f37f788450f347c950b Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 30 Sep 2021 00:02:07 +0200 Subject: [PATCH 05/15] sling: Animate manually. --- .../VPT/Surface/SlingshotInspector.cs | 12 ++++++- .../VPT/Surface/SlingshotComponent.cs | 35 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs index a0ebff9a8..7fa9dc323 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs @@ -27,6 +27,8 @@ public class SlingshotInspector : ItemInspector private SerializedProperty _surfaceProperty; private SerializedProperty _rubberOffProperty; private SerializedProperty _rubberOnProperty; + private SerializedProperty _animationDurationProperty; + private SerializedProperty _animationCurveProperty; protected override MonoBehaviour UndoTarget => target as MonoBehaviour; @@ -39,7 +41,8 @@ protected override void OnEnable() _surfaceProperty = serializedObject.FindProperty(nameof(SlingshotComponent.SlingshotSurface)); _rubberOffProperty = serializedObject.FindProperty(nameof(SlingshotComponent.RubberOff)); _rubberOnProperty = serializedObject.FindProperty(nameof(SlingshotComponent.RubberOn)); - + _animationDurationProperty = serializedObject.FindProperty(nameof(SlingshotComponent.AnimationDuration)); + _animationCurveProperty = serializedObject.FindProperty(nameof(SlingshotComponent.AnimationCurve)); } public override void OnInspectorGUI() @@ -56,6 +59,9 @@ public override void OnInspectorGUI() PropertyField(_rubberOffProperty, "Rubber Off", true); PropertyField(_rubberOnProperty, "Rubber On", true); + PropertyField(_animationDurationProperty, "Animation Duration (ms)"); + PropertyField(_animationCurveProperty, "Animation Curve"); + EditorGUI.BeginChangeCheck(); var pos = EditorGUILayout.Slider("Test", _slingShot.Position, 0f, 1f); if (EditorGUI.EndChangeCheck()) { @@ -66,6 +72,10 @@ public override void OnInspectorGUI() base.OnInspectorGUI(); EndEditing(); + + if (GUILayout.Button("Trigger")) { + _slingShot.TriggerAnimation(); + } } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs index 4222e2ff8..0b0413e8a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs @@ -17,7 +17,9 @@ // ReSharper disable InconsistentNaming using System; +using System.Collections; using Unity.Entities; +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT.Rubber; @@ -30,9 +32,42 @@ public class SlingshotComponent : MonoBehaviour, IMeshComponent, IMainRenderable public RubberComponent RubberOn; public RubberComponent RubberOff; + public float AnimationDuration = 200f; + public AnimationCurve AnimationCurve = new AnimationCurve( + new Keyframe(0, 0), + new Keyframe(0.5f, 1, 3.535f, 0f, 0.03333336f, 0.5416666f), + new Keyframe(1, 0) + ); + [NonSerialized] public float Position; [SerializeField] private bool _isLocked; + #region Runtime + + public void TriggerAnimation() + { + StopAllCoroutines(); + StartCoroutine(nameof(Animate)); + } + + private IEnumerator Animate() + { + var duration = AnimationDuration / 1000; + var journey = 0f; + while (journey <= duration) { + + journey += Time.deltaTime; + var curvePercent = AnimationCurve.Evaluate(journey / duration); + Position = math.clamp(curvePercent, 0f, 1f); + + RebuildMeshes(); + + yield return null; + } + } + + #endregion + #region IRubberData public DragPointData[] DragPoints => DragPointsAt(Position); From 614d65cff3c226c735d2dc05d112b7e374c1cf26 Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 30 Sep 2021 00:24:10 +0200 Subject: [PATCH 06/15] sling: Animate on hit. --- .../VPT/Surface/SlingshotInspector.cs | 19 +++++----- .../Game/DebugBallCreator.cs | 4 +-- .../Game/Engine/DefaultGamelogicEngine.cs | 2 +- .../VPT/Surface/SlingshotComponent.cs | 36 +++++++++++++++++++ 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs index 7fa9dc323..2e539bba7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs @@ -47,21 +47,24 @@ protected override void OnEnable() public override void OnInspectorGUI() { - // if (HasErrors()) { - // return; - // } - BeginEditing(); OnPreInspectorGUI(); - PropertyField(_surfaceProperty, "Collider Surface"); + PropertyField(_surfaceProperty, "Slingshot Wall"); PropertyField(_rubberOffProperty, "Rubber Off", true); PropertyField(_rubberOnProperty, "Rubber On", true); - PropertyField(_animationDurationProperty, "Animation Duration (ms)"); + if (_slingShot.RubberOn && _slingShot.RubberOff && + _slingShot.RubberOn.DragPoints.Length != _slingShot.RubberOff.DragPoints.Length) { + EditorGUILayout.HelpBox($"In order to animate the rubber, the number of drag points of both rubbers must be equal. Here we have {_slingShot.RubberOn.DragPoints.Length} (on) and {_slingShot.RubberOff.DragPoints.Length} (off).", MessageType.Error); + } + + EditorGUILayout.Space(10f); + PropertyField(_animationDurationProperty, "Animation Duration"); PropertyField(_animationCurveProperty, "Animation Curve"); + EditorGUILayout.Space(10f); EditorGUI.BeginChangeCheck(); var pos = EditorGUILayout.Slider("Test", _slingShot.Position, 0f, 1f); if (EditorGUI.EndChangeCheck()) { @@ -72,10 +75,6 @@ public override void OnInspectorGUI() base.OnInspectorGUI(); EndEditing(); - - if (GUILayout.Button("Trigger")) { - _slingShot.TriggerAnimation(); - } } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/DebugBallCreator.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/DebugBallCreator.cs index ca999702e..dfe2759d8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/DebugBallCreator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/DebugBallCreator.cs @@ -34,8 +34,8 @@ public DebugBallCreator(float x, float y, float playfieldHeight) _x = x; _y = y; _z = playfieldHeight; - _kickAngle = math.radians(180f);; - _kickForce = 20; + _kickAngle = 0; + _kickForce = 0; } public DebugBallCreator(float x, float y, float playfieldHeight, float kickAngle, float kickForce) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs index 0dae09132..9993e46b5 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs @@ -229,7 +229,7 @@ public void Switch(string id, bool isClosed) case SwCreateBall: { if (isClosed) { - _ballManager.CreateBall(new DebugBallCreator(_playfieldComponent.Width / 2f, _playfieldComponent.Height / 2f, _playfieldComponent.TableHeight)); + _ballManager.CreateBall(new DebugBallCreator(630, _playfieldComponent.Height / 2f, _playfieldComponent.TableHeight)); } break; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs index 0b0413e8a..8762bbab7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs @@ -26,13 +26,22 @@ namespace VisualPinball.Unity { + [AddComponentMenu("Visual Pinball/Game Item/Slingshot")] public class SlingshotComponent : MonoBehaviour, IMeshComponent, IMainRenderableComponent, IRubberData { + [Tooltip("Reference to the wall that acts as slingshot.")] public SurfaceColliderComponent SlingshotSurface; + + [Tooltip("Reference to the rubber at \"enabled\" position (coil on).")] public RubberComponent RubberOn; + + [Tooltip("Reference to the rubber at \"disabled\" position (coil off).")] public RubberComponent RubberOff; + [Tooltip("Total duration of the animation in milliseconds.")] public float AnimationDuration = 200f; + + [Tooltip("Animation curve. Starts at 0 and ends at 0.")] public AnimationCurve AnimationCurve = new AnimationCurve( new Keyframe(0, 0), new Keyframe(0.5f, 1, 3.535f, 0f, 0.03333336f, 0.5416666f), @@ -44,6 +53,28 @@ public class SlingshotComponent : MonoBehaviour, IMeshComponent, IMainRenderable #region Runtime + private void Start() + { + var player = GetComponentInParent(); + if (!player || player.TableApi == null || !SlingshotSurface) { + return; + } + var slingshotSurfaceApi = player.TableApi.Surface(SlingshotSurface.MainComponent); + slingshotSurfaceApi.Slingshot += OnSlingshot; + } + + private void OnDestroy() + { + var player = GetComponentInParent(); + if (!player || player.TableApi == null || !SlingshotSurface) { + return; + } + var slingshotSurfaceApi = player.TableApi.Surface(SlingshotSurface.MainComponent); + slingshotSurfaceApi.Slingshot -= OnSlingshot; + } + + private void OnSlingshot(object sender, EventArgs e) => TriggerAnimation(); + public void TriggerAnimation() { StopAllCoroutines(); @@ -60,6 +91,7 @@ private IEnumerator Animate() var curvePercent = AnimationCurve.Evaluate(journey / duration); Position = math.clamp(curvePercent, 0f, 1f); + // todo cache the meshes RebuildMeshes(); yield return null; @@ -100,6 +132,10 @@ public void RebuildMeshes() return; } + if (DragPoints.Length < 3) { + return; + } + var mesh = new RubberMeshGenerator(this) .GetTransformedMesh(pf.PlayfieldHeight, r0.Height, pf.PlayfieldDetailLevel) .ToUnityMesh(); From ecc8fae1b1f62b636617e43fa88291ae8a4956aa Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 30 Sep 2021 00:34:52 +0200 Subject: [PATCH 07/15] sling: Cache generated meshes during gameplay. --- .../VPT/Surface/SlingshotComponent.cs | 50 ++++++++++++++----- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs index 8762bbab7..85ff3a352 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs @@ -18,6 +18,7 @@ using System; using System.Collections; +using System.Collections.Generic; using Unity.Entities; using Unity.Mathematics; using UnityEngine; @@ -50,9 +51,18 @@ public class SlingshotComponent : MonoBehaviour, IMeshComponent, IMainRenderable [NonSerialized] public float Position; [SerializeField] private bool _isLocked; + [NonSerialized] private readonly Dictionary _meshes = new Dictionary(); + [NonSerialized] private RubberMeshGenerator _meshGenerator; + + private const int MaxNumMeshCaches = 15; #region Runtime + private void Awake() + { + _meshGenerator = new RubberMeshGenerator(this); + } + private void Start() { var player = GetComponentInParent(); @@ -66,11 +76,12 @@ private void Start() private void OnDestroy() { var player = GetComponentInParent(); - if (!player || player.TableApi == null || !SlingshotSurface) { - return; + if (player && player.TableApi != null && SlingshotSurface) { + var slingshotSurfaceApi = player.TableApi.Surface(SlingshotSurface.MainComponent); + slingshotSurfaceApi.Slingshot -= OnSlingshot; } - var slingshotSurfaceApi = player.TableApi.Surface(SlingshotSurface.MainComponent); - slingshotSurfaceApi.Slingshot -= OnSlingshot; + + _meshes.Clear(); } private void OnSlingshot(object sender, EventArgs e) => TriggerAnimation(); @@ -123,24 +134,39 @@ public void RebuildMeshes() return; } - if (!RubberOff) { - return; + var mesh = GetMesh(); + + if (mesh != null) { + mf.sharedMesh = mesh; } + } + + private Mesh GetMesh() + { + var pos = (int)(Position * MaxNumMeshCaches); + if (_meshes.ContainsKey(pos)) { + return _meshes[pos]; + } + + if (!RubberOff || DragPoints.Length < 3) { + return null; + } + var pf = GetComponentInParent(); var r0 = RubberOff.GetComponent(); if (!r0 || !pf) { - return; + return null; } - if (DragPoints.Length < 3) { - return; - } + Debug.Log($"Generating new mesh at {pos}"); - var mesh = new RubberMeshGenerator(this) + var mesh = _meshGenerator .GetTransformedMesh(pf.PlayfieldHeight, r0.Height, pf.PlayfieldDetailLevel) .ToUnityMesh(); - mf.sharedMesh = mesh; + _meshes[pos] = mesh; + + return mesh; } #endregion From 7ffbac146dad833f417080d2137dd1b18745d1c7 Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 30 Sep 2021 00:44:03 +0200 Subject: [PATCH 08/15] sling: Add manual page. --- .../manual/mechanisms/slingshots.md | 47 +++++++++++++++++++ .../Documentation~/creators-guide/toc.yml | 2 + .../VPT/Surface/SlingshotComponent.cs | 3 +- 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshots.md diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshots.md b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshots.md new file mode 100644 index 000000000..5bb5437d3 --- /dev/null +++ b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshots.md @@ -0,0 +1,47 @@ +# Slingshots + +Slinghots are usually located above the flippers. They consist of two blade switches that are located at the inner side of a triangle-shaped rubber. Between the switches there is a coil that propellers the ball in the opposite direction when either switch closes. + +Visual Pinball doesn't have an explicit slingshot element. Instead, it relies on walls, where a segment can be marked as *slingshot*, with the effect of an additional force being applied to the ball when the segment is hit. However, the rubber animation is up to the table script to implement. + +VPE provides a slingshot component that implements the rubber animation during runtime. This allows for functional slingshots without any additional code. Note however that this approach isn't ideal and will be replaced with a proper slingshot element in the future. + + +# Setup + +## Wall + + +## Rubbers + +> [!NOTE] +> In VPX, tables often come with three rubbers elements that are toggled in order to fake an animation. When using VPE's slingshot component, you can delete the rubber at mid position, since only the start and end rubbers are used. The interpolation is calculated in real time depending on the speed of the slingshot. + + +# Howtos + +## Set Up a Slingshot from an Imported Table + +This howto uses the blank table, but other tables should be quite similar. Usually, slingshots consist of three rubbers for the animation, plus a wall for the physics. What we need is the following: + +1. The wall with a segment set to *slingshot* +2. The rubber at idle position +3. The rubber at *activated* position + +Note that both rubbers must have the same number of drag points. This is because during the animation, the rubber is linearly interpolated between the two drag points positions, which isn't possible if the number differs. When converting a table from Visual Pinball, that means that you most probably need to add two additional drag points to the rubber at idle position. + +### 1. Identify and clean up the elements + +Zoom in to the slingshot you want to set up. You'll probably want to temporily hide the plastic that covers up the rubbers and the wall. Find the rubber at idle position and at activated position. Delete the rubber in-between, we don't need that one because VPE automatically interpolates between the two depending on the duration of the animation. + +Note the names of those rubbers. Here it's `RSling` and `RSling1`. Also look for the wall that acts as the physical slingshot, which is called `RightSlingshot`. + +### 2. Add additional drag points if necessary + +Now, since `RSling1` is bent and thus contains three additional control points, we'll add the same points to `RSling`. + +We can now also can hide the meshes of the rubbers. + +### 3. Add the slingshot component + +Any game object will do, but in order to keep things together, we'll add the slingshot component to the wall's game object. Select it, click on *Add component* in the inspector, and choose *Visual Pinball -> Elements -> Slingsho*. diff --git a/VisualPinball.Unity/Documentation~/creators-guide/toc.yml b/VisualPinball.Unity/Documentation~/creators-guide/toc.yml index 0d948af46..f63844590 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/toc.yml +++ b/VisualPinball.Unity/Documentation~/creators-guide/toc.yml @@ -45,3 +45,5 @@ href: manual/mechanisms/troughs.md - name: Flippers href: manual/mechanisms/flippers.md + - name: Slingshots + href: manual/mechanisms/slingshots.md diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs index 85ff3a352..6b520b190 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs @@ -86,7 +86,7 @@ private void OnDestroy() private void OnSlingshot(object sender, EventArgs e) => TriggerAnimation(); - public void TriggerAnimation() + private void TriggerAnimation() { StopAllCoroutines(); StartCoroutine(nameof(Animate)); @@ -102,7 +102,6 @@ private IEnumerator Animate() var curvePercent = AnimationCurve.Evaluate(journey / duration); Position = math.clamp(curvePercent, 0f, 1f); - // todo cache the meshes RebuildMeshes(); yield return null; From a8a07f8641118a0209620c15a7102adc61278fdb Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 2 Oct 2021 01:08:44 +0200 Subject: [PATCH 09/15] sling: Add arm animation. --- .../Editor/Icons/large_blue/slingshot.png | Bin 0 -> 10259 bytes .../Icons/large_blue/slingshot.png.meta | 108 ++++++++++++++++++ .../Editor/Icons/large_gray/slingshot.png | Bin 0 -> 10358 bytes .../Icons/large_gray/slingshot.png.meta | 108 ++++++++++++++++++ .../Editor/Icons/large_green/slingshot.png | Bin 0 -> 10285 bytes .../Icons/large_green/slingshot.png.meta | 108 ++++++++++++++++++ .../Editor/Icons/large_orange/slingshot.png | Bin 0 -> 10347 bytes .../Icons/large_orange/slingshot.png.meta | 108 ++++++++++++++++++ .../Editor/Icons/small_blue/slingshot.png | Bin 0 -> 808 bytes .../Icons/small_blue/slingshot.png.meta | 108 ++++++++++++++++++ .../Editor/Icons/small_gray/slingshot.png | Bin 0 -> 812 bytes .../Icons/small_gray/slingshot.png.meta | 108 ++++++++++++++++++ .../Editor/Icons/small_green/slingshot.png | Bin 0 -> 810 bytes .../Icons/small_green/slingshot.png.meta | 108 ++++++++++++++++++ .../Editor/Icons/small_orange/slingshot.png | Bin 0 -> 812 bytes .../Icons/small_orange/slingshot.png.meta | 108 ++++++++++++++++++ .../manual/mechanisms/slingshots.md | 49 +++++++- .../Import/VpxSceneConverter.cs | 16 +-- .../Toolbox/ToolboxEditor.cs | 19 +++ .../VisualPinball.Unity.Editor/Utils/Icons.cs | 8 +- .../VPT/Surface/SlingshotInspector.cs | 24 ++-- .../VPT/Surface/SlingshotComponent.cs | 38 ++++-- .../VPT/Surface/SlingshotComponent.cs.meta | 2 +- 23 files changed, 988 insertions(+), 32 deletions(-) create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/large_blue/slingshot.png create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/large_blue/slingshot.png.meta create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/large_gray/slingshot.png create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/large_gray/slingshot.png.meta create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/large_green/slingshot.png create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/large_green/slingshot.png.meta create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/large_orange/slingshot.png create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/large_orange/slingshot.png.meta create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_blue/slingshot.png create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_blue/slingshot.png.meta create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_gray/slingshot.png create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_gray/slingshot.png.meta create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_green/slingshot.png create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_green/slingshot.png.meta create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_orange/slingshot.png create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_orange/slingshot.png.meta diff --git a/VisualPinball.Unity/Assets/Editor/Icons/large_blue/slingshot.png b/VisualPinball.Unity/Assets/Editor/Icons/large_blue/slingshot.png new file mode 100644 index 0000000000000000000000000000000000000000..25e7d9081941a7fd9f79e73bdc7a3b6ba12cc6f2 GIT binary patch literal 10259 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 XBins?%mmUA{5xQ3xwmMKYxw^Gnl($b literal 0 HcmV?d00001 diff --git a/VisualPinball.Unity/Assets/Editor/Icons/large_gray/slingshot.png.meta b/VisualPinball.Unity/Assets/Editor/Icons/large_gray/slingshot.png.meta new file mode 100644 index 000000000..e2014c784 --- /dev/null +++ b/VisualPinball.Unity/Assets/Editor/Icons/large_gray/slingshot.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: c61b00d7596aa4f44a57a28e43e0d345 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 2 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 512 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 512 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Editor/Icons/large_green/slingshot.png b/VisualPinball.Unity/Assets/Editor/Icons/large_green/slingshot.png new file mode 100644 index 0000000000000000000000000000000000000000..81eade77a0e32b3ed8014d78e9f515de6bb8cf8d GIT binary patch literal 10285 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 Ycb~=Uj{9-O@E+M(@7(%ui+9Zb00?I&z5oCK literal 0 HcmV?d00001 diff --git a/VisualPinball.Unity/Assets/Editor/Icons/large_orange/slingshot.png.meta b/VisualPinball.Unity/Assets/Editor/Icons/large_orange/slingshot.png.meta new file mode 100644 index 000000000..b1b48872a --- /dev/null +++ b/VisualPinball.Unity/Assets/Editor/Icons/large_orange/slingshot.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: a159bc399cb055441820093655e8d7b8 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 2 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 512 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 512 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Editor/Icons/small_blue/slingshot.png b/VisualPinball.Unity/Assets/Editor/Icons/small_blue/slingshot.png new file mode 100644 index 0000000000000000000000000000000000000000..2d3b10101f5194730ea80f08c49f7633669b70ab GIT binary patch literal 808 zcmV+@1K0eCP)2{L$pu$``V?r?-bGkz9(% zni{9vaWtaFI$e{WNl8_A(bRuTMOm?>$+K-+=P`+1(f1@{)v4s6ihmBSyMlOiqStr<9S4@DIucREv7Y1 zx&(fT$ZuoaN|OsrYx7wDmeCoCWm2Lbt5bVTRX3W3k<$9(w<7XntXm+e>%cqFBD7_8 zCJM3!>rya%bCYMP;1^^)9=e|IjdfeiGmUi@)W$Cm+%kI+C4wb9u)qtbGD_fsh+I;d zrhUG8a!}bj#-?Kcb|Z=i^}%pvteaDt8h&B&O!x3p5&5o!NV!+3DSa}05c~#pC!w{5 zAbX;!(mDMJy3cd{)-C&*10N>M&;tYf?r4#6+C z7kEPw^$2>Wu}-~q)n;YMuN~>CCP);0wf$42>9RTtLns<`RYNK=$u(~AUcB587rCul zCIYRfIg9NjAyp*3AH`M8FBD58*W5k@$s?*(6=$LIeQHH()of8wk2NM{YfVk`MYK1l zxPgk%nSSbEVlwA?*wZsqrT*r*t>bYdwW3R!-rGuBXB8Q#71gM%yRhh`A|v^~A_yE! mNOWy#Bc*}SBJvmS`V$9HziM0n0000D+L-O&+cYLJZ?Du>5AMoxNI2OiE zMP!8HLzX5F5!WyO|Rnrxdo4@vZzzURKqgAgr@uA+Vf6@!0>-scdkruc|r zx0qBk=@R%MBEJh`SDIX4T5Ex2bbIx0;5L()#0fBJxxiyFgS|fp?;X zZ_8{=6l7;umxAe=XKA7een!^gpzHZ(VeD4(%);19YU2TTx6DpNiQo<%m}3B{j1qV+ zB3G2AX`e5S98`9Wv1u8A?T8{mT`*h}#?Glt4L{G)MECG>5&5QsNVylODP1yr5c~>t zN4~X&AbX;!^Ez89jMXLf9mN|gxqs-g*Uu14UXlQ-`Pjxg8Ljtvkedc{b!e4$)}hzz zspOr~);k$}r+KXWeOvEjye84kYD%9(VfO5u$4NA%n$j0hcqiLZaDrTiz7*x-#yZ41 z;SfBygTU*PsDsx#3uD!5S4~zH{o0YPYP>|zSKB|9ny$*T(1)TyS2d&}lU(B#@7dt` zxX5kXG7)Hb#aZki2`MA${V1+#exbNSa?R}%kUXJkRdMP&-^W(8Qq2Yx^;k0t*;->0 zeHQICDsG@+aHbzSn3&914|{rsD%GoscXOvij3(0 qiXd<_AuDC?5AWj95D~M8o zK>HBAyA4Vuc%Xh!&uS}gDu573*#;UND5-W~%->&#R{ zMkwCrS#e+PGq|GGnT3d)ipW?*&QN`qXT_BJ4N4SBUW4dasrh}L6>DF}UZ36*Oo-%4 zL^jko;f{k5wa)08{76cwy34x$Ln_LOt@5ncHFX}6=rw&`ggOsG)H=fvzR$CwcFH}5 zQk1PHNK%f3=sf0u7wt@l+)EMJ#Y`Wb(I7fOam!wrj3^QFAHU8vu~XPZ%^=YRVr4X1 z3%&0Y-surLxS*o*(u+MFG^>{Rk>X{}8>;BU()f5yfsX zsc6zA@LfcHTW8jqTwq#jfn{`o_I*;KAgfb*LsdUC4I`!X$8SaC(K@q4R9As_qGf2y zY)uqoXIPhl>6_IDRYIiPi`0}Z89oSpfx4s6 zT0@XMQq_5#Em>!DiG4%y221WAy6p8c1XGYCz-m6U@lHnTy&mMI5nVl6<(+ltHG3*~ zr?mA>M&D^3D}UG4I~lJ@w6mJhCsCL^d*^WyO{u2zMHJr2wiKNp*P$;(`M9x(@lH4d z5AI3e^-0tt=$+OX_1blll_kG+q^p`BQS{aJ52dE7@+|bBXw+2=smLTZxW#)mxIQj& z8@EgZ+M?nt_9O`@BkBDpu4;avxJPo$?GunZp=woe8am&nREu4sC1OKq)HWTaM9qqge8qLYe@0{hFc|z=^gSswK%5EUE}&+Ex)aoypy{(7>OAl{ z?gUXLsJ%g=1!_;wdH^E!1TMbf2%Hs3q!cQ04=`Z+zD5BOy8udEiAm6C(FgM7AN*hi5d1PEg#iS4JaB#N5ZNvvuecc2Uzyw1HR| zjn+c%JB4>TgbvQD=)Ck|heu5w>Jo?su$t>d0U7&pzl_<#S)ZS3l4^6{JY5nm#5qYvET_CEfz&p{x zw`H~_3bHe-OTqNbldMn$KO^gL(DnS&nslpqrZwp$webMFTV^MsL~sWW%rO8}MhUzZ zkt<5mw9gku4k|mx*t875c0>`OE*LJXN$1q2hMy-{p?mnbhkUde=d7Uj;lj;)tj^YiL+&^^L>t_fiFG+yad~D;LjMjTS$V~&fI<(3=>(FcV zRPs(~>z$0g(>zxGzO8pMUXy5NHKk9YFnjjS<0KkWP3em$ypwGyI6~Nv?5=_iS)| zT;w)xnFzGJ;w*NMgp?8WeiTNS;u&syOwX?_(=ksb+(UdaRj+Y^|}0 zK8yAm6*o{ZIMa_EOiX61hdn(*mFiW+aogiaY(djbz#v-MMm_0 qMG!cekm%afNY+DN(2ZR!MC32&j}-Pi$j|@)0000 [!Video https://www.youtube.com/embed/rL2uZyYXBHk] -Now, since `RSling1` is bent and thus contains three additional control points, we'll add the same points to `RSling`. + +### 2. Add additional control points if necessary + +Now, since `LSling1` is bent and thus contains three additional control points, we'll add the same points to `LSling`. We can now also can hide the meshes of the rubbers. +> [!Video https://www.youtube.com/embed/Yie1Pby8iGs] + + ### 3. Add the slingshot component -Any game object will do, but in order to keep things together, we'll add the slingshot component to the wall's game object. Select it, click on *Add component* in the inspector, and choose *Visual Pinball -> Elements -> Slingsho*. +In the Toolbox, click on the *Slingshot* icon, which will create a new element in the scene. Rename it and link the elements we've identified in step 1: + +- *Slingshot Wall* links to `LeftSlingShot` +- *Rubber Off* links to `LSling` +- *Rubber On* links to `LSling1` + +The slingshot component is now able to create the mesh. The animation can be tested with the *Test* slide. + +> [!Video https://www.youtube.com/embed/421gesRScYo] + + +### 4. Setup the coil arm animation + +Since the rubber is pushed inside by the arm, the arm should be animated along with the rubber. This can be a bit fiddly, since the arm should be as close to the rubber without clipping through it. + +Move the *Test* slider all to the right, and play with the *X-Rotation* of the coil arm. Once you're happy, copy the angle to the clipboard. Then, select the slingshot. In the inspector, set the following fields: + +- *Coil Arm* links to the primitive, in our case `Sling2`. +- *Arm Angle* is the angle when the coil is enabled. Paste the angle you've copied before. + +Now, when moving the *Test* slider, the arm should animate along with the rubbers. + +> [!Video https://www.youtube.com/embed/Q1jeJHRIziM] + + +### 5. Wrap-up and test + +Before we test, there are two things left to do: + +1. Select the coil arm and disable the collider +2. Enable the plastic we've hidden in step 1. + +Then hit test and have a game! + +> [!Video https://www.youtube.com/embed/dNS4YPdRXTc] diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs index 419c29695..146fb2b48 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs @@ -556,18 +556,20 @@ private void CreateRootHierarchy(string tableName = null) _playfieldComponent.SetData(_sourceTable.Data); } - private GameObject GetGroupParent(IItem item) + private GameObject GetGroupParent(IItem item) => GetGroupParent(item.ItemGroupName); + + public GameObject GetGroupParent(string name) { // create group parent if not created (if null, attach it to the table directly). - if (!string.IsNullOrEmpty(item.ItemGroupName)) { - if (!_groupParents.ContainsKey(item.ItemGroupName)) { - var parent = new GameObject(item.ItemGroupName); + if (!string.IsNullOrEmpty(name)) { + if (!_groupParents.ContainsKey(name)) { + var parent = new GameObject(name); parent.transform.SetParent(_playfieldGo.transform, false); - _groupParents[item.ItemGroupName] = parent; + _groupParents[name] = parent; } } - var groupParent = !string.IsNullOrEmpty(item.ItemGroupName) - ? _groupParents[item.ItemGroupName] + var groupParent = !string.IsNullOrEmpty(name) + ? _groupParents[name] : _playfieldGo; return groupParent; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Toolbox/ToolboxEditor.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Toolbox/ToolboxEditor.cs index 0e1db2409..93e96edb1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Toolbox/ToolboxEditor.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Toolbox/ToolboxEditor.cs @@ -167,6 +167,10 @@ private void OnGUI() CreateItem(Trough.GetDefault, "New Trough"); } + if (CreateButton("Slingshot", Icons.Slingshot(color: iconColor), iconSize, buttonStyle)) { + CreatePrefab("Slingshots", "Prefabs/Slingshot"); + } + GUILayout.EndHorizontal(); } @@ -196,5 +200,20 @@ private GameObject CreateRenderable(IItem item) TableComponent.TableContainer.Refresh(); return converter.InstantiateAndPersistPrefab(item).GameObject; } + + private void CreatePrefab(string groupName, string path) + { + var converter = new VpxSceneConverter(TableComponent); + TableComponent.TableContainer.Refresh(); + + var parentGo = converter.GetGroupParent(groupName); + + var prefab = Resources.Load(path); + var go = PrefabUtility.InstantiatePrefab(prefab) as GameObject; + if (go) { + go.transform.SetParent(parentGo.transform, false); + } + Selection.activeGameObject = go; + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs index 980fd8482..a23abd9e2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs @@ -54,6 +54,7 @@ public IconVariant(string name, IconSize size, IconColor color) private const string RubberName = "rubber"; private const string SpinnerName = "spinner"; private const string SurfaceName = "surface"; + private const string SlingshotName = "slingshot"; private const string TableName = "table"; private const string TriggerName = "trigger"; private const string TroughName = "trough"; @@ -63,7 +64,7 @@ public IconVariant(string name, IconSize size, IconColor color) private static readonly string[] Names = { BumperName, BoltName, CoilName, DropTargetName, FlipperName, HitTargetName, GateName, KeyName, KickerName, LightName, PlayfieldName, PlungerName, PlugName, PrimitiveName, RampName, RubberName, SpinnerName, SurfaceName, TableName, TriggerName, TroughName, - SwitchNcName, SwitchNoName + SlingshotName, SwitchNcName, SwitchNoName }; private readonly Dictionary _icons = new Dictionary(); @@ -76,7 +77,7 @@ public IconVariant(string name, IconSize size, IconColor color) private static readonly int MonoBehaviourClassID = 114; private static Icons _instance; - private static Icons Instance => _instance ?? (_instance = new Icons()); + private static Icons Instance => _instance ??= new Icons(); private Icons() { @@ -112,6 +113,7 @@ private Icons() public static Texture2D Table(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(TableName, size, color); public static Texture2D Trigger(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(TriggerName, size, color); public static Texture2D Trough(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(TroughName, size, color); + public static Texture2D Slingshot(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(SlingshotName, size, color); public static Texture2D Switch(bool isClosed, IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(isClosed ? SwitchNcName : SwitchNoName, size, color); public static Texture2D Coil(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(CoilName, size, color); public static Texture2D Key(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(KeyName, size, color); @@ -134,6 +136,7 @@ public static Texture2D ByComponent(T mb, IconSize size = IconSize.Large, Ico case RampComponent _: return Ramp(size, color); case RubberComponent _: return Rubber(size, color); case SpinnerComponent _: return Spinner(size, color); + case SlingshotComponent _: return Slingshot(size, color); case SurfaceComponent _: return Surface(size, color); case TriggerComponent _: return Trigger(size, color); case TroughComponent _: return Trough(size, color); @@ -186,6 +189,7 @@ public static void DisableGizmoIcons() DisableGizmo(); DisableGizmo(); DisableGizmo(); + DisableGizmo(); DisableGizmo(); DisableGizmo(); DisableGizmo(); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs index 2e539bba7..9ee20146a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SlingshotInspector.cs @@ -22,13 +22,15 @@ namespace VisualPinball.Unity.Editor [CustomEditor(typeof(SlingshotComponent))] public class SlingshotInspector : ItemInspector { - private SlingshotComponent _slingShot; + private SlingshotComponent _slingshot; private SerializedProperty _surfaceProperty; private SerializedProperty _rubberOffProperty; private SerializedProperty _rubberOnProperty; private SerializedProperty _animationDurationProperty; private SerializedProperty _animationCurveProperty; + private SerializedProperty _coilArmProperty; + private SerializedProperty _coilArmAngleProperty; protected override MonoBehaviour UndoTarget => target as MonoBehaviour; @@ -36,11 +38,13 @@ protected override void OnEnable() { base.OnEnable(); - _slingShot = target as SlingshotComponent; + _slingshot = target as SlingshotComponent; _surfaceProperty = serializedObject.FindProperty(nameof(SlingshotComponent.SlingshotSurface)); _rubberOffProperty = serializedObject.FindProperty(nameof(SlingshotComponent.RubberOff)); _rubberOnProperty = serializedObject.FindProperty(nameof(SlingshotComponent.RubberOn)); + _coilArmProperty = serializedObject.FindProperty(nameof(SlingshotComponent.CoilArm)); + _coilArmAngleProperty = serializedObject.FindProperty(nameof(SlingshotComponent.CoilArmAngle)); _animationDurationProperty = serializedObject.FindProperty(nameof(SlingshotComponent.AnimationDuration)); _animationCurveProperty = serializedObject.FindProperty(nameof(SlingshotComponent.AnimationCurve)); } @@ -55,21 +59,25 @@ public override void OnInspectorGUI() PropertyField(_rubberOffProperty, "Rubber Off", true); PropertyField(_rubberOnProperty, "Rubber On", true); - if (_slingShot.RubberOn && _slingShot.RubberOff && - _slingShot.RubberOn.DragPoints.Length != _slingShot.RubberOff.DragPoints.Length) { - EditorGUILayout.HelpBox($"In order to animate the rubber, the number of drag points of both rubbers must be equal. Here we have {_slingShot.RubberOn.DragPoints.Length} (on) and {_slingShot.RubberOff.DragPoints.Length} (off).", MessageType.Error); + if (_slingshot.RubberOn && _slingshot.RubberOff && + _slingshot.RubberOn.DragPoints.Length != _slingshot.RubberOff.DragPoints.Length) { + EditorGUILayout.HelpBox($"In order to animate the rubber, the number of drag points of both rubbers must be equal. Here we have {_slingshot.RubberOn.DragPoints.Length} (on) and {_slingshot.RubberOff.DragPoints.Length} (off).", MessageType.Error); } + EditorGUILayout.Space(10f); + PropertyField(_coilArmProperty, "Coil Arm"); + PropertyField(_coilArmAngleProperty, "Arm Angle"); + EditorGUILayout.Space(10f); PropertyField(_animationDurationProperty, "Animation Duration"); PropertyField(_animationCurveProperty, "Animation Curve"); EditorGUILayout.Space(10f); EditorGUI.BeginChangeCheck(); - var pos = EditorGUILayout.Slider("Test", _slingShot.Position, 0f, 1f); + var pos = EditorGUILayout.Slider("Test", _slingshot.Position, 0f, 1f); if (EditorGUI.EndChangeCheck()) { - _slingShot.Position = pos; - _slingShot.RebuildMeshes(); + _slingshot.Position = pos; + _slingshot.RebuildMeshes(); } base.OnInspectorGUI(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs index 6b520b190..7670e4d91 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs @@ -39,8 +39,16 @@ public class SlingshotComponent : MonoBehaviour, IMeshComponent, IMainRenderable [Tooltip("Reference to the rubber at \"disabled\" position (coil off).")] public RubberComponent RubberOff; + [Tooltip("Reference to the arm attached to the coil. Rotates around X.")] + public PrimitiveComponent CoilArm; + + [Range(-180f, 180f)] + [Tooltip("Angle of the coil arm when on.")] + public float CoilArmAngle; + + [Min(0f)] [Tooltip("Total duration of the animation in milliseconds.")] - public float AnimationDuration = 200f; + public float AnimationDuration = 70f; [Tooltip("Animation curve. Starts at 0 and ends at 0.")] public AnimationCurve AnimationCurve = new AnimationCurve( @@ -53,16 +61,12 @@ public class SlingshotComponent : MonoBehaviour, IMeshComponent, IMainRenderable [SerializeField] private bool _isLocked; [NonSerialized] private readonly Dictionary _meshes = new Dictionary(); [NonSerialized] private RubberMeshGenerator _meshGenerator; + private RubberMeshGenerator MeshGenerator => _meshGenerator ??= new RubberMeshGenerator(this); private const int MaxNumMeshCaches = 15; #region Runtime - private void Awake() - { - _meshGenerator = new RubberMeshGenerator(this); - } - private void Start() { var player = GetComponentInParent(); @@ -128,22 +132,36 @@ private IEnumerator Animate() public void RebuildMeshes() { var mf = GetComponent(); - if (!mf) { + var mr = GetComponent(); + if (!mf || !mr) { Debug.LogWarning("Mesh filter or renderer not found."); return; } + // mesh var mesh = GetMesh(); - if (mesh != null) { mf.sharedMesh = mesh; } + + // material + if (RubberOff && !mr.sharedMaterial) { + var rubberMr = RubberOff.GetComponent(); + if (rubberMr) { + mr.sharedMaterial = rubberMr.sharedMaterial; + } + } + + if (CoilArm) { + CoilArm.Rotation.x = CoilArmAngle * Position; + CoilArm.UpdateTransforms(); + } } private Mesh GetMesh() { var pos = (int)(Position * MaxNumMeshCaches); - if (_meshes.ContainsKey(pos)) { + if (Application.isPlaying && _meshes.ContainsKey(pos)) { return _meshes[pos]; } @@ -159,7 +177,7 @@ private Mesh GetMesh() Debug.Log($"Generating new mesh at {pos}"); - var mesh = _meshGenerator + var mesh = MeshGenerator .GetTransformedMesh(pf.PlayfieldHeight, r0.Height, pf.PlayfieldDetailLevel) .ToUnityMesh(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs.meta index a4e746f87..6ec07deb3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs.meta +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs.meta @@ -5,7 +5,7 @@ MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 - icon: {instanceID: 0} + icon: {fileID: 2800000, guid: a01c1f02db832b64bbc574fddbde82a1, type: 3} userData: assetBundleName: assetBundleVariant: From 7b7b9f5a9dea56136d3abea6d5593b1704151ec8 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 2 Oct 2021 01:09:09 +0200 Subject: [PATCH 10/15] sling: Add prefab. --- .../Assets/Resources/Prefabs/Slingshot.prefab | 136 ++++++++++++++++++ .../Resources/Prefabs/Slingshot.prefab.meta | 7 + 2 files changed, 143 insertions(+) create mode 100644 VisualPinball.Unity/Assets/Resources/Prefabs/Slingshot.prefab create mode 100644 VisualPinball.Unity/Assets/Resources/Prefabs/Slingshot.prefab.meta diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Slingshot.prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Slingshot.prefab new file mode 100644 index 000000000..6fa531177 --- /dev/null +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Slingshot.prefab @@ -0,0 +1,136 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &6110464775177654975 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1604346372501559456} + - component: {fileID: 8345816335357486725} + - component: {fileID: 4498014268656306279} + - component: {fileID: 1260714541426936682} + m_Layer: 0 + m_Name: Slingshot + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1604346372501559456 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6110464775177654975} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &8345816335357486725 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6110464775177654975} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8840a3e9d44a5a443a20087784eae747, type: 3} + m_Name: + m_EditorClassIdentifier: + SlingshotSurface: {fileID: 0} + RubberOn: {fileID: 0} + RubberOff: {fileID: 0} + CoilArm: {fileID: 0} + CoilArmAngle: 0 + AnimationDuration: 70 + AnimationCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 0.5 + value: 1 + inSlope: 3.535 + outSlope: 0 + tangentMode: 0 + weightedMode: 3 + inWeight: 0.03333336 + outWeight: 0.5416666 + - serializedVersion: 3 + time: 1 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + _isLocked: 0 +--- !u!33 &4498014268656306279 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6110464775177654975} + m_Mesh: {fileID: 0} +--- !u!23 &1260714541426936682 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6110464775177654975} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 257 + m_RendererPriority: 0 + m_Materials: + - {fileID: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Slingshot.prefab.meta b/VisualPinball.Unity/Assets/Resources/Prefabs/Slingshot.prefab.meta new file mode 100644 index 000000000..8827ba025 --- /dev/null +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Slingshot.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c557a5a88939f4f48ab972272730aa0b +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From b555d1977afb6b070c14fe56059e094d626173e3 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 2 Oct 2021 13:25:20 +0200 Subject: [PATCH 11/15] sling: Add screenshots to documentation. --- .../manual/mechanisms/slingshot-curve.png | Bin 0 -> 11506 bytes .../manual/mechanisms/slingshot-inspector.png | Bin 0 -> 29906 bytes .../manual/mechanisms/slingshots.md | 28 +++++++++++++++--- 3 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshot-curve.png create mode 100644 VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshot-inspector.png diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshot-curve.png b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshot-curve.png new file mode 100644 index 0000000000000000000000000000000000000000..87f2da1968dc7ab3ebc2bbe1022e888b574675fc GIT binary patch literal 11506 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 +### Slingshot Wall -## Rubbers +In order to trigger the animation, the slingshot component needs a reference to the wall which has one of the control points set to *slingshot*. + +### Rubbers + +VPE animates the control points of the rubber from a start position to an end position and back to start. It does that by interpolating the positions from start to end depending on the frame and the duration of the animation. + +To set the start and end positions of the control points, we reference two rubber elements which need to have the same number of control points. In the inspector, *Rubber Off* references the rubber representing the start position, while *Rubber On* goes to the end position. > [!NOTE] > In VPX, tables often come with three rubbers elements that are toggled in order to fake an animation. When using VPE's slingshot component, you can delete the rubber at mid position, since only the start and end rubbers are used. The interpolation is calculated in real time depending on the speed of the slingshot. +### Coil Arm + +On physical machines, the rubber is moved by an arm attached to the coil. VPE can simulate the movement of that arm by rotating a primitive across the X-axis. In the *Coil Arm* field, a reference to the primive can be set, and the angle at and position under *Arm Angle*. + +Slingshot Animation Curve + +### Animation + +The animation has two parameters: The duration in milliseconds, and the curve. The curve represents the position in function of time. It covers both ways. This allows non-linear movement of the rubber. + +### Test + +With the test slider you can easily verify how the rubber and arm is animated. -# Howtos +# Howto ## Set Up a Slingshot from an Imported Table @@ -83,6 +103,6 @@ Before we test, there are two things left to do: 1. Select the coil arm and disable the collider 2. Enable the plastic we've hidden in step 1. -Then hit test and have a game! +Then hit play and have a game! > [!Video https://www.youtube.com/embed/dNS4YPdRXTc] From 30631279de835571ef01b92f2b13cfb1935e566e Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 3 Oct 2021 00:01:03 +0200 Subject: [PATCH 12/15] dragpoints: Fix persitence when adding points. --- .../DragPoint/DragPointsHandler.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs index a36e084e5..845155435 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs @@ -20,6 +20,7 @@ using UnityEditor; using UnityEngine; using VisualPinball.Engine.Math; +using Color = UnityEngine.Color; using Object = UnityEngine.Object; namespace VisualPinball.Unity.Editor @@ -31,17 +32,17 @@ public class DragPointsHandler /// /// Component /// - public IMainRenderableComponent MainComponent { get; private set; } + public IMainRenderableComponent MainComponent { get; } /// /// Component item as IDragPointsEditable /// - public IDragPointsInspector DragPointInspector { get; private set; } + public IDragPointsInspector DragPointInspector { get; } /// /// Transform component of the game object /// - public Transform Transform { get; private set; } + public Transform Transform { get; } /// /// Control points storing & rendering @@ -55,7 +56,7 @@ public class DragPointsHandler /// /// Will handle all the rendering part and update some handler's variables about curve traveller /// - private readonly DragPointsSceneViewHandler _sceneViewHandler = null; + private readonly DragPointsSceneViewHandler _sceneViewHandler; /// /// Drag points selection @@ -72,7 +73,7 @@ public class DragPointsHandler /// public int CurveTravellerControlId { get; private set; } public Vector3 CurveTravellerPosition { get; set; } = Vector3.zero; - public bool CurveTravellerVisible { get; set; } = false; + public bool CurveTravellerVisible { get; set; } public int CurveTravellerControlPointIdx { get; set; } = -1; /// @@ -95,8 +96,8 @@ public DragPointsHandler(IMainRenderableComponent mainComponent, IDragPointsInsp _sceneViewHandler = new DragPointsSceneViewHandler(this){ CurveWidth = 10.0f, - CurveColor = UnityEngine.Color.blue, - CurveSlingShotColor = UnityEngine.Color.red, + CurveColor = Color.blue, + CurveSlingShotColor = Color.red, ControlPointsSizeRatio = 1.0f, CurveTravellerSizeRatio = 0.75f }; @@ -258,6 +259,10 @@ private void RebuildControlPoints() ControlPoints.Add(cp); } CurveTravellerControlId = GUIUtility.GetControlID(FocusType.Passive); + + // persist prefab changes + EditorUtility.SetDirty(MainComponent.gameObject); + PrefabUtility.RecordPrefabInstancePropertyModifications(MainComponent as Object); } /// From 3f977bda89c33ba79323191bd27143fc5869573c Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 3 Oct 2021 00:31:59 +0200 Subject: [PATCH 13/15] test: Add coverage for drag point lerping. --- VisualPinball.Engine.Test/Math/VectorTests.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/VisualPinball.Engine.Test/Math/VectorTests.cs b/VisualPinball.Engine.Test/Math/VectorTests.cs index 9c2f9bf50..e24ef006c 100644 --- a/VisualPinball.Engine.Test/Math/VectorTests.cs +++ b/VisualPinball.Engine.Test/Math/VectorTests.cs @@ -51,5 +51,15 @@ public void ShouldCorrectlyCrossVectors() .Should().BeEquivalentTo(new Vertex3D(-162.5f, -128.5f, 141.25f)); } + [Test] + public void ShouldCorrectlyLerpDragPoints() + { + var dp0 = new DragPointData(2f, 3f); + var dp1 = new DragPointData(8f, 12f); + + dp0.Lerp(dp1, 0).Center.Should().Be(new Vertex3D(2f, 3f, 0f)); + dp0.Lerp(dp1, 0.2f).Center.Should().Be(new Vertex3D(3.2f, 4.8f, 0f)); + dp0.Lerp(dp1, 1).Center.Should().Be(new Vertex3D(8f, 12f, 0f)); + } } } From 9a35ea58253263765d3c5040094db60cf4790078 Mon Sep 17 00:00:00 2001 From: Eli Curtz Date: Sun, 3 Oct 2021 11:25:26 -0700 Subject: [PATCH 14/15] Editing pass on slingshot documentation --- .../manual/mechanisms/slingshots.md | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshots.md b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshots.md index e7c7f1bdf..2d816fd7e 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshots.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshots.md @@ -1,10 +1,10 @@ # Slingshots -Slinghots are usually located above the flippers. They consist of two blade switches that are located at the inner side of a triangle-shaped rubber. Between the switches there is a coil that propellers the ball in the opposite direction when either switch closes. +Slingshots are most commonly located just above the flippers. They usually consist of two "blade" switches on the inner side of a triangular rubber. Between the switches there is a coil driven arm that propellers the ball away from the slingshot when either switch closes. -Visual Pinball doesn't have an explicit slingshot element. Instead, it relies on walls, where a segment can be marked as *slingshot*, with the effect of an additional force being applied to the ball when the segment is hit. However, the rubber animation is up to the table script to implement. +Visual Pinball doesn't have an explicit slingshot element. Instead, it relies on walls with a segment marked as *slingshot*, which generates an additional force being applied to the ball when the segment is hit. However, the rubber animation is up to the table script to implement. -VPE provides a slingshot component that implements the rubber animation during runtime. This allows for functional slingshots without any additional code. Note however that this approach isn't ideal and will be replaced with a proper slingshot element in the future. +VPE does provide a slingshot component that implements the rubber animation during runtime. This allows for functional slingshots without any additional code. However this approach isn't ideal and will be replaced with a proper slingshot element in the future. # Setup @@ -17,43 +17,43 @@ In order to trigger the animation, the slingshot component needs a reference to ### Rubbers -VPE animates the control points of the rubber from a start position to an end position and back to start. It does that by interpolating the positions from start to end depending on the frame and the duration of the animation. +VPE animates the control points of the rubber by interpolating between a start position to an end position and then back to the start over the duration of the animation. -To set the start and end positions of the control points, we reference two rubber elements which need to have the same number of control points. In the inspector, *Rubber Off* references the rubber representing the start position, while *Rubber On* goes to the end position. +To set the start and end positions of the control points, we reference two rubber elements (which must have the same number of control points.) In the inspector, *Rubber Off* indicates the rubber representing the start position, and *Rubber On* the end position. > [!NOTE] > In VPX, tables often come with three rubbers elements that are toggled in order to fake an animation. When using VPE's slingshot component, you can delete the rubber at mid position, since only the start and end rubbers are used. The interpolation is calculated in real time depending on the speed of the slingshot. ### Coil Arm -On physical machines, the rubber is moved by an arm attached to the coil. VPE can simulate the movement of that arm by rotating a primitive across the X-axis. In the *Coil Arm* field, a reference to the primive can be set, and the angle at and position under *Arm Angle*. +On physical machines, the rubber is moved by an arm attached to the coil. VPE can simulate the movement of that arm by rotating a primitive across the X-axis. In the *Coil Arm* field, a reference to the primive can be set, and the total angle of rotation under *Arm Angle*. Slingshot Animation Curve ### Animation -The animation has two parameters: The duration in milliseconds, and the curve. The curve represents the position in function of time. It covers both ways. This allows non-linear movement of the rubber. +The animation has two parameters: The duration in milliseconds, and the curve. The curve represents the position in function of time and covers both directions of travel. This allows non-linear movement of the rubber. ### Test -With the test slider you can easily verify how the rubber and arm is animated. +With the test slider you can easily preview how the rubber and arm are animated. # Howto ## Set Up a Slingshot from an Imported Table -This howto uses the blank table, but other tables should be quite similar. Usually, slingshots consist of three rubbers for the animation, plus a wall for the physics. What we need is the following: +This howto uses the blank table, but other tables should be similar. Usually, slingshots consist of three rubbers for the animation, plus a wall for the physics. We need the following elements: 1. The wall with a segment set to *slingshot* 2. The rubber at idle position 3. The rubber at *activated* position -4. Optionally the coil arm that pushes the rubber +4. Optionally, the coil arm that pushes the rubber -Note that both rubbers must have the same number of drag points. This is because during the animation, the rubber is linearly interpolated between the two drag points positions, which isn't possible if the number differs. When converting a table from Visual Pinball, that means that you most probably need to add two additional drag points to the rubber at idle position. +Note that both rubbers *must* have the same number of drag points. This is because during the animation, the rubber is linearly interpolated between the two drag points positions, which isn't possible if the number differs. When converting a table from Visual Pinball, that means that you may need to add additional drag points to the rubber at idle position. ### 1. Identify and clean up the elements -Zoom in to the slingshot you want to set up. You'll probably want to temporily hide the plastic that covers up the rubbers and the wall. Find the rubber at idle position and at activated position. Delete the rubber in-between, we don't need that one because VPE automatically interpolates between the two depending on the duration of the animation. +Zoom in to the slingshot you want to set up. You'll probably want to temporily hide the plastic that covers up the rubbers and the wall. Find the rubber at idle position and at activated position. Delete the rubber in-between, we don't need that one. Note the names of those rubbers. Here it's `LSling` and `LSling1`. Also look for the the coil arm, which is called `Sling2`, as well as wall that acts as the physical slingshot, here `LeftSlingShot`. @@ -77,14 +77,14 @@ In the Toolbox, click on the *Slingshot* icon, which will create a new element i - *Rubber Off* links to `LSling` - *Rubber On* links to `LSling1` -The slingshot component is now able to create the mesh. The animation can be tested with the *Test* slide. +The slingshot component is now able to create the mesh. The animation can be tested with the *Test* slider. > [!Video https://www.youtube.com/embed/421gesRScYo] ### 4. Setup the coil arm animation -Since the rubber is pushed inside by the arm, the arm should be animated along with the rubber. This can be a bit fiddly, since the arm should be as close to the rubber without clipping through it. +Since the rubber is pushed inside by the arm, the arm should be animated along with the rubber. This can be a bit fiddly, since the arm should be as close to the rubber as possible without clipping through it. Move the *Test* slider all to the right, and play with the *X-Rotation* of the coil arm. Once you're happy, copy the angle to the clipboard. Then, select the slingshot. In the inspector, set the following fields: @@ -98,10 +98,10 @@ Now, when moving the *Test* slider, the arm should animate along with the rubber ### 5. Wrap-up and test -Before we test, there are two things left to do: +Before final testing, there are two things left to do: 1. Select the coil arm and disable the collider -2. Enable the plastic we've hidden in step 1. +2. Enable the plastic we hid in step 1. Then hit play and have a game! From 8bce4098b6ac5baae064ccf9c1ae0250a4701e36 Mon Sep 17 00:00:00 2001 From: Eli Curtz Date: Sun, 3 Oct 2021 11:30:13 -0700 Subject: [PATCH 15/15] Dumb typo added in first pass --- .../creators-guide/manual/mechanisms/slingshots.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshots.md b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshots.md index 2d816fd7e..415025a41 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshots.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshots.md @@ -1,6 +1,6 @@ # Slingshots -Slingshots are most commonly located just above the flippers. They usually consist of two "blade" switches on the inner side of a triangular rubber. Between the switches there is a coil driven arm that propellers the ball away from the slingshot when either switch closes. +Slingshots are most commonly located just above the flippers. They usually consist of two "blade" switches on the inner side of a triangular rubber. Between the switches there is a coil driven arm that propels the ball away from the slingshot when either switch closes. Visual Pinball doesn't have an explicit slingshot element. Instead, it relies on walls with a segment marked as *slingshot*, which generates an additional force being applied to the ball when the segment is hit. However, the rubber animation is up to the table script to implement.