groupsCommunity

Common Pitfalls and How to Avoid Them

calendar_today May 3, 2026 schedule ~1 min read person patrickjr verified 50

s&box Common Pitfalls and How to Avoid Them

Community-compiled list of frequently encountered issues and their solutions, based on Discord support channels and official docs.

1. Component Reference Caching

The Pitfall: Calling GetComponent() every frame causes performance issues. The Fix: Cache references in OnAwake or OnStart:
CSHARP
public class Player : Component
{
    // ❌ Bad: Getting component every frame
    protected override void OnUpdate()
    {
        GetComponent<Health>().Damage(10); // Expensive!
    }
    
    // ✅ Good: Cache the reference
    Health HealthComponent { get; set; }
    
    protected override void OnAwake()
    {
        HealthComponent = Components.Get<Health>();
    }
    
    protected override void OnUpdate()
    {
        HealthComponent?.Damage(10);
    }
}

2. Not Checking IsProxy in Multiplayer

The Pitfall: All clients run the same logic, causing desync. The Fix: Always check if you're the owner or proxy:
CSHARP
protected override void OnUpdate()
{
    // ❌ Bad: Everyone runs this
    MovePlayer();
    
    // ✅ Good: Only owner runs simulation
    if (IsProxy) return;
    MovePlayer();
}

3. Forgetting GameObject.IsValid()

The Pitfall: Accessing destroyed GameObjects causes null reference exceptions. The Fix: Use IsValid() before accessing:
CSHARP
void UpdateTarget(GameObject newTarget)
{
    // ✅ Good: Check validity first
    if (Target.IsValid())
    {
        Target.Tags.Remove("targeted");
    }
    
    Target = newTarget;
    
    if (Target.IsValid())
    {
        Target.Tags.Add("targeted");
    }
}

4. Calling Destroy() on Components Wrong

The Pitfall: Component.Destroy() doesn't exist. The Fix: Destroy the GameObject, not the Component:
CSHARP
// ❌ Bad: This won't compile
GetComponent<Bullet>().Destroy();

// ✅ Good: Destroy the GameObject
GameObject.Destroy();

5. Scene.Load in Synchronous Context

The Pitfall: Forgetting await causes race conditions. The Fix: Always await scene loads:
CSHARP
// ❌ Bad: Fire and forget
Scene.Load("next_level.scene");
ShowLoadingScreen(); // Might show after scene loads!

// ✅ Good: Proper async flow
async void LoadNextLevel()
{
    ShowLoadingScreen();
    await Scene.Load("next_level.scene");
    HideLoadingScreen();
}

6. Using LINQ in Hot Paths

The Pitfall: LINQ allocates and is slow for frequent operations. The Fix: Use regular loops for performance-critical code:
CSHARP
// ❌ Bad: Allocates every frame
var enemies = Scene.GetAllObjects().Where(o => o.Tags.Has("enemy"));

// ✅ Good: Zero allocation
foreach (var obj in Scene.GetAllObjects())
{
    if (obj.Tags.Has("enemy"))
    {
        ProcessEnemy(obj);
    }
}

7. Modifying Sync Properties from Non-Owners

The Pitfall: Changes get overwritten by the owner. The Fix: Only the owner modifies Sync properties:
CSHARP
[Sync] public int Health { get; set; }

void TakeDamage(int amount)
{
    // ✅ Good: Only owner applies damage
    if (!Network.IsOwner) return;
    Health -= amount;
}

Source

  • Official docs: https://sbox.game/dev/doc
  • Discord community support patterns
  • Facepunch developer recommendations
Was this helpful?