Code edit (2 edits merged)
Please save this source code
User prompt
set debug mode on
User prompt
increase scan bar rotation frequency with level
Code edit (5 edits merged)
Please save this source code
User prompt
when is Died hide attackButton
Code edit (1 edits merged)
Please save this source code
Code edit (21 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Timeout.tick error: Cannot read properties of undefined (reading 'width')' in or related to this line: 'self.droneScanLaser.x = droneGraphics.width * 2;' Line Number: 270
Code edit (1 edits merged)
Please save this source code
Code edit (2 edits merged)
Please save this source code
User prompt
animate the game container scale to 4 when knight dies by using tween with bounce easing ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
animate the game container when knight dies by using tween with bounce easing ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
animate when knight dies by using tween with bounce easing on the game container ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
call LK.showYouWin(); after 2000sec
User prompt
play knight-win before showing youWin screen
Code edit (2 edits merged)
Please save this source code
User prompt
in start(), if self.droneCounter > 3 show LK win
Code edit (3 edits merged)
Please save this source code
User prompt
add a droneCounter in droneManager
Code edit (5 edits merged)
Please save this source code
User prompt
when drone explode stop its scaning sound
Code edit (1 edits merged)
Please save this source code
User prompt
play knight-killed when knight die
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Background = Container.expand(function () { var self = Container.call(this); var backgroundGraphics = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5 }); self.addChild(backgroundGraphics); self.x = 2048 / 2; self.y = 2732 / 2; // Move the down handler to the Background class self.down = function (x, y, obj) { var game_position = game.toLocal(obj.global); target.x = Math.max(corners[0].x, Math.min(game_position.x, corners[3].x)); target.y = Math.max(corners[0].y, Math.min(game_position.y, corners[3].y)); }; }); var ButtonPunch = Container.expand(function () { var self = Container.call(this); var buttonGraphics = LK.getAsset('buttonPunch', { anchorX: 0.5, anchorY: 0.5 }); self.addChild(buttonGraphics); // Add click/tap handler self.down = function () { // Trigger knight attack when button is pressed if (knight && !knight.isAttacking) { knight.attack(); } }; }); // Import the tween plugin var Drone = Container.expand(function () { var self = Container.call(this); self.health = 3; // Initialize health property with a value of 3 var droneGraphics = LK.getAsset('drone', { anchorX: 0.5, anchorY: 0.5 }); var shadowDrone = LK.getAsset('drone', { anchorX: 0.5, anchorY: 0.5 }); self.shadowOffset = { x: -30, y: 40 }; // Define shadow offset property shadowDrone.alpha = 0.5; shadowDrone.tint = 0x000000; shadowDrone.x = -10; shadowDrone.y = 40; self.addChild(shadowDrone); self.addChild(droneGraphics); self.droneScanLaser = LK.getAsset('droneScanLaser', { anchorX: 0.5, anchorY: 0.5 }); self.droneScanBar = LK.getAsset('droneScanBar', { anchorX: 0, anchorY: 0.5 }); self.droneScanLaser.visible = true; // Always visible self.droneScanLaser.x = droneGraphics.width * 2; self.droneScanLaser.alpha = 0.3; // Set alpha to 0.3 self.addChild(self.droneScanLaser); self.droneScanBar.x = droneGraphics.width / 2; // droneScanLaser.x; self.droneScanBar.blendMode = 3; self.droneScanBar.y = self.droneScanLaser.y; self.addChild(self.droneScanBar); function loopScanLaserTint() { if (self.state !== 'scanning') { return; } tween(self.droneScanLaser, { tint: 0x00FF00 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(self.droneScanLaser, { tint: 0x00AA00 }, { duration: 1000, easing: tween.easeInOut, onFinish: loopScanLaserTint // Recursively call to loop }); } }); } function animateScanBar() { if (self.state !== 'scanning') { return; } tween(self.droneScanBar, { alpha: 0.3, rotation: Math.PI / 6.5 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { tween(self.droneScanBar, { alpha: 0.6, rotation: -Math.PI / 6.5 }, { duration: 1000, easing: tween.easeInOut, onFinish: animateScanBar // Recursively call to loop }); // Ensure the tween is started } }); // Ensure the tween is started } self.speed = 5; self.state = ''; // Possible states: scanning, attacking // Add debug text to track drone state self.debugText = new Text2(self.state, { size: 50, fill: 0xFFFFFF }); self.debugText.x = -droneGraphics.width / 2; self.debugText.y = -droneGraphics.height / 2 - 50; self.debugText.visible = isDebug; // Set visibility based on debug mode self.addChild(self.debugText); self.fireRange = 300; // Example fire range value self.currentTargetIndex = -1; self.target = { x: 0, y: 0 }; self.lastKnightCheckTime = 0; // Track when we last checked for knight intersection self.lastFireTime = 0; // Track when we last fired at the knight self.lastRotationTime = 0; // Track the last time we updated rotation self.isRotating = false; // Flag to track if we're currently rotating self.isMoving = false; // Flag to track if we're currently moving self.needsRotation = false; // Flag to track if we need to rotate before moving self.justSwitchedFromAttacking = false; // Flag to track if we just switched from attacking mode // Function to select a new random target self.selectTarget = function () { log("selectTarget...:"); var newTargetIndex; do { newTargetIndex = Math.floor(Math.random() * corners.length); } while (newTargetIndex === self.currentTargetIndex); self.currentTargetIndex = newTargetIndex; self.target = corners[newTargetIndex]; log("New target selected at position:", self.target.x, self.target.y); // Set a flag to indicate the target is new and needs rotation first self.needsRotation = true; // Return the new target (not used currently but could be useful) return self.target; }; // Function to fire a laser beam self.fire = function () { log("Drone firing laser beam..."); LK.getSound('drone-beam').play(); var laserBeam = new LaserBeam(); laserBeam.fire(self.x, self.y, self.rotation); }; // Function to handle state transitions self.switchState = function (newState) { // Don't do anything if the state isn't changing if (self.state === newState) { return; } log("Switching drone state from " + self.state + " to " + newState); // Stop any existing tweens to ensure clean state transition tween.stop(self); // Clear scan sound interval if it exists if (self.scanSoundInterval) { LK.clearInterval(self.scanSoundInterval); self.scanSoundInterval = null; } // Reset movement flags self.isRotating = false; self.isMoving = false; self.justSwitchedFromAttacking = newState === 'scanning' && self.state === 'attacking'; // Update the state self.state = newState; self.debugText.setText(self.state); // Update debug text with current state // Handle entry actions for new state if (newState === 'scanning') { // Play drone-scan sound self.scanSoundInterval = LK.setInterval(function () { LK.getSound('drone-scan').play(); }, 1000); // Entering scanning state self.debugText.tint = 0xFFFFFF; // Reset tint to white for other states self.droneScanLaser.tint = 0x00FF00; // Reset tint to green for scanning mode // Clear current target and set flag to select a new one //self.target = null; //self.needsRotation = true; animateScanBar(); // Select a new target (this will be handled in updateScanning on next update) } else if (newState === 'attacking') { // Entering attacking state self.debugText.tint = 0xFFA500; // Change tint to orange when attacking self.droneScanLaser.tint = 0xFFA500; // Change tint to orange when attacking // Start following the knight self.followKnight(); } }; self.init = function () { log("Drone initialized at position:", self.x, self.y); // Reset state flags self.isRotating = false; self.isMoving = false; self.needsRotation = true; // Start the animations animateScanBar(); loopScanLaserTint(); // Switch to scanning state to begin patrol self.switchState('scanning'); }; self.update = function () { if (self.state === 'scanning') { self.updateScanning(); } else if (self.state === 'attacking') { self.updateAttacking(); } // Update shadow position shadowDrone.x = self.shadowOffset.x * Math.cos(self.rotation); shadowDrone.y = self.shadowOffset.y * Math.sin(self.rotation); // Update droneScanBar width based on its rotation self.droneScanBar.width = 620 * (1 + 0.33 * Math.abs(Math.sin(self.droneScanBar.rotation))); }; // Function to perform a full 360° scan self.performFullScan = function (callback) { // Calculate duration based on a consistent rotation speed var scanRotationSpeed = 1.0; // Radians per second (slower for scanning) var fullRotationDuration = Math.PI * 2 / scanRotationSpeed * 1000; // ms // Store current rotation var startRotation = self.rotation; // Mark that we're rotating to prevent updateScanning from starting other rotations self.isRotating = true; log("Performing 360° scan"); // Perform a full 360° rotation tween(self, { rotation: startRotation + Math.PI * 2 // Full 360° rotation }, { duration: fullRotationDuration, easing: tween.linear, // Linear easing for constant speed onFinish: function onFinish() { // Clear rotation flag when done self.isRotating = false; log("Full scan complete"); // Call callback if provided if (callback) { callback(); } } }); }; // Handle scanning mode behavior self.updateScanning = function () { // Check for intersection with knight if (!isDied && self.droneScanBar.intersects(knight.boundingBox)) { self.switchState('attacking'); log("Drone switched to attacking mode!"); return; } // If no target is set, select one if (!self.target) { log("No target set, selecting a new one"); // If we just switched from attacking to scanning mode, do a 360° scan first if (self.justSwitchedFromAttacking) { self.justSwitchedFromAttacking = false; // Perform full scan before selecting a new target self.performFullScan(function () { // After scan is complete, select a new target self.selectTarget(); }); return; } // Otherwise just select a new target self.selectTarget(); return; } // Calculate direction and distance to target var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // If very close to target, select a new one if (distance < 10) { log("Reached target, selecting a new one"); self.selectTarget(); return; } // Calculate target angle var targetAngle = Math.atan2(dy, dx); // Check if rotation is needed var angleDiff = Math.abs(calculateAngleDiff(targetAngle, self.rotation)); // If rotation is needed (more than 0.1 radians difference or flag is set) if (angleDiff > 0.1 || self.needsRotation) { // Only start a new rotation if not already rotating if (!self.isRotating) { log("Rotating to face target"); self.isRotating = true; // Use the updateRotation function with callback to clear isRotating flag self.updateRotation(targetAngle, 600, function () { self.isRotating = false; self.needsRotation = false; log("Rotation complete"); }); } } // If rotation is close enough and we're not currently moving, start movement else if (!self.isMoving) { log("Starting movement to target"); self.isMoving = true; // Calculate duration based on distance var moveSpeed = 0.2; // Adjust this value to control drone speed var duration = distance / moveSpeed; // Start moving toward the target tween(self, { x: self.target.x, y: self.target.y }, { duration: duration, easing: tween.easeInOut, onFinish: function onFinish() { self.isMoving = false; log("Movement complete"); // Don't automatically select a new target, let updateScanning handle it } }); } }; // Handle attacking mode behavior self.updateAttacking = function () { var currentTime = Date.now(); // Fire using separate counter if (currentTime - self.lastFireTime > 1200) { // Fire every 1.2 seconds self.lastFireTime = currentTime; self.fire(); } var dx = knight.x - self.x; var dy = knight.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx); // Drone rotation logic - directly rotate the drone to face the knight // Only do smooth rotation tracking when not already moving to a new position //self.lastRotationTime = currentTime; // Calculate a smoother intermediate angle for natural tracking var currentAngle = self.rotation; var angleDiff = calculateAngleDiff(angle, currentAngle); // Only rotate a portion of the way to the target for smoother tracking // This creates a slight lag effect that looks more natural var partialAngle = currentAngle + angleDiff * 0.3; // Directly set rotation instead of using updateRotation self.rotation = partialAngle; //self.isRotating = false; // No need for callback since we're setting directly // Rotate droneScanBar to point at knight within limits var relativeAngle = calculateAngleDiff(angle, self.rotation); // Clamp the scan bar rotation to the maximum allowed range var maxRotation = Math.PI / 6.5; var clampedRotation = Math.max(-maxRotation, Math.min(maxRotation, relativeAngle)); // Set the scan bar rotation directly self.droneScanBar.rotation = clampedRotation; self.droneScanBar.alpha = 0.1; // First check if knight is still in range - do this every second if (currentTime - self.lastKnightCheckTime > 1000) { self.lastKnightCheckTime = currentTime; // Check if we can still see the knight if (!self.droneScanBar.intersects(knight.boundingBox)) { log("Knight lost! Returning to scanning mode."); self.switchState('scanning'); return; } log("Knight still in range at distance: " + distance); } // Position update logic - if we're not currently moving but need to if (isDied) { log("Knight is dead, returning to scanning mode."); self.switchState('scanning'); return; } if (!self.isMoving && distance > 550) { log("Knight moved - updating drone position"); // Move to a better position self.followKnight(); return; } }; self.followKnight = function () { log("followKnight..."); // Cancel any existing movement tweens tween.stop(self); // Calculate direction to knight var dx = knight.x - self.x; var dy = knight.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx); // Set the movement flag self.isMoving = true; self.isRotating = true; // After rotation is complete, start moving toward knight self.moveTowardKnight(); }; // Function to calculate distance to knight self.distanceToKnight = function () { var dx = knight.x - self.x; var dy = knight.y - self.y; return Math.sqrt(dx * dx + dy * dy); }; self.moveTowardKnight = function () { var dx = knight.x - self.x; var dy = knight.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // If already within the desired distance, stop moving if (distance <= 500) { return; } // Calculate target position that's 500 units away from knight var ratio = (distance - 500) / distance; var targetX = self.x + dx * ratio; var targetY = self.y + dy * ratio; // Move toward the target tween(self, { x: targetX, y: targetY }, { duration: distance * 2, // Speed based on distance easing: tween.linear, onUpdate: function onUpdate() { // Update rotation to continuously face the knight during movement var currentDx = knight.x - self.x; var currentDy = knight.y - self.y; var currentDistance = Math.sqrt(currentDx * currentDx + currentDy * currentDy); // Only update rotation if not very close to the target if (currentDistance > 10) { var currentAngle = Math.atan2(currentDy, currentDx); // Calculate target rotation with a smaller adjustment for smoothness using the utility function var newRotation = self.rotation + calculateAngleDiff(currentAngle, self.rotation) * 0.1; // Create a separate quick tween for the rotation tween(self, { rotation: newRotation }, { duration: 600, // Very short duration for responsive updates easing: tween.linear }); } }, onFinish: function onFinish() { // Check if we're still in attacking mode before continuing to follow if (self.state === 'attacking') { // Check if knight has moved and we need to follow again if (self.distanceToKnight() > 500) { self.moveTowardKnight(); } } } }); }; self.updateRotation = function (targetAngle, maxDuration, callback) { maxDuration = maxDuration || 500; // Default max duration if not specified // Cancel any existing rotation tweens to prevent conflicts tween.stop(self, 'rotation'); // Normalize angles to be between -PI and PI for proper comparison var normalizedCurrentAngle = self.rotation % (2 * Math.PI); if (normalizedCurrentAngle > Math.PI) { normalizedCurrentAngle -= 2 * Math.PI; } if (normalizedCurrentAngle < -Math.PI) { normalizedCurrentAngle += 2 * Math.PI; } // Normalize target angle var normalizedTargetAngle = targetAngle % (2 * Math.PI); if (normalizedTargetAngle > Math.PI) { normalizedTargetAngle -= 2 * Math.PI; } if (normalizedTargetAngle < -Math.PI) { normalizedTargetAngle += 2 * Math.PI; } // Calculate the angle difference using the normalized angles var angleDiff = calculateAngleDiff(normalizedTargetAngle, normalizedCurrentAngle); var absDiff = Math.abs(angleDiff); // If the angle difference is greater than PI, rotate in the shorter direction if (absDiff > Math.PI) { if (angleDiff > 0) { angleDiff -= 2 * Math.PI; } else { angleDiff += 2 * Math.PI; } } // Calculate duration based on angle difference to achieve constant rotation speed // Use radians per second as the speed unit var rotationSpeed = 6; // Radians per second var duration = Math.min(absDiff / rotationSpeed * 1000, maxDuration); // Ensure a minimum duration for very small rotations duration = Math.max(duration, 50); // Calculate the target rotation by adding the angle difference to the current rotation var targetRotation = self.rotation + angleDiff; // Log the rotation parameters for debugging log("Rotating: angle diff=" + absDiff + ", duration=" + duration + "ms"); // Perform the rotation tween tween(self, { rotation: targetRotation }, { duration: duration, easing: tween.easeInOut, onFinish: function onFinish() { log("Rotation complete"); if (callback) { callback(); } } }); }; self.takeHit = function (bullet) { // Play drone-hit sound LK.getSound('drone-hit').play(); // Flash the drone red to indicate damage droneGraphics.tint = 0xff0000; // Red tint // After a short delay, restore the original tint LK.setTimeout(function () { droneGraphics.tint = 0xFFFFFF; // Restore original color }, 200); // Add knockback effect based on bullet direction var knockbackDistance = 50; var knockbackAngle = bullet.rotation; // Calculate potential new position var newX = self.x + Math.cos(knockbackAngle) * knockbackDistance; var newY = self.y + Math.sin(knockbackAngle) * knockbackDistance; // Clamp new position using corners newX = Math.max(corners[0].x, Math.min(newX, corners[3].x)); newY = Math.max(corners[0].y, Math.min(newY, corners[3].y)); // Apply knockback using tween tween(self, { x: newX, y: newY }, { duration: 200, easing: tween.easeOut }); // Log hit for debugging log("Drone hit by attack!"); // Reduce health when hit self.health -= 1; // Check if drone is destroyed if (self.health <= 0) { log("Drone destroyed!"); self.explode(); return; } // After 1 second, rotate the drone toward the knight LK.setTimeout(function () { // Calculate angle to knight var dx = knight.x - self.x; var dy = knight.y - self.y; var angleToKnight = Math.atan2(dy, dx); // Calculate the angle difference for proper duration var normalizedCurrentAngle = self.rotation % (2 * Math.PI); if (normalizedCurrentAngle > Math.PI) { normalizedCurrentAngle -= 2 * Math.PI; } if (normalizedCurrentAngle < -Math.PI) { normalizedCurrentAngle += 2 * Math.PI; } var normalizedTargetAngle = angleToKnight % (2 * Math.PI); if (normalizedTargetAngle > Math.PI) { normalizedTargetAngle -= 2 * Math.PI; } if (normalizedTargetAngle < -Math.PI) { normalizedTargetAngle += 2 * Math.PI; } var angleDiff = calculateAngleDiff(normalizedTargetAngle, normalizedCurrentAngle); if (Math.abs(angleDiff) > Math.PI) { if (angleDiff > 0) { angleDiff -= 2 * Math.PI; } else { angleDiff += 2 * Math.PI; } } // Calculate duration based on angle difference var rotationSpeed = 6; // Radians per second var absDiff = Math.abs(angleDiff); var duration = Math.min(absDiff / rotationSpeed * 1000, 500); duration = Math.max(duration, 50); // Ensure minimum duration // Rotate drone to face knight with calculated duration tween(self, { rotation: self.rotation + angleDiff }, { duration: duration, easing: tween.easeInOut }); // Switch to attacking state if not already attacking if (self.state !== 'attacking') { self.switchState('attacking'); } }, 1000); }; self.explode = function () { // Simply destroy the drone tween.stop(self); // Stop all tweens on the drone var explosion = new Explosion(); explosion.init(self.x, self.y); middlegroundContainer.addChild(explosion); LK.getSound('drone-explode').play(); self.destroy(); droneManager.start(); }; }); var Explosion = Container.expand(function () { var self = Container.call(this); for (var i = 0; i < 30; i++) { var particle = LK.getAsset('explosionParticle', { anchorX: 0.5, anchorY: 0.5 }); var scale = Math.random() * 0.5 + 0.5; // Random scale between 0.5 and 1.0 particle.scale.set(scale); var colors = [0xFF4500, 0xFF6347, 0xFFFFFF, 0xFFFF00, 0x8B0000]; // Array of colors: orange, red, white, yellow, dark red particle.tint = colors[Math.floor(Math.random() * colors.length)]; // Randomly choose a color from the array var angle = Math.random() * Math.PI * 2; // Random direction var speed = Math.random() * 5 + 100; // Random speed between 2 and 7 particle.vx = Math.cos(angle) * speed; particle.vy = Math.sin(angle) * speed; particle.rotation = angle; // Rotate particle in the direction of movement self.addChild(particle); } // Initialize explosion properties self.init = function (x, y) { self.x = x; self.y = y; self.scale.set(0.8); // Start small self.alpha = 1.0; // Fully visible // Animate the explosion var particles = self.children; var duration = 2000; // Duration for particles to move and fade out particles.forEach(function (particle) { tween(particle, { x: particle.x + particle.vx * duration / 1000, y: particle.y + particle.vy * duration / 1000, alpha: 0.0 }, { duration: duration, easing: tween.easeOut }); }); LK.setTimeout(function () { self.destroy(); // Remove explosion after animation }, duration); }; }); var ExplosionKnight = Container.expand(function () { var self = Container.call(this); for (var i = 0; i < 40; i++) { var particle = LK.getAsset('explosionParticle', { anchorX: 0.5, anchorY: 0.5 }); var scale = Math.random() * 0.5 + 0.5; // Random scale between 0.5 and 1.0 particle.scale.set(scale); particle.tint = 0x000000; // Set particle color to black var speed = 100 + Math.random() * 100; // Random speed between 2 and 7 particle.vx = 0; // No horizontal movement particle.vy = -speed; // Negative vertical movement particle.rotation = Math.PI / 2; // Rotate by PI/2 self.addChild(particle); } // Initialize explosion properties self.init = function (x, y) { self.x = x; self.y = y; self.scale.set(0.8); // Start small self.alpha = 1.0; // Fully visible // Animate the explosion var particles = self.children; var duration = 600; // Duration for particles to move and fade out particles.forEach(function (particle) { particle.x += -30 + Math.random() * 60; particle.y += -30 + Math.random() * 60; tween(particle, { x: particle.x + particle.vx * duration / 500, y: particle.y + particle.vy * duration / 500, alpha: 0.0 }, { duration: duration, easing: tween.easeOut }); }); LK.setTimeout(function () { self.destroy(); // Remove explosion after animation }, duration); }; }); var Knight = Container.expand(function () { var self = Container.call(this); self.health = 3; // Initialize health property with a value of 3 // Add boundingBox for collision detection self.boundingBox = LK.getAsset('boundingBox', { anchorX: 0.5, anchorY: 0.5, width: 80, height: 150, y: -50 }); self.addChild(self.boundingBox); self.boundingBox.alpha = isDebug ? 0.5 : 0; // Set to false if you don't want it visible var directionMapping = { 'down-left': 'dir1', 'left': 'dir2', 'up-left': 'dir3', 'up': 'dir4', 'up-right': 'dir5', 'right': 'dir6', 'down-right': 'dir7', 'down': 'dir8' }; // Pre-create a list of assets for each direction var knightAssets = {}; var shadowAssets = {}; // Shadow assets for the knight var knightIdleAssets = {}; // Idle assets for the knight var shadowIdleAssets = {}; // Shadow idle assets for the knight var knightAttackAssets = {}; // Attack assets for the knight var shadowAttackAssets = {}; // Shadow attack assets for the knight var color = 0xFFFFFF; // Original blue / 0xff4d4d; // Red / 0x9aff9a; // Green // Initialize run animation assets for (var dir in directionMapping) { knightAssets[dir] = []; shadowAssets[dir] = []; // Initialize shadow assets array for each direction knightAttackAssets[dir] = []; // Initialize attack assets array for each direction shadowAttackAssets[dir] = []; // Initialize shadow attack assets array for each direction // Load run animation frames (8 frames) for (var i = 1; i <= 8; i++) { var frameNumber = i.toString().padStart(3, '0'); // Create knight sprite knightAssets[dir].push(LK.getAsset('knight-run-' + directionMapping[dir] + '-' + frameNumber, { anchorX: 0.5, anchorY: 0.5, tint: color })); // Create shadow sprite using the same assets but with modifications var shadowSprite = LK.getAsset('knight-run-' + directionMapping[dir] + '-' + frameNumber, { anchorX: 0.5, anchorY: 0.5 }); // Apply shadow properties shadowSprite.alpha = 0.5; shadowSprite.tint = 0x000000; shadowSprite.rotation = Math.PI / 12; // Rotate by 15 degrees (π/12 radians) shadowSprite.scale.y = 0.5; // Flatten the shadow vertically shadowAssets[dir].push(shadowSprite); } // Load attack animation frames (15 frames) for (var i = 1; i <= 15; i++) { var frameNumber = i.toString().padStart(3, '0'); // Create attack sprite knightAttackAssets[dir].push(LK.getAsset('knight-attack-' + directionMapping[dir] + '-' + frameNumber, { anchorX: 0.5, anchorY: 0.5, tint: color })); // Create shadow attack sprite using the same assets but with modifications var shadowAttackSprite = LK.getAsset('knight-attack-' + directionMapping[dir] + '-' + frameNumber, { anchorX: 0.5, anchorY: 0.5 }); // Apply shadow properties shadowAttackSprite.alpha = 0.5; shadowAttackSprite.tint = 0x000000; shadowAttackSprite.rotation = Math.PI / 12; // Rotate by 15 degrees (π/12 radians) shadowAttackSprite.scale.y = 0.5; // Flatten the shadow vertically shadowAttackAssets[dir].push(shadowAttackSprite); } // Initialize idle animation assets (one frame per direction) knightIdleAssets[dir] = LK.getAsset('knight-idle-' + directionMapping[dir] + '-001', { anchorX: 0.5, anchorY: 0.5, tint: color }); // Create shadow for idle animation var shadowIdleSprite = LK.getAsset('knight-idle-' + directionMapping[dir] + '-001', { anchorX: 0.5, anchorY: 0.5 }); // Apply shadow properties shadowIdleSprite.alpha = 0.5; shadowIdleSprite.tint = 0x000000; shadowIdleSprite.rotation = Math.PI / 12; shadowIdleSprite.scale.y = 0.5; shadowIdleAssets[dir] = shadowIdleSprite; } var currentFrame = 0; var animationSpeed = 0.2; // Controls how fast the animation plays var frameCounter = 0; var knightGraphics = knightIdleAssets['down']; // Initialize with the 'down' direction idle asset var shadowGraphics = shadowIdleAssets['down']; // Initialize shadow with the 'down' direction idle asset // Add shadow first (so it appears behind the knight) self.addChild(shadowGraphics); // Then add the knight self.addChild(knightGraphics); knightGraphics.anchor.set(0.5, 0.5); knightGraphics.visible = true; shadowGraphics.visible = true; // Position the shadow slightly offset from the knight var shadowOffsetX = -5; var shadowOffsetY = 20; shadowGraphics.x = shadowOffsetX; shadowGraphics.y = shadowOffsetY; self.lastDirection = 'down'; // Initialize lastDirection to track previous direction self.isMoving = false; // Track if the knight is currently moving self.isAttacking = false; // Track if the knight is currently attacking self.speed = 9; var distanceThreshold = 8; // Define a threshold to avoid small movements var directionThreshold = 5; // Define a threshold for direction changes to avoid shaking self.update = function () { // Don't process movement if attacking if (self.isAttacking) { return; } var dx = target.x - self.x; var dy = target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > distanceThreshold) { // Set knight to moving state if not already moving if (!self.isMoving) { self.isMoving = true; self.switchAsset(self.lastDirection || 'down'); // Update asset to running animation } // Determine the primary direction based on the larger component (dx or dy) if (Math.abs(dx) > Math.abs(dy) + directionThreshold) { // Horizontal movement dominates if (dx > 0) { if (dy > directionThreshold) { self.moveToDirection('down-right'); } else if (dy < -directionThreshold) { self.moveToDirection('up-right'); } else { self.moveToDirection('right'); } } else { if (dy > directionThreshold) { self.moveToDirection('down-left'); } else if (dy < -directionThreshold) { self.moveToDirection('up-left'); } else { self.moveToDirection('left'); } } } else if (Math.abs(dy) > Math.abs(dx) + directionThreshold) { // Vertical movement dominates if (dy > 0) { if (dx > directionThreshold) { self.moveToDirection('down-right'); } else if (dx < -directionThreshold) { self.moveToDirection('down-left'); } else { self.moveToDirection('down'); } } else { if (dx > directionThreshold) { self.moveToDirection('up-right'); } else if (dx < -directionThreshold) { self.moveToDirection('up-left'); } else { self.moveToDirection('up'); } } } else { // The difference between dx and dy is small, use diagonal movement if (dx > 0 && dy > 0) { self.moveToDirection('down-right'); } else if (dx > 0 && dy < 0) { self.moveToDirection('up-right'); } else if (dx < 0 && dy > 0) { self.moveToDirection('down-left'); } else if (dx < 0 && dy < 0) { self.moveToDirection('up-left'); } } } else { // Check if knight is not moving and distance to drone is less than 400 if (!self.isMoving) { if (droneManager && droneManager.drones.length > 0) { var drone = droneManager.drones[0]; // Assuming single drone for simplicity var dx = drone.x - self.x; var dy = drone.y - self.y; var distanceToDrone = Math.sqrt(dx * dx + dy * dy); } if (distanceToDrone < 400) { // Calculate angle to orient knight towards drone var angleToDrone = Math.atan2(dy, dx); var directionToDrone; if (angleToDrone >= -Math.PI / 8 && angleToDrone < Math.PI / 8) { directionToDrone = 'right'; } else if (angleToDrone >= Math.PI / 8 && angleToDrone < 3 * Math.PI / 8) { directionToDrone = 'down-right'; } else if (angleToDrone >= 3 * Math.PI / 8 && angleToDrone < 5 * Math.PI / 8) { directionToDrone = 'down'; } else if (angleToDrone >= 5 * Math.PI / 8 && angleToDrone < 7 * Math.PI / 8) { directionToDrone = 'down-left'; } else if (angleToDrone >= 7 * Math.PI / 8 || angleToDrone < -7 * Math.PI / 8) { directionToDrone = 'left'; } else if (angleToDrone >= -7 * Math.PI / 8 && angleToDrone < -5 * Math.PI / 8) { directionToDrone = 'up-left'; } else if (angleToDrone >= -5 * Math.PI / 8 && angleToDrone < -3 * Math.PI / 8) { directionToDrone = 'up'; } else if (angleToDrone >= -3 * Math.PI / 8 && angleToDrone < -Math.PI / 8) { directionToDrone = 'up-right'; } if (directionToDrone) { self.switchAsset(directionToDrone); } } } // Set knight to idle state if currently moving if (self.isMoving) { self.isMoving = false; self.switchAsset(self.lastDirection || 'down'); // Update asset to idle animation } } // Update knight animation self.updateAnimation(); }; self.switchAsset = function (direction) { if (!knightGraphics) { return; } // If currently attacking, don't switch assets if (self.isAttacking) { return; } // Detach current assets // Hide current assets knightGraphics.visible = false; shadowGraphics.visible = false; // Switch to new assets based on direction and movement state if (self.isMoving) { // Use running animation knightGraphics = knightAssets[direction][currentFrame]; shadowGraphics = shadowAssets[direction][currentFrame]; } else { // Use idle animation knightGraphics = knightIdleAssets[direction]; shadowGraphics = shadowIdleAssets[direction]; } self.addChild(shadowGraphics); self.addChild(knightGraphics); // Show new assets knightGraphics.visible = true; shadowGraphics.visible = true; // Position the shadow slightly offset from the knight shadowGraphics.x = shadowOffsetX; shadowGraphics.y = shadowOffsetY; // Update last direction self.lastDirection = direction; }; self.updateAnimation = function () { // If currently attacking, don't switch assets if (self.isAttacking) { return; } if (self.isMoving) { // Only update animation if moving frameCounter += animationSpeed; if (frameCounter >= 1) { frameCounter = 0; currentFrame = (currentFrame + 1) % 8; // Cycle through 8 frames if (self.lastDirection) { self.switchAsset(self.lastDirection); } } } else if (self.lastDirection) { // Make sure we're showing the idle animation when not moving self.switchAsset(self.lastDirection); } }; self.moveToDirection = function (direction) { if (self.isAttacking) { return; } if (self.lastDirection !== direction) { self.switchAsset(direction); self.lastDirection = direction; } switch (direction) { case 'up': self.y -= self.speed; break; case 'down': self.y += self.speed; break; case 'left': self.x -= self.speed; break; case 'right': self.x += self.speed; break; case 'up-left': self.x -= self.speed / Math.sqrt(2); self.y -= self.speed / Math.sqrt(2); break; case 'up-right': self.x += self.speed / Math.sqrt(2); self.y -= self.speed / Math.sqrt(2); break; case 'down-left': self.x -= self.speed / Math.sqrt(2); self.y += self.speed / Math.sqrt(2); break; case 'down-right': self.x += self.speed / Math.sqrt(2); self.y += self.speed / Math.sqrt(2); break; } }; self.takeHit = function (bullet) { if (isDied) { return; } // Play knight-hit sound LK.getSound('knight-hit').play(); // Flash the knight red to indicate damage self.tint = 0xff0000; // Red tint // After a short delay, restore the original tint LK.setTimeout(function () { self.tint = color; // Restore original color }, 200); // Optional: Add knockback effect based on bullet direction var knockbackDistance = 30; var knockbackAngle = bullet.rotation; // Apply knockback self.x += Math.cos(knockbackAngle) * knockbackDistance; self.y += Math.sin(knockbackAngle) * knockbackDistance; self.health--; if (self.health < 0) { self.dieAnim(); } // Log hit for debugging log("Knight hit by laser beam!"); }; self.dieAnim = function () { // Animate knight tint to black tween(self, { tint: 0x000000 }, { duration: 1000, // 1 second duration easing: tween.easeInOut, onFinish: function onFinish() { if (isDied) { return; } isDied = true; var explosion = new ExplosionKnight(); explosion.init(self.x, self.y); middlegroundContainer.addChild(explosion); LK.getSound('knight-killed').play(); self.destroy(); // Destroy knight after animation LK.setTimeout(function () { LK.showGameOver(); }, 2000); // Call game over after 2 seconds } }); }; self.attack = function () { // Check if already attacking if (self.isAttacking) { return; } // Play knight-attack sound LK.getSound('knight-attack').play(); self.isAttacking = true; log("Knight attacking!"); // Store current direction var direction = self.lastDirection || 'down'; // Get attack animation frames for current direction var attackFrames = knightAttackAssets[direction]; var shadowAttackFrames = shadowAttackAssets[direction]; // Hide current graphics // knightGraphics.visible = false; // shadowGraphics.visible = false; // Add all frames but make them invisible initially for (var i = 0; i < attackFrames.length; i++) { self.addChild(shadowAttackFrames[i]); self.addChild(attackFrames[i]); shadowAttackFrames[i].visible = false; attackFrames[i].visible = false; } // Create attack animation var currentFrame = 0; var attackInterval = LK.setInterval(function () { // Hide previous frame if it exists if (currentFrame > 0) { attackFrames[currentFrame - 1].visible = false; shadowAttackFrames[currentFrame - 1].visible = false; } // Hide current graphics knightGraphics.visible = false; shadowGraphics.visible = false; // Show current frame shadowAttackFrames[currentFrame].visible = true; attackFrames[currentFrame].visible = true; // Check for collision with drones (middle of the attack animation) if (currentFrame === Math.floor(attackFrames.length / 2)) { // Calculate attack range and direction var attackRange = 100; // Range of the knight's attack var attackDirection = direction; var attackAngle; // Determine attack angle based on direction if (attackDirection === 'right') { attackAngle = 0; } else if (attackDirection === 'down-right') { attackAngle = Math.PI / 4; } else if (attackDirection === 'down') { attackAngle = Math.PI / 2; } else if (attackDirection === 'down-left') { attackAngle = 3 * Math.PI / 4; } else if (attackDirection === 'left') { attackAngle = Math.PI; } else if (attackDirection === 'up-left') { attackAngle = 5 * Math.PI / 4; } else if (attackDirection === 'up') { attackAngle = 3 * Math.PI / 2; } else if (attackDirection === 'up-right') { attackAngle = 7 * Math.PI / 4; } // Calculate attack point in front of the knight var attackX = self.x + Math.cos(attackAngle) * attackRange; var attackY = self.y + Math.sin(attackAngle) * attackRange; // Check if any drone is within attack range for (var i = 0; i < droneManager.drones.length; i++) { var drone = droneManager.drones[i]; var dx = drone.x - attackX; var dy = drone.y - attackY; var distance = Math.sqrt(dx * dx + dy * dy); // If drone is within attack range, call its takeHit method if (distance < 120) { // Adjust hit radius as needed drone.takeHit({ rotation: attackAngle // Pass attack angle for knockback direction }); } } } // Move to next frame currentFrame++; // Check if animation is complete if (currentFrame >= attackFrames.length) { // Clear interval LK.clearInterval(attackInterval); // Hide all attack frames for (var i = 0; i < attackFrames.length; i++) { attackFrames[i].visible = false; shadowAttackFrames[i].visible = false; } // Show regular graphics again knightGraphics.visible = true; shadowGraphics.visible = true; // Reset attacking flag self.isAttacking = false; // Reset the global target position to knight's current position target.x = self.x; target.y = self.y; } }, 50); // 50ms per frame for a fast attack animation }; }); var LaserBeam = Container.expand(function () { var self = Container.call(this); var laserBeamGraphics = LK.getAsset('droneLaserBeam', { anchorX: 0.5, anchorY: 0.5, blendMode: 1 }); self.addChild(laserBeamGraphics); self.speed = 1500; // Speed of the laser beam in pixels per second self.fire = function (x, y, rotation) { self.x = x; self.y = y; self.rotation = rotation; middlegroundContainer.addChild(self); // Calculate the distance the laser beam will travel var distance = 3000; // Calculate the duration based on speed var duration = distance / self.speed * 1000; // Convert to milliseconds // Tween the laser beam to move forward tween(self, { x: self.x + Math.cos(self.rotation) * distance, y: self.y + Math.sin(self.rotation) * distance }, { duration: duration, easing: tween.linear, onFinish: function onFinish() { self.destroy(); } }); }; self.update = function () { if (self.intersects(knight.boundingBox)) { knight.takeHit(self); self.destroy(); } }; }); var Room = Container.expand(function () { var self = Container.call(this); var background = LK.getAsset('backgroundWalls', { anchorX: 0.5, anchorY: 0.5 }); self.addChild(background); self.x = 2048 / 2; self.y = 2732 / 2; }); var Target = Container.expand(function () { var self = Container.call(this); self.x = 0; self.y = 0; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 //Init game with black background }); /**** * Game Code ****/ // Utility function to calculate the angle difference function calculateAngleDiff(targetAngle, currentAngle) { var angleDiff = targetAngle - currentAngle; // Normalize angle difference to [-PI, PI] while (angleDiff > Math.PI) { angleDiff -= 2 * Math.PI; } while (angleDiff < -Math.PI) { angleDiff += 2 * Math.PI; } return angleDiff; } //<Write entity 'classes' with empty functions for important behavior here> //<Write imports for supported plugins here> //<Assets used in the game will automatically appear here> //<Write game logic code here, including initializing arrays and variables> var DroneManager = function DroneManager() { var self = this; self.drones = []; self.start = function () { // Clear previous drones from the scene and the drones array self.drones.forEach(function (drone) { drone.destroy(); }); self.drones = []; // Spawn a drone in a random corner //var currentTargetIndex = Math.floor(Math.random() * corners.length); //var cornerPosition = corners[currentTargetIndex]; // Create the drone var drone = new Drone(); // Explicitly set position before adding to the scene drone.x = Math.random() < 0.5 ? -1024 : 3072; drone.y = 1366; // Store the current target index //drone.currentTargetIndex = currentTargetIndex; // Set the initial target to the current corner position drone.target = { x: 1024, y: 1366 }; // Add to container and array middlegroundContainer.addChild(drone); self.drones.push(drone); // This ensures all properties are set before tweening begins LK.setTimeout(function () { drone.init(); }, 100); }; }; /**** * Global variables ****/ var isDebug = false; var isDied = false; // Global flag to track if the knight has died var gameStarted = false; var droneManager = new DroneManager(); var backgroundContainer; var middlegroundContainer; var foregroundContainer; var knight; var background; // Declare a global variable for the background var target; var punchButton; var runSoundInterval; var borderOffset = 380; var corners = [{ x: borderOffset, y: borderOffset }, { x: 2048 - borderOffset, y: borderOffset }, { x: borderOffset, y: 2732 - borderOffset }, { x: 2048 - borderOffset, y: 2732 - borderOffset }]; function log() { if (isDebug) { console.log.apply(console, arguments); } } function initializeGame() { // Create containers backgroundContainer = new Container(); middlegroundContainer = new Container(); foregroundContainer = new Container(); // Add containers to game game.addChild(backgroundContainer); game.addChild(middlegroundContainer); game.addChild(foregroundContainer); // Initialize background background = new Background(); // Use Background class instead of LK.getAsset backgroundContainer.addChild(background); // Initialize room var room = new Room(); backgroundContainer.addChild(room); // Initialize knight knight = new Knight(); knight.x = 2048 / 2; knight.y = 2732 / 2; middlegroundContainer.addChild(knight); // Initialize target target = new Target(); target.x = knight.x; target.y = knight.y; foregroundContainer.addChild(target); // Initialize punch button punchButton = new ButtonPunch(); punchButton.x = 2048 - 220; punchButton.y = 220; foregroundContainer.addChild(punchButton); // Initialize sound runSoundInterval = LK.setInterval(playRunSound, 350); droneManager = new DroneManager(); LK.setTimeout(function () { gameStarted = true; droneManager.start(); }, 1000); } function playRunSound() { if (knight.isMoving) { LK.getSound('knight-run').play(); } } initializeGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Background = Container.expand(function () {
var self = Container.call(this);
var backgroundGraphics = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(backgroundGraphics);
self.x = 2048 / 2;
self.y = 2732 / 2;
// Move the down handler to the Background class
self.down = function (x, y, obj) {
var game_position = game.toLocal(obj.global);
target.x = Math.max(corners[0].x, Math.min(game_position.x, corners[3].x));
target.y = Math.max(corners[0].y, Math.min(game_position.y, corners[3].y));
};
});
var ButtonPunch = Container.expand(function () {
var self = Container.call(this);
var buttonGraphics = LK.getAsset('buttonPunch', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(buttonGraphics);
// Add click/tap handler
self.down = function () {
// Trigger knight attack when button is pressed
if (knight && !knight.isAttacking) {
knight.attack();
}
};
});
// Import the tween plugin
var Drone = Container.expand(function () {
var self = Container.call(this);
self.health = 3; // Initialize health property with a value of 3
var droneGraphics = LK.getAsset('drone', {
anchorX: 0.5,
anchorY: 0.5
});
var shadowDrone = LK.getAsset('drone', {
anchorX: 0.5,
anchorY: 0.5
});
self.shadowOffset = {
x: -30,
y: 40
}; // Define shadow offset property
shadowDrone.alpha = 0.5;
shadowDrone.tint = 0x000000;
shadowDrone.x = -10;
shadowDrone.y = 40;
self.addChild(shadowDrone);
self.addChild(droneGraphics);
self.droneScanLaser = LK.getAsset('droneScanLaser', {
anchorX: 0.5,
anchorY: 0.5
});
self.droneScanBar = LK.getAsset('droneScanBar', {
anchorX: 0,
anchorY: 0.5
});
self.droneScanLaser.visible = true; // Always visible
self.droneScanLaser.x = droneGraphics.width * 2;
self.droneScanLaser.alpha = 0.3; // Set alpha to 0.3
self.addChild(self.droneScanLaser);
self.droneScanBar.x = droneGraphics.width / 2; // droneScanLaser.x;
self.droneScanBar.blendMode = 3;
self.droneScanBar.y = self.droneScanLaser.y;
self.addChild(self.droneScanBar);
function loopScanLaserTint() {
if (self.state !== 'scanning') {
return;
}
tween(self.droneScanLaser, {
tint: 0x00FF00
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self.droneScanLaser, {
tint: 0x00AA00
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: loopScanLaserTint // Recursively call to loop
});
}
});
}
function animateScanBar() {
if (self.state !== 'scanning') {
return;
}
tween(self.droneScanBar, {
alpha: 0.3,
rotation: Math.PI / 6.5
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self.droneScanBar, {
alpha: 0.6,
rotation: -Math.PI / 6.5
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: animateScanBar // Recursively call to loop
}); // Ensure the tween is started
}
}); // Ensure the tween is started
}
self.speed = 5;
self.state = ''; // Possible states: scanning, attacking
// Add debug text to track drone state
self.debugText = new Text2(self.state, {
size: 50,
fill: 0xFFFFFF
});
self.debugText.x = -droneGraphics.width / 2;
self.debugText.y = -droneGraphics.height / 2 - 50;
self.debugText.visible = isDebug; // Set visibility based on debug mode
self.addChild(self.debugText);
self.fireRange = 300; // Example fire range value
self.currentTargetIndex = -1;
self.target = {
x: 0,
y: 0
};
self.lastKnightCheckTime = 0; // Track when we last checked for knight intersection
self.lastFireTime = 0; // Track when we last fired at the knight
self.lastRotationTime = 0; // Track the last time we updated rotation
self.isRotating = false; // Flag to track if we're currently rotating
self.isMoving = false; // Flag to track if we're currently moving
self.needsRotation = false; // Flag to track if we need to rotate before moving
self.justSwitchedFromAttacking = false; // Flag to track if we just switched from attacking mode
// Function to select a new random target
self.selectTarget = function () {
log("selectTarget...:");
var newTargetIndex;
do {
newTargetIndex = Math.floor(Math.random() * corners.length);
} while (newTargetIndex === self.currentTargetIndex);
self.currentTargetIndex = newTargetIndex;
self.target = corners[newTargetIndex];
log("New target selected at position:", self.target.x, self.target.y);
// Set a flag to indicate the target is new and needs rotation first
self.needsRotation = true;
// Return the new target (not used currently but could be useful)
return self.target;
};
// Function to fire a laser beam
self.fire = function () {
log("Drone firing laser beam...");
LK.getSound('drone-beam').play();
var laserBeam = new LaserBeam();
laserBeam.fire(self.x, self.y, self.rotation);
};
// Function to handle state transitions
self.switchState = function (newState) {
// Don't do anything if the state isn't changing
if (self.state === newState) {
return;
}
log("Switching drone state from " + self.state + " to " + newState);
// Stop any existing tweens to ensure clean state transition
tween.stop(self);
// Clear scan sound interval if it exists
if (self.scanSoundInterval) {
LK.clearInterval(self.scanSoundInterval);
self.scanSoundInterval = null;
}
// Reset movement flags
self.isRotating = false;
self.isMoving = false;
self.justSwitchedFromAttacking = newState === 'scanning' && self.state === 'attacking';
// Update the state
self.state = newState;
self.debugText.setText(self.state); // Update debug text with current state
// Handle entry actions for new state
if (newState === 'scanning') {
// Play drone-scan sound
self.scanSoundInterval = LK.setInterval(function () {
LK.getSound('drone-scan').play();
}, 1000);
// Entering scanning state
self.debugText.tint = 0xFFFFFF; // Reset tint to white for other states
self.droneScanLaser.tint = 0x00FF00; // Reset tint to green for scanning mode
// Clear current target and set flag to select a new one
//self.target = null;
//self.needsRotation = true;
animateScanBar();
// Select a new target (this will be handled in updateScanning on next update)
} else if (newState === 'attacking') {
// Entering attacking state
self.debugText.tint = 0xFFA500; // Change tint to orange when attacking
self.droneScanLaser.tint = 0xFFA500; // Change tint to orange when attacking
// Start following the knight
self.followKnight();
}
};
self.init = function () {
log("Drone initialized at position:", self.x, self.y);
// Reset state flags
self.isRotating = false;
self.isMoving = false;
self.needsRotation = true;
// Start the animations
animateScanBar();
loopScanLaserTint();
// Switch to scanning state to begin patrol
self.switchState('scanning');
};
self.update = function () {
if (self.state === 'scanning') {
self.updateScanning();
} else if (self.state === 'attacking') {
self.updateAttacking();
}
// Update shadow position
shadowDrone.x = self.shadowOffset.x * Math.cos(self.rotation);
shadowDrone.y = self.shadowOffset.y * Math.sin(self.rotation);
// Update droneScanBar width based on its rotation
self.droneScanBar.width = 620 * (1 + 0.33 * Math.abs(Math.sin(self.droneScanBar.rotation)));
};
// Function to perform a full 360° scan
self.performFullScan = function (callback) {
// Calculate duration based on a consistent rotation speed
var scanRotationSpeed = 1.0; // Radians per second (slower for scanning)
var fullRotationDuration = Math.PI * 2 / scanRotationSpeed * 1000; // ms
// Store current rotation
var startRotation = self.rotation;
// Mark that we're rotating to prevent updateScanning from starting other rotations
self.isRotating = true;
log("Performing 360° scan");
// Perform a full 360° rotation
tween(self, {
rotation: startRotation + Math.PI * 2 // Full 360° rotation
}, {
duration: fullRotationDuration,
easing: tween.linear,
// Linear easing for constant speed
onFinish: function onFinish() {
// Clear rotation flag when done
self.isRotating = false;
log("Full scan complete");
// Call callback if provided
if (callback) {
callback();
}
}
});
};
// Handle scanning mode behavior
self.updateScanning = function () {
// Check for intersection with knight
if (!isDied && self.droneScanBar.intersects(knight.boundingBox)) {
self.switchState('attacking');
log("Drone switched to attacking mode!");
return;
}
// If no target is set, select one
if (!self.target) {
log("No target set, selecting a new one");
// If we just switched from attacking to scanning mode, do a 360° scan first
if (self.justSwitchedFromAttacking) {
self.justSwitchedFromAttacking = false;
// Perform full scan before selecting a new target
self.performFullScan(function () {
// After scan is complete, select a new target
self.selectTarget();
});
return;
}
// Otherwise just select a new target
self.selectTarget();
return;
}
// Calculate direction and distance to target
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If very close to target, select a new one
if (distance < 10) {
log("Reached target, selecting a new one");
self.selectTarget();
return;
}
// Calculate target angle
var targetAngle = Math.atan2(dy, dx);
// Check if rotation is needed
var angleDiff = Math.abs(calculateAngleDiff(targetAngle, self.rotation));
// If rotation is needed (more than 0.1 radians difference or flag is set)
if (angleDiff > 0.1 || self.needsRotation) {
// Only start a new rotation if not already rotating
if (!self.isRotating) {
log("Rotating to face target");
self.isRotating = true;
// Use the updateRotation function with callback to clear isRotating flag
self.updateRotation(targetAngle, 600, function () {
self.isRotating = false;
self.needsRotation = false;
log("Rotation complete");
});
}
}
// If rotation is close enough and we're not currently moving, start movement
else if (!self.isMoving) {
log("Starting movement to target");
self.isMoving = true;
// Calculate duration based on distance
var moveSpeed = 0.2; // Adjust this value to control drone speed
var duration = distance / moveSpeed;
// Start moving toward the target
tween(self, {
x: self.target.x,
y: self.target.y
}, {
duration: duration,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.isMoving = false;
log("Movement complete");
// Don't automatically select a new target, let updateScanning handle it
}
});
}
};
// Handle attacking mode behavior
self.updateAttacking = function () {
var currentTime = Date.now();
// Fire using separate counter
if (currentTime - self.lastFireTime > 1200) {
// Fire every 1.2 seconds
self.lastFireTime = currentTime;
self.fire();
}
var dx = knight.x - self.x;
var dy = knight.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
// Drone rotation logic - directly rotate the drone to face the knight
// Only do smooth rotation tracking when not already moving to a new position
//self.lastRotationTime = currentTime;
// Calculate a smoother intermediate angle for natural tracking
var currentAngle = self.rotation;
var angleDiff = calculateAngleDiff(angle, currentAngle);
// Only rotate a portion of the way to the target for smoother tracking
// This creates a slight lag effect that looks more natural
var partialAngle = currentAngle + angleDiff * 0.3;
// Directly set rotation instead of using updateRotation
self.rotation = partialAngle;
//self.isRotating = false; // No need for callback since we're setting directly
// Rotate droneScanBar to point at knight within limits
var relativeAngle = calculateAngleDiff(angle, self.rotation);
// Clamp the scan bar rotation to the maximum allowed range
var maxRotation = Math.PI / 6.5;
var clampedRotation = Math.max(-maxRotation, Math.min(maxRotation, relativeAngle));
// Set the scan bar rotation directly
self.droneScanBar.rotation = clampedRotation;
self.droneScanBar.alpha = 0.1;
// First check if knight is still in range - do this every second
if (currentTime - self.lastKnightCheckTime > 1000) {
self.lastKnightCheckTime = currentTime;
// Check if we can still see the knight
if (!self.droneScanBar.intersects(knight.boundingBox)) {
log("Knight lost! Returning to scanning mode.");
self.switchState('scanning');
return;
}
log("Knight still in range at distance: " + distance);
}
// Position update logic - if we're not currently moving but need to
if (isDied) {
log("Knight is dead, returning to scanning mode.");
self.switchState('scanning');
return;
}
if (!self.isMoving && distance > 550) {
log("Knight moved - updating drone position");
// Move to a better position
self.followKnight();
return;
}
};
self.followKnight = function () {
log("followKnight...");
// Cancel any existing movement tweens
tween.stop(self);
// Calculate direction to knight
var dx = knight.x - self.x;
var dy = knight.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
// Set the movement flag
self.isMoving = true;
self.isRotating = true;
// After rotation is complete, start moving toward knight
self.moveTowardKnight();
};
// Function to calculate distance to knight
self.distanceToKnight = function () {
var dx = knight.x - self.x;
var dy = knight.y - self.y;
return Math.sqrt(dx * dx + dy * dy);
};
self.moveTowardKnight = function () {
var dx = knight.x - self.x;
var dy = knight.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If already within the desired distance, stop moving
if (distance <= 500) {
return;
}
// Calculate target position that's 500 units away from knight
var ratio = (distance - 500) / distance;
var targetX = self.x + dx * ratio;
var targetY = self.y + dy * ratio;
// Move toward the target
tween(self, {
x: targetX,
y: targetY
}, {
duration: distance * 2,
// Speed based on distance
easing: tween.linear,
onUpdate: function onUpdate() {
// Update rotation to continuously face the knight during movement
var currentDx = knight.x - self.x;
var currentDy = knight.y - self.y;
var currentDistance = Math.sqrt(currentDx * currentDx + currentDy * currentDy);
// Only update rotation if not very close to the target
if (currentDistance > 10) {
var currentAngle = Math.atan2(currentDy, currentDx);
// Calculate target rotation with a smaller adjustment for smoothness using the utility function
var newRotation = self.rotation + calculateAngleDiff(currentAngle, self.rotation) * 0.1;
// Create a separate quick tween for the rotation
tween(self, {
rotation: newRotation
}, {
duration: 600,
// Very short duration for responsive updates
easing: tween.linear
});
}
},
onFinish: function onFinish() {
// Check if we're still in attacking mode before continuing to follow
if (self.state === 'attacking') {
// Check if knight has moved and we need to follow again
if (self.distanceToKnight() > 500) {
self.moveTowardKnight();
}
}
}
});
};
self.updateRotation = function (targetAngle, maxDuration, callback) {
maxDuration = maxDuration || 500; // Default max duration if not specified
// Cancel any existing rotation tweens to prevent conflicts
tween.stop(self, 'rotation');
// Normalize angles to be between -PI and PI for proper comparison
var normalizedCurrentAngle = self.rotation % (2 * Math.PI);
if (normalizedCurrentAngle > Math.PI) {
normalizedCurrentAngle -= 2 * Math.PI;
}
if (normalizedCurrentAngle < -Math.PI) {
normalizedCurrentAngle += 2 * Math.PI;
}
// Normalize target angle
var normalizedTargetAngle = targetAngle % (2 * Math.PI);
if (normalizedTargetAngle > Math.PI) {
normalizedTargetAngle -= 2 * Math.PI;
}
if (normalizedTargetAngle < -Math.PI) {
normalizedTargetAngle += 2 * Math.PI;
}
// Calculate the angle difference using the normalized angles
var angleDiff = calculateAngleDiff(normalizedTargetAngle, normalizedCurrentAngle);
var absDiff = Math.abs(angleDiff);
// If the angle difference is greater than PI, rotate in the shorter direction
if (absDiff > Math.PI) {
if (angleDiff > 0) {
angleDiff -= 2 * Math.PI;
} else {
angleDiff += 2 * Math.PI;
}
}
// Calculate duration based on angle difference to achieve constant rotation speed
// Use radians per second as the speed unit
var rotationSpeed = 6; // Radians per second
var duration = Math.min(absDiff / rotationSpeed * 1000, maxDuration);
// Ensure a minimum duration for very small rotations
duration = Math.max(duration, 50);
// Calculate the target rotation by adding the angle difference to the current rotation
var targetRotation = self.rotation + angleDiff;
// Log the rotation parameters for debugging
log("Rotating: angle diff=" + absDiff + ", duration=" + duration + "ms");
// Perform the rotation tween
tween(self, {
rotation: targetRotation
}, {
duration: duration,
easing: tween.easeInOut,
onFinish: function onFinish() {
log("Rotation complete");
if (callback) {
callback();
}
}
});
};
self.takeHit = function (bullet) {
// Play drone-hit sound
LK.getSound('drone-hit').play();
// Flash the drone red to indicate damage
droneGraphics.tint = 0xff0000; // Red tint
// After a short delay, restore the original tint
LK.setTimeout(function () {
droneGraphics.tint = 0xFFFFFF; // Restore original color
}, 200);
// Add knockback effect based on bullet direction
var knockbackDistance = 50;
var knockbackAngle = bullet.rotation;
// Calculate potential new position
var newX = self.x + Math.cos(knockbackAngle) * knockbackDistance;
var newY = self.y + Math.sin(knockbackAngle) * knockbackDistance;
// Clamp new position using corners
newX = Math.max(corners[0].x, Math.min(newX, corners[3].x));
newY = Math.max(corners[0].y, Math.min(newY, corners[3].y));
// Apply knockback using tween
tween(self, {
x: newX,
y: newY
}, {
duration: 200,
easing: tween.easeOut
});
// Log hit for debugging
log("Drone hit by attack!");
// Reduce health when hit
self.health -= 1;
// Check if drone is destroyed
if (self.health <= 0) {
log("Drone destroyed!");
self.explode();
return;
}
// After 1 second, rotate the drone toward the knight
LK.setTimeout(function () {
// Calculate angle to knight
var dx = knight.x - self.x;
var dy = knight.y - self.y;
var angleToKnight = Math.atan2(dy, dx);
// Calculate the angle difference for proper duration
var normalizedCurrentAngle = self.rotation % (2 * Math.PI);
if (normalizedCurrentAngle > Math.PI) {
normalizedCurrentAngle -= 2 * Math.PI;
}
if (normalizedCurrentAngle < -Math.PI) {
normalizedCurrentAngle += 2 * Math.PI;
}
var normalizedTargetAngle = angleToKnight % (2 * Math.PI);
if (normalizedTargetAngle > Math.PI) {
normalizedTargetAngle -= 2 * Math.PI;
}
if (normalizedTargetAngle < -Math.PI) {
normalizedTargetAngle += 2 * Math.PI;
}
var angleDiff = calculateAngleDiff(normalizedTargetAngle, normalizedCurrentAngle);
if (Math.abs(angleDiff) > Math.PI) {
if (angleDiff > 0) {
angleDiff -= 2 * Math.PI;
} else {
angleDiff += 2 * Math.PI;
}
}
// Calculate duration based on angle difference
var rotationSpeed = 6; // Radians per second
var absDiff = Math.abs(angleDiff);
var duration = Math.min(absDiff / rotationSpeed * 1000, 500);
duration = Math.max(duration, 50); // Ensure minimum duration
// Rotate drone to face knight with calculated duration
tween(self, {
rotation: self.rotation + angleDiff
}, {
duration: duration,
easing: tween.easeInOut
});
// Switch to attacking state if not already attacking
if (self.state !== 'attacking') {
self.switchState('attacking');
}
}, 1000);
};
self.explode = function () {
// Simply destroy the drone
tween.stop(self); // Stop all tweens on the drone
var explosion = new Explosion();
explosion.init(self.x, self.y);
middlegroundContainer.addChild(explosion);
LK.getSound('drone-explode').play();
self.destroy();
droneManager.start();
};
});
var Explosion = Container.expand(function () {
var self = Container.call(this);
for (var i = 0; i < 30; i++) {
var particle = LK.getAsset('explosionParticle', {
anchorX: 0.5,
anchorY: 0.5
});
var scale = Math.random() * 0.5 + 0.5; // Random scale between 0.5 and 1.0
particle.scale.set(scale);
var colors = [0xFF4500, 0xFF6347, 0xFFFFFF, 0xFFFF00, 0x8B0000]; // Array of colors: orange, red, white, yellow, dark red
particle.tint = colors[Math.floor(Math.random() * colors.length)]; // Randomly choose a color from the array
var angle = Math.random() * Math.PI * 2; // Random direction
var speed = Math.random() * 5 + 100; // Random speed between 2 and 7
particle.vx = Math.cos(angle) * speed;
particle.vy = Math.sin(angle) * speed;
particle.rotation = angle; // Rotate particle in the direction of movement
self.addChild(particle);
}
// Initialize explosion properties
self.init = function (x, y) {
self.x = x;
self.y = y;
self.scale.set(0.8); // Start small
self.alpha = 1.0; // Fully visible
// Animate the explosion
var particles = self.children;
var duration = 2000; // Duration for particles to move and fade out
particles.forEach(function (particle) {
tween(particle, {
x: particle.x + particle.vx * duration / 1000,
y: particle.y + particle.vy * duration / 1000,
alpha: 0.0
}, {
duration: duration,
easing: tween.easeOut
});
});
LK.setTimeout(function () {
self.destroy(); // Remove explosion after animation
}, duration);
};
});
var ExplosionKnight = Container.expand(function () {
var self = Container.call(this);
for (var i = 0; i < 40; i++) {
var particle = LK.getAsset('explosionParticle', {
anchorX: 0.5,
anchorY: 0.5
});
var scale = Math.random() * 0.5 + 0.5; // Random scale between 0.5 and 1.0
particle.scale.set(scale);
particle.tint = 0x000000; // Set particle color to black
var speed = 100 + Math.random() * 100; // Random speed between 2 and 7
particle.vx = 0; // No horizontal movement
particle.vy = -speed; // Negative vertical movement
particle.rotation = Math.PI / 2; // Rotate by PI/2
self.addChild(particle);
}
// Initialize explosion properties
self.init = function (x, y) {
self.x = x;
self.y = y;
self.scale.set(0.8); // Start small
self.alpha = 1.0; // Fully visible
// Animate the explosion
var particles = self.children;
var duration = 600; // Duration for particles to move and fade out
particles.forEach(function (particle) {
particle.x += -30 + Math.random() * 60;
particle.y += -30 + Math.random() * 60;
tween(particle, {
x: particle.x + particle.vx * duration / 500,
y: particle.y + particle.vy * duration / 500,
alpha: 0.0
}, {
duration: duration,
easing: tween.easeOut
});
});
LK.setTimeout(function () {
self.destroy(); // Remove explosion after animation
}, duration);
};
});
var Knight = Container.expand(function () {
var self = Container.call(this);
self.health = 3; // Initialize health property with a value of 3
// Add boundingBox for collision detection
self.boundingBox = LK.getAsset('boundingBox', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 150,
y: -50
});
self.addChild(self.boundingBox);
self.boundingBox.alpha = isDebug ? 0.5 : 0; // Set to false if you don't want it visible
var directionMapping = {
'down-left': 'dir1',
'left': 'dir2',
'up-left': 'dir3',
'up': 'dir4',
'up-right': 'dir5',
'right': 'dir6',
'down-right': 'dir7',
'down': 'dir8'
};
// Pre-create a list of assets for each direction
var knightAssets = {};
var shadowAssets = {}; // Shadow assets for the knight
var knightIdleAssets = {}; // Idle assets for the knight
var shadowIdleAssets = {}; // Shadow idle assets for the knight
var knightAttackAssets = {}; // Attack assets for the knight
var shadowAttackAssets = {}; // Shadow attack assets for the knight
var color = 0xFFFFFF; // Original blue / 0xff4d4d; // Red / 0x9aff9a; // Green
// Initialize run animation assets
for (var dir in directionMapping) {
knightAssets[dir] = [];
shadowAssets[dir] = []; // Initialize shadow assets array for each direction
knightAttackAssets[dir] = []; // Initialize attack assets array for each direction
shadowAttackAssets[dir] = []; // Initialize shadow attack assets array for each direction
// Load run animation frames (8 frames)
for (var i = 1; i <= 8; i++) {
var frameNumber = i.toString().padStart(3, '0');
// Create knight sprite
knightAssets[dir].push(LK.getAsset('knight-run-' + directionMapping[dir] + '-' + frameNumber, {
anchorX: 0.5,
anchorY: 0.5,
tint: color
}));
// Create shadow sprite using the same assets but with modifications
var shadowSprite = LK.getAsset('knight-run-' + directionMapping[dir] + '-' + frameNumber, {
anchorX: 0.5,
anchorY: 0.5
});
// Apply shadow properties
shadowSprite.alpha = 0.5;
shadowSprite.tint = 0x000000;
shadowSprite.rotation = Math.PI / 12; // Rotate by 15 degrees (π/12 radians)
shadowSprite.scale.y = 0.5; // Flatten the shadow vertically
shadowAssets[dir].push(shadowSprite);
}
// Load attack animation frames (15 frames)
for (var i = 1; i <= 15; i++) {
var frameNumber = i.toString().padStart(3, '0');
// Create attack sprite
knightAttackAssets[dir].push(LK.getAsset('knight-attack-' + directionMapping[dir] + '-' + frameNumber, {
anchorX: 0.5,
anchorY: 0.5,
tint: color
}));
// Create shadow attack sprite using the same assets but with modifications
var shadowAttackSprite = LK.getAsset('knight-attack-' + directionMapping[dir] + '-' + frameNumber, {
anchorX: 0.5,
anchorY: 0.5
});
// Apply shadow properties
shadowAttackSprite.alpha = 0.5;
shadowAttackSprite.tint = 0x000000;
shadowAttackSprite.rotation = Math.PI / 12; // Rotate by 15 degrees (π/12 radians)
shadowAttackSprite.scale.y = 0.5; // Flatten the shadow vertically
shadowAttackAssets[dir].push(shadowAttackSprite);
}
// Initialize idle animation assets (one frame per direction)
knightIdleAssets[dir] = LK.getAsset('knight-idle-' + directionMapping[dir] + '-001', {
anchorX: 0.5,
anchorY: 0.5,
tint: color
});
// Create shadow for idle animation
var shadowIdleSprite = LK.getAsset('knight-idle-' + directionMapping[dir] + '-001', {
anchorX: 0.5,
anchorY: 0.5
});
// Apply shadow properties
shadowIdleSprite.alpha = 0.5;
shadowIdleSprite.tint = 0x000000;
shadowIdleSprite.rotation = Math.PI / 12;
shadowIdleSprite.scale.y = 0.5;
shadowIdleAssets[dir] = shadowIdleSprite;
}
var currentFrame = 0;
var animationSpeed = 0.2; // Controls how fast the animation plays
var frameCounter = 0;
var knightGraphics = knightIdleAssets['down']; // Initialize with the 'down' direction idle asset
var shadowGraphics = shadowIdleAssets['down']; // Initialize shadow with the 'down' direction idle asset
// Add shadow first (so it appears behind the knight)
self.addChild(shadowGraphics);
// Then add the knight
self.addChild(knightGraphics);
knightGraphics.anchor.set(0.5, 0.5);
knightGraphics.visible = true;
shadowGraphics.visible = true;
// Position the shadow slightly offset from the knight
var shadowOffsetX = -5;
var shadowOffsetY = 20;
shadowGraphics.x = shadowOffsetX;
shadowGraphics.y = shadowOffsetY;
self.lastDirection = 'down'; // Initialize lastDirection to track previous direction
self.isMoving = false; // Track if the knight is currently moving
self.isAttacking = false; // Track if the knight is currently attacking
self.speed = 9;
var distanceThreshold = 8; // Define a threshold to avoid small movements
var directionThreshold = 5; // Define a threshold for direction changes to avoid shaking
self.update = function () {
// Don't process movement if attacking
if (self.isAttacking) {
return;
}
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > distanceThreshold) {
// Set knight to moving state if not already moving
if (!self.isMoving) {
self.isMoving = true;
self.switchAsset(self.lastDirection || 'down'); // Update asset to running animation
}
// Determine the primary direction based on the larger component (dx or dy)
if (Math.abs(dx) > Math.abs(dy) + directionThreshold) {
// Horizontal movement dominates
if (dx > 0) {
if (dy > directionThreshold) {
self.moveToDirection('down-right');
} else if (dy < -directionThreshold) {
self.moveToDirection('up-right');
} else {
self.moveToDirection('right');
}
} else {
if (dy > directionThreshold) {
self.moveToDirection('down-left');
} else if (dy < -directionThreshold) {
self.moveToDirection('up-left');
} else {
self.moveToDirection('left');
}
}
} else if (Math.abs(dy) > Math.abs(dx) + directionThreshold) {
// Vertical movement dominates
if (dy > 0) {
if (dx > directionThreshold) {
self.moveToDirection('down-right');
} else if (dx < -directionThreshold) {
self.moveToDirection('down-left');
} else {
self.moveToDirection('down');
}
} else {
if (dx > directionThreshold) {
self.moveToDirection('up-right');
} else if (dx < -directionThreshold) {
self.moveToDirection('up-left');
} else {
self.moveToDirection('up');
}
}
} else {
// The difference between dx and dy is small, use diagonal movement
if (dx > 0 && dy > 0) {
self.moveToDirection('down-right');
} else if (dx > 0 && dy < 0) {
self.moveToDirection('up-right');
} else if (dx < 0 && dy > 0) {
self.moveToDirection('down-left');
} else if (dx < 0 && dy < 0) {
self.moveToDirection('up-left');
}
}
} else {
// Check if knight is not moving and distance to drone is less than 400
if (!self.isMoving) {
if (droneManager && droneManager.drones.length > 0) {
var drone = droneManager.drones[0]; // Assuming single drone for simplicity
var dx = drone.x - self.x;
var dy = drone.y - self.y;
var distanceToDrone = Math.sqrt(dx * dx + dy * dy);
}
if (distanceToDrone < 400) {
// Calculate angle to orient knight towards drone
var angleToDrone = Math.atan2(dy, dx);
var directionToDrone;
if (angleToDrone >= -Math.PI / 8 && angleToDrone < Math.PI / 8) {
directionToDrone = 'right';
} else if (angleToDrone >= Math.PI / 8 && angleToDrone < 3 * Math.PI / 8) {
directionToDrone = 'down-right';
} else if (angleToDrone >= 3 * Math.PI / 8 && angleToDrone < 5 * Math.PI / 8) {
directionToDrone = 'down';
} else if (angleToDrone >= 5 * Math.PI / 8 && angleToDrone < 7 * Math.PI / 8) {
directionToDrone = 'down-left';
} else if (angleToDrone >= 7 * Math.PI / 8 || angleToDrone < -7 * Math.PI / 8) {
directionToDrone = 'left';
} else if (angleToDrone >= -7 * Math.PI / 8 && angleToDrone < -5 * Math.PI / 8) {
directionToDrone = 'up-left';
} else if (angleToDrone >= -5 * Math.PI / 8 && angleToDrone < -3 * Math.PI / 8) {
directionToDrone = 'up';
} else if (angleToDrone >= -3 * Math.PI / 8 && angleToDrone < -Math.PI / 8) {
directionToDrone = 'up-right';
}
if (directionToDrone) {
self.switchAsset(directionToDrone);
}
}
}
// Set knight to idle state if currently moving
if (self.isMoving) {
self.isMoving = false;
self.switchAsset(self.lastDirection || 'down'); // Update asset to idle animation
}
}
// Update knight animation
self.updateAnimation();
};
self.switchAsset = function (direction) {
if (!knightGraphics) {
return;
}
// If currently attacking, don't switch assets
if (self.isAttacking) {
return;
}
// Detach current assets
// Hide current assets
knightGraphics.visible = false;
shadowGraphics.visible = false;
// Switch to new assets based on direction and movement state
if (self.isMoving) {
// Use running animation
knightGraphics = knightAssets[direction][currentFrame];
shadowGraphics = shadowAssets[direction][currentFrame];
} else {
// Use idle animation
knightGraphics = knightIdleAssets[direction];
shadowGraphics = shadowIdleAssets[direction];
}
self.addChild(shadowGraphics);
self.addChild(knightGraphics);
// Show new assets
knightGraphics.visible = true;
shadowGraphics.visible = true;
// Position the shadow slightly offset from the knight
shadowGraphics.x = shadowOffsetX;
shadowGraphics.y = shadowOffsetY;
// Update last direction
self.lastDirection = direction;
};
self.updateAnimation = function () {
// If currently attacking, don't switch assets
if (self.isAttacking) {
return;
}
if (self.isMoving) {
// Only update animation if moving
frameCounter += animationSpeed;
if (frameCounter >= 1) {
frameCounter = 0;
currentFrame = (currentFrame + 1) % 8; // Cycle through 8 frames
if (self.lastDirection) {
self.switchAsset(self.lastDirection);
}
}
} else if (self.lastDirection) {
// Make sure we're showing the idle animation when not moving
self.switchAsset(self.lastDirection);
}
};
self.moveToDirection = function (direction) {
if (self.isAttacking) {
return;
}
if (self.lastDirection !== direction) {
self.switchAsset(direction);
self.lastDirection = direction;
}
switch (direction) {
case 'up':
self.y -= self.speed;
break;
case 'down':
self.y += self.speed;
break;
case 'left':
self.x -= self.speed;
break;
case 'right':
self.x += self.speed;
break;
case 'up-left':
self.x -= self.speed / Math.sqrt(2);
self.y -= self.speed / Math.sqrt(2);
break;
case 'up-right':
self.x += self.speed / Math.sqrt(2);
self.y -= self.speed / Math.sqrt(2);
break;
case 'down-left':
self.x -= self.speed / Math.sqrt(2);
self.y += self.speed / Math.sqrt(2);
break;
case 'down-right':
self.x += self.speed / Math.sqrt(2);
self.y += self.speed / Math.sqrt(2);
break;
}
};
self.takeHit = function (bullet) {
if (isDied) {
return;
}
// Play knight-hit sound
LK.getSound('knight-hit').play();
// Flash the knight red to indicate damage
self.tint = 0xff0000; // Red tint
// After a short delay, restore the original tint
LK.setTimeout(function () {
self.tint = color; // Restore original color
}, 200);
// Optional: Add knockback effect based on bullet direction
var knockbackDistance = 30;
var knockbackAngle = bullet.rotation;
// Apply knockback
self.x += Math.cos(knockbackAngle) * knockbackDistance;
self.y += Math.sin(knockbackAngle) * knockbackDistance;
self.health--;
if (self.health < 0) {
self.dieAnim();
}
// Log hit for debugging
log("Knight hit by laser beam!");
};
self.dieAnim = function () {
// Animate knight tint to black
tween(self, {
tint: 0x000000
}, {
duration: 1000,
// 1 second duration
easing: tween.easeInOut,
onFinish: function onFinish() {
if (isDied) {
return;
}
isDied = true;
var explosion = new ExplosionKnight();
explosion.init(self.x, self.y);
middlegroundContainer.addChild(explosion);
LK.getSound('knight-killed').play();
self.destroy(); // Destroy knight after animation
LK.setTimeout(function () {
LK.showGameOver();
}, 2000); // Call game over after 2 seconds
}
});
};
self.attack = function () {
// Check if already attacking
if (self.isAttacking) {
return;
}
// Play knight-attack sound
LK.getSound('knight-attack').play();
self.isAttacking = true;
log("Knight attacking!");
// Store current direction
var direction = self.lastDirection || 'down';
// Get attack animation frames for current direction
var attackFrames = knightAttackAssets[direction];
var shadowAttackFrames = shadowAttackAssets[direction];
// Hide current graphics
// knightGraphics.visible = false;
// shadowGraphics.visible = false;
// Add all frames but make them invisible initially
for (var i = 0; i < attackFrames.length; i++) {
self.addChild(shadowAttackFrames[i]);
self.addChild(attackFrames[i]);
shadowAttackFrames[i].visible = false;
attackFrames[i].visible = false;
}
// Create attack animation
var currentFrame = 0;
var attackInterval = LK.setInterval(function () {
// Hide previous frame if it exists
if (currentFrame > 0) {
attackFrames[currentFrame - 1].visible = false;
shadowAttackFrames[currentFrame - 1].visible = false;
}
// Hide current graphics
knightGraphics.visible = false;
shadowGraphics.visible = false;
// Show current frame
shadowAttackFrames[currentFrame].visible = true;
attackFrames[currentFrame].visible = true;
// Check for collision with drones (middle of the attack animation)
if (currentFrame === Math.floor(attackFrames.length / 2)) {
// Calculate attack range and direction
var attackRange = 100; // Range of the knight's attack
var attackDirection = direction;
var attackAngle;
// Determine attack angle based on direction
if (attackDirection === 'right') {
attackAngle = 0;
} else if (attackDirection === 'down-right') {
attackAngle = Math.PI / 4;
} else if (attackDirection === 'down') {
attackAngle = Math.PI / 2;
} else if (attackDirection === 'down-left') {
attackAngle = 3 * Math.PI / 4;
} else if (attackDirection === 'left') {
attackAngle = Math.PI;
} else if (attackDirection === 'up-left') {
attackAngle = 5 * Math.PI / 4;
} else if (attackDirection === 'up') {
attackAngle = 3 * Math.PI / 2;
} else if (attackDirection === 'up-right') {
attackAngle = 7 * Math.PI / 4;
}
// Calculate attack point in front of the knight
var attackX = self.x + Math.cos(attackAngle) * attackRange;
var attackY = self.y + Math.sin(attackAngle) * attackRange;
// Check if any drone is within attack range
for (var i = 0; i < droneManager.drones.length; i++) {
var drone = droneManager.drones[i];
var dx = drone.x - attackX;
var dy = drone.y - attackY;
var distance = Math.sqrt(dx * dx + dy * dy);
// If drone is within attack range, call its takeHit method
if (distance < 120) {
// Adjust hit radius as needed
drone.takeHit({
rotation: attackAngle // Pass attack angle for knockback direction
});
}
}
}
// Move to next frame
currentFrame++;
// Check if animation is complete
if (currentFrame >= attackFrames.length) {
// Clear interval
LK.clearInterval(attackInterval);
// Hide all attack frames
for (var i = 0; i < attackFrames.length; i++) {
attackFrames[i].visible = false;
shadowAttackFrames[i].visible = false;
}
// Show regular graphics again
knightGraphics.visible = true;
shadowGraphics.visible = true;
// Reset attacking flag
self.isAttacking = false;
// Reset the global target position to knight's current position
target.x = self.x;
target.y = self.y;
}
}, 50); // 50ms per frame for a fast attack animation
};
});
var LaserBeam = Container.expand(function () {
var self = Container.call(this);
var laserBeamGraphics = LK.getAsset('droneLaserBeam', {
anchorX: 0.5,
anchorY: 0.5,
blendMode: 1
});
self.addChild(laserBeamGraphics);
self.speed = 1500; // Speed of the laser beam in pixels per second
self.fire = function (x, y, rotation) {
self.x = x;
self.y = y;
self.rotation = rotation;
middlegroundContainer.addChild(self);
// Calculate the distance the laser beam will travel
var distance = 3000;
// Calculate the duration based on speed
var duration = distance / self.speed * 1000; // Convert to milliseconds
// Tween the laser beam to move forward
tween(self, {
x: self.x + Math.cos(self.rotation) * distance,
y: self.y + Math.sin(self.rotation) * distance
}, {
duration: duration,
easing: tween.linear,
onFinish: function onFinish() {
self.destroy();
}
});
};
self.update = function () {
if (self.intersects(knight.boundingBox)) {
knight.takeHit(self);
self.destroy();
}
};
});
var Room = Container.expand(function () {
var self = Container.call(this);
var background = LK.getAsset('backgroundWalls', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(background);
self.x = 2048 / 2;
self.y = 2732 / 2;
});
var Target = Container.expand(function () {
var self = Container.call(this);
self.x = 0;
self.y = 0;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000 //Init game with black background
});
/****
* Game Code
****/
// Utility function to calculate the angle difference
function calculateAngleDiff(targetAngle, currentAngle) {
var angleDiff = targetAngle - currentAngle;
// Normalize angle difference to [-PI, PI]
while (angleDiff > Math.PI) {
angleDiff -= 2 * Math.PI;
}
while (angleDiff < -Math.PI) {
angleDiff += 2 * Math.PI;
}
return angleDiff;
}
//<Write entity 'classes' with empty functions for important behavior here>
//<Write imports for supported plugins here>
//<Assets used in the game will automatically appear here>
//<Write game logic code here, including initializing arrays and variables>
var DroneManager = function DroneManager() {
var self = this;
self.drones = [];
self.start = function () {
// Clear previous drones from the scene and the drones array
self.drones.forEach(function (drone) {
drone.destroy();
});
self.drones = [];
// Spawn a drone in a random corner
//var currentTargetIndex = Math.floor(Math.random() * corners.length);
//var cornerPosition = corners[currentTargetIndex];
// Create the drone
var drone = new Drone();
// Explicitly set position before adding to the scene
drone.x = Math.random() < 0.5 ? -1024 : 3072;
drone.y = 1366;
// Store the current target index
//drone.currentTargetIndex = currentTargetIndex;
// Set the initial target to the current corner position
drone.target = {
x: 1024,
y: 1366
};
// Add to container and array
middlegroundContainer.addChild(drone);
self.drones.push(drone);
// This ensures all properties are set before tweening begins
LK.setTimeout(function () {
drone.init();
}, 100);
};
};
/****
* Global variables
****/
var isDebug = false;
var isDied = false; // Global flag to track if the knight has died
var gameStarted = false;
var droneManager = new DroneManager();
var backgroundContainer;
var middlegroundContainer;
var foregroundContainer;
var knight;
var background; // Declare a global variable for the background
var target;
var punchButton;
var runSoundInterval;
var borderOffset = 380;
var corners = [{
x: borderOffset,
y: borderOffset
}, {
x: 2048 - borderOffset,
y: borderOffset
}, {
x: borderOffset,
y: 2732 - borderOffset
}, {
x: 2048 - borderOffset,
y: 2732 - borderOffset
}];
function log() {
if (isDebug) {
console.log.apply(console, arguments);
}
}
function initializeGame() {
// Create containers
backgroundContainer = new Container();
middlegroundContainer = new Container();
foregroundContainer = new Container();
// Add containers to game
game.addChild(backgroundContainer);
game.addChild(middlegroundContainer);
game.addChild(foregroundContainer);
// Initialize background
background = new Background(); // Use Background class instead of LK.getAsset
backgroundContainer.addChild(background);
// Initialize room
var room = new Room();
backgroundContainer.addChild(room);
// Initialize knight
knight = new Knight();
knight.x = 2048 / 2;
knight.y = 2732 / 2;
middlegroundContainer.addChild(knight);
// Initialize target
target = new Target();
target.x = knight.x;
target.y = knight.y;
foregroundContainer.addChild(target);
// Initialize punch button
punchButton = new ButtonPunch();
punchButton.x = 2048 - 220;
punchButton.y = 220;
foregroundContainer.addChild(punchButton);
// Initialize sound
runSoundInterval = LK.setInterval(playRunSound, 350);
droneManager = new DroneManager();
LK.setTimeout(function () {
gameStarted = true;
droneManager.start();
}, 1000);
}
function playRunSound() {
if (knight.isMoving) {
LK.getSound('knight-run').play();
}
}
initializeGame();