User prompt
in drone.explode, stop all drone tweens ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'explode')' in or related to this line: 'self.explode = function () {' Line Number: 1411
User prompt
add an explode function to drone
Code edit (2 edits merged)
Please save this source code
User prompt
add health to drone like in knight class
Code edit (1 edits merged)
Please save this source code
User prompt
in drone takeHit, clamp knockback position using corners
Code edit (9 edits merged)
Please save this source code
User prompt
Please fix the bug: 'ReferenceError: drone is not defined' in or related to this line: 'var dx = drone.x - self.x;' Line Number: 893
User prompt
when knight stops moving, if distance to drone is <400, oriente the knight in the drone direction
User prompt
in drone.takeHit, use tween for knockback ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
reset the global target when kight attacks
Code edit (1 edits merged)
Please save this source code
Code edit (2 edits merged)
Please save this source code
User prompt
in Background down, clamp target position using corners
Code edit (4 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'down')' in or related to this line: 'knight.isMoving = false;' Line Number: 1179
User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'down')' in or related to this line: 'self.down = function (x, y, obj) {' Line Number: 1181
User prompt
move current down handler from game.down to Background class down
User prompt
declare a global background then in initilizeGame, use Background class instead of of LK.getAsset
User prompt
create a new class for background
User prompt
Please fix the bug: 'background is not defined' in or related to this line: 'background.down = function (x, y, obj) {' Line Number: 1101
User prompt
move current down handler from game.down to background.down
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ 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); 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.addChild(self.debugText); self.fireRange = 300; // Example fire range value self.currentTargetIndex = 0; 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..."); 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); // 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') { // 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 (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 (!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'); // Calculate the angle difference var angleDiff = calculateAngleDiff(targetAngle, self.rotation); var absDiff = Math.abs(angleDiff); // 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); var targetRotation = self.rotation + angleDiff; // Log the rotation parameters for debugging log("Rotating: angle diff=" + absDiff + ", duration=" + duration + "ms"); tween(self, { rotation: targetRotation }, { duration: duration, easing: tween.easeInOut, onFinish: function onFinish() { log("Rotation complete"); if (callback) { callback(); } } }); }; }); 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 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 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); } // 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.speed = 9; self.switchAsset = function (direction) { if (!knightGraphics) { 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 (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.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) { // Flash the knight red to indicate damage knightGraphics.tint = 0xff0000; // Red tint // After a short delay, restore the original tint LK.setTimeout(function () { knightGraphics.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) { // TODO : switch to die anim } // Log hit for debugging log("Knight hit by laser beam!"); }; self.attack = function () { // Check if already attacking if (self.isAttacking) { return; } self.isAttacking = true; log("Knight attacking!"); // Store current direction var direction = self.lastDirection || 'down'; // Get attack animation frames for current direction var attackFrames = []; for (var i = 1; i <= 15; i++) { var frameNumber = i.toString().padStart(3, '0'); attackFrames.push(LK.getAsset('knight-attack-' + directionMapping[direction] + '-' + frameNumber, { anchorX: 0.5, anchorY: 0.5, tint: color })); } // Hide current graphics knightGraphics.visible = false; // Create attack animation var currentFrame = 0; var attackInterval = LK.setInterval(function () { // Remove previous frame if exists if (currentFrame > 0) { self.removeChild(attackFrames[currentFrame - 1]); } // Add current frame self.addChild(attackFrames[currentFrame]); // Move to next frame currentFrame++; // Check if animation is complete if (currentFrame >= attackFrames.length) { // Clear interval LK.clearInterval(attackInterval); // Remove last frame self.removeChild(attackFrames[currentFrame - 1]); // Show regular graphics again knightGraphics.visible = true; // Reset attacking flag self.isAttacking = false; } }, 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.update = function () { if (self.intersects(knight.boundingBox)) { knight.takeHit(self); self.destroy(); } }; 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(); } }); }; }); 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 () { // 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 = cornerPosition.x; drone.y = cornerPosition.y; // Store the current target index drone.currentTargetIndex = currentTargetIndex; // Set the initial target to the current corner position drone.target = { x: cornerPosition.x, y: cornerPosition.y }; // 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 = true; var gameStarted = false; var droneManager; var backgroundContainer; var middlegroundContainer; var foregroundContainer; var knight; 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 var background = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5 }); background.x = 2048 / 2; background.y = 2732 / 2; backgroundContainer.addChild(background); // Initialize knight // 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(); } } background.down = function (x, y, obj) { var game_position = game.toLocal(obj.global); target.x = game_position.x; target.y = game_position.y; }; game.update = function () { var dx = target.x - knight.x; var dy = target.y - knight.y; var distance = Math.sqrt(dx * dx + dy * dy); var distanceThreshold = 8; // Define a threshold to avoid small movements var directionThreshold = 5; // Define a threshold for direction changes to avoid shaking if (distance > distanceThreshold) { // Set knight to moving state if not already moving if (!knight.isMoving) { knight.isMoving = true; knight.switchAsset(knight.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) { knight.moveToDirection('down-right'); } else if (dy < -directionThreshold) { knight.moveToDirection('up-right'); } else { knight.moveToDirection('right'); } } else { if (dy > directionThreshold) { knight.moveToDirection('down-left'); } else if (dy < -directionThreshold) { knight.moveToDirection('up-left'); } else { knight.moveToDirection('left'); } } } else if (Math.abs(dy) > Math.abs(dx) + directionThreshold) { // Vertical movement dominates if (dy > 0) { if (dx > directionThreshold) { knight.moveToDirection('down-right'); } else if (dx < -directionThreshold) { knight.moveToDirection('down-left'); } else { knight.moveToDirection('down'); } } else { if (dx > directionThreshold) { knight.moveToDirection('up-right'); } else if (dx < -directionThreshold) { knight.moveToDirection('up-left'); } else { knight.moveToDirection('up'); } } } else { // The difference between dx and dy is small, use diagonal movement if (dx > 0 && dy > 0) { knight.moveToDirection('down-right'); } else if (dx > 0 && dy < 0) { knight.moveToDirection('up-right'); } else if (dx < 0 && dy > 0) { knight.moveToDirection('down-left'); } else if (dx < 0 && dy < 0) { knight.moveToDirection('up-left'); } } } else { // Set knight to idle state if currently moving if (knight.isMoving) { knight.isMoving = false; knight.switchAsset(knight.lastDirection || 'down'); // Update asset to idle animation } } // Update knight animation knight.updateAnimation(); }; initializeGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
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);
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.addChild(self.debugText);
self.fireRange = 300; // Example fire range value
self.currentTargetIndex = 0;
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...");
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);
// 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') {
// 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 (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 (!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');
// Calculate the angle difference
var angleDiff = calculateAngleDiff(targetAngle, self.rotation);
var absDiff = Math.abs(angleDiff);
// 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);
var targetRotation = self.rotation + angleDiff;
// Log the rotation parameters for debugging
log("Rotating: angle diff=" + absDiff + ", duration=" + duration + "ms");
tween(self, {
rotation: targetRotation
}, {
duration: duration,
easing: tween.easeInOut,
onFinish: function onFinish() {
log("Rotation complete");
if (callback) {
callback();
}
}
});
};
});
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 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
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);
}
// 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.speed = 9;
self.switchAsset = function (direction) {
if (!knightGraphics) {
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 (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.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) {
// Flash the knight red to indicate damage
knightGraphics.tint = 0xff0000; // Red tint
// After a short delay, restore the original tint
LK.setTimeout(function () {
knightGraphics.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) {
// TODO : switch to die anim
}
// Log hit for debugging
log("Knight hit by laser beam!");
};
self.attack = function () {
// Check if already attacking
if (self.isAttacking) {
return;
}
self.isAttacking = true;
log("Knight attacking!");
// Store current direction
var direction = self.lastDirection || 'down';
// Get attack animation frames for current direction
var attackFrames = [];
for (var i = 1; i <= 15; i++) {
var frameNumber = i.toString().padStart(3, '0');
attackFrames.push(LK.getAsset('knight-attack-' + directionMapping[direction] + '-' + frameNumber, {
anchorX: 0.5,
anchorY: 0.5,
tint: color
}));
}
// Hide current graphics
knightGraphics.visible = false;
// Create attack animation
var currentFrame = 0;
var attackInterval = LK.setInterval(function () {
// Remove previous frame if exists
if (currentFrame > 0) {
self.removeChild(attackFrames[currentFrame - 1]);
}
// Add current frame
self.addChild(attackFrames[currentFrame]);
// Move to next frame
currentFrame++;
// Check if animation is complete
if (currentFrame >= attackFrames.length) {
// Clear interval
LK.clearInterval(attackInterval);
// Remove last frame
self.removeChild(attackFrames[currentFrame - 1]);
// Show regular graphics again
knightGraphics.visible = true;
// Reset attacking flag
self.isAttacking = false;
}
}, 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.update = function () {
if (self.intersects(knight.boundingBox)) {
knight.takeHit(self);
self.destroy();
}
};
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();
}
});
};
});
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 () {
// 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 = cornerPosition.x;
drone.y = cornerPosition.y;
// Store the current target index
drone.currentTargetIndex = currentTargetIndex;
// Set the initial target to the current corner position
drone.target = {
x: cornerPosition.x,
y: cornerPosition.y
};
// 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 = true;
var gameStarted = false;
var droneManager;
var backgroundContainer;
var middlegroundContainer;
var foregroundContainer;
var knight;
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
var background = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5
});
background.x = 2048 / 2;
background.y = 2732 / 2;
backgroundContainer.addChild(background);
// Initialize knight
// 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();
}
}
background.down = function (x, y, obj) {
var game_position = game.toLocal(obj.global);
target.x = game_position.x;
target.y = game_position.y;
};
game.update = function () {
var dx = target.x - knight.x;
var dy = target.y - knight.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var distanceThreshold = 8; // Define a threshold to avoid small movements
var directionThreshold = 5; // Define a threshold for direction changes to avoid shaking
if (distance > distanceThreshold) {
// Set knight to moving state if not already moving
if (!knight.isMoving) {
knight.isMoving = true;
knight.switchAsset(knight.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) {
knight.moveToDirection('down-right');
} else if (dy < -directionThreshold) {
knight.moveToDirection('up-right');
} else {
knight.moveToDirection('right');
}
} else {
if (dy > directionThreshold) {
knight.moveToDirection('down-left');
} else if (dy < -directionThreshold) {
knight.moveToDirection('up-left');
} else {
knight.moveToDirection('left');
}
}
} else if (Math.abs(dy) > Math.abs(dx) + directionThreshold) {
// Vertical movement dominates
if (dy > 0) {
if (dx > directionThreshold) {
knight.moveToDirection('down-right');
} else if (dx < -directionThreshold) {
knight.moveToDirection('down-left');
} else {
knight.moveToDirection('down');
}
} else {
if (dx > directionThreshold) {
knight.moveToDirection('up-right');
} else if (dx < -directionThreshold) {
knight.moveToDirection('up-left');
} else {
knight.moveToDirection('up');
}
}
} else {
// The difference between dx and dy is small, use diagonal movement
if (dx > 0 && dy > 0) {
knight.moveToDirection('down-right');
} else if (dx > 0 && dy < 0) {
knight.moveToDirection('up-right');
} else if (dx < 0 && dy > 0) {
knight.moveToDirection('down-left');
} else if (dx < 0 && dy < 0) {
knight.moveToDirection('up-left');
}
}
} else {
// Set knight to idle state if currently moving
if (knight.isMoving) {
knight.isMoving = false;
knight.switchAsset(knight.lastDirection || 'down'); // Update asset to idle animation
}
}
// Update knight animation
knight.updateAnimation();
};
initializeGame();