Code edit (4 edits merged)
Please save this source code
Code edit (8 edits merged)
Please save this source code
User prompt
play knight-bonus when bonus taken
Code edit (1 edits merged)
Please save this source code
User prompt
bonus thow move should be slower and look more the result of an explosion ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
when spawned, bonus should be thrown with animation to one of the corners;move should look parabolic (game is in top down view) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
when spawned, animate bonus movement to one of the corners;move should look parabolic (game is in top down view) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
bonus spawn move should look parabolic ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
when spawned, animate bonus movement to a random corner ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
knight only collects the bonus if active
User prompt
add a boolean self.active=false; to bonus
Code edit (2 edits merged)
Please save this source code
User prompt
Now when knight boundingbox intersects bonusLight take the bonus (destory bonus and add 2 to health
Code edit (1 edits merged)
Please save this source code
/****
* 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 Bonus = Container.expand(function (x, y) {
var self = Container.call(this);
self.x = x;
self.y = y;
// Create shadow for bonus
var shadowBonus = LK.getAsset('bonusHealth', {
anchorX: 0.5,
anchorY: 0.5
});
shadowBonus.alpha = 0.5;
shadowBonus.tint = 0x000000;
shadowBonus.x = 10; // Offset shadow position
shadowBonus.y = 10; // Offset shadow position
self.addChild(shadowBonus);
self.active = false;
var bonusGraphics = LK.getAsset('bonusHealth', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(bonusGraphics);
self.bonusLight = LK.getAsset('bonusHealthLight', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x00FF00
});
self.addChild(self.bonusLight);
self.animLight = function () {
function loopTint() {
tween(self.bonusLight, {
tint: 0xFFFFFF
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self.bonusLight, {
tint: 0x00FF00
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: loopTint
});
}
});
}
loopTint();
};
self.animLight();
self.animMove = function () {
function loopMove() {
tween(self, {
y: self.y + 10
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
y: self.y - 10
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: loopMove
});
}
});
}
loopMove();
};
self.throwToCorner = function () {
// Calculate a random position within range instead of using corners
var minDistance = 1024;
var maxDistance = 1480;
// Generate random angle
var randomAngle = Math.random() * Math.PI * 2;
// Calculate random distance within range
var randomDistance = minDistance + Math.random() * (maxDistance - minDistance);
// Calculate target position
var targetX = self.x + Math.cos(randomAngle) * randomDistance;
var targetY = self.y + Math.sin(randomAngle) * randomDistance;
// Ensure target is within the playable area (within corners)
targetX = Math.max(borderOffset, Math.min(2048 - borderOffset, targetX));
targetY = Math.max(borderOffset, Math.min(2732 - borderOffset, targetY));
// Calculate control point for parabolic motion
var controlX = (self.x + targetX) / 2;
var controlY = Math.min(self.y, targetY) - 500; // Control point above the line for a parabolic effect
// Initial position
var startX = self.x;
var startY = self.y;
// Create a tween for parabolic motion
tween(self, {
x: targetX,
y: targetY
}, {
duration: 1200,
easing: tween.easeOutQuad,
onUpdate: function onUpdate() {
// Calculate parabolic path using quadratic Bezier curve formula
var t = this.progress;
self.x = Math.pow(1 - t, 2) * startX + 2 * (1 - t) * t * controlX + Math.pow(t, 2) * targetX;
self.y = Math.pow(1 - t, 2) * startY + 2 * (1 - t) * t * controlY + Math.pow(t, 2) * targetY;
// Add rotation for visual effect
self.rotation = t * Math.PI * 3; // 1.5 full rotations during flight
},
onFinish: function onFinish() {
self.rotation = 0; // Reset rotation
self.active = true; // Activate bonus after reaching the corner
self.animMove();
}
});
};
self.throwToCorner(); // Call the function to start the animation
});
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 (!isDied && knight && knight.health >= 0 && !knight.isAttacking) {
knight.attack();
}
};
});
// Import the tween plugin
var Drone = Container.expand(function (level) {
var self = Container.call(this);
self.level = level || 0; // Set level to the passed parameter or default to 0
self.health = (isDebug ? 2 : 4) + self.level * 2; // Add level*2 to drone's health
var droneWidth = 200;
// Increase size based on level
var levelGrowth = self.level * self.level * 80;
self.droneScanLaser = LK.getAsset('droneScanLaser', {
anchorX: 0,
anchorY: 0.5
});
self.droneScanLaser.visible = true; // Always visible
self.droneScanLaser.x = droneWidth / 2;
self.droneScanLaser.alpha = 0.3; // Set alpha to 0.3
self.droneScanLaser.width += levelGrowth;
self.droneScanLaser.height += levelGrowth;
self.addChild(self.droneScanLaser);
self.droneScanBar = LK.getAsset('droneScanBar', {
anchorX: 0,
anchorY: 0.5
});
self.droneScanBar.x = droneWidth / 2;
self.droneScanBar.blendMode = 3;
self.droneScanBar.width = self.droneScanLaser.width;
self.addChild(self.droneScanBar);
var droneGraphics = LK.getAsset('drone', {
anchorX: 0.5,
anchorY: 0.5,
width: droneWidth,
height: droneWidth
});
var shadowDrone = LK.getAsset('drone', {
anchorX: 0.5,
anchorY: 0.5,
width: droneWidth,
height: droneWidth
});
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);
// Add health bar under the drone
self.healthBar = LK.getAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5,
width: 100,
height: 10,
color: 0x00FF00 // Green color for health
});
self.healthBar.y = droneWidth / 2 + 20; // Position the health bar under the drone
self.addChild(self.healthBar);
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.75
}, {
duration: 600 - self.level * 50,
//{V} // Decrease duration to increase frequency with level
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self.droneScanBar, {
alpha: 0.6,
rotation: -Math.PI / 6.75
}, {
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.fireDelay = 1200;
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') {
// don't fire directly, consider a part of fire delay
self.lastFireTime = Date.now() - self.fireDelay * 0.5;
// 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.collectBonus = function collectBonus(bonus) {
// Increase knight's health by 2
knight.health += 2;
// Play knight-bonus sound
LK.getSound('knight-bonus').play();
// Destroy the bonus
bonus.destroy();
};
self.update = function () {
if (self.state === 'scanning') {
self.updateScanning();
} else if (self.state === 'attacking') {
self.updateAttacking();
}
// Update health bar width and color regardless of state
self.healthBar.width = Math.max(0, (self.health + 1) / 4 * 100); // Assuming max health is 4
// Change health bar color based on health
var healthPercentage = self.health / 4; // Assuming max health is 4
var red, green;
// Transition from green to orange
if (healthPercentage > 0.5) {
red = Math.min(255, Math.floor((1 - healthPercentage) * 2 * 255));
green = 255;
} else {
// Transition from orange to red
red = 255;
green = Math.min(255, Math.floor(healthPercentage * 2 * 255));
}
self.healthBar.tint = red << 16 | green << 8; // Combine red and green to form the color
// Iterate over all bonuses in the middlegroundContainer
for (var i = middlegroundContainer.children.length - 1; i >= 0; i--) {
var child = middlegroundContainer.children[i];
if (child instanceof Bonus) {
// Check if knight's boundingBox intersects with bonusLight
if (child.active && knight.boundingBox.intersects(child.bonusLight)) {
self.collectBonus(child);
}
}
}
// 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 = self.droneScanLaser.width * (1 + 0.1 * 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;
self.fire();
// 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 > self.fireDelay) {
// 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);
// 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;
}
// Switch to attacking state immediately if not already attacking
if (self.state !== 'attacking') {
self.switchState('attacking');
}
// Reset the lastKnightCheckTime to ensure we don't immediately check for knight visibility
self.lastKnightCheckTime = Date.now();
// 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;
// Check if new position would be too close to a corner
var tooCloseToCorner = false;
var cornerSafeDistance = 100; // Distance to stay away from corners
for (var i = 0; i < corners.length; i++) {
var dx = newX - corners[i].x;
var dy = newY - corners[i].y;
var distToCorner = Math.sqrt(dx * dx + dy * dy);
if (distToCorner < cornerSafeDistance) {
tooCloseToCorner = true;
break;
}
}
// If too close to a corner, adjust the knockback to push away from corner
if (tooCloseToCorner) {
// Find the center of the playfield
var centerX = (corners[0].x + corners[3].x) / 2;
var centerY = (corners[0].y + corners[3].y) / 2;
// Calculate direction toward center
var dxToCenter = centerX - self.x;
var dyToCenter = centerY - self.y;
var angleToCenter = Math.atan2(dyToCenter, dxToCenter);
// Use this angle for knockback instead
newX = self.x + Math.cos(angleToCenter) * knockbackDistance;
newY = self.y + Math.sin(angleToCenter) * knockbackDistance;
}
// Clamp new position using corners
newX = Math.max(corners[0].x + cornerSafeDistance, Math.min(newX, corners[3].x - cornerSafeDistance));
newY = Math.max(corners[0].y + cornerSafeDistance, Math.min(newY, corners[3].y - cornerSafeDistance));
// Apply knockback using tween
tween(self, {
x: newX,
y: newY
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// After knockback, immediately rotate the drone toward the knight
// 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,
onFinish: function onFinish() {
// Switch to attacking state if not already attacking
if (self.state !== 'attacking') {
self.switchState('attacking');
}
// Reset the lastKnightCheckTime to ensure we don't immediately check for knight visibility
self.lastKnightCheckTime = Date.now();
}
});
}
});
};
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);
// Clear scan sound interval if it exists
if (self.scanSoundInterval) {
LK.clearInterval(self.scanSoundInterval);
self.scanSoundInterval = null;
}
LK.getSound('drone-explode').play();
// Spawn a bonus at the drone's position
var bonus = new Bonus(self.x, self.y);
middlegroundContainer.addChild(bonus);
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 < 80; 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 = 2000; // Duration for particles to move and fade out
particles.forEach(function (particle) {
particle.x += -40 + Math.random() * 80;
particle.y += -40 + Math.random() * 80;
tween(particle, {
x: particle.x + particle.vx * duration / 500,
y: particle.y + particle.vy * duration / 500,
alpha: 0.0,
scaleY: 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 = isDebug ? 10 : 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 health bar under the knight
self.healthBar = LK.getAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5,
width: 100,
height: 10,
color: 0x00FF00 // Green color for health
});
self.healthBar.y = 60; // Position the health bar under the knight
self.addChild(self.healthBar);
// 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 () {
// Update health bar width based on knight's health
self.healthBar.width = Math.max(0, (self.health + 1) / 4 * 100); // Assuming max health is 3
// Change health bar color based on health
var healthPercentage = self.health / 3; // Assuming max health is 3
var red, green;
// Transition from green to orange
if (healthPercentage > 0.5) {
red = Math.min(255, Math.floor((1 - healthPercentage) * 2 * 255));
green = 255;
} else {
// Transition from orange to red
red = 255;
green = Math.min(255, Math.floor(healthPercentage * 2 * 255));
}
self.healthBar.tint = red << 16 | green << 8; // Combine red and green to form the color
// Don't process movement if attacking
if (isDied || self.health < 0 || 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 (isDied || self.health < 0 || 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 (isDied || self.health < 0 || 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 (isDied || self.health < 0 || 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;
}
// 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();
} else {
// Play knight-hit sound
LK.getSound('knight-hit').play();
}
// Log hit for debugging
log("Knight hit by laser beam!");
};
self.dieAnim = function () {
// Animate knight tint to black
LK.getSound('knight-die').play();
punchButton.visible = false; // Hide attackButton when the knight is dead
if (isDied) {
return;
}
isDied = true;
tween(self, {
tint: 0x000000
}, {
duration: 1000,
// 1 second duration
easing: tween.easeInOut,
onFinish: function onFinish() {
var explosion = new ExplosionKnight();
explosion.init(self.x, self.y - 50);
middlegroundContainer.addChild(explosion);
LK.getSound('knight-killed').play();
self.destroy(); // Destroy knight after animation
LK.setTimeout(function () {
LK.showGameOver();
}, 3000); // 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 () {
if (self.isDied) {
return;
}
// 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.droneCounter = 0; // Initialize droneCounter to track the number of drones
self.drones = [];
self.start = function () {
// Clear previous drones from the scene and the drones array
self.drones.forEach(function (drone) {
drone.destroy();
});
self.drones = [];
// Create the drone
var drone = new Drone(self.droneCounter); // Pass the current level to the Drone constructor
// Increment counter after drone creation so first drone is level 0
self.droneCounter++;
// Check win condition after incrementing counter
if (self.droneCounter > 3) {
LK.getSound('knight-win').play(); // Play knight-win sound
LK.setTimeout(function () {
LK.showYouWin(); // Show win screen after 2000ms delay
}, 2000);
return;
}
// 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 = true;
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(); ===================================================================
--- original.js
+++ change.js
@@ -88,43 +88,47 @@
});
}
loopMove();
};
- self.animMove();
self.throwToCorner = function () {
- // Randomly select one of the corners
- var targetCorner = corners[Math.floor(Math.random() * corners.length)];
- // For a top-down view, we want the object to slide along the ground
- // rather than appearing to jump in the air
- // Calculate the angle between current position and target
- var dx = targetCorner.x - self.x;
- var dy = targetCorner.y - self.y;
- var distance = Math.sqrt(dx * dx + dy * dy);
- var angle = Math.atan2(dy, dx);
- // Create a tween for realistic sliding motion
+ // Calculate a random position within range instead of using corners
+ var minDistance = 1024;
+ var maxDistance = 1480;
+ // Generate random angle
+ var randomAngle = Math.random() * Math.PI * 2;
+ // Calculate random distance within range
+ var randomDistance = minDistance + Math.random() * (maxDistance - minDistance);
+ // Calculate target position
+ var targetX = self.x + Math.cos(randomAngle) * randomDistance;
+ var targetY = self.y + Math.sin(randomAngle) * randomDistance;
+ // Ensure target is within the playable area (within corners)
+ targetX = Math.max(borderOffset, Math.min(2048 - borderOffset, targetX));
+ targetY = Math.max(borderOffset, Math.min(2732 - borderOffset, targetY));
+ // Calculate control point for parabolic motion
+ var controlX = (self.x + targetX) / 2;
+ var controlY = Math.min(self.y, targetY) - 500; // Control point above the line for a parabolic effect
+ // Initial position
+ var startX = self.x;
+ var startY = self.y;
+ // Create a tween for parabolic motion
tween(self, {
- x: targetCorner.x,
- y: targetCorner.y
+ x: targetX,
+ y: targetY
}, {
- duration: 2000,
+ duration: 1200,
easing: tween.easeOutQuad,
onUpdate: function onUpdate() {
- // Calculate position along a straight line with a slight curve
+ // Calculate parabolic path using quadratic Bezier curve formula
var t = this.progress;
- // Apply a slight curve to the path for more natural movement
- var curveAmount = Math.sin(t * Math.PI) * 30; // Smaller curve amount for subtle effect
- // Calculate base position along straight line
- var newX = self.x + (targetCorner.x - self.x) * t;
- var newY = self.y + (targetCorner.y - self.y) * t;
- // Apply slight perpendicular offset for curved path
- self.x = newX + Math.sin(angle + Math.PI / 2) * curveAmount;
- self.y = newY - Math.cos(angle + Math.PI / 2) * curveAmount;
- // Add slight rotation to simulate the object sliding/rolling
- self.rotation = t * Math.PI * 0.5; // Half rotation during the movement
+ self.x = Math.pow(1 - t, 2) * startX + 2 * (1 - t) * t * controlX + Math.pow(t, 2) * targetX;
+ self.y = Math.pow(1 - t, 2) * startY + 2 * (1 - t) * t * controlY + Math.pow(t, 2) * targetY;
+ // Add rotation for visual effect
+ self.rotation = t * Math.PI * 3; // 1.5 full rotations during flight
},
onFinish: function onFinish() {
- self.rotation = 0; // Reset rotation at the end
+ self.rotation = 0; // Reset rotation
self.active = true; // Activate bonus after reaching the corner
+ self.animMove();
}
});
};
self.throwToCorner(); // Call the function to start the animation
@@ -150,9 +154,9 @@
self.level = level || 0; // Set level to the passed parameter or default to 0
self.health = (isDebug ? 2 : 4) + self.level * 2; // Add level*2 to drone's health
var droneWidth = 200;
// Increase size based on level
- var levelGrowth = self.level * self.level * 100;
+ var levelGrowth = self.level * self.level * 80;
self.droneScanLaser = LK.getAsset('droneScanLaser', {
anchorX: 0,
anchorY: 0.5
});
@@ -351,45 +355,43 @@
};
self.collectBonus = function collectBonus(bonus) {
// Increase knight's health by 2
knight.health += 2;
+ // Play knight-bonus sound
+ LK.getSound('knight-bonus').play();
// Destroy the bonus
bonus.destroy();
};
self.update = function () {
if (self.state === 'scanning') {
self.updateScanning();
} else if (self.state === 'attacking') {
- // Update function to check for intersection between knight and bonusLight
self.updateAttacking();
- //<Write game logic code here, including initializing arrays and variables>
- // Function to handle bonus collection
- game.update = function () {
- // Iterate over all bonuses in the middlegroundContainer
- for (var i = middlegroundContainer.children.length - 1; i >= 0; i--) {
- var child = middlegroundContainer.children[i];
- if (child instanceof Bonus) {
- // Check if knight's boundingBox intersects with bonusLight
- if (child.active && knight.boundingBox.intersects(child.bonusLight)) {
- self.collectBonus(child);
- }
- }
+ }
+ // Update health bar width and color regardless of state
+ self.healthBar.width = Math.max(0, (self.health + 1) / 4 * 100); // Assuming max health is 4
+ // Change health bar color based on health
+ var healthPercentage = self.health / 4; // Assuming max health is 4
+ var red, green;
+ // Transition from green to orange
+ if (healthPercentage > 0.5) {
+ red = Math.min(255, Math.floor((1 - healthPercentage) * 2 * 255));
+ green = 255;
+ } else {
+ // Transition from orange to red
+ red = 255;
+ green = Math.min(255, Math.floor(healthPercentage * 2 * 255));
+ }
+ self.healthBar.tint = red << 16 | green << 8; // Combine red and green to form the color
+ // Iterate over all bonuses in the middlegroundContainer
+ for (var i = middlegroundContainer.children.length - 1; i >= 0; i--) {
+ var child = middlegroundContainer.children[i];
+ if (child instanceof Bonus) {
+ // Check if knight's boundingBox intersects with bonusLight
+ if (child.active && knight.boundingBox.intersects(child.bonusLight)) {
+ self.collectBonus(child);
}
- };
- self.healthBar.width = Math.max(0, (self.health + 1) / 4 * 100); // Assuming max health is 4
- // Change health bar color based on health
- var healthPercentage = self.health / 4; // Assuming max health is 4
- var red, green;
- // Transition from green to orange
- if (healthPercentage > 0.5) {
- red = Math.min(255, Math.floor((1 - healthPercentage) * 2 * 255));
- green = 255;
- } else {
- // Transition from orange to red
- red = 255;
- green = Math.min(255, Math.floor(healthPercentage * 2 * 255));
}
- self.healthBar.tint = red << 16 | green << 8; // Combine red and green to form the color
}
// Update shadow position
shadowDrone.x = self.shadowOffset.x * Math.cos(self.rotation);
shadowDrone.y = self.shadowOffset.y * Math.sin(self.rotation);