/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // No need for update loop for MVP // Prevent elements in top-left 100x100 // (Handled by spawnTarget margin and minY); // Enemy class for different difficulties var Enemy = Container.expand(function () { var self = Container.call(this); // Determine which asset to use based on selectedDifficulty var assetId = 'enemyMid'; if (typeof selectedDifficulty !== 'undefined') { if (selectedDifficulty === 'easy') { assetId = 'enemyEasy'; } else if (selectedDifficulty === 'hard') { assetId = 'enemyHard'; } } // Attach the correct asset var enemyAsset = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); // Set size property for positioning and hit detection self.size = enemyAsset.width; // Track lastX, lastY for possible future use self.lastX = self.x; self.lastY = self.y; // Vibration logic self._vibAngle = Math.random() * Math.PI * 2; self._vibSpeed = 0.18 + Math.random() * 0.12; // radians per frame self._vibRadius = 18 + Math.random() * 10; // px self._vibBaseX = 0; self._vibBaseY = 0; self._vibTick = 0; self._vibActive = false; self._vibInit = function () { self._vibBaseX = self.x; self._vibBaseY = self.y; self._vibTick = 0; self._vibActive = vibrationEnabled === true; }; self.update = function () { if (self._vibActive) { self._vibTick++; self._vibAngle += self._vibSpeed; // Circular vibration self.x = self._vibBaseX + Math.cos(self._vibAngle) * self._vibRadius; self.y = self._vibBaseY + Math.sin(self._vibAngle) * self._vibRadius; } }; // Hit/miss handlers (set externally) self.onHit = null; self.onMiss = null; // Called when player hits the enemy self.down = function () { if (typeof self.onHit === 'function') { self.onHit(); } var randomSound; if (lastPlayedSound === 'enemyKill' && consecutiveSoundCount >= 2) { randomSound = 'enemyKill2'; consecutiveSoundCount = 1; } else if (lastPlayedSound === 'enemyKill2' && consecutiveSoundCount >= 2) { randomSound = 'enemyKill'; consecutiveSoundCount = 1; } else { randomSound = Math.random() < 0.5 ? 'enemyKill' : 'enemyKill2'; if (randomSound === lastPlayedSound) { consecutiveSoundCount++; } else { consecutiveSoundCount = 1; } } lastPlayedSound = randomSound; LK.getSound(randomSound).play(); self.destroy(); }; // Called when player misses the enemy (timeout or miss) self.miss = function () { if (typeof self.onMiss === 'function') { self.onMiss(); } self.destroy(); }; // Defensive: destroy cleans up self.destroy = function () { if (self.parent) { self.parent.removeChild(self); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181c24 }); /**** * Game Code ****/ // Import tween plugin for animations and filters // Add background asset to the game scene (randomly select one) // Enemy assets for each difficulty var backgroundIds = ['background1', 'background2', 'background3']; var selectedBackgroundId = backgroundIds[Math.floor(Math.random() * backgroundIds.length)]; var background = LK.getAsset(selectedBackgroundId, { width: 2048, height: 2732, anchorX: 0, anchorY: 0, x: 0, y: 0, alpha: 0.5 }); game.addChild(background); // Apply blur effect to background (using tween plugin for filter) if (typeof background.filters === 'undefined') { background.filters = []; } if (typeof tween.BlurFilter === 'function') { var blur = new tween.BlurFilter(); blur.blur = 16; // Adjust blur strength as needed background.filters.push(blur); } // Game settings var GAME_TIME = 30; // seconds var TARGET_LIFETIME = 900; // ms before target disappears var TARGET_SPAWN_DELAY = 200; // ms between targets (after hit/miss) var MIN_TARGETS = 1; var MAX_TARGETS = 1; // Only one target at a time for MVP // State var score = 0; var lastPlayedSound = null; var consecutiveSoundCount = 0; var timeLeft = GAME_TIME; var timerInterval = null; var targetTimeout = null; var currentTarget = null; var gameActive = false; // Score display var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); // anchor center, top scoreTxt.x = LK.gui.top.width / 2; // center horizontally scoreTxt.y = 0; LK.gui.top.addChild(scoreTxt); // Timer display var timerTxt = new Text2(GAME_TIME + '', { size: 90, fill: 0xFFD700 }); timerTxt.anchor.set(0.5, 0); // anchor center, top timerTxt.x = LK.gui.top.width / 2; // center horizontally timerTxt.y = scoreTxt.height + 10; LK.gui.top.addChild(timerTxt); // Combo system variables and UI var combo = 0; var maxCombo = 0; var comboTxt = new Text2('', { size: 80, fill: 0xFFD700 }); comboTxt.anchor.set(0.5, 1); // anchor to center bottom comboTxt.y = 0; // will be positioned by gui.bottom comboTxt.visible = false; LK.gui.bottom.addChild(comboTxt); // Feedback text (shows "Miss!" or "+1") var feedbackTxt = new Text2('', { size: 120, fill: 0xFF3B3B }); feedbackTxt.anchor.set(0.5, 0.5); feedbackTxt.visible = false; LK.gui.center.addChild(feedbackTxt); // Helper: spawn a new target at a random position function spawnTarget() { // Remove previous target if any if (currentTarget) { currentTarget.destroy(); currentTarget = null; } // Create new enemy (use Enemy class instead of Target) var thisTargetDifficulty = selectedDifficulty; if (typeof mixedDifficultiesEnabled !== 'undefined' && mixedDifficultiesEnabled) { var pool = ['easy', 'mid', 'hard']; thisTargetDifficulty = pool[Math.floor(Math.random() * pool.length)]; } var prevSelectedDifficulty = selectedDifficulty; selectedDifficulty = thisTargetDifficulty; var target = new Enemy(); selectedDifficulty = prevSelectedDifficulty; // restore for next spawn // Randomize position, keep fully inside screen and away from top-left 100x100 var margin = 80; var minX = margin + target.size / 2; var maxX = 2048 - margin - target.size / 2; var minY = margin + target.size / 2 + 100; // avoid top 100px var maxY = 2732 - margin - target.size / 2; target.x = minX + Math.random() * (maxX - minX); target.y = minY + Math.random() * (maxY - minY); // Attach hit/miss handlers target.onHit = function () { score += 1; LK.setScore(score); scoreTxt.setText(score + ''); // Combo logic combo += 1; if (combo > maxCombo) { maxCombo = combo; } comboTxt.setText('Combo: ' + combo); comboTxt.visible = true; // Animate combo text comboTxt.alpha = 1; tween(comboTxt, { alpha: 0.5 }, { duration: 200, easing: tween.cubicOut }); showFeedback('+1', 0x3bff6a); scheduleNextTarget(); }; target.onMiss = function () { // Combo reset on miss if (combo > 0) { showFeedback('Miss! Combo Broken', 0xff3b3b); } else { showFeedback('Miss!', 0xff3b3b); } combo = 0; comboTxt.setText(''); comboTxt.visible = false; scheduleNextTarget(); }; // Add to game game.addChild(target); currentTarget = target; if (typeof target._vibInit === 'function') { target._vibInit(); } // Set up timeout for missing the target if (targetTimeout) { LK.clearTimeout(targetTimeout); } targetTimeout = LK.setTimeout(function () { if (currentTarget) { currentTarget.miss(); currentTarget = null; } }, TARGET_LIFETIME); } // Helper: show feedback text at center function showFeedback(text, color) { feedbackTxt.setText(text); // Use setStyle to update fill color safely feedbackTxt.setStyle({ fill: '#' + color.toString(16).padStart(6, '0') }); feedbackTxt.visible = true; feedbackTxt.alpha = 1; tween(feedbackTxt, { alpha: 0 }, { duration: 500, easing: tween.cubicOut, onFinish: function onFinish() { feedbackTxt.visible = false; } }); } // Helper: schedule next target after a short delay function scheduleNextTarget() { if (targetTimeout) { LK.clearTimeout(targetTimeout); } targetTimeout = LK.setTimeout(function () { if (gameActive) { spawnTarget(); } }, TARGET_SPAWN_DELAY); } // Start the game function startGame() { score = 0; LK.setScore(0); scoreTxt.setText('0'); combo = 0; maxCombo = 0; comboTxt.setText(''); comboTxt.visible = false; timeLeft = GAME_TIME; timerTxt.setText(timeLeft + ''); feedbackTxt.visible = false; gameActive = true; // Remove any lingering target if (currentTarget) { currentTarget.destroy(); currentTarget = null; } // Start timer if (timerInterval) { LK.clearInterval(timerInterval); } timerInterval = LK.setInterval(function () { if (!gameActive) { return; } timeLeft -= 0.1; if (timeLeft < 0) { timeLeft = 0; } timerTxt.setText(Math.ceil(timeLeft) + ''); if (timeLeft <= 0) { endGame(); } }, 100); // Spawn first target spawnTarget(); } // End the game function endGame() { gameActive = false; if (timerInterval) { LK.clearInterval(timerInterval); } if (targetTimeout) { LK.clearTimeout(targetTimeout); } if (currentTarget) { currentTarget.destroy(); currentTarget = null; } // Update leaderboards // Max Point leaderboard var leaderboardMaxPoint = storage.leaderboardMaxPoint || []; leaderboardMaxPoint.push(score); leaderboardMaxPoint.sort(function (a, b) { return b - a; }); if (leaderboardMaxPoint.length > 5) { leaderboardMaxPoint = leaderboardMaxPoint.slice(0, 5); } storage.leaderboardMaxPoint = leaderboardMaxPoint; // Max Combo leaderboard var leaderboardMaxCombo = storage.leaderboardMaxCombo || []; leaderboardMaxCombo.push(maxCombo); leaderboardMaxCombo.sort(function (a, b) { return b - a; }); if (leaderboardMaxCombo.length > 5) { leaderboardMaxCombo = leaderboardMaxCombo.slice(0, 5); } storage.leaderboardMaxCombo = leaderboardMaxCombo; // Show max combo in feedback if (maxCombo > 1) { feedbackTxt.setText('Max Combo: ' + maxCombo); feedbackTxt.setStyle({ fill: '#FFD700' }); feedbackTxt.visible = true; feedbackTxt.alpha = 1; tween(feedbackTxt, { alpha: 0 }, { duration: 1200, easing: tween.cubicOut, onFinish: function onFinish() { feedbackTxt.visible = false; } }); } // Show leaderboard feedback var leaderboardText = '🏆 Max Points:\n'; for (var i = 0; i < leaderboardMaxPoint.length; i++) { leaderboardText += i + 1 + '. ' + leaderboardMaxPoint[i] + '\n'; } leaderboardText += '\n🔥 Max Combos:\n'; for (var i = 0; i < leaderboardMaxCombo.length; i++) { leaderboardText += i + 1 + '. ' + leaderboardMaxCombo[i] + '\n'; } feedbackTxt.setText(leaderboardText); feedbackTxt.setStyle({ fill: '#FFD700', fontSize: 80 }); feedbackTxt.visible = true; feedbackTxt.alpha = 1; tween(feedbackTxt, { alpha: 0 }, { duration: 2200, easing: tween.cubicOut, onFinish: function onFinish() { feedbackTxt.visible = false; } }); LK.showGameOver(); } // Game tap handler: if tap is not on a target, show miss feedback game.down = function (x, y, obj) { if (!gameActive) { return; } // If tap is not on the target, count as miss if (currentTarget) { // Convert tap to target local coordinates var local = currentTarget.toLocal(game.toGlobal({ x: x, y: y })); var dx = local.x; var dy = local.y; var r = currentTarget.size / 2; if (dx * dx + dy * dy > r * r) { // Missed the target currentTarget.miss(); currentTarget = null; } // else: handled by target.down } }; // --- Main Menu Implementation --- // Difficulty settings var DIFFICULTY_SETTINGS = { easy: { GAME_TIME: 30, TARGET_LIFETIME: Math.round(1200 * 1.3), // 1560 TARGET_SPAWN_DELAY: Math.round(250 * 1.3) // 325 }, mid: { GAME_TIME: 30, TARGET_LIFETIME: Math.round(900 * 1.3), // 1170 TARGET_SPAWN_DELAY: Math.round(200 * 1.3) // 260 }, hard: { GAME_TIME: 30, TARGET_LIFETIME: Math.round(650 * 1.3), // 845 TARGET_SPAWN_DELAY: Math.round(120 * 1.3) // 156 } }; var selectedDifficulty = 'mid'; // Menu container var menuContainer = new Container(); LK.gui.center.addChild(menuContainer); // Title var titleTxt = new Text2('Aim Trainer', { size: 180, fill: 0xffffff }); titleTxt.anchor.set(0.5, 0.5); titleTxt.y = -350; menuContainer.addChild(titleTxt); // --- MAIN MENU TEXTS --- // 1. Title: 'Aim Trainer' // 2. Difficulty Buttons: 'Easy', 'Mid', 'Hard' // 3. Vibration Toggle: 'Enemy Vibration: OFF' / 'Enemy Vibration: ON' // 4. Start Button: 'Start' // 5. Mixed Difficulties Toggle: 'Mixed Difficulties: OFF' / 'Mixed Difficulties: ON' // 6. Leaderboard Section: 'Max Points:', 'Max Combos:' // Difficulty buttons var diffBtns = []; var diffNames = ['easy', 'mid', 'hard']; var diffLabels = ['Easy', 'Mid', 'Hard']; var diffColors = [0x3bff6a, 0x3b9cff, 0xff3b3b]; for (var i = 0; i < diffNames.length; i++) { var btn = new Container(); // Use the correct asset for each difficulty var assetId = diffNames[i] + 'Box'; var bg = LK.getAsset(assetId, { width: 340, height: 140, anchorX: 0.5, anchorY: 0.5 }); btn.addChild(bg); var label = new Text2(diffLabels[i], { size: 80, fill: 0xffffff }); label.anchor.set(0.5, 0.5); btn.addChild(label); btn.x = 0; btn.y = -100 + i * 180; btn.diff = diffNames[i]; btn.selected = false; btn.setSelected = function (selected) { this.selected = selected; this.children[0].alpha = selected ? 1 : 0.6; // Use setStyle to update fill color safely if (this.children[1] && typeof this.children[1].setStyle === 'function') { this.children[1].setStyle({ fill: selected ? '#ffffff' : '#e0e0e0' }); } }; btn.setSelected(i === 1); // Default to 'mid' btn.down = function (diffName, btnRef) { return function () { selectedDifficulty = diffName; for (var j = 0; j < diffBtns.length; j++) { diffBtns[j].setSelected(diffBtns[j] === btnRef); } }; }(diffNames[i], btn); diffBtns.push(btn); menuContainer.addChild(btn); } // Vibration toggle var vibrationEnabled = false; var vibrationBtn = new Container(); var vibrationBg = LK.getAsset('midBox', { width: 340, height: 100, anchorX: 0.5, anchorY: 0.5 }); vibrationBtn.addChild(vibrationBg); var vibrationLabel = new Text2('Enemy Vibration: OFF', { size: 60, fill: 0xffffff }); vibrationLabel.anchor.set(0.5, 0.5); vibrationBtn.addChild(vibrationLabel); // Start button var startBtn = new Container(); var startBg = LK.getAsset('targetShape', { width: 420, height: 160, color: 0xffe23b, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); startBtn.addChild(startBg); var startLabel = new Text2('Start', { size: 100, fill: 0xffffff, stroke: 0x000000, strokeThickness: 8 }); startLabel.anchor.set(0.5, 0.5); startBtn.addChild(startLabel); startBtn.x = 0; startBtn.y = 500; startBtn.down = function () { showGame(); }; menuContainer.addChild(startBtn); // Vibration toggle (moved below Start) vibrationBtn.x = 0; vibrationBtn.y = 670; vibrationBtn.down = function () { vibrationEnabled = !vibrationEnabled; vibrationLabel.setText('Enemy Vibration: ' + (vibrationEnabled ? 'ON' : 'OFF')); vibrationBg.alpha = vibrationEnabled ? 1 : 0.7; }; menuContainer.addChild(vibrationBtn); // Mixed Difficulties toggle (moved below vibration) var mixedDifficultiesEnabled = false; var mixedBtn = new Container(); var mixedBg = LK.getAsset('midBox', { width: 340, height: 100, anchorX: 0.5, anchorY: 0.5 }); mixedBtn.addChild(mixedBg); var mixedLabel = new Text2('Mixed Difficulties: OFF', { size: 60, fill: 0xffffff }); mixedLabel.anchor.set(0.5, 0.5); mixedBtn.addChild(mixedLabel); mixedBtn.x = 0; mixedBtn.y = 790; mixedBtn.down = function () { mixedDifficultiesEnabled = !mixedDifficultiesEnabled; mixedLabel.setText('Mixed Difficulties: ' + (mixedDifficultiesEnabled ? 'ON' : 'OFF')); mixedBg.alpha = mixedDifficultiesEnabled ? 1 : 0.7; }; menuContainer.addChild(mixedBtn); // Show menu function showMenu() { menuContainer.visible = true; scoreTxt.visible = false; timerTxt.visible = false; feedbackTxt.visible = false; gameActive = false; // Show leaderboard on menu ; } if (currentTarget) { currentTarget.destroy(); currentTarget = null; } if (timerInterval) { LK.clearInterval(timerInterval); } if (targetTimeout) { LK.clearTimeout(targetTimeout); } // Hide menu and start game function showGame() { // Set difficulty if (typeof mixedDifficultiesEnabled !== 'undefined' && mixedDifficultiesEnabled) { // Use 'mid' as base, but spawnTarget will randomize var settings = DIFFICULTY_SETTINGS['mid']; } else { var settings = DIFFICULTY_SETTINGS[selectedDifficulty]; } GAME_TIME = settings.GAME_TIME; TARGET_LIFETIME = settings.TARGET_LIFETIME; TARGET_SPAWN_DELAY = settings.TARGET_SPAWN_DELAY; menuContainer.visible = false; scoreTxt.visible = true; timerTxt.visible = true; feedbackTxt.visible = false; startGame(); } // On game over, show menu again game.on('destroy', function () { gameActive = false; if (timerInterval) { LK.clearInterval(timerInterval); } if (targetTimeout) { LK.clearTimeout(targetTimeout); } if (currentTarget) { currentTarget.destroy(); currentTarget = null; } showMenu(); }); // Show menu on first frame LK.setTimeout(function () { showMenu(); }, 200);
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// No need for update loop for MVP
// Prevent elements in top-left 100x100
// (Handled by spawnTarget margin and minY);
// Enemy class for different difficulties
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Determine which asset to use based on selectedDifficulty
var assetId = 'enemyMid';
if (typeof selectedDifficulty !== 'undefined') {
if (selectedDifficulty === 'easy') {
assetId = 'enemyEasy';
} else if (selectedDifficulty === 'hard') {
assetId = 'enemyHard';
}
}
// Attach the correct asset
var enemyAsset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Set size property for positioning and hit detection
self.size = enemyAsset.width;
// Track lastX, lastY for possible future use
self.lastX = self.x;
self.lastY = self.y;
// Vibration logic
self._vibAngle = Math.random() * Math.PI * 2;
self._vibSpeed = 0.18 + Math.random() * 0.12; // radians per frame
self._vibRadius = 18 + Math.random() * 10; // px
self._vibBaseX = 0;
self._vibBaseY = 0;
self._vibTick = 0;
self._vibActive = false;
self._vibInit = function () {
self._vibBaseX = self.x;
self._vibBaseY = self.y;
self._vibTick = 0;
self._vibActive = vibrationEnabled === true;
};
self.update = function () {
if (self._vibActive) {
self._vibTick++;
self._vibAngle += self._vibSpeed;
// Circular vibration
self.x = self._vibBaseX + Math.cos(self._vibAngle) * self._vibRadius;
self.y = self._vibBaseY + Math.sin(self._vibAngle) * self._vibRadius;
}
};
// Hit/miss handlers (set externally)
self.onHit = null;
self.onMiss = null;
// Called when player hits the enemy
self.down = function () {
if (typeof self.onHit === 'function') {
self.onHit();
}
var randomSound;
if (lastPlayedSound === 'enemyKill' && consecutiveSoundCount >= 2) {
randomSound = 'enemyKill2';
consecutiveSoundCount = 1;
} else if (lastPlayedSound === 'enemyKill2' && consecutiveSoundCount >= 2) {
randomSound = 'enemyKill';
consecutiveSoundCount = 1;
} else {
randomSound = Math.random() < 0.5 ? 'enemyKill' : 'enemyKill2';
if (randomSound === lastPlayedSound) {
consecutiveSoundCount++;
} else {
consecutiveSoundCount = 1;
}
}
lastPlayedSound = randomSound;
LK.getSound(randomSound).play();
self.destroy();
};
// Called when player misses the enemy (timeout or miss)
self.miss = function () {
if (typeof self.onMiss === 'function') {
self.onMiss();
}
self.destroy();
};
// Defensive: destroy cleans up
self.destroy = function () {
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181c24
});
/****
* Game Code
****/
// Import tween plugin for animations and filters
// Add background asset to the game scene (randomly select one)
// Enemy assets for each difficulty
var backgroundIds = ['background1', 'background2', 'background3'];
var selectedBackgroundId = backgroundIds[Math.floor(Math.random() * backgroundIds.length)];
var background = LK.getAsset(selectedBackgroundId, {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0.5
});
game.addChild(background);
// Apply blur effect to background (using tween plugin for filter)
if (typeof background.filters === 'undefined') {
background.filters = [];
}
if (typeof tween.BlurFilter === 'function') {
var blur = new tween.BlurFilter();
blur.blur = 16; // Adjust blur strength as needed
background.filters.push(blur);
}
// Game settings
var GAME_TIME = 30; // seconds
var TARGET_LIFETIME = 900; // ms before target disappears
var TARGET_SPAWN_DELAY = 200; // ms between targets (after hit/miss)
var MIN_TARGETS = 1;
var MAX_TARGETS = 1; // Only one target at a time for MVP
// State
var score = 0;
var lastPlayedSound = null;
var consecutiveSoundCount = 0;
var timeLeft = GAME_TIME;
var timerInterval = null;
var targetTimeout = null;
var currentTarget = null;
var gameActive = false;
// Score display
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0); // anchor center, top
scoreTxt.x = LK.gui.top.width / 2; // center horizontally
scoreTxt.y = 0;
LK.gui.top.addChild(scoreTxt);
// Timer display
var timerTxt = new Text2(GAME_TIME + '', {
size: 90,
fill: 0xFFD700
});
timerTxt.anchor.set(0.5, 0); // anchor center, top
timerTxt.x = LK.gui.top.width / 2; // center horizontally
timerTxt.y = scoreTxt.height + 10;
LK.gui.top.addChild(timerTxt);
// Combo system variables and UI
var combo = 0;
var maxCombo = 0;
var comboTxt = new Text2('', {
size: 80,
fill: 0xFFD700
});
comboTxt.anchor.set(0.5, 1); // anchor to center bottom
comboTxt.y = 0; // will be positioned by gui.bottom
comboTxt.visible = false;
LK.gui.bottom.addChild(comboTxt);
// Feedback text (shows "Miss!" or "+1")
var feedbackTxt = new Text2('', {
size: 120,
fill: 0xFF3B3B
});
feedbackTxt.anchor.set(0.5, 0.5);
feedbackTxt.visible = false;
LK.gui.center.addChild(feedbackTxt);
// Helper: spawn a new target at a random position
function spawnTarget() {
// Remove previous target if any
if (currentTarget) {
currentTarget.destroy();
currentTarget = null;
}
// Create new enemy (use Enemy class instead of Target)
var thisTargetDifficulty = selectedDifficulty;
if (typeof mixedDifficultiesEnabled !== 'undefined' && mixedDifficultiesEnabled) {
var pool = ['easy', 'mid', 'hard'];
thisTargetDifficulty = pool[Math.floor(Math.random() * pool.length)];
}
var prevSelectedDifficulty = selectedDifficulty;
selectedDifficulty = thisTargetDifficulty;
var target = new Enemy();
selectedDifficulty = prevSelectedDifficulty; // restore for next spawn
// Randomize position, keep fully inside screen and away from top-left 100x100
var margin = 80;
var minX = margin + target.size / 2;
var maxX = 2048 - margin - target.size / 2;
var minY = margin + target.size / 2 + 100; // avoid top 100px
var maxY = 2732 - margin - target.size / 2;
target.x = minX + Math.random() * (maxX - minX);
target.y = minY + Math.random() * (maxY - minY);
// Attach hit/miss handlers
target.onHit = function () {
score += 1;
LK.setScore(score);
scoreTxt.setText(score + '');
// Combo logic
combo += 1;
if (combo > maxCombo) {
maxCombo = combo;
}
comboTxt.setText('Combo: ' + combo);
comboTxt.visible = true;
// Animate combo text
comboTxt.alpha = 1;
tween(comboTxt, {
alpha: 0.5
}, {
duration: 200,
easing: tween.cubicOut
});
showFeedback('+1', 0x3bff6a);
scheduleNextTarget();
};
target.onMiss = function () {
// Combo reset on miss
if (combo > 0) {
showFeedback('Miss! Combo Broken', 0xff3b3b);
} else {
showFeedback('Miss!', 0xff3b3b);
}
combo = 0;
comboTxt.setText('');
comboTxt.visible = false;
scheduleNextTarget();
};
// Add to game
game.addChild(target);
currentTarget = target;
if (typeof target._vibInit === 'function') {
target._vibInit();
}
// Set up timeout for missing the target
if (targetTimeout) {
LK.clearTimeout(targetTimeout);
}
targetTimeout = LK.setTimeout(function () {
if (currentTarget) {
currentTarget.miss();
currentTarget = null;
}
}, TARGET_LIFETIME);
}
// Helper: show feedback text at center
function showFeedback(text, color) {
feedbackTxt.setText(text);
// Use setStyle to update fill color safely
feedbackTxt.setStyle({
fill: '#' + color.toString(16).padStart(6, '0')
});
feedbackTxt.visible = true;
feedbackTxt.alpha = 1;
tween(feedbackTxt, {
alpha: 0
}, {
duration: 500,
easing: tween.cubicOut,
onFinish: function onFinish() {
feedbackTxt.visible = false;
}
});
}
// Helper: schedule next target after a short delay
function scheduleNextTarget() {
if (targetTimeout) {
LK.clearTimeout(targetTimeout);
}
targetTimeout = LK.setTimeout(function () {
if (gameActive) {
spawnTarget();
}
}, TARGET_SPAWN_DELAY);
}
// Start the game
function startGame() {
score = 0;
LK.setScore(0);
scoreTxt.setText('0');
combo = 0;
maxCombo = 0;
comboTxt.setText('');
comboTxt.visible = false;
timeLeft = GAME_TIME;
timerTxt.setText(timeLeft + '');
feedbackTxt.visible = false;
gameActive = true;
// Remove any lingering target
if (currentTarget) {
currentTarget.destroy();
currentTarget = null;
}
// Start timer
if (timerInterval) {
LK.clearInterval(timerInterval);
}
timerInterval = LK.setInterval(function () {
if (!gameActive) {
return;
}
timeLeft -= 0.1;
if (timeLeft < 0) {
timeLeft = 0;
}
timerTxt.setText(Math.ceil(timeLeft) + '');
if (timeLeft <= 0) {
endGame();
}
}, 100);
// Spawn first target
spawnTarget();
}
// End the game
function endGame() {
gameActive = false;
if (timerInterval) {
LK.clearInterval(timerInterval);
}
if (targetTimeout) {
LK.clearTimeout(targetTimeout);
}
if (currentTarget) {
currentTarget.destroy();
currentTarget = null;
}
// Update leaderboards
// Max Point leaderboard
var leaderboardMaxPoint = storage.leaderboardMaxPoint || [];
leaderboardMaxPoint.push(score);
leaderboardMaxPoint.sort(function (a, b) {
return b - a;
});
if (leaderboardMaxPoint.length > 5) {
leaderboardMaxPoint = leaderboardMaxPoint.slice(0, 5);
}
storage.leaderboardMaxPoint = leaderboardMaxPoint;
// Max Combo leaderboard
var leaderboardMaxCombo = storage.leaderboardMaxCombo || [];
leaderboardMaxCombo.push(maxCombo);
leaderboardMaxCombo.sort(function (a, b) {
return b - a;
});
if (leaderboardMaxCombo.length > 5) {
leaderboardMaxCombo = leaderboardMaxCombo.slice(0, 5);
}
storage.leaderboardMaxCombo = leaderboardMaxCombo;
// Show max combo in feedback
if (maxCombo > 1) {
feedbackTxt.setText('Max Combo: ' + maxCombo);
feedbackTxt.setStyle({
fill: '#FFD700'
});
feedbackTxt.visible = true;
feedbackTxt.alpha = 1;
tween(feedbackTxt, {
alpha: 0
}, {
duration: 1200,
easing: tween.cubicOut,
onFinish: function onFinish() {
feedbackTxt.visible = false;
}
});
}
// Show leaderboard feedback
var leaderboardText = '🏆 Max Points:\n';
for (var i = 0; i < leaderboardMaxPoint.length; i++) {
leaderboardText += i + 1 + '. ' + leaderboardMaxPoint[i] + '\n';
}
leaderboardText += '\n🔥 Max Combos:\n';
for (var i = 0; i < leaderboardMaxCombo.length; i++) {
leaderboardText += i + 1 + '. ' + leaderboardMaxCombo[i] + '\n';
}
feedbackTxt.setText(leaderboardText);
feedbackTxt.setStyle({
fill: '#FFD700',
fontSize: 80
});
feedbackTxt.visible = true;
feedbackTxt.alpha = 1;
tween(feedbackTxt, {
alpha: 0
}, {
duration: 2200,
easing: tween.cubicOut,
onFinish: function onFinish() {
feedbackTxt.visible = false;
}
});
LK.showGameOver();
}
// Game tap handler: if tap is not on a target, show miss feedback
game.down = function (x, y, obj) {
if (!gameActive) {
return;
}
// If tap is not on the target, count as miss
if (currentTarget) {
// Convert tap to target local coordinates
var local = currentTarget.toLocal(game.toGlobal({
x: x,
y: y
}));
var dx = local.x;
var dy = local.y;
var r = currentTarget.size / 2;
if (dx * dx + dy * dy > r * r) {
// Missed the target
currentTarget.miss();
currentTarget = null;
}
// else: handled by target.down
}
};
// --- Main Menu Implementation ---
// Difficulty settings
var DIFFICULTY_SETTINGS = {
easy: {
GAME_TIME: 30,
TARGET_LIFETIME: Math.round(1200 * 1.3),
// 1560
TARGET_SPAWN_DELAY: Math.round(250 * 1.3) // 325
},
mid: {
GAME_TIME: 30,
TARGET_LIFETIME: Math.round(900 * 1.3),
// 1170
TARGET_SPAWN_DELAY: Math.round(200 * 1.3) // 260
},
hard: {
GAME_TIME: 30,
TARGET_LIFETIME: Math.round(650 * 1.3),
// 845
TARGET_SPAWN_DELAY: Math.round(120 * 1.3) // 156
}
};
var selectedDifficulty = 'mid';
// Menu container
var menuContainer = new Container();
LK.gui.center.addChild(menuContainer);
// Title
var titleTxt = new Text2('Aim Trainer', {
size: 180,
fill: 0xffffff
});
titleTxt.anchor.set(0.5, 0.5);
titleTxt.y = -350;
menuContainer.addChild(titleTxt);
// --- MAIN MENU TEXTS ---
// 1. Title: 'Aim Trainer'
// 2. Difficulty Buttons: 'Easy', 'Mid', 'Hard'
// 3. Vibration Toggle: 'Enemy Vibration: OFF' / 'Enemy Vibration: ON'
// 4. Start Button: 'Start'
// 5. Mixed Difficulties Toggle: 'Mixed Difficulties: OFF' / 'Mixed Difficulties: ON'
// 6. Leaderboard Section: 'Max Points:', 'Max Combos:'
// Difficulty buttons
var diffBtns = [];
var diffNames = ['easy', 'mid', 'hard'];
var diffLabels = ['Easy', 'Mid', 'Hard'];
var diffColors = [0x3bff6a, 0x3b9cff, 0xff3b3b];
for (var i = 0; i < diffNames.length; i++) {
var btn = new Container();
// Use the correct asset for each difficulty
var assetId = diffNames[i] + 'Box';
var bg = LK.getAsset(assetId, {
width: 340,
height: 140,
anchorX: 0.5,
anchorY: 0.5
});
btn.addChild(bg);
var label = new Text2(diffLabels[i], {
size: 80,
fill: 0xffffff
});
label.anchor.set(0.5, 0.5);
btn.addChild(label);
btn.x = 0;
btn.y = -100 + i * 180;
btn.diff = diffNames[i];
btn.selected = false;
btn.setSelected = function (selected) {
this.selected = selected;
this.children[0].alpha = selected ? 1 : 0.6;
// Use setStyle to update fill color safely
if (this.children[1] && typeof this.children[1].setStyle === 'function') {
this.children[1].setStyle({
fill: selected ? '#ffffff' : '#e0e0e0'
});
}
};
btn.setSelected(i === 1); // Default to 'mid'
btn.down = function (diffName, btnRef) {
return function () {
selectedDifficulty = diffName;
for (var j = 0; j < diffBtns.length; j++) {
diffBtns[j].setSelected(diffBtns[j] === btnRef);
}
};
}(diffNames[i], btn);
diffBtns.push(btn);
menuContainer.addChild(btn);
}
// Vibration toggle
var vibrationEnabled = false;
var vibrationBtn = new Container();
var vibrationBg = LK.getAsset('midBox', {
width: 340,
height: 100,
anchorX: 0.5,
anchorY: 0.5
});
vibrationBtn.addChild(vibrationBg);
var vibrationLabel = new Text2('Enemy Vibration: OFF', {
size: 60,
fill: 0xffffff
});
vibrationLabel.anchor.set(0.5, 0.5);
vibrationBtn.addChild(vibrationLabel);
// Start button
var startBtn = new Container();
var startBg = LK.getAsset('targetShape', {
width: 420,
height: 160,
color: 0xffe23b,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
startBtn.addChild(startBg);
var startLabel = new Text2('Start', {
size: 100,
fill: 0xffffff,
stroke: 0x000000,
strokeThickness: 8
});
startLabel.anchor.set(0.5, 0.5);
startBtn.addChild(startLabel);
startBtn.x = 0;
startBtn.y = 500;
startBtn.down = function () {
showGame();
};
menuContainer.addChild(startBtn);
// Vibration toggle (moved below Start)
vibrationBtn.x = 0;
vibrationBtn.y = 670;
vibrationBtn.down = function () {
vibrationEnabled = !vibrationEnabled;
vibrationLabel.setText('Enemy Vibration: ' + (vibrationEnabled ? 'ON' : 'OFF'));
vibrationBg.alpha = vibrationEnabled ? 1 : 0.7;
};
menuContainer.addChild(vibrationBtn);
// Mixed Difficulties toggle (moved below vibration)
var mixedDifficultiesEnabled = false;
var mixedBtn = new Container();
var mixedBg = LK.getAsset('midBox', {
width: 340,
height: 100,
anchorX: 0.5,
anchorY: 0.5
});
mixedBtn.addChild(mixedBg);
var mixedLabel = new Text2('Mixed Difficulties: OFF', {
size: 60,
fill: 0xffffff
});
mixedLabel.anchor.set(0.5, 0.5);
mixedBtn.addChild(mixedLabel);
mixedBtn.x = 0;
mixedBtn.y = 790;
mixedBtn.down = function () {
mixedDifficultiesEnabled = !mixedDifficultiesEnabled;
mixedLabel.setText('Mixed Difficulties: ' + (mixedDifficultiesEnabled ? 'ON' : 'OFF'));
mixedBg.alpha = mixedDifficultiesEnabled ? 1 : 0.7;
};
menuContainer.addChild(mixedBtn);
// Show menu
function showMenu() {
menuContainer.visible = true;
scoreTxt.visible = false;
timerTxt.visible = false;
feedbackTxt.visible = false;
gameActive = false;
// Show leaderboard on menu
;
}
if (currentTarget) {
currentTarget.destroy();
currentTarget = null;
}
if (timerInterval) {
LK.clearInterval(timerInterval);
}
if (targetTimeout) {
LK.clearTimeout(targetTimeout);
}
// Hide menu and start game
function showGame() {
// Set difficulty
if (typeof mixedDifficultiesEnabled !== 'undefined' && mixedDifficultiesEnabled) {
// Use 'mid' as base, but spawnTarget will randomize
var settings = DIFFICULTY_SETTINGS['mid'];
} else {
var settings = DIFFICULTY_SETTINGS[selectedDifficulty];
}
GAME_TIME = settings.GAME_TIME;
TARGET_LIFETIME = settings.TARGET_LIFETIME;
TARGET_SPAWN_DELAY = settings.TARGET_SPAWN_DELAY;
menuContainer.visible = false;
scoreTxt.visible = true;
timerTxt.visible = true;
feedbackTxt.visible = false;
startGame();
}
// On game over, show menu again
game.on('destroy', function () {
gameActive = false;
if (timerInterval) {
LK.clearInterval(timerInterval);
}
if (targetTimeout) {
LK.clearTimeout(targetTimeout);
}
if (currentTarget) {
currentTarget.destroy();
currentTarget = null;
}
showMenu();
});
// Show menu on first frame
LK.setTimeout(function () {
showMenu();
}, 200);