Game Logic
Game Components
A basic game consists of a few simple components:
- Game Loop - timed loop to update the world
- Game Elements - objects and components in your game
- Inputs/Controls - user actions and inputs
- Feedback - response to user actions applied to elements
- Rendering - visual representation of game elements
Timing & Delta
Time plays a very important role in any game. You can use it for physics calculations or custom timers:
# get current unix time
num nowTime = GAME.TIME.now();
# delta value
public action update(num delta) {
(num) object.x += ((num) object.speed * delta);
# using delta ensures consistent speed
# regardless of framerate
}
# stop default game timer (server-side only):
GAME.TIME.freeze();
GAME.TIME.unfreeze();
Countdown Timer
num countdown = 10000; # 10000 ms = 10 seconds
public action update(num delta) {
countdown -= delta; # delta is the time since the last frame/tick
if (countdown <= 0) {
countdown = 10000; # reset
# this code will run every 10 seconds
GAME.log("Custom Loop");
}
}
Players
KrunkScript allows you to interact with the default player objects:
# find a player by their id
obj player = GAME.PLAYERS.findByID(id);
if (notEmpty player) {
# do something
};
# access player list
obj[] players = GAME.PLAYERS.list();
for (num i = 0; i < lengthOf players; i++) {
# do something
}
# get player object of active player (client-side only)
obj you = GAME.PLAYERS.getSelf();
Player Properties
player.position.x = 10; # set player x pos
(num) player.position.y += 0.1; # move player y pos
player.position.z = 10; # move player z pos
player.rotation.x = 0.3; # set player direction
player.velocity.x = 0.1; # set player velocity
(num) player.health -= 10; # change player health
player.score = 5; # (server-side) change player score
player.team = 1; # (server-side) change player team
player.visible = false; # hide player object
# read-only properties
(num) player.classIndex; # returns class ID
(num) player.weaponIndex; # returns current weapon ID
(num) player.sid; # short unique ID
(str) player.id; # long unique ID
(str) player.username; # player display name
(str) player.accountName; # account name (used for data storage)
(str) player.accountID; # profile ID (persistent across sessions)
(bool) player.active; # if player has spawned in
(bool) player.onWall; # if player is touching a wall
(bool) player.onGround; # if player is on the ground
(bool) player.isCrouching; # if player is crouching
(bool) player.onTerrain; # if player is on terrain
(bool) player.isYou; # (client-side) if this is the local player
player.assetID = "325253"; # update player model asset
Disabling Default Behaviors
Krunker provides a lot of default behavior out of the box. You can disable specific behaviors to customize your game:
GAME.DEFAULT.disablePrediction(); # disable client prediction (client only)
GAME.DEFAULT.disablePlayerBehaviour(); # disable all default player logic (client & server)
player.defaultMovement = false; # disable movement, jumping & crouching (client & server)
player.defaultVelocity = false; # disable default velocity (client & server)
player.defaultRotation = false; # disable default rotations (client & server)
player.disableShooting = true; # disable shooting & reloading
player.disableMelee = true; # disable melee
player.disableDefault("jump"); # disable specific default behavior
GAME.DEFAULT.disable3D(); # disable 3D Scene for 2D-only games (client only)
GAME.DEFAULT.disableServerSync(); # disable server sending player data every tick (server only)
GAME.INPUTS.disableDefault(); # don't send default inputs to server (client only)
GAME.UI.hideDefault(); # hide most default krunker UI (client only)
GAME.UI.hideCrosshair(); # hide crosshair (client only)
GAME.PLAYERS.disableMeshes(); # hide default player models
Custom Movement
To override Krunker's default movement:
# disable default movement on spawn
public action onPlayerSpawn(str id) {
obj plr = GAME.PLAYERS.findByID(id);
if (!!plr) {
plr.defaultMovement = false;
# also available:
# plr.defaultRotation = false;
# plr.defaultVelocity = false;
# these are reset on spawn
}
}
# built-in player update action
public action onPlayerUpdate(str id, num delta, static obj inputs) {
obj plr = GAME.PLAYERS.findByID(id);
if (!!plr) {
if (inputs.jump) {
# jump key pressed
# add identical logic to client & server
}
}
}
Input Object Properties
The inputs object in onPlayerUpdate contains:
| Property | Type | Description |
|---|---|---|
inputs.mouseY | num | Mouse Y direction |
inputs.mouseX | num | Mouse X direction |
inputs.movDir | num | WASD inputs converted to direction |
inputs.lMouse | bool | Left mouse button down |
inputs.rMouse | bool | Right mouse button down |
inputs.jump | bool | Jump key down (space by default) |
inputs.reload | bool | Reload key down |
inputs.crouch | bool | Crouch key down |
inputs.scroll | bool | Scroll wheel delta |
inputs.swap | bool | Swap keys |
inputs.restK | bool | Reset key (parkour) |
inputs.inter | bool | Interact key down |
You can disable the default behavior of individual keys:
player.disableDefault("jump");
# ignores all default krunker behavior for the jump key
# this is reset on spawn
If you want to simply change speed, velocity, or jump height, you can use the Class Configuration in the Editor instead.
AIs & NPCs
Krunker offers built-in AI objects. Add them using the Editor or KrunkScript (server-side):
# add AI to world
obj testBot = GAME.AI.spawn(
"11441g", # str asset id
"bot 1", # str AI name
0, 0, 0, # num x, y, z position
{} # obj additional data
);
# optional data to customize your AI:
obj data = {
animStart: "Idle", # str starting animation clip
animSpeed: 0.5, # num animation playback speed
health: 100, # num ai health
speed: 1.0, # num speed multiplier
turnSpeed: 1.0, # num turn speed multiplier
gravity: 1.0, # num gravity multiplier
respawnT: 1000, # num respawn time (ms)
targPlr: false, # bool target players
visionDis: 120, # num vision distance
chaseDis: 20, # num chase distance
canMelee: false, # bool can melee hit
meleeRate: 500, # num melee rate (ms)
meleeDmg: 500, # num melee damage
canShoot: false, # bool can shoot
fireRate: 500, # num fire rate (ms)
roamRadius: 0, # num roam radius
modelScale: 10, # num model size
hitBotW: 1, # num hitbox width multiplier
hitBoxH: 1 # num hitbox height multiplier
};
AI Management
# remove AI
GAME.AI.remove(testBot.sid);
# list all AIs
obj[] ais = GAME.AI.list();
for (num i = 0; i < lengthOf ais; i++) {
# do something
}
# interact with AI properties
(str) testBot.displayName; # returns name
(num) testBot.health -= 10; # change health
testBot.position.x = 10; # set position
testBot.rotation.x = Math.PI; # set rotation
testBot.behaviour = 1; # 0 = no default, 1 = default
testBot.pathIndex = 5; # pathnode index
You are limited to 40 active AIs per game. An AI is considered active if it has an active respawn timer or is currently alive.
Keyboard Inputs
Check for keyboard input (client-side only):
action update(num delta) {
# uses javascript keycodes
if (GAME.INPUTS.keyDown(67)) {
# key with code 67 is pressed
};
};
Mouse Input
# get mouse position on screen (client-side)
obj posNormal = GAME.INPUTS.mousePos();
# get mouse position on overlay canvas
obj pos = GAME.INPUTS.mousePosOverlay();
GAME.OVERLAY.drawCircle((num) pos.x, (num) pos.y, 10, 10, 0, "#ff0000");
# get mouse movement since last frame
obj mov = GAME.INPUTS.mouseMovement();
# unlock mouse for UI interaction
GAME.INPUTS.unlockMouse();
# permanently unlock mouse
GAME.INPUTS.freeMouse();
Input Listeners
The client script has pre-built hooks for input events:
# mouse click listener
public action onMouseClick(num button, num x, num y) {
# called every mouse click
}
public action onMouseUp(num button, num x, num y) {
# called on mouse release
}
# key press listener
public action onKeyPress(str key, num code) {
if (key == "w") {
# pressed W
};
}
# key up listener
public action onKeyUp(str key, num code) {
if (key == "w") {
# released W
};
}
# key held listener
public action onKeyHeld(str key, num code) {
if (key == "w") {
# holding W
};
}
# mouse scroll listener
public action onMouseScroll(num dir) {
# scroll direction
}
Collisions
Krunker offers built-in collision functionality. The most performant method is between two Axis Aligned Bounding Boxes (AABB). All krunker objects default to this method. You can enable it on most objects in the editor using the Collidable option.
Alternatively, select the Complex Collisions option for rotated objects and assets. It is recommended to use a simple invisible mesh for collision and a detailed mesh for display.
Complex collisions use an object's triangle data. You are limited to 5000 collision triangles per map export.
Path Nodes
Path/coordinate 3D objects that can be added in the editor and accessed via KrunkScript:
# fetch nodes list
obj[] nodes = GAME.OBJECTS.getPathNodes();
for (num i = 0; i < lengthOf nodes; ++i) {
(num) nodes[i].id; # unique id
(num) nodes[i].x; # x coordinate
(num) nodes[i].y; # y coordinate
(num) nodes[i].z; # z coordinate
}
This is useful for creating basic pathfinding or accessing coordinates.