Entities/Blocks/BlockSmile.cs

A game entity class for a "Smile" block that toggles visibility on impacts and phases. It handles invisible/visible state, alpha fading over time, brief reveal logic for phase 2, plays sound effects when changing visibility, and responds to phase changes, player death and block death.

Native Interop
namespace BlockParty;

/// <summary>
/// Smile block — fades invisible on impact. Phase 1 toggles visible/invisible each impact.
/// Phase 2 stays invisible but briefly fades back in on each impact, then goes invisible
/// again during the eye-cycle pause so it's always invisible before it moves off. While
/// invisible it keeps moving and is still dangerous; its speed-lines still show as a tell.
/// Port of the original BlockSmile (the staggered greyscale fade is realised as an alpha fade).
/// </summary>
public sealed class BlockSmile : Block
{
	const float FADE_TIME = 0.2f;

	bool _invisible;
	bool _briefReveal;
	float _alpha = 1f, _alphaTarget = 1f;

	public override void Impact( Block otherBlock )
	{
		ImpactEffects( otherBlock );
		if ( IsDead ) return;

		if ( Phase == 1 )
		{
			if ( !_invisible ) GoInvisible(); else GoVisible();
		}
		else if ( Phase == 2 )
		{
			if ( !_invisible ) GoInvisible();
			else { GoVisible(); _briefReveal = true; } // re-hidden in OnEyesClosed (before it moves)
		}

		CloseEyesAndPickNewDirection();
	}

	// Eyes have shut and the new direction is chosen — re-hide now so the phase-2 "peek" is
	// always over before the block opens its eyes and moves off.
	protected override void OnEyesClosed()
	{
		if ( _briefReveal )
		{
			_briefReveal = false;
			GoInvisible();
		}
	}

	public override void Tick( float dt )
	{
		base.Tick( dt );

		if ( _alpha != _alphaTarget )
		{
			float step = dt / FADE_TIME;
			_alpha = _alpha < _alphaTarget ? MathF.Min( _alphaTarget, _alpha + step ) : MathF.Max( _alphaTarget, _alpha - step );
			SetLayersAlpha( _alpha );
		}
	}

	void GoInvisible()
	{
		if ( _invisible ) return;
		_invisible = true;
		_alphaTarget = 0f;
		Audio.PlaySfx( SfxType.TurnInvisible, Position );
	}

	void GoVisible()
	{
		if ( !_invisible ) return;
		_invisible = false;
		_alphaTarget = 1f;
		Audio.PlaySfx( SfxType.TurnVisible, Position );
	}

	protected override void NextPhase()
	{
		base.NextPhase();
		_briefReveal = false;
		if ( _invisible ) GoVisible();
	}

	public override void PlayerHasDied()
	{
		_briefReveal = false;
		GoVisible();
	}

	public override void Die()
	{
		base.Die();
		_briefReveal = false;
		GoVisible();
	}
}