A game component that implements a time-freeze ability. When activated it finds nearby game objects within a sphere, stores their Rigidbody and CharacterController components, zeros their velocities each update while active, tints ModelRenderer components, plays a particle and sound, and then restores state when the duration ends and starts a cooldown.
using Sandbox;
using System.Collections.Generic;
using System.Linq;
public sealed class TimeFreezeAbility : Component
{
[Property] public float FreezeDuration { get; set; } = 4f;
[Property] public float Cooldown { get; set; } = 20f;
[Property] public float FreezeRadius { get; set; } = 500f;
[Property] public GameObject FreezeParticle { get; set; }
[Property] public SoundEvent FreezeSound { get; set; }
public bool IsFreezeActive { get; private set; } = false;
private TimeSince timeSinceActivated;
private TimeSince timeSinceCooldown;
private bool isCoolingDown = false;
private List<Rigidbody> frozenRigidbodies = new();
private List<CharacterController> frozenControllers = new();
private Dictionary<ModelRenderer, Color> originalRendererColors = new();
protected override void OnUpdate()
{
if ( IsFreezeActive && timeSinceActivated >= FreezeDuration )
{
UnfreezeTime();
}
if ( Input.Pressed( "use" ) )
{
TryActivateFreeze();
}
if ( IsFreezeActive )
{
foreach ( var rb in frozenRigidbodies )
{
if ( rb.IsValid() )
{
rb.Velocity = Vector3.Zero;
rb.AngularVelocity = Vector3.Zero;
}
}
foreach ( var cc in frozenControllers )
{
if ( cc.IsValid() )
{
cc.Velocity = Vector3.Zero;
}
}
}
}
public void TryActivateFreeze()
{
if ( IsFreezeActive ) return;
if ( isCoolingDown && timeSinceCooldown < Cooldown ) return;
IsFreezeActive = true;
timeSinceActivated = 0;
frozenRigidbodies.Clear();
frozenControllers.Clear();
originalRendererColors.Clear();
if ( FreezeParticle.IsValid() )
{
FreezeParticle.Clone( WorldPosition );
}
if ( FreezeSound != null )
{
Sound.Play( FreezeSound, WorldPosition );
}
var startPos = WorldPosition;
var endPos = WorldPosition + Vector3.Up * 1f;
var hits = Scene.Trace.Sphere( FreezeRadius, startPos, endPos )
.IgnoreGameObject( GameObject )
.RunAll();
foreach ( var hit in hits )
{
if ( !hit.GameObject.IsValid() ) continue;
var targetObj = hit.GameObject.Root;
var rb = targetObj.Components.Get<Rigidbody>();
if ( rb.IsValid() && !frozenRigidbodies.Contains( rb ) )
{
frozenRigidbodies.Add( rb );
}
var cc = targetObj.Components.Get<CharacterController>();
if ( cc.IsValid() && !frozenControllers.Contains( cc ) )
{
frozenControllers.Add( cc );
}
var renderers = targetObj.Components.GetAll<ModelRenderer>();
foreach ( var mr in renderers )
{
if ( mr.IsValid() && !originalRendererColors.ContainsKey( mr ) )
{
originalRendererColors[mr] = mr.Tint;
mr.Tint = new Color( 0.3f, 0.7f, 1.0f, 1.0f );
}
}
}
Log.Info( $"[TimeFreeze] Frozen {frozenRigidbodies.Count} Rigidbodies and {frozenControllers.Count} Controllers!" );
}
private void UnfreezeTime()
{
IsFreezeActive = false;
isCoolingDown = true;
timeSinceCooldown = 0;
foreach ( var kvp in originalRendererColors )
{
if ( kvp.Key.IsValid() )
{
kvp.Key.Tint = kvp.Value;
}
}
frozenRigidbodies.Clear();
frozenControllers.Clear();
originalRendererColors.Clear();
Log.Info( "[TimeFreeze] Time Freeze Expired!" );
}
}