Examples

Complete working examples demonstrating KrunkScript features.

Table of Contents

Editor Templates

These examples are from the in-game editor templates.


Editor Templates

These examples come from the built-in editor templates and demonstrate various KrunkScript features.

Simple .io Game

Client Script

# INIT:
obj window = {};
obj ball = {
    x: 0,
    y: 0,
    scale: 30,
    speed: 0.4
};

# PEBBLES
obj[] pebbles = obj[];
num pebbleCount = 70; # number of pebbles

# SPIKES:
obj[] spikes = obj[];
num spikePowerDivisor = 2;
num spikeCount = 5; # number of spikes

# WORLD:
num worldScale = 2000;
num gridSize = 60;

# IDEAS:
# Randomize Pebble Colors
# Add Multiplayer using NETWORK object
# Add Spike Objects

# PEBBLE:
obj action genPebble() {
	num hue = UTILS.randInt(0, 360);
	num scale = UTILS.randFloat(17, 20);
	return {
		x: UTILS.randInt(-worldScale, worldScale),
		y: UTILS.randInt(-worldScale, worldScale),
		scale: scale,
		power: 3,
		innerCol: UTILS.hexFromHue(hue),
		outerCol: UTILS.hexFromHue(Math.max(0, hue - 10))
	};
}

# SPIKE:
obj action genSpike() {
	num scale = UTILS.randFloat(150, 200);
	return {
		x: UTILS.randInt(-worldScale, worldScale),
		y: UTILS.randInt(-worldScale, worldScale),
		scale: scale,
		power: scale / spikePowerDivisor,
		color: '#90ff21'
	};
}

# PLAYER:
action resetBall() {
	ball.x = UTILS.randInt(-worldScale, worldScale);
	ball.y = UTILS.randInt(-worldScale, worldScale);
	ball.scale = 30;
	ball.speed = 0.2;
}

# GAME:
public action start() {
    
    # SETUP
	window = GAME.OVERLAY.getSize();
    GAME.INPUTS.freeMouse();
    GAME.UI.hideDefault();
    GAME.DEFAULT.disable3D();
    GAME.DEFAULT.disablePlayerBehaviour();
    GAME.DEFAULT.disableServerSync();
    
    # INIT PEBBLES
    for (num i = 0; i < pebbleCount; i++) {
        addTo pebbles genPebble();
    }

    # SPIKES:
    for (num i = 0; i < spikeCount; i++) {
        addTo spikes genSpike();
    }

}

# GAME:
public action update(num delta) {
    
    # GET POSITION:
    obj mousePos = GAME.INPUTS.mousePosOverlay();
    
    # DIRECTION:
    num movDir = UTILS.getDir2D(mousePos.x, mousePos.y, ((num) window.width/2), ((num) window.height/2));

    # BALL:
    (num) ball.x += ((num) ball.speed * delta) * Math.cos(movDir);
    (num) ball.y += ((num) ball.speed * delta) * Math.sin(movDir);

    # PEBBLE CHECKS:
    for (num i = 0; i < lengthOf pebbles; i++) {
        obj tmpObj = pebbles[i];
        num distance = UTILS.getDist2D(ball.x, ball.y, tmpObj.x, tmpObj.y);
        if (distance <= ((num) tmpObj.scale + (num) ball.scale)) {
           	(num) ball.scale += (num) tmpObj.power;
            pebbles[i] = genPebble();
        }
    }

    # SPIKE CHECKS:
    for (num i = 0; i < lengthOf spikes; i++) {
        obj tmpObj = spikes[i];
        num distance = UTILS.getDist2D(ball.x, ball.y, tmpObj.x, tmpObj.y) * 2;
        if (distance <= ((num) tmpObj.scale + (num) ball.scale)) {
			if ((num) ball.scale >= (num) tmpObj.scale) {
				(num) ball.scale += (num) tmpObj.power;
				spikes[i] = genSpike();
			} else {
           		resetBall();
			}
        }
    }

	# BORDER CHECKS:
	if (((num) ball.x - (num) ball.scale) <= -worldScale) {
		ball.x = (num) ball.scale + -worldScale;
	}
	if (((num) ball.x + (num) ball.scale) >= worldScale) {
		ball.x = worldScale - (num) ball.scale;
	}
	if (((num) ball.y - (num) ball.scale) <= -worldScale) {
		ball.y = (num) ball.scale + -worldScale;
	}
	if (((num) ball.y + (num) ball.scale) >= worldScale) {
		ball.y = worldScale - (num) ball.scale;
	}
    
}

# SPIKES:
action drawSpike(num x, num y, num scale, str color) {
	for (num i = 0; i < 7; i++) {
        GAME.OVERLAY.drawRect(x, y, scale, scale, i * 15, color, 1, true);
    }
}

# RENDER:
public action render(num delta) {
    
    # SETUP:
    GAME.OVERLAY.clear();
    window = GAME.OVERLAY.getSize();
    
    # BACKGROUND:
    GAME.OVERLAY.drawRect(0, 0, window.width, window.height, 0, '#fafafa', 1);

	# GRID:
	num xPos = (-((num) ball.x) % gridSize) - gridSize;
	while (xPos < (num) window.width) {
		xPos = xPos + gridSize;	
		if (xPos > 0) {
			GAME.OVERLAY.drawLine(xPos, 0, xPos, window.height, 3, '#000000', 0.1);
		}
	}
	num yPos = (-((num) ball.y) % gridSize) - gridSize;
	while (yPos < (num) window.height) {
		yPos = yPos + gridSize;
		if (yPos > 0) {
			GAME.OVERLAY.drawLine(0, yPos, window.width, yPos, 3, '#000000', 0.1);
		}
	}

 	# CAMERA:
   	GAME.OVERLAY.offset(-((num) ball.x) + ((num) window.width/2), -((num) ball.y) + ((num) window.height/2));

    # PEBBLES:
    for (num i = 0; i < lengthOf pebbles; i++) {
        obj tmpObj = pebbles[i];
        GAME.OVERLAY.drawCircle(tmpObj.x, tmpObj.y, tmpObj.scale, 
            tmpObj.scale, 0, tmpObj.outerCol, 1);
        GAME.OVERLAY.drawCircle(tmpObj.x, tmpObj.y, (num) tmpObj.scale * 0.7, 
            (num) tmpObj.scale * 0.7, 0, tmpObj.innerCol, 1);
    }

    # SPIKES:
    for (num i = 0; i < lengthOf spikes; i++) {
        obj tmpObj = spikes[i];
		drawSpike((num) tmpObj.x, (num) tmpObj.y, (num) tmpObj.scale, (str) tmpObj.color);
    }

    # BALL:
    GAME.OVERLAY.drawCircle((num) ball.x, (num) ball.y, (num) ball.scale + 10, 
        (num) ball.scale + 10, 0, '#db3532', 1);
	GAME.OVERLAY.drawCircle((num) ball.x, (num) ball.y, (num) ball.scale, 
        (num) ball.scale, 0, '#ff3b38', 1);
    
}


