/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Enemy target class
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Attach enemy image, anchor center
var enemyImg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// For hit animation
self.isHit = false;
// Show hit effect
self.showHit = function () {
if (self.isHit) return;
self.isHit = true;
// Add hit effect
var hitFx = self.attachAsset('hit', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
alpha: 0.8,
scaleX: 0.7,
scaleY: 0.7
});
tween(hitFx, {
alpha: 0,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 350,
easing: tween.cubicOut,
onFinish: function onFinish() {
hitFx.destroy();
}
});
};
// Show shot effect
self.showShot = function (localX, localY) {
var shotFx = self.attachAsset('shot', {
anchorX: 0.1,
anchorY: 0.5,
x: localX,
y: localY,
alpha: 0.9,
scaleX: 0.7 + Math.random() * 0.3,
scaleY: 0.7 + Math.random() * 0.3,
rotation: (Math.random() - 0.5) * 0.5
});
tween(shotFx, {
alpha: 0,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 180,
easing: tween.linear,
onFinish: function onFinish() {
shotFx.destroy();
}
});
};
// Handle tap
self.down = function (x, y, obj) {
if (self.isHit) return;
self.showShot(0, 0);
LK.getSound('shotSfx').play();
// Mark as hit
self.showHit();
LK.getSound('hitSfx').play();
// Notify game
if (typeof onEnemyHit === 'function') {
onEnemyHit(self);
}
};
return self;
});
// LaserTrail class: draws a fading laser from (x0, y0) to (x1, y1)
var LaserTrail = Container.expand(function () {
var self = Container.call(this);
// Draw a rectangle as the laser beam between (x0, y0) and (x1, y1)
// Call: .init(x0, y0, x1, y1)
self.init = function (x0, y0, x1, y1) {
// Calculate distance and angle
var dx = x1 - x0;
var dy = y1 - y0;
var dist = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
// Use a neon blue color for the laser
var laserWidth = Math.max(24, weaponSize * 0.06); // scale with weapon
var laserAsset = self.attachAsset('neon_sweep', {
anchorX: 0,
anchorY: 0.5,
x: 0,
y: 0,
width: dist,
height: laserWidth,
alpha: 0.85
});
// Position and rotate the container
self.x = x0;
self.y = y0;
self.rotation = angle;
// Animate fade out and destroy
tween(self, {
alpha: 0
}, {
duration: 320,
easing: tween.cubicOut,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0a1a // Deep blue-black for cyberpunk
});
/****
* Game Code
****/
// Music: synthwave background
// Sound: glitch hit
// Sound: electric shot
// Neon-glitch hit effect
// Electric shot effect (neon bolt)
// Neon-glitch enemy (paper cutout style)
// Add cyberpunk neon city photo-style background
var bg = LK.getAsset('cyberpunk_bg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
game.addChildAt(bg, 0); // Ensure background is at the back
// Add subtle animated neon light sweep overlay for ambiance
var neonSweep = LK.getAsset('neon_sweep', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732,
alpha: 0.18
});
game.addChild(neonSweep);
tween(neonSweep, {
x: 2048
}, {
duration: 3200,
repeat: Infinity,
yoyo: true,
easing: tween.sineInOut
});
// Play synthwave music
LK.playMusic('synthwave');
// Game state
var isGameActive = true; // Track if the game is currently active
var totalTargets = 100;
var targetsHit = 0;
var currentTarget = null;
var targetIndex = 0;
var reactionTimes = [];
var targetAppearTime = 0;
// Track consecutive accurate hits for encouragement message
var consecutiveAccurateHits = 0;
// Message text for encouragement, hidden by default
var encouragementTxt = new Text2("You're doing great!", {
size: 100,
fill: 0xFFFC00
});
encouragementTxt.anchor.set(0.5, 0.5);
encouragementTxt.visible = false;
LK.gui.center.addChild(encouragementTxt);
// Add neon-glitch border overlay for cyberpunk effect
var borderOverlay = LK.getAsset('neon_border', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732,
alpha: 0.7
});
game.addChild(borderOverlay);
// Score text
var scoreTxt = new Text2('0 / 15', {
size: 120,
fill: 0x00FFF7
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Add a small neon-glitch logo at the top center for game branding
var logo = LK.getAsset('reflexometry_logo', {
anchorX: 0.5,
anchorY: 0,
x: 1024,
y: 10,
width: 400,
height: 100
});
LK.gui.top.addChild(logo);
// Reaction time text
var reactionTxt = new Text2('Reaction: -- ms', {
size: 70,
fill: 0xFF00C8
});
reactionTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(reactionTxt);
reactionTxt.y = 130;
// Fastest reaction time text
var fastestTxt = new Text2('Fastest: -- ms', {
size: 60,
fill: 0x00FFB0
});
fastestTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(fastestTxt);
fastestTxt.y = 210;
// Track fastest reaction time for this session
var fastestReaction = null;
// End screen text (hidden by default)
var endTxt = new Text2('', {
size: 110,
fill: 0xFFF600
});
endTxt.anchor.set(0.5, 0.5);
endTxt.visible = false;
LK.gui.center.addChild(endTxt);
// Used for event callback
var onEnemyHit = null;
// Generate random position for enemy (avoid top 200px and bottom 200px, and left 100px/right 100px)
function getRandomEnemyPos(enemyW, enemyH) {
var marginX = 160;
var marginY = 220;
var x = marginX + Math.random() * (2048 - 2 * marginX);
var y = marginY + Math.random() * (2732 - 2 * marginY);
return {
x: x,
y: y
};
}
// Show next target
function showNextTarget() {
if (currentTarget) {
currentTarget.destroy();
currentTarget = null;
}
if (targetIndex >= totalTargets) {
endGame();
return;
}
// Randomly select enemy size and spawn region
var sizeRand = Math.random();
var enemySize = 80;
var spawnInCenter = false;
if (sizeRand < 0.65) {
// 65%: 80x80 in center zone
enemySize = 80;
spawnInCenter = true;
} else if (sizeRand < 0.90) {
// 25%: 160x160 anywhere
enemySize = 160;
} else {
// 15%: 320x320 anywhere
enemySize = 320;
}
// Create enemy
var enemy = new Enemy();
// Set scale so the base asset (320x320) matches the chosen size
var scale = enemySize / 320;
enemy.scale.set(scale * (1.1 + Math.random() * 0.2));
// Get random position, passing the actual size
var pos;
if (spawnInCenter) {
// Center region: 2048x2732, center zone is 768px wide x 1024px high, centered
// Center zone: x in [640, 1408], y in [854, 1878]
var centerZoneW = 768;
var centerZoneH = 1024;
var minX = 1024 - centerZoneW / 2;
var minY = 1366 - centerZoneH / 2;
var x = minX + Math.random() * (centerZoneW - enemySize);
var y = minY + Math.random() * (centerZoneH - enemySize);
pos = {
x: x + enemySize / 2,
y: y + enemySize / 2
};
} else {
pos = getRandomEnemyPos(enemySize, enemySize);
}
enemy.x = pos.x;
enemy.y = pos.y;
enemy.rotation = (Math.random() - 0.5) * 0.2;
enemy.alpha = 0.0;
game.addChild(enemy);
// Animate in
tween(enemy, {
alpha: 1
}, {
duration: 120,
easing: tween.cubicOut
});
// Set up hit callback
onEnemyHit = function onEnemyHit(e) {
if (!isGameActive) return;
// Calculate reaction time
var now = Date.now();
var react = now - targetAppearTime;
reactionTimes.push(react);
targetsHit++;
scoreTxt.setText(targetsHit + ' / ' + totalTargets);
reactionTxt.setText('Reaction: ' + react + ' ms');
// If hit is accurate (under 1000ms), increment consecutiveAccurateHits, else reset
if (react < 1000) {
consecutiveAccurateHits++;
// Show encouragement every 10 consecutive accurate hits
if (consecutiveAccurateHits > 0 && consecutiveAccurateHits % 10 === 0) {
encouragementTxt.visible = true;
// Hide after 1.2s
LK.setTimeout(function () {
encouragementTxt.visible = false;
}, 1200);
}
} else {
consecutiveAccurateHits = 0;
}
// Update fastest reaction time if this is the best so far
if (fastestReaction === null || react < fastestReaction) {
fastestReaction = react;
fastestTxt.setText('Fastest: ' + fastestReaction + ' ms\nNew Record!');
// Remove 'New Record!' after 1.2s
LK.setTimeout(function () {
if (typeof fastestTxt.text === "string" && fastestTxt.text.indexOf('New Record!') !== -1) {
fastestTxt.setText('Fastest: ' + fastestReaction + ' ms');
}
}, 1200);
}
// Remove enemy after short delay
LK.setTimeout(function () {
if (currentTarget) {
currentTarget.destroy();
currentTarget = null;
}
targetIndex++;
showNextTarget();
}, 220);
};
// Set up auto-miss (if not hit in 1.2s)
LK.setTimeout(function () {
if (enemy.isHit || !isGameActive) return;
// Missed: just remove and go to next
reactionTimes.push(1200);
reactionTxt.setText('Reaction: MISS');
// Reset consecutive accurate hits on miss
consecutiveAccurateHits = 0;
tween(enemy, {
alpha: 0
}, {
duration: 120,
onFinish: function onFinish() {
if (currentTarget) {
currentTarget.destroy();
currentTarget = null;
}
targetIndex++;
showNextTarget();
}
});
}, 1200);
// Track for removal
currentTarget = enemy;
targetAppearTime = Date.now();
}
// End game
function endGame() {
isGameActive = false;
if (currentTarget) {
currentTarget.destroy();
currentTarget = null;
}
// Calculate stats
var hits = targetsHit;
var avgReact = 0;
var hitCount = 0;
for (var i = 0; i < reactionTimes.length; i++) {
if (reactionTimes[i] < 1000) {
avgReact += reactionTimes[i];
hitCount++;
}
}
avgReact = hitCount ? Math.round(avgReact / hitCount) : 0;
var msg = '';
if (hits === totalTargets) {
msg = 'PERFECT!\n';
} else if (hits >= totalTargets - 2) {
msg = 'Great job!\n';
} else {
msg = 'Keep practicing!\n';
}
msg += 'You hit ' + hits + ' / ' + totalTargets + '\n';
// Show only the fastest hit time
msg += 'Fastest Hit: ' + (fastestReaction !== null ? fastestReaction + ' ms' : '--') + '\n\nTap to play again!';
// Show 'Excellent performance!' at the end
msg += '\n\nExcellent performance!';
endTxt.setText(msg);
endTxt.visible = true;
// Show win/lose popup
if (hits === totalTargets) {
LK.showYouWin();
} else {
LK.showGameOver();
}
}
// Restart game on tap after end
game.down = function (x, y, obj) {
if (!isGameActive && endTxt.visible) {
// Reset state
targetsHit = 0;
targetIndex = 0;
reactionTimes = [];
isGameActive = true;
scoreTxt.setText('0 / ' + totalTargets);
reactionTxt.setText('Reaction: -- ms');
fastestReaction = null;
fastestTxt.setText('Fastest: -- ms');
endTxt.visible = false;
showNextTarget();
return;
}
// --- Weapon fire with laser trail ---
// Only fire if game is active
if (isGameActive) {
// Weapon origin: center bottom of weapon
var x0 = weapon.x;
var y0 = weapon.y + weaponSize / 2;
// Target: where user tapped (x, y)
var x1 = x;
var y1 = y;
// Clamp y1 to not go below the weapon
if (y1 > y0) y1 = y0;
// Create and show laser trail
var laser = new LaserTrail();
laser.init(x0, y0, x1, y1);
game.addChild(laser);
// Optionally: play shot sound
LK.getSound('shotSfx').play();
}
};
// Start game
showNextTarget();
// Add a bottom neon-glitch HUD bar for future UI elements
var hudBar = LK.getAsset('neon_hud_bar', {
anchorX: 0.5,
anchorY: 1,
x: 1024,
y: 2732,
width: 1800,
height: 120,
alpha: 0.85
});
LK.gui.bottom.addChild(hudBar);
// Show weapon (electric shot) at bottom center, above HUD bar
var weaponSize = 900; // Make weapon a much bigger square
// Position weapon so its bottom edge touches the bottom line of the game area
var weaponY = 2732 - weaponSize / 2;
var weapon = LK.getAsset('shot', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: weaponY,
width: weaponSize,
height: weaponSize,
alpha: 0.92,
rotation: 0
});
game.addChild(weapon);
// No dragging or move needed for this game
game.move = function (x, y, obj) {};
game.up = function (x, y, obj) {};
// No per-frame update needed
game.update = function () {};
; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Enemy target class
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Attach enemy image, anchor center
var enemyImg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// For hit animation
self.isHit = false;
// Show hit effect
self.showHit = function () {
if (self.isHit) return;
self.isHit = true;
// Add hit effect
var hitFx = self.attachAsset('hit', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
alpha: 0.8,
scaleX: 0.7,
scaleY: 0.7
});
tween(hitFx, {
alpha: 0,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 350,
easing: tween.cubicOut,
onFinish: function onFinish() {
hitFx.destroy();
}
});
};
// Show shot effect
self.showShot = function (localX, localY) {
var shotFx = self.attachAsset('shot', {
anchorX: 0.1,
anchorY: 0.5,
x: localX,
y: localY,
alpha: 0.9,
scaleX: 0.7 + Math.random() * 0.3,
scaleY: 0.7 + Math.random() * 0.3,
rotation: (Math.random() - 0.5) * 0.5
});
tween(shotFx, {
alpha: 0,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 180,
easing: tween.linear,
onFinish: function onFinish() {
shotFx.destroy();
}
});
};
// Handle tap
self.down = function (x, y, obj) {
if (self.isHit) return;
self.showShot(0, 0);
LK.getSound('shotSfx').play();
// Mark as hit
self.showHit();
LK.getSound('hitSfx').play();
// Notify game
if (typeof onEnemyHit === 'function') {
onEnemyHit(self);
}
};
return self;
});
// LaserTrail class: draws a fading laser from (x0, y0) to (x1, y1)
var LaserTrail = Container.expand(function () {
var self = Container.call(this);
// Draw a rectangle as the laser beam between (x0, y0) and (x1, y1)
// Call: .init(x0, y0, x1, y1)
self.init = function (x0, y0, x1, y1) {
// Calculate distance and angle
var dx = x1 - x0;
var dy = y1 - y0;
var dist = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
// Use a neon blue color for the laser
var laserWidth = Math.max(24, weaponSize * 0.06); // scale with weapon
var laserAsset = self.attachAsset('neon_sweep', {
anchorX: 0,
anchorY: 0.5,
x: 0,
y: 0,
width: dist,
height: laserWidth,
alpha: 0.85
});
// Position and rotate the container
self.x = x0;
self.y = y0;
self.rotation = angle;
// Animate fade out and destroy
tween(self, {
alpha: 0
}, {
duration: 320,
easing: tween.cubicOut,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0a1a // Deep blue-black for cyberpunk
});
/****
* Game Code
****/
// Music: synthwave background
// Sound: glitch hit
// Sound: electric shot
// Neon-glitch hit effect
// Electric shot effect (neon bolt)
// Neon-glitch enemy (paper cutout style)
// Add cyberpunk neon city photo-style background
var bg = LK.getAsset('cyberpunk_bg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
game.addChildAt(bg, 0); // Ensure background is at the back
// Add subtle animated neon light sweep overlay for ambiance
var neonSweep = LK.getAsset('neon_sweep', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732,
alpha: 0.18
});
game.addChild(neonSweep);
tween(neonSweep, {
x: 2048
}, {
duration: 3200,
repeat: Infinity,
yoyo: true,
easing: tween.sineInOut
});
// Play synthwave music
LK.playMusic('synthwave');
// Game state
var isGameActive = true; // Track if the game is currently active
var totalTargets = 100;
var targetsHit = 0;
var currentTarget = null;
var targetIndex = 0;
var reactionTimes = [];
var targetAppearTime = 0;
// Track consecutive accurate hits for encouragement message
var consecutiveAccurateHits = 0;
// Message text for encouragement, hidden by default
var encouragementTxt = new Text2("You're doing great!", {
size: 100,
fill: 0xFFFC00
});
encouragementTxt.anchor.set(0.5, 0.5);
encouragementTxt.visible = false;
LK.gui.center.addChild(encouragementTxt);
// Add neon-glitch border overlay for cyberpunk effect
var borderOverlay = LK.getAsset('neon_border', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732,
alpha: 0.7
});
game.addChild(borderOverlay);
// Score text
var scoreTxt = new Text2('0 / 15', {
size: 120,
fill: 0x00FFF7
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Add a small neon-glitch logo at the top center for game branding
var logo = LK.getAsset('reflexometry_logo', {
anchorX: 0.5,
anchorY: 0,
x: 1024,
y: 10,
width: 400,
height: 100
});
LK.gui.top.addChild(logo);
// Reaction time text
var reactionTxt = new Text2('Reaction: -- ms', {
size: 70,
fill: 0xFF00C8
});
reactionTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(reactionTxt);
reactionTxt.y = 130;
// Fastest reaction time text
var fastestTxt = new Text2('Fastest: -- ms', {
size: 60,
fill: 0x00FFB0
});
fastestTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(fastestTxt);
fastestTxt.y = 210;
// Track fastest reaction time for this session
var fastestReaction = null;
// End screen text (hidden by default)
var endTxt = new Text2('', {
size: 110,
fill: 0xFFF600
});
endTxt.anchor.set(0.5, 0.5);
endTxt.visible = false;
LK.gui.center.addChild(endTxt);
// Used for event callback
var onEnemyHit = null;
// Generate random position for enemy (avoid top 200px and bottom 200px, and left 100px/right 100px)
function getRandomEnemyPos(enemyW, enemyH) {
var marginX = 160;
var marginY = 220;
var x = marginX + Math.random() * (2048 - 2 * marginX);
var y = marginY + Math.random() * (2732 - 2 * marginY);
return {
x: x,
y: y
};
}
// Show next target
function showNextTarget() {
if (currentTarget) {
currentTarget.destroy();
currentTarget = null;
}
if (targetIndex >= totalTargets) {
endGame();
return;
}
// Randomly select enemy size and spawn region
var sizeRand = Math.random();
var enemySize = 80;
var spawnInCenter = false;
if (sizeRand < 0.65) {
// 65%: 80x80 in center zone
enemySize = 80;
spawnInCenter = true;
} else if (sizeRand < 0.90) {
// 25%: 160x160 anywhere
enemySize = 160;
} else {
// 15%: 320x320 anywhere
enemySize = 320;
}
// Create enemy
var enemy = new Enemy();
// Set scale so the base asset (320x320) matches the chosen size
var scale = enemySize / 320;
enemy.scale.set(scale * (1.1 + Math.random() * 0.2));
// Get random position, passing the actual size
var pos;
if (spawnInCenter) {
// Center region: 2048x2732, center zone is 768px wide x 1024px high, centered
// Center zone: x in [640, 1408], y in [854, 1878]
var centerZoneW = 768;
var centerZoneH = 1024;
var minX = 1024 - centerZoneW / 2;
var minY = 1366 - centerZoneH / 2;
var x = minX + Math.random() * (centerZoneW - enemySize);
var y = minY + Math.random() * (centerZoneH - enemySize);
pos = {
x: x + enemySize / 2,
y: y + enemySize / 2
};
} else {
pos = getRandomEnemyPos(enemySize, enemySize);
}
enemy.x = pos.x;
enemy.y = pos.y;
enemy.rotation = (Math.random() - 0.5) * 0.2;
enemy.alpha = 0.0;
game.addChild(enemy);
// Animate in
tween(enemy, {
alpha: 1
}, {
duration: 120,
easing: tween.cubicOut
});
// Set up hit callback
onEnemyHit = function onEnemyHit(e) {
if (!isGameActive) return;
// Calculate reaction time
var now = Date.now();
var react = now - targetAppearTime;
reactionTimes.push(react);
targetsHit++;
scoreTxt.setText(targetsHit + ' / ' + totalTargets);
reactionTxt.setText('Reaction: ' + react + ' ms');
// If hit is accurate (under 1000ms), increment consecutiveAccurateHits, else reset
if (react < 1000) {
consecutiveAccurateHits++;
// Show encouragement every 10 consecutive accurate hits
if (consecutiveAccurateHits > 0 && consecutiveAccurateHits % 10 === 0) {
encouragementTxt.visible = true;
// Hide after 1.2s
LK.setTimeout(function () {
encouragementTxt.visible = false;
}, 1200);
}
} else {
consecutiveAccurateHits = 0;
}
// Update fastest reaction time if this is the best so far
if (fastestReaction === null || react < fastestReaction) {
fastestReaction = react;
fastestTxt.setText('Fastest: ' + fastestReaction + ' ms\nNew Record!');
// Remove 'New Record!' after 1.2s
LK.setTimeout(function () {
if (typeof fastestTxt.text === "string" && fastestTxt.text.indexOf('New Record!') !== -1) {
fastestTxt.setText('Fastest: ' + fastestReaction + ' ms');
}
}, 1200);
}
// Remove enemy after short delay
LK.setTimeout(function () {
if (currentTarget) {
currentTarget.destroy();
currentTarget = null;
}
targetIndex++;
showNextTarget();
}, 220);
};
// Set up auto-miss (if not hit in 1.2s)
LK.setTimeout(function () {
if (enemy.isHit || !isGameActive) return;
// Missed: just remove and go to next
reactionTimes.push(1200);
reactionTxt.setText('Reaction: MISS');
// Reset consecutive accurate hits on miss
consecutiveAccurateHits = 0;
tween(enemy, {
alpha: 0
}, {
duration: 120,
onFinish: function onFinish() {
if (currentTarget) {
currentTarget.destroy();
currentTarget = null;
}
targetIndex++;
showNextTarget();
}
});
}, 1200);
// Track for removal
currentTarget = enemy;
targetAppearTime = Date.now();
}
// End game
function endGame() {
isGameActive = false;
if (currentTarget) {
currentTarget.destroy();
currentTarget = null;
}
// Calculate stats
var hits = targetsHit;
var avgReact = 0;
var hitCount = 0;
for (var i = 0; i < reactionTimes.length; i++) {
if (reactionTimes[i] < 1000) {
avgReact += reactionTimes[i];
hitCount++;
}
}
avgReact = hitCount ? Math.round(avgReact / hitCount) : 0;
var msg = '';
if (hits === totalTargets) {
msg = 'PERFECT!\n';
} else if (hits >= totalTargets - 2) {
msg = 'Great job!\n';
} else {
msg = 'Keep practicing!\n';
}
msg += 'You hit ' + hits + ' / ' + totalTargets + '\n';
// Show only the fastest hit time
msg += 'Fastest Hit: ' + (fastestReaction !== null ? fastestReaction + ' ms' : '--') + '\n\nTap to play again!';
// Show 'Excellent performance!' at the end
msg += '\n\nExcellent performance!';
endTxt.setText(msg);
endTxt.visible = true;
// Show win/lose popup
if (hits === totalTargets) {
LK.showYouWin();
} else {
LK.showGameOver();
}
}
// Restart game on tap after end
game.down = function (x, y, obj) {
if (!isGameActive && endTxt.visible) {
// Reset state
targetsHit = 0;
targetIndex = 0;
reactionTimes = [];
isGameActive = true;
scoreTxt.setText('0 / ' + totalTargets);
reactionTxt.setText('Reaction: -- ms');
fastestReaction = null;
fastestTxt.setText('Fastest: -- ms');
endTxt.visible = false;
showNextTarget();
return;
}
// --- Weapon fire with laser trail ---
// Only fire if game is active
if (isGameActive) {
// Weapon origin: center bottom of weapon
var x0 = weapon.x;
var y0 = weapon.y + weaponSize / 2;
// Target: where user tapped (x, y)
var x1 = x;
var y1 = y;
// Clamp y1 to not go below the weapon
if (y1 > y0) y1 = y0;
// Create and show laser trail
var laser = new LaserTrail();
laser.init(x0, y0, x1, y1);
game.addChild(laser);
// Optionally: play shot sound
LK.getSound('shotSfx').play();
}
};
// Start game
showNextTarget();
// Add a bottom neon-glitch HUD bar for future UI elements
var hudBar = LK.getAsset('neon_hud_bar', {
anchorX: 0.5,
anchorY: 1,
x: 1024,
y: 2732,
width: 1800,
height: 120,
alpha: 0.85
});
LK.gui.bottom.addChild(hudBar);
// Show weapon (electric shot) at bottom center, above HUD bar
var weaponSize = 900; // Make weapon a much bigger square
// Position weapon so its bottom edge touches the bottom line of the game area
var weaponY = 2732 - weaponSize / 2;
var weapon = LK.getAsset('shot', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: weaponY,
width: weaponSize,
height: weaponSize,
alpha: 0.92,
rotation: 0
});
game.addChild(weapon);
// No dragging or move needed for this game
game.move = function (x, y, obj) {};
game.up = function (x, y, obj) {};
// No per-frame update needed
game.update = function () {};
;
A dark, cyberpunk-style photo background (city, alley, rooftop, etc.) Neon lighting, ambient flickers, and animated details for atmosphere.Creat more building and more windows could I see.. 2d
remove back ground
Looking like rough and dark with electrical weapon targets you a man.. make a yellow neon line to image 2d