User prompt
do not play the mov sound when players are moving. instead, play it when if any character jumps and he falls to the ground
User prompt
make the movement sound plays as the character moves
User prompt
only 1 moving sound can play even if both of the characters are moving
User prompt
make the movement sound plays as the character moves
User prompt
Do not overlap movement sounds
User prompt
play the mov sound when any character is moving
User prompt
play the dodge sound when any dodge happens
User prompt
play the throw sound when projectile is spawned not when it hit
User prompt
play the throw sound when any throwable happens
User prompt
play the spatt sound when any special attack happens
User prompt
the volume of the regatt is randomized every attack
User prompt
play the regatt sound when any regular attack happens
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'healthBar')' in or related to this line: 'fighter1.healthBar.visible = visible;' Line Number: 462
User prompt
The main menu is not visible. Can you fix it?
User prompt
make main menu visible
User prompt
Make a separate background for the main menu
User prompt
make a main menu for the game
User prompt
make the arrow closer to the player
User prompt
make the projectile leaves a trail effect
User prompt
make the arrow smaller
User prompt
put an arrow over the controlled player
User prompt
make the clothes of the enemy ai red
User prompt
Use the new 'fighter1' asset for the controlled fighter (fighter1), keep 'fighter' for others
User prompt
make the controlled fighter a different asset
User prompt
make the enemy AI can also dodge and use throwables
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // --- Arena class --- // Provides a background and groundY property for fighter placement var Arena = Container.expand(function () { var self = Container.call(this); // Add arena background image, anchored at top-left var bg = self.attachAsset('arena_bg', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); // Set groundY to a reasonable value for fighter placement // Place ground near the bottom of the visible area, but above the very bottom self.groundY = 2048 + 300; // 2048 is the base height, +300 for a bit above the bottom return self; }); // --- Attack class --- // Represents a regular or special attack hitbox, with owner and damage var Attack = Container.expand(function () { var self = Container.call(this); // Arguments: owner (Fighter), isSpecial (bool) self.owner = arguments[0] || null; self.isSpecial = !!arguments[1]; self.hit = false; self.destroyed = false; // Damage values self.damage = self.isSpecial ? 24 : 8; // Visual var assetId = self.isSpecial ? 'special_attack' : 'attack'; var sprite = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); // Set size for hitbox (use asset size) self.width = sprite.width; self.height = sprite.height; // Set owner after creation if needed self.setOwner = function (fighter) { self.owner = fighter; }; // Update: attacks last for a short time, then destroy self.lifetime = self.isSpecial ? 30 : 18; // special lasts longer self.update = function () { if (self.destroyed) return; self.lifetime--; if (self.lifetime <= 0) { self.destroy(); } }; self.destroy = function () { if (self.destroyed) return; self.destroyed = true; if (self.parent && self.parent.removeChild) { self.parent.removeChild(self); } }; return self; }); // --- Fighter class --- // Handles player and AI fighter logic, health, attacks, movement, and health bar var Fighter = Container.expand(function () { var self = Container.call(this); // --- Properties --- self.maxHealth = 100; self.health = self.maxHealth; self.moveSpeed = 13; self.jumpStrength = 60; self.gravity = 6; self.isJumping = false; self.isAttacking = false; self.isSpecialReady = true; self.specialCooldown = 360; // 6 seconds at 60fps self.specialTimer = 0; self.immobile = false; self.isFacingRight = true; self.attackCooldown = 72; // 1.2 seconds at 60fps self.attackTimer = 0; self.lastJumpPressed = false; // --- Dodge mechanic --- self.isDodging = false; self.dodgeDuration = 18; // 0.3s at 60fps self.dodgeTimer = 0; self.dodgeSpeed = 30; // Slightly increased dodge distance self.dodgeInvuln = false; self.lastDodgePressed = false; self.dodgeCooldown = 180; // 3s cooldown self.dodgeCooldownTimer = 0; // Start dodge if not already dodging or on 3s cooldown self.dodge = function () { if (self.isDodging || self.immobile || self.dodgeCooldownTimer > 0 || self.isJumping) return; self.isDodging = true; self.dodgeTimer = self.dodgeDuration; self.dodgeInvuln = true; self.dodgeCooldownTimer = self.dodgeCooldown; // Play dodge sound when dodge happens var dodgeSound = LK.getSound('dodge'); if (dodgeSound) dodgeSound.play(); // Visual feedback: flash fighter blue for dodge duration LK.effects.flashObject(self, 0x00aaff, self.dodgeDuration * 16); }; // --- Visuals --- // Use a different asset for player1 (controlled fighter) var assetId = typeof self.isPlayer1 !== "undefined" && self.isPlayer1 === true ? 'fighter1' : 'fighter'; var sprite = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 1.0, x: 0, y: 0 }); // Health bar container self.healthBar = new Container(); // Health bar outline (drawn behind the background) var healthBarOutline = new Container(); var outlineThickness = 6; var outlineW = 300 + outlineThickness * 2; var outlineH = 50 + outlineThickness * 2; var outlineRect = LK.getAsset('healthbar_bg', { anchorX: 0, anchorY: 0.5, x: -outlineThickness, y: 0, width: outlineW, height: outlineH, color: 0xffffff // white outline }); healthBarOutline.addChild(outlineRect); self.healthBar.addChild(healthBarOutline); // Health bar background var healthBarBg = LK.getAsset('healthbar_bg', { anchorX: 0, anchorY: 0.5, x: 0, y: 0 }); self.healthBar.addChild(healthBarBg); // Health bar foreground var healthBarFg = LK.getAsset('healthbar_fg', { anchorX: 0, anchorY: 0.5, x: 0, y: 0 }); self.healthBar.addChild(healthBarFg); self.healthBar.width = 300; self.healthBar.height = 50; self.healthBarFg = healthBarFg; self.updateHealthBar = function () { var pct = Math.max(0, self.health / self.maxHealth); self.healthBarFg.width = 300 * pct; }; self.updateHealthBar(); // --- Methods --- self.setFacing = function (right) { self.isFacingRight = right; sprite.scaleX = right ? 1 : -1; }; self.jump = function () { if (!self.isJumping && !self.immobile) { self.isJumping = true; self.vy = -self.jumpStrength; } }; self.attack = function () { if (self.isAttacking || self.immobile || self.attackTimer > 0) return; self.isAttacking = true; self.attackTimer = self.attackCooldown; // Create attack object var atk = new Attack(self, false); atk.x = self.x + (self.isFacingRight ? 120 : -120); atk.y = self.y - 200; attacks.push(atk); game.addChild(atk); // Play regatt sound for regular attack with randomized volume var regattSound = LK.getSound('regatt'); regattSound.volume = 0.2 + Math.random() * 0.8; // random volume between 0.2 and 1.0 regattSound.play(); // End attack after short delay LK.setTimeout(function () { self.isAttacking = false; }, 300); }; self.special = function () { if (!self.isSpecialReady || self.immobile) return; self.isSpecialReady = false; self.specialTimer = self.specialCooldown; // Create special attack object var atk = new Attack(self, true); atk.x = self.x + (self.isFacingRight ? 180 : -180); atk.y = self.y - 200; attacks.push(atk); game.addChild(atk); // Play spatt sound for special attack var spattSound = LK.getSound('spatt'); spattSound.play(); }; self.takeDamage = function (amount, dir, knockback) { if (self.immobile) return; self.health -= amount; self.updateHealthBar(); // Flash health bar foreground red for 200ms when taking damage LK.effects.flashObject(self.healthBarFg, 0xff0000, 200); // Knockback self.x += (dir || 1) * (knockback || 32); // Clamp to arena self.x = Math.max(120, Math.min(2048 - 120, self.x)); }; self.update = function () { // Dodge cooldown if (self.dodgeCooldownTimer > 0) { self.dodgeCooldownTimer--; } // Dodge logic if (self.isDodging) { // Move quickly in facing direction var dir = self.isFacingRight ? 1 : -1; self.x += dir * self.dodgeSpeed; // Clamp to arena self.x = Math.max(120, Math.min(2048 - 120, self.x)); self.dodgeTimer--; if (self.dodgeTimer <= 0) { self.isDodging = false; self.dodgeInvuln = false; } } // Attack cooldown if (self.attackTimer > 0) { self.attackTimer--; } // Special cooldown if (!self.isSpecialReady) { if (self.specialTimer > 0) { self.specialTimer--; } if (self.specialTimer <= 0) { self.isSpecialReady = true; } } // Jumping physics if (self.isJumping) { if (typeof self.vy === "undefined") self.vy = 0; // Track lastY for landing detection if (typeof self.lastY === "undefined") self.lastY = self.y; self.y += self.vy; self.vy += self.gravity; // Land on ground (detect landing this frame) if (self.lastY < arena.groundY && self.y >= arena.groundY) { self.y = arena.groundY; self.isJumping = false; self.vy = 0; // Play mov sound when landing from a jump var movSound = LK.getSound('mov'); if (movSound) movSound.play(); } self.lastY = self.y; } }; // --- Throw mechanic --- self.throwObject = function () { if (self.immobile || self.isDodging || self.isAttacking || self.isJumping) return; // Only allow one projectile at a time per fighter (optional, can remove for spam) if (typeof self.lastProjectileTick !== "undefined" && LK.ticks - self.lastProjectileTick < 30) return; if (typeof throwablesLeft !== "undefined" && throwablesLeft > 0) { throwablesLeft--; if (typeof throwablesText !== "undefined" && throwablesText.setText) { throwablesText.setText('Throwables: ' + throwablesLeft); } } self.lastProjectileTick = LK.ticks; var proj = new Projectile(self, self.isFacingRight); proj.x = self.x + (self.isFacingRight ? 120 : -120); proj.y = self.y - 180; // Play throw sound when projectile is spawned var throwSound = LK.getSound('throw'); if (throwSound) throwSound.play(); attacks.push(proj); game.addChild(proj); }; return self; }); // --- Projectile class --- // Represents a thrown projectile (e.g. shuriken, fireball) var Projectile = Container.expand(function () { var self = Container.call(this); // Arguments: owner (Fighter), isRight (bool) self.owner = arguments[0] || null; self.isRight = !!arguments[1]; self.hit = false; self.destroyed = false; self.damage = 12; self.speed = 38; self.lifetime = 60; // 1 second // Visual var sprite = self.attachAsset('btn_throw', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); self.width = sprite.width; self.height = sprite.height; // Set owner after creation if needed self.setOwner = function (fighter) { self.owner = fighter; }; self.update = function () { if (self.destroyed) return; // --- Trail effect: spawn faded throw asset at current position --- // Only spawn trail every 2 frames for performance if (typeof self._trailTick === "undefined") self._trailTick = 0; self._trailTick++; if (self._trailTick % 2 === 0) { var trail = LK.getAsset('btn_throw', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, scaleX: 0.7, scaleY: 0.7 }); trail.alpha = 0.35; if (self.parent && self.parent.addChild) self.parent.addChild(trail); // Fade out and destroy after 180ms tween(trail, { alpha: 0 }, { duration: 180, onFinish: function onFinish() { if (trail && trail.destroy) trail.destroy(); } }); } // Move in direction self.x += self.isRight ? self.speed : -self.speed; self.lifetime--; // Remove if out of bounds or expired if (self.x < -200 || self.x > 2048 + 200 || self.lifetime <= 0) { self.destroy(); } }; self.destroy = function () { if (self.destroyed) return; self.destroyed = true; if (self.parent && self.parent.removeChild) { self.parent.removeChild(self); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // --- Main Menu Overlay --- var mainMenuOverlay = new Container(); mainMenuOverlay.visible = true; // Ensure menu is visible at game start // Main menu background image (separate from arena) var menuBgImg = LK.getAsset('arena_bg', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); mainMenuOverlay.addChild(menuBgImg); // Semi-transparent overlay for menu text readability var menuBg = LK.getAsset('healthbar_bg', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732, color: 0x000000 }); menuBg.alpha = 0.7; mainMenuOverlay.addChild(menuBg); // Game title var titleText = new Text2('FIGHTER DUEL', { size: 180, fill: "#fff", stroke: 0x000000, strokeThickness: 16 }); titleText.anchor.set(0.5, 0); titleText.x = 2048 / 2; titleText.y = 420; mainMenuOverlay.addChild(titleText); // Subtitle var subtitleText = new Text2('Touch to Start', { size: 90, fill: 0xFFE066, stroke: 0x000000, strokeThickness: 8 }); subtitleText.anchor.set(0.5, 0); subtitleText.x = 2048 / 2; subtitleText.y = 700; mainMenuOverlay.addChild(subtitleText); // Simple instructions var instrText = new Text2('Move, Jump, Attack, Dodge, Throw!\nFirst to 2 wins!', { size: 60, fill: "#fff", stroke: 0x000000, strokeThickness: 6 }); instrText.anchor.set(0.5, 0); instrText.x = 2048 / 2; instrText.y = 900; mainMenuOverlay.addChild(instrText); // Add overlay to game game.addChild(mainMenuOverlay); // Block game start until menu is dismissed var gameStarted = false; // Hide all gameplay UI elements until menu is dismissed function setGameplayUIVisible(visible) { // Hide or show all gameplay UI elements if (typeof fighter1 !== "undefined" && fighter1 && fighter1.healthBar) { fighter1.healthBar.visible = visible; } if (typeof fighter2 !== "undefined" && fighter2 && fighter2.healthBar) { fighter2.healthBar.visible = visible; } if (typeof comboText1 !== "undefined") comboText1.visible = false; if (typeof comboText2 !== "undefined") comboText2.visible = false; if (typeof timerText !== "undefined") timerText.visible = visible; if (typeof throwablesText !== "undefined") throwablesText.visible = visible; if (typeof spcCooldownOverlay !== "undefined") spcCooldownOverlay.visible = false; if (typeof spcCooldownText !== "undefined") spcCooldownText.visible = false; if (typeof spcCooldownCounter !== "undefined") spcCooldownCounter.visible = false; if (typeof dodgeCooldownOverlay !== "undefined") dodgeCooldownOverlay.visible = false; if (typeof dodgeCooldownText !== "undefined") dodgeCooldownText.visible = false; if (typeof dodgeCooldownCounter !== "undefined") dodgeCooldownCounter.visible = false; if (typeof btnLeft !== "undefined") btnLeft.visible = visible; if (typeof btnRight !== "undefined") btnRight.visible = visible; if (typeof btnAtk !== "undefined") btnAtk.visible = visible; if (typeof btnJump !== "undefined") btnJump.visible = visible; if (typeof btnSpc !== "undefined") btnSpc.visible = visible; if (typeof btnThrow !== "undefined") btnThrow.visible = visible; if (typeof btnDodge !== "undefined") btnDodge.visible = visible; if (typeof arrowAbovePlayer1 !== "undefined") arrowAbovePlayer1.visible = visible; // Hide ground tiles if (typeof groundTiles !== "undefined") { for (var i = 0; i < groundTiles.length; i++) { if (groundTiles[i]) groundTiles[i].visible = visible; } } // Hide arena if (typeof arena !== "undefined") arena.visible = visible; // Hide fighters if (typeof fighter1 !== "undefined" && fighter1) fighter1.visible = visible; if (typeof fighter2 !== "undefined" && fighter2) fighter2.visible = visible; } // Hide gameplay UI at start setGameplayUIVisible(false); // Dismiss menu on any touch/click mainMenuOverlay.down = function () { mainMenuOverlay.visible = false; setGameplayUIVisible(true); gameStarted = true; }; // --- Arena setup --- //Note game dimensions are 2048x2732 //Minimalistic tween library which should be used for animations over time, including tinting / colouring an object, scaling, rotating, or changing any game object property. // Unique asset for player1 var arena = new Arena(); arena.x = 0; arena.y = 0; game.addChild(arena); // --- Fixed ground platform (visual) --- var groundHeight = 60; var groundTileWidth = LK.getAsset('ground', { anchorX: 0, anchorY: 0 }).width; var groundY = arena.groundY - 100; // Move ground even further down by reducing the offset (was -300) var groundTiles = []; var numGroundTiles = Math.ceil(2048 / groundTileWidth); // Only enough to fill the screen for (var i = 0; i < numGroundTiles; i++) { var groundTile = LK.getAsset('ground', { anchorX: 0, anchorY: 0, x: i * groundTileWidth, y: groundY }); game.addChild(groundTile); groundTiles.push(groundTile); } // No infinite scrolling, ground is fixed and does not move // --- Fighters --- var fighter1 = new Fighter(); fighter1.isPlayer1 = true; // Mark as player1 for asset selection fighter1.lastJumpPressed = false; fighter1.lastHitTime = -9999; // Track last time this fighter hit the other fighter1.comboCount = 0; // Current combo count var fighter2 = new Fighter(); fighter2.lastHitTime = -9999; fighter2.comboCount = 0; // Place fighters on opposite sides fighter1.x = 600; fighter1.y = arena.groundY; fighter1.setFacing(true); fighter2.x = 2048 - 600; fighter2.y = arena.groundY; fighter2.setFacing(false); game.addChild(fighter1); game.addChild(fighter2); // --- Arrow above controlled player (fighter1) --- var arrowAbovePlayer1 = LK.getAsset('btn_jump', { anchorX: 0.5, anchorY: 1.0, x: 0, y: 0, scaleX: 0.7, scaleY: 0.7 }); arrowAbovePlayer1.rotation = Math.PI; // Point downwards game.addChild(arrowAbovePlayer1); // --- Health bar GUI --- // Removed number healthbars (Text2 healthBar1 and healthBar2) // Move the box healthbars (fighter healthBar containers) to a more visible, symmetric place below the timer at the top center // Remove from fighter containers and add to GUI, position symmetrically below timer fighter1.removeChild(fighter1.healthBar); fighter2.removeChild(fighter2.healthBar); // Place both healthbars below the timer, offset horizontally from center fighter1.healthBar.x = -550; fighter1.healthBar.y = 170; fighter2.healthBar.x = 250; fighter2.healthBar.y = 170; LK.gui.top.addChild(fighter1.healthBar); LK.gui.top.addChild(fighter2.healthBar); // --- Combo counters under health bars --- var comboText1 = new Text2('Combo: 0', { size: 48, fill: 0xFF0000, stroke: 0xffffff, strokeThickness: 8 }); comboText1.anchor.set(0.5, 0); comboText1.x = fighter1.healthBar.x + 150; // center under health bar (300px wide) comboText1.y = fighter1.healthBar.y + 60; // just below health bar var comboText2 = new Text2('Combo: 0', { size: 48, fill: 0xFF0000, stroke: 0xffffff, strokeThickness: 8 }); comboText2.anchor.set(0.5, 0); comboText2.x = fighter2.healthBar.x + 150; comboText2.y = fighter2.healthBar.y + 60; LK.gui.top.addChild(comboText1); LK.gui.top.addChild(comboText2); // Combo timer variables (in frames, 1.5s = 90 frames) var comboTimer1 = 0; var comboTimer2 = 0; // --- Round and timer --- var round = 1; var maxRounds = 3; var wins1 = 0; var wins2 = 0; var roundTime = 60 * 30; // 30 seconds at 60fps var roundTimer = roundTime; var timerText = new Text2('30', { size: 80, fill: "#fff" }); timerText.anchor.set(0.5, 0); LK.gui.top.addChild(timerText); // --- Touch controls (mobile-friendly) --- var leftPressed = false, rightPressed = false, atkPressed = false, jumpPressed = false, spcPressed = false, throwPressed = false, dodgePressed = false; // New for dodge // Special attack cooldown overlay for button var spcCooldownOverlay = LK.getAsset('healthbar_bg', { anchorX: 0.5, anchorY: 0.5, width: 180, height: 180, color: 0x000000 }); spcCooldownOverlay.alpha = 0.5; spcCooldownOverlay.visible = false; spcCooldownOverlay.x = 2048 - 350; spcCooldownOverlay.y = 2450; game.addChild(spcCooldownOverlay); var spcCooldownText = new Text2('3', { size: 90, fill: "#fff" }); spcCooldownText.anchor.set(0.5, 0.5); spcCooldownText.x = 2048 - 350; spcCooldownText.y = 2450; spcCooldownText.visible = false; game.addChild(spcCooldownText); // Special attack cooldown counter above the special attack button var spcCooldownCounter = new Text2('', { size: 60, fill: "#fff" }); spcCooldownCounter.anchor.set(0.5, 1.0); // Place above the special button (btnSpc) spcCooldownCounter.x = 2048 - 350; spcCooldownCounter.y = 2450 - 120; spcCooldownCounter.visible = false; game.addChild(spcCooldownCounter); // --- Dodge cooldown overlay and timer (like special) --- // --- Dodge cooldown overlay and timer (like special) --- // (Positioning is set after btnDodge is defined below) var dodgeCooldownOverlay = LK.getAsset('healthbar_bg', { anchorX: 0.5, anchorY: 0.5, width: 180, height: 180, color: 0x000000 }); dodgeCooldownOverlay.alpha = 0.5; dodgeCooldownOverlay.visible = false; game.addChild(dodgeCooldownOverlay); var dodgeCooldownText = new Text2('3', { size: 90, fill: "#fff" }); dodgeCooldownText.anchor.set(0.5, 0.5); dodgeCooldownText.visible = false; game.addChild(dodgeCooldownText); var dodgeCooldownCounter = new Text2('', { size: 60, fill: "#fff" }); dodgeCooldownCounter.anchor.set(0.5, 1.0); dodgeCooldownCounter.visible = false; game.addChild(dodgeCooldownCounter); // Control buttons (simple rectangles for now) var btnLeft = LK.getAsset('btn_left', { anchorX: 0.5, anchorY: 0.5, x: 200, y: 2600, scaleX: 1.8, scaleY: 1.8 }); var btnRight = LK.getAsset('btn_right', { anchorX: 0.5, anchorY: 0.5, x: 500, y: 2600, scaleX: 1.8, scaleY: 1.8 }); var btnAtk = LK.getAsset('btn_atk', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 500, y: 2600, scaleX: 1.8, scaleY: 1.8 }); var btnJump = LK.getAsset('btn_jump', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 200, y: 2600, scaleX: 1.8, scaleY: 1.8 }); var btnSpc = LK.getAsset('btn_spc', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 350, y: 2450, scaleX: 1.8, scaleY: 1.8 }); // Add dodge button (reuse btn_right asset, place above the throw button, lower opacity) var btnDodge = LK.getAsset('btn_right', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 200, y: 2080, // revert to old position, higher above throw button scaleX: 1.8, scaleY: 1.8 }); btnDodge.alpha = 0.7; // Add throw button (use unique btn_throw asset, place visually below the dodge button) var btnThrow = LK.getAsset('btn_throw', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 200, y: 2320, //{3u} // moved higher scaleX: 1.8, scaleY: 1.8 }); game.addChild(btnLeft); game.addChild(btnRight); game.addChild(btnAtk); game.addChild(btnJump); game.addChild(btnSpc); game.addChild(btnThrow); game.addChild(btnDodge); // Set dodge cooldown overlay and text positions now that btnDodge is defined dodgeCooldownOverlay.x = btnDodge.x; dodgeCooldownOverlay.y = btnDodge.y; dodgeCooldownText.x = btnDodge.x; dodgeCooldownText.y = btnDodge.y; dodgeCooldownCounter.x = btnDodge.x; dodgeCooldownCounter.y = btnDodge.y - 120; // Button event helpers btnLeft.down = function () { leftPressed = true; }; btnLeft.up = function () { leftPressed = false; }; btnRight.down = function () { rightPressed = true; }; btnRight.up = function () { rightPressed = false; }; btnAtk.down = function () { atkPressed = true; }; btnAtk.up = function () { atkPressed = false; }; btnJump.down = function () { jumpPressed = true; }; btnJump.up = function () { jumpPressed = false; }; btnSpc.down = function () { spcPressed = true; }; btnSpc.up = function () { spcPressed = false; }; btnThrow.down = function () { throwPressed = true; }; btnThrow.up = function () { throwPressed = false; }; btnDodge.down = function () { dodgePressed = true; }; btnDodge.up = function () { dodgePressed = false; }; // --- Attacks array --- var attacks = []; // --- Throwables counter and display --- var throwablesLeft = 5; var throwablesText = new Text2('Throwables: 5', { size: 60, fill: "#fff", stroke: 0x000000, strokeThickness: 8 }); throwablesText.anchor.set(0, 0); // Top left, but not in the 100x100 reserved area throwablesText.x = 120; throwablesText.y = 30; LK.gui.top.addChild(throwablesText); // --- Game update loop --- game.update = function () { // --- Main menu block: If not started, skip all gameplay logic --- if (typeof gameStarted !== "undefined" && !gameStarted) { // Animate subtitle (blink) if (typeof subtitleText !== "undefined") { subtitleText.alpha = 0.5 + 0.5 * Math.sin(LK.ticks / 20); } return; } // --- Timer --- if (roundTimer > 0) { roundTimer--; timerText.setText(Math.ceil(roundTimer / 60)); } // --- Update arrow position above fighter1 --- if (typeof arrowAbovePlayer1 !== "undefined" && typeof fighter1 !== "undefined") { arrowAbovePlayer1.x = fighter1.x; // Place arrow 30px above the top of the fighter sprite (closer to player) arrowAbovePlayer1.y = fighter1.y - (fighter1.height || 400) - 30; } // --- Ground is fixed, no update needed --- // --- Player 1 controls (left side) --- if (!fighter1.immobile) { if (typeof fighter1.isMoving === "undefined") fighter1.isMoving = false; if (typeof fighter1.lastMoving === "undefined") fighter1.lastMoving = false; fighter1.isMoving = leftPressed || rightPressed; // Removed mov sound on movement for player if (leftPressed) { fighter1.x = Math.max(120, fighter1.x - fighter1.moveSpeed); fighter1.setFacing(false); } if (rightPressed) { fighter1.x = Math.min(2048 - 120, fighter1.x + fighter1.moveSpeed); fighter1.setFacing(true); } fighter1.lastMoving = fighter1.isMoving; // Only trigger jump on the frame the button is pressed (rising edge) if (jumpPressed && !fighter1.lastJumpPressed) { fighter1.jump(); } // Only trigger dodge on the frame the button is pressed (rising edge) if (dodgePressed && !fighter1.lastDodgePressed) { fighter1.dodge(); } if (atkPressed) { fighter1.attack(); } if (throwPressed && typeof fighter1.throwObject === "function") { if (throwablesLeft > 0) { fighter1.throwObject(); } } if (spcPressed) { if (fighter1.isSpecialReady) { fighter1.special(); } } } fighter1.lastJumpPressed = jumpPressed; fighter1.lastDodgePressed = dodgePressed; // --- Special attack cooldown overlay update --- if (!fighter1.isSpecialReady) { spcCooldownOverlay.visible = true; spcCooldownText.visible = true; var secondsLeft = Math.ceil(fighter1.specialTimer / 60); spcCooldownText.setText(secondsLeft); // Show and update the counter above the special button spcCooldownCounter.visible = true; spcCooldownCounter.setText(secondsLeft + "s"); } else { spcCooldownOverlay.visible = false; spcCooldownText.visible = false; spcCooldownCounter.visible = false; } // --- Dodge cooldown overlay update --- if (fighter1.dodgeCooldownTimer > 0) { dodgeCooldownOverlay.visible = true; dodgeCooldownText.visible = true; var dodgeSecondsLeft = Math.ceil(fighter1.dodgeCooldownTimer / 60); dodgeCooldownText.setText(dodgeSecondsLeft); dodgeCooldownCounter.visible = true; dodgeCooldownCounter.setText(dodgeSecondsLeft + "s"); } else { dodgeCooldownOverlay.visible = false; dodgeCooldownText.visible = false; dodgeCooldownCounter.visible = false; } // --- AI for fighter2 (move toward player, attack, dodge, and throw) --- if (!fighter2.immobile) { var dx = fighter1.x - fighter2.x; // --- Dodge logic: randomly dodge if player is attacking and close, or sometimes randomly --- var shouldDodge = false; // If player is attacking and close, higher chance to dodge if (!fighter2.isDodging && fighter2.dodgeCooldownTimer === 0 && !fighter2.isJumping && Math.abs(dx) < 350 && fighter1.isAttacking && Math.random() < 0.18) { shouldDodge = true; } // Occasionally dodge randomly if (!fighter2.isDodging && fighter2.dodgeCooldownTimer === 0 && !fighter2.isJumping && Math.random() < 0.01) { shouldDodge = true; } if (shouldDodge) { fighter2.dodge(); } // --- Move toward player --- if (Math.abs(dx) > 180) { if (typeof fighter2.isMoving === "undefined") fighter2.isMoving = false; if (typeof fighter2.lastMoving === "undefined") fighter2.lastMoving = false; fighter2.isMoving = false; if (Math.abs(dx) > 180) { if (dx < 0) { fighter2.x -= fighter2.moveSpeed * 0.7; fighter2.setFacing(false); fighter2.isMoving = true; } else { fighter2.x += fighter2.moveSpeed * 0.7; fighter2.setFacing(true); fighter2.isMoving = true; } // Removed mov sound on movement for AI } else if (!fighter2.isAttacking && Math.random() < 0.04) { fighter2.attack(); } fighter2.lastMoving = fighter2.isMoving; } else if (!fighter2.isAttacking && Math.random() < 0.04) { fighter2.attack(); } // --- Jump occasionally --- if (!fighter2.isJumping && Math.random() < 0.01) { fighter2.jump(); } // --- Special attack occasionally --- if (fighter2.isSpecialReady && Math.random() < 0.008) { fighter2.special(); } // --- Throw object if available and player is in range --- // Give AI its own throwables count (initialize if needed) if (typeof fighter2.throwablesLeft === "undefined") { fighter2.throwablesLeft = 5; } // Only allow one projectile at a time per fighter (like player) if (typeof fighter2.lastProjectileTick === "undefined" || LK.ticks - fighter2.lastProjectileTick >= 30) { // If player is in range and AI has throwables, throw with some probability if (fighter2.throwablesLeft > 0 && Math.abs(dx) > 300 && Math.abs(dx) < 1200 && Math.random() < 0.04) { // Use the same throwObject logic, but decrement AI's own throwablesLeft fighter2.throwablesLeft--; fighter2.lastProjectileTick = LK.ticks; var proj = new Projectile(fighter2, fighter2.isFacingRight); proj.x = fighter2.x + (fighter2.isFacingRight ? 120 : -120); proj.y = fighter2.y - 180; attacks.push(proj); game.addChild(proj); } } } // --- Update fighters --- fighter1.update(); fighter2.update(); // --- Update attacks --- for (var i = attacks.length - 1; i >= 0; i--) { var atk = attacks[i]; // Store last position for possible future use (e.g. for projectiles) if (typeof atk.lastX === "undefined") atk.lastX = atk.x; if (typeof atk.lastY === "undefined") atk.lastY = atk.y; atk.update(); // Remove if destroyed if (atk.destroyed) { attacks.splice(i, 1); continue; } // Set owner if not set if (!atk.owner) { atk.setOwner(atk.isSpecial ? atk.x < 2048 / 2 ? fighter1 : fighter2 : fighter1); } // Handle collision with the other fighter var target = atk.owner === fighter1 ? fighter2 : fighter1; // If target is dodging and invulnerable, skip hit if (target.dodgeInvuln) { atk.lastX = atk.x; atk.lastY = atk.y; continue; } // Only trigger on the exact frame of collision (rising edge) var wasIntersecting = atk.lastWasIntersecting || false; var isIntersecting = atk.intersects(target); if (!wasIntersecting && isIntersecting && !atk.hit) { // Combo logic: check if this hit is within 1.5 seconds (90 frames) of the last hit by this attacker var nowTick = LK.ticks || 0; if (typeof atk.owner.lastHitTime === "undefined") atk.owner.lastHitTime = -9999; if (typeof atk.owner.comboCount === "undefined") atk.owner.comboCount = 0; var prevCombo = atk.owner.comboCount; if (nowTick - atk.owner.lastHitTime <= 90) { atk.owner.comboCount++; // Start combo timer for this fighter if (atk.owner === fighter1 && atk.owner.comboCount > prevCombo) { comboTimer1 = 90; } if (atk.owner === fighter2 && atk.owner.comboCount > prevCombo) { comboTimer2 = 90; } // Impact effect on combo text when combo increases if (atk.owner === fighter1 && atk.owner.comboCount > prevCombo) { comboText1.scaleX = comboText1.scaleY = 1.3; // Impact scale tween(comboText1, { scaleX: 1, scaleY: 1 }, { duration: 180 }); // Shake effect var shakeTimes = 10; var shakeMagnitude = 18; var shakeDuration = 180; var shakeStep = Math.floor(shakeDuration / shakeTimes); var origX = comboText1.x; var origY = comboText1.y; for (var s = 0; s < shakeTimes; s++) { (function (s) { LK.setTimeout(function () { // Alternate shake direction var dx = (s % 2 === 0 ? 1 : -1) * shakeMagnitude * (1 - s / shakeTimes); var dy = (s % 2 === 0 ? -1 : 1) * shakeMagnitude * 0.5 * (1 - s / shakeTimes); comboText1.x = origX + dx; comboText1.y = origY + dy; // Restore at end if (s === shakeTimes - 1) { LK.setTimeout(function () { comboText1.x = origX; comboText1.y = origY; }, shakeStep); } }, s * shakeStep); })(s); } } else if (atk.owner === fighter2 && atk.owner.comboCount > prevCombo) { comboText2.scaleX = comboText2.scaleY = 1.3; tween(comboText2, { scaleX: 1, scaleY: 1 }, { duration: 180 }); // Shake effect var shakeTimes2 = 10; var shakeMagnitude2 = 18; var shakeDuration2 = 180; var shakeStep2 = Math.floor(shakeDuration2 / shakeTimes2); var origX2 = comboText2.x; var origY2 = comboText2.y; for (var s2 = 0; s2 < shakeTimes2; s2++) { (function (s2) { LK.setTimeout(function () { var dx2 = (s2 % 2 === 0 ? 1 : -1) * shakeMagnitude2 * (1 - s2 / shakeTimes2); var dy2 = (s2 % 2 === 0 ? -1 : 1) * shakeMagnitude2 * 0.5 * (1 - s2 / shakeTimes2); comboText2.x = origX2 + dx2; comboText2.y = origY2 + dy2; if (s2 === shakeTimes2 - 1) { LK.setTimeout(function () { comboText2.x = origX2; comboText2.y = origY2; }, shakeStep2); } }, s2 * shakeStep2); })(s2); } } } else { atk.owner.comboCount = 1; // Start combo timer for this fighter if (atk.owner === fighter1 && atk.owner.comboCount > prevCombo) { comboTimer1 = 90; } if (atk.owner === fighter2 && atk.owner.comboCount > prevCombo) { comboTimer2 = 90; } // Impact effect on combo text when combo increases from 0 if (atk.owner === fighter1 && atk.owner.comboCount > prevCombo) { comboText1.scaleX = comboText1.scaleY = 1.3; tween(comboText1, { scaleX: 1, scaleY: 1 }, { duration: 180 }); // Shake effect var shakeTimes = 10; var shakeMagnitude = 18; var shakeDuration = 180; var shakeStep = Math.floor(shakeDuration / shakeTimes); var origX = comboText1.x; var origY = comboText1.y; for (var s = 0; s < shakeTimes; s++) { (function (s) { LK.setTimeout(function () { var dx = (s % 2 === 0 ? 1 : -1) * shakeMagnitude * (1 - s / shakeTimes); var dy = (s % 2 === 0 ? -1 : 1) * shakeMagnitude * 0.5 * (1 - s / shakeTimes); comboText1.x = origX + dx; comboText1.y = origY + dy; if (s === shakeTimes - 1) { LK.setTimeout(function () { comboText1.x = origX; comboText1.y = origY; }, shakeStep); } }, s * shakeStep); })(s); } } else if (atk.owner === fighter2 && atk.owner.comboCount > prevCombo) { comboText2.scaleX = comboText2.scaleY = 1.3; tween(comboText2, { scaleX: 1, scaleY: 1 }, { duration: 180 }); // Shake effect var shakeTimes2 = 10; var shakeMagnitude2 = 18; var shakeDuration2 = 180; var shakeStep2 = Math.floor(shakeDuration2 / shakeTimes2); var origX2 = comboText2.x; var origY2 = comboText2.y; for (var s2 = 0; s2 < shakeTimes2; s2++) { (function (s2) { LK.setTimeout(function () { var dx2 = (s2 % 2 === 0 ? 1 : -1) * shakeMagnitude2 * (1 - s2 / shakeTimes2); var dy2 = (s2 % 2 === 0 ? -1 : 1) * shakeMagnitude2 * 0.5 * (1 - s2 / shakeTimes2); comboText2.x = origX2 + dx2; comboText2.y = origY2 + dy2; if (s2 === shakeTimes2 - 1) { LK.setTimeout(function () { comboText2.x = origX2; comboText2.y = origY2; }, shakeStep2); } }, s2 * shakeStep2); })(s2); } } } atk.owner.lastHitTime = nowTick; // Knockback direction: +1 if attacker is facing right, -1 if left var knockbackDir = atk.owner && atk.owner.isFacingRight ? 1 : -1; // Knockback strength: special attacks knock back SIGNIFICANTLY more (much stronger effect) var knockbackStrength = atk.isSpecial ? 480 : 64; target.takeDamage(atk.damage, knockbackDir, knockbackStrength); // If hit by a special attack, make target immobile for 1 second (60 frames) if (atk.isSpecial) { target.immobile = true; LK.setTimeout(function () { target.immobile = false; }, 1000); } atk.hit = true; atk.destroy(); // Flash on hit LK.effects.flashObject(target, 0xff0000, 200); // Show a visible attack effect at the hit location, rotated to match the attack direction var hitEffect; if (typeof Projectile !== "undefined" && atk instanceof Projectile) { // Unique projectile hit effect: use btn_throw asset, smaller, quick fade hitEffect = LK.getAsset('btn_throw', { anchorX: 0.5, anchorY: 0.5, x: target.x, y: target.y - 200 }); hitEffect.rotation = 0; game.addChild(hitEffect); hitEffect.scaleX = hitEffect.scaleY = 1.0; hitEffect.alpha = 1; tween(hitEffect, { scaleX: 1.7, scaleY: 1.7, alpha: 0 }, { duration: 220, onFinish: function onFinish() { if (hitEffect && hitEffect.destroy) hitEffect.destroy(); } }); } else { // Regular or special attack effect hitEffect = LK.getAsset(atk.isSpecial ? 'special_attack' : 'attack', { anchorX: 0.5, anchorY: 0.5, x: target.x, y: target.y - 200 }); // Rotate the effect to match the direction the attacker is facing if (atk.owner && atk.owner.isFacingRight === false) { hitEffect.rotation = Math.PI; // Face left } else { hitEffect.rotation = 0; // Face right (default) } game.addChild(hitEffect); // Animate the effect: scale up and fade out, then destroy hitEffect.scaleX = hitEffect.scaleY = 1.2; hitEffect.alpha = 1; tween(hitEffect, { scaleX: 2.0, scaleY: 2.0, alpha: 0 }, { duration: 350, onFinish: function onFinish() { if (hitEffect && hitEffect.destroy) hitEffect.destroy(); } }); } } // Update last intersection state for next frame atk.lastWasIntersecting = isIntersecting; atk.lastX = atk.x; atk.lastY = atk.y; } // --- Health bar updates --- // Removed number healthbars update // --- Combo reset logic: reset combo if 1.5 seconds (90 frames) have passed since last hit --- var nowTick = LK.ticks || 0; if (nowTick - fighter1.lastHitTime > 90) { fighter1.comboCount = 0; } if (nowTick - fighter2.lastHitTime > 90) { fighter2.comboCount = 0; } // Decrement combo timers if (comboTimer1 > 0) comboTimer1--; if (comboTimer2 > 0) comboTimer2--; // Update combo counters under health bars, only show if timer is active if (comboTimer1 > 0 && fighter1.comboCount > 0) { comboText1.visible = true; comboText1.setText(('COMBO: ' + (fighter1.comboCount || 0) + '!').toUpperCase()); } else { comboText1.visible = false; } if (comboTimer2 > 0 && fighter2.comboCount > 0) { comboText2.visible = true; comboText2.setText(('COMBO: ' + (fighter2.comboCount || 0) + '!').toUpperCase()); } else { comboText2.visible = false; } // --- Win/lose/round logic --- var roundOver = false; var winner = null; if (fighter1.health <= 0) { wins2++; roundOver = true; winner = 2; } else if (fighter2.health <= 0) { wins1++; roundOver = true; winner = 1; } else if (roundTimer <= 0) { if (fighter1.health > fighter2.health) { wins1++; winner = 1; } else if (fighter2.health > fighter1.health) { wins2++; winner = 2; } else { // Draw, no one gets a win } roundOver = true; } if (roundOver) { if (wins1 >= 2) { LK.showYouWin(); return; } else if (wins2 >= 2) { LK.showGameOver(); return; } // Next round round++; roundTimer = roundTime; fighter1.health = fighter1.maxHealth; fighter2.health = fighter2.maxHealth; fighter1.updateHealthBar(); fighter2.updateHealthBar(); fighter1.x = 600; fighter2.x = 2048 - 600; fighter1.setFacing(true); fighter2.setFacing(false); // Reset throwables for both fighters throwablesLeft = 5; if (typeof throwablesText !== "undefined" && throwablesText.setText) { throwablesText.setText('Throwables: ' + throwablesLeft); } fighter2.throwablesLeft = 5; // Remove all attacks for (var j = attacks.length - 1; j >= 0; j--) { if (attacks[j].destroy) attacks[j].destroy(); } attacks = []; } };
===================================================================
--- original.js
+++ change.js
@@ -247,16 +247,22 @@
}
// Jumping physics
if (self.isJumping) {
if (typeof self.vy === "undefined") self.vy = 0;
+ // Track lastY for landing detection
+ if (typeof self.lastY === "undefined") self.lastY = self.y;
self.y += self.vy;
self.vy += self.gravity;
- // Land on ground
- if (self.y >= arena.groundY) {
+ // Land on ground (detect landing this frame)
+ if (self.lastY < arena.groundY && self.y >= arena.groundY) {
self.y = arena.groundY;
self.isJumping = false;
self.vy = 0;
+ // Play mov sound when landing from a jump
+ var movSound = LK.getSound('mov');
+ if (movSound) movSound.play();
}
+ self.lastY = self.y;
}
};
// --- Throw mechanic ---
self.throwObject = function () {
@@ -804,26 +810,9 @@
if (!fighter1.immobile) {
if (typeof fighter1.isMoving === "undefined") fighter1.isMoving = false;
if (typeof fighter1.lastMoving === "undefined") fighter1.lastMoving = false;
fighter1.isMoving = leftPressed || rightPressed;
- //{5d} // Only one movement sound for both fighters
- if (typeof globalMovSoundInstance === "undefined") globalMovSoundInstance = null;
- // Play movement sound as long as either fighter is moving, only one instance at a time
- if (fighter1.isMoving || typeof fighter2 !== "undefined" && fighter2.isMoving) {
- if (!globalMovSoundInstance || (typeof globalMovSoundInstance.playing !== "boolean" ? globalMovSoundInstance.ended : !globalMovSoundInstance.playing)) {
- var movSound = LK.getSound('mov');
- if (movSound) {
- globalMovSoundInstance = movSound.play({
- loop: true
- });
- }
- }
- } else {
- if (globalMovSoundInstance && typeof globalMovSoundInstance.stop === "function") {
- globalMovSoundInstance.stop();
- globalMovSoundInstance = null;
- }
- }
+ // Removed mov sound on movement for player
if (leftPressed) {
fighter1.x = Math.max(120, fighter1.x - fighter1.moveSpeed);
fighter1.setFacing(false);
}
@@ -913,9 +902,9 @@
fighter2.x += fighter2.moveSpeed * 0.7;
fighter2.setFacing(true);
fighter2.isMoving = true;
}
- // Movement sound for fighter2 is handled globally above; no per-fighter sound logic needed here.
+ // Removed mov sound on movement for AI
} else if (!fighter2.isAttacking && Math.random() < 0.04) {
fighter2.attack();
}
fighter2.lastMoving = fighter2.isMoving;