Moving Objects

Client Script

# Client Script runs only on the client
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd

# Light Object
obj redLamp = {};
obj blueLamp = {};

# Position
num distance = 18;
num yPos = 12;

# Angle
num angle = 0;
num angularVelocity = 0.006;

# Create a new lamp
obj action createLamp(str color) {
	obj light = GAME.SCENE.addPointLight(color, distance, yPos, 0, 100, 1);
    obj ball = GAME.SCENE.addSphere('', color, distance, yPos, 0, 5, 5, 5);
    ball.emissive = color;
    return {
        light: light,
        ball: ball
    };
}

# Runs when the game starts
public action start() {
    # force lighting
	GAME.SETTINGS.set('lighting', 2); 

    # create lamps
    redLamp = createLamp('#ff0000');
    blueLamp = createLamp('#0000ff');
}

# Rotate angle
action rotateAngle(num delta) {
    angle = angle + (angularVelocity * delta);
    angle = angle % Math.PI2;
}

# Update lamp position
action updateLamp(obj lamp, num offset) {
    lamp.light.position.x = distance * Math.cos(angle + offset);
	lamp.light.position.z = distance * Math.sin(angle + offset);
	lamp.ball.position.x = lamp.light.position.x;
	lamp.ball.position.z = lamp.light.position.z;
}

# Runs every game tick
public action update(num delta) {
	# rotate angle
	rotateAngle(delta);
	
	# update lamp positions
    updateLamp(blueLamp, Math.PI);
	updateLamp(redLamp, 0);
}

Server Script

# Server Script runs only on Hosted server & not in test mode
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd
# 
# Add custom actions here

# Runs when the game starts
public action start() {

}

# Runs every game tick
public action update(num delta) {

}

# Player spawns in
public action onPlayerSpawn(str id) {

}

# Player update
public action onPlayerUpdate(str id, num delta, static obj inputs) {

}

# Called from Custom Trigger Action
public action onCustomTrigger(str playerID, str customParam) {

}

# Server receives network message
public action onNetworkMessage(str id, obj data, str playerID) {

}

# When a player leaves the server
public action onPlayerLeave(str playerID) {

}

Bouncing Ball

Client Script

# Client Script runs only on the client
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd

obj ball = {};
num ballScale = 5; # play around with this
num ballVelocity = 0;
num gravity = 0.01; # play around with this
num dropHeight = 100; # play around with this
num squishiness = 0.5; # play around with this

# Runs when the game starts
public action start() {

	# detach camera
	GAME.CAMERA.detach();

	# hide player
	GAME.PLAYERS.disableMeshes();
	GAME.DEFAULT.disablePlayerBehaviour();
	
	# hide ui
	GAME.UI.hideDefault();
	GAME.UI.hideCrosshair();
	
	# add ball to scene
	ball = GAME.SCENE.addSphere('', '#ff0000', 0, dropHeight, 0, 
		ballScale, ballScale, ballScale);
	
	# add custom UI
	str divID = GAME.UI.addDIV('infoText', true,
		'color:#f1f1f1;position:absolute;top:90%;left:50%;' +
		'transform: translate(-50%,-50%);font-size: 24px;' +
		'border:solid 2px #fff;border-radius:4px;' +
		'padding:10px 30px 10px 30px;background-color: rgba(0,0,0,0.2);'
	);
	
	# add text to div
	GAME.UI.updateDIVText(divID, 'Press R to Reset Ball');
	
}

# custom reset ball action
action resetBall() {
	ball.position.y = dropHeight;
	ballVelocity = 0;
	ball.scale.y = ballScale;
}


# Runs every game tick
public action update(num delta) {
	
	# Update Gravity 
	ballVelocity = ballVelocity - (gravity * delta);
	
	# Move Ball
	(num) ball.position.y += ballVelocity;
	
	# Check if on Floor
	if ((num) ball.position.y - ballScale <= 0) {
		ball.position.y = ballScale;
		ballVelocity = ballVelocity * -1;
		(num) ball.scale.y -= ballVelocity * squishiness;
	}
	
	# Squish Animation
	if ((num) ball.scale.y <= ballScale) {
		(num) ball.scale.y += 0.01 * delta;
		if ((num) ball.scale.y > ballScale) {
			ball.scale.y = ballScale;
		}
		num scaleDiff = (ballScale - (num) ball.scale.y);
		ball.scale.x = (ballScale + scaleDiff);
		ball.scale.z = (ballScale + scaleDiff);
		
	}
	
	# update camera
	GAME.CAMERA.move(0, 40, 90);
	GAME.CAMERA.rotate(0, 0, 0);
	
}

# User pressed a key
public action onKeyPress(str key, num code) {
	GAME.log(key, toStr code);
	if (key == 'r') {
		resetBall();
	}
}

Client Script

# Client Script runs only on the client
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd
# 
# Add custom actions here

