Elysium scripting Editor API index
Build

Camera & projection

Read and move the camera, change the field of view, and turn a point in the world into a point on the screen.

Two things belong here: the camera itself - where it is, which way it looks, how wide - and projection: finding where a point in the world lands on your screen, which is how a script draws a label above a player's head.

-- a dot above every enemy's head
on( "frame", function( canvas )
	for _, player in ipairs( ents.players( { enemies = true, alive = true } ) ) do
		local head = player:bone( ents.bone.head )

		if head then
			local x, y, on_screen = view.project( head + vec3( 0, 0, 12 ) )

			if on_screen then
				canvas:disc( x, y, 3, color( 244, 63, 94 ) )
			end
		end
	end
end )

#Angles and units

Angles are vec3s of degrees: x is the pitch (positive looks down, negative up), y the yaw (0 looks along +X, 90 along +Y, counter-clockwise seen from above), z the roll, which is 0 in practice. Positions are world units. vec3:forward(), right() and up() turn angles into direction vectors.

#The camera as the game renders it

#view.origin() → vec3

Where the camera is.

#view.angles() → vec3

Which way it looks.

#view.fov() → number

The field of view in degrees.

#view.ready() → boolean

Whether the game has produced a camera yet. Before it has - on the main menu, while a map loads - the other calls have nothing meaningful to answer, and project answers nil.

#view.project( position ) → x, y, on_screen, depth | nil

Where a world point lands on the screen. position is a vec3. Returns four values:

  • x, y - the pixel the point projects to, from the top left corner of the screen.
  • on_screen - true when the point is in front of the camera and inside the picture. When it is false the point is behind the camera or outside the view, and x and y are not meaningful - do not draw there.
  • depth - how far in front of the camera the point is, roughly in world units. Use it to scale things with distance.

Returns nil instead when there is no camera yet.

local x, y, on_screen, depth = view.project( point )

if on_screen then
	local size = math.clamp( 600 / depth, 4, 24 )        -- smaller when farther away
	canvas:disc( x, y, size, color( 255, 255, 255 ) )
end

#Changing the camera

The view event hands a script the camera as it is about to be rendered, after the cheat's own camera work. The object it receives reads and writes the camera directly, so a change made there is what the game draws. It is only valid inside the handler - using one you kept raises this view is only valid inside the view event that received it.

on( "view", function( view )
	view:set_fov( 105 )
end )

#v:origin() → vec3

The camera's position.

#v:set_origin( position )

Moves the camera. The player does not move - only what you see does.

#v:angles() → vec3

The camera's angles.

#v:set_angles( angles )

Turns the camera without turning the player: the game's picture changes, the command and the server's view of where you look do not.

#v:fov() → number

#v:set_fov( degrees )

Sets the field of view, clamped to 1 to 170 degrees.

-- a third-person camera: 80 units behind the head
on( "view", function( view )
	local forward = view:angles( ):forward( )
	view:set_origin( view:origin( ) - forward * 80 )
end )

#Putting it together

A name tag that scales with distance and fades out at range:

local font = render.font( "Segoe UI", 14 )

on( "frame", function( canvas )
	local me = ents.local_pawn( )
	if not me or not font then return end

	for _, player in ipairs( ents.players( { enemies = true, alive = true } ) ) do
		local head = player:bone( ents.bone.head )

		if head then
			local x, y, on_screen, depth = view.project( head + vec3( 0, 0, 14 ) )

			if on_screen then
				local fade  = math.clamp( 1 - depth / 2500, 0.15, 1 )
				local label = string.format( "%s  %d", player:name( ), player:health( ) )

				canvas:text( x, y, label, color( 255, 255, 255, 255 * fade ),
					{ font = font, align = "center", valign = "bottom", shadow = true } )
			end
		end
	end
end )
Elysium scripting guide · built 2026-10-03