The VScript Book

Chapter 4.3: Seeing the World with TraceLine

How does a script know if there's a wall between a security camera and a player? How does it know where exactly a player is looking? The answer is raycasting. A raycast is like an invisible, instantaneous laser beam that you can fire from one point to another to see what it hits.

The standard VScript function for this is TraceLine(start_vector, end_vector, entity_to_ignore).

Understanding the Result: The Fraction

This is the most confusing part for beginners. TraceLine does not return the position it hit. Instead, it returns a fraction (a float between 0.0 and 1.0).

  • A fraction of 1.0 means the trace did NOT hit anything. It reached its end point successfully.
  • A fraction less than 1.0 means the trace hit something. The value represents how far along the line the hit occurred. For example, 0.5 means it hit exactly halfway.

Getting the Hit Position

To get the actual coordinates of the hit, you need to do a little bit of vector math:

local startPos = Vector(0, 0, 0)
local endPos = Vector(100, 0, 0)

local traceFraction = TraceLine(startPos, endPos, null)

if (traceFraction < 1.0) {
    // The trace hit something! Let's find out where.
    local direction = endPos - startPos
    local hitPos = startPos + (direction * traceFraction)
    printl("Trace hit a wall at: " + hitPos)
} else {
    printl("The path is clear!")
}

Critical Limitation: The standard TraceLine function can only detect collisions with world geometry (walls, floors, ceilings). It completely ignores dynamic props, players, cubes, and all other entities. This makes it useful for checking line of sight to a location, but not for seeing if an entity is in the way.

Practical Example: A Security Beam

Let's create a security beam between two points. If the player walks through, the beam is broken.

In Hammer: Create two info_target entities named `beam_start` and `beam_end`.

In your logic_script:

// This think function will check the beam's status continuously.
function CheckSecurityBeam() {
    local startEnt = Entities.FindByName(null, "beam_start")
    local endEnt = Entities.FindByName(null, "beam_end")

    if (startEnt && endEnt) {
        local startPos = startEnt.GetOrigin()
        local endPos = endEnt.GetOrigin()

        local fraction = TraceLine(startPos, endPos, null)

        if (fraction < 1.0) {
            printl("SECURITY BEAM BLOCKED BY A WALL!")
        } else {
            // The beam is not blocked by a wall, but is the entities like func_brush in the way?
            // This is where TraceLine fails. We would need more advanced techniques
            // to check for entities blocking the path.
        }
    }
    return 0.1 // Check again in 0.1 seconds.
}

This example highlights both the usefulness and the limitations of TraceLine. You've now reached the edge of what's possible with the standard VScript API, and are ready to learn about libraries that expand these capabilities.