# Runs when the game starts
public action start() {
	
	# create popup div
	str popupDiv = GAME.UI.addDIV(
		'popupTemp', 
		false, 
		'width:400px;height:225px;position:absolute;' +
		'top:40%;left:50%;transform: translate(-50%, -50%);' +
		'background-color:grey;cursor:pointer;border-radius:4px;' +
		'border:solid 2px black;'
	);
	
	# add text to popup div
	GAME.UI.updateDIVText(popupDiv, 'Hello World');
	
	# create close button & add to popup
	str closeDiv = GAME.UI.addDIV(
		'popupTempClose', 
		true, 
		'width:30px;height:30px;position:absolute;' +
		'top:10px;left:10px;background-color:white;' +
		'cursor:pointer;border-radius:4px;',
		popupDiv
	);
	
	# add text to close div
	GAME.UI.updateDIVText(closeDiv, 'x');
	
	# add custom UI
	str divID = GAME.UI.addDIV(
		'infoText', 
		true,
		'color:#f1f1f1;position:absolute;top:90%;left:50%;' +
		'transform: translate(-50%,-50%);font-size: 24px;' +
		'border:solid 2px #fff;border-radius:4px;' +
		'padding:10px 30px 10px 30px;background-color: rgba(0,0,0,0.2);'
	);
	
	# add text to div
	GAME.UI.updateDIVText(divID, 'Press G to Open Popup');
}

# User pressed a key
public action onKeyPress(str key, num code) {
	
	# check if pressed correct key
	if (key == 'g') {
		# show popup
		GAME.UI.updateDIV('popupTemp', 'display', 'block');
		
		# unlock mouse so users can click popup
		GAME.INPUTS.unlockMouse();
	}
}

# User clicked a DIV (ID)
public action onDIVClicked(str id) {
	
	# check if clicked on close div
	if (id == 'popupTempClose') {
		# hide popup
		GAME.UI.updateDIV('popupTemp', 'display', 'none');
		
		# lock mouse so users can continue;
		GAME.INPUTS.lockMouse();
	}
}

Flashlight

Client Script

# Client Script runs only on the client
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd
# 
# Add custom actions here
obj torch = {};

# Runs when the game starts
public action start() {
   
	# Hide defaults:
	GAME.UI.hideCrosshair();
	GAME.UI.hideDefault();
	GAME.PLAYERS.disableMeshes();
	
	# Add light object:
	torch = GAME.SCENE.addSpotLight("#fff", 0, 0, 0, 0, 0, 0, 60, 1, 1, 60, 0.5);
    
}

# Runs every game tick
public action update(num delta) {
	obj plr = GAME.PLAYERS.getSelf();
	if (notEmpty plr) {
		num yPos = ((num) plr.position.y + 10);
		torch.move(
			plr.position.x, 
			yPos, 
			plr.position.z
		);	
		torch.lookAt(
			((num) plr.position.x + (1000 * Math.sin((num) plr.rotation.x + Math.PI) * Math.cos((num) plr.rotation.y))), 
			(yPos + (1000 * Math.sin((num) plr.rotation.y))), 
			((num) plr.position.z + (1000 * Math.cos((num) plr.rotation.x + Math.PI) * Math.cos((num) plr.rotation.y)))
		);
	}
}

# Add rendering logic in here
public action render(num delta) {

}

# User pressed a key
public action onKeyPress(str key, num code) {

}

# User released a key
public action onKeyUp(str key, num code) {

}

# User held a key
public action onKeyHeld(str key, num code) {

}

# User clicked on screen
public action onMouseClick(num button, num x, num y) {

}

# User scrolled on screen
public action onMouseScroll(num dir) {

}

# User clicked a DIV (ID)
public action onDIVClicked(str id) {

}

# Client receives network message
public action onNetworkMessage(str id, obj data) {

}

Snake Demo

Client Script

# SCREEN
obj bounds = GAME.OVERLAY.getSize();
num width = (num) bounds.width;
num height = (num) bounds.height;

# TIMER
num tick = 0;
num tickRate = 50;
num clock = 0;

# PLAYER
obj player = {
	size: 20,
	color: 0,
	
	pos: num[] [num [(width - 40) / 2, (height - 40) / 2]],
	tail: 1,
	
	horz: 0,
	vert: -1
};


public action start() {
	# SETTINGS
	GAME.SETTINGS.set('scaleUI', 1);
	GAME.SETTINGS.set('showUI', false);

	# DEFAULT
	GAME.DEFAULT.disablePlayerBehaviour();
	GAME.DEFAULT.disable3D();
	GAME.DEFAULT.disableServerSync();
	GAME.DEFAULT.disablePrediction();
}

# DRAW BACKGROUND
action drawBackground() {
	GAME.OVERLAY.drawRect(
		0,
		0,
		width,
		height,
	0, '#000000', 1);
}

# DRAW PLAYER
action drawPlayer() {
	for (num i = 0; i < lengthOf (num[][]) player.pos; i++) {
		GAME.OVERLAY.drawRect(
			(num) player.pos[i][0],
			(num) player.pos[i][1],
			(num) player.size,
			(num) player.size,
		0, GAME.UTILS.hexFromHue((num) player.color), 1);	
	}
}

public action render(num delta) {
	drawBackground();
	drawPlayer();
}

# MOVE PLAYER
action movePlayer() {
	
	num[] old = (num[]) player.pos[(lengthOf (num[][]) player.pos) - 1];
	num[] new = num [old[0] + (num) player.horz * (num) player.size, old[1] + (num) player.vert * (num) player.size];
	
	new[0] = (new[0] + width) % width;
	new[1] = (new[1] + height) % height;
	
	addTo (num[][]) player.pos new;
	
	if (lengthOf (num[][]) player.pos > (num) player.tail) {
		remove player.pos[0];
	}
}

public action update(num delta) {
	if (tick > tickRate) {
		tick = 0;
		clock++;

		if (clock % 1 == 0) {
			movePlayer();
		}
	
	
		if (clock % 10 == 0) {
			(num) player.tail++;
		}
	}
	
	(num) player.color += 0.1 * delta;
	tick += delta;
}


public action onKeyPress(str key, num code) {
    if (key == 'w') {
		player.vert = -1;
		player.horz = 0;
    }
    if (key == 's') {
		player.vert = 1;
		player.horz = 0;
    }
    if (key == 'a') {
		player.vert = 0;
		player.horz = -1;
    }
    if (key == 'd') {
		player.vert = 0;
		player.horz = 1;
    }
}


