User prompt
they has to be on platform only not outstage
User prompt
Put characters on the platform. Seperate them
User prompt
add background assets
Code edit (1 edits merged)
Please save this source code
User prompt
Gravity Duel: Sword & Bow
Initial prompt
Game Overview: Include 2 playable characters for now. Add a Custom Game Mode where players can play against each other or against a computer-controlled bot. Add a Computer AI opponent (medium difficulty). Map: Use a simple horizontal rectangular platform as the map. No holes or pits — the map should be a flat surface (no platforms or gaps). Movement & Controls: W: Jump (allow up to 3 jumps in midair) A / D: Move left / right S: Crouch (optional) J: Light attack (can be directional) K: Heavy attack (can be used on ground or in midair) L: Dodge / Dash H: Throw weapon Wall Climbing: Characters should be able to climb walls by repeatedly jumping while next to a wall. Weapons: Include 2 weapons: Sword and Bow Place weapon pickup icons on the map. When a player touches an icon, they pick up that weapon and the icon disappears. Players can only have one weapon at a time. Weapons can be thrown using the H key and will affect the enemy on hit. Attacks: Light Attacks (J): Should vary by direction: W + J → Up attack A + J → Left attack D + J → Right attack S + J → Downward attack Heavy Attacks (K): Can be used both on ground and midair. In midair, heavy attacks act as recovery attacks (knock enemies upward on hit). Gravity Cancel System: If player presses L while in air, a dash occurs and enables a Gravity Cancel. After performing a Gravity Cancel (L), pressing J or K allows performing light or heavy attacks while airborne as if grounded. Physics: Medium gravity Wall jumps for vertical mobility No edge clinging Flat terrain only (no ledges or falling) Start with just 2 characters, a single flat map, and the two weapons (sword and bow). More features can be added later.
/**** * 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 ****/ // 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"; player1.spawnX = 2048 / 2 - 300; player1.spawnY = 1700; 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"; player2.spawnX = 2048 / 2 + 300; player2.spawnY = 1700; 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(); } };
===================================================================
--- original.js
+++ change.js
@@ -1,6 +1,812 @@
-/****
+/****
+* 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: 0x000000
-});
\ No newline at end of file
+ backgroundColor: 0x1a1a1a
+});
+
+/****
+* Game Code
+****/
+// 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";
+player1.spawnX = 2048 / 2 - 300;
+player1.spawnY = 1700;
+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";
+player2.spawnX = 2048 / 2 + 300;
+player2.spawnY = 1700;
+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();
+ }
+};
\ No newline at end of file