/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // FallingArrow: a single falling arrow var FallingArrow = Container.expand(function () { var self = Container.call(this); // Arrow type: 'up', 'down', 'left', 'right' self.arrowType = 'up'; self.speed = 8; // Will be set on spawn // Attach arrow asset, set anchor to center self.arrowAsset = null; self.setArrowType = function (type) { self.arrowType = type; if (self.arrowAsset) { self.removeChild(self.arrowAsset); } var assetId = ''; if (type === 'up') assetId = 'arrowUp'; if (type === 'down') assetId = 'arrowDown'; if (type === 'left') assetId = 'arrowLeft'; if (type === 'right') assetId = 'arrowRight'; self.arrowAsset = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); // Rotate asset to match direction if (type === 'up') self.arrowAsset.rotation = 0; if (type === 'down') self.arrowAsset.rotation = Math.PI; if (type === 'left') self.arrowAsset.rotation = -Math.PI / 2; if (type === 'right') self.arrowAsset.rotation = Math.PI / 2; }; // Called every tick self.update = function () { self.y += self.speed; }; return self; }); // FeedbackText: floating feedback text (e.g. "Very Good", "Bad", etc) var FeedbackText = Container.expand(function () { var self = Container.call(this); self.textObj = new Text2('', { size: 120, fill: "#fff", font: "Impact" }); self.textObj.anchor.set(0.5, 0.5); self.addChild(self.textObj); self.show = function (msg, color) { self.textObj.setText(msg); // Use setStyle to update fill color, as direct assignment may not work in this context self.textObj.setStyle({ fill: color }); self.alpha = 1; // Animate up and fade out tween(self, { y: self.y - 120, alpha: 0 }, { duration: 700, easing: tween.cubicOut, onFinish: function onFinish() { if (self.parent) self.parent.removeChild(self); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87ceeb // Sky blue background for a sky effect }); /**** * Game Code ****/ // Add several semi-transparent white ellipses as clouds // --- Clouds background --- // No need to init Text2 assets, just use Text2 // Feedback text colors // Target zone highlight // Arrow shapes (up, down, left, right) - colored for clarity // --- Constants --- var cloudConfigs = [{ x: 400, y: 350, width: 520, height: 180 }, { x: 1200, y: 420, width: 400, height: 140 }, { x: 900, y: 200, width: 320, height: 110 }, { x: 1700, y: 300, width: 380, height: 120 }, { x: 600, y: 700, width: 600, height: 200 }, { x: 1500, y: 800, width: 500, height: 170 }]; for (var i = 0; i < cloudConfigs.length; i++) { var cfg = cloudConfigs[i]; var cloud = LK.getAsset('cloudShape' + i, { width: cfg.width, height: cfg.height, color: 0xffffff, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5, x: cfg.x, y: cfg.y }); cloud.alpha = 1; game.addChildAt(cloud, 0); // Add behind all other elements } var ARROW_TYPES = ['left', 'down', 'up', 'right']; var ARROW_COLORS = { 'up': '#4fc3f7', 'down': '#ffb74d', 'left': '#81c784', 'right': '#e57373' }; var FEEDBACKS = [{ msg: "Very Good", color: 0x00E676 }, { msg: "Good", color: 0xFFD600 }, { msg: "Bad", color: 0xFF3D00 }, { msg: "Disgusting", color: 0xB71C1C }]; // --- Layout --- var GAME_W = 2048, GAME_H = 2732; var ARROW_SIZE = 180; var BUTTON_SIZE = 220; var BUTTON_MARGIN = 60; var TARGET_ZONE_HEIGHT = 220; var TARGET_ZONE_Y = GAME_H - 600; // --- State --- var fallingArrows = []; var arrowSpawnInterval = 60; // ticks between spawns (start slow) var arrowSpeed = 8; // px per tick (start slow) var minArrowInterval = 24; // fastest spawn interval var maxArrowSpeed = 32; // fastest speed var ticksSinceLastArrow = 0; var score = 0; var combo = 0; var lastArrowTypeTapped = null; var gameOver = false; // --- Monster event state --- var monsterEventActive = false; var monsterEventStartTick = null; var monsterEventDuration = 600; // 10 seconds at 60fps var monsterEventTriggered = false; var normalArrowSpawnInterval = 60; var normalArrowSpeed = 8; // --- UI Elements --- // Add player's skin image to the left side of the screen var skinImg = LK.getAsset('Skin', { anchorX: 0.5, anchorY: 0.5, x: 200, // Move higher: raise the y position by 220px (was 120px) y: TARGET_ZONE_Y + TARGET_ZONE_HEIGHT / 2 - 220, width: 320, height: 320 }); game.addChild(skinImg); // Target zone highlight var targetZone = LK.getAsset('targetZone', { anchorX: 0, anchorY: 0, x: 0, y: TARGET_ZONE_Y, width: GAME_W, height: TARGET_ZONE_HEIGHT }); targetZone.alpha = 0.18; game.addChild(targetZone); // Score text var scoreTxt = new Text2('0', { size: 120, fill: "#fff", font: "Impact" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Feedback text (floating, created as needed) // --- Arrow Button Controls --- var buttonY = GAME_H - BUTTON_SIZE / 2 - 60; var buttonXs = [GAME_W / 2 - (BUTTON_SIZE * 1.5 + BUTTON_MARGIN), GAME_W / 2 - (BUTTON_SIZE / 2 + BUTTON_MARGIN / 2), GAME_W / 2 + (BUTTON_SIZE / 2 + BUTTON_MARGIN / 2), GAME_W / 2 + (BUTTON_SIZE * 1.5 + BUTTON_MARGIN)]; var arrowButtonOrder = ['left', 'down', 'up', 'right']; var arrowButtons = []; // Create arrow buttons for (var i = 0; i < 4; i++) { (function (i) { var type = arrowButtonOrder[i]; var btn = new Container(); var assetId = ''; if (type === 'up') assetId = 'arrowUp'; if (type === 'down') assetId = 'arrowDown'; if (type === 'left') assetId = 'arrowLeft'; if (type === 'right') assetId = 'arrowRight'; var arrowAsset = btn.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5, width: BUTTON_SIZE - 20, height: BUTTON_SIZE - 20 }); // Rotate to match direction if (type === 'up') arrowAsset.rotation = 0; if (type === 'down') arrowAsset.rotation = Math.PI; if (type === 'left') arrowAsset.rotation = -Math.PI / 2; if (type === 'right') arrowAsset.rotation = Math.PI / 2; btn.x = buttonXs[i]; btn.y = buttonY; btn.type = type; btn.interactive = true; // Add a subtle background highlight var bg = LK.getAsset('targetZone', { anchorX: 0.5, anchorY: 0.5, width: BUTTON_SIZE, height: BUTTON_SIZE, x: 0, y: 0 }); bg.alpha = 0.10; btn.addChildAt(bg, 0); // Touch/click handler btn.down = function (x, y, obj) { handleArrowButtonPress(type); }; game.addChild(btn); arrowButtons.push(btn); })(i); } // --- Helper: Find the closest arrow in the target zone for a given type --- function findMatchingArrowInZone(type) { var best = null; var bestDist = 99999; for (var i = 0; i < fallingArrows.length; i++) { var arr = fallingArrows[i]; if (arr.arrowType !== type) continue; // Check if in target zone var arrowY = arr.y; if (arrowY >= TARGET_ZONE_Y && arrowY <= TARGET_ZONE_Y + TARGET_ZONE_HEIGHT) { // Closer to center of zone is better var dist = Math.abs(arrowY - (TARGET_ZONE_Y + TARGET_ZONE_HEIGHT / 2)); if (dist < bestDist) { best = arr; bestDist = dist; } } } return best; } // --- Helper: Show feedback text at a position --- function showFeedback(msg, color, x, y) { // Determine color: green for good, red for bad var feedbackColor; if (msg === "Very Good" || msg === "Good") { feedbackColor = "#00e676"; // green } else { feedbackColor = "#ff1744"; // red } var fb = new FeedbackText(); fb.x = x; fb.y = y; fb.show(msg, feedbackColor); game.addChild(fb); } // --- Handle arrow button press --- function handleArrowButtonPress(type) { if (gameOver) return; // --- Begin: Temporarily increase KSK music volume for 1s when arrow pressed --- LK.playMusic('Ksk', { fade: { start: 1, end: 2, duration: 50 } }); LK.setTimeout(function () { LK.playMusic('Ksk', { fade: { start: 2, end: 1, duration: 200 } }); }, 1000); // --- End: music volume boost --- var arr = findMatchingArrowInZone(type); if (arr) { // Good timing! var centerY = arr.y; var dist = Math.abs(centerY - (TARGET_ZONE_Y + TARGET_ZONE_HEIGHT / 2)); var feedback, points; if (dist < 30) { feedback = FEEDBACKS[0]; // Very Good points = 3; } else if (dist < 60) { feedback = FEEDBACKS[1]; // Good points = 2; } else { feedback = FEEDBACKS[2]; // Bad points = 1; } score += points; combo += 1; showFeedback(feedback.msg, feedback.color, arr.x, arr.y - 100); // Remove arrow arr.destroy(); for (var i = 0; i < fallingArrows.length; i++) { if (fallingArrows[i] === arr) { fallingArrows.splice(i, 1); break; } } // Animate button var btn = null; for (var i = 0; i < arrowButtons.length; i++) { if (arrowButtons[i].type === type) btn = arrowButtons[i]; } if (btn) { tween(btn, { scaleX: 1.2, scaleY: 1.2 }, { duration: 80, easing: tween.cubicOut, onFinish: function onFinish() { tween(btn, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.cubicIn }); } }); } } else { // No matching arrow in zone: mistake! combo = 0; showFeedback(FEEDBACKS[3].msg, FEEDBACKS[3].color, GAME_W / 2, TARGET_ZONE_Y + TARGET_ZONE_HEIGHT / 2); // Animate all buttons red for (var i = 0; i < arrowButtons.length; i++) { var btn = arrowButtons[i]; tween(btn, { alpha: 0.5 }, { duration: 80, onFinish: function onFinish() { tween(btn, { alpha: 1 }, { duration: 120 }); } }); } } scoreTxt.setText(score); } // --- Spawn a new falling arrow --- function spawnArrow() { var type = ARROW_TYPES[Math.floor(Math.random() * 4)]; var arr = new FallingArrow(); arr.setArrowType(type); arr.arrowType = type; arr.speed = arrowSpeed; var idx = arrowButtonOrder.indexOf(type); arr.x = buttonXs[idx]; arr.y = -ARROW_SIZE / 2; fallingArrows.push(arr); game.addChild(arr); } // --- Game update loop --- game.update = function () { if (gameOver) return; // Spawn arrows ticksSinceLastArrow++; if (ticksSinceLastArrow >= arrowSpawnInterval) { if (monsterEventActive) { // Spawn only 1 arrow during monster event (make it easier) var arr = new FallingArrow(); var type = ARROW_TYPES[Math.floor(Math.random() * 4)]; arr.setArrowType(type); arr.arrowType = type; arr.speed = arrowSpeed; var idx = arrowButtonOrder.indexOf(type); arr.x = buttonXs[idx]; arr.y = -ARROW_SIZE / 2; fallingArrows.push(arr); game.addChild(arr); } else { // Spawn arrows in pairs spawnArrow(); } ticksSinceLastArrow = 0; } // Update arrows, check for misses for (var i = fallingArrows.length - 1; i >= 0; i--) { var arr = fallingArrows[i]; // If arrow passes target zone without being hit if (arr.y > TARGET_ZONE_Y + TARGET_ZONE_HEIGHT + ARROW_SIZE / 2) { // Missed! showFeedback(FEEDBACKS[2].msg, FEEDBACKS[2].color, arr.x, TARGET_ZONE_Y + TARGET_ZONE_HEIGHT / 2); combo = 0; arr.destroy(); fallingArrows.splice(i, 1); continue; } } // --- Monster event logic --- if (!monsterEventTriggered && LK.ticks >= 1200) { // 20 seconds * 60fps monsterEventActive = true; monsterEventTriggered = true; monsterEventStartTick = LK.ticks; // Save normal values normalArrowSpawnInterval = arrowSpawnInterval; normalArrowSpeed = arrowSpeed; // Make arrows slightly faster and a bit harder during monster event arrowSpawnInterval = 40; // slightly faster than before arrowSpeed = 10; // slightly faster than before } if (monsterEventActive && LK.ticks - monsterEventStartTick >= monsterEventDuration) { // Revert to normal after 10 seconds monsterEventActive = false; arrowSpawnInterval = normalArrowSpawnInterval; arrowSpeed = normalArrowSpeed; } // Gradually increase difficulty (skip if monster event is active) if (!monsterEventActive && LK.ticks % 300 === 0) { // every 5 seconds if (arrowSpawnInterval > minArrowInterval) arrowSpawnInterval -= 2; if (arrowSpeed < maxArrowSpeed) arrowSpeed += 1; } }; // --- Touch handling for game area (ignore, only arrow buttons are interactive) --- game.down = function (x, y, obj) { // Do nothing }; game.move = function (x, y, obj) { // Do nothing }; game.up = function (x, y, obj) { // Do nothing }; // --- Reset state on game over --- game.onGameOver = function () { gameOver = true; // Remove all arrows for (var i = 0; i < fallingArrows.length; i++) { fallingArrows[i].destroy(); } fallingArrows = []; monsterEventActive = false; monsterEventTriggered = false; monsterEventStartTick = null; arrowSpawnInterval = normalArrowSpawnInterval; arrowSpeed = normalArrowSpeed; }; // --- Score display update --- scoreTxt.setText(score); // --- Start the game --- score = 0; combo = 0; arrowSpawnInterval = 60; arrowSpeed = 8; normalArrowSpawnInterval = 60; normalArrowSpeed = 8; monsterEventActive = false; monsterEventTriggered = false; monsterEventStartTick = null; gameOver = false; fallingArrows = []; ticksSinceLastArrow = 0; // Play 'Ksk' music at game start LK.playMusic('Ksk');
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// FallingArrow: a single falling arrow
var FallingArrow = Container.expand(function () {
var self = Container.call(this);
// Arrow type: 'up', 'down', 'left', 'right'
self.arrowType = 'up';
self.speed = 8; // Will be set on spawn
// Attach arrow asset, set anchor to center
self.arrowAsset = null;
self.setArrowType = function (type) {
self.arrowType = type;
if (self.arrowAsset) {
self.removeChild(self.arrowAsset);
}
var assetId = '';
if (type === 'up') assetId = 'arrowUp';
if (type === 'down') assetId = 'arrowDown';
if (type === 'left') assetId = 'arrowLeft';
if (type === 'right') assetId = 'arrowRight';
self.arrowAsset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Rotate asset to match direction
if (type === 'up') self.arrowAsset.rotation = 0;
if (type === 'down') self.arrowAsset.rotation = Math.PI;
if (type === 'left') self.arrowAsset.rotation = -Math.PI / 2;
if (type === 'right') self.arrowAsset.rotation = Math.PI / 2;
};
// Called every tick
self.update = function () {
self.y += self.speed;
};
return self;
});
// FeedbackText: floating feedback text (e.g. "Very Good", "Bad", etc)
var FeedbackText = Container.expand(function () {
var self = Container.call(this);
self.textObj = new Text2('', {
size: 120,
fill: "#fff",
font: "Impact"
});
self.textObj.anchor.set(0.5, 0.5);
self.addChild(self.textObj);
self.show = function (msg, color) {
self.textObj.setText(msg);
// Use setStyle to update fill color, as direct assignment may not work in this context
self.textObj.setStyle({
fill: color
});
self.alpha = 1;
// Animate up and fade out
tween(self, {
y: self.y - 120,
alpha: 0
}, {
duration: 700,
easing: tween.cubicOut,
onFinish: function onFinish() {
if (self.parent) self.parent.removeChild(self);
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Sky blue background for a sky effect
});
/****
* Game Code
****/
// Add several semi-transparent white ellipses as clouds
// --- Clouds background ---
// No need to init Text2 assets, just use Text2
// Feedback text colors
// Target zone highlight
// Arrow shapes (up, down, left, right) - colored for clarity
// --- Constants ---
var cloudConfigs = [{
x: 400,
y: 350,
width: 520,
height: 180
}, {
x: 1200,
y: 420,
width: 400,
height: 140
}, {
x: 900,
y: 200,
width: 320,
height: 110
}, {
x: 1700,
y: 300,
width: 380,
height: 120
}, {
x: 600,
y: 700,
width: 600,
height: 200
}, {
x: 1500,
y: 800,
width: 500,
height: 170
}];
for (var i = 0; i < cloudConfigs.length; i++) {
var cfg = cloudConfigs[i];
var cloud = LK.getAsset('cloudShape' + i, {
width: cfg.width,
height: cfg.height,
color: 0xffffff,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: cfg.x,
y: cfg.y
});
cloud.alpha = 1;
game.addChildAt(cloud, 0); // Add behind all other elements
}
var ARROW_TYPES = ['left', 'down', 'up', 'right'];
var ARROW_COLORS = {
'up': '#4fc3f7',
'down': '#ffb74d',
'left': '#81c784',
'right': '#e57373'
};
var FEEDBACKS = [{
msg: "Very Good",
color: 0x00E676
}, {
msg: "Good",
color: 0xFFD600
}, {
msg: "Bad",
color: 0xFF3D00
}, {
msg: "Disgusting",
color: 0xB71C1C
}];
// --- Layout ---
var GAME_W = 2048,
GAME_H = 2732;
var ARROW_SIZE = 180;
var BUTTON_SIZE = 220;
var BUTTON_MARGIN = 60;
var TARGET_ZONE_HEIGHT = 220;
var TARGET_ZONE_Y = GAME_H - 600;
// --- State ---
var fallingArrows = [];
var arrowSpawnInterval = 60; // ticks between spawns (start slow)
var arrowSpeed = 8; // px per tick (start slow)
var minArrowInterval = 24; // fastest spawn interval
var maxArrowSpeed = 32; // fastest speed
var ticksSinceLastArrow = 0;
var score = 0;
var combo = 0;
var lastArrowTypeTapped = null;
var gameOver = false;
// --- Monster event state ---
var monsterEventActive = false;
var monsterEventStartTick = null;
var monsterEventDuration = 600; // 10 seconds at 60fps
var monsterEventTriggered = false;
var normalArrowSpawnInterval = 60;
var normalArrowSpeed = 8;
// --- UI Elements ---
// Add player's skin image to the left side of the screen
var skinImg = LK.getAsset('Skin', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
// Move higher: raise the y position by 220px (was 120px)
y: TARGET_ZONE_Y + TARGET_ZONE_HEIGHT / 2 - 220,
width: 320,
height: 320
});
game.addChild(skinImg);
// Target zone highlight
var targetZone = LK.getAsset('targetZone', {
anchorX: 0,
anchorY: 0,
x: 0,
y: TARGET_ZONE_Y,
width: GAME_W,
height: TARGET_ZONE_HEIGHT
});
targetZone.alpha = 0.18;
game.addChild(targetZone);
// Score text
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff",
font: "Impact"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Feedback text (floating, created as needed)
// --- Arrow Button Controls ---
var buttonY = GAME_H - BUTTON_SIZE / 2 - 60;
var buttonXs = [GAME_W / 2 - (BUTTON_SIZE * 1.5 + BUTTON_MARGIN), GAME_W / 2 - (BUTTON_SIZE / 2 + BUTTON_MARGIN / 2), GAME_W / 2 + (BUTTON_SIZE / 2 + BUTTON_MARGIN / 2), GAME_W / 2 + (BUTTON_SIZE * 1.5 + BUTTON_MARGIN)];
var arrowButtonOrder = ['left', 'down', 'up', 'right'];
var arrowButtons = [];
// Create arrow buttons
for (var i = 0; i < 4; i++) {
(function (i) {
var type = arrowButtonOrder[i];
var btn = new Container();
var assetId = '';
if (type === 'up') assetId = 'arrowUp';
if (type === 'down') assetId = 'arrowDown';
if (type === 'left') assetId = 'arrowLeft';
if (type === 'right') assetId = 'arrowRight';
var arrowAsset = btn.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
width: BUTTON_SIZE - 20,
height: BUTTON_SIZE - 20
});
// Rotate to match direction
if (type === 'up') arrowAsset.rotation = 0;
if (type === 'down') arrowAsset.rotation = Math.PI;
if (type === 'left') arrowAsset.rotation = -Math.PI / 2;
if (type === 'right') arrowAsset.rotation = Math.PI / 2;
btn.x = buttonXs[i];
btn.y = buttonY;
btn.type = type;
btn.interactive = true;
// Add a subtle background highlight
var bg = LK.getAsset('targetZone', {
anchorX: 0.5,
anchorY: 0.5,
width: BUTTON_SIZE,
height: BUTTON_SIZE,
x: 0,
y: 0
});
bg.alpha = 0.10;
btn.addChildAt(bg, 0);
// Touch/click handler
btn.down = function (x, y, obj) {
handleArrowButtonPress(type);
};
game.addChild(btn);
arrowButtons.push(btn);
})(i);
}
// --- Helper: Find the closest arrow in the target zone for a given type ---
function findMatchingArrowInZone(type) {
var best = null;
var bestDist = 99999;
for (var i = 0; i < fallingArrows.length; i++) {
var arr = fallingArrows[i];
if (arr.arrowType !== type) continue;
// Check if in target zone
var arrowY = arr.y;
if (arrowY >= TARGET_ZONE_Y && arrowY <= TARGET_ZONE_Y + TARGET_ZONE_HEIGHT) {
// Closer to center of zone is better
var dist = Math.abs(arrowY - (TARGET_ZONE_Y + TARGET_ZONE_HEIGHT / 2));
if (dist < bestDist) {
best = arr;
bestDist = dist;
}
}
}
return best;
}
// --- Helper: Show feedback text at a position ---
function showFeedback(msg, color, x, y) {
// Determine color: green for good, red for bad
var feedbackColor;
if (msg === "Very Good" || msg === "Good") {
feedbackColor = "#00e676"; // green
} else {
feedbackColor = "#ff1744"; // red
}
var fb = new FeedbackText();
fb.x = x;
fb.y = y;
fb.show(msg, feedbackColor);
game.addChild(fb);
}
// --- Handle arrow button press ---
function handleArrowButtonPress(type) {
if (gameOver) return;
// --- Begin: Temporarily increase KSK music volume for 1s when arrow pressed ---
LK.playMusic('Ksk', {
fade: {
start: 1,
end: 2,
duration: 50
}
});
LK.setTimeout(function () {
LK.playMusic('Ksk', {
fade: {
start: 2,
end: 1,
duration: 200
}
});
}, 1000);
// --- End: music volume boost ---
var arr = findMatchingArrowInZone(type);
if (arr) {
// Good timing!
var centerY = arr.y;
var dist = Math.abs(centerY - (TARGET_ZONE_Y + TARGET_ZONE_HEIGHT / 2));
var feedback, points;
if (dist < 30) {
feedback = FEEDBACKS[0]; // Very Good
points = 3;
} else if (dist < 60) {
feedback = FEEDBACKS[1]; // Good
points = 2;
} else {
feedback = FEEDBACKS[2]; // Bad
points = 1;
}
score += points;
combo += 1;
showFeedback(feedback.msg, feedback.color, arr.x, arr.y - 100);
// Remove arrow
arr.destroy();
for (var i = 0; i < fallingArrows.length; i++) {
if (fallingArrows[i] === arr) {
fallingArrows.splice(i, 1);
break;
}
}
// Animate button
var btn = null;
for (var i = 0; i < arrowButtons.length; i++) {
if (arrowButtons[i].type === type) btn = arrowButtons[i];
}
if (btn) {
tween(btn, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(btn, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
}
} else {
// No matching arrow in zone: mistake!
combo = 0;
showFeedback(FEEDBACKS[3].msg, FEEDBACKS[3].color, GAME_W / 2, TARGET_ZONE_Y + TARGET_ZONE_HEIGHT / 2);
// Animate all buttons red
for (var i = 0; i < arrowButtons.length; i++) {
var btn = arrowButtons[i];
tween(btn, {
alpha: 0.5
}, {
duration: 80,
onFinish: function onFinish() {
tween(btn, {
alpha: 1
}, {
duration: 120
});
}
});
}
}
scoreTxt.setText(score);
}
// --- Spawn a new falling arrow ---
function spawnArrow() {
var type = ARROW_TYPES[Math.floor(Math.random() * 4)];
var arr = new FallingArrow();
arr.setArrowType(type);
arr.arrowType = type;
arr.speed = arrowSpeed;
var idx = arrowButtonOrder.indexOf(type);
arr.x = buttonXs[idx];
arr.y = -ARROW_SIZE / 2;
fallingArrows.push(arr);
game.addChild(arr);
}
// --- Game update loop ---
game.update = function () {
if (gameOver) return;
// Spawn arrows
ticksSinceLastArrow++;
if (ticksSinceLastArrow >= arrowSpawnInterval) {
if (monsterEventActive) {
// Spawn only 1 arrow during monster event (make it easier)
var arr = new FallingArrow();
var type = ARROW_TYPES[Math.floor(Math.random() * 4)];
arr.setArrowType(type);
arr.arrowType = type;
arr.speed = arrowSpeed;
var idx = arrowButtonOrder.indexOf(type);
arr.x = buttonXs[idx];
arr.y = -ARROW_SIZE / 2;
fallingArrows.push(arr);
game.addChild(arr);
} else {
// Spawn arrows in pairs
spawnArrow();
}
ticksSinceLastArrow = 0;
}
// Update arrows, check for misses
for (var i = fallingArrows.length - 1; i >= 0; i--) {
var arr = fallingArrows[i];
// If arrow passes target zone without being hit
if (arr.y > TARGET_ZONE_Y + TARGET_ZONE_HEIGHT + ARROW_SIZE / 2) {
// Missed!
showFeedback(FEEDBACKS[2].msg, FEEDBACKS[2].color, arr.x, TARGET_ZONE_Y + TARGET_ZONE_HEIGHT / 2);
combo = 0;
arr.destroy();
fallingArrows.splice(i, 1);
continue;
}
}
// --- Monster event logic ---
if (!monsterEventTriggered && LK.ticks >= 1200) {
// 20 seconds * 60fps
monsterEventActive = true;
monsterEventTriggered = true;
monsterEventStartTick = LK.ticks;
// Save normal values
normalArrowSpawnInterval = arrowSpawnInterval;
normalArrowSpeed = arrowSpeed;
// Make arrows slightly faster and a bit harder during monster event
arrowSpawnInterval = 40; // slightly faster than before
arrowSpeed = 10; // slightly faster than before
}
if (monsterEventActive && LK.ticks - monsterEventStartTick >= monsterEventDuration) {
// Revert to normal after 10 seconds
monsterEventActive = false;
arrowSpawnInterval = normalArrowSpawnInterval;
arrowSpeed = normalArrowSpeed;
}
// Gradually increase difficulty (skip if monster event is active)
if (!monsterEventActive && LK.ticks % 300 === 0) {
// every 5 seconds
if (arrowSpawnInterval > minArrowInterval) arrowSpawnInterval -= 2;
if (arrowSpeed < maxArrowSpeed) arrowSpeed += 1;
}
};
// --- Touch handling for game area (ignore, only arrow buttons are interactive) ---
game.down = function (x, y, obj) {
// Do nothing
};
game.move = function (x, y, obj) {
// Do nothing
};
game.up = function (x, y, obj) {
// Do nothing
};
// --- Reset state on game over ---
game.onGameOver = function () {
gameOver = true;
// Remove all arrows
for (var i = 0; i < fallingArrows.length; i++) {
fallingArrows[i].destroy();
}
fallingArrows = [];
monsterEventActive = false;
monsterEventTriggered = false;
monsterEventStartTick = null;
arrowSpawnInterval = normalArrowSpawnInterval;
arrowSpeed = normalArrowSpeed;
};
// --- Score display update ---
scoreTxt.setText(score);
// --- Start the game ---
score = 0;
combo = 0;
arrowSpawnInterval = 60;
arrowSpeed = 8;
normalArrowSpawnInterval = 60;
normalArrowSpeed = 8;
monsterEventActive = false;
monsterEventTriggered = false;
monsterEventStartTick = null;
gameOver = false;
fallingArrows = [];
ticksSinceLastArrow = 0;
// Play 'Ksk' music at game start
LK.playMusic('Ksk');