Basic Networking

Client Script

# Client Script runs only on the client
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd
# 
# Add custom actions here
str resDivId = "";

# Runs when the game starts
public action start() {
	
	# add custom UI
	resDivId = GAME.UI.addDIV(
		'resText', 
		false,
		'color:#f1f1f1;position:absolute;top:50%;left:50%;' +
		'transform: translate(-50%,-50%);font-size: 24px;' +
		'border:solid 2px #fff;border-radius:4px;' +
		'padding:10px 30px 10px 30px;background-color: rgba(0,0,0,0.2);'
	);
	
	# add custom UI
	str divID = GAME.UI.addDIV(
		'infoText', 
		true,
		'color:#f1f1f1;position:absolute;top:90%;left:50%;' +
		'transform: translate(-50%,-50%);font-size: 24px;' +
		'border:solid 2px #fff;border-radius:4px;' +
		'padding:10px 30px 10px 30px;background-color: rgba(0,0,0,0.2);'
	);
	
	# add text to div
	GAME.UI.updateDIVText(divID, 'Press G to Validate Age');
}

# User pressed a key
public action onKeyPress(str key, num code) {
	
	# check if pressed correct key
	if (key == 'g') {
		GAME.NETWORK.send("check", {
			name: "Test",
			age: GAME.UTILS.randInt(0, 21)
		});
	}
}

# Client receives network message
public action onNetworkMessage(str id, obj data) {
	if (id == "check") {
		str status = (str) data.status;
		GAME.UI.updateDIVText(resDivId, 'You ' + status + ' Age Validation');
		GAME.UI.updateDIV(resDivId, 'display', 'block');
	}
}

Server Script

# Server Script runs only on Hosted server & not in test mode
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd
# 
# Add custom actions here

# Runs when the game starts
public action start() {

}

# Runs every game tick
public action update(num delta) {

}

# Server receives network message
public action onNetworkMessage(str id, obj data, str playerID) {
	if (id == "check") {
		num age = (num) data.age;
	
		bool valid = age >= 13;
		GAME.NETWORK.send("check", {
			status: valid ? "Passed" : "Failed"
		}, playerID);
	}
}

Networking Objects

Client Script

# Client Script runs only on the client
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd
# 
# Add custom actions here

# Runs when the game starts
public action start() {

}

# Runs every game tick
public action update(num delta) {

}

# Add rendering logic in here
public action render(num delta) {

}

# Player spawns in
public action onPlayerSpawn(str id) {

}

# Player update
public action onPlayerUpdate(str id, num delta, obj inputs) {

}

# User pressed a key
public action onKeyPress(str key, num code) {

}

# User released a key
public action onKeyUp(str key, num code) {

}

# User held a key
public action onKeyHeld(str key, num code) {

}

# User clicked on screen
public action onMouseClick(num button, num x, num y) {

}

# User released clicked on screen
public action onMouseUp(num button, num x, num y) {

}

# User scrolled on screen
public action onMouseScroll(num dir) {

}

# User clicked a DIV (ID)
public action onDIVClicked(str id) {

}

# Client receives network message
public action onNetworkMessage(str id, obj data) {

}

Server Script

# Server Script runs only on Hosted server & not in test mode
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd
# 
# Add custom actions here

# Runs when the game starts
public action start() {

}

# Runs every game tick
public action update(num delta) {

}

# Player spawns in
public action onPlayerSpawn(str id) {

}

# Player update
public action onPlayerUpdate(str id, num delta, obj inputs) {

}

# Called from Custom Trigger Action
public action onCustomTrigger(str playerID, str customParam, num value) {
	
	# check parameter
	if (customParam == "1") {
		GAME.log("First Trigger");
	}

	# check parameter
	if (customParam == "2") {
		GAME.log("Second Trigger");
	}
	
}

# Server receives network message
public action onNetworkMessage(str id, obj data, str playerID) {

}

# When a player leaves the server
public action onPlayerLeave(str playerID) {

}

Converting 3D to 2D

Client Script

# Client Script runs only on the client
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd

# Add rendering logic in here
public action render(num delta) {
	obj coords = GAME.SCENE.posToScreen(0, 10, 0);
 	if ((bool) coords.onScreen) {
		GAME.OVERLAY.drawCircle(coords.x, coords.y, 80, 80, 0, "#ff0000", 1);	
		GAME.OVERLAY.drawText("1", coords.x, (num) coords.y+40, 0, 70, "center", "#fff", 1);
	}
}


Jetpacks

Client Script

# Client Script runs only on the client
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd

# Player update
public action onPlayerUpdate(str id, num delta, obj inputs) {
	obj tPlr = GAME.PLAYERS.findByID(id);
    if (!!tPlr && (num) tPlr.classIndex == 1) { # Hunter Only
		tPlr.disableDefault("jump");
        if ((bool) inputs.jump) {
           	(num) tPlr.velocity.y += 0.0003 * delta;
			if ((num) tPlr.velocity.y > 0.03) {
				tPlr.velocity.y = 0.03;
			}
        }
    }
}

Server Script

# Server Script runs only on Hosted server & not in test mode
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd

# Player update
public action onPlayerUpdate(str id, num delta, static obj inputs) {
	obj tPlr = GAME.PLAYERS.findByID(id);
    if (!!tPlr && (num) tPlr.classIndex == 1) { # Hunter Only
		tPlr.disableDefault("jump");
        if (inputs.jump) {
           	(num) tPlr.velocity.y += 0.0003 * delta;
			if ((num) tPlr.velocity.y > 0.03) {
				tPlr.velocity.y = 0.03;
			}
        }
    }
}


Sprinting

Client Script

# Client Script runs only on the client
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd

