/**** * 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();
}
};