/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Arrow class (projectile)
var Arrow = Container.expand(function () {
var self = Container.call(this);
var arrowGfx = self.attachAsset('arrow', {
anchorX: 0.1,
anchorY: 0.5
});
self.vx = 0;
self.vy = 0;
self.owner = null;
self.lifetime = 60;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
self.lifetime--;
if (self.lifetime <= 0 || self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 3000) {
self.destroy();
}
};
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
self.isAI = false;
self.hp = 100;
self.maxHp = 100;
self.stocks = 3;
self.facing = 1; // 1 = right, -1 = left
self.vx = 0;
self.vy = 0;
self.grounded = false;
self.jumps = 3;
self.maxJumps = 3;
self.wallCling = false;
self.gravityCancel = false;
self.gravityCancelTimer = 0;
self.dashTimer = 0;
self.dashDir = 0;
self.dashCooldown = 0;
self.attackCooldown = 0;
self.hitstun = 0;
self.invuln = 0;
self.weapon = null;
self.input = {
left: false,
right: false,
up: false,
down: false,
jump: false,
attackLight: false,
attackHeavy: false,
dash: false,
throwWeapon: false,
gravityCancel: false
};
self.lastInput = {};
self.charType = 1; // 1 or 2
self.name = '';
self.spawnX = 0;
self.spawnY = 0;
self.dead = false;
// Graphics
var charGfx = self.attachAsset(self.charType === 1 ? 'char1' : 'char2', {
anchorX: 0.5,
anchorY: 0.5
});
self.hpBar = new Text2('100', {
size: 60,
fill: "#fff"
});
self.hpBar.anchor.set(0.5, 0.5);
self.addChild(self.hpBar);
self.setCharType = function (type) {
self.charType = type;
charGfx.destroy();
var newGfx = self.attachAsset(type === 1 ? 'char1' : 'char2', {
anchorX: 0.5,
anchorY: 0.5
});
};
self.respawn = function () {
self.x = self.spawnX;
self.y = self.spawnY;
self.vx = 0;
self.vy = 0;
self.hp = self.maxHp;
self.jumps = self.maxJumps;
self.grounded = false;
self.wallCling = false;
self.gravityCancel = false;
self.gravityCancelTimer = 0;
self.dashTimer = 0;
self.dashDir = 0;
self.dashCooldown = 0;
self.attackCooldown = 0;
self.hitstun = 0;
self.invuln = 60;
self.dead = false;
};
self.takeDamage = function (amount, knockbackX, knockbackY) {
if (self.invuln > 0) return;
self.hp -= amount;
self.vx += knockbackX;
self.vy += knockbackY;
self.hitstun = 18 + Math.floor(amount * 0.7);
self.invuln = 18;
// Flash effect
LK.effects.flashObject(self, 0xffe066, 200);
// Show hit effect
var fx = LK.getAsset('hitfx', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y
});
game.addChild(fx);
tween(fx, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
fx.destroy();
}
});
if (self.hp <= 0) {
self.stocks--;
self.dead = true;
self.hp = 0;
self.vx = 0;
self.vy = 0;
self.x = -9999;
self.y = -9999;
if (self.stocks > 0) {
LK.setTimeout(function () {
self.respawn();
}, 1200);
}
}
};
self.pickupWeapon = function (weapon) {
if (self.weapon) return;
weapon.pickup(self);
self.weapon = weapon;
};
self.dropWeapon = function () {
if (!self.weapon) return;
self.weapon.drop();
self.weapon = null;
};
self.throwWeapon = function () {
if (!self.weapon) return;
var throwVX = self.facing * 24 + self.vx * 0.5;
var throwVY = -8 + self.vy * 0.2;
self.weapon.throwWeapon(throwVX, throwVY);
self.weapon = null;
};
self.attack = function (type) {
if (!self.weapon) return;
if (self.weapon.cooldown > 0) return;
self.weapon.attack(type, self.facing);
};
self.gravityCancelStart = function () {
if (self.gravityCancel || self.grounded) return;
self.gravityCancel = true;
self.gravityCancelTimer = 24;
self.vx *= 0.7;
self.vy *= 0.7;
};
self.gravityCancelEnd = function () {
self.gravityCancel = false;
self.gravityCancelTimer = 0;
};
self.update = function () {
// Update HP bar
self.hpBar.setText(self.hp + '');
self.hpBar.x = 0;
self.hpBar.y = -140;
if (self.dead) return;
// Invuln
if (self.invuln > 0) self.invuln--;
// Hitstun
if (self.hitstun > 0) {
self.hitstun--;
self.x += self.vx;
self.y += self.vy;
self.vx *= 0.85;
self.vy *= 0.85;
return;
}
// Dashing
if (self.dashTimer > 0) {
self.dashTimer--;
self.x += self.dashDir * 32;
if (self.dashTimer === 0) {
self.dashCooldown = 24;
}
} else {
// Movement
if (self.input.left) {
self.vx -= 2.2;
self.facing = -1;
}
if (self.input.right) {
self.vx += 2.2;
self.facing = 1;
}
if (!self.input.left && !self.input.right) {
self.vx *= 0.8;
}
if (self.input.jump && !self.lastInput.jump) {
if (self.grounded || self.jumps > 0 || self.wallCling) {
self.vy = -32;
if (!self.grounded) self.jumps--;
if (self.wallCling) {
self.vx = -self.facing * 18;
self.wallCling = false;
}
}
}
if (self.input.dash && !self.lastInput.dash && self.dashCooldown === 0) {
self.dashTimer = 8;
self.dashDir = self.facing;
}
if (self.input.gravityCancel && !self.lastInput.gravityCancel) {
self.gravityCancelStart();
}
if (!self.input.gravityCancel && self.gravityCancel) {
self.gravityCancelEnd();
}
if (self.input.attackLight && !self.lastInput.attackLight) {
self.attack('light');
}
if (self.input.attackHeavy && !self.lastInput.attackHeavy) {
self.attack('heavy');
}
if (self.input.throwWeapon && !self.lastInput.throwWeapon) {
self.throwWeapon();
}
}
// Gravity
if (!self.grounded && !self.gravityCancel) {
self.vy += 2.2;
if (self.vy > 40) self.vy = 40;
}
if (self.gravityCancel) {
self.gravityCancelTimer--;
if (self.gravityCancelTimer <= 0) self.gravityCancelEnd();
}
// Wall climbing
if (!self.grounded && !self.gravityCancel) {
// Left wall
if (self.x < platform.x - platform.width / 2 + 100 && self.vy > 0) {
self.wallCling = true;
self.jumps = self.maxJumps;
self.vy *= 0.7;
}
// Right wall
if (self.x > platform.x + platform.width / 2 - 100 && self.vy > 0) {
self.wallCling = true;
self.jumps = self.maxJumps;
self.vy *= 0.7;
}
} else {
self.wallCling = false;
}
// Apply velocity
self.x += self.vx;
self.y += self.vy;
// Friction
if (self.grounded) self.vx *= 0.7;else self.vx *= 0.98;
// Clamp position
if (self.x < 100) self.x = 100;
if (self.x > 1948) self.x = 1948;
// Platform collision
if (self.y + 110 > platform.y - platform.height / 2 && self.y < platform.y + platform.height / 2) {
if (self.y < platform.y) {
self.y = platform.y - 110;
self.vy = 0;
self.grounded = true;
self.jumps = self.maxJumps;
}
} else {
self.grounded = false;
}
// Out of bounds
if (self.y > 3000) {
self.stocks--;
self.dead = true;
self.hp = 0;
self.x = -9999;
self.y = -9999;
if (self.stocks > 0) {
LK.setTimeout(function () {
self.respawn();
}, 1200);
}
}
// Update last input
for (var k in self.input) {
self.lastInput[k] = self.input[k];
}
};
return self;
});
// Weapon base class
var Weapon = Container.expand(function () {
var self = Container.call(this);
self.owner = null; // Player who owns this weapon, or null if on ground
self.isHeld = false;
self.isThrown = false;
self.throwVX = 0;
self.throwVY = 0;
self.type = 'none'; // 'sword' or 'bow'
self.damage = 0;
self.dir = 1; // 1 = right, -1 = left
self.cooldown = 0; // frames until can attack again
self.attackType = 'light'; // 'light' or 'heavy'
self.attackTimer = 0; // frames left in attack animation
self.attackDir = 1; // 1 = right, -1 = left
self.attackActive = false;
self.hitbox = null; // Rectangle for attack
self.arrow = null; // For bow, the arrow object
self.init = function () {};
self.update = function () {};
self.attack = function (type, dir) {};
self.throwWeapon = function (vx, vy) {};
self.pickup = function (player) {};
self.drop = function () {};
self.destroyWeapon = function () {
self.destroy();
};
return self;
});
// Sword class
var Sword = Weapon.expand(function () {
var self = Weapon.call(this);
var swordGfx = self.attachAsset('sword', {
anchorX: 0.1,
anchorY: 0.5
});
self.type = 'sword';
self.damage = 8;
self.cooldown = 0;
self.attackType = 'light';
self.attackTimer = 0;
self.attackActive = false;
self.dir = 1;
self.hitbox = null;
self.isThrown = false;
self.throwVX = 0;
self.throwVY = 0;
self.isHeld = false;
self.owner = null;
self.attack = function (type, dir) {
if (self.cooldown > 0) return;
self.attackType = type;
self.attackDir = dir;
self.attackTimer = type === 'light' ? 12 : 22;
self.attackActive = true;
self.cooldown = type === 'light' ? 18 : 32;
};
self.update = function () {
if (self.isHeld && self.owner) {
// Follow owner's hand
self.x = self.owner.x + self.owner.facing * 80;
self.y = self.owner.y + 40;
self.scaleX = self.owner.facing;
self.scaleY = 1;
self.rotation = self.attackActive ? self.attackDir * 0.5 * (self.attackType === 'light' ? 1 : 1.5) : 0;
}
if (self.attackActive) {
self.attackTimer--;
if (self.attackTimer <= 0) {
self.attackActive = false;
self.rotation = 0;
}
}
if (self.cooldown > 0) self.cooldown--;
// Thrown logic
if (self.isThrown) {
self.x += self.throwVX;
self.y += self.throwVY;
self.throwVY += 1.2; // gravity
self.rotation += 0.25 * self.throwVX / 10;
// Out of bounds
if (self.y > 3000 || self.x < -200 || self.x > 2248) {
self.destroyWeapon();
}
}
};
self.throwWeapon = function (vx, vy) {
self.isHeld = false;
self.owner = null;
self.isThrown = true;
self.throwVX = vx;
self.throwVY = vy;
};
self.pickup = function (player) {
self.isHeld = true;
self.owner = player;
self.isThrown = false;
};
self.drop = function () {
self.isHeld = false;
self.owner = null;
self.isThrown = false;
};
return self;
});
// Bow class
var Bow = Weapon.expand(function () {
var self = Weapon.call(this);
var bowGfx = self.attachAsset('bow', {
anchorX: 0.1,
anchorY: 0.5
});
self.type = 'bow';
self.damage = 6;
self.cooldown = 0;
self.attackType = 'light';
self.attackTimer = 0;
self.attackActive = false;
self.dir = 1;
self.arrow = null;
self.isThrown = false;
self.throwVX = 0;
self.throwVY = 0;
self.isHeld = false;
self.owner = null;
self.attack = function (type, dir) {
if (self.cooldown > 0) return;
self.attackType = type;
self.attackDir = dir;
self.attackTimer = type === 'light' ? 18 : 30;
self.attackActive = true;
self.cooldown = type === 'light' ? 28 : 44;
// Fire arrow at peak of draw
if (self.arrow === null) {
var arrow = new Arrow();
arrow.x = self.x + self.attackDir * 80;
arrow.y = self.y;
arrow.vx = self.attackDir * (type === 'light' ? 32 : 48);
arrow.vy = 0;
arrow.owner = self.owner;
self.arrow = arrow;
game.addChild(arrow);
arrows.push(arrow);
}
};
self.update = function () {
if (self.isHeld && self.owner) {
self.x = self.owner.x + self.owner.facing * 80;
self.y = self.owner.y + 40;
self.scaleX = self.owner.facing;
self.scaleY = 1;
self.rotation = self.attackActive ? self.attackDir * 0.2 : 0;
}
if (self.attackActive) {
self.attackTimer--;
if (self.attackTimer <= 0) {
self.attackActive = false;
self.rotation = 0;
self.arrow = null;
}
}
if (self.cooldown > 0) self.cooldown--;
// Thrown logic
if (self.isThrown) {
self.x += self.throwVX;
self.y += self.throwVY;
self.throwVY += 1.2; // gravity
self.rotation += 0.25 * self.throwVX / 10;
// Out of bounds
if (self.y > 3000 || self.x < -200 || self.x > 2248) {
self.destroyWeapon();
}
}
};
self.throwWeapon = function (vx, vy) {
self.isHeld = false;
self.owner = null;
self.isThrown = true;
self.throwVX = vx;
self.throwVY = vy;
};
self.pickup = function (player) {
self.isHeld = true;
self.owner = player;
self.isThrown = false;
};
self.drop = function () {
self.isHeld = false;
self.owner = null;
self.isThrown = false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
// background image asset
// Add background image behind all gameplay elements
var bg = LK.getAsset('bg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChild(bg);
// Hit effect
// Platform
// Weapons
// Characters
// Platform
var platform = LK.getAsset('platform', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2000
});
game.addChild(platform);
// Players
var player1 = new Player();
player1.setCharType(1);
player1.name = "Player 1";
// Calculate platform bounds for safe spawn
var platformLeft = platform.x - platform.width / 2 + 100;
var platformRight = platform.x + platform.width / 2 - 100;
var platformY = platform.y - 110;
// Place player1 on the left side of the platform, clamped to platform bounds
player1.spawnX = Math.max(platformLeft, platform.x - platform.width / 4);
player1.spawnY = platformY;
player1.x = player1.spawnX;
player1.y = player1.spawnY;
player1.facing = 1;
player1.isAI = false;
player1.stocks = 3;
game.addChild(player1);
var player2 = new Player();
player2.setCharType(2);
player2.name = "Player 2";
// Place player2 on the right side of the platform, clamped to platform bounds
player2.spawnX = Math.min(platformRight, platform.x + platform.width / 4);
player2.spawnY = platformY;
player2.x = player2.spawnX;
player2.y = player2.spawnY;
player2.facing = -1;
player2.isAI = true; // Set to false for local 2P
player2.stocks = 3;
game.addChild(player2);
// Weapons
var weapons = [];
var arrows = [];
// Spawn initial weapons
function spawnWeapon(type, x, y) {
var w;
if (type === 'sword') w = new Sword();else w = new Bow();
w.x = x;
w.y = y;
weapons.push(w);
game.addChild(w);
return w;
}
var sword1 = spawnWeapon('sword', 2048 / 2 - 200, platform.y - 100);
var bow1 = spawnWeapon('bow', 2048 / 2 + 200, platform.y - 100);
// GUI
var p1StockTxt = new Text2('P1: 3', {
size: 80,
fill: 0x3A7BD5
});
p1StockTxt.anchor.set(0, 0);
LK.gui.top.addChild(p1StockTxt);
p1StockTxt.x = 120;
p1StockTxt.y = 20;
var p2StockTxt = new Text2('P2: 3', {
size: 80,
fill: 0xD53A3A
});
p2StockTxt.anchor.set(1, 0);
LK.gui.top.addChild(p2StockTxt);
p2StockTxt.x = LK.gui.top.width - 120;
p2StockTxt.y = 20;
// Touch controls (mobile)
var touchState = {
left: false,
right: false,
up: false,
down: false,
jump: false,
attackLight: false,
attackHeavy: false,
dash: false,
throwWeapon: false,
gravityCancel: false
};
var dragNode = null;
// Touch area for movement (left half)
var moveArea = {
x: 0,
y: 300,
width: 1024,
height: 2432
};
// Touch area for actions (right half)
var actionArea = {
x: 1024,
y: 300,
width: 1024,
height: 2432
};
// Helper: get touch area
function getTouchArea(x, y) {
if (x < moveArea.x + moveArea.width && y > moveArea.y) return 'move';
if (x > actionArea.x && y > actionArea.y) return 'action';
return null;
}
// Touch input
game.down = function (x, y, obj) {
if (x < 100 && y < 100) return; // Don't allow in top left
var area = getTouchArea(x, y);
if (area === 'move') {
dragNode = 'move';
if (y < platform.y) {
touchState.jump = true;
} else if (x < moveArea.x + moveArea.width / 2) {
touchState.left = true;
} else {
touchState.right = true;
}
} else if (area === 'action') {
dragNode = 'action';
if (y < platform.y - 200) {
touchState.attackHeavy = true;
} else if (y < platform.y) {
touchState.attackLight = true;
} else if (x > 2048 - 200) {
touchState.throwWeapon = true;
} else if (x > 2048 - 400) {
touchState.dash = true;
} else {
touchState.gravityCancel = true;
}
}
updatePlayerInput();
};
game.move = function (x, y, obj) {
if (!dragNode) return;
var area = getTouchArea(x, y);
// Reset all
for (var k in touchState) touchState[k] = false;
if (area === 'move') {
if (y < platform.y) {
touchState.jump = true;
} else if (x < moveArea.x + moveArea.width / 2) {
touchState.left = true;
} else {
touchState.right = true;
}
} else if (area === 'action') {
if (y < platform.y - 200) {
touchState.attackHeavy = true;
} else if (y < platform.y) {
touchState.attackLight = true;
} else if (x > 2048 - 200) {
touchState.throwWeapon = true;
} else if (x > 2048 - 400) {
touchState.dash = true;
} else {
touchState.gravityCancel = true;
}
}
updatePlayerInput();
};
game.up = function (x, y, obj) {
dragNode = null;
for (var k in touchState) touchState[k] = false;
updatePlayerInput();
};
// Update player1 input from touchState
function updatePlayerInput() {
for (var k in player1.input) {
player1.input[k] = !!touchState[k];
}
}
// AI logic for player2
function aiUpdate() {
if (!player2.isAI || player2.dead) return;
// Simple AI: move toward player1, jump if close, attack if in range
var dx = player1.x - player2.x;
var dy = player1.y - player2.y;
for (var k in player2.input) player2.input[k] = false;
if (Math.abs(dx) > 80) {
player2.input.left = dx < 0;
player2.input.right = dx > 0;
}
if (Math.abs(dx) < 220 && Math.abs(dy) < 120 && player2.weapon && player2.weapon.cooldown === 0) {
player2.input.attackLight = true;
}
if (Math.abs(dx) < 120 && Math.abs(dy) > 80 && player2.jumps > 0) {
player2.input.jump = true;
}
if (!player2.weapon) {
// Try to pick up weapon
for (var i = 0; i < weapons.length; ++i) {
var w = weapons[i];
if (!w.isHeld && !w.isThrown && Math.abs(w.x - player2.x) < 100 && Math.abs(w.y - player2.y) < 120) {
player2.pickupWeapon(w);
break;
}
}
}
}
// Main update
game.update = function () {
// Update players
player1.update();
player2.update();
aiUpdate();
// Update weapons
for (var i = weapons.length - 1; i >= 0; --i) {
var w = weapons[i];
w.update();
// Pickup logic
if (!w.isHeld && !w.isThrown) {
// Player1
if (!player1.weapon && !player1.dead && Math.abs(w.x - player1.x) < 100 && Math.abs(w.y - player1.y) < 120) {
player1.pickupWeapon(w);
}
// Player2
if (!player2.weapon && !player2.dead && Math.abs(w.x - player2.x) < 100 && Math.abs(w.y - player2.y) < 120) {
player2.pickupWeapon(w);
}
}
// Remove destroyed
if (w.destroyed) {
weapons.splice(i, 1);
}
}
// Update arrows
for (var i = arrows.length - 1; i >= 0; --i) {
var a = arrows[i];
a.update();
// Hit player1
if (!player1.dead && a.owner !== player1 && Math.abs(a.x - player1.x) < 100 && Math.abs(a.y - player1.y) < 80) {
player1.takeDamage(12, a.vx * 0.7, -8);
a.destroy();
arrows.splice(i, 1);
continue;
}
// Hit player2
if (!player2.dead && a.owner !== player2 && Math.abs(a.x - player2.x) < 100 && Math.abs(a.y - player2.y) < 80) {
player2.takeDamage(12, a.vx * 0.7, -8);
a.destroy();
arrows.splice(i, 1);
continue;
}
// Remove destroyed
if (a.destroyed) {
arrows.splice(i, 1);
}
}
// Sword attack hit detection
for (var i = 0; i < weapons.length; ++i) {
var w = weapons[i];
if (w.type === 'sword' && w.attackActive && w.owner) {
var target = w.owner === player1 ? player2 : player1;
if (!target.dead && Math.abs(w.x + w.attackDir * 80 - target.x) < 120 && Math.abs(w.y - target.y) < 120) {
target.takeDamage(w.attackType === 'light' ? 10 : 18, w.attackDir * (w.attackType === 'light' ? 18 : 28), -12);
w.attackActive = false;
}
}
}
// Bow melee (if needed)
for (var i = 0; i < weapons.length; ++i) {
var w = weapons[i];
if (w.type === 'bow' && w.attackActive && w.owner) {
var target = w.owner === player1 ? player2 : player1;
if (!target.dead && Math.abs(w.x + w.attackDir * 80 - target.x) < 120 && Math.abs(w.y - target.y) < 120) {
target.takeDamage(w.attackType === 'light' ? 8 : 14, w.attackDir * (w.attackType === 'light' ? 12 : 22), -8);
w.attackActive = false;
}
}
}
// Thrown weapon hit detection
for (var i = 0; i < weapons.length; ++i) {
var w = weapons[i];
if (w.isThrown) {
// Hit player1
if (!player1.dead && Math.abs(w.x - player1.x) < 100 && Math.abs(w.y - player1.y) < 100 && w.owner !== player1) {
player1.takeDamage(16, w.throwVX * 0.7, -12);
w.isThrown = false;
}
// Hit player2
if (!player2.dead && Math.abs(w.x - player2.x) < 100 && Math.abs(w.y - player2.y) < 100 && w.owner !== player2) {
player2.takeDamage(16, w.throwVX * 0.7, -12);
w.isThrown = false;
}
}
}
// Update GUI
p1StockTxt.setText('P1: ' + player1.stocks);
p2StockTxt.setText('P2: ' + player2.stocks);
// Win/Lose
if (player1.stocks <= 0 && !player1.dead) {
LK.showGameOver();
}
if (player2.stocks <= 0 && !player2.dead) {
LK.showYouWin();
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Arrow class (projectile)
var Arrow = Container.expand(function () {
var self = Container.call(this);
var arrowGfx = self.attachAsset('arrow', {
anchorX: 0.1,
anchorY: 0.5
});
self.vx = 0;
self.vy = 0;
self.owner = null;
self.lifetime = 60;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
self.lifetime--;
if (self.lifetime <= 0 || self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 3000) {
self.destroy();
}
};
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
self.isAI = false;
self.hp = 100;
self.maxHp = 100;
self.stocks = 3;
self.facing = 1; // 1 = right, -1 = left
self.vx = 0;
self.vy = 0;
self.grounded = false;
self.jumps = 3;
self.maxJumps = 3;
self.wallCling = false;
self.gravityCancel = false;
self.gravityCancelTimer = 0;
self.dashTimer = 0;
self.dashDir = 0;
self.dashCooldown = 0;
self.attackCooldown = 0;
self.hitstun = 0;
self.invuln = 0;
self.weapon = null;
self.input = {
left: false,
right: false,
up: false,
down: false,
jump: false,
attackLight: false,
attackHeavy: false,
dash: false,
throwWeapon: false,
gravityCancel: false
};
self.lastInput = {};
self.charType = 1; // 1 or 2
self.name = '';
self.spawnX = 0;
self.spawnY = 0;
self.dead = false;
// Graphics
var charGfx = self.attachAsset(self.charType === 1 ? 'char1' : 'char2', {
anchorX: 0.5,
anchorY: 0.5
});
self.hpBar = new Text2('100', {
size: 60,
fill: "#fff"
});
self.hpBar.anchor.set(0.5, 0.5);
self.addChild(self.hpBar);
self.setCharType = function (type) {
self.charType = type;
charGfx.destroy();
var newGfx = self.attachAsset(type === 1 ? 'char1' : 'char2', {
anchorX: 0.5,
anchorY: 0.5
});
};
self.respawn = function () {
self.x = self.spawnX;
self.y = self.spawnY;
self.vx = 0;
self.vy = 0;
self.hp = self.maxHp;
self.jumps = self.maxJumps;
self.grounded = false;
self.wallCling = false;
self.gravityCancel = false;
self.gravityCancelTimer = 0;
self.dashTimer = 0;
self.dashDir = 0;
self.dashCooldown = 0;
self.attackCooldown = 0;
self.hitstun = 0;
self.invuln = 60;
self.dead = false;
};
self.takeDamage = function (amount, knockbackX, knockbackY) {
if (self.invuln > 0) return;
self.hp -= amount;
self.vx += knockbackX;
self.vy += knockbackY;
self.hitstun = 18 + Math.floor(amount * 0.7);
self.invuln = 18;
// Flash effect
LK.effects.flashObject(self, 0xffe066, 200);
// Show hit effect
var fx = LK.getAsset('hitfx', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y
});
game.addChild(fx);
tween(fx, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
fx.destroy();
}
});
if (self.hp <= 0) {
self.stocks--;
self.dead = true;
self.hp = 0;
self.vx = 0;
self.vy = 0;
self.x = -9999;
self.y = -9999;
if (self.stocks > 0) {
LK.setTimeout(function () {
self.respawn();
}, 1200);
}
}
};
self.pickupWeapon = function (weapon) {
if (self.weapon) return;
weapon.pickup(self);
self.weapon = weapon;
};
self.dropWeapon = function () {
if (!self.weapon) return;
self.weapon.drop();
self.weapon = null;
};
self.throwWeapon = function () {
if (!self.weapon) return;
var throwVX = self.facing * 24 + self.vx * 0.5;
var throwVY = -8 + self.vy * 0.2;
self.weapon.throwWeapon(throwVX, throwVY);
self.weapon = null;
};
self.attack = function (type) {
if (!self.weapon) return;
if (self.weapon.cooldown > 0) return;
self.weapon.attack(type, self.facing);
};
self.gravityCancelStart = function () {
if (self.gravityCancel || self.grounded) return;
self.gravityCancel = true;
self.gravityCancelTimer = 24;
self.vx *= 0.7;
self.vy *= 0.7;
};
self.gravityCancelEnd = function () {
self.gravityCancel = false;
self.gravityCancelTimer = 0;
};
self.update = function () {
// Update HP bar
self.hpBar.setText(self.hp + '');
self.hpBar.x = 0;
self.hpBar.y = -140;
if (self.dead) return;
// Invuln
if (self.invuln > 0) self.invuln--;
// Hitstun
if (self.hitstun > 0) {
self.hitstun--;
self.x += self.vx;
self.y += self.vy;
self.vx *= 0.85;
self.vy *= 0.85;
return;
}
// Dashing
if (self.dashTimer > 0) {
self.dashTimer--;
self.x += self.dashDir * 32;
if (self.dashTimer === 0) {
self.dashCooldown = 24;
}
} else {
// Movement
if (self.input.left) {
self.vx -= 2.2;
self.facing = -1;
}
if (self.input.right) {
self.vx += 2.2;
self.facing = 1;
}
if (!self.input.left && !self.input.right) {
self.vx *= 0.8;
}
if (self.input.jump && !self.lastInput.jump) {
if (self.grounded || self.jumps > 0 || self.wallCling) {
self.vy = -32;
if (!self.grounded) self.jumps--;
if (self.wallCling) {
self.vx = -self.facing * 18;
self.wallCling = false;
}
}
}
if (self.input.dash && !self.lastInput.dash && self.dashCooldown === 0) {
self.dashTimer = 8;
self.dashDir = self.facing;
}
if (self.input.gravityCancel && !self.lastInput.gravityCancel) {
self.gravityCancelStart();
}
if (!self.input.gravityCancel && self.gravityCancel) {
self.gravityCancelEnd();
}
if (self.input.attackLight && !self.lastInput.attackLight) {
self.attack('light');
}
if (self.input.attackHeavy && !self.lastInput.attackHeavy) {
self.attack('heavy');
}
if (self.input.throwWeapon && !self.lastInput.throwWeapon) {
self.throwWeapon();
}
}
// Gravity
if (!self.grounded && !self.gravityCancel) {
self.vy += 2.2;
if (self.vy > 40) self.vy = 40;
}
if (self.gravityCancel) {
self.gravityCancelTimer--;
if (self.gravityCancelTimer <= 0) self.gravityCancelEnd();
}
// Wall climbing
if (!self.grounded && !self.gravityCancel) {
// Left wall
if (self.x < platform.x - platform.width / 2 + 100 && self.vy > 0) {
self.wallCling = true;
self.jumps = self.maxJumps;
self.vy *= 0.7;
}
// Right wall
if (self.x > platform.x + platform.width / 2 - 100 && self.vy > 0) {
self.wallCling = true;
self.jumps = self.maxJumps;
self.vy *= 0.7;
}
} else {
self.wallCling = false;
}
// Apply velocity
self.x += self.vx;
self.y += self.vy;
// Friction
if (self.grounded) self.vx *= 0.7;else self.vx *= 0.98;
// Clamp position
if (self.x < 100) self.x = 100;
if (self.x > 1948) self.x = 1948;
// Platform collision
if (self.y + 110 > platform.y - platform.height / 2 && self.y < platform.y + platform.height / 2) {
if (self.y < platform.y) {
self.y = platform.y - 110;
self.vy = 0;
self.grounded = true;
self.jumps = self.maxJumps;
}
} else {
self.grounded = false;
}
// Out of bounds
if (self.y > 3000) {
self.stocks--;
self.dead = true;
self.hp = 0;
self.x = -9999;
self.y = -9999;
if (self.stocks > 0) {
LK.setTimeout(function () {
self.respawn();
}, 1200);
}
}
// Update last input
for (var k in self.input) {
self.lastInput[k] = self.input[k];
}
};
return self;
});
// Weapon base class
var Weapon = Container.expand(function () {
var self = Container.call(this);
self.owner = null; // Player who owns this weapon, or null if on ground
self.isHeld = false;
self.isThrown = false;
self.throwVX = 0;
self.throwVY = 0;
self.type = 'none'; // 'sword' or 'bow'
self.damage = 0;
self.dir = 1; // 1 = right, -1 = left
self.cooldown = 0; // frames until can attack again
self.attackType = 'light'; // 'light' or 'heavy'
self.attackTimer = 0; // frames left in attack animation
self.attackDir = 1; // 1 = right, -1 = left
self.attackActive = false;
self.hitbox = null; // Rectangle for attack
self.arrow = null; // For bow, the arrow object
self.init = function () {};
self.update = function () {};
self.attack = function (type, dir) {};
self.throwWeapon = function (vx, vy) {};
self.pickup = function (player) {};
self.drop = function () {};
self.destroyWeapon = function () {
self.destroy();
};
return self;
});
// Sword class
var Sword = Weapon.expand(function () {
var self = Weapon.call(this);
var swordGfx = self.attachAsset('sword', {
anchorX: 0.1,
anchorY: 0.5
});
self.type = 'sword';
self.damage = 8;
self.cooldown = 0;
self.attackType = 'light';
self.attackTimer = 0;
self.attackActive = false;
self.dir = 1;
self.hitbox = null;
self.isThrown = false;
self.throwVX = 0;
self.throwVY = 0;
self.isHeld = false;
self.owner = null;
self.attack = function (type, dir) {
if (self.cooldown > 0) return;
self.attackType = type;
self.attackDir = dir;
self.attackTimer = type === 'light' ? 12 : 22;
self.attackActive = true;
self.cooldown = type === 'light' ? 18 : 32;
};
self.update = function () {
if (self.isHeld && self.owner) {
// Follow owner's hand
self.x = self.owner.x + self.owner.facing * 80;
self.y = self.owner.y + 40;
self.scaleX = self.owner.facing;
self.scaleY = 1;
self.rotation = self.attackActive ? self.attackDir * 0.5 * (self.attackType === 'light' ? 1 : 1.5) : 0;
}
if (self.attackActive) {
self.attackTimer--;
if (self.attackTimer <= 0) {
self.attackActive = false;
self.rotation = 0;
}
}
if (self.cooldown > 0) self.cooldown--;
// Thrown logic
if (self.isThrown) {
self.x += self.throwVX;
self.y += self.throwVY;
self.throwVY += 1.2; // gravity
self.rotation += 0.25 * self.throwVX / 10;
// Out of bounds
if (self.y > 3000 || self.x < -200 || self.x > 2248) {
self.destroyWeapon();
}
}
};
self.throwWeapon = function (vx, vy) {
self.isHeld = false;
self.owner = null;
self.isThrown = true;
self.throwVX = vx;
self.throwVY = vy;
};
self.pickup = function (player) {
self.isHeld = true;
self.owner = player;
self.isThrown = false;
};
self.drop = function () {
self.isHeld = false;
self.owner = null;
self.isThrown = false;
};
return self;
});
// Bow class
var Bow = Weapon.expand(function () {
var self = Weapon.call(this);
var bowGfx = self.attachAsset('bow', {
anchorX: 0.1,
anchorY: 0.5
});
self.type = 'bow';
self.damage = 6;
self.cooldown = 0;
self.attackType = 'light';
self.attackTimer = 0;
self.attackActive = false;
self.dir = 1;
self.arrow = null;
self.isThrown = false;
self.throwVX = 0;
self.throwVY = 0;
self.isHeld = false;
self.owner = null;
self.attack = function (type, dir) {
if (self.cooldown > 0) return;
self.attackType = type;
self.attackDir = dir;
self.attackTimer = type === 'light' ? 18 : 30;
self.attackActive = true;
self.cooldown = type === 'light' ? 28 : 44;
// Fire arrow at peak of draw
if (self.arrow === null) {
var arrow = new Arrow();
arrow.x = self.x + self.attackDir * 80;
arrow.y = self.y;
arrow.vx = self.attackDir * (type === 'light' ? 32 : 48);
arrow.vy = 0;
arrow.owner = self.owner;
self.arrow = arrow;
game.addChild(arrow);
arrows.push(arrow);
}
};
self.update = function () {
if (self.isHeld && self.owner) {
self.x = self.owner.x + self.owner.facing * 80;
self.y = self.owner.y + 40;
self.scaleX = self.owner.facing;
self.scaleY = 1;
self.rotation = self.attackActive ? self.attackDir * 0.2 : 0;
}
if (self.attackActive) {
self.attackTimer--;
if (self.attackTimer <= 0) {
self.attackActive = false;
self.rotation = 0;
self.arrow = null;
}
}
if (self.cooldown > 0) self.cooldown--;
// Thrown logic
if (self.isThrown) {
self.x += self.throwVX;
self.y += self.throwVY;
self.throwVY += 1.2; // gravity
self.rotation += 0.25 * self.throwVX / 10;
// Out of bounds
if (self.y > 3000 || self.x < -200 || self.x > 2248) {
self.destroyWeapon();
}
}
};
self.throwWeapon = function (vx, vy) {
self.isHeld = false;
self.owner = null;
self.isThrown = true;
self.throwVX = vx;
self.throwVY = vy;
};
self.pickup = function (player) {
self.isHeld = true;
self.owner = player;
self.isThrown = false;
};
self.drop = function () {
self.isHeld = false;
self.owner = null;
self.isThrown = false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
// background image asset
// Add background image behind all gameplay elements
var bg = LK.getAsset('bg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChild(bg);
// Hit effect
// Platform
// Weapons
// Characters
// Platform
var platform = LK.getAsset('platform', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2000
});
game.addChild(platform);
// Players
var player1 = new Player();
player1.setCharType(1);
player1.name = "Player 1";
// Calculate platform bounds for safe spawn
var platformLeft = platform.x - platform.width / 2 + 100;
var platformRight = platform.x + platform.width / 2 - 100;
var platformY = platform.y - 110;
// Place player1 on the left side of the platform, clamped to platform bounds
player1.spawnX = Math.max(platformLeft, platform.x - platform.width / 4);
player1.spawnY = platformY;
player1.x = player1.spawnX;
player1.y = player1.spawnY;
player1.facing = 1;
player1.isAI = false;
player1.stocks = 3;
game.addChild(player1);
var player2 = new Player();
player2.setCharType(2);
player2.name = "Player 2";
// Place player2 on the right side of the platform, clamped to platform bounds
player2.spawnX = Math.min(platformRight, platform.x + platform.width / 4);
player2.spawnY = platformY;
player2.x = player2.spawnX;
player2.y = player2.spawnY;
player2.facing = -1;
player2.isAI = true; // Set to false for local 2P
player2.stocks = 3;
game.addChild(player2);
// Weapons
var weapons = [];
var arrows = [];
// Spawn initial weapons
function spawnWeapon(type, x, y) {
var w;
if (type === 'sword') w = new Sword();else w = new Bow();
w.x = x;
w.y = y;
weapons.push(w);
game.addChild(w);
return w;
}
var sword1 = spawnWeapon('sword', 2048 / 2 - 200, platform.y - 100);
var bow1 = spawnWeapon('bow', 2048 / 2 + 200, platform.y - 100);
// GUI
var p1StockTxt = new Text2('P1: 3', {
size: 80,
fill: 0x3A7BD5
});
p1StockTxt.anchor.set(0, 0);
LK.gui.top.addChild(p1StockTxt);
p1StockTxt.x = 120;
p1StockTxt.y = 20;
var p2StockTxt = new Text2('P2: 3', {
size: 80,
fill: 0xD53A3A
});
p2StockTxt.anchor.set(1, 0);
LK.gui.top.addChild(p2StockTxt);
p2StockTxt.x = LK.gui.top.width - 120;
p2StockTxt.y = 20;
// Touch controls (mobile)
var touchState = {
left: false,
right: false,
up: false,
down: false,
jump: false,
attackLight: false,
attackHeavy: false,
dash: false,
throwWeapon: false,
gravityCancel: false
};
var dragNode = null;
// Touch area for movement (left half)
var moveArea = {
x: 0,
y: 300,
width: 1024,
height: 2432
};
// Touch area for actions (right half)
var actionArea = {
x: 1024,
y: 300,
width: 1024,
height: 2432
};
// Helper: get touch area
function getTouchArea(x, y) {
if (x < moveArea.x + moveArea.width && y > moveArea.y) return 'move';
if (x > actionArea.x && y > actionArea.y) return 'action';
return null;
}
// Touch input
game.down = function (x, y, obj) {
if (x < 100 && y < 100) return; // Don't allow in top left
var area = getTouchArea(x, y);
if (area === 'move') {
dragNode = 'move';
if (y < platform.y) {
touchState.jump = true;
} else if (x < moveArea.x + moveArea.width / 2) {
touchState.left = true;
} else {
touchState.right = true;
}
} else if (area === 'action') {
dragNode = 'action';
if (y < platform.y - 200) {
touchState.attackHeavy = true;
} else if (y < platform.y) {
touchState.attackLight = true;
} else if (x > 2048 - 200) {
touchState.throwWeapon = true;
} else if (x > 2048 - 400) {
touchState.dash = true;
} else {
touchState.gravityCancel = true;
}
}
updatePlayerInput();
};
game.move = function (x, y, obj) {
if (!dragNode) return;
var area = getTouchArea(x, y);
// Reset all
for (var k in touchState) touchState[k] = false;
if (area === 'move') {
if (y < platform.y) {
touchState.jump = true;
} else if (x < moveArea.x + moveArea.width / 2) {
touchState.left = true;
} else {
touchState.right = true;
}
} else if (area === 'action') {
if (y < platform.y - 200) {
touchState.attackHeavy = true;
} else if (y < platform.y) {
touchState.attackLight = true;
} else if (x > 2048 - 200) {
touchState.throwWeapon = true;
} else if (x > 2048 - 400) {
touchState.dash = true;
} else {
touchState.gravityCancel = true;
}
}
updatePlayerInput();
};
game.up = function (x, y, obj) {
dragNode = null;
for (var k in touchState) touchState[k] = false;
updatePlayerInput();
};
// Update player1 input from touchState
function updatePlayerInput() {
for (var k in player1.input) {
player1.input[k] = !!touchState[k];
}
}
// AI logic for player2
function aiUpdate() {
if (!player2.isAI || player2.dead) return;
// Simple AI: move toward player1, jump if close, attack if in range
var dx = player1.x - player2.x;
var dy = player1.y - player2.y;
for (var k in player2.input) player2.input[k] = false;
if (Math.abs(dx) > 80) {
player2.input.left = dx < 0;
player2.input.right = dx > 0;
}
if (Math.abs(dx) < 220 && Math.abs(dy) < 120 && player2.weapon && player2.weapon.cooldown === 0) {
player2.input.attackLight = true;
}
if (Math.abs(dx) < 120 && Math.abs(dy) > 80 && player2.jumps > 0) {
player2.input.jump = true;
}
if (!player2.weapon) {
// Try to pick up weapon
for (var i = 0; i < weapons.length; ++i) {
var w = weapons[i];
if (!w.isHeld && !w.isThrown && Math.abs(w.x - player2.x) < 100 && Math.abs(w.y - player2.y) < 120) {
player2.pickupWeapon(w);
break;
}
}
}
}
// Main update
game.update = function () {
// Update players
player1.update();
player2.update();
aiUpdate();
// Update weapons
for (var i = weapons.length - 1; i >= 0; --i) {
var w = weapons[i];
w.update();
// Pickup logic
if (!w.isHeld && !w.isThrown) {
// Player1
if (!player1.weapon && !player1.dead && Math.abs(w.x - player1.x) < 100 && Math.abs(w.y - player1.y) < 120) {
player1.pickupWeapon(w);
}
// Player2
if (!player2.weapon && !player2.dead && Math.abs(w.x - player2.x) < 100 && Math.abs(w.y - player2.y) < 120) {
player2.pickupWeapon(w);
}
}
// Remove destroyed
if (w.destroyed) {
weapons.splice(i, 1);
}
}
// Update arrows
for (var i = arrows.length - 1; i >= 0; --i) {
var a = arrows[i];
a.update();
// Hit player1
if (!player1.dead && a.owner !== player1 && Math.abs(a.x - player1.x) < 100 && Math.abs(a.y - player1.y) < 80) {
player1.takeDamage(12, a.vx * 0.7, -8);
a.destroy();
arrows.splice(i, 1);
continue;
}
// Hit player2
if (!player2.dead && a.owner !== player2 && Math.abs(a.x - player2.x) < 100 && Math.abs(a.y - player2.y) < 80) {
player2.takeDamage(12, a.vx * 0.7, -8);
a.destroy();
arrows.splice(i, 1);
continue;
}
// Remove destroyed
if (a.destroyed) {
arrows.splice(i, 1);
}
}
// Sword attack hit detection
for (var i = 0; i < weapons.length; ++i) {
var w = weapons[i];
if (w.type === 'sword' && w.attackActive && w.owner) {
var target = w.owner === player1 ? player2 : player1;
if (!target.dead && Math.abs(w.x + w.attackDir * 80 - target.x) < 120 && Math.abs(w.y - target.y) < 120) {
target.takeDamage(w.attackType === 'light' ? 10 : 18, w.attackDir * (w.attackType === 'light' ? 18 : 28), -12);
w.attackActive = false;
}
}
}
// Bow melee (if needed)
for (var i = 0; i < weapons.length; ++i) {
var w = weapons[i];
if (w.type === 'bow' && w.attackActive && w.owner) {
var target = w.owner === player1 ? player2 : player1;
if (!target.dead && Math.abs(w.x + w.attackDir * 80 - target.x) < 120 && Math.abs(w.y - target.y) < 120) {
target.takeDamage(w.attackType === 'light' ? 8 : 14, w.attackDir * (w.attackType === 'light' ? 12 : 22), -8);
w.attackActive = false;
}
}
}
// Thrown weapon hit detection
for (var i = 0; i < weapons.length; ++i) {
var w = weapons[i];
if (w.isThrown) {
// Hit player1
if (!player1.dead && Math.abs(w.x - player1.x) < 100 && Math.abs(w.y - player1.y) < 100 && w.owner !== player1) {
player1.takeDamage(16, w.throwVX * 0.7, -12);
w.isThrown = false;
}
// Hit player2
if (!player2.dead && Math.abs(w.x - player2.x) < 100 && Math.abs(w.y - player2.y) < 100 && w.owner !== player2) {
player2.takeDamage(16, w.throwVX * 0.7, -12);
w.isThrown = false;
}
}
}
// Update GUI
p1StockTxt.setText('P1: ' + player1.stocks);
p2StockTxt.setText('P2: ' + player2.stocks);
// Win/Lose
if (player1.stocks <= 0 && !player1.dead) {
LK.showGameOver();
}
if (player2.stocks <= 0 && !player2.dead) {
LK.showYouWin();
}
};