| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- using UnityEngine;
- using UnityEngine.InputSystem;
- [RequireComponent(typeof(Rigidbody2D))]
- [RequireComponent(typeof(Collider2D))]
- public class BallScript : MonoBehaviour
- {
- [Header("Launch")]
- [SerializeField] private float launchSpeed = 10f;
- [Tooltip("Maximum random angle (degrees) from straight up on launch.")]
- [SerializeField] private float maxLaunchAngle = 30f;
- [Tooltip("Base damage before ball effects modify it.")]
- [SerializeField] private float baseDamage = 1f;
- [Header("References")]
- [Tooltip("Assign the Paddle transform in the Inspector, or tag the paddle 'Paddle' for auto-find.")]
- [SerializeField] private Transform paddle;
- [Tooltip("Offset above the paddle centre where the ball sits before launch.")]
- [SerializeField] private Vector3 spawnOffset = new Vector3(0f, 0.7f, 0f);
- public float LaunchSpeed => launchSpeed;
- public float BaseDamage => baseDamage;
- public Rigidbody2D Body => _rb;
- public bool IsQueueManaged => _isQueueManaged;
- public bool IsInPlay => _rb != null && _rb.simulated && _collider != null && _collider.enabled;
- private Rigidbody2D _rb;
- private Collider2D _collider;
- private Renderer _renderer;
- private Material[] _baseMaterials;
- private bool _launched;
- private bool _followPaddleWhenUnlaunched = true;
- private bool _isQueueManaged;
- private BallEffectBase[] _effects;
- private Vector3 _initialScale;
- private float _damageMultiplier = 1f;
- private Vector2 _prePhysicsVelocity;
- void Awake()
- {
- _rb = GetComponent<Rigidbody2D>();
- _collider = GetComponent<Collider2D>();
- _renderer = GetComponentInChildren<Renderer>();
- _effects = GetComponents<BallEffectBase>();
- _rb.gravityScale = 0f;
- _rb.linearDamping = 0f;
- _rb.angularDamping = 0f;
- _rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
- _initialScale = transform.localScale;
- // Use physics to handle bouncing naturally - bounciness = 1 means perfect reflection.
- // Friction = 0 means no slowdown.
- var mat = new PhysicsMaterial2D { bounciness = 1f, friction = 0f };
- _collider.sharedMaterial = mat;
- if (_renderer != null)
- _baseMaterials = _renderer.sharedMaterials;
- }
- void Start()
- {
- if (paddle == null)
- paddle = GameObject.FindGameObjectWithTag("Paddle")?.transform;
- _launched = false;
- }
- void Update()
- {
- if (_launched || !_followPaddleWhenUnlaunched)
- return;
- // Follow the paddle until launched.
- if (paddle != null)
- transform.position = paddle.position + spawnOffset;
- bool spacePressed = Keyboard.current != null && Keyboard.current.spaceKey.wasPressedThisFrame;
- bool mousePressed = Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame;
- if (spacePressed || mousePressed)
- Launch();
- }
- void FixedUpdate()
- {
- if (!_launched)
- return;
- // Store incoming velocity for collision-response effects (e.g. piercing).
- _prePhysicsVelocity = _rb.linearVelocity;
- Vector2 velocity = _rb.linearVelocity;
- for (int i = 0; i < _effects.Length; i++)
- {
- BallEffectBase effect = _effects[i];
- if (effect != null && effect.isActiveAndEnabled)
- effect.ModifyVelocity(this, ref velocity, Time.fixedDeltaTime);
- }
- _rb.linearVelocity = velocity;
- }
- public void SetQueueManaged(bool value)
- {
- _isQueueManaged = value;
- }
- public void SetPaddleReference(Transform paddleTransform, Vector3 offset)
- {
- paddle = paddleTransform;
- spawnOffset = offset;
- }
- public void ParkAtPosition(Vector3 worldPosition)
- {
- _launched = false;
- _followPaddleWhenUnlaunched = false;
- _rb.linearVelocity = Vector2.zero;
- _rb.simulated = false;
- _collider.enabled = false;
- transform.position = worldPosition;
- }
- public void PrepareOnPaddle()
- {
- _launched = false;
- _followPaddleWhenUnlaunched = true;
- _rb.simulated = true;
- _collider.enabled = true;
- _rb.linearVelocity = Vector2.zero;
- _damageMultiplier = 1f;
- transform.localScale = _initialScale;
- if (paddle == null)
- paddle = GameObject.FindGameObjectWithTag("Paddle")?.transform;
- if (paddle != null)
- transform.position = paddle.position + spawnOffset;
- for (int i = 0; i < _effects.Length; i++)
- {
- BallEffectBase effect = _effects[i];
- if (effect != null && effect.isActiveAndEnabled)
- effect.OnBallReset(this);
- }
- }
- /// <summary>Called by DeathZoneScript for fallback non-queue behavior.</summary>
- public void ResetBall()
- {
- PrepareOnPaddle();
- }
- public void SetEffectDefinition(BallEffectDefinition effectDefinition)
- {
- // Disable all currently attached effects first.
- BallEffectBase[] allEffects = GetComponents<BallEffectBase>();
- for (int i = 0; i < allEffects.Length; i++)
- allEffects[i].enabled = false;
- ApplyOverlayMaterial(effectDefinition != null ? effectDefinition.OverlayMaterial : null);
- if (effectDefinition == null)
- {
- Debug.Log($"[{gameObject.name}] SetEffectDefinition: Received NULL definition (no effect assigned)");
- RebuildEffectsCache();
- return;
- }
- Debug.Log($"[{gameObject.name}] SetEffectDefinition: Assigning effect type: {effectDefinition.EffectType}");
- MonoBehaviour selected = null;
- switch (effectDefinition.EffectType)
- {
- case BallEffectType.TripleSplit:
- Debug.Log($"[{gameObject.name}] Matched TripleSplit case");
- selected = GetOrAddEffect<TripleSplitEffect>();
- break;
- case BallEffectType.Explosive:
- Debug.Log($"[{gameObject.name}] Matched Explosive case");
- selected = GetOrAddEffect<ExplosiveHitEffect>();
- break;
- case BallEffectType.Piercing:
- Debug.Log($"[{gameObject.name}] Matched Piercing case");
- selected = GetOrAddEffect<PiercingEffect>();
- break;
- case BallEffectType.Homing:
- Debug.Log($"[{gameObject.name}] Matched Homing case");
- selected = GetOrAddEffect<HomingEffect>();
- break;
- case BallEffectType.Teleport:
- Debug.Log($"[{gameObject.name}] Matched Teleport case");
- selected = GetOrAddEffect<TeleportEffect>();
- break;
- case BallEffectType.None:
- default:
- Debug.LogError($"[{gameObject.name}] ERROR: EffectType is None or unrecognized: {effectDefinition.EffectType}");
- break;
- }
- if (selected is BallEffectBase effectBase)
- {
- Debug.Log($"[{gameObject.name}] Successfully enabled effect: {selected.GetType().Name}");
- effectBase.SetDefinition(effectDefinition);
- selected.enabled = true;
- }
- else
- {
- Debug.LogError($"[{gameObject.name}] ERROR: selected component is not a BallEffectBase! Type: {selected?.GetType().Name ?? "NULL"}");
- }
- RebuildEffectsCache();
- }
- public void ApplyOverlayMaterial(Material overlayMaterial)
- {
- if (_renderer == null)
- return;
- if (overlayMaterial == null)
- {
- _renderer.sharedMaterials = _baseMaterials;
- return;
- }
- Material[] stacked = new Material[_baseMaterials.Length + 1];
- for (int i = 0; i < _baseMaterials.Length; i++)
- stacked[i] = _baseMaterials[i];
- stacked[stacked.Length - 1] = overlayMaterial;
- _renderer.sharedMaterials = stacked;
- }
- public float GetDamageAgainstBlock(blockScript block, Collision2D collision)
- {
- float damage = baseDamage * _damageMultiplier;
- for (int i = 0; i < _effects.Length; i++)
- {
- BallEffectBase effect = _effects[i];
- if (effect != null && effect.isActiveAndEnabled)
- damage = effect.ModifyBlockDamage(this, block, collision, damage);
- }
- return Mathf.Max(0f, damage);
- }
- public void NotifyHitBlock(blockScript block, Collision2D collision)
- {
- for (int i = 0; i < _effects.Length; i++)
- {
- BallEffectBase effect = _effects[i];
- if (effect != null && effect.isActiveAndEnabled)
- effect.OnHitBlock(this, block, collision);
- }
- }
- public void MultiplyDamage(float multiplier)
- {
- _damageMultiplier *= multiplier;
- }
- public void MultiplyScale(float scaleMultiplier)
- {
- transform.localScale *= scaleMultiplier;
- }
- public float GetCurrentSpeed()
- {
- float speed = _rb.linearVelocity.magnitude;
- return speed > 0.001f ? speed : launchSpeed;
- }
- public Vector2 GetPrePhysicsVelocity()
- {
- if (_prePhysicsVelocity.sqrMagnitude > 0.0001f)
- return _prePhysicsVelocity;
- return _rb.linearVelocity;
- }
- public void LaunchInDirection(Vector2 direction, float speed)
- {
- _launched = true;
- _followPaddleWhenUnlaunched = false;
- _rb.simulated = true;
- _collider.enabled = true;
- Vector2 dir = direction.sqrMagnitude > 0.0001f ? direction.normalized : Vector2.up;
- _rb.linearVelocity = dir * speed;
- }
- public BallScript SpawnClone(Vector2 direction, float speedMultiplier, float scaleMultiplier, float damageMultiplier)
- {
- GameObject cloneObject = Instantiate(gameObject, transform.position, Quaternion.identity);
- BallScript cloneBall = cloneObject.GetComponent<BallScript>();
- if (cloneBall == null)
- return null;
- // Clones are temporary gameplay balls and do not consume queue slots.
- cloneBall.SetQueueManaged(false);
- cloneBall.SetPaddleReference(paddle, spawnOffset);
- cloneBall.LaunchInDirection(direction, launchSpeed * speedMultiplier);
- cloneBall.MultiplyScale(scaleMultiplier);
- cloneBall.MultiplyDamage(damageMultiplier);
- return cloneBall;
- }
- private void Launch()
- {
- _launched = true;
- float angle = Random.Range(-maxLaunchAngle, maxLaunchAngle) * Mathf.Deg2Rad;
- _rb.linearVelocity = new Vector2(Mathf.Sin(angle), Mathf.Cos(angle)) * launchSpeed;
- for (int i = 0; i < _effects.Length; i++)
- {
- BallEffectBase effect = _effects[i];
- if (effect != null && effect.isActiveAndEnabled)
- effect.OnBallLaunched(this);
- }
- }
- private T GetOrAddEffect<T>() where T : MonoBehaviour
- {
- T existing = GetComponent<T>();
- if (existing != null)
- return existing;
- return gameObject.AddComponent<T>();
- }
- private void RebuildEffectsCache()
- {
- _effects = GetComponents<BallEffectBase>();
- }
- }
|