PowerUps/PowerUp.cs

A component representing a falling power-up pickup in the Breakout game. It moves downward each fixed update, checks for collision with the paddle to apply PowerUpEffect components, plays a sound and spawns score popups, and destroys itself when collected or when falling past a death Z.

File Access
using System;
using Sandbox;

namespace Breakout;

/// <summary>
/// A pickup that falls from a broken brick. If the paddle catches it, every PowerUpEffect on this
/// object is applied (wide paddle, extra ball, and so on). If it falls past the bottom edge
/// instead, it just disappears.
/// </summary>
[Title( "Power Up" ), Category( "Breakout" ), Icon( "redeem" )]
public sealed class PowerUp : Component
{
	[Property] public float FallSpeed { get; set; } = 130f;

	[Property] public float CatchHeight { get; set; } = 24f;

	public Playfield Playfield { get; set; }
	public BreakoutGame Game { get; set; }

	protected override void OnEnabled()
	{
		Playfield ??= Scene.Get<Playfield>();
		Game ??= Scene.Get<BreakoutGame>();
		GameObject.Tags.Add( "powerup" );
	}

	protected override void OnFixedUpdate()
	{
		if ( Playfield is null )
			return;

		var pos = WorldPosition;
		pos.z -= FallSpeed * Time.Delta;
		pos.y = Playfield.PlayY;
		WorldPosition = pos;

		var paddle = Scene.Get<Paddle>();
		if ( paddle.IsValid() && IsCaughtBy( paddle, pos ) )
		{
			ApplyEffects( paddle );
			GameObject.Destroy();
			return;
		}

		if ( pos.z < Playfield.DeathZ )
			GameObject.Destroy();
	}

	private bool IsCaughtBy( Paddle paddle, Vector3 pos )
	{
		float dx = MathF.Abs( pos.x - paddle.WorldPosition.x );
		float dz = MathF.Abs( pos.z - paddle.WorldPosition.z );
		return dx <= paddle.HalfWidth && dz <= CatchHeight;
	}

	private void ApplyEffects( Paddle paddle )
	{
		var caught = Sound.Play( "ball_impact_pad_01" );
		if ( caught is not null )
			caught.Pitch = 1.7f;

		Game?.ShakeCamera( 0.25f );

		foreach ( var effect in Components.GetAll<PowerUpEffect>( FindMode.EnabledInSelfAndDescendants ) )
		{
			effect.Apply( Game, paddle );

			if ( !string.IsNullOrEmpty( effect.Label ) )
				ScorePopup.Spawn( Scene, WorldPosition + Vector3.Up * 20f, effect.Label, effect.LabelColor, 0.3f );
		}
	}
}