From 2d38838efda320a2876b38e0f01fae6ade486d07 Mon Sep 17 00:00:00 2001 From: hz Date: Sun, 1 Feb 2026 15:13:20 -0600 Subject: [PATCH] Tricks can now occupy baskets and block scoring --- Assets/Scripts/BasketController.cs | 176 +++++++++++++++++++++++ Assets/Scripts/PlayerPickupController.cs | 10 +- Assets/Scripts/TrickController.cs | 93 ++++++++++++ 3 files changed, 274 insertions(+), 5 deletions(-) diff --git a/Assets/Scripts/BasketController.cs b/Assets/Scripts/BasketController.cs index feb727c..b9c42a8 100644 --- a/Assets/Scripts/BasketController.cs +++ b/Assets/Scripts/BasketController.cs @@ -1,8 +1,24 @@ +using System.Collections.Generic; using UnityEngine; public class BasketController : MonoBehaviour { public ScoreManager scoreManager; + [SerializeField] private Transform basketHoldPoint; + [SerializeField] private LayerMask trickDetectMask = ~0; + private readonly Collider2D[] overlapHits = new Collider2D[8]; + private BoxCollider2D basketCollider; + private TrickController currentTrick; + + private void Awake() + { + basketCollider = GetComponent(); + } + + private void FixedUpdate() + { + RefreshTricksInBasket(); + } private void OnCollisionEnter2D(Collision2D collision) { @@ -11,6 +27,13 @@ public class BasketController : MonoBehaviour if (collision.gameObject.CompareTag("Interactable")) { if (collision.gameObject.GetComponent() != null) + { + RegisterTrick(collision.gameObject); + return; + } + + RefreshTricksInBasket(); + if (currentTrick != null) { return; } @@ -20,4 +43,157 @@ public class BasketController : MonoBehaviour GameObject.Destroy(collision.gameObject); } } + + private void OnCollisionExit2D(Collision2D collision) + { + UnregisterTrick(collision.gameObject); + } + + private void OnTriggerEnter2D(Collider2D other) + { + if (!other.CompareTag("Interactable")) + { + return; + } + + if (other.GetComponent() != null) + { + RegisterTrick(other.gameObject); + } + } + + private void OnTriggerExit2D(Collider2D other) + { + UnregisterTrick(other.gameObject); + } + + private void RegisterTrick(GameObject obj) + { + if (obj.TryGetComponent(out var trick)) + { + if (currentTrick != null && currentTrick != trick) + { + return; + } + + currentTrick = trick; + trick.SetBasketed(true, GetBasketAnchorPosition(trick)); + } + } + + private void UnregisterTrick(GameObject obj) + { + if (obj == null) + { + return; + } + + if (obj.TryGetComponent(out var trick)) + { + if (currentTrick == trick) + { + currentTrick = null; + trick.SetBasketed(false); + } + } + } + + private void RefreshTricksInBasket() + { + if (basketCollider == null) + { + return; + } + + var filter = new ContactFilter2D(); + filter.SetLayerMask(trickDetectMask); + filter.useTriggers = true; + + var hitCount = basketCollider.Overlap(filter, overlapHits); + for (var i = 0; i < hitCount; i++) + { + var hit = overlapHits[i]; + if (hit == null || !hit.CompareTag("Interactable")) + { + continue; + } + + if (hit.TryGetComponent(out var trick)) + { + if (currentTrick == null) + { + currentTrick = trick; + trick.SetBasketed(true, GetBasketAnchorPosition(trick)); + } + + break; + } + } + + if (currentTrick == null) + { + return; + } + + if (currentTrick.IsCarried) + { + currentTrick.SetBasketed(false); + currentTrick = null; + return; + } + + var stillInside = false; + for (var i = 0; i < hitCount; i++) + { + var hit = overlapHits[i]; + if (hit == null || !hit.CompareTag("Interactable")) + { + continue; + } + + if (hit.TryGetComponent(out var trick) && trick == currentTrick) + { + stillInside = true; + break; + } + } + + if (!stillInside) + { + currentTrick.SetBasketed(false); + currentTrick = null; + } + } + + private Vector2? GetBasketAnchorPosition(TrickController trick) + { + if (basketHoldPoint != null) + { + return OffsetForTrickHeight(basketHoldPoint.position, trick); + } + + if (basketCollider != null) + { + return OffsetForTrickHeight(basketCollider.bounds.center, trick); + } + + return null; + } + + private Vector2 OffsetForTrickHeight(Vector2 anchor, TrickController trick) + { + if (trick == null) + { + return anchor; + } + + var renderer = trick.GetComponent(); + if (renderer == null) + { + return anchor; + } + + anchor.y += renderer.bounds.size.y * 0.5f; + return anchor; + } } diff --git a/Assets/Scripts/PlayerPickupController.cs b/Assets/Scripts/PlayerPickupController.cs index 83d724b..4ad428f 100644 --- a/Assets/Scripts/PlayerPickupController.cs +++ b/Assets/Scripts/PlayerPickupController.cs @@ -165,6 +165,11 @@ public class PlayerPickupController : MonoBehaviour heldObject.transform.localPosition = Vector3.zero; heldObject.transform.localRotation = Quaternion.identity; + if (trick != null) + { + trick.SetCarried(true); + } + // Disable physics so it doesn't fall or collide with the player if (heldObject.TryGetComponent(out Rigidbody2D rb)) { @@ -181,11 +186,6 @@ public class PlayerPickupController : MonoBehaviour rb.angularVelocity = 0f; } - if (trick != null) - { - trick.SetCarried(true); - } - if (playerController != null) { playerController.enabled = false; diff --git a/Assets/Scripts/TrickController.cs b/Assets/Scripts/TrickController.cs index bf9496f..e657385 100644 --- a/Assets/Scripts/TrickController.cs +++ b/Assets/Scripts/TrickController.cs @@ -46,6 +46,7 @@ public class TrickController : MonoBehaviour private Vector2 wanderDirection; private float nextWanderTime; private bool isCarried; + private bool isBasketed; private float nextConsumeTime; private float desiredXVelocity; private float nextJumpTime; @@ -54,6 +55,19 @@ public class TrickController : MonoBehaviour private readonly RaycastHit2D[] wallHits = new RaycastHit2D[4]; private float wallAvoidUntil; private float wallAvoidDirection; + private RigidbodyType2D basketBodyType; + private float basketGravityScale; + private bool basketSimulated; + private bool hasBasketAnchor; + private Vector2 basketAnchorPosition; + [SerializeField] private string basketSortingLayer = ""; + [SerializeField] private int basketSortingOrder = -10; + private SpriteRenderer cachedRenderer; + private string originalSortingLayer; + private int originalSortingOrder; + + public bool IsCarried => isCarried; + public bool IsBasketed => isBasketed; private void Awake() { @@ -63,6 +77,7 @@ public class TrickController : MonoBehaviour body.freezeRotation = true; body.collisionDetectionMode = CollisionDetectionMode2D.Continuous; body.interpolation = RigidbodyInterpolation2D.Interpolate; + cachedRenderer = GetComponent(); } private void OnEnable() @@ -88,6 +103,11 @@ public class TrickController : MonoBehaviour return; } + if (isBasketed) + { + return; + } + var position = transform.position; if (position.x < despawnMinX || position.x > despawnMaxX || position.y < despawnMinY || position.y > despawnMaxY) { @@ -131,6 +151,19 @@ public class TrickController : MonoBehaviour return; } + if (isBasketed) + { + if (hasBasketAnchor) + { + transform.position = basketAnchorPosition; + } + var held = body.linearVelocity; + held.x = 0f; + held.y = 0f; + body.linearVelocity = held; + return; + } + if (Time.time < movementLockUntil) { return; @@ -448,6 +481,10 @@ public class TrickController : MonoBehaviour public void SetCarried(bool carried) { isCarried = carried; + if (carried && isBasketed) + { + SetBasketed(false); + } if (carried) { target = null; @@ -475,6 +512,62 @@ public class TrickController : MonoBehaviour } } + public void SetBasketed(bool basketed, Vector2? anchorPosition = null) + { + if (isBasketed == basketed) + { + return; + } + + isBasketed = basketed; + if (basketed) + { + if (cachedRenderer != null) + { + originalSortingLayer = cachedRenderer.sortingLayerName; + originalSortingOrder = cachedRenderer.sortingOrder; + if (!string.IsNullOrWhiteSpace(basketSortingLayer)) + { + cachedRenderer.sortingLayerName = basketSortingLayer; + } + cachedRenderer.sortingOrder = basketSortingOrder; + } + if (anchorPosition.HasValue) + { + hasBasketAnchor = true; + basketAnchorPosition = anchorPosition.Value; + } + else + { + hasBasketAnchor = false; + } + basketBodyType = body.bodyType; + basketGravityScale = body.gravityScale; + basketSimulated = body.simulated; + body.simulated = true; + body.bodyType = RigidbodyType2D.Kinematic; + body.gravityScale = 0f; + body.linearVelocity = Vector2.zero; + body.angularVelocity = 0f; + nextJumpTime = Time.time + 0.1f; + } + else + { + if (cachedRenderer != null) + { + if (!string.IsNullOrWhiteSpace(basketSortingLayer)) + { + cachedRenderer.sortingLayerName = originalSortingLayer; + } + cachedRenderer.sortingOrder = originalSortingOrder; + } + hasBasketAnchor = false; + body.simulated = basketSimulated; + body.bodyType = basketBodyType; + body.gravityScale = basketGravityScale; + } + } + public void OnThrown() { movementLockUntil = Time.time + throwControlLockTime;