# Player update
public action onPlayerUpdate(str id, num delta, obj inputs) {
    obj tPlr = GAME.PLAYERS.findByID(id);
    if (!!tPlr) {
        
        # Disable Default Movement:
        tPlr.defaultMovement = false;
        tPlr.disableDefault("crouch");

        # Move Direction:
        if ((num) inputs.movDir >= -Math.PI && (num) inputs.movDir <= Math.PI) {
            num movDir = 0;
            if (!!(num) inputs.movDir) {
                movDir = (num) inputs.movDir;
            }
            movDir -= ((num) inputs.mouseX);
            num movSpd = 0.0003 * delta;
            if ((bool) inputs.crouch) { # Sprint
                movSpd *= 1.6;
            }
            (num) tPlr.velocity.x += (movSpd * Math.cos(movDir));
            (num) tPlr.velocity.z += (movSpd * Math.sin(movDir));    
        }
        
    }
}

Server Script

# Server Script runs only on Hosted server & not in test mode
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd

# Player update
public action onPlayerUpdate(str id, num delta, obj inputs) {
    obj tPlr = GAME.PLAYERS.findByID(id);
    if (!!tPlr) {
        
        # Disable Default Movement:
        tPlr.defaultMovement = false;
        tPlr.disableDefault("crouch");

        # Move Direction:
        if ((num) inputs.movDir >= -Math.PI && (num) inputs.movDir <= Math.PI) {
            num movDir = 0;
            if (!!inputs.movDir) {
                movDir = (num) inputs.movDir;
            }
            movDir -= ((num) inputs.mouseX);
            num movSpd = 0.0003 * delta;
            if ((bool) inputs.crouch) { # Sprint
                movSpd *= 1.6;
            }
            (num) tPlr.velocity.x += (movSpd * Math.cos(movDir));
            (num) tPlr.velocity.z += (movSpd * Math.sin(movDir));    
        }
        
    }
}


Triggers & Scripts

Client Script

# Client Script runs only on the client
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd
# 
# Add custom actions here

# Runs when the game starts
public action start() {

}

# Runs every game tick
public action update(num delta) {

}

# Add rendering logic in here
public action render(num delta) {

}

# Player spawns in
public action onPlayerSpawn(str id) {

}

# Player update
public action onPlayerUpdate(str id, num delta, static obj inputs) {

}

# User pressed a key
public action onKeyPress(str key, num code) {

}

# User released a key
public action onKeyUp(str key, num code) {

}

# User held a key
public action onKeyHeld(str key, num code) {

}

# User clicked on screen
public action onMouseClick(num button, num x, num y) {

}

# User released clicked on screen
public action onMouseUp(num button, num x, num y) {

}

# User scrolled on screen
public action onMouseScroll(num dir) {

}

# User clicked a DIV (ID)
public action onDIVClicked(str id) {

}

# Client receives network message
public action onNetworkMessage(str id, obj data) {

}

Server Script

# Server Script runs only on Hosted server & not in test mode
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd
# 
# Add custom actions here

# Runs when the game starts
public action start() {

}

# Runs every game tick
public action update(num delta) {

}

# Player spawns in
public action onPlayerSpawn(str id) {

}

# Player update
public action onPlayerUpdate(str id, num delta, static obj inputs) {

}

# Called from Custom Trigger Action
public action onCustomTrigger(str playerID, str customParam) {
	
	# check parameter
	if (customParam == "1") {
		GAME.log("First Trigger");
	}

	# check parameter
	if (customParam == "2") {
		GAME.log("Second Trigger");
	}
	
}

# Server receives network message
public action onNetworkMessage(str id, obj data, str playerID) {

}

# When a player leaves the server
public action onPlayerLeave(str playerID) {

}

Game of Life

Client Script

# Client Script runs only on the client
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd

obj window = GAME.OVERLAY.getSize();
obj oldWindow = window;
obj pos = GAME.INPUTS.mousePosOverlay(); 
bool mouseReleased = true;           
bool initialized = false;
bool first = false;                      
bool pauseState = true;
num oldTime = GAME.TIME.now();
num track = 0;
num timerDelay = 250;                 # Initial timer (0.25s)
num timerStepper = 50;         	      # Defines the step count for the timer in ms
num gridStepper = 1;                  # Defines the step count for tile size in px
num maxGrid = 100;                    # Maximum tile size amount in px
num minGrid = 20;                     # Minimum tile size amount in px
obj grid = {
	x: 0,
	y: 0,
	w: 0,
	h: 0,
	scale: 30,                    # Initial tile size (lower = more tiles)
	tileColor: "#3e4240",
	backgroundColor: "#000000",
	borderColor: "#707070",
	hoverColor: "#464a48",
	lifeColor: "#5dde6c"
};

obj ui = {
	height: 99
};

num[][] tiles = num[][];


# Counts how many live neighors each tile has
num action neighbors(num x, num y){
num neighborCount = 0;
# top
if(y != 0 && tiles[x][y - 1] == 1){
neighborCount++;
};
# bottom
if(y != (num) grid.h - 1 && tiles[x][y + 1] == 1){
neighborCount++;
};
# left
if(x != 0  && tiles[x - 1][y] == 1){
neighborCount++;
};
# right
if(x != (num) grid.w - 1  && tiles[x + 1][y] == 1){
neighborCount++;
};
# top left
if(y != 0 && x != 0  && tiles[x - 1][y - 1] == 1){
neighborCount++;
};
# top right
if(y != 0 && x != (num) grid.w - 1  && tiles[x + 1][y - 1] == 1){
neighborCount++;
};
# bottom left
if(y != (num) grid.h - 1 && x != 0 && tiles[x - 1][y + 1] == 1){
neighborCount++;
};
# bottom right
if(y != (num) grid.h - 1 && x != (num) grid.w - 1 && tiles[x + 1][y + 1] == 1){
neighborCount++;
};

return neighborCount;
}

# Advances to the next generation -- Updates board
action tick(){
		obj[] buffer = obj[];
		for(num i = 0; i < (num) grid.w; i++){
			for(num j = 0; j < (num) grid.h; j++){
			num count = neighbors(i, j);
            if((count == 2 || count == 3) && tiles[i][j] == 1){
				addTo buffer {x: i, y: j, val: 1};
            };
            if(count == 3 && tiles[i][j] == 0){
				addTo buffer {x: i, y: j, val: 1};
            };
            if(count < 2 && tiles[i][j] == 1) {
				addTo buffer {x: i, y: j, val: 0};
            };
            if(count > 3 && tiles[i][j] == 1) {
				addTo buffer {x: i, y: j, val: 0};
            };
	
}
}

# Prevents neighbors from changing while updating -- Updates all changes at once
for(num i = 0; i < lengthOf buffer; i++){
	tiles[(num) buffer[i].x][(num) buffer[i].y] = (num) buffer[i].val;
}

}



