Camera, aiming, pictures and local figures
These client features work on the player's own machine.
Free camera
kcds.ui.NoClip flies the camera through walls. In 'body' mode the player's body goes too, so others see it
fly, and it lands where the flight ends. In 'camera' mode only the view moves.
// client const { NoClip, Key } = kcds.ui; Key.bind('f3', () => { if (NoClip.isActive()) { NoClip.disable(); } else { NoClip.enable({ mode: 'body', speed: 20 }); } });
Keys: W A S D move, Space goes up, C goes down, Shift is fast and Alt is slow.
For cutscenes and spectators, use input: false and steer it yourself with NoClip.move(...), setPose,
lookAt and focus(target, distance). NoClip.toScreen(point) and screenLine(x, y) help pick things on
screen. noclipChanged(active, reason) tells you when it ends.
Who may fly
Everyone may fly by default, and the server decides otherwise. Setting player.noclipAllowed = false ends that
player's flight with noclipChanged(false, 'notAllowed'). After that, NoClip.enable() returns 'notAllowed'
and does nothing. The refusal comes from their game, so a client script cannot get around it.
To keep flying for staff, set it when a player joins:
// server kcds.core.Events.on('playerConnect', (player) => { player.noclipAllowed = staff.has(player.steamId); });
Client scripts can check NoClip.isAllowed() and the noclipAllowedChanged(allowed) event, or test the result:
// client const { NoClip, Hud, Key } = kcds.ui; Key.bind('f3', () => { if (NoClip.isActive()) { NoClip.disable(); } else if (NoClip.enable() === 'notAllowed') { Hud.showNotification('Flying is not allowed here.'); } });
For a shot the server controls, see Camera.startShot in Server rules.
Finding what the player looks at
Client scripts can ask their own game about the world and get the answer within a frame or two, with no trip to the server. This suits aiming, fishing, building, or pressing a key while looking at something.
// client const { Key, Camera } = kcds.ui; const { World } = kcds.world; const { Events } = kcds.core; Key.bind('e', () => { const ray = Camera.rayFromScreen({ range: 4 }); const hit = ray && World.raycast(ray.origin, ray.target, { mode: 'anything' }); if (!hit) { return; } if (hit.entityClass === 'AnimDoor') { Events.toServer('useDoor', hit.entityGuid); } else if (hit.surface === 'mat_water') { Events.toServer('fish', hit.position); } else if (hit.category === 'tree') { Events.toServer('chop', { model: hit.model, position: hit.position }); } else if (hit.player) { Events.toServer('greet', hit.player.id); } });
| Call | Result |
|---|---|
Camera.getPose() | { position, forward, right, up, fov, aspectRatio }, or null in a menu or while loading |
Camera.rayFromScreen({ x, y, range }) | { origin, direction, target }; x and y run from -1 to 1, default centre |
World.raycast(from, to, { mode, ignoreSelf }) | the first thing hit, or null |
World.traceAll(from, to, { mode, maxHits }) | everything along the line, nearest first, up to 8 |
World.groundHeight(position, { up, down }) | the ground's height under a point; a bridge or floor counts |
World.snapToGround(...) | the same, as a hit |
World.entitiesAround(centre, radius, { class, max, physicalOnly }) | entities in a sphere up to 256 m, nearest first, with class, name and level GUID |
mode is 'cover' (the world, props and doors), 'anything' (bodies too) or 'ground'.
What a hit contains
| Field | Meaning |
|---|---|
kind | 'terrain', 'entity', 'vegetation', 'brush' or 'static' |
surface | such as mat_wood or mat_water |
model, category | a tree, bush or placed mesh; category is 'tree', 'bush', 'plant' or 'rock' |
entityClass, entityName | for an entity |
entityGuid | the level's sixteen hex digits, the same on all machines |
player | whose body it was |
Do not treat a client's answer as proof. A server that must rely on it should check for itself. On the server,
World.raycast asks a player's game and returns a promise.
Pictures from any camera
kcds.world.Render.view takes a picture from a camera you choose, without the player seeing that view. The
promise gives a PNG data URL to show in a web view or send to your server.
// client const { Vector3 } = kcds.math; const shot = await kcds.world.Render.view({ position: new Vector3(1652.5, 2151.5, 37.5), lookAt: new Vector3(1647.5, 2155.5, 36), fov: 60, // degrees; leave out or use 0 for the game's value width: 960, // PNG width; leave out for the window's size }); // shot is { png: 'data:image/png;base64,iVBORw0...', width: 960, height: 530 }
Pose: give it as for Camera.startShot: position, then either rotation (a quaternion) or lookAt with an
optional roll. A pose startShot refuses throws the same error here.
Contents: the picture is the frame as the game renders it, with the HUD as the player has it. Hide elements
with Hud.setHidden. KCDS's chat and nameplates are not included.
While it is taken: this takes about a tenth of a second, during which the player keeps seeing the previous frame, so the game seems to pause briefly. The game's eye adaptation follows the other view, so a much brighter or darker view can briefly change the player's brightness.
Timing and failure: one picture is taken at a time, and others wait. The promise rejects with Render.view failed: <reason> when the world changed, the view could not be made, or no frame arrived in 15 seconds. A minimised
window draws no frames.
A picture of a character alone on a transparent background is not available yet.
Character creator
kcds.ui.CharacterCreator opens the game's character stage, with a panel of choices next to the figure.
// client kcds.ui.CharacterCreator.open({ title: 'Who are you?', genders: 'both' }); kcds.core.Events.on('characterCreatorClosed', (appearance, reason) => { if (reason === 'accepted') { kcds.core.Events.toServer('saveLook', appearance); } });
It changes nothing itself. Send the accepted look to the server and apply it with player.setAppearance. The
freeroam resource does this with /creator.
Local NPCs
kcds.characters.LocalNpc is a body shown only on this machine, such as a menu figure, a guide or a preview. A
machine can have up to 64, and they disappear when your resource stops.
// client const guide = kcds.characters.LocalNpc.create({ soul: '210170d8-afb2-4cbd-9df5-da2a8e722af4', name: 'Guide', position: new kcds.math.Vector3(100, 200, 10), }); guide.say('Follow the road to the mill.');
The local player
kcds.players.LocalPlayer is the Player at this machine, or null while loading. Compare ids with it when the
server sends an event meant for one player.
Reference: NoClip, Camera, Render, World, CharacterCreator, LocalNpc, LocalPlayer.