facepunch/sandbox

repository·main·Indexed 16 days ago

https://github.com/facepunch/sandbox

A multiplayer sandbox game built using the s&box engine and tools. The documentation provides detailed implementation guides for creating custom NPCs by subclassing Npc, configuring factions and relationships, and utilizing the tick-based thinking loop consisting of Senses, Decision Making (Schedules), and Execution (Tasks).

Tokens
1.5K
Snippets
2
Records
6
Agent score
14%

What's inside facepunch-sandbox

  1. How NPCs work in Sandbox

    main

    NPCs operate on a tick-based thinking loop consisting of three stages:

    1. Senses: The NPC gathers perceptions (visible/audible stimuli, disturbances) into an Awareness flag set.
    2. Decision Making: The GetSchedule() method is called to select the highest-priority behavior that applies to the current state.
    3. Execution: The active schedule executes a sequence of tasks (e.g., moving, looking, firing, or waiting).

    Preemption: Higher-priority schedules automatically interrupt lower-priority ones when awareness changes. For example, an NPC will automatically drop a WanderSchedule to engage in a CombatEngageSchedule the moment it detects a threat, meaning you do not need to manually poll for state changes.

    /* The NPC thinking loop logic */
    // 1. Senses gather awareness
    // 2. GetSchedule() picks behavior
    // 3. Schedule runs tasks
  2. Configure NPC Factions and Relationships

    main

    An NPC's Faction defines its identity. Common values include Factions.Player, Factions.Ally, Factions.Enemy, Factions.Citizen, and Factions.Monster, though you can use custom strings.

    Defining Relationships

    In the SetupRelationships() method, use the following to define faction attitudes:

    • Hates(FactionA, FactionB)
    • Fears(FactionA, FactionB)
    • Likes(FactionA, FactionB)

    Note: Any faction not explicitly listed is treated as neutral. Members of the same faction are friendly by default.

    Runtime Disposition

    To change a relationship for a specific individual at runtime (e.g., making a specific player an enemy), use: SetDisposition(other, Disposition.Hostile)

  3. Use Schedules and Tasks for NPC behavior

    main

    NPC behavior is composed of two hierarchical layers:

    • Schedules (ScheduleBase): A collection of tasks. Schedules are built in the OnStart method using AddTask(...) and have a Priority. Reusable schedules like WanderSchedule, InvestigateSchedule, and FollowSchedule are available in the Schedules/ directory.
    • Tasks (TaskBase): Individual steps within a schedule. A task returns Running, Success, or Failed each tick. Reusable tasks like MoveTo, LookAt, Wait, and FireWeapon are available in the Tasks/ directory.

    Important: When using GetSchedule<T>(), the system returns a cached, reused instance. You must set the necessary input parameters on the instance every time you return it from GetSchedule().

  4. Set up an NPC Prefab

    main

    While the AI layers are added automatically, an NPC prefab requires specific components to function correctly. It is recommended to copy an existing prefab from Assets/entities/sents/npc/ rather than building from scratch.

    Required components:

    • NavMeshAgent: Required for movement and pathfinding. (Note: Purely physics-driven NPCs like rollermines may skip this).
    • SkinnedModelRenderer: Provides the visual model; must be wired to the Renderer field.
    • Collider: Provides a solid body for combat and interaction. Do not use the playercontroller tag on this collider.
    • Rigidbody: Required for physics interactions such as being moved by a physgun or triggering ragdolls.
  5. Create a custom NPC by subclassing Npc

    main

    To create a new NPC, subclass Npc and implement the following three core requirements:

    1. Identity: Override the Faction property to define who the NPC is (e.g., Factions.Ally).
    2. Relationships: Override SetupRelationships() to define how the NPC feels about other factions using Hates(), Fears(), or Likes().
    3. Behavior: Override GetSchedule() to return the appropriate ScheduleBase. This method is called every tick; you should use GetSchedule<T>() to retrieve cached instances and update their parameters before returning them.

    Everything else—including senses, navigation, animation, speech, and preemption—is handled by the base class.

    public sealed class GuardNpc : Npc
    {
        // 1. Who am I? (drives how others treat me)
        public override string Faction => Factions.Ally;
    
        // 2. How do I feel about other factions?
        protected override void SetupRelationships()
        {
            Hates( Factions.Enemy, Factions.Monster );
            Likes( Factions.Player );
        }
    
        // 3. What do I want to do right now? (most important first)
        public override ScheduleBase GetSchedule()
        {
            var enemy = Senses.GetBestTarget();
            if ( enemy.IsValid() )
                return GetSchedule<CombatEngageSchedule>(); // configure & return
    
            return GetSchedule<WanderSchedule>();
        }
    }
  6. Interact with NPC Senses and Events

    main

    Sensing the World

    The Senses API allows NPCs to perceive their environment:

    • GetBestTarget(): Returns the highest-priority hostile target.
    • GetNearestVisible(disposition): Finds the nearest entity matching a specific disposition.
    • Disturbance: Represents heard sounds like gunshots or deaths.

    Stimulating NPCs

    To make other NPCs react to an event (like a gunshot), broadcast a stimulus: EmitStimulus(StimulusKind.Gunshot) This will be perceived by other NPCs as a Senses.Disturbance.

    Handling Damage and Death

    Override these methods to implement custom reactions to combat:

    • OnHurt(in DamageInfo): Called when the NPC takes damage.
    • Die(in DamageInfo): Called when the NPC dies. Always call base.Die(in DamageInfo) to ensure proper cleanup.