/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0 }); /**** * Classes ****/ var Arrow = Container.expand(function () { var self = Container.call(this); // Arrow body var arrowBody = self.attachAsset('arrow', { anchorX: 0.5, anchorY: 0.5 }); // Arrow tip var arrowTip = self.attachAsset('arrowTip', { anchorX: 0.5, anchorY: 0.5, x: 65 }); // Properties self.velocity = { x: 0, y: 0 }; self.gravity = 0.3; self.friction = 0.99; self.flying = false; self.stuck = false; self.targetHit = null; // Update arrow physics self.update = function () { if (self.lastX === undefined) { self.lastX = self.x; } if (self.lastY === undefined) { self.lastY = self.y; } if (self.flying && !self.stuck) { // Apply physics self.velocity.y += self.gravity; self.x += self.velocity.x; self.y += self.velocity.y; // Update rotation to match trajectory self.rotation = Math.atan2(self.velocity.y, self.velocity.x); // Check if arrow is off-screen if (self.y > 2732 || self.x < -100 || self.x > 2148) { if (!self.hasHitGround) { self.hasHitGround = true; LK.getSound('miss').play(); } } self.lastX = self.x; self.lastY = self.y; self.lastWasIntersecting = false; // Reset intersection state for next checks } }; // Fire the arrow with given power and angle self.fire = function (power, angle) { self.flying = true; self.velocity.x = Math.cos(angle) * power; self.velocity.y = Math.sin(angle) * power; self.rotation = angle; LK.getSound('bowRelease').play(); }; // Stick arrow to target self.stickTo = function (target, score) { self.stuck = true; self.flying = false; self.targetHit = target; self.score = score; // Play sound LK.getSound('targetHit').play(); }; return self; }); var Bow = Container.expand(function () { var self = Container.call(this); // Bow body var bowBody = self.attachAsset('bow', { anchorX: 0.5, anchorY: 0.5 }); // Bow string var bowString = self.attachAsset('bowString', { anchorX: 0.5, anchorY: 0.5, x: -10 }); // Properties self.aimAngle = -Math.PI / 4; // Default angle (slightly up) self.power = 0; self.maxPower = 35; self.charging = false; // Update bow self.update = function () { // Update bow rotation to match aim angle self.rotation = self.aimAngle; // Update string position/appearance based on pull if (self.charging) { var pullFactor = self.power / self.maxPower; bowString.scaleX = 1 + pullFactor * 0.5; // Thicker when pulled } else { bowString.scaleX = 1; } }; // Start charging (pulling back) self.startCharging = function () { self.charging = true; self.power = 0; }; // Continue charging self.updateCharging = function () { if (self.charging) { if (!self.loadingSoundPlayed) { LK.getSound('bowLoading').play(); self.loadingSoundPlayed = true; } self.power += 0.4; if (self.power > self.maxPower) { self.power = self.maxPower; } } }; // Release arrow self.releaseArrow = function () { var finalPower = self.power; self.charging = false; self.loadingSoundPlayed = false; self.power = 0; return finalPower; }; return self; }); var PowerMeter = Container.expand(function () { var self = Container.call(this); // Background var background = self.attachAsset('powerMeter', { anchorX: 0.5, anchorY: 0.5 }); // Fill var fill = self.attachAsset('powerFill', { anchorX: 0.5, anchorY: 1, // Anchor to bottom y: 195 // Half height of background }); fill.height = 0; // Start empty // Update power meter self.updatePower = function (power, maxPower) { var percentage = power / maxPower; fill.height = 390 * percentage; // 390 is the max height of fill // Change color based on power if (percentage < 0.33) { fill.tint = 0x00FF00; // Green } else if (percentage < 0.66) { fill.tint = 0xFFFF00; // Yellow } else { fill.tint = 0xFF0000; // Red } }; return self; }); var Target = Container.expand(function () { var self = Container.call(this); // Create rings from outside to inside var outerRing = self.attachAsset('target', { anchorX: 0.5, anchorY: 0.5 }); var ring1 = self.attachAsset('targetRing1', { anchorX: 0.5, anchorY: 0.5 }); var ring2 = self.attachAsset('targetRing2', { anchorX: 0.5, anchorY: 0.5 }); var ring3 = self.attachAsset('targetRing3', { anchorX: 0.5, anchorY: 0.5 }); var bullseye = self.attachAsset('targetRing4', { anchorX: 0.5, anchorY: 0.5 }); // Target properties self.radius = 100; // Outer radius self.direction = 1; // 1 for right, -1 for left self.speed = 2; self.minX = 300; self.maxX = 1748; // 2048 - 300 // Score values for each ring self.scoreValues = [10, 25, 50, 75, 100]; // Outer to inner // Update target movement self.update = function () { if (!self["static"]) { self.x += self.speed * self.direction; // Reverse direction at edges if (self.x > self.maxX || self.x < self.minX) { self.direction *= -1; } } }; // Get score based on distance from center self.getScore = function (hitX, hitY) { // Calculate distance from center var dx = hitX - self.x; var dy = hitY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Determine which ring was hit if (distance < 20) { return self.scoreValues[4]; // Bullseye } else if (distance < 40) { return self.scoreValues[3]; } else if (distance < 60) { return self.scoreValues[2]; } else if (distance < 80) { return self.scoreValues[1]; } else if (distance < 100) { return self.scoreValues[0]; // Outer ring } return 0; // Miss }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ // No title, no description // Always backgroundColor is black backgroundColor: 0x000000 }); /**** * Game Code ****/ // Load level function to initialize targets and reset game state function loadLevel(level) { currentLevel = level; maxArrows = levels[currentLevel].arrows; arrowCount = 0; arrows = []; initializeTargets(); gameActive = true; arrowsTxt.setText('Arrows: ' + (maxArrows - arrowCount) + '/' + maxArrows); levelTxt.setText('Level: ' + (currentLevel + 1)); // Update level display } var arrows = []; var targets = []; var currentArrow = null; var arrowCount = 0; var levels = [{ targets: 3, arrows: 5 }, { targets: 5, arrows: 7 }, { targets: 7, arrows: 9 }, { targets: 9, arrows: 11 }]; var currentLevel = 0; var maxArrows = levels[currentLevel].arrows; var totalScore = 0; var gameActive = true; var aimPoint = { x: 0, y: 0 }; var dragStart = { x: 0, y: 0 }; var isDragging = false; // Create the background var skyBackground = game.addChild(LK.getAsset('sky', { anchorX: 0, anchorY: 0, y: 0 })); var ground = game.addChild(LK.getAsset('ground', { anchorX: 0, anchorY: 0, y: 2532 // Position at bottom of screen })); // Create bow var bow = new Bow(); bow.x = 1024; // Center the bow horizontally on the screen bow.y = 2500; // Position near bottom game.addChild(bow); // Create power meter var powerMeter = new PowerMeter(); powerMeter.x = 100; powerMeter.y = 1800; game.addChild(powerMeter); // Create targets function createTarget(x, y, isStatic, speed) { var target = new Target(); target.x = x; target.y = y; target["static"] = isStatic; if (speed !== undefined) { target.speed = speed; } game.addChild(target); targets.push(target); return target; } // Create initial targets based on current level function initializeTargets() { targets = []; for (var i = 0; i < levels[currentLevel].targets; i++) { var x = 300 + Math.random() * 1448; // Random x position within bounds var y = 500 + i * 300; // Staggered y positions var speed = 2 + Math.random() * 4; // Random speed between 2 and 6 createTarget(x, y, false, speed); } } initializeTargets(); // Score display var scoreTxt = new Text2('Score: 0', { size: 80, fill: 0x0000FF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Arrows left display var arrowsTxt = new Text2('Arrows: ' + (maxArrows - arrowCount) + '/' + maxArrows, { size: 80, fill: 0x00FF00 }); arrowsTxt.anchor.set(0.5, 0); arrowsTxt.y = 100; LK.gui.top.addChild(arrowsTxt); // High score display var highScoreTxt = new Text2('High Score: ' + storage.highScore, { size: 60, fill: 0xFFA500 }); highScoreTxt.anchor.set(1, 0); highScoreTxt.y = 20; LK.gui.topRight.addChild(highScoreTxt); // Level display var levelTxt = new Text2('Level: ' + (currentLevel + 1), { size: 80, fill: 0x800080 }); levelTxt.anchor.set(0, 0); levelTxt.x = 20; levelTxt.y = 20; LK.gui.topLeft.addChild(levelTxt); // Aim guide var aimGuideTxt = new Text2('Touch and drag to aim, release to shoot', { size: 60, fill: 0xFF0000 }); aimGuideTxt.anchor.set(0.5, 0); aimGuideTxt.y = 200; LK.gui.top.addChild(aimGuideTxt); // Create a new arrow function createArrow() { if (arrowCount >= maxArrows) { if (arrows.length === 0) { // Check if all arrows have been used and none are flying LK.showGameOver(); // Trigger game over } return null; } var arrow = new Arrow(); arrow.x = bow.x; arrow.y = bow.y; arrow.rotation = bow.aimAngle; game.addChild(arrow); arrows.push(arrow); arrowCount++; // Update arrows left display arrowsTxt.setText('Arrows: ' + (maxArrows - arrowCount) + '/' + maxArrows); return arrow; } // Handle collision between arrow and targets function checkArrowCollisions() { for (var i = 0; i < arrows.length; i++) { var arrow = arrows[i]; if (arrow.flying && !arrow.stuck) { // Check collision with each target for (var j = 0; j < targets.length; j++) { var target = targets[j]; // Arrow tip position var tipX = arrow.x + Math.cos(arrow.rotation) * 65; var tipY = arrow.y + Math.sin(arrow.rotation) * 65; // Check if tip is inside target var dx = tipX - target.x; var dy = tipY - target.y; var distance = Math.sqrt(dx * dx + dy * dy); if (arrow.lastWasIntersecting === undefined) { arrow.lastWasIntersecting = false; } if (!arrow.lastWasIntersecting && distance <= target.radius) { if (!target._hit) { // Prevent double-count target._hit = true; arrow.lastWasIntersecting = true; // Ensure we update the intersection state // Hit! Calculate score var score = target.getScore(tipX, tipY); totalScore += score; scoreTxt.setText('Score: ' + totalScore); // Stick arrow to target and remove from screen arrow.stickTo(target, score); arrow.destroy(); arrows.splice(i, 1); // Create explosion effect var explosion = new Container(); var explosionParticle = LK.getAsset('target', { anchorX: 0.5, anchorY: 0.5 }); explosion.addChild(explosionParticle); explosion.x = target.x; explosion.y = target.y; game.addChild(explosion); // Animate explosion effect tween(explosion, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { explosion.destroy(); } }); // Remove the hit target targets.splice(j, 1); target.destroy(); arrow.lastWasIntersecting = false; // Reset intersection state for next checks // Create score popup var scorePop = new Text2("+" + score, { size: 60, fill: 0xFFFF00 }); scorePop.x = tipX; scorePop.y = tipY; game.addChild(scorePop); // Animate score popup tween(scorePop, { y: scorePop.y - 100, alpha: 0 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { scorePop.destroy(); } }); break; } arrow.lastWasIntersecting = distance <= target.radius; } } } // Move arrows that are stuck to targets with the targets if (arrow.stuck && arrow.targetHit) { var targetPos = arrow.targetHit; var dx = arrow.x - targetPos.x; var dy = arrow.y - targetPos.y; arrow.x = targetPos.x + dx; arrow.y = targetPos.y + dy; } } } // Check if game is over function checkGameOver() { if (arrowCount >= maxArrows && arrows.length === 0) { var allArrowsLanded = true; // Check if all arrows have landed for (var i = 0; i < arrows.length; i++) { if (arrows[i].flying) { allArrowsLanded = false; break; } } if (allArrowsLanded && gameActive) { gameActive = false; console.log("Game Over: All arrows used and not all targets hit."); // Debugging log // Update high score if needed if (totalScore > storage.highScore) { storage.highScore = totalScore; highScoreTxt.setText('High Score: ' + storage.highScore); } // Check if all targets are hit or score condition for level 1 is met if (targets.length === 0 || currentLevel === 0 && totalScore >= 30) { // Create level up text var levelUpTxt = new Text2('Level Up!', { size: 120, fill: 0xFFFF00 }); levelUpTxt.anchor.set(0.5, 0.5); levelUpTxt.x = 1024; // Center horizontally levelUpTxt.y = 1366; // Center vertically game.addChild(levelUpTxt); // Tween effect for level up text tween(levelUpTxt, { alpha: 0 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { levelUpTxt.destroy(); currentLevel++; if (currentLevel < levels.length) { // Advance to next level using loadLevel function loadLevel(currentLevel); levelTxt.setText('Level: 27'); // Update level text to 27 levels[currentLevel].targets += 5; // Add 5 targets initializeTargets(); // Reinitialize targets for the new level } else { // All levels completed, show game over LK.setTimeout(function () { LK.showGameOver(); }, 2000); } } }); } else { // Wait 2 seconds before showing game over LK.setTimeout(function () { LK.showGameOver(); }, 2000); } } } } // Game loop game.update = function () { // Update all targets for (var i = 0; i < targets.length; i++) { targets[i].update(); } // Update all arrows for (var i = 0; i < arrows.length; i++) { arrows[i].update(); } // Update bow charging if (bow.charging) { bow.updateCharging(); powerMeter.updatePower(bow.power, bow.maxPower); } // Check for collisions checkArrowCollisions(); // Check if game is over checkGameOver(); }; // Touch/mouse events game.down = function (x, y, obj) { if (!gameActive || arrowCount >= maxArrows) { return; } isDragging = true; dragStart.x = x; dragStart.y = y; // Start charging bow bow.startCharging(); // Hide aim guide aimGuideTxt.alpha = 0; // Create new arrow currentArrow = createArrow(); }; game.move = function (x, y, obj) { if (!isDragging || !currentArrow) { return; } // Calculate aim angle var dx = dragStart.x - x; var dy = dragStart.y - y; var angle = Math.atan2(dy, dx); // Constrain angle (can't aim down into ground or directly backward) if (angle > 0 && angle < Math.PI / 2) { angle = 0; } else if (angle > Math.PI / 2 && angle < Math.PI) { angle = Math.PI; } bow.aimAngle = angle; // Position current arrow currentArrow.x = bow.x; currentArrow.y = bow.y; currentArrow.rotation = angle; }; game.up = function (x, y, obj) { if (!isDragging || !currentArrow) { return; } isDragging = false; // Fire arrow with current power and angle var power = bow.releaseArrow(); currentArrow.fire(power, bow.aimAngle); // Reset power meter powerMeter.updatePower(0, bow.maxPower); currentArrow = null; }; // Play background music LK.playMusic('bgMusic');
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
var Arrow = Container.expand(function () {
var self = Container.call(this);
// Arrow body
var arrowBody = self.attachAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5
});
// Arrow tip
var arrowTip = self.attachAsset('arrowTip', {
anchorX: 0.5,
anchorY: 0.5,
x: 65
});
// Properties
self.velocity = {
x: 0,
y: 0
};
self.gravity = 0.3;
self.friction = 0.99;
self.flying = false;
self.stuck = false;
self.targetHit = null;
// Update arrow physics
self.update = function () {
if (self.lastX === undefined) {
self.lastX = self.x;
}
if (self.lastY === undefined) {
self.lastY = self.y;
}
if (self.flying && !self.stuck) {
// Apply physics
self.velocity.y += self.gravity;
self.x += self.velocity.x;
self.y += self.velocity.y;
// Update rotation to match trajectory
self.rotation = Math.atan2(self.velocity.y, self.velocity.x);
// Check if arrow is off-screen
if (self.y > 2732 || self.x < -100 || self.x > 2148) {
if (!self.hasHitGround) {
self.hasHitGround = true;
LK.getSound('miss').play();
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastWasIntersecting = false; // Reset intersection state for next checks
}
};
// Fire the arrow with given power and angle
self.fire = function (power, angle) {
self.flying = true;
self.velocity.x = Math.cos(angle) * power;
self.velocity.y = Math.sin(angle) * power;
self.rotation = angle;
LK.getSound('bowRelease').play();
};
// Stick arrow to target
self.stickTo = function (target, score) {
self.stuck = true;
self.flying = false;
self.targetHit = target;
self.score = score;
// Play sound
LK.getSound('targetHit').play();
};
return self;
});
var Bow = Container.expand(function () {
var self = Container.call(this);
// Bow body
var bowBody = self.attachAsset('bow', {
anchorX: 0.5,
anchorY: 0.5
});
// Bow string
var bowString = self.attachAsset('bowString', {
anchorX: 0.5,
anchorY: 0.5,
x: -10
});
// Properties
self.aimAngle = -Math.PI / 4; // Default angle (slightly up)
self.power = 0;
self.maxPower = 35;
self.charging = false;
// Update bow
self.update = function () {
// Update bow rotation to match aim angle
self.rotation = self.aimAngle;
// Update string position/appearance based on pull
if (self.charging) {
var pullFactor = self.power / self.maxPower;
bowString.scaleX = 1 + pullFactor * 0.5; // Thicker when pulled
} else {
bowString.scaleX = 1;
}
};
// Start charging (pulling back)
self.startCharging = function () {
self.charging = true;
self.power = 0;
};
// Continue charging
self.updateCharging = function () {
if (self.charging) {
if (!self.loadingSoundPlayed) {
LK.getSound('bowLoading').play();
self.loadingSoundPlayed = true;
}
self.power += 0.4;
if (self.power > self.maxPower) {
self.power = self.maxPower;
}
}
};
// Release arrow
self.releaseArrow = function () {
var finalPower = self.power;
self.charging = false;
self.loadingSoundPlayed = false;
self.power = 0;
return finalPower;
};
return self;
});
var PowerMeter = Container.expand(function () {
var self = Container.call(this);
// Background
var background = self.attachAsset('powerMeter', {
anchorX: 0.5,
anchorY: 0.5
});
// Fill
var fill = self.attachAsset('powerFill', {
anchorX: 0.5,
anchorY: 1,
// Anchor to bottom
y: 195 // Half height of background
});
fill.height = 0; // Start empty
// Update power meter
self.updatePower = function (power, maxPower) {
var percentage = power / maxPower;
fill.height = 390 * percentage; // 390 is the max height of fill
// Change color based on power
if (percentage < 0.33) {
fill.tint = 0x00FF00; // Green
} else if (percentage < 0.66) {
fill.tint = 0xFFFF00; // Yellow
} else {
fill.tint = 0xFF0000; // Red
}
};
return self;
});
var Target = Container.expand(function () {
var self = Container.call(this);
// Create rings from outside to inside
var outerRing = self.attachAsset('target', {
anchorX: 0.5,
anchorY: 0.5
});
var ring1 = self.attachAsset('targetRing1', {
anchorX: 0.5,
anchorY: 0.5
});
var ring2 = self.attachAsset('targetRing2', {
anchorX: 0.5,
anchorY: 0.5
});
var ring3 = self.attachAsset('targetRing3', {
anchorX: 0.5,
anchorY: 0.5
});
var bullseye = self.attachAsset('targetRing4', {
anchorX: 0.5,
anchorY: 0.5
});
// Target properties
self.radius = 100; // Outer radius
self.direction = 1; // 1 for right, -1 for left
self.speed = 2;
self.minX = 300;
self.maxX = 1748; // 2048 - 300
// Score values for each ring
self.scoreValues = [10, 25, 50, 75, 100]; // Outer to inner
// Update target movement
self.update = function () {
if (!self["static"]) {
self.x += self.speed * self.direction;
// Reverse direction at edges
if (self.x > self.maxX || self.x < self.minX) {
self.direction *= -1;
}
}
};
// Get score based on distance from center
self.getScore = function (hitX, hitY) {
// Calculate distance from center
var dx = hitX - self.x;
var dy = hitY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Determine which ring was hit
if (distance < 20) {
return self.scoreValues[4]; // Bullseye
} else if (distance < 40) {
return self.scoreValues[3];
} else if (distance < 60) {
return self.scoreValues[2];
} else if (distance < 80) {
return self.scoreValues[1];
} else if (distance < 100) {
return self.scoreValues[0]; // Outer ring
}
return 0; // Miss
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
// No title, no description
// Always backgroundColor is black
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Load level function to initialize targets and reset game state
function loadLevel(level) {
currentLevel = level;
maxArrows = levels[currentLevel].arrows;
arrowCount = 0;
arrows = [];
initializeTargets();
gameActive = true;
arrowsTxt.setText('Arrows: ' + (maxArrows - arrowCount) + '/' + maxArrows);
levelTxt.setText('Level: ' + (currentLevel + 1)); // Update level display
}
var arrows = [];
var targets = [];
var currentArrow = null;
var arrowCount = 0;
var levels = [{
targets: 3,
arrows: 5
}, {
targets: 5,
arrows: 7
}, {
targets: 7,
arrows: 9
}, {
targets: 9,
arrows: 11
}];
var currentLevel = 0;
var maxArrows = levels[currentLevel].arrows;
var totalScore = 0;
var gameActive = true;
var aimPoint = {
x: 0,
y: 0
};
var dragStart = {
x: 0,
y: 0
};
var isDragging = false;
// Create the background
var skyBackground = game.addChild(LK.getAsset('sky', {
anchorX: 0,
anchorY: 0,
y: 0
}));
var ground = game.addChild(LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
y: 2532 // Position at bottom of screen
}));
// Create bow
var bow = new Bow();
bow.x = 1024; // Center the bow horizontally on the screen
bow.y = 2500; // Position near bottom
game.addChild(bow);
// Create power meter
var powerMeter = new PowerMeter();
powerMeter.x = 100;
powerMeter.y = 1800;
game.addChild(powerMeter);
// Create targets
function createTarget(x, y, isStatic, speed) {
var target = new Target();
target.x = x;
target.y = y;
target["static"] = isStatic;
if (speed !== undefined) {
target.speed = speed;
}
game.addChild(target);
targets.push(target);
return target;
}
// Create initial targets based on current level
function initializeTargets() {
targets = [];
for (var i = 0; i < levels[currentLevel].targets; i++) {
var x = 300 + Math.random() * 1448; // Random x position within bounds
var y = 500 + i * 300; // Staggered y positions
var speed = 2 + Math.random() * 4; // Random speed between 2 and 6
createTarget(x, y, false, speed);
}
}
initializeTargets();
// Score display
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0x0000FF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Arrows left display
var arrowsTxt = new Text2('Arrows: ' + (maxArrows - arrowCount) + '/' + maxArrows, {
size: 80,
fill: 0x00FF00
});
arrowsTxt.anchor.set(0.5, 0);
arrowsTxt.y = 100;
LK.gui.top.addChild(arrowsTxt);
// High score display
var highScoreTxt = new Text2('High Score: ' + storage.highScore, {
size: 60,
fill: 0xFFA500
});
highScoreTxt.anchor.set(1, 0);
highScoreTxt.y = 20;
LK.gui.topRight.addChild(highScoreTxt);
// Level display
var levelTxt = new Text2('Level: ' + (currentLevel + 1), {
size: 80,
fill: 0x800080
});
levelTxt.anchor.set(0, 0);
levelTxt.x = 20;
levelTxt.y = 20;
LK.gui.topLeft.addChild(levelTxt);
// Aim guide
var aimGuideTxt = new Text2('Touch and drag to aim, release to shoot', {
size: 60,
fill: 0xFF0000
});
aimGuideTxt.anchor.set(0.5, 0);
aimGuideTxt.y = 200;
LK.gui.top.addChild(aimGuideTxt);
// Create a new arrow
function createArrow() {
if (arrowCount >= maxArrows) {
if (arrows.length === 0) {
// Check if all arrows have been used and none are flying
LK.showGameOver(); // Trigger game over
}
return null;
}
var arrow = new Arrow();
arrow.x = bow.x;
arrow.y = bow.y;
arrow.rotation = bow.aimAngle;
game.addChild(arrow);
arrows.push(arrow);
arrowCount++;
// Update arrows left display
arrowsTxt.setText('Arrows: ' + (maxArrows - arrowCount) + '/' + maxArrows);
return arrow;
}
// Handle collision between arrow and targets
function checkArrowCollisions() {
for (var i = 0; i < arrows.length; i++) {
var arrow = arrows[i];
if (arrow.flying && !arrow.stuck) {
// Check collision with each target
for (var j = 0; j < targets.length; j++) {
var target = targets[j];
// Arrow tip position
var tipX = arrow.x + Math.cos(arrow.rotation) * 65;
var tipY = arrow.y + Math.sin(arrow.rotation) * 65;
// Check if tip is inside target
var dx = tipX - target.x;
var dy = tipY - target.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (arrow.lastWasIntersecting === undefined) {
arrow.lastWasIntersecting = false;
}
if (!arrow.lastWasIntersecting && distance <= target.radius) {
if (!target._hit) {
// Prevent double-count
target._hit = true;
arrow.lastWasIntersecting = true; // Ensure we update the intersection state
// Hit! Calculate score
var score = target.getScore(tipX, tipY);
totalScore += score;
scoreTxt.setText('Score: ' + totalScore);
// Stick arrow to target and remove from screen
arrow.stickTo(target, score);
arrow.destroy();
arrows.splice(i, 1);
// Create explosion effect
var explosion = new Container();
var explosionParticle = LK.getAsset('target', {
anchorX: 0.5,
anchorY: 0.5
});
explosion.addChild(explosionParticle);
explosion.x = target.x;
explosion.y = target.y;
game.addChild(explosion);
// Animate explosion effect
tween(explosion, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
explosion.destroy();
}
});
// Remove the hit target
targets.splice(j, 1);
target.destroy();
arrow.lastWasIntersecting = false; // Reset intersection state for next checks
// Create score popup
var scorePop = new Text2("+" + score, {
size: 60,
fill: 0xFFFF00
});
scorePop.x = tipX;
scorePop.y = tipY;
game.addChild(scorePop);
// Animate score popup
tween(scorePop, {
y: scorePop.y - 100,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
scorePop.destroy();
}
});
break;
}
arrow.lastWasIntersecting = distance <= target.radius;
}
}
}
// Move arrows that are stuck to targets with the targets
if (arrow.stuck && arrow.targetHit) {
var targetPos = arrow.targetHit;
var dx = arrow.x - targetPos.x;
var dy = arrow.y - targetPos.y;
arrow.x = targetPos.x + dx;
arrow.y = targetPos.y + dy;
}
}
}
// Check if game is over
function checkGameOver() {
if (arrowCount >= maxArrows && arrows.length === 0) {
var allArrowsLanded = true;
// Check if all arrows have landed
for (var i = 0; i < arrows.length; i++) {
if (arrows[i].flying) {
allArrowsLanded = false;
break;
}
}
if (allArrowsLanded && gameActive) {
gameActive = false;
console.log("Game Over: All arrows used and not all targets hit."); // Debugging log
// Update high score if needed
if (totalScore > storage.highScore) {
storage.highScore = totalScore;
highScoreTxt.setText('High Score: ' + storage.highScore);
}
// Check if all targets are hit or score condition for level 1 is met
if (targets.length === 0 || currentLevel === 0 && totalScore >= 30) {
// Create level up text
var levelUpTxt = new Text2('Level Up!', {
size: 120,
fill: 0xFFFF00
});
levelUpTxt.anchor.set(0.5, 0.5);
levelUpTxt.x = 1024; // Center horizontally
levelUpTxt.y = 1366; // Center vertically
game.addChild(levelUpTxt);
// Tween effect for level up text
tween(levelUpTxt, {
alpha: 0
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
levelUpTxt.destroy();
currentLevel++;
if (currentLevel < levels.length) {
// Advance to next level using loadLevel function
loadLevel(currentLevel);
levelTxt.setText('Level: 27'); // Update level text to 27
levels[currentLevel].targets += 5; // Add 5 targets
initializeTargets(); // Reinitialize targets for the new level
} else {
// All levels completed, show game over
LK.setTimeout(function () {
LK.showGameOver();
}, 2000);
}
}
});
} else {
// Wait 2 seconds before showing game over
LK.setTimeout(function () {
LK.showGameOver();
}, 2000);
}
}
}
}
// Game loop
game.update = function () {
// Update all targets
for (var i = 0; i < targets.length; i++) {
targets[i].update();
}
// Update all arrows
for (var i = 0; i < arrows.length; i++) {
arrows[i].update();
}
// Update bow charging
if (bow.charging) {
bow.updateCharging();
powerMeter.updatePower(bow.power, bow.maxPower);
}
// Check for collisions
checkArrowCollisions();
// Check if game is over
checkGameOver();
};
// Touch/mouse events
game.down = function (x, y, obj) {
if (!gameActive || arrowCount >= maxArrows) {
return;
}
isDragging = true;
dragStart.x = x;
dragStart.y = y;
// Start charging bow
bow.startCharging();
// Hide aim guide
aimGuideTxt.alpha = 0;
// Create new arrow
currentArrow = createArrow();
};
game.move = function (x, y, obj) {
if (!isDragging || !currentArrow) {
return;
}
// Calculate aim angle
var dx = dragStart.x - x;
var dy = dragStart.y - y;
var angle = Math.atan2(dy, dx);
// Constrain angle (can't aim down into ground or directly backward)
if (angle > 0 && angle < Math.PI / 2) {
angle = 0;
} else if (angle > Math.PI / 2 && angle < Math.PI) {
angle = Math.PI;
}
bow.aimAngle = angle;
// Position current arrow
currentArrow.x = bow.x;
currentArrow.y = bow.y;
currentArrow.rotation = angle;
};
game.up = function (x, y, obj) {
if (!isDragging || !currentArrow) {
return;
}
isDragging = false;
// Fire arrow with current power and angle
var power = bow.releaseArrow();
currentArrow.fire(power, bow.aimAngle);
// Reset power meter
powerMeter.updatePower(0, bow.maxPower);
currentArrow = null;
};
// Play background music
LK.playMusic('bgMusic');
a sharp cartoon-style arrow flying to the right, with colorful string, 2D game asset, minimal shading, pixel-perfect edges, isolated, flat art style. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A 2D flat digital illustration of a sharp and colorful cartoonish sleek arrow tip, designed for a mobile archery game. The arrow tip should be metallic (steel or iron), with subtle gradients for a polished look. It should have a pointed triangular shape with a slightly stylized, game-friendly appearance, matching the clean, minimal aesthetic of vector-based graphics. Use a transparent background and ensure it's facing upward. Resolution: 512x512.". Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
"A 2D digital illustration of a bright, cartoony sky background for a mobile archery game. The sky is a soft gradient from light blue at the top to pale near the horizon. Include fluffy white clouds scattered naturally. Style is flat, colorful, and minimal—perfect for a fun, casual game. No sun or dramatic lighting. Resolution: 1920x1080. Seamless and loopable edges. Transparent-free, clean background.". Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows