Detect (and count) collision between vehicle and NPC

Hi all,

I am wanting to detect and count how many NPCs collide with a player controlled helicopter. Not sure if it is possible to attach a volume to the spawned heli (or player with offset to be centered to vehicle) or there is another way. I would need the ‘NPC collided with’ count as it is part of gameplay.

Also is is possible to detect the impact speed so I can see if the NPC has been struck fatally or just clipped.

Thank you

Hey @OddButAwesome how are you?

I think the best way to do this is by using “proximity polling”, which consist in check continuously if the helicopter is close enough to a NPC. You will need to do it with Verse, of course.

I’ve been doing this to check collisions between players so I adapted my Verse code a little bit to match your requirements! The code is completely commented so you can understand what is happening in each function.

using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Fortnite.com/Game }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }

# Creative device that counts how many times a player-driven helicopter
# collides with NPCs, using periodic proximity checks (polling).
helicopter_npc_collision_counter := class(creative_device):

    # The helicopter vehicle spawner placed in the island editor.
    # Drag your Vehicle Spawner Helicopter device here.
    @editable
    HelicopterSpawner : vehicle_spawner_helicopter_device = vehicle_spawner_helicopter_device{}

    # All NPC Spawner devices placed in the island.
    # Drag every NPC Spawner you want to track here.
    @editable
    NPCSpawners : []npc_spawner_device = array{}

    # Seconds between each proximity check.
    # Lower values are more responsive but more CPU-expensive.
    @editable
    CheckInterval : float = 0.1

    # Distance in Unreal Units to consider a helicopter "colliding" with an NPC.
    @editable
    CollisionRadius : float = 350.0

    # Minimum seconds that must pass before the same NPC
    # can register another collision with the helicopter.
    # Prevents counting the same hit dozens of times per second.
    @editable
    CooldownPerNPC : float = 1.5

    # Running total of all collisions detected during the session.
    var TotalCollisions : int = 0

    # Holds the agent currently driving the helicopter, or false if empty.
    # Using ?agent (option type) to represent the presence or absence of a driver.
    var HelicopterPlayer : ?agent = false

    # Tracks all NPC fort_characters currently alive in the island.
    # NPCs are added on spawn and removed on elimination.
    var LiveNPCCharacters : []fort_character = array{}

    # Maps each live NPC fort_character to the timestamp of its last collision.
    # Used to enforce the per-NPC cooldown between hits.
    var LastHitTime : [fort_character]float = map{}

    # Entry point: called once when the device starts.
    # Subscribes to vehicle and NPC events, then starts the detection loop.
    OnBegin<override>()<suspends> : void =
        # Track which agent enters or exits the helicopter
        HelicopterSpawner.AgentEntersVehicleEvent.Subscribe(OnPlayerEnterVehicle)
        HelicopterSpawner.AgentExitsVehicleEvent.Subscribe(OnPlayerExitVehicle)

        # Subscribe to each NPC Spawner's spawn event so we can
        # capture the fort_character of every NPC as soon as it appears
        for (Spawner : NPCSpawners):
            Spawner.SpawnedEvent.Subscribe(OnNPCSpawned)

        StartDetectionLoop()

    # Called when an agent enters the helicopter.
    # Stores the driver so the detection loop can track its position.
    OnPlayerEnterVehicle(Player : agent) : void =
        set HelicopterPlayer = option{Player}

    # Called when an agent exits the helicopter.
    # Clears the stored driver so collision checks are skipped while empty.
    OnPlayerExitVehicle(Player : agent) : void =
        set HelicopterPlayer = false

    # Called when any NPC spawns from one of the registered NPC Spawners.
    # Casts the agent to fort_character and adds it to the live NPC list.
    OnNPCSpawned(SpawnedAgent : agent) : void =
        if (NPCCharacter := SpawnedAgent.GetFortCharacter[]):
            set LiveNPCCharacters = LiveNPCCharacters + array{NPCCharacter}

            # Subscribe to this NPC's elimination event so we can
            # remove it from the live list when it dies
            NPCCharacter.EliminatedEvent().Subscribe(OnNPCEliminated)

    # Called when a tracked NPC is eliminated.
    # Rebuilds the live NPC array excluding the eliminated character,
    # avoiding stale references in future proximity checks.
    OnNPCEliminated(Result : elimination_result) : void =
        EliminatedCharacter := Result.EliminatedCharacter
        var NewLiveNPCs : []fort_character = array{}
        # Manually filter the array since Filter() is not available in this API version
        for (C : LiveNPCCharacters):
            if (C <> EliminatedCharacter):
                set NewLiveNPCs = NewLiveNPCs + array{C}
        set LiveNPCCharacters = NewLiveNPCs

    # Runs forever, triggering a proximity check every CheckInterval seconds.
    StartDetectionLoop()<suspends> : void =
        loop:
            CheckHelicopterNPCCollisions()
            Sleep(CheckInterval)

    # Core detection logic executed on each polling tick.
    # Only runs if there is a driver in the helicopter.
    # Compares the helicopter's position against every live NPC,
    # and registers a collision if within radius and outside cooldown.
    CheckHelicopterNPCCollisions() : void =
        if (Player := HelicopterPlayer?):
            # The player's fort_character position mirrors the vehicle's position in Verse
            if (PlayerCharacter := Player.GetFortCharacter[]):
                VehiclePos := PlayerCharacter.GetTransform().Translation

                for (NPCCharacter : LiveNPCCharacters):
                    NPCPos := NPCCharacter.GetTransform().Translation

                    # Check if the NPC is within the collision radius
                    if (Distance(VehiclePos, NPCPos) <= CollisionRadius):
                        CurrentTime := GetSimulationElapsedTime()
                        LastTime    := GetLastHitTime(NPCCharacter)

                        # Only register a hit if the cooldown has elapsed for this NPC
                        if (CurrentTime - LastTime >= CooldownPerNPC):
                            RegisterCollision(Player, NPCCharacter, CurrentTime)

    # Returns the last hit timestamp for a given NPC.
    # Returns 0.0 if the NPC has never been hit, allowing an immediate first collision.
    GetLastHitTime(NPC : fort_character) : float =
        if (T := LastHitTime[NPC]):
            T
        else:
            0.0

    # Records a valid collision: increments the counter,
    # updates the NPC's cooldown timestamp, and prints a debug message.
    RegisterCollision(Player : agent, NPC : fort_character, HitTime : float) : void =
        set TotalCollisions = TotalCollisions + 1
        # Store the current time as the last hit time for this NPC
        if (set LastHitTime[NPC] = HitTime) {}
        Print("Helicopter hit NPC! Total collisions: {TotalCollisions}")

After you implement this code, you need to drop the new device on your island and add your helicopter spawner and all your NPC spawners to the array.

With that, you will be ready to detect collisions with your helicopter! You can modify some variables directly on the editor to improve the feeling such as the collission check interval, the collision radius and the cooldown before checking again against the same npc!

Quick clarification, this code was created to work with the NPC_Spawner_Device but you can replace it by any spawner device that spawns agents.

Another quick clarification: sadly, I didn’t find a way to check the speed at the moment of collision, so it will only count collisions no matter the speed.

Hope this works for you!

Wow! thank you so much. I will try this tomorrow when I get home from work.