var DropEffect = Container.expand(function (parent, x, y) { var self = Container.call(this); parent.addChild(self); self.x = x; self.y = y; var angle = Math.random() * Math.PI; var speed = 10 + Math.random() * 5; var speedX = Math.cos(angle) * speed; var speedY = Math.sin(angle) * speed; var spinSpeed = Math.random() * 0.2 - 0.1; var scale = 1; var scaleDecrement = 1 / 60; var variant = Math.floor(Math.random() * 3); var graphics = self.createAsset('pickup' + variant, 'Drop graphics', 0.5, 0.95); graphics.rotation = Math.random() * Math.PI * 2; self.update = update; function update(velocityX, velocityY) { self.x += speedX - velocityX; self.y += speedY - velocityY; scale -= scaleDecrement; graphics.rotation += spinSpeed; graphics.scale.set(scale); return scale <= 0; } }); var BloodsplatterEffect = Container.expand(function (parent, x, y) { var self = Container.call(this); parent.addChild(self); self.x = x; self.y = y; var duration = 15; var remaining = duration; var maxScale = 2.0; var startAlpha = 0.5; var graphics = self.createAsset('bloodsplatter', 'Bloodsplatter graphics', 0.5, 0.5); graphics.alpha = startAlpha; graphics.y = -graphics.height / 2; self.update = update; function update(velocityX, velocityY) { var progress = 1 - remaining-- / duration; if (remaining <= 0) { return true; } graphics.scale.set(1 + (maxScale - 1) * progress); graphics.alpha = startAlpha * (1 - progress); self.x -= velocityX; self.y -= velocityY; } }); var Monster = Container.expand(function (parent, x, y) { var self = Container.call(this); parent.addChild(self); self.x = x; self.y = y; var active = false; var updateTick = 0; var updatePeriod = 30; var jumpSpeedMax = 30; var jumpHeight = 100; var jumpVelocityX = 0; var jumpVelocityY = 0; var jumpDuration = updatePeriod * 2 / 3; var invulnerabilityBoost = 1.8; var jumpDistMax = jumpSpeedMax * jumpDuration; var jumpDistMaxSqr = jumpDistMax * jumpDistMax; var activationDist = 1000; var activationDistSqr = activationDist * activationDist; var despawnY = -2500; var shadowReduction = 0.1; var shadow = self.createAsset('shadow', 'Monster shadow', 0.45, 1); var graphics = self.createAsset('monster', 'Monster graphics', 0.5, 1); graphics.scale.x = Math.random() < 0.5 ? 1 : -1; shadow.width = graphics.width * 0.9; shadow.height = graphics.width * 0.45; shadow.tint = 0x000000; shadow.alpha = 0.5; self.jumping = false; self.hitbox = shadow; self.update = update; function update(velocityX, velocityY, player) { if (!active) { var dx = player.x - self.x; var dy = player.y - self.y; var distSqr = dx * dx + dy * dy; if (distSqr <= activationDistSqr) { active = true; } } if (active) { var updateCurve = (-Math.cos(++updateTick * Math.PI / (updatePeriod / 2)) + 0.5) / 1.5; if (self.jumping) { if (updateCurve <= 0) { self.jumping = false; jumpVelocityX = 0; jumpVelocityY = 0; } } else { if (updateCurve > 0) { var boost = player.invulnerable ? invulnerabilityBoost : 1; var targetPosX = player.x + velocityX * jumpDuration * boost; var targetPosY = player.y + velocityY * jumpDuration * boost; var ddx = targetPosX - self.x; var ddy = targetPosY - self.y; var angle = Math.atan2(ddy, ddx); var targetDistSqr = ddx * ddx + ddy * ddy; var jumpSpeed = jumpSpeedMax * (targetDistSqr >= jumpDistMaxSqr ? 1 : Math.sqrt(targetDistSqr) / jumpDistMax); jumpVelocityX = Math.cos(angle) * jumpSpeed; jumpVelocityY = Math.sin(angle) * jumpSpeed; self.jumping = true; } } graphics.y = -Math.max(0, updateCurve) * jumpHeight; graphics.scale.x = player.x < self.x ? -1 : 1; graphics.scale.y = 1 + Math.min(0, updateCurve); shadow.scale.set(1 - shadowReduction * updateCurve); } self.x += jumpVelocityX - velocityX; self.y += jumpVelocityY - velocityY; return self.y < despawnY; } }); var Pickup = Container.expand(function (parent, x, y) { var self = Container.call(this); parent.addChild(self); self.x = x; self.y = y; var activeDist = 300; var activeDistSqr = activeDist * activeDist; var collectDist = 50; var collectDistSqr = collectDist * collectDist; var collectSpeed = 25; var collectOffset = -50; var active = false; var variant = Math.floor(Math.random() * 3); var graphics = self.createAsset('pickup' + variant, 'Pickup graphics', 0.5, 0.95); var backShadow = self.createAsset('shadow', 'Pickup background shadow', 0.5, 0.5); var foreShadow = self.createAsset('shadow', 'Pickup foreground shadow', 0.5, 0.5); foreShadow.width = graphics.width; foreShadow.height = graphics.width * 0.25; backShadow.width = graphics.width; backShadow.height = graphics.width * 0.25; backShadow.tint = 0x000000; backShadow.y = -3; self.update = update; function update(velocityX, velocityY, player) { var dx = player.x - self.x; var dy = player.y + collectOffset - self.y; var distSqr = dx * dx + dy * dy; if (self.active) { var dist = Math.sqrt(distSqr); self.x += dx / dist * collectSpeed; self.y += dy / dist * collectSpeed; } else if (distSqr <= activeDistSqr) { self.active = true; backShadow.destroy(); foreShadow.destroy(); } else { self.x -= velocityX; self.y -= velocityY; } return distSqr <= collectDistSqr; } }); var LandscapeTile = Container.expand(function (parent, x, y, {lookup, xIndex, yIndex, width, height, density, pickups, monsters, player, key, landscapeTiles}) { var self = Container.call(this); parent.addChild(self); self.x = x; self.y = y; var pickupChance = 0.1; var monsterChance = 0.01; var distanceFactor = 10000; var monsterDistance = 20000; var obstructions = []; var visual = self.createAsset('hitbox', 'Landscape area', 0.5, 0.5); visual.alpha = 0; visual.width = width; visual.height = height; self.activate = activate; self.width = width; self.height = height; self.active = false; self.key = key; self.checkCollision = checkCollision; function checkCollision(player) { var reach = 100; var dx = Math.abs(player.x - self.x); var dy = Math.abs(player.y - self.y); if (dx < width / 2 + reach && dy < height / 2 + reach) { for (var i = 0; i < obstructions.length; i++) { var obstruction = obstructions[i]; if (obstruction.active && player.hitbox.intersects(obstruction.hitbox)) { obstruction.deactivate(); return true; } } } return false; } function activate() { if (!self.active) { self.active = true; var densityChance = density * Math.random(); var obstructionTypes = ['largeTree', 'smallTree', 'smallTree', 'deadTree', 'smallRock', 'smallRock', 'largeRock', 'stump']; var clearanceDist = 150; var clearanceDistSqr = clearanceDist * clearanceDist; var spawnBorder = 25; var cellSize = 150; var cols = Math.ceil(width / cellSize); var rows = Math.ceil(height / cellSize); var cellWidth = width / cols; var cellHeight = height / rows; var pickup; if (Math.random() <= 0.1) { var {pointX, pointY} = randomPointInRect(self.x, self.y, width, height, clearanceDist); pickups.push(pickup = new Pickup(parent, pointX, pointY)); } if (player.distanceTravelled > monsterDistance && Math.random() <= monsterChance) { var {pointX, pointY} = randomPointInRect(self.x, self.y, width, height, clearanceDist); monsters.push(new Monster(parent, self.x, self.y)); } for (i = 0; i < cols; i++) { for (j = 0; j < rows; j++) { if (Math.random() <= densityChance) { var canPlace = true; var {pointX, pointY} = randomPointInRect(cellWidth * i, cellHeight * j, cellWidth, cellHeight, spawnBorder); if (pickup) { var dx = pickup.x - pointX; var dy = pickup.y - pointY; var distSqr = dx * dx + dy * dy; if (distSqr <= clearanceDistSqr) { canPlace = false; } } if (canPlace) { var type = obstructionTypes[Math.floor(Math.random() * obstructionTypes.length)]; obstructions.push(new Obstruction(self, pointX, pointY, type)); } } } } } var leftKey = xIndex - 1 + ':' + yIndex; var rightKey = xIndex + 1 + ':' + yIndex; var downKey = xIndex + ':' + (yIndex + 1); var newDensity = (1 + Math.pow(Math.log(player.distanceTravelled / distanceFactor + 2), 2)) / 100; if (!lookup[leftKey]) { landscapeTiles.push(lookup[leftKey] = new LandscapeTile(parent, self.x - width, self.y, { key: leftKey, density: newDensity, xIndex: xIndex - 1, yIndex, landscapeTiles, width, height, lookup, pickups, monsters, player })); } if (!lookup[rightKey]) { landscapeTiles.push(lookup[rightKey] = new LandscapeTile(parent, self.x + width, self.y, { key: rightKey, density: newDensity, xIndex: xIndex + 1, yIndex, landscapeTiles, width, height, lookup, pickups, monsters, player })); } if (!lookup[downKey]) { landscapeTiles.push(lookup[downKey] = new LandscapeTile(parent, self.x, self.y + height, { key: downKey, density: newDensity, yIndex: yIndex + 1, xIndex, landscapeTiles, width, height, lookup, pickups, monsters, player })); } function randomPointInRect(centerX, centerY, width, height, border = 0) { var startX = centerX - width / 2 + border; var startY = centerY - height / 2 + border; var innerWidth = width - border * 2; var innerHeight = height - border * 2; var pointX = startX + Math.random() * innerWidth; var pointY = startY + Math.random() * innerHeight; return { pointX, pointY }; } } }); var Obstruction = Container.expand(function (parent, x, y, type) { var self = Container.call(this); parent.addChild(self); self.x = x; self.y = y; var graphics = self.createAsset(type, 'Obstruction graphics', 0.5, 1); var hitbox = self.createAsset('hitbox', 'Obstruction hitbox', 0.5, 0.5); var randomScale = 0.9 + Math.random() * 0.2; hitbox.y = -25; hitbox.alpha = 0; hitbox.width = graphics.width * 0.8; hitbox.height = graphics.width * 0.45; self.scale.set(randomScale, randomScale); self.active = true; self.hitbox = hitbox; self.deactivate = deactivate; function deactivate() { self.active = false; graphics.alpha = 0.5; } }); var Player = Container.expand(function (parent, x, y) { var self = Container.call(this); parent.addChild(self); self.x = x; self.y = y; var speed = 0; var tiltSpeedFactor = 100; var platformGraphics = self.createAsset('platform', 'Platform image', 0.4, 0.5); var playerGraphics = self.createAsset('player', 'Player character', 0.575, 1); var hitbox = self.createAsset('hitbox', 'Player Hitbox', 0.5, 0.5); hitbox.width = 25; hitbox.height = 25; hitbox.alpha = 0; self.invulnerable = false; self.invulnerableTime = 3 * 60; self.invulnerableTimer = 0; self.distanceTravelled = 0; self.hitbox = hitbox; self.update = update; function update(targetPosition) { if (self.invulnerable) { if (--self.invulnerableTimer <= 0) { self.invulnerable = false; } self.alpha = self.invulnerableTimer % 60 < 30 ? 1 : 0.5; } var dx = targetPosition.x - self.x; var dy = targetPosition.y - self.y; var angle = angleClamp(Math.atan2(dy, dx)); var acceleration = (Math.sin(angle) - 0.1) * 2.0; var reduction = self.invulnerable ? 1 - self.invulnerableTimer / self.invulnerableTime : 1; speed = Math.max(0, speed * 0.95 + acceleration); var velocityX = Math.cos(angle) * reduction * speed; var velocityY = Math.sin(angle) * reduction * speed; var facingSide = targetPosition.x < self.x ? -1 : 1; self.distanceTravelled += speed; platformGraphics.rotation = angle; playerGraphics.rotation = speed * reduction / tiltSpeedFactor * (Math.PI / 2) * facingSide; playerGraphics.scale.x = facingSide; return { velocityX, velocityY }; } function angleClamp(angle) { return angle >= 0 ? angle : angle < -Math.PI / 2 ? Math.PI : 0; } }); var Trail = Container.expand(function (parent, x, y) { var self = Container.call(this); parent.addChild(self); self.x = x; self.y = y; var trailLength = 60; var trailAlpha = 0.4; var trailElements = []; self.update = update; function update(velocityX, velocityY) { var trailElement = null; if (velocityX !== 0 || velocityY !== 0) { trailElement = self.createAsset('trail', 'Trail element', 0, 0.5); var angle = Math.atan2(velocityY, velocityX); var speed = Math.sqrt(velocityX * velocityX + velocityY * velocityY); trailElement.rotation = angle; trailElement.scale.x = speed / 100; } trailElements.push(trailElement); if (trailElements.length > trailLength) { var removedElement = trailElements.shift(); if (removedElement) { removedElement.destroy(); } } for (var i = trailElements.length - 1; i >= 0; i--) { var element = trailElements[i]; if (element) { var alpha = trailAlpha * (i / trailLength); element.x -= velocityX; element.y -= velocityY; element.alpha = alpha; } } } }); var Interface = Container.expand(function (parent, x, y) { var self = Container.call(this); parent.addChild(self); self.x = x; self.y = y; var score = 0; var lives = 3; var lifeIcons = []; var scoreText = new Text2('| ' + score, { size: 70, fill: "#000000", align: 'left', font: "'Press Start 2P', monospace" }); self.addChild(scoreText); for (var i = 0; i < lives; i++) { var lifeIcon = self.createAsset('lifeIcon', 'Life icon', 1, 0); lifeIcon.x = -30 - i * 80; lifeIcon.y = scoreText.height - 60; lifeIcons.push(lifeIcon); } scoreText.x = -5; scoreText.y = 0; scoreText.anchor.set(0, 0); self.changeScore = changeScore; self.changeLives = changeLives; function changeLives(amount) { lives += amount; for (var i = 0; i < lifeIcons.length; i++) { lifeIcons[i].alpha = i < lives ? 1 : 0.5; } if (amount < 0) { LK.effects.flashScreen(0xaa0000, 300); if (lives <= 0) { return true; } } return false; } function changeScore(amount) { var returnValue = amount < 0 && score > 0; score = Math.max(0, score + amount); LK.setScore(score); scoreText.setText('| ' + score); return returnValue; } }); var Game = Container.expand(function () { var self = Container.call(this); var stageWidth = 2048; var stageHeight = 2732; var tileSize = 512; var playerPosX = Math.round(stageWidth / 2); var playerPosY = Math.round(stageWidth / 3); var tileMargin = tileSize; var background = self.createAsset('background', 'Fullscreen background', 0, 0); var landscapeLookup = {}; var landscapeTiles = []; var pickups = []; var monsters = []; var effects = []; var speed = 0; background.width = stageWidth; background.height = stageHeight; var interface = new Interface(self, 0, 10); LK.gui.topCenter.addChild(interface); var trail = new Trail(self, playerPosX, playerPosY); var player = new Player(self, playerPosX, playerPosY); landscapeTiles.push(landscapeLookup['0:0'] = new LandscapeTile(self, player.x, player.y, { width: tileSize, height: tileSize, density: -1, lookup: landscapeLookup, xIndex: 0, yIndex: 0, key: '0:0', landscapeTiles, monsters, pickups, player })); var isMouseDown = false; var targetPosition = { x: playerPosX, y: playerPosY }; stage.on('down', function (obj) { isMouseDown = true; updatePosition(obj.event); }); stage.on('up', function (obj) { isMouseDown = false; }); stage.on('move', function (obj) { if (isMouseDown) { updatePosition(obj.event); } }); LK.on('tick', function () { var {velocityX, velocityY} = player.update(targetPosition); trail.update(velocityX, velocityY); for (var i = landscapeTiles.length - 1; i >= 0; i--) { var landscapeTile = landscapeTiles[i]; landscapeTile.x -= velocityX; landscapeTile.y -= velocityY; if (landscapeTile.y < -(tileSize / 2 + tileMargin)) { landscapeTile.destroy(); landscapeTiles.splice(i, 1); landscapeLookup[landscapeTile.key] = undefined; } else if (!landscapeTile.active && landscapeTile.x > -tileMargin && landscapeTile.x < stageWidth + tileMargin && landscapeTile.y < stageHeight + tileMargin) { landscapeTile.activate(); } else if (landscapeTile.checkCollision(player)) { if (interface.changeScore(-1)) { effects.push(new DropEffect(self, player.x, player.y)); } if (!player.invulnerable) { player.invulnerable = true; player.invulnerableTimer = player.invulnerableTime; } } } for (var i = pickups.length - 1; i >= 0; i--) { var pickup = pickups[i]; if (pickup.update(velocityX, velocityY, player)) { interface.changeScore(1); pickup.destroy(); pickups.splice(i, 1); } else if (pickup.y < -(tileSize / 2 + tileMargin) || pickup.active && player.hitbox.intersects(pickup.hitbox)) { pickup.destroy(); pickups.splice(i, 1); } } for (var i = effects.length - 1; i >= 0; i--) { var effect = effects[i]; if (effect.update(velocityX, velocityY)) { effect.destroy(); effects.splice(i, 1); } } for (var i = monsters.length - 1; i >= 0; i--) { var monster = monsters[i]; if (monster.update(velocityX, velocityY, player)) { monster.destroy(); monsters.splice(i, 1); } else if (!monster.jumping && monster.hitbox.intersects(player.hitbox)) { effects.push(new BloodsplatterEffect(self, monster.x, monster.y)); monster.destroy(); monsters.splice(i, 1); if (interface.changeLives(-1)) { LK.showGameOver(); } } } }); function updatePosition(event) { var pos = event.getLocalPosition(self); targetPosition.x = pos.x; targetPosition.y = pos.y; } });
var DropEffect = Container.expand(function (parent, x, y) {
var self = Container.call(this);
parent.addChild(self);
self.x = x;
self.y = y;
var angle = Math.random() * Math.PI;
var speed = 10 + Math.random() * 5;
var speedX = Math.cos(angle) * speed;
var speedY = Math.sin(angle) * speed;
var spinSpeed = Math.random() * 0.2 - 0.1;
var scale = 1;
var scaleDecrement = 1 / 60;
var variant = Math.floor(Math.random() * 3);
var graphics = self.createAsset('pickup' + variant, 'Drop graphics', 0.5, 0.95);
graphics.rotation = Math.random() * Math.PI * 2;
self.update = update;
function update(velocityX, velocityY) {
self.x += speedX - velocityX;
self.y += speedY - velocityY;
scale -= scaleDecrement;
graphics.rotation += spinSpeed;
graphics.scale.set(scale);
return scale <= 0;
}
});
var BloodsplatterEffect = Container.expand(function (parent, x, y) {
var self = Container.call(this);
parent.addChild(self);
self.x = x;
self.y = y;
var duration = 15;
var remaining = duration;
var maxScale = 2.0;
var startAlpha = 0.5;
var graphics = self.createAsset('bloodsplatter', 'Bloodsplatter graphics', 0.5, 0.5);
graphics.alpha = startAlpha;
graphics.y = -graphics.height / 2;
self.update = update;
function update(velocityX, velocityY) {
var progress = 1 - remaining-- / duration;
if (remaining <= 0) {
return true;
}
graphics.scale.set(1 + (maxScale - 1) * progress);
graphics.alpha = startAlpha * (1 - progress);
self.x -= velocityX;
self.y -= velocityY;
}
});
var Monster = Container.expand(function (parent, x, y) {
var self = Container.call(this);
parent.addChild(self);
self.x = x;
self.y = y;
var active = false;
var updateTick = 0;
var updatePeriod = 30;
var jumpSpeedMax = 30;
var jumpHeight = 100;
var jumpVelocityX = 0;
var jumpVelocityY = 0;
var jumpDuration = updatePeriod * 2 / 3;
var invulnerabilityBoost = 1.8;
var jumpDistMax = jumpSpeedMax * jumpDuration;
var jumpDistMaxSqr = jumpDistMax * jumpDistMax;
var activationDist = 1000;
var activationDistSqr = activationDist * activationDist;
var despawnY = -2500;
var shadowReduction = 0.1;
var shadow = self.createAsset('shadow', 'Monster shadow', 0.45, 1);
var graphics = self.createAsset('monster', 'Monster graphics', 0.5, 1);
graphics.scale.x = Math.random() < 0.5 ? 1 : -1;
shadow.width = graphics.width * 0.9;
shadow.height = graphics.width * 0.45;
shadow.tint = 0x000000;
shadow.alpha = 0.5;
self.jumping = false;
self.hitbox = shadow;
self.update = update;
function update(velocityX, velocityY, player) {
if (!active) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distSqr = dx * dx + dy * dy;
if (distSqr <= activationDistSqr) {
active = true;
}
}
if (active) {
var updateCurve = (-Math.cos(++updateTick * Math.PI / (updatePeriod / 2)) + 0.5) / 1.5;
if (self.jumping) {
if (updateCurve <= 0) {
self.jumping = false;
jumpVelocityX = 0;
jumpVelocityY = 0;
}
} else {
if (updateCurve > 0) {
var boost = player.invulnerable ? invulnerabilityBoost : 1;
var targetPosX = player.x + velocityX * jumpDuration * boost;
var targetPosY = player.y + velocityY * jumpDuration * boost;
var ddx = targetPosX - self.x;
var ddy = targetPosY - self.y;
var angle = Math.atan2(ddy, ddx);
var targetDistSqr = ddx * ddx + ddy * ddy;
var jumpSpeed = jumpSpeedMax * (targetDistSqr >= jumpDistMaxSqr ? 1 : Math.sqrt(targetDistSqr) / jumpDistMax);
jumpVelocityX = Math.cos(angle) * jumpSpeed;
jumpVelocityY = Math.sin(angle) * jumpSpeed;
self.jumping = true;
}
}
graphics.y = -Math.max(0, updateCurve) * jumpHeight;
graphics.scale.x = player.x < self.x ? -1 : 1;
graphics.scale.y = 1 + Math.min(0, updateCurve);
shadow.scale.set(1 - shadowReduction * updateCurve);
}
self.x += jumpVelocityX - velocityX;
self.y += jumpVelocityY - velocityY;
return self.y < despawnY;
}
});
var Pickup = Container.expand(function (parent, x, y) {
var self = Container.call(this);
parent.addChild(self);
self.x = x;
self.y = y;
var activeDist = 300;
var activeDistSqr = activeDist * activeDist;
var collectDist = 50;
var collectDistSqr = collectDist * collectDist;
var collectSpeed = 25;
var collectOffset = -50;
var active = false;
var variant = Math.floor(Math.random() * 3);
var graphics = self.createAsset('pickup' + variant, 'Pickup graphics', 0.5, 0.95);
var backShadow = self.createAsset('shadow', 'Pickup background shadow', 0.5, 0.5);
var foreShadow = self.createAsset('shadow', 'Pickup foreground shadow', 0.5, 0.5);
foreShadow.width = graphics.width;
foreShadow.height = graphics.width * 0.25;
backShadow.width = graphics.width;
backShadow.height = graphics.width * 0.25;
backShadow.tint = 0x000000;
backShadow.y = -3;
self.update = update;
function update(velocityX, velocityY, player) {
var dx = player.x - self.x;
var dy = player.y + collectOffset - self.y;
var distSqr = dx * dx + dy * dy;
if (self.active) {
var dist = Math.sqrt(distSqr);
self.x += dx / dist * collectSpeed;
self.y += dy / dist * collectSpeed;
} else if (distSqr <= activeDistSqr) {
self.active = true;
backShadow.destroy();
foreShadow.destroy();
} else {
self.x -= velocityX;
self.y -= velocityY;
}
return distSqr <= collectDistSqr;
}
});
var LandscapeTile = Container.expand(function (parent, x, y, {lookup, xIndex, yIndex, width, height, density, pickups, monsters, player, key, landscapeTiles}) {
var self = Container.call(this);
parent.addChild(self);
self.x = x;
self.y = y;
var pickupChance = 0.1;
var monsterChance = 0.01;
var distanceFactor = 10000;
var monsterDistance = 20000;
var obstructions = [];
var visual = self.createAsset('hitbox', 'Landscape area', 0.5, 0.5);
visual.alpha = 0;
visual.width = width;
visual.height = height;
self.activate = activate;
self.width = width;
self.height = height;
self.active = false;
self.key = key;
self.checkCollision = checkCollision;
function checkCollision(player) {
var reach = 100;
var dx = Math.abs(player.x - self.x);
var dy = Math.abs(player.y - self.y);
if (dx < width / 2 + reach && dy < height / 2 + reach) {
for (var i = 0; i < obstructions.length; i++) {
var obstruction = obstructions[i];
if (obstruction.active && player.hitbox.intersects(obstruction.hitbox)) {
obstruction.deactivate();
return true;
}
}
}
return false;
}
function activate() {
if (!self.active) {
self.active = true;
var densityChance = density * Math.random();
var obstructionTypes = ['largeTree', 'smallTree', 'smallTree', 'deadTree', 'smallRock', 'smallRock', 'largeRock', 'stump'];
var clearanceDist = 150;
var clearanceDistSqr = clearanceDist * clearanceDist;
var spawnBorder = 25;
var cellSize = 150;
var cols = Math.ceil(width / cellSize);
var rows = Math.ceil(height / cellSize);
var cellWidth = width / cols;
var cellHeight = height / rows;
var pickup;
if (Math.random() <= 0.1) {
var {pointX, pointY} = randomPointInRect(self.x, self.y, width, height, clearanceDist);
pickups.push(pickup = new Pickup(parent, pointX, pointY));
}
if (player.distanceTravelled > monsterDistance && Math.random() <= monsterChance) {
var {pointX, pointY} = randomPointInRect(self.x, self.y, width, height, clearanceDist);
monsters.push(new Monster(parent, self.x, self.y));
}
for (i = 0; i < cols; i++) {
for (j = 0; j < rows; j++) {
if (Math.random() <= densityChance) {
var canPlace = true;
var {pointX, pointY} = randomPointInRect(cellWidth * i, cellHeight * j, cellWidth, cellHeight, spawnBorder);
if (pickup) {
var dx = pickup.x - pointX;
var dy = pickup.y - pointY;
var distSqr = dx * dx + dy * dy;
if (distSqr <= clearanceDistSqr) {
canPlace = false;
}
}
if (canPlace) {
var type = obstructionTypes[Math.floor(Math.random() * obstructionTypes.length)];
obstructions.push(new Obstruction(self, pointX, pointY, type));
}
}
}
}
}
var leftKey = xIndex - 1 + ':' + yIndex;
var rightKey = xIndex + 1 + ':' + yIndex;
var downKey = xIndex + ':' + (yIndex + 1);
var newDensity = (1 + Math.pow(Math.log(player.distanceTravelled / distanceFactor + 2), 2)) / 100;
if (!lookup[leftKey]) {
landscapeTiles.push(lookup[leftKey] = new LandscapeTile(parent, self.x - width, self.y, {
key: leftKey,
density: newDensity,
xIndex: xIndex - 1,
yIndex,
landscapeTiles,
width,
height,
lookup,
pickups,
monsters,
player
}));
}
if (!lookup[rightKey]) {
landscapeTiles.push(lookup[rightKey] = new LandscapeTile(parent, self.x + width, self.y, {
key: rightKey,
density: newDensity,
xIndex: xIndex + 1,
yIndex,
landscapeTiles,
width,
height,
lookup,
pickups,
monsters,
player
}));
}
if (!lookup[downKey]) {
landscapeTiles.push(lookup[downKey] = new LandscapeTile(parent, self.x, self.y + height, {
key: downKey,
density: newDensity,
yIndex: yIndex + 1,
xIndex,
landscapeTiles,
width,
height,
lookup,
pickups,
monsters,
player
}));
}
function randomPointInRect(centerX, centerY, width, height, border = 0) {
var startX = centerX - width / 2 + border;
var startY = centerY - height / 2 + border;
var innerWidth = width - border * 2;
var innerHeight = height - border * 2;
var pointX = startX + Math.random() * innerWidth;
var pointY = startY + Math.random() * innerHeight;
return {
pointX,
pointY
};
}
}
});
var Obstruction = Container.expand(function (parent, x, y, type) {
var self = Container.call(this);
parent.addChild(self);
self.x = x;
self.y = y;
var graphics = self.createAsset(type, 'Obstruction graphics', 0.5, 1);
var hitbox = self.createAsset('hitbox', 'Obstruction hitbox', 0.5, 0.5);
var randomScale = 0.9 + Math.random() * 0.2;
hitbox.y = -25;
hitbox.alpha = 0;
hitbox.width = graphics.width * 0.8;
hitbox.height = graphics.width * 0.45;
self.scale.set(randomScale, randomScale);
self.active = true;
self.hitbox = hitbox;
self.deactivate = deactivate;
function deactivate() {
self.active = false;
graphics.alpha = 0.5;
}
});
var Player = Container.expand(function (parent, x, y) {
var self = Container.call(this);
parent.addChild(self);
self.x = x;
self.y = y;
var speed = 0;
var tiltSpeedFactor = 100;
var platformGraphics = self.createAsset('platform', 'Platform image', 0.4, 0.5);
var playerGraphics = self.createAsset('player', 'Player character', 0.575, 1);
var hitbox = self.createAsset('hitbox', 'Player Hitbox', 0.5, 0.5);
hitbox.width = 25;
hitbox.height = 25;
hitbox.alpha = 0;
self.invulnerable = false;
self.invulnerableTime = 3 * 60;
self.invulnerableTimer = 0;
self.distanceTravelled = 0;
self.hitbox = hitbox;
self.update = update;
function update(targetPosition) {
if (self.invulnerable) {
if (--self.invulnerableTimer <= 0) {
self.invulnerable = false;
}
self.alpha = self.invulnerableTimer % 60 < 30 ? 1 : 0.5;
}
var dx = targetPosition.x - self.x;
var dy = targetPosition.y - self.y;
var angle = angleClamp(Math.atan2(dy, dx));
var acceleration = (Math.sin(angle) - 0.1) * 2.0;
var reduction = self.invulnerable ? 1 - self.invulnerableTimer / self.invulnerableTime : 1;
speed = Math.max(0, speed * 0.95 + acceleration);
var velocityX = Math.cos(angle) * reduction * speed;
var velocityY = Math.sin(angle) * reduction * speed;
var facingSide = targetPosition.x < self.x ? -1 : 1;
self.distanceTravelled += speed;
platformGraphics.rotation = angle;
playerGraphics.rotation = speed * reduction / tiltSpeedFactor * (Math.PI / 2) * facingSide;
playerGraphics.scale.x = facingSide;
return {
velocityX,
velocityY
};
}
function angleClamp(angle) {
return angle >= 0 ? angle : angle < -Math.PI / 2 ? Math.PI : 0;
}
});
var Trail = Container.expand(function (parent, x, y) {
var self = Container.call(this);
parent.addChild(self);
self.x = x;
self.y = y;
var trailLength = 60;
var trailAlpha = 0.4;
var trailElements = [];
self.update = update;
function update(velocityX, velocityY) {
var trailElement = null;
if (velocityX !== 0 || velocityY !== 0) {
trailElement = self.createAsset('trail', 'Trail element', 0, 0.5);
var angle = Math.atan2(velocityY, velocityX);
var speed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
trailElement.rotation = angle;
trailElement.scale.x = speed / 100;
}
trailElements.push(trailElement);
if (trailElements.length > trailLength) {
var removedElement = trailElements.shift();
if (removedElement) {
removedElement.destroy();
}
}
for (var i = trailElements.length - 1; i >= 0; i--) {
var element = trailElements[i];
if (element) {
var alpha = trailAlpha * (i / trailLength);
element.x -= velocityX;
element.y -= velocityY;
element.alpha = alpha;
}
}
}
});
var Interface = Container.expand(function (parent, x, y) {
var self = Container.call(this);
parent.addChild(self);
self.x = x;
self.y = y;
var score = 0;
var lives = 3;
var lifeIcons = [];
var scoreText = new Text2('| ' + score, {
size: 70,
fill: "#000000",
align: 'left',
font: "'Press Start 2P', monospace"
});
self.addChild(scoreText);
for (var i = 0; i < lives; i++) {
var lifeIcon = self.createAsset('lifeIcon', 'Life icon', 1, 0);
lifeIcon.x = -30 - i * 80;
lifeIcon.y = scoreText.height - 60;
lifeIcons.push(lifeIcon);
}
scoreText.x = -5;
scoreText.y = 0;
scoreText.anchor.set(0, 0);
self.changeScore = changeScore;
self.changeLives = changeLives;
function changeLives(amount) {
lives += amount;
for (var i = 0; i < lifeIcons.length; i++) {
lifeIcons[i].alpha = i < lives ? 1 : 0.5;
}
if (amount < 0) {
LK.effects.flashScreen(0xaa0000, 300);
if (lives <= 0) {
return true;
}
}
return false;
}
function changeScore(amount) {
var returnValue = amount < 0 && score > 0;
score = Math.max(0, score + amount);
LK.setScore(score);
scoreText.setText('| ' + score);
return returnValue;
}
});
var Game = Container.expand(function () {
var self = Container.call(this);
var stageWidth = 2048;
var stageHeight = 2732;
var tileSize = 512;
var playerPosX = Math.round(stageWidth / 2);
var playerPosY = Math.round(stageWidth / 3);
var tileMargin = tileSize;
var background = self.createAsset('background', 'Fullscreen background', 0, 0);
var landscapeLookup = {};
var landscapeTiles = [];
var pickups = [];
var monsters = [];
var effects = [];
var speed = 0;
background.width = stageWidth;
background.height = stageHeight;
var interface = new Interface(self, 0, 10);
LK.gui.topCenter.addChild(interface);
var trail = new Trail(self, playerPosX, playerPosY);
var player = new Player(self, playerPosX, playerPosY);
landscapeTiles.push(landscapeLookup['0:0'] = new LandscapeTile(self, player.x, player.y, {
width: tileSize,
height: tileSize,
density: -1,
lookup: landscapeLookup,
xIndex: 0,
yIndex: 0,
key: '0:0',
landscapeTiles,
monsters,
pickups,
player
}));
var isMouseDown = false;
var targetPosition = {
x: playerPosX,
y: playerPosY
};
stage.on('down', function (obj) {
isMouseDown = true;
updatePosition(obj.event);
});
stage.on('up', function (obj) {
isMouseDown = false;
});
stage.on('move', function (obj) {
if (isMouseDown) {
updatePosition(obj.event);
}
});
LK.on('tick', function () {
var {velocityX, velocityY} = player.update(targetPosition);
trail.update(velocityX, velocityY);
for (var i = landscapeTiles.length - 1; i >= 0; i--) {
var landscapeTile = landscapeTiles[i];
landscapeTile.x -= velocityX;
landscapeTile.y -= velocityY;
if (landscapeTile.y < -(tileSize / 2 + tileMargin)) {
landscapeTile.destroy();
landscapeTiles.splice(i, 1);
landscapeLookup[landscapeTile.key] = undefined;
} else if (!landscapeTile.active && landscapeTile.x > -tileMargin && landscapeTile.x < stageWidth + tileMargin && landscapeTile.y < stageHeight + tileMargin) {
landscapeTile.activate();
} else if (landscapeTile.checkCollision(player)) {
if (interface.changeScore(-1)) {
effects.push(new DropEffect(self, player.x, player.y));
}
if (!player.invulnerable) {
player.invulnerable = true;
player.invulnerableTimer = player.invulnerableTime;
}
}
}
for (var i = pickups.length - 1; i >= 0; i--) {
var pickup = pickups[i];
if (pickup.update(velocityX, velocityY, player)) {
interface.changeScore(1);
pickup.destroy();
pickups.splice(i, 1);
} else if (pickup.y < -(tileSize / 2 + tileMargin) || pickup.active && player.hitbox.intersects(pickup.hitbox)) {
pickup.destroy();
pickups.splice(i, 1);
}
}
for (var i = effects.length - 1; i >= 0; i--) {
var effect = effects[i];
if (effect.update(velocityX, velocityY)) {
effect.destroy();
effects.splice(i, 1);
}
}
for (var i = monsters.length - 1; i >= 0; i--) {
var monster = monsters[i];
if (monster.update(velocityX, velocityY, player)) {
monster.destroy();
monsters.splice(i, 1);
} else if (!monster.jumping && monster.hitbox.intersects(player.hitbox)) {
effects.push(new BloodsplatterEffect(self, monster.x, monster.y));
monster.destroy();
monsters.splice(i, 1);
if (interface.changeLives(-1)) {
LK.showGameOver();
}
}
}
});
function updatePosition(event) {
var pos = event.getLocalPosition(self);
targetPosition.x = pos.x;
targetPosition.y = pos.y;
}
});
two vertical lines with a blank background.
8 bit. cartoon. shark. ingame asset. seen from the front. open mouth. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
8-bit. cartoon. gold coin. in game asset. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
8-bit. cartoon. old boot from ocean. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
8 bit blood spatter. cartoon. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
8 bit. cartoon. old car tire. floating in the water. in game asset. no background. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
8 bit. cartoon. tree log. floating in the water. in game asset. no background. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
8 bit. cartoon. big buoy. floating in the water. in game asset. no background. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
8 bit. cartoon. bottle with a message . floating in the water. in game asset. no background. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
8 bit. cartoon. old open can. rusty . floating in the water. in game asset. no background. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.