# Creates the grid array according to screen size
action makeGrid(){
window = GAME.OVERLAY.getSize();
(num) window.height -= (num) ui.height;

grid.w = Math.floor((num) window.width / (num) grid.scale);
grid.h = Math.floor((num) window.height / (num) grid.scale);

grid.x = ((num) window.width - (num) grid.scale * Math.floor((num) window.width / (num) grid.scale))/2;   # Always center board in the center
grid.y = ((num) window.height - (num) grid.scale * Math.floor((num) window.height / (num) grid.scale))/2; 


# Grid is defined as a nested array -- tiles[x][y]


tiles = num[][];
for(num i = 0; i < (num) grid.w; i++) {
    num[] tempArray = num[];
	
    for(num j = 0; j < (num) grid.h; j++) {
        addTo tempArray 0;
    }
	
    addTo tiles tempArray;
}

# tiles[
# [0,0,0,0],
# [0,0,0,0],
# [0,0,0,0]
# ]

	
}




# Saves previous live tiles in buffer and re-adjusts grid on scale/window change
action updateGrid(){
obj[] buffer = obj[];
	
window = GAME.OVERLAY.getSize();
(num) window.height -= (num) ui.height;

grid.w = Math.floor((num) window.width / (num) grid.scale);
grid.h = Math.floor((num) window.height / (num) grid.scale);
grid.x = ((num) window.width - (num) grid.scale * Math.floor((num) window.width / (num) grid.scale))/2;
grid.y = ((num) window.height - (num) grid.scale * Math.floor((num) window.height / (num) grid.scale))/2;


for(num i = 0; i < lengthOf tiles; i++){
	for(num j = 0; j < lengthOf tiles[i]; j++){
		if(tiles[i][j] == 1){
			addTo buffer {x: i, y: j};
		}
	}
}

tiles = num[][];

for(num i = 0; i < (num) grid.w; i++) {
    num[] tempArray = num[];
	
    for(num j = 0; j < (num) grid.h; j++) {
        addTo tempArray 0;
    }
	
    addTo tiles tempArray;
}


# Load old live tiles
for(num i = 0; i < lengthOf buffer; i++){
	tiles[(num) buffer[i].x][(num) buffer[i].y] = 1;
}


		

}





# Create GUI and set custom controls

public action start() {
GAME.DEFAULT.disablePlayerBehaviour(); 
GAME.INPUTS.disableDefault();
GAME.UI.hideDefault(); 
GAME.UI.hideCrosshair(); 
GAME.INPUTS.freeMouse();


str defaultBackground = "background-color: black;width: 30px;height: 30px;border-radius: 15px;margin: 5px;font-family: sans-serif;color: white;";
str customValues = "color: white;background: black;font-family: sans-serif;font-weight: bold;font-size: 18px;width: 50px;height: fit-content;padding: 11px;border-radius: 10px;display: inline-grid;";

str startMenu = GAME.UI.addDIV(
    "startMenu",
    true,
    "position: absolute;background: #2b2b2b;width: 100%;height: 100%;display: grid;justify-content: center;align-items: center;justify-items: center;z-index:1000;"
);

str title = GAME.UI.addDIV(
    "title",
    true,
    "text-align: center;font-size: 70px;color: white;font-family: monospace;",
	"startMenu"
);

str startBtn = GAME.UI.addDIV(
    "startBtn",
    true,
    "color: black;font-size: 65px;font-family: sans-serif;border: 1px solid white;border-radius: 19px;padding: 10px;background: white;font-weight: bold;user-select: none;width: fit-content;height: fit-content;",
	"startMenu"
);

str ruleText = GAME.UI.addDIV(
    "ruleText",
    true,
    "text-align: center; color: white;width: 900px;font-size: 25px;white-space: break-spaces;font-family: sans-serif;",
	"startMenu"
);

GAME.UI.updateDIVText(
    "title",
    "Conways Game of Life" 
);

GAME.UI.updateDIVText(
    "startBtn",
    "START" 
);

GAME.UI.updateDIVText(
    "ruleText",
    "Any live cell with fewer than two live neighbours dies, as if by underpopulation. Any live cell with two or three live neighbours lives on to the next generation. Any live cell with more than three live neighbours dies, as if by overpopulation. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction." 
);

str ui = GAME.UI.addDIV(
    "ui",
    true,
    "height: 100px;width: 100%;background: #2f2f2f;position: absolute;bottom: 0px;display: grid;justify-content: center;align-content: center;grid-auto-flow: column;gap: 9px;align-items: center;user-select: none;"
);

str backgroundClear = GAME.UI.addDIV(
    "backgroundClear",
    true,
    "background-color: black;width: 70px;height: 70px;border-radius: 15px;margin: 5px;float: right;",
	"ui"
);

str startGui = GAME.UI.addDIV(
    "startGui",
    true,
    "color: white;background: black;font-family: sans-serif;font-weight: bold;font-size: 40px;width: 137px;height: fit-content;padding: 11px;border-radius: 10px;display: inline-grid;",
	"ui"
);

GAME.UI.updateDIVText(
    "startGui",
    "START" 
);

str backgroundNext = GAME.UI.addDIV(
    "backgroundNext",
    true,
    "background-color: black;width: 70px;height: 70px;border-radius: 15px;margin: 5px;float: right;",
	"ui"
);

str next = GAME.UI.addDIV(
    "next",
    true,
    "background-color: #ffffff;clip-path: polygon(100% 50%, 0 100%, 25% 49%, 0 0);width: 50%;height: 50%;position: relative;top: 23%;left: 32%;",
	"backgroundNext"
);

str clear = GAME.UI.addDIV(
    "clear",
    true,
    "background-color: #ffffff;clip-path: polygon(20% 0%, 0% 20%, 30% 50%, 0% 80%, 20% 100%, 50% 70%, 80% 100%, 100% 80%, 70% 50%, 100% 20%, 80% 0%, 50% 30%);width: 50%;height: 50%;position: relative;top: 24%;left: 26%;",
	"backgroundClear"
);

str backgroundRemove = GAME.UI.addDIV(
    "backgroundRemove",
    true,
    defaultBackground + "font-size: 23px;",
	"ui"
);

str timer = GAME.UI.addDIV(
    "timer",
    true,
    customValues,
	"ui"
);

GAME.UI.updateDIVText(
    "timer",
    toStr (timerDelay / 1000) + "s"
);

str backgroundAdd = GAME.UI.addDIV(
    "backgroundAdd",
    true,
    defaultBackground + "font-size: 27px;",
	"ui"
);

GAME.UI.updateDIVText(
    "backgroundAdd",
    "+"
);

GAME.UI.updateDIVText(
    "backgroundRemove",
    "-"
);

str backgroundRemoveScale = GAME.UI.addDIV(
    "backgroundRemoveScale",
    true,
    defaultBackground + "font-size: 23px;",
	"ui"
);

str scale = GAME.UI.addDIV(
    "scale",
    true,
    customValues,
	"ui"
);

str backgroundAddScale = GAME.UI.addDIV(
    "backgroundAddScale",
    true,
    defaultBackground + "font-size: 27px;",
	"ui"
);

GAME.UI.updateDIVText(
    "backgroundAddScale",
    "+"
);

GAME.UI.updateDIVText(
    "scale",
    toStr grid.scale + "px"
);

GAME.UI.updateDIVText(
    "backgroundRemoveScale",
    "-"
);
}


