/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Arrow Button class var ArrowButton = Container.expand(function () { var self = Container.call(this); self.direction = 'left'; // 'left', 'right', 'up', 'down' self.arrowAsset = null; self.setDirection = function (dir) { self.direction = dir; if (self.arrowAsset) { self.removeChild(self.arrowAsset); } var assetId = 'arrow_' + dir; self.arrowAsset = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); // Add a simple arrow "icon" using Text2 var arrowChar = { 'left': '←', 'right': '→', 'up': '↑', 'down': '↓' }[dir]; if (self.arrowText) { self.removeChild(self.arrowText); } self.arrowText = new Text2(arrowChar, { size: 120, fill: 0x222222 }); self.arrowText.anchor.set(0.5, 0.5); self.addChild(self.arrowText); }; return self; }); // Cat class var Cat = Container.expand(function () { var self = Container.call(this); self.lane = 1; // start in lane 1 (from 0-3) self.catAsset = self.attachAsset('cat', { anchorX: 0.5, anchorY: 0.5 }); self.setLane = function (lane) { self.lane = lane; self.x = laneCenters[lane]; }; return self; }); // Note class var Note = Container.expand(function () { var self = Container.call(this); self.color = 'red'; // default, will be set on spawn self.lane = 0; // 0-3 self.hit = false; self.setColor = function (color) { self.color = color; if (self.noteAsset) { self.removeChild(self.noteAsset); } var assetId = 'note_' + color; self.noteAsset = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); }; self.update = function () { self.y += noteSpeed; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 }); /**** * Game Code ****/ // Music (looping, for rhythm) // Sound effects // Arrow buttons // Lane backgrounds (for visual feedback) // Note shapes: four colors // Cat character: cute box, default color yellow // Lane setup var laneCount = 4; var laneWidth = 400; var laneSpacing = 32; var laneColors = ['red', 'blue', 'green', 'yellow']; var laneCenters = []; var laneLeft = (2048 - (laneCount * laneWidth + (laneCount - 1) * laneSpacing)) / 2; for (var i = 0; i < laneCount; i++) { laneCenters[i] = laneLeft + i * (laneWidth + laneSpacing) + laneWidth / 2; } // Lane backgrounds var laneBGs = []; for (var i = 0; i < laneCount; i++) { var assetId = 'lane_' + laneColors[i]; var laneBG = LK.getAsset(assetId, { anchorX: 0.5, anchorY: 0, x: laneCenters[i], y: 0, width: laneWidth, height: 2732 }); laneBG.interactive = true; laneBG.buttonMode = true; laneBG.laneIndex = i; laneBG.down = function (x, y, obj) { // Move cat to this lane if not already there if (cat.lane !== this.laneIndex) { cat.setLane(this.laneIndex); } }; game.addChild(laneBG); laneBGs.push(laneBG); } // Cat setup var cat = new Cat(); cat.setLane(1); cat.y = 2732 - 350; game.addChild(cat); // Arrow buttons (bottom of screen, left/right/up/down) var arrowButtons = []; var arrowButtonSize = 180; var arrowButtonY = 2732 - 120; var arrowButtonSpacing = 220; var arrowButtonXStart = 2048 / 2 - arrowButtonSpacing * 1.5; var arrowDirs = ['left', 'right', 'up', 'down']; for (var i = 0; i < 4; i++) { var btn = new ArrowButton(); btn.setDirection(arrowDirs[i]); btn.x = arrowButtonXStart + i * arrowButtonSpacing; btn.y = arrowButtonY; btn.direction = arrowDirs[i]; btn.interactive = true; btn.buttonMode = true; game.addChild(btn); // Place arrow buttons in the main game area so they are always visible arrowButtons.push(btn); } // Color change buttons removed (no longer needed) // Score display var scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Combo display var comboTxt = new Text2('', { size: 80, fill: 0xFFE066 }); comboTxt.anchor.set(0.5, 0); comboTxt.y = 130; LK.gui.top.addChild(comboTxt); // Speed selection buttons var speedOptions = [{ label: "0.5x", speed: 0.5, interval: 72 }, { label: "1x", speed: 1, interval: 36 }, { label: "2x", speed: 2, interval: 18 }]; var speedBtnY = 320; var speedBtnSpacing = 320; var speedBtnXStart = 2048 / 2 - speedBtnSpacing; var speedBtns = []; var selectedSpeedIdx = 1; // default 1x function setGameSpeed(idx) { selectedSpeedIdx = idx; for (var i = 0; i < speedBtns.length; i++) { speedBtns[i].alpha = i === idx ? 1 : 0.5; } noteSpeed = 18 * speedOptions[idx].speed; noteSpawnInterval = speedOptions[idx].interval; } for (var i = 0; i < speedOptions.length; i++) { var btn = new Container(); var bg = LK.getAsset('arrow_up', { anchorX: 0.5, anchorY: 0.5 }); bg.width = 220; bg.height = 120; btn.addChild(bg); var txt = new Text2(speedOptions[i].label, { size: 60, fill: "#222" }); txt.anchor.set(0.5, 0.5); btn.addChild(txt); btn.x = speedBtnXStart + i * speedBtnSpacing; btn.y = speedBtnY; btn.interactive = true; btn.buttonMode = true; (function (idx) { btn.down = function (x, y, obj) { setGameSpeed(idx); }; })(i); btn.alpha = i === selectedSpeedIdx ? 1 : 0.5; game.addChild(btn); speedBtns.push(btn); } setGameSpeed(selectedSpeedIdx); // Notes array var notes = []; var noteSpeed = 18; // will be set by speed selection var noteSpawnInterval = 36; // will be set by speed selection var noteTimer = 0; var noteColors = ['red', 'blue', 'green', 'yellow']; // Rhythm pattern (random for now, can be improved) function spawnNote() { var lane = Math.floor(Math.random() * 4); var color = noteColors[Math.floor(Math.random() * 4)]; var note = new Note(); note.setColor(color); note.lane = lane; note.x = laneCenters[lane]; note.y = -100; notes.push(note); game.addChild(note); } // Hit window (distance from cat.y to note.y for a "hit") var hitWindow = 120; // Score/combo var score = 0; var combo = 0; var maxCombo = 0; // Input handling function handleArrowButton(dir) { var newLane = cat.lane; if (dir === 'left') { newLane = Math.max(0, cat.lane - 1); } else if (dir === 'right') { newLane = Math.min(laneCount - 1, cat.lane + 1); } else if (dir === 'up') { // No vertical movement, but can add jump effect tween(cat, { y: cat.y - 60 }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { tween(cat, { y: 2732 - 350 }, { duration: 120, easing: tween.easeIn }); } }); return; } else if (dir === 'down') { // No vertical movement, but can add crouch effect tween(cat, { y: cat.y + 40 }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { tween(cat, { y: 2732 - 350 }, { duration: 120, easing: tween.easeIn }); } }); return; } if (newLane !== cat.lane) { cat.setLane(newLane); } } // Color change input handler and event attachment removed (no longer needed) for (var i = 0; i < arrowButtons.length; i++) { (function (btn) { btn.down = function (x, y, obj) { handleArrowButton(btn.direction); }; })(arrowButtons[i]); } // Attach input events to color buttons // Touch input: allow swipe left/right to move cat, tap on color buttons to change color var touchStartX = null; var touchStartY = null; game.down = function (x, y, obj) { touchStartX = x; touchStartY = y; }; game.up = function (x, y, obj) { if (touchStartX !== null && Math.abs(x - touchStartX) > 80 && Math.abs(x - touchStartX) > Math.abs(y - touchStartY)) { // Horizontal swipe if (x < touchStartX) { handleArrowButton('left'); } else { handleArrowButton('right'); } } touchStartX = null; touchStartY = null; }; // Main update loop game.update = function () { // Spawn notes if (LK.ticks % noteSpawnInterval === 0) { spawnNote(); } // Update notes for (var i = notes.length - 1; i >= 0; i--) { var note = notes[i]; note.update(); // Check for hit if (!note.hit && Math.abs(note.y - cat.y) < hitWindow && note.lane === cat.lane) { // Hit! note.hit = true; score += 1; combo += 1; if (combo > maxCombo) maxCombo = combo; scoreTxt.setText(score); comboTxt.setText(combo > 1 ? combo + ' combo!' : ''); LK.getSound('note_hit').play(); LK.effects.flashObject(cat, 0xffffff, 120); // Animate note tween(note, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 180, onFinish: function onFinish() { note.destroy(); } }); notes.splice(i, 1); continue; } // Missed note (passed cat) if (!note.hit && note.y > cat.y + hitWindow) { note.hit = true; combo = 0; comboTxt.setText(''); LK.getSound('note_miss').play(); LK.effects.flashScreen(0xff4b4b, 200); // Animate note tween(note, { alpha: 0 }, { duration: 120, onFinish: function onFinish() { note.destroy(); } }); notes.splice(i, 1); continue; } // Remove notes off screen if (note.y > 2732 + 200) { note.destroy(); notes.splice(i, 1); } } // Infinite mode: no win condition }; // Start music LK.playMusic('bgmusic', { fade: { start: 0, end: 0.7, duration: 1200 } });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Arrow Button class
var ArrowButton = Container.expand(function () {
var self = Container.call(this);
self.direction = 'left'; // 'left', 'right', 'up', 'down'
self.arrowAsset = null;
self.setDirection = function (dir) {
self.direction = dir;
if (self.arrowAsset) {
self.removeChild(self.arrowAsset);
}
var assetId = 'arrow_' + dir;
self.arrowAsset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Add a simple arrow "icon" using Text2
var arrowChar = {
'left': '←',
'right': '→',
'up': '↑',
'down': '↓'
}[dir];
if (self.arrowText) {
self.removeChild(self.arrowText);
}
self.arrowText = new Text2(arrowChar, {
size: 120,
fill: 0x222222
});
self.arrowText.anchor.set(0.5, 0.5);
self.addChild(self.arrowText);
};
return self;
});
// Cat class
var Cat = Container.expand(function () {
var self = Container.call(this);
self.lane = 1; // start in lane 1 (from 0-3)
self.catAsset = self.attachAsset('cat', {
anchorX: 0.5,
anchorY: 0.5
});
self.setLane = function (lane) {
self.lane = lane;
self.x = laneCenters[lane];
};
return self;
});
// Note class
var Note = Container.expand(function () {
var self = Container.call(this);
self.color = 'red'; // default, will be set on spawn
self.lane = 0; // 0-3
self.hit = false;
self.setColor = function (color) {
self.color = color;
if (self.noteAsset) {
self.removeChild(self.noteAsset);
}
var assetId = 'note_' + color;
self.noteAsset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
};
self.update = function () {
self.y += noteSpeed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// Music (looping, for rhythm)
// Sound effects
// Arrow buttons
// Lane backgrounds (for visual feedback)
// Note shapes: four colors
// Cat character: cute box, default color yellow
// Lane setup
var laneCount = 4;
var laneWidth = 400;
var laneSpacing = 32;
var laneColors = ['red', 'blue', 'green', 'yellow'];
var laneCenters = [];
var laneLeft = (2048 - (laneCount * laneWidth + (laneCount - 1) * laneSpacing)) / 2;
for (var i = 0; i < laneCount; i++) {
laneCenters[i] = laneLeft + i * (laneWidth + laneSpacing) + laneWidth / 2;
}
// Lane backgrounds
var laneBGs = [];
for (var i = 0; i < laneCount; i++) {
var assetId = 'lane_' + laneColors[i];
var laneBG = LK.getAsset(assetId, {
anchorX: 0.5,
anchorY: 0,
x: laneCenters[i],
y: 0,
width: laneWidth,
height: 2732
});
laneBG.interactive = true;
laneBG.buttonMode = true;
laneBG.laneIndex = i;
laneBG.down = function (x, y, obj) {
// Move cat to this lane if not already there
if (cat.lane !== this.laneIndex) {
cat.setLane(this.laneIndex);
}
};
game.addChild(laneBG);
laneBGs.push(laneBG);
}
// Cat setup
var cat = new Cat();
cat.setLane(1);
cat.y = 2732 - 350;
game.addChild(cat);
// Arrow buttons (bottom of screen, left/right/up/down)
var arrowButtons = [];
var arrowButtonSize = 180;
var arrowButtonY = 2732 - 120;
var arrowButtonSpacing = 220;
var arrowButtonXStart = 2048 / 2 - arrowButtonSpacing * 1.5;
var arrowDirs = ['left', 'right', 'up', 'down'];
for (var i = 0; i < 4; i++) {
var btn = new ArrowButton();
btn.setDirection(arrowDirs[i]);
btn.x = arrowButtonXStart + i * arrowButtonSpacing;
btn.y = arrowButtonY;
btn.direction = arrowDirs[i];
btn.interactive = true;
btn.buttonMode = true;
game.addChild(btn); // Place arrow buttons in the main game area so they are always visible
arrowButtons.push(btn);
}
// Color change buttons removed (no longer needed)
// Score display
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Combo display
var comboTxt = new Text2('', {
size: 80,
fill: 0xFFE066
});
comboTxt.anchor.set(0.5, 0);
comboTxt.y = 130;
LK.gui.top.addChild(comboTxt);
// Speed selection buttons
var speedOptions = [{
label: "0.5x",
speed: 0.5,
interval: 72
}, {
label: "1x",
speed: 1,
interval: 36
}, {
label: "2x",
speed: 2,
interval: 18
}];
var speedBtnY = 320;
var speedBtnSpacing = 320;
var speedBtnXStart = 2048 / 2 - speedBtnSpacing;
var speedBtns = [];
var selectedSpeedIdx = 1; // default 1x
function setGameSpeed(idx) {
selectedSpeedIdx = idx;
for (var i = 0; i < speedBtns.length; i++) {
speedBtns[i].alpha = i === idx ? 1 : 0.5;
}
noteSpeed = 18 * speedOptions[idx].speed;
noteSpawnInterval = speedOptions[idx].interval;
}
for (var i = 0; i < speedOptions.length; i++) {
var btn = new Container();
var bg = LK.getAsset('arrow_up', {
anchorX: 0.5,
anchorY: 0.5
});
bg.width = 220;
bg.height = 120;
btn.addChild(bg);
var txt = new Text2(speedOptions[i].label, {
size: 60,
fill: "#222"
});
txt.anchor.set(0.5, 0.5);
btn.addChild(txt);
btn.x = speedBtnXStart + i * speedBtnSpacing;
btn.y = speedBtnY;
btn.interactive = true;
btn.buttonMode = true;
(function (idx) {
btn.down = function (x, y, obj) {
setGameSpeed(idx);
};
})(i);
btn.alpha = i === selectedSpeedIdx ? 1 : 0.5;
game.addChild(btn);
speedBtns.push(btn);
}
setGameSpeed(selectedSpeedIdx);
// Notes array
var notes = [];
var noteSpeed = 18; // will be set by speed selection
var noteSpawnInterval = 36; // will be set by speed selection
var noteTimer = 0;
var noteColors = ['red', 'blue', 'green', 'yellow'];
// Rhythm pattern (random for now, can be improved)
function spawnNote() {
var lane = Math.floor(Math.random() * 4);
var color = noteColors[Math.floor(Math.random() * 4)];
var note = new Note();
note.setColor(color);
note.lane = lane;
note.x = laneCenters[lane];
note.y = -100;
notes.push(note);
game.addChild(note);
}
// Hit window (distance from cat.y to note.y for a "hit")
var hitWindow = 120;
// Score/combo
var score = 0;
var combo = 0;
var maxCombo = 0;
// Input handling
function handleArrowButton(dir) {
var newLane = cat.lane;
if (dir === 'left') {
newLane = Math.max(0, cat.lane - 1);
} else if (dir === 'right') {
newLane = Math.min(laneCount - 1, cat.lane + 1);
} else if (dir === 'up') {
// No vertical movement, but can add jump effect
tween(cat, {
y: cat.y - 60
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(cat, {
y: 2732 - 350
}, {
duration: 120,
easing: tween.easeIn
});
}
});
return;
} else if (dir === 'down') {
// No vertical movement, but can add crouch effect
tween(cat, {
y: cat.y + 40
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(cat, {
y: 2732 - 350
}, {
duration: 120,
easing: tween.easeIn
});
}
});
return;
}
if (newLane !== cat.lane) {
cat.setLane(newLane);
}
}
// Color change input handler and event attachment removed (no longer needed)
for (var i = 0; i < arrowButtons.length; i++) {
(function (btn) {
btn.down = function (x, y, obj) {
handleArrowButton(btn.direction);
};
})(arrowButtons[i]);
}
// Attach input events to color buttons
// Touch input: allow swipe left/right to move cat, tap on color buttons to change color
var touchStartX = null;
var touchStartY = null;
game.down = function (x, y, obj) {
touchStartX = x;
touchStartY = y;
};
game.up = function (x, y, obj) {
if (touchStartX !== null && Math.abs(x - touchStartX) > 80 && Math.abs(x - touchStartX) > Math.abs(y - touchStartY)) {
// Horizontal swipe
if (x < touchStartX) {
handleArrowButton('left');
} else {
handleArrowButton('right');
}
}
touchStartX = null;
touchStartY = null;
};
// Main update loop
game.update = function () {
// Spawn notes
if (LK.ticks % noteSpawnInterval === 0) {
spawnNote();
}
// Update notes
for (var i = notes.length - 1; i >= 0; i--) {
var note = notes[i];
note.update();
// Check for hit
if (!note.hit && Math.abs(note.y - cat.y) < hitWindow && note.lane === cat.lane) {
// Hit!
note.hit = true;
score += 1;
combo += 1;
if (combo > maxCombo) maxCombo = combo;
scoreTxt.setText(score);
comboTxt.setText(combo > 1 ? combo + ' combo!' : '');
LK.getSound('note_hit').play();
LK.effects.flashObject(cat, 0xffffff, 120);
// Animate note
tween(note, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 180,
onFinish: function onFinish() {
note.destroy();
}
});
notes.splice(i, 1);
continue;
}
// Missed note (passed cat)
if (!note.hit && note.y > cat.y + hitWindow) {
note.hit = true;
combo = 0;
comboTxt.setText('');
LK.getSound('note_miss').play();
LK.effects.flashScreen(0xff4b4b, 200);
// Animate note
tween(note, {
alpha: 0
}, {
duration: 120,
onFinish: function onFinish() {
note.destroy();
}
});
notes.splice(i, 1);
continue;
}
// Remove notes off screen
if (note.y > 2732 + 200) {
note.destroy();
notes.splice(i, 1);
}
}
// Infinite mode: no win condition
};
// Start music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 0.7,
duration: 1200
}
});