diff --git a/Assets/Prefabs/Match.prefab b/Assets/Prefabs/Match.prefab index c0ef0b8..292984e 100644 --- a/Assets/Prefabs/Match.prefab +++ b/Assets/Prefabs/Match.prefab @@ -24,7 +24,7 @@ GameObject: m_Component: - component: {fileID: 8523065194936625137} - component: {fileID: 8523065194936625138} - - component: {fileID: 8839065093546782790} + - component: {fileID: 5940000930525792920} m_Layer: 0 m_Name: Match m_TagString: Untagged @@ -59,7 +59,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: cd85f814f92a48f7bf516ac06983ab78, type: 3} m_Name: m_EditorClassIdentifier: ---- !u!114 &8839065093546782790 +--- !u!114 &5940000930525792920 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -68,9 +68,13 @@ MonoBehaviour: m_GameObject: {fileID: 8523065194936625136} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: a115767594c2aa643b4a85719c052d96, type: 3} + m_Script: {fileID: 11500000, guid: 29aab9a7c4fc6a04e854dffd2cf68812, type: 3} m_Name: m_EditorClassIdentifier: + pastPosition: {x: 0, y: 0, z: 0} + currentPosition: {x: 0, y: 0, z: 0} + wrapContactables: [] + radius: 0.1 --- !u!1001 &8523065195629964848 PrefabInstance: m_ObjectHideFlags: 0 @@ -78,11 +82,6 @@ PrefabInstance: m_Modification: m_TransformParent: {fileID: 8523065194936625137} m_Modifications: - - target: {fileID: -927199367670048503, guid: abe6dda665f9e2f4bb9fa774c916e053, - type: 3} - propertyPath: m_Name - value: Mesh - objectReference: {fileID: 0} - target: {fileID: -4216859302048453862, guid: abe6dda665f9e2f4bb9fa774c916e053, type: 3} propertyPath: m_LocalPosition.x @@ -158,6 +157,11 @@ PrefabInstance: propertyPath: m_Materials.Array.data[0] value: objectReference: {fileID: 2100000, guid: 87c8d427e891ff14c8a9fc1b5571c399, type: 2} + - target: {fileID: -927199367670048503, guid: abe6dda665f9e2f4bb9fa774c916e053, + type: 3} + propertyPath: m_Name + value: Mesh + objectReference: {fileID: 0} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: abe6dda665f9e2f4bb9fa774c916e053, type: 3} --- !u!1 &390106403564140345 stripped diff --git a/Assets/Prefabs/Nail.prefab b/Assets/Prefabs/Nail.prefab index 375e932..0e1965b 100644 --- a/Assets/Prefabs/Nail.prefab +++ b/Assets/Prefabs/Nail.prefab @@ -10,7 +10,7 @@ GameObject: m_Component: - component: {fileID: 1293638319414553333} - component: {fileID: 1293638319414553339} - - component: {fileID: 2956844451186357919} + - component: {fileID: 2226853645861646875} m_Layer: 0 m_Name: Nail m_TagString: Untagged @@ -45,7 +45,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 1e4e97f2fe5cdf848bd6fe3be8acf8dd, type: 3} m_Name: m_EditorClassIdentifier: ---- !u!114 &2956844451186357919 +--- !u!114 &2226853645861646875 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -54,9 +54,13 @@ MonoBehaviour: m_GameObject: {fileID: 1293638319414553334} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: a115767594c2aa643b4a85719c052d96, type: 3} + m_Script: {fileID: 11500000, guid: 29aab9a7c4fc6a04e854dffd2cf68812, type: 3} m_Name: m_EditorClassIdentifier: + pastPosition: {x: 0, y: 0, z: 0} + currentPosition: {x: 0, y: 0, z: 0} + wrapContactables: [] + radius: 0.1 --- !u!136 &7231737498855535285 CapsuleCollider: m_ObjectHideFlags: 0 @@ -92,11 +96,6 @@ PrefabInstance: m_Modification: m_TransformParent: {fileID: 1293638319414553333} m_Modifications: - - target: {fileID: -927199367670048503, guid: e7221bb4299f2d9418bc4bf09d1a609f, - type: 3} - propertyPath: m_Name - value: Mesh - objectReference: {fileID: 0} - target: {fileID: -4216859302048453862, guid: e7221bb4299f2d9418bc4bf09d1a609f, type: 3} propertyPath: m_LocalPosition.x @@ -172,6 +171,11 @@ PrefabInstance: propertyPath: m_Materials.Array.data[0] value: objectReference: {fileID: 2100000, guid: 1fa75e2ebacf4c243999e3f7628e4373, type: 2} + - target: {fileID: -927199367670048503, guid: e7221bb4299f2d9418bc4bf09d1a609f, + type: 3} + propertyPath: m_Name + value: Mesh + objectReference: {fileID: 0} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: e7221bb4299f2d9418bc4bf09d1a609f, type: 3} --- !u!1 &7120754119071130942 stripped diff --git a/Assets/Prefabs/Needle.prefab b/Assets/Prefabs/Needle.prefab index 34a88e6..cf9e7f5 100644 --- a/Assets/Prefabs/Needle.prefab +++ b/Assets/Prefabs/Needle.prefab @@ -38,7 +38,8 @@ GameObject: m_Component: - component: {fileID: 8515438956722547815} - component: {fileID: 8515438956722547808} - - component: {fileID: 6464732955907209868} + - component: {fileID: 6561178820540645814} + - component: {fileID: 7689935896013740308} m_Layer: 0 m_Name: Needle m_TagString: Untagged @@ -73,7 +74,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 98e1c197ea674b5a9a129fd58e6579f2, type: 3} m_Name: m_EditorClassIdentifier: ---- !u!114 &6464732955907209868 +--- !u!114 &6561178820540645814 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -82,9 +83,29 @@ MonoBehaviour: m_GameObject: {fileID: 8515438956722547814} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: a115767594c2aa643b4a85719c052d96, type: 3} + m_Script: {fileID: 11500000, guid: 29aab9a7c4fc6a04e854dffd2cf68812, type: 3} m_Name: m_EditorClassIdentifier: + pastPosition: {x: 0, y: 0, z: 0} + currentPosition: {x: 0, y: 0, z: 0} + wrapContactables: [] + radius: 0.1 +--- !u!114 &7689935896013740308 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8515438956722547814} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: cb3afdf96468fd04b95c0148819ca2d6, type: 3} + m_Name: + m_EditorClassIdentifier: + pastPosition: {x: 0, y: 0, z: 0} + currentPosition: {x: 0, y: 0, z: 0} + wrapContactables: [] + pointOffset: {x: 0, y: 0, z: 0} --- !u!1001 &8515438957613842491 PrefabInstance: m_ObjectHideFlags: 0 @@ -92,11 +113,6 @@ PrefabInstance: m_Modification: m_TransformParent: {fileID: 8515438956722547815} m_Modifications: - - target: {fileID: -927199367670048503, guid: 8ed1fafe755612547a4b526e9c6a946d, - type: 3} - propertyPath: m_Name - value: Mesh - objectReference: {fileID: 0} - target: {fileID: -4216859302048453862, guid: 8ed1fafe755612547a4b526e9c6a946d, type: 3} propertyPath: m_LocalPosition.x @@ -162,6 +178,11 @@ PrefabInstance: propertyPath: m_Materials.Array.data[0] value: objectReference: {fileID: 2100000, guid: 1fa75e2ebacf4c243999e3f7628e4373, type: 2} + - target: {fileID: -927199367670048503, guid: 8ed1fafe755612547a4b526e9c6a946d, + type: 3} + propertyPath: m_Name + value: Mesh + objectReference: {fileID: 0} - target: {fileID: 4063835366044962077, guid: 8ed1fafe755612547a4b526e9c6a946d, type: 3} propertyPath: m_Materials.Array.data[0] diff --git a/Assets/Prefabs/Player.prefab b/Assets/Prefabs/Player.prefab index 0999ff6..22f9f69 100644 --- a/Assets/Prefabs/Player.prefab +++ b/Assets/Prefabs/Player.prefab @@ -9,12 +9,13 @@ GameObject: serializedVersion: 6 m_Component: - component: {fileID: 8072518499017028993} - - component: {fileID: 8072518499017028992} - component: {fileID: 8072518499017028995} - component: {fileID: 8072518499017028994} + - component: {fileID: 8072518499017028992} - component: {fileID: 8072518499017028997} - component: {fileID: 8072518499017028996} - component: {fileID: 8072518499017028999} + - component: {fileID: 2313245474546743258} m_Layer: 0 m_Name: Player m_TagString: Player @@ -37,20 +38,6 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!114 &8072518499017028992 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 8072518499017028998} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 0daccf5fb693e6b4b817a62deb9df3db, type: 3} - m_Name: - m_EditorClassIdentifier: - normalMesh: {fileID: 227068772183194632} - holdingMesh: {fileID: 227068771119847383} --- !u!54 &8072518499017028995 Rigidbody: m_ObjectHideFlags: 0 @@ -81,6 +68,20 @@ CapsuleCollider: m_Height: 2 m_Direction: 1 m_Center: {x: 0, y: 0, z: 0} +--- !u!114 &8072518499017028992 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8072518499017028998} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0daccf5fb693e6b4b817a62deb9df3db, type: 3} + m_Name: + m_EditorClassIdentifier: + normalMesh: {fileID: 227068772183194632} + holdingMesh: {fileID: 227068771119847383} --- !u!114 &8072518499017028997 MonoBehaviour: m_ObjectHideFlags: 0 @@ -205,6 +206,22 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: a79d0552d8ac375449d5c8b767c78682, type: 3} m_Name: m_EditorClassIdentifier: +--- !u!114 &2313245474546743258 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8072518499017028998} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: ab040f71654faec4c811af8fee2ae1c4, type: 3} + m_Name: + m_EditorClassIdentifier: + pastPosition: {x: 0, y: 0, z: 0} + currentPosition: {x: 0, y: 0, z: 0} + wrapContactables: [] + pointOffset: {x: 0, y: 0, z: 0} --- !u!1 &8072518499020361757 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Prefabs/Pushpin.prefab b/Assets/Prefabs/Pushpin.prefab index 9377194..a6cbb9a 100644 --- a/Assets/Prefabs/Pushpin.prefab +++ b/Assets/Prefabs/Pushpin.prefab @@ -24,7 +24,8 @@ GameObject: m_Component: - component: {fileID: 6461784659212241894} - component: {fileID: 6461784659212241895} - - component: {fileID: 2124173856722540634} + - component: {fileID: 4833415352386671245} + - component: {fileID: 1545922046492766045} m_Layer: 0 m_Name: Pushpin m_TagString: Pushpin @@ -59,7 +60,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 75ae2d8f4608499fbbfc9f480d1f0a73, type: 3} m_Name: m_EditorClassIdentifier: ---- !u!114 &2124173856722540634 +--- !u!114 &4833415352386671245 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -68,9 +69,29 @@ MonoBehaviour: m_GameObject: {fileID: 6461784659212241893} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: a115767594c2aa643b4a85719c052d96, type: 3} + m_Script: {fileID: 11500000, guid: 29aab9a7c4fc6a04e854dffd2cf68812, type: 3} m_Name: m_EditorClassIdentifier: + pastPosition: {x: 0, y: 0, z: 0} + currentPosition: {x: 0, y: 0, z: 0} + wrapContactables: [] + radius: 0.1 +--- !u!114 &1545922046492766045 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6461784659212241893} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: cb3afdf96468fd04b95c0148819ca2d6, type: 3} + m_Name: + m_EditorClassIdentifier: + pastPosition: {x: 0, y: 0, z: 0} + currentPosition: {x: 0, y: 0, z: 0} + wrapContactables: [] + pointOffset: {x: 0, y: 0, z: 0} --- !u!1001 &6461784659076987962 PrefabInstance: m_ObjectHideFlags: 0 @@ -78,11 +99,11 @@ PrefabInstance: m_Modification: m_TransformParent: {fileID: 6461784659212241894} m_Modifications: - - target: {fileID: -927199367670048503, guid: e1d1a8f7ba95e0f4f91d68f542216319, + - target: {fileID: -6054058774237248251, guid: e1d1a8f7ba95e0f4f91d68f542216319, type: 3} - propertyPath: m_Name - value: Mesh - objectReference: {fileID: 0} + propertyPath: m_Materials.Array.data[0] + value: + objectReference: {fileID: 2100000, guid: fc883cb8b3fd8fa4a9c4b19e725146c3, type: 2} - target: {fileID: -4216859302048453862, guid: e1d1a8f7ba95e0f4f91d68f542216319, type: 3} propertyPath: m_LocalPosition.x @@ -153,16 +174,16 @@ PrefabInstance: propertyPath: m_LocalScale.z value: 0.7 objectReference: {fileID: 0} + - target: {fileID: -927199367670048503, guid: e1d1a8f7ba95e0f4f91d68f542216319, + type: 3} + propertyPath: m_Name + value: Mesh + objectReference: {fileID: 0} - target: {fileID: 4063835366044962077, guid: e1d1a8f7ba95e0f4f91d68f542216319, type: 3} propertyPath: m_Materials.Array.data[0] value: objectReference: {fileID: 2100000, guid: 1fa75e2ebacf4c243999e3f7628e4373, type: 2} - - target: {fileID: -6054058774237248251, guid: e1d1a8f7ba95e0f4f91d68f542216319, - type: 3} - propertyPath: m_Materials.Array.data[0] - value: - objectReference: {fileID: 2100000, guid: fc883cb8b3fd8fa4a9c4b19e725146c3, type: 2} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: e1d1a8f7ba95e0f4f91d68f542216319, type: 3} --- !u!1 &3066164092647859507 stripped diff --git a/Assets/Scenes/Template.unity b/Assets/Scenes/Template.unity index 8954cff..e23f455 100644 --- a/Assets/Scenes/Template.unity +++ b/Assets/Scenes/Template.unity @@ -1511,7 +1511,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 4 + m_RootOrder: 5 m_LocalEulerAnglesHint: {x: 50, y: 0, z: 0} --- !u!114 &963194229 MonoBehaviour: @@ -2766,7 +2766,7 @@ PrefabInstance: - target: {fileID: 1293638319414553333, guid: 3f7ba35961265c14d95fd3df6e0e7e17, type: 3} propertyPath: m_RootOrder - value: 5 + value: 4 objectReference: {fileID: 0} - target: {fileID: 1293638319414553333, guid: 3f7ba35961265c14d95fd3df6e0e7e17, type: 3} @@ -2857,6 +2857,11 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} + - target: {fileID: 7411008065860400178, guid: eedf6123f261655408fea12ace3e43f3, + type: 3} + propertyPath: m_IsActive + value: 1 + objectReference: {fileID: 0} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: eedf6123f261655408fea12ace3e43f3, type: 3} --- !u!1001 &6461784657988510636 diff --git a/Assets/Scripts/GameManager.cs b/Assets/Scripts/GameManager.cs index 3cb70b8..dcb4305 100644 --- a/Assets/Scripts/GameManager.cs +++ b/Assets/Scripts/GameManager.cs @@ -29,15 +29,15 @@ private void Awake() DontDestroyOnLoad(gameObject); playerInput = new PlayerInput(); - } - - private void OnEnable() { playerInput.Player.Restart.started += OnRestart; playerInput.Player.Enable(); } - - private void OnDisable() { - playerInput?.Player.Disable(); + + private void OnDestroy() { + if (playerInput != null) { + playerInput.Player.Restart.started -= OnRestart; + playerInput.Player.Disable(); + } } /** diff --git a/Assets/Scripts/Interactables/Air.cs b/Assets/Scripts/Interactables/Air.cs index 275e287..433e770 100644 --- a/Assets/Scripts/Interactables/Air.cs +++ b/Assets/Scripts/Interactables/Air.cs @@ -1,5 +1,5 @@ public class Air : Interactable { - public override void Interact() { + public override void Interact(Player player) { print("Air was interacted with and nothing happened."); } } \ No newline at end of file diff --git a/Assets/Scripts/Interactables/Door.cs b/Assets/Scripts/Interactables/Door.cs index 21fad6c..c0e8ab8 100644 --- a/Assets/Scripts/Interactables/Door.cs +++ b/Assets/Scripts/Interactables/Door.cs @@ -1,11 +1,10 @@ - using UnityEngine.SceneManagement; using UnityEngine.UI; -public class Door : Interactable -{ + +public class Door : Interactable { public string goToLevel; public Text doorText; - public override void Interact() + public override void Interact(Player player) { SceneManager.LoadScene(goToLevel); } diff --git a/Assets/Scripts/Interactables/Interactable.cs b/Assets/Scripts/Interactables/Interactable.cs index e113651..5cf0106 100644 --- a/Assets/Scripts/Interactables/Interactable.cs +++ b/Assets/Scripts/Interactables/Interactable.cs @@ -6,8 +6,7 @@ * An Interactable will create its own BoxCollider according to dimensions and offset */ public abstract class Interactable : MonoBehaviour { - protected GameObject player; - protected PlayerManager playerManager; + protected Player player; protected PlayerInteract playerInteract; // Sets the dimensions of the collider and offset from center of this GameObject @@ -16,10 +15,6 @@ public abstract class Interactable : MonoBehaviour { private BoxCollider boxCollider; protected virtual void Awake() { - player = GameObject.FindGameObjectWithTag("Player"); - playerManager = player.GetComponent(); - playerInteract = player.GetComponent(); - // place a trigger region on this Interactable boxCollider = gameObject.AddComponent(); boxCollider.isTrigger = true; @@ -31,7 +26,10 @@ protected virtual void Awake() { * Performs an interaction with this Interactable. * Should be customized per type of Interactable. */ - public abstract void Interact(); + public virtual void Interact(Player player) { + this.player = player; + playerInteract = player.GetComponent(); + } private void OnTriggerEnter(Collider other) { if (other.CompareTag("Player")) { diff --git a/Assets/Scripts/Interactables/Match.cs b/Assets/Scripts/Interactables/Match.cs index 4ed9d67..62b402e 100644 --- a/Assets/Scripts/Interactables/Match.cs +++ b/Assets/Scripts/Interactables/Match.cs @@ -1,5 +1,5 @@ public class Match : Interactable { - public override void Interact() { + public override void Interact(Player player) { print("A Match was interacted with and nothing happened."); } } \ No newline at end of file diff --git a/Assets/Scripts/Interactables/Nail.cs b/Assets/Scripts/Interactables/Nail.cs index 9c63095..bdda971 100644 --- a/Assets/Scripts/Interactables/Nail.cs +++ b/Assets/Scripts/Interactables/Nail.cs @@ -15,7 +15,7 @@ protected override void Awake() { meshLowerable = GetComponentInChildren(); } - public override void Interact() { + public override void Interact(Player player) { ToggleState(); } diff --git a/Assets/Scripts/Interactables/Needle.cs b/Assets/Scripts/Interactables/Needle.cs index e6ad924..173b846 100644 --- a/Assets/Scripts/Interactables/Needle.cs +++ b/Assets/Scripts/Interactables/Needle.cs @@ -12,7 +12,7 @@ protected override void Awake() { meshLowerable = GetComponentInChildren(); } - public override void Interact() { + public override void Interact(Player player) { ToggleState(); } diff --git a/Assets/Scripts/Interactables/Pushpin.cs b/Assets/Scripts/Interactables/Pushpin.cs index 747a70e..11f59a9 100644 --- a/Assets/Scripts/Interactables/Pushpin.cs +++ b/Assets/Scripts/Interactables/Pushpin.cs @@ -1,84 +1,90 @@ +using System; + public class Pushpin : Interactable { + + public Yarn connectedYarn { get; protected set; } + + public InteractContactable interactContactable { get; protected set; } + public enum State { Untied, Tied, Done, } private State state; - - public bool IsDone() { return state == State.Done; } - // the yarn that is connected to this pushpin. can only be one - private Yarn connectedYarn; - + private bool CheckState(State state) { return state == this.state; } + public bool IsUntied() { return CheckState(State.Untied); } + public bool IsTied() { return CheckState(State.Tied); } + public bool IsDone() { return CheckState(State.Done); } + + private void SetState(State state) { this.state = state; } + protected override void Awake() { base.Awake(); state = State.Untied; connectedYarn = null; + interactContactable = GetComponent(); } - public override void Interact() { - YarnUpdate(); - } - - /** - * A Pushpin will attempt to update the player and any yarn they're holding - * based on the states of those objects. - */ - private void YarnUpdate() { - Yarn playerYarn = playerManager.YarnHeld; - - /* - * Yarn can be attached to this Pushpin if: - * - This Pushpin is Untied to anything - * - The player is holding onto yarn, in which case the player wants to start a line of yarn - * - The player is pulling yarn, in which case the player wants to finish a line of yarn - */ - if (state == State.Untied) { - if (playerManager.CheckState(PlayerManager.State.Holding)) { - // start a line of yarn - state = State.Tied; - playerManager.SetState(PlayerManager.State.Pulling); - connectedYarn = playerYarn; - connectedYarn.AddContact(gameObject); - print("Yarn was just tied to a Pushpin, starting a line."); + public override void Interact(Player player) { + base.Interact(player); - } else if (playerManager.CheckState(PlayerManager.State.Pulling)) { - // finish a line of yarn - state = State.Tied; - connectedYarn = playerYarn; - connectedYarn.TieTo(this); - print("Yarn was just tied to a Pushpin, ending a line."); + switch (state) { + /* + * Yarn can be attached to this Pushpin if: + * - This Pushpin is Untied to anything + * - The player is holding onto yarn, in which case the player wants to start a line of yarn + * - The player is pulling yarn, in which case the player wants to finish a line of yarn + */ + case State.Untied: { + connectedYarn = player.yarnHeld; + if (player.IsHolding()) { + // start a line of yarn + player.StartPulling(connectedYarn, this); + connectedYarn.StartYarnLine(player, this); + SetState(State.Tied); + print("Yarn was just tied to a Pushpin, starting a line."); + + } else if (player.IsPulling()) { + // finish a line of yarn + player.FinishPulling(connectedYarn, this); + connectedYarn.FinishYarnLine(this); + SetState(State.Tied); + print("Yarn was just tied to a Pushpin, ending a line."); + } + break; } - } - - /* - * Yarn can be detached from this Pushpin if: - * - This Pushpin is Tied OR Done already - * - The player is pulling yarn, which case they want to return to just holding yarn - * - This Pushpin must be part of this line of yarn AND be the only thing other than the player - * - The player is normal, in which case they want to change an existing line of yarn - * - This Pushpin must be either the first or last in the line of yarn AND there are at least two contacts - */ - else if (state == State.Tied || state == State.Done) { - if (playerManager.CheckState(PlayerManager.State.Pulling) - && connectedYarn.ContactCount() == 2 - && connectedYarn == playerYarn) { - // return to holding yarn - state = State.Untied; - playerManager.SetState(PlayerManager.State.Holding); - connectedYarn.RemoveContactAll(gameObject); - connectedYarn = null; - print("Yarn was just untied from a Pushpin, undoing a line."); + /* + * Yarn can be detached from this Pushpin if: + * - This Pushpin is Tied OR Done already + * - The player is normal, in which case they want to change an existing line of yarn + * - This Pushpin must be either the first or last in the line of yarn + * - The player is pulling yarn, in which case they want to return to just holding yarn + * - This Pushpin must be part of this line of yarn AND be the only thing other than the player + */ + case State.Tied: + case State.Done: { + // pushpin needs to have connected yarn for said yarn to be detached + if (connectedYarn == null) throw new Exception($"{this} was interacted with in a Tied or Done state but somehow has no connected Yarn!"); - } else if (playerManager.CheckState(PlayerManager.State.Normal) - && connectedYarn.ContactCount() >= 2 - && connectedYarn.IsContactAtEnd(gameObject)) { - // start changing a line of yarn again - state = State.Untied; - connectedYarn.UntieFrom(this); + if (player.IsNormal()) { + // resume editing a line of yarn + player.EditPulling(connectedYarn, this); + connectedYarn.EditYarnLine(this, player); + SetState(State.Untied); + print("Yarn was just untied from a Pushpin, to edit a line."); + + } else if (player.IsPulling()) { + // undo the line of yarn entirely + player.UndoPulling(connectedYarn, this); + connectedYarn.UndoYarnLine(this); + SetState(State.Untied); + print("Yarn was just untied from a Pushpin, undoing a line."); + } + connectedYarn = null; - print("Yarn was just untied from a Pushpin, to edit a line."); + break; } } } diff --git a/Assets/Scripts/Interactables/Yarn.cs b/Assets/Scripts/Interactables/Yarn.cs index 4ebb45e..9601d8a 100644 --- a/Assets/Scripts/Interactables/Yarn.cs +++ b/Assets/Scripts/Interactables/Yarn.cs @@ -5,8 +5,10 @@ public class Yarn : Interactable { public Vector3 positionYarnInPlayersArms; public LineRenderer lineRenderer; + public GameObject mesh; private LevelManager levelManager; + public YarnLine yarnLine { get; private set; } public enum State { Normal, @@ -14,96 +16,40 @@ public enum State { } private State state; - public bool IsDestroyed() { return state == State.Destroyed; } - - /** - * The Yarn object maintains a list of Contacts to know what it has been wrapped around - */ - private List contacts; - - public void AddContact(GameObject obj) { - contacts.Add(new Contact(this, obj, levelManager, 0)); - } - - public void InsertContact(GameObject obj, int index, float initialAngle) { - contacts.Insert(index, new Contact(this, obj, levelManager, initialAngle)); - } - - /* Remove all Contacts with obj as its source. */ - public void RemoveContactAll(GameObject obj) { - foreach(Contact c in contacts.FindAll(c => c.source == obj)) - contacts.Remove(c); - } - - public void RemoveContact(Contact c) { contacts.Remove(c); } - - /* - * Remove a Contacts with obj as its source, but only at the beginning or end of the contacts list. - * - * Additionally causes the contacts list to reverse order if trying to remove the contact from the front. - * This is done in the event that the player tries to remove the yarn from the opposite end, so that - * the yarn's contacts are always added from the end of the list. - */ - public void RemoveContactFromEnd(GameObject obj) { - if(contacts[0].source == obj) contacts.Reverse(); - if(contacts[contacts.Count - 1].source == obj) contacts.RemoveAt(contacts.Count - 1); - } - - /* Does the contacts list contain any Contact with obj as a source? */ - public bool ContainsContact(GameObject obj) { - return contacts.Exists(c => c.source == obj); - } - - /* Is obj a Contact at the beginning or end of the contacts list? */ - public bool IsContactAtEnd(GameObject obj) { - return contacts[0].source == obj || contacts[contacts.Count - 1].source == obj; - } + private bool CheckState(State state) { return state == this.state; } + public bool IsNormal() { return CheckState(State.Normal); } + public bool IsDestroyed() { return CheckState(State.Destroyed); } - public int ContactCount() { return contacts.Count; } - - public GameObject mesh; - protected override void Awake() { base.Awake(); state = State.Normal; - contacts = new List(); + yarnLine = null; levelManager = FindObjectOfType(); } + private void FixedUpdate() { + // update the physics of the yarn line + yarnLine?.PhysicsUpdate(); + } + private void Update() { + // update the visuals of the yarn line + yarnLine?.Draw(); + // update the points the LineRenderer is rendering - if (lineRenderer) { + if (lineRenderer && yarnLine != null) { var linePoints = new List(); - foreach (var c in contacts) linePoints.AddRange(c.renderPoints); + linePoints.AddRange(yarnLine.renderPoints); lineRenderer.positionCount = linePoints.Count; lineRenderer.SetPositions(linePoints.ToArray()); } } - private void FixedUpdate() { UpdateAllContacts(); } - - private void UpdateAllContacts() { - // first, update the render points for all contacts. - foreach(Contact c in contacts) - c.UpdateRenderPoints(); - - // next, update the angles that all contacts have to their next contact. - for(var i = 0; i < contacts.Count - 1; i++) - contacts[i].UpdateLine(contacts[i + 1]); - - // contacts remove themselves if they've been unraveled - /*for(var i = 1; i < contacts.Count - 1; i++) - contacts[i].UpdateUnraveled(contacts[i - 1]);*/ - - // update the potential contacts list, adding new proceeding contacts if necessary. - for(var i = 0; i < contacts.Count - 1; i++) - contacts[i].UpdateCandidates(contacts[i + 1], i); - } - - public override void Interact() { + public override void Interact(Player player) { + base.Interact(player); // the player picks up the yarn if they have their arms free - if (playerManager.CheckState(PlayerManager.State.Normal)) { - PickUp(); + if (player.IsNormal()) { + GetPickedUp(player); } } @@ -111,71 +57,67 @@ public override void Interact() { private void ShowMesh() { mesh.SetActive(true); } - private void PickUp() { - AddContact(player.gameObject); - playerManager.SetState(PlayerManager.State.Holding); - playerManager.YarnHeld = this; + /* A Player picks up this Yarn. */ + private void GetPickedUp(Player player) { + // give the player this yarn + player.GiveYarn(this); + + // this yarn is no longer interactable playerInteract.RemoveInteractable(this); + + // parent the yarn to the player and position it in their arms transform.SetParent(player.transform); transform.localPosition = positionYarnInPlayersArms; + print("The player just picked up some yarn."); } - public void PutDown() { - playerManager.SetState(PlayerManager.State.Normal); - playerManager.YarnHeld = null; + /* A Player puts down this Yarn. */ + public void GetPutDown(Player player) { + // return the player to normal, take it from the player + player.RemoveYarn(this); + + // unparent the yarn, pulling it out to the outermost level transform.parent = null; + + // calculate a new position for the yarn on the grid and set it there Vector3 positionOnGround = new Vector3( Mathf.Round(player.transform.localPosition.x/2f)*2f, 0, Mathf.Round(player.transform.localPosition.z/2f)*2f); transform.localPosition = positionOnGround; + print("The player has dropped the yarn."); } - - /* Untie this Yarn object from the given Pushpin, returning control to the player. */ - public void UntieFrom(Pushpin pushpin) { - playerManager.SetState(PlayerManager.State.Pulling); - RemoveContactFromEnd(pushpin.gameObject); - AddContact(player.gameObject); - if(mesh) ShowMesh(); - } - /* Ties this Yarn off on the given Pushpin, removing control from the player. */ - public void TieTo(Pushpin pushpin) { - playerManager.SetState(PlayerManager.State.Normal); - RemoveContactFromEnd(player); - AddContact(pushpin.gameObject); - if(mesh) HideMesh(); + /* Starts a line of yarn between a Player and a Pushpin. */ + public void StartYarnLine(Player player, Pushpin pushpin) { + yarnLine = new YarnLine(this, player.playerContactable.GetNewContact(), pushpin.interactContactable.GetNewContact()); } - private void OnDrawGizmos() { - // render gizmos showing the render point connections - if (contacts != null) - for (var i = 0; i < contacts.Count; i++) { - // draw lines between all of the render points for this contact - var thisContact = contacts[i]; - if (thisContact.renderPoints.Count < 1) continue; - - for (var j = 0; j < thisContact.renderPoints.Count - 1; j++) { - var thisPoint = thisContact.renderPoints[j]; - var nextPoint = thisContact.renderPoints[j+1]; - Gizmos.DrawLine(thisPoint, nextPoint); - } - // if we know there's another contact after this one, draw a line - // from the last renderPoint on this contact to the first renderPoint - // on the next contact. - if (i >= contacts.Count - 1) continue; + /* Finishes off this Yarn's line on the given Pushpin. */ + public void FinishYarnLine(Pushpin pushpin) { + if(mesh) HideMesh(); + yarnLine.SetTail(pushpin.interactContactable.GetNewContact()); + } - var nextContact = contacts[i + 1]; - if (nextContact.renderPoints.Count < 1) continue; - var thisContactLastPointIndex = thisContact.renderPoints.Count - 1; - - var thisContactLastPoint = thisContact.renderPoints[thisContactLastPointIndex]; - var nextContactFirstPoint = nextContact.renderPoints[0]; - Gizmos.DrawLine(thisContactLastPoint, nextContactFirstPoint); - } + /* Unties this Yarn's line from a Pushpin to allow a Player to edit the line. */ + public void EditYarnLine(Pushpin pushpin, Player player) { + if(mesh) ShowMesh(); + + // add the player on the same side of the YarnLine that the given Pushpin is at + if (yarnLine.GetHead().gameObject.Equals(pushpin.gameObject)) { + yarnLine.SetHead(player.playerContactable.GetNewContact()); + } else if (yarnLine.GetTail().gameObject.Equals(pushpin.gameObject)) { + yarnLine.SetTail(player.playerContactable.GetNewContact()); + } else { + throw new Exception($"{this} was told to edit its YarnLine, but was given a Pushpin that isn't on either side of the YarnLine!"); + } } - + /* Undoes this Yarn's line from a Pushpin to remove the line. */ + public void UndoYarnLine(Pushpin pushpin) { + yarnLine.Clear(); + yarnLine = null; + } } \ No newline at end of file diff --git a/Assets/Scripts/PlayerBehavior/Player.cs b/Assets/Scripts/PlayerBehavior/Player.cs new file mode 100644 index 0000000..7adaac0 --- /dev/null +++ b/Assets/Scripts/PlayerBehavior/Player.cs @@ -0,0 +1,92 @@ +using System; +using UnityEngine; + +public class Player : MonoBehaviour { + + public GameObject normalMesh; + public GameObject holdingMesh; + + public Yarn yarnHeld { get; set; } + public PlayerContactable playerContactable { get; protected set; } + + public enum State { + Normal, + Holding, + Pulling, + } + + private State state; + private const State DEFAULT_STATE = State.Normal; + + private bool CheckState(State state) { return state == this.state; } + public bool IsNormal() { return CheckState(State.Normal); } + public bool IsHolding() { return CheckState(State.Holding); } + public bool IsPulling() { return CheckState(State.Pulling); } + + private void SetState(State state) { + if(state == this.state) Debug.LogWarning($"{this} was told to set state to {state}, but was already of state {this.state}."); + this.state = state; + + // perform updates that can be entirely determined from state + if(IsNormal()) ShowNormalMesh(); + else ShowHoldingMesh(); + } + + /* Player, not holding yarn, is given yarn to hold. */ + public void GiveYarn(Yarn yarn) { + if (yarnHeld != null) throw new Exception($"{this} was given yarn, but already had yarn!"); + + yarnHeld = yarn; + SetState(State.Holding); + } + + /* Player, already holding yarn, loses their yarn. */ + public void RemoveYarn(Yarn yarn) { + if (yarnHeld != yarn) throw new Exception($"{this} was told to remove yarn that wasn't the yarn being held!"); + + yarnHeld = null; + SetState(State.Normal); + } + + /* Player, already holding yarn, starts pulling a line of yarn from a Pushpin. */ + public void StartPulling(Yarn yarn, Pushpin pushpin) { + if (yarn != yarnHeld) throw new Exception($"{this} was told to start pulling yarn from a pushpin, but StartPulling was given yarn different from the yarn the player is holding."); + SetState(State.Pulling); + } + + /* Player, previously holding yarn, finishes pulling a line of yarn at a Pushpin. */ + public void FinishPulling(Yarn yarn, Pushpin pushpin) { + if(yarn != yarnHeld) throw new Exception($"{this} was told to finish pulling yarn at a pushpin, but FinishPulling was given yarn different from the yarn the player is holding."); + RemoveYarn(yarn); + } + + /* Player, holding nothing, wants to edit a line of yarn from a Pushpin. */ + public void EditPulling(Yarn yarn, Pushpin pushpin) { + if (yarnHeld != null) throw new Exception($"{this} was told to edit a line of yarn at a pushpin, but the player was already holding yarn."); + if (pushpin.connectedYarn != yarn) throw new Exception($"{this} was told to edit a line of yarn at a pushpin, but EditPulling was given a pushpin and yarn that were not connected!"); + // give the player the yarn and then move directly into a pulling state, + // because this yarn and pushpin are already connected + GiveYarn(yarn); + SetState(State.Pulling); + } + + /* Player, already pulling yarn, wants to undo a line of yarn they just started from a Pushpin. */ + public void UndoPulling(Yarn yarn, Pushpin pushpin) { + SetState(State.Holding); + } + + private void ShowNormalMesh() { + if(normalMesh) normalMesh.SetActive(true); + if(holdingMesh) holdingMesh.SetActive(false); + } + + private void ShowHoldingMesh() { + if(holdingMesh) holdingMesh.SetActive(true); + if(normalMesh) normalMesh.SetActive(false); + } + + private void Awake() { + SetState(DEFAULT_STATE); + playerContactable = GetComponent(); + } +} diff --git a/Assets/Scripts/PlayerBehavior/PlayerManager.cs.meta b/Assets/Scripts/PlayerBehavior/Player.cs.meta similarity index 100% rename from Assets/Scripts/PlayerBehavior/PlayerManager.cs.meta rename to Assets/Scripts/PlayerBehavior/Player.cs.meta diff --git a/Assets/Scripts/PlayerBehavior/PlayerInteract.cs b/Assets/Scripts/PlayerBehavior/PlayerInteract.cs index 1dffe07..0063a15 100644 --- a/Assets/Scripts/PlayerBehavior/PlayerInteract.cs +++ b/Assets/Scripts/PlayerBehavior/PlayerInteract.cs @@ -7,26 +7,30 @@ public class PlayerInteract : MonoBehaviour { private PlayerInput playerInput; - private PlayerManager playerManager; + private Player player; private List interactables; private void Awake() { playerInput = new PlayerInput(); - playerManager = GetComponent(); + player = GetComponent(); - playerInput.Player.Fire.started += OnInteract; SceneManager.sceneLoaded += (scene, mode) => { interactables.Clear(); }; - interactables = new List(); } private void OnEnable() { + playerInput.Player.Fire.started += OnInteract; playerInput.Player.Enable(); } + private void OnDisable() { + playerInput.Player.Fire.started -= OnInteract; + playerInput.Player.Disable(); + } + private void Update() { // player rotates to face cursor on screen var ray = Camera.main.ScreenPointToRay(Input.mousePosition); @@ -44,9 +48,9 @@ private void Update() { private void OnInteract(InputAction.CallbackContext ctx) { if (interactables.Count > 0) { - TopInteractable().Interact(); - } else if (playerManager.CheckState(PlayerManager.State.Holding)) { - playerManager.YarnHeld.PutDown(); + TopInteractable().Interact(player); + } else if (player.IsHolding()) { + player.yarnHeld.GetPutDown(player); } } } diff --git a/Assets/Scripts/PlayerBehavior/PlayerManager.cs b/Assets/Scripts/PlayerBehavior/PlayerManager.cs deleted file mode 100644 index 9760f88..0000000 --- a/Assets/Scripts/PlayerBehavior/PlayerManager.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using UnityEngine; - -public class PlayerManager : MonoBehaviour -{ - public enum State { - Normal, - Holding, - Pulling, - } - - private const State DEFAULT_STATE = State.Normal; - private State state; - - public GameObject normalMesh; - public GameObject holdingMesh; - - public Yarn YarnHeld { get; set; } - - private void Start() { - SetState(DEFAULT_STATE); - } - - public void SetState(State state) { - this.state = state; - UpdateFromState(); - } - public bool CheckState(State state) { return state == this.state; } - - private void UpdateFromState() { - if (CheckState(State.Normal)) { - if(normalMesh) normalMesh.SetActive(true); - if(holdingMesh) holdingMesh.SetActive(false); - } else { - if(holdingMesh) holdingMesh.SetActive(true); - if(normalMesh) normalMesh.SetActive(false); - } - } -} diff --git a/Assets/Scripts/YarnContacting/Contact.cs b/Assets/Scripts/YarnContacting/Contact.cs index 73765f6..8433a13 100644 --- a/Assets/Scripts/YarnContacting/Contact.cs +++ b/Assets/Scripts/YarnContacting/Contact.cs @@ -1,164 +1,101 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using UnityEngine; +using Vector3 = UnityEngine.Vector3; -public class Contact { - // this Contact is in this Yarn's contacts list - public Yarn yarn; +/** + * A Contact is a single instance of a Yarn object being connected to something. + * + * Only Contactables can create Contacts of themselves. + */ +public abstract class Contact { + // uses host for GameObject data and Transform + public Contactable host { get; protected set; } - // this Contact's GameObject - public GameObject source; + // needs prev and nextContacts for calculating angles + public Contact prevContact { get; set; } + public Contact nextContact { get; set; } - private LevelManager levelManager; - - /* - * the line that this Contact is responsible for checking. - * leaves from this Contact's last renderPoint and goes to the first - * renderPoint in the next Contact. - * - * note that this value may be null if this Contact has no next Contact. - */ - public Vector3 yarnLine; - public Vector2 yarnLineXZ; - - // the initial angular velocity the player had when this Contact was created - private float initialAngle; - - // a list of calculated points that the yarn is actually rendered at - public List renderPoints; + // render points describe how the yarn is visually connected to this Contact + public List renderPoints { get; protected set; } + public Vector3 headRenderPoint => renderPoints[0]; + public Vector3 tailRenderPoint => renderPoints[renderPoints.Count-1]; - // a list of candidates that this Contact might turn into a proceeding contact - public Dictionary candidates; - - public Contact(Yarn yarn, GameObject source, LevelManager levelManager, float initialAngle) { - this.yarn = yarn; - this.source = source; - this.levelManager = levelManager; - this.initialAngle = initialAngle; - renderPoints = new List(); - candidates = new Dictionary(); - UpdateRenderPoints(); - } - - /* Update the render points for this Contact. */ - public void UpdateRenderPoints() { - // todo: show render points properly by using values from Contactable class - renderPoints.Clear(); - renderPoints.Add(source.transform.position); - } - - /* Update the line that this Contact has to the nextContact following it. */ - public void UpdateLine(Contact nextContact) { - // next contact must exist to find values for it! - if (nextContact == null) return; - - // update the values this Contact has with the next Contact. - // use render points to determine this. - yarnLine = nextContact.renderPoints[0] - renderPoints[renderPoints.Count - 1]; - yarnLineXZ = new Vector2(yarnLine.x, yarnLine.z); - } - - /* - * Updates the list of Candidates that this Contact is watching. - * - * If a Candidate has changed in some way this update that suggests it should - * become a new Contact, this Contact will add it. - */ - public void UpdateCandidates(Contact nextContact, int index) { - if (nextContact == null) return; - - /* - * Create a new list of candidates. - */ - Dictionary newCandidates = new Dictionary(); - foreach (var c in levelManager.allContactables) { - if (c.gameObject != source - && c.gameObject != nextContact.source - && c.enabled) { - newCandidates.Add(c, new Candidate(this, c)); + // the vector entering this Contact from a previous Contact + public Vector3 arrivingVector { + get { + if (prevContact == null) { + Debug.LogError($"{host.name} is hosting a Contact that was asked for an arriving vector, " + + $"but this Contact has no previous Contact!"); } + return headRenderPoint - prevContact.tailRenderPoint; } - - /* - * For each Candidate this Contact cares about this update, check to see if it has an old - * record. If it does, check several criteria to see if it changed in a way that makes it - * deserve to become a Contact. If it doesn't, - */ - foreach (var candidate in newCandidates) { - var oldCandidate = GetCandidate(candidate.Key); - var newCandidate = candidate.Value; - - if (!oldCandidate.Equals(default(Candidate))) { - // so we found an old record of this candidate. cool. - // get the angles of both of these - var oldAngle = oldCandidate.parentAngle; - var newAngle = newCandidate.parentAngle; - var newRadius = newCandidate.parentRadius; - - // has this Candidate rotated in a way that crossed over this Contact's angle? - // and is this Candidate now within distance of this Contact's line? - // and is the new angle close-ish to zero so we aren't connecting backwards? - if (oldAngle * newAngle < 0 - && newRadius < yarnLineXZ.magnitude - && Math.Abs(newAngle) < 90) { - // if all of this is true, this candidate becomes a new contact - yarn.InsertContact(newCandidate.source.gameObject, index + 1, newAngle); - } + } + + // the vector leaving this Contact to the next Contact + public Vector3 leavingVector { + get { + if (prevContact == null) { + Debug.LogError($"{host.name} is hosting a Contact that was asked for a leaving vector, " + + $"but this Contact has no next Contact!"); } - // update the list of candidates, deleting the old ones - candidates = newCandidates; + return nextContact.headRenderPoint - tailRenderPoint; } } - /* - * Disconnects the most recent contact based on yarn tracking. + /** + * Updates all of this Contact's data based on the position of the nextContact. + * + * It's assumed that the nextContact is a fluidly moving GameObject, so all calculations + * are performed between previous and upcoming data. */ - public void UpdateUnraveled(Contact prevContact) { - var prevYarnLineXZ = prevContact.yarnLineXZ; - var angleToPrevious = Vector2.SignedAngle(prevYarnLineXZ, yarnLineXZ); - if (angleToPrevious * initialAngle < 0 - && Math.Abs(angleToPrevious) < 90) { - yarn.RemoveContact(this); - // todo: does this delete this contact properly? - } - } + public abstract void Update(Contact nextContact); +} + +public class CircleContact : Contact { + // how many times has this CircleContact been wrapped around? + public int rotations { get; } + // what angle does this CircleContact's line leave at compared to its initial angle? + public float angle { get; } - private Candidate GetCandidate(Contactable c) { - return candidates.ContainsKey(c) ? candidates[c] : default; - } - /** - * A Candidate is created by a currently existing Contact somewhere in the scene - * because it cares about this Contactable's angle to it. + * A CircleContact can only be created once: + * - We know the Contactable that is hosting the CircleContact + * - We know the initial render point that caused the CircleContact + * - We know the initial angle of contact so we can tell which way yarn is wrapping + * - We may know the previous Contact or the next Contact */ - public struct Candidate { - /* - * the Contact that is interested in the details of this Candidate - */ - public Contact parent; - - // the Contactable behind this Candidate - public Contactable source; + public CircleContact( + WrapContactable host, + Vector3 initialPoint, + float initialAngle, + Contact prevContact = null, + Contact nextContact = null) { + this.host = host; + this.prevContact = prevContact; + this.nextContact = nextContact; + // render points always has at least one point in it, it may never be empty + renderPoints = new List{initialPoint}; + rotations = 0; + angle = initialAngle; + } + + public override void Update(Contact nextContact) { - // the angle this Contactable has with the parent Contact - public float parentAngle; - public float parentRadius; + } +} - public Candidate(Contact parent, Contactable source) { - this.parent = parent; - this.source = source; - - /* - * get the signed angle between the parent Contact's yarnLineXZ - * and the line between the parent and this Contactable - */ - var parentPos = parent.renderPoints[parent.renderPoints.Count - 1]; - var sourcePos = source.transform.position; - var vecParentToThis = sourcePos - parentPos; - var vecParentToThisXZ = new Vector2(vecParentToThis.x, vecParentToThis.z); - parentAngle = Vector2.SignedAngle(parent.yarnLineXZ, vecParentToThisXZ); - parentRadius = vecParentToThisXZ.magnitude; - } +public class PointContact : Contact { + public PointContact( + InteractContactable host, + Contact prevContact = null, + Contact nextContact = null) { + this.host = host; + this.prevContact = prevContact; + this.nextContact = nextContact; + renderPoints = new List{host.transform.position + host.pointOffset}; + } + + public override void Update(Contact nextContact) { + } } \ No newline at end of file diff --git a/Assets/Scripts/YarnContacting/Contact.cs.meta b/Assets/Scripts/YarnContacting/Contact.cs.meta index 4472acb..97f36bb 100644 --- a/Assets/Scripts/YarnContacting/Contact.cs.meta +++ b/Assets/Scripts/YarnContacting/Contact.cs.meta @@ -1,3 +1,3 @@ fileFormatVersion: 2 -guid: 8313f239bd594e03ae63464437b19a2e -timeCreated: 1575148448 \ No newline at end of file +guid: 6195e93a001149388842f1854309f7eb +timeCreated: 1582155899 \ No newline at end of file diff --git a/Assets/Scripts/YarnContacting/Contact_Old.cs b/Assets/Scripts/YarnContacting/Contact_Old.cs new file mode 100644 index 0000000..ae0c8b4 --- /dev/null +++ b/Assets/Scripts/YarnContacting/Contact_Old.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +/* +public class Contact_Old { + // this Contact is in this Yarn's contacts list + public Yarn yarn; + + // the Contactable that this Contact points to + public Contactable source; + + private LevelManager levelManager; + + /* + * the line that this Contact is responsible for checking. + * leaves from this Contact's last renderPoint and goes to the first + * renderPoint in the next Contact. + * + * note that this value may be null if this Contact has no next Contact. + #1# + public Vector3 yarnLine; + public Vector2 yarnLineXZ; + + // the initial angular velocity the player had when this Contact was created + private float initialAngle; + + // a list of calculated points that the yarn is actually rendered at + public List renderPoints; + + // a list of candidates that this Contact might turn into a proceeding contact + public Dictionary candidates; + + public Contact_Old(Yarn yarn, Contactable source, LevelManager levelManager, float initialAngle) { + this.yarn = yarn; + this.source = source; + this.levelManager = levelManager; + this.initialAngle = initialAngle; + renderPoints = new List(); + candidates = new Dictionary(); + UpdateRenderPoints(); + } + + /* Update the render points for this Contact. #1# + public void UpdateRenderPoints() { + // todo: show render points properly by using values from Contactable class + renderPoints.Clear(); + renderPoints.Add(source.transform.position); + } + + /* Update the line that this Contact has to the nextContact following it. #1# + public void UpdateLine(Contact nextContact) { + // next contact must exist to find values for it! + if (nextContact == null) return; + + // update the values this Contact has with the next Contact. + // use render points to determine this. + yarnLine = nextContact.renderPoints[0] - renderPoints[renderPoints.Count - 1]; + yarnLineXZ = new Vector2(yarnLine.x, yarnLine.z); + } + + /* + * Updates the list of Candidates that this Contact is watching. + * + * If a Candidate has changed in some way this update that suggests it should + * become a new Contact, this Contact will add it. + #1# + public void UpdateCandidates(Contact nextContact, int index) { + if (nextContact == null) return; + + /* + * Create a new list of candidates. + #1# + Dictionary newCandidates = new Dictionary(); + foreach (var c in levelManager.allContactables) { + if (c.gameObject != source + && c.gameObject != nextContact.source + && c.enabled) { + newCandidates.Add(c, new Candidate(this, c)); + } + } + + /* + * For each Candidate this Contact cares about this update, check to see if it has an old + * record. If it does, check several criteria to see if it changed in a way that makes it + * deserve to become a Contact. If it doesn't, + #1# + foreach (var candidate in newCandidates) { + var oldCandidate = GetCandidate(candidate.Key); + var newCandidate = candidate.Value; + + if (!oldCandidate.Equals(default(Candidate))) { + // so we found an old record of this candidate. cool. + // get the angles of both of these + var oldAngle = oldCandidate.parentAngle; + var newAngle = newCandidate.parentAngle; + var newRadius = newCandidate.parentRadius; + + // has this Candidate rotated in a way that crossed over this Contact's angle? + // and is this Candidate now within distance of this Contact's line? + // and is the new angle close-ish to zero so we aren't connecting backwards? + if (oldAngle * newAngle < 0 + && newRadius < yarnLineXZ.magnitude + && Math.Abs(newAngle) < 90) { + // if all of this is true, this candidate becomes a new contact + yarn.InsertContact(newCandidate.source.gameObject, index + 1, newAngle); + } + } + // update the list of candidates, deleting the old ones + candidates = newCandidates; + } + } + + /* + * Disconnects the most recent contact based on yarn tracking. + #1# + public void UpdateUnraveled(Contact prevContact) { + var prevYarnLineXZ = prevContact.yarnLineXZ; + var angleToPrevious = Vector2.SignedAngle(prevYarnLineXZ, yarnLineXZ); + if (angleToPrevious * initialAngle < 0 + && Math.Abs(angleToPrevious) < 90) { + yarn.RemoveContact(this); + // todo: does this delete this contact properly? + } + } + + private Candidate GetCandidate(Contactable c) { + return candidates.ContainsKey(c) ? candidates[c] : default; + } + + /** + * A Candidate is created by a currently existing Contact somewhere in the scene + * because it cares about this Contactable's angle to it. + #1# + public struct Candidate { + /* + * the Contact that is interested in the details of this Candidate + #1# + public Contact parent; + + // the Contactable behind this Candidate + public Contactable source; + + // the angle this Contactable has with the parent Contact + public float parentAngle; + public float parentRadius; + + public Candidate(Contact parent, Contactable source) { + this.parent = parent; + this.source = source; + + /* + * get the signed angle between the parent Contact's yarnLineXZ + * and the line between the parent and this Contactable + #1# + var parentPos = parent.renderPoints[parent.renderPoints.Count - 1]; + var sourcePos = source.transform.position; + var vecParentToThis = sourcePos - parentPos; + var vecParentToThisXZ = new Vector2(vecParentToThis.x, vecParentToThis.z); + parentAngle = Vector2.SignedAngle(parent.yarnLineXZ, vecParentToThisXZ); + parentRadius = vecParentToThisXZ.magnitude; + } + } +}*/ \ No newline at end of file diff --git a/Assets/Scripts/YarnContacting/Contact_Old.cs.meta b/Assets/Scripts/YarnContacting/Contact_Old.cs.meta new file mode 100644 index 0000000..4472acb --- /dev/null +++ b/Assets/Scripts/YarnContacting/Contact_Old.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8313f239bd594e03ae63464437b19a2e +timeCreated: 1575148448 \ No newline at end of file diff --git a/Assets/Scripts/YarnContacting/Contactable.cs b/Assets/Scripts/YarnContacting/Contactable.cs index 24f9a2b..b7b8db0 100644 --- a/Assets/Scripts/YarnContacting/Contactable.cs +++ b/Assets/Scripts/YarnContacting/Contactable.cs @@ -1,4 +1,8 @@ -using UnityEngine; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Vector3 = UnityEngine.Vector3; /** * A GameObject with this class has the ability to become a Contact in a line of yarn. @@ -8,6 +12,45 @@ * A Contactable is responsible for containing the sort of information that a Contact would * need if this GameObject became a Contact. */ -public class Contactable : MonoBehaviour { +public abstract class Contactable : MonoBehaviour { + [HideInInspector] public Vector3 pastPosition; + [HideInInspector] public Vector3 currentPosition; + + [HideInInspector] public List wrapContactables; -} + protected virtual void Awake() { + var position = transform.position; + pastPosition = position; + currentPosition = position; + + wrapContactables = FindObjectsOfType().ToList(); + } + + public void UpdatePosition() { + // update past and current positions + pastPosition = currentPosition; + currentPosition = transform.position; + } + + /** + * Return new Contacts for every wrap around a Contactable that is being made from this Contact to the other Contact. + */ + public List CheckOverlaps(Contact other) { + // create an edited watch list + var watchedContactablesEdit = new List(wrapContactables); + // if the other Contactable is a WrapContactable, remove it + if(other.host.GetType() == typeof(WrapContactable)) + watchedContactablesEdit.Remove((WrapContactable)other.host); + + List newContacts = new List(); + + // loop through each WrapContactable and consider whether not a line has crossed it + foreach (var watchedContactable in wrapContactables) { + /*if (WrapContactable.CheckWrapAround(, watchedContactable, other)) { + + }*/ + } + + return newContacts; + } +} \ No newline at end of file diff --git a/Assets/Scripts/YarnContacting/InteractContactable.cs b/Assets/Scripts/YarnContacting/InteractContactable.cs new file mode 100644 index 0000000..09a7928 --- /dev/null +++ b/Assets/Scripts/YarnContacting/InteractContactable.cs @@ -0,0 +1,14 @@ +using UnityEngine; + +/** + * If the player interacting with this Contactable should send a Contact to the player, + * put this Behaviour on that GameObject. + */ +public class InteractContactable : Contactable { + [Tooltip("This is the offset from the GameObject's position where yarn will be attached.")] + public Vector3 pointOffset; + + public PointContact GetNewContact() { + return new PointContact(this); + } +} diff --git a/Assets/Scripts/YarnContacting/InteractContactable.cs.meta b/Assets/Scripts/YarnContacting/InteractContactable.cs.meta new file mode 100644 index 0000000..708999f --- /dev/null +++ b/Assets/Scripts/YarnContacting/InteractContactable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cb3afdf96468fd04b95c0148819ca2d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/YarnContacting/PlayerContactable.cs b/Assets/Scripts/YarnContacting/PlayerContactable.cs new file mode 100644 index 0000000..76937a1 --- /dev/null +++ b/Assets/Scripts/YarnContacting/PlayerContactable.cs @@ -0,0 +1,11 @@ +using UnityEngine; + +/** + * This Contactable uniquely belongs to the Player. + * + * It's an InteractContactable because interaction is the only way in which the player is added or removed + * as a Contact to a Yarn object. + */ +public class PlayerContactable : InteractContactable { + +} \ No newline at end of file diff --git a/Assets/Scripts/YarnContacting/PlayerContactable.cs.meta b/Assets/Scripts/YarnContacting/PlayerContactable.cs.meta new file mode 100644 index 0000000..cff4090 --- /dev/null +++ b/Assets/Scripts/YarnContacting/PlayerContactable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ab040f71654faec4c811af8fee2ae1c4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/YarnContacting/WrapContactable.cs b/Assets/Scripts/YarnContacting/WrapContactable.cs new file mode 100644 index 0000000..eba2f1a --- /dev/null +++ b/Assets/Scripts/YarnContacting/WrapContactable.cs @@ -0,0 +1,101 @@ +using UnityEngine; + +/** + * If a Yarn object wrapping around this Contactable should send a Contact to that Yarn, + * put this Behaviour on that GameObject. + */ + +public class WrapContactable : Contactable { + // this is the radius of a circle on the XZ plane that the yarn line can hit to contact this object. + [Tooltip("Specifies the radius of the infinitely tall cylinder that represents the WrapContactable.")] + public float radius = 0.1f; + + protected override void Awake() { + base.Awake(); + // if we're a WrapContactable, we better remove ourselves from our own list of wrap contactables + wrapContactables.Remove(this); + } + + public CircleContact GetNewContact(Vector3 initialPoint, float initialAngle, Contact prevContact, Contact nextContact = null) { + return new CircleContact(this, initialPoint, initialAngle, prevContact, nextContact); + } + + /** + * Determines if, from one Contact to the other's position, a wrap has been made around a wrappee. + * The WrapResult will specify whether a wrap has occurred or not. + */ + public static WrapResult CheckWrapAround(Contact from, WrapContactable wrappee, Contact to) { + // this is the point that all vectors under consideration are based from + var hostPosition = from.tailRenderPoint; + + var positionDelta = to.host.currentPosition - to.host.pastPosition; + + // these are the vectors from the host position to the other contact, before and after. + Vector3 oldVecToWrapper = to.headRenderPoint - hostPosition; + Vector3 newVecToWrapper = to.headRenderPoint - hostPosition; + + // get both lines from the host that are tangent to the wrappee + Vector3[] wrappeeLines = FindTangentPointsToCircle(wrappee.transform.position - hostPosition, wrappee.radius); + var wrappeeLine = wrappeeLines[1]; + + // pick one based on the direction of the old vec to the new vec + // if the signed angle is position, rotation is clockwise, pick the clockwise-most wrappee line + if (Vector3.SignedAngle(oldVecToWrapper, newVecToWrapper, Vector3.up) >= 0) { + wrappeeLine = wrappeeLines[0]; + } + + + + return new WrapResult(); + } + + public struct WrapResult { + public bool wrapOccurred; + public WrapContactable wrappee; + public Vector3 wrapPosition; + public float initialAngle; + + public WrapResult(bool wrapOccurred, WrapContactable wrappee, Vector3 wrapPosition, float initialAngle) { + this.wrapOccurred = wrapOccurred; + this.wrappee = wrappee; + this.wrapPosition = wrapPosition; + this.initialAngle = initialAngle; + } + } + + /** + * Returns true if, from oldVec to newVec, it has wrapped around baseVec on the XZ plane. + */ + public static bool VectorHasWrappedVector(Vector3 baseVec, Vector3 oldVec, Vector3 newVec) { + float oldAngle = Vector3.SignedAngle(baseVec, oldVec, Vector3.up); + float newAngle = Vector3.SignedAngle(baseVec, newVec, Vector3.up); + + // if the angle between baseVec to the other vector has flipped (one is positive, one is negative) + // and if either the old or the new vector is longer than baseVec + // then one vector has wrapped another + return (oldAngle * newAngle < 0) && + (oldVec.magnitude > baseVec.magnitude || newVec.magnitude > baseVec.magnitude); + } + + /** + * This method returns both points on a circle on the XZ axis that is tangent from the base of vecToCircleCenter. + * + * Starting from the base of vecToCircleCenter, two lines can always be drawn tangent to a circle centered at the + * end of vecToCircleCenter with a radius of radius. Both of these points are returned in a Vector3[], with + * element 0 being the vector counter-clockwise (negative angle) from vecToCircleCenter and element 1 being + * the other. + */ + public static Vector3[] FindTangentPointsToCircle(Vector3 vecToCircleCenter, float radius) { + // this is the angle between vecToCircleCenter to both tangents + float angleToTangents = Mathf.Asin(radius / vecToCircleCenter.magnitude) * Mathf.Rad2Deg; + // rotates counter clockwise + Vector3 negativeTangent = Quaternion.Euler(0, -angleToTangents, 0) * vecToCircleCenter; + // rotates clockwise + Vector3 positiveTangent = Quaternion.Euler(0, angleToTangents, 0) * vecToCircleCenter; + + return new [] { + Vector3.Project(vecToCircleCenter, negativeTangent), + Vector3.Project(vecToCircleCenter, positiveTangent) + }; + } +} \ No newline at end of file diff --git a/Assets/Scripts/YarnContacting/WrapContactable.cs.meta b/Assets/Scripts/YarnContacting/WrapContactable.cs.meta new file mode 100644 index 0000000..c57426a --- /dev/null +++ b/Assets/Scripts/YarnContacting/WrapContactable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 29aab9a7c4fc6a04e854dffd2cf68812 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/YarnContacting/YarnLine.cs b/Assets/Scripts/YarnContacting/YarnLine.cs new file mode 100644 index 0000000..81651dd --- /dev/null +++ b/Assets/Scripts/YarnContacting/YarnLine.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +/** + * A YarnLine is a list that represents all the connections of a line of yarn. + */ +public class YarnLine { + // the head and tail contacts are always PointContacts + private PointContact head, tail; + + // every other contact in the list is some sort of Contact + private List wraps; + + private Yarn yarn; + + public List contacts { get; private set; } + public List renderPoints { get; private set; } + + /** + * A YarnLine can only be created by a Yarn that has been started between the Player and a Pushpin + */ + public YarnLine(Yarn yarn, PointContact player, PointContact pushpin) { + this.yarn = yarn; + head = pushpin; + tail = player; + wraps = new List(); + OnLineChanged(); + } + + /** + * Updates the line of yarn physically, considering new contact points and removing invalid ones + */ + public void PhysicsUpdate() { + // run through all Contacts: + // if a Contact's Contactable sees an overlap with other Contactables, add them + // if a Contact has become invalid, remove it + for (var i = 0; i < contacts.Count; i++) { + var contact = contacts[i]; + // todo: check whatever needs to be checked to add or remove contacts from this YarnLine + } + } + + /** + * Updates the line of yarn visually, after physics calculation have been performed + */ + public void Draw() { + + } + + /* Fires whenever the structure of the line changes. */ + private void OnLineChanged() { + // update the contacts list + contacts = new List(wraps.Count + 2) {head}; + contacts.AddRange(wraps); + contacts.Add(tail); + + // update the renderpoints based on changed contacts + renderPoints = new List(); + foreach (var contact in contacts) { + if(contact != null) renderPoints.AddRange(contact.renderPoints); + } + } + + private void Add(Contact contact) { + wraps.Add(contact); + OnLineChanged(); + } + + private int RemoveAll(Contact contact) { return RemoveAll(contact.host.gameObject); } + private int RemoveAll(GameObject gameObject) { + int result = wraps.RemoveAll(wrap => wrap.host.gameObject.Equals(gameObject)); + OnLineChanged(); + return result; + } + + public Contactable GetHead() { return head.host; } + public Contactable GetTail() { return tail.host; } + + /** + * If you try to give this function a Player's Contact, it will flip the entire list + * to ensure the Player is at the tail. + */ + public void SetHead(PointContact contact) { + head = contact; + if (contact.host.CompareTag("Player")) ReverseList(); + OnLineChanged(); + } + + public void SetTail(PointContact contact) { + tail = contact; + OnLineChanged(); + } + + /* Reverses the entire list, including the head and tail contacts. */ + private void ReverseList() { + // swap head and tail + var temp = tail; + tail = head; + head = temp; + // reverse all wrapping points + wraps.Reverse(); + // todo: reverse all prev and next Contact references + } + + /** + * Clears the entire line, removing everything. + */ + public void Clear() { + head = null; + tail = null; + wraps.Clear(); + OnLineChanged(); + } + + private void OnDrawGizmos() { + // render gizmos showing the render point connections + if (contacts != null) + for (var i = 0; i < contacts.Count; i++) { + // draw lines between all of the render points for this contact + var thisContact = contacts[i]; + if (thisContact.renderPoints.Count < 1) continue; + + for (var j = 0; j < thisContact.renderPoints.Count - 1; j++) { + var thisPoint = thisContact.renderPoints[j]; + var nextPoint = thisContact.renderPoints[j+1]; + Gizmos.DrawLine(thisPoint, nextPoint); + } + // if we know there's another contact after this one, draw a line + // from the last renderPoint on this contact to the first renderPoint + // on the next contact. + if (i >= contacts.Count - 1) continue; + + var nextContact = contacts[i + 1]; + if (nextContact.renderPoints.Count < 1) continue; + var thisContactLastPointIndex = thisContact.renderPoints.Count - 1; + + var thisContactLastPoint = thisContact.renderPoints[thisContactLastPointIndex]; + var nextContactFirstPoint = nextContact.renderPoints[0]; + Gizmos.DrawLine(thisContactLastPoint, nextContactFirstPoint); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/YarnContacting/YarnLine.cs.meta b/Assets/Scripts/YarnContacting/YarnLine.cs.meta new file mode 100644 index 0000000..a54fd9d --- /dev/null +++ b/Assets/Scripts/YarnContacting/YarnLine.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2bfac64ab1734a58858c2eef220fcbe4 +timeCreated: 1582156244 \ No newline at end of file