# Function manager for GUI and keybinds
action guiFunction(str function){

if(function == "addTime"){
	if(timerDelay < 10000){
		timerDelay += timerStepper;
		GAME.UI.updateDIVText("timer", toStr (timerDelay / 1000) + "s");
	}
};
	
if(function == "removeTime"){
	if(timerDelay > timerStepper){
		timerDelay -= timerStepper;
		GAME.UI.updateDIVText("timer", toStr (timerDelay / 1000) + "s");
	}
};

if(function == "toggleTimer"){
		if(pauseState == false){
			GAME.UI.updateDIVText("startGui","START");
			pauseState = true;
		} else {
			GAME.UI.updateDIVText("startGui","STOP");
			pauseState = false;
		};
};

if(function == "scaleIncrease"){
	if((num) grid.scale < maxGrid){
		(num) grid.scale += gridStepper;
		GAME.UI.updateDIVText("scale", toStr grid.scale + "px");
		updateGrid();
	}
};

if(function == "scaleDecrease"){
	if((num) grid.scale > minGrid){
		(num) grid.scale -= gridStepper;
		GAME.UI.updateDIVText("scale", toStr grid.scale + "px");
		updateGrid();
	}
};
	
if(function == "clearGrid"){
	GAME.UI.updateDIVText("startGui","START");
	pauseState = true;
	makeGrid();
};

}







public action update(num delta) {

	# Runs evolution timer
	if	(pauseState == false && GAME.TIME.now() - oldTime > timerDelay){
			tick();
		oldTime = GAME.TIME.now();
	}

	# Right-click and hold function
	if(mouseReleased == false) {
		for(num i = 0; i < (num) grid.w; i++){
			for(num j = 0; j < (num) grid.h + 1; j++){
				if((num) pos.x >  (((num) grid.scale * i) + (num) grid.x) && (num) pos.x < (((num) grid.scale * i)) + (num) grid.scale  + (num) grid.x && (num) pos.y >  (((num) grid.scale * j)  + (num) grid.y) && (num) pos.y < (((num) grid.scale * j)) + (num) grid.scale  + (num) grid.y){
						if(first == false){
							track = tiles[i][j];
							first = true;
						};

						if(tiles[i][j] != track){
							tiles[i][j] = track;
						}

						if(pauseState == false) {
							guiFunction("toggleTimer");
						};
				}
			}
		}
	}
}



# Updates all overlay elements -- Makes board visible
public action render(num delta) {
window = GAME.OVERLAY.getSize();
(num) window.height -= (num) ui.height;
if ((Math.floor((num) window.width) != Math.floor((num) oldWindow.width) || Math.floor((num) window.height) != Math.floor((num) oldWindow.height)) && initialized == true){
GAME.log(Math.floor((num) window.height), Math.floor((num) oldWindow.height));
GAME.log("updateWindow");
updateGrid();
oldWindow.width = (num) window.width;
oldWindow.height = (num) window.height;
}

pos = GAME.INPUTS.mousePosOverlay();



GAME.OVERLAY.drawRect(
	0,
	0,
	window.width,
	window.height,
	false,
	grid.backgroundColor, 
	1
);


for(num i = 0; i < lengthOf tiles; i++){
	for(num j = 0; j < lengthOf tiles[i]; j++){
		if((num) pos.x >  (((num) grid.scale * i) + (num) grid.x) && (num) pos.x < (((num) grid.scale * i)) + (num) grid.scale  + (num) grid.x && (num) pos.y >  (((num) grid.scale * j)  + (num) grid.y) && (num) pos.y < (((num) grid.scale * j)) + (num) grid.scale  + (num) grid.y){
			GAME.OVERLAY.drawRect(((num) grid.scale * i) + (num) grid.x, (j * (num) grid.scale) + (num) grid.y, (num) grid.scale, (num) grid.scale, 0, (str) grid.hoverColor, 1);
		}
		if(tiles[i][j] == 1){
			GAME.OVERLAY.drawRect(((num) grid.scale * i) + (num) grid.x, (j * (num) grid.scale) + (num) grid.y, (num) grid.scale, (num) grid.scale, 0, (str) grid.lifeColor, 1);
		} else {
			GAME.OVERLAY.drawRect(((num) grid.scale * i) + (num) grid.x, (j * (num) grid.scale) + (num) grid.y, (num) grid.scale, (num) grid.scale, 0, (str) grid.tileColor, 1);	
		}
	}
}


for(num i = 0; i < (num) grid.w + 1; i++){
		for(num j = 0; j < (num) grid.h + 1; j++){
			GAME.OVERLAY.drawLine((num) grid.scale * i + (num) grid.x, (num) grid.y, (num) grid.scale * i + (num) grid.x, (num) window.height - (num) grid.y, 1, (str) grid.borderColor, 1);
			GAME.OVERLAY.drawLine((num) grid.x, j * (num) grid.scale + (num) grid.y, (num) window.width - (num) grid.x, j * (num) grid.scale + (num) grid.y, 1, (str) grid.borderColor, 1);
		};
	}
}


# Checks if GUI element was clicked -- Timer prevents propagation

public action onDIVClicked(str id) {
if(GAME.TIME.now() - oldTime > 10){
    if (id == "startBtn") {
	GAME.UI.updateDIV(
		"startMenu", 
		"display", 
		"none"
	);
		makeGrid();
		initialized = true;
};
	
    if (id == "backgroundNext" || id == "next") {
		tick();
	};

    if (id == "backgroundClear" || id == "clear") {
		guiFunction("clearGrid");
	};

    if (id == "backgroundAddScale") {
		guiFunction("scaleIncrease");
	};

    if (id == "backgroundRemoveScale") {
		guiFunction("scaleDecrease");
	};

    if (id == "backgroundAdd") {
		guiFunction("addTime");
	};

    if (id == "backgroundRemove") {
		guiFunction("removeTime");
	};

    if (id == "startGui") {
		guiFunction("toggleTimer");
	};	


oldTime = GAME.TIME.now();
}

}


# Toggles tile alive or empty
public action onMouseClick(num button, num x, num y) {
	if(button == 1) {
		for(num i = 0; i < (num) grid.w; i++){
			for(num j = 0; j < (num) grid.h + 1; j++){
				if((num) pos.x >  (((num) grid.scale * i) + (num) grid.x) && (num) pos.x < (((num) grid.scale * i)) + (num) grid.scale  + (num) grid.x && (num) pos.y >  (((num) grid.scale * j)  + (num) grid.y) && (num) pos.y < (((num) grid.scale * j)) + (num) grid.scale  + (num) grid.y){
					if(tiles[i][j] == 1){
						tiles[i][j] = 0;
					}	else	{
						tiles[i][j] = 1;
					};
					
					if(pauseState == false) {
						guiFunction("toggleTimer");  # Stops timer if grid is clicked
					};
				}
			}
		}
	}

	if (button == 3){
		mouseReleased = false;
	}
}


public action onMouseUp(num button, num x, num y) {
	mouseReleased = true;
	first = false;
}


public action onKeyPress(str key, num code) {
	if(code == 39 || code == 68){   # D or Right Arrow
		tick();
	};

	if(code == 32){                 # Space
		guiFunction("toggleTimer");
	};

	if(code == 8){ # Backspace
		guiFunction("clearGrid");
	};

	if(code == 38 || code == 87){   # W or Up Arrow
		guiFunction("addTime");
	};

	if(code == 40 || code == 83){   # S or Down Arrow
		guiFunction("removeTime");
	};
}




public action onMouseScroll(num dir) {
if (dir == 1){ # Scroll up -- Zoom in
		guiFunction("scaleIncrease");
	}
if (dir == -1){ # Scroll up -- Zoom out
	guiFunction("scaleDecrease");
}
	
}


public action onKeyHeld(str key, num code) {}
public action onKeyUp(str key, num code) {}
public action onNetworkMessage(str id, obj data) {}

Server Script

# Server Script runs only on Hosted server & not in test mode
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd
# 
# Add custom actions here

# Runs when the game starts
public action start() {

}

# Runs every game tick
public action update(num delta) {

}

# Player spawns in
public action onPlayerSpawn(str id) {

}

# Player update
public action onPlayerUpdate(str id, num delta, static obj inputs) {

}

# Called from Custom Trigger Action
public action onCustomTrigger(str playerID, str customParam) {

}

# Server receives network message
public action onNetworkMessage(str id, obj data, str playerID) {

}

# When a player leaves the server
public action onPlayerLeave(str playerID) {

}

Custom Geometry

Client Script

# Client Script runs only on the client
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd
# 
# Add custom actions here

# Runs when the game starts
public action start() {
	
	# Create Custom Geometry vertices:
	GAME.SCENE.addCustom("", "#00ffff", num[
		0.5, 0.5, 0.5,
		-0.5, 0.5, 0.5,
		-0.5, -0.5, 0.5,
		0.5, -0.5, 0.5,
		0.5, 0.5, -0.5,
		-0.5, 0.5, -0.5,
		-0.5, -0.5, -0.5,
		0.5, -0.5, -0.5
	], 0, 10, 0, 10, 10, 10);

}


Attaching

Client Script

# Client Script runs only on the client
# KrunkScript Copyright (C) Yendis Entertainment Pty Ltd
# 
# Add custom actions here

obj leftPalm = {};
obj rightPalm = {};
obj leftCube = {};
obj rightCube = {};

# Player spawns in
public action onPlayerSpawn(str id) {
	obj pl = GAME.PLAYERS.getSelf();
	if (notEmpty pl) {
		obj dat = (obj) pl.getAsset();	
		# GET BONES
		leftPalm = (obj) dat.getBone("Palm2L");
		rightPalm = (obj) dat.getBone("Palm2R");

		# CREATE CUBES TO ATTACH
		leftCube = GAME.SCENE.addCube("13075", "#00ffff", 0, 0, 0, 0.01, 0.01, 0.01, {textureStretching:true});
		rightCube = GAME.SCENE.addCube("13075", "#ff0000", 0, 0, 0, 0.01, 0.01, 0.01, {textureStretching:true});

		# ATTACH TO PALMS
		leftCube.attachTo(leftPalm, 0, 0, 0);
		rightCube.attachTo(rightPalm, 0, 0, 0);
	}
}