/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // EnemyBike class: faster enemy type var EnemyBike = Container.expand(function () { var self = Container.call(this); // Attach enemyBike asset (image, facing upward) var bike = self.attachAsset('enemyBike', { anchorX: 0.5, anchorY: 0.5 }); // Set vertical speed (will be set on spawn) self.speedY = 24; // Track lastY for event triggers self.lastY = self.y; self.update = function () { self.lastY = self.y; self.y += self.speedY; // EnemyBike moves straight down in its lane }; return self; }); // Motorcycle class: player-controlled bike var Motorcycle = Container.expand(function () { var self = Container.call(this); // Attach motorcycle asset (image, facing upward) var bike = self.attachAsset('motorcycle', { anchorX: 0.5, anchorY: 0.5 }); // Set initial speed and direction self.speedY = 18; // vertical speed (track scrolls down) self.laneSpeed = 0; // horizontal speed (player control) self.maxX = 2048 - bike.width / 2; self.minX = bike.width / 2; // Track lastX for event triggers self.lastX = self.x; // Update method: move forward, apply lane movement self.update = function () { self.lastX = self.x; self.x += self.laneSpeed; // --- Invisible collision boundaries at road edges --- // Calculate road boundaries (match neon lines) var roadLeftEdge = 2048 / 2 - 600; var roadRightEdge = 2048 / 2 + 600; var bikeHalfWidth = self.width ? self.width / 2 : 60; // fallback if width not set // Left boundary collision if (self.x - bikeHalfWidth < roadLeftEdge) { self.x = roadLeftEdge + bikeHalfWidth; // Bounce-back effect: nudge right with tween if just hit if (self.lastX - bikeHalfWidth >= roadLeftEdge) { tween.stop(self, { x: true }); tween(self, { x: self.x + 40 }, { duration: 80, easing: tween.cubicOut, onFinish: function onFinish() { tween(self, { x: roadLeftEdge + bikeHalfWidth }, { duration: 60, easing: tween.cubicIn }); } }); } } // Right boundary collision if (self.x + bikeHalfWidth > roadRightEdge) { self.x = roadRightEdge - bikeHalfWidth; // Bounce-back effect: nudge left with tween if just hit if (self.lastX + bikeHalfWidth <= roadRightEdge) { tween.stop(self, { x: true }); tween(self, { x: self.x - 40 }, { duration: 80, easing: tween.cubicOut, onFinish: function onFinish() { tween(self, { x: roadRightEdge - bikeHalfWidth }, { duration: 60, easing: tween.cubicIn }); } }); } } }; return self; }); // Obstacle class: static/dynamic hazards var Obstacle = Container.expand(function () { var self = Container.call(this); // Attach obstacle asset (box, red) var obs = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.5 }); // Set vertical speed (scrolls with track) self.speedY = 18; // Track lastY for event triggers self.lastY = self.y; self.update = function () { self.lastY = self.y; self.y += self.speedY; }; return self; }); // PowerUp class: collectible items var PowerUp = Container.expand(function () { var self = Container.call(this); // Attach powerup asset (ellipse, yellow) var pu = self.attachAsset('powerup', { anchorX: 0.5, anchorY: 0.5 }); self.speedY = 18; self.lastY = self.y; self.update = function () { self.lastY = self.y; self.y += self.speedY; }; return self; }); /**** * Initialize Game ****/ // Motorcycle asset (blue box) // Obstacle asset (red box) // PowerUp asset (yellow ellipse) var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // --- Combo Counter --- //Minimalistic tween library which should be used for animations over time, including tinting / colouring an object, scaling, rotating, or changing any game object property. var comboCount = 0; var comboText = new Text2('Combo! 0', { size: 60, fill: "#fff", font: "Arial, Helvetica, sans-serif" }); comboText.anchor.set(1, 0); // Match scoreTxt anchor (right edge, top) // comboText.x and comboText.y will be set after scoreTxt is defined // Helper to update comboText display and position function updateComboText() { // Always show "Combo! 0" when comboCount is zero comboText.setText("Combo! " + comboCount); comboText.visible = true; // Position below scoreTxt, right-aligned comboText.anchor.set(1, 0); comboText.x = scoreTxt.x; comboText.y = scoreTxt.y + scoreTxt.height + 10; if (comboText.parent !== LK.gui.topRight) { if (comboText.parent) { comboText.parent.removeChild(comboText); } LK.gui.topRight.addChild(comboText); } } // Ensure comboText is added to the display list so it appears on screen if (!comboText.parent) { LK.gui.topRight.addChild(comboText); } // --- Katana Combo System --- var katanaComboIndex = 0; // --- Katana Combo Sound System --- var katanaSounds = ["katanaslashsound", "katanaslash", "katanaslash1", "katanaslash2", "katanaslash3"]; // Track the currently playing katana sound instance var currentKatanaSound = null; function playKatanaComboSound() { if (Array.isArray(katanaSounds) && katanaSounds.length > 0) { // Use a unique sound channel for katanaSlash sounds var katanaChannel = "katanaComboChannel"; // Stop any currently playing katana sound instance if (currentKatanaSound && typeof currentKatanaSound.stop === "function") { currentKatanaSound.stop(); } var soundId = katanaSounds[katanaComboIndex]; var sound = LK.getSound(soundId); if (sound && typeof sound.play === "function") { // Play the sound on the unique channel sound.play({ channel: katanaChannel }); currentKatanaSound = sound; } else { currentKatanaSound = null; } katanaComboIndex++; if (katanaComboIndex >= katanaSounds.length) { katanaComboIndex = 0; } } } // --- Road Background --- // Motorcycle asset (blue box) // Obstacle asset (red box) // PowerUp asset (yellow ellipse) // --- Game State --- // Road background: gray // (punchProjectile logic removed) // Robust typeof helper for sandboxed environment (no Symbol polyfill issues) // Robust typeof helper for sandboxed environment (no Symbol polyfill issues) function _typeof9(o) { "@babel/helpers - typeof"; return _typeof9 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof9(o); } function _typeof8(o) { "@babel/helpers - typeof"; return _typeof8 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof8(o); } function _typeof7(o) { "@babel/helpers - typeof"; return _typeof7 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof7(o); } function _typeof6(o) { "@babel/helpers - typeof"; return _typeof6 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof6(o); } function _typeof5(o) { "@babel/helpers - typeof"; return _typeof5 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof5(o); } function _typeof4(o) { // Robust typeof helper for sandboxed environment (no Symbol polyfill issues) if (o === null) { return "object"; } var t = _typeof5(o); // Defensive: avoid TypeError if t is undefined or not a string if (typeof t === "undefined") { return "undefined"; } if (typeof t !== "string") { return t; } // Defensive: avoid accessing t[0] if t is undefined, not a string, or empty if (!t || typeof t !== "string" || t.length === 0) { return t; } if (t[0] !== "o") { return t; } // Defensive: avoid accessing properties of undefined or null // No Symbol checks, just return "object" for objects/arrays return t; } function _typeof3(o) { // Robust typeof helper for sandboxed environment (no Symbol polyfill issues) if (o === null) { return "object"; } var t = _typeof5(o); if (t !== "object") { return t; } // Array.isArray is always "object" in typeof return t; } function _typeof2(o) { // Only use native typeof, avoid Symbol checks for compatibility and sandbox errors // This is safe in all supported environments if (o === null) { return "object"; } var t = _typeof5(o); if (t !== "object") { return t; } return t; } function _typeof(o) { return _typeof2(o); } var roadCenterX = 2048 / 2; var roadBg1 = LK.getAsset('roadBg', { anchorX: 0.5, anchorY: 0, width: 1200, height: 2732, x: roadCenterX, y: 0 }); var roadBg2 = LK.getAsset('roadBg', { anchorX: 0.5, anchorY: 0, width: 1200, height: 2732, x: roadCenterX, y: -2732 }); // If the asset doesn't exist, create it as a gray box if (!roadBg1) { roadBg1 = LK.getAsset('roadBg', { anchorX: 0.5, anchorY: 0, width: 1200, height: 2732, x: roadCenterX, y: 0 }); roadBg2 = LK.getAsset('roadBg', { anchorX: 0.5, anchorY: 0, width: 1200, height: 2732, x: roadCenterX, y: -2732 }); } game.addChild(roadBg1); game.addChild(roadBg2); // --- Road Boundary Lines (Neon) --- // Define neon color (electric blue) var boundaryColor = 0x00ffff; // Electric blue var boundaryWidth = 12; var boundaryHeight = 2732; var roadLeftEdge = roadCenterX - 600; // roadBg is 1200 wide, centered var roadRightEdge = roadCenterX + 600; // Left boundary line var leftBoundary = LK.getAsset('laneDash', { width: boundaryWidth, height: boundaryHeight, color: boundaryColor, anchorX: 0.5, anchorY: 0 }); leftBoundary.x = roadLeftEdge; leftBoundary.y = 0; game.addChild(leftBoundary); // Right boundary line var rightBoundary = LK.getAsset('laneDash', { width: boundaryWidth, height: boundaryHeight, color: boundaryColor, anchorX: 0.5, anchorY: 0 }); rightBoundary.x = roadRightEdge; rightBoundary.y = 0; game.addChild(rightBoundary); // Lane definitions for 3-lane road (centered, full width 2048) var laneCount = 3; var laneWidth = 400; var laneX = [2048 / 2 - laneWidth, // left lane center 2048 / 2, // center lane center 2048 / 2 + laneWidth // right lane center ]; // --- Lane Lines (dashed) --- var laneLineContainers = []; var laneLineCount = 2; // 2 lines between 3 lanes var dashHeight = 80; var dashGap = 80; var lineWidth = 16; var lineColor = 0xffffff; var roadTop = 0; var roadBottom = 2732; var dashesPerLine = Math.ceil((roadBottom - roadTop) / (dashHeight + dashGap)) + 2; for (var i = 0; i < laneLineCount; i++) { var lineContainer = new Container(); var x = (laneX[i] + laneX[i + 1]) / 2; for (var d = 0; d < dashesPerLine; d++) { // Use the laneDash asset for white dashes var dash = LK.getAsset('laneDash', { width: lineWidth, height: dashHeight, anchorX: 0.5, anchorY: 0 }); dash.x = x; dash.y = roadTop + d * (dashHeight + dashGap); lineContainer.addChild(dash); } laneLineContainers.push(lineContainer); game.addChild(lineContainer); } var player = new Motorcycle(); player.x = 2048 / 2; // Center horizontally player.y = 2732 - 520; // Move player even higher up (was 2732 - 420) game.addChild(player); // Defensive: always initialize arrays to avoid undefined/null errors if (typeof obstacles === "undefined" || !obstacles || !Array.isArray(obstacles)) { var obstacles = []; } else if (!Array.isArray(obstacles)) { obstacles = []; } if (typeof enemyBikes === "undefined" || !enemyBikes || !Array.isArray(enemyBikes)) { var enemyBikes = []; } else if (!Array.isArray(enemyBikes)) { enemyBikes = []; } if (typeof powerups === "undefined" || !powerups || !Array.isArray(powerups)) { var powerups = []; } else if (!Array.isArray(powerups)) { powerups = []; } // --- Score --- var score = 0; var distance = 0; // --- Health --- var playerHealth = 3; var maxPlayerHealth = 3; // Container for tire icons var healthIcons = new Container(); healthIcons.x = 110; healthIcons.y = 0; LK.gui.top.addChild(healthIcons); // Helper to update tire icons function updateHealthIcons() { // Remove all previous icons while (healthIcons.children.length > 0) { var c = healthIcons.children.pop(); if (c && typeof c.destroy === "function") { c.destroy(); } } // Add one tire icon per health for (var h = 0; h < playerHealth; h++) { var tire = LK.getAsset('tireIcon', { anchorX: 0, anchorY: 0, width: 90, height: 90 }); tire.x = h * 100; tire.y = 10; healthIcons.addChild(tire); } } updateHealthIcons(); // Score display var scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); // Anchor to the right edge, top (1, 0) scoreTxt.anchor.set(1, 0); // Add to top-right GUI overlay LK.gui.topRight.addChild(scoreTxt); // Now that scoreTxt is defined, set comboText position and add to GUI if not already present // --- Motocockpit UI (center-bottom, slightly above edge) --- var motocockpit = LK.getAsset('motocockpit', { anchorX: 0.5, anchorY: 1 }); motocockpit.x = LK.gui.center.width / 2; motocockpit.y = 10; // 30px above the bottom edge to avoid clipping LK.gui.bottom.addChild(motocockpit); // --- Version Label (bottom-left) --- var versionLabel = new Text2('v_0.2', { size: 48, fill: "#fff", font: "Arial, Helvetica, sans-serif" }); versionLabel.anchor.set(0, 1); // left-bottom corner of text // Position: bottom-left, with a small margin from the edge versionLabel.x = 40; versionLabel.y = -20; LK.gui.bottomLeft.addChild(versionLabel); // (Punch button removed) // Touch drag to steer var dragActive = false; var dragOffsetX = 0; game.down = function (x, y, obj) { // (Punch logic on right half tap removed) // Only start drag if touch is near the player var dx = x - player.x; var dy = y - player.y; if (dx * dx + dy * dy < 300 * 300) { dragActive = true; dragOffsetX = player.x - x; } }; game.move = function (x, y, obj) { if (dragActive) { player.x = x + dragOffsetX; } }; game.up = function (x, y, obj) { dragActive = false; }; // --- Spawning logic --- var obstacleTimer = 0; var powerupTimer = 0; // --- Difficulty scaling --- var enemyBaseSpeed = 24; // initial EnemyBike speed var enemySpeed = enemyBaseSpeed; var enemyBaseSpawnInterval = 40; // initial spawn interval (frames) var enemySpawnInterval = enemyBaseSpawnInterval; var difficultyTimer = 0; // counts frames for difficulty increase var difficultyInterval = 600; // every 600 frames (~10s at 60fps), increase difficulty var enemySpeedIncrement = 2; // how much to increase speed each interval var enemySpawnDecrement = 4; // how much to decrease spawn interval each interval (minimum capped) var minEnemySpawnInterval = 12; // minimum allowed spawn interval // --- KatanaSlash Melee System --- var katanaSlashCooldown = 0; // frames until next slash (120 frames = 2s at 60fps) var katanaSlashRadius = 200; // px radius for melee // --- Main update loop --- game.update = function () { // Scroll road background downward to simulate forward motion roadBg1.y += player.speedY; roadBg2.y += player.speedY; // Loop backgrounds if (roadBg1.y >= 2732) { roadBg1.y = roadBg2.y - 2732; } if (roadBg2.y >= 2732) { roadBg2.y = roadBg1.y - 2732; } // Scroll and loop lane lines downward for (var i = 0; i < laneLineContainers.length; i++) { var lineContainer = laneLineContainers[i]; for (var j = 0; j < lineContainer.children.length; j++) { var dash = lineContainer.children[j]; dash.y += player.speedY; if (dash.y > 2732) { dash.y -= (dashHeight + dashGap) * lineContainer.children.length; // This ensures the dash loops back to the top } } } // Move player (handled in class) player.update(); // Scroll obstacles and powerups for (var i = obstacles.length - 1; i >= 0; i--) { var obs = obstacles[i]; obs.update(); // Remove if off screen (bottom or top or far left/right) if (obs.y >= 2732 + 100 || obs.y < -200 || obs.x < -200 || obs.x > 2048 + 200) { obs.destroy(); obstacles.splice(i, 1); continue; } // Collision detection (trigger on first intersect) if (!obs.lastWasIntersecting && obs.intersects(player)) { // Log game over trigger console.log("Game Over Triggered"); // Check if carcrashsound is loaded var carCrashSound = LK.getSound('carcrashsound'); if (carCrashSound && typeof carCrashSound.play === "function") { console.log("carcrashsound loaded successfully"); } else { console.log("carcrashsound not loaded or invalid"); } LK.effects.flashScreen(0xff0000, 800); // Immediately stop all sounds and music before delay LK.stopMusic(); // Stop any currently playing katana sound if (currentKatanaSound && typeof currentKatanaSound.stop === "function") { currentKatanaSound.stop(); } // Stop all other sounds that might be playing var testSound = LK.getSound('testSound'); if (testSound && typeof testSound.stop === "function") { testSound.stop(); } var motorCrashSound = LK.getSound('motorcrashsound'); if (motorCrashSound && typeof motorCrashSound.stop === "function") { motorCrashSound.stop(); } var katanaSlashSound = LK.getSound('katanaslashsound'); if (katanaSlashSound && typeof katanaSlashSound.stop === "function") { katanaSlashSound.stop(); } // Play carcrashsound immediately before showing game over if (LK.getSound && typeof LK.getSound === "function") { var gameOverCrashSound = LK.getSound('carcrashsound'); if (gameOverCrashSound && typeof gameOverCrashSound.play === "function") { gameOverCrashSound.volume = 1.0; gameOverCrashSound.play(); console.log("carcrashsound play triggered"); } } // Wait 0.65 seconds after sound starts, then show game over LK.setTimeout(function () { LK.showGameOver(); }, 650); return; } obs.lastWasIntersecting = obs.intersects(player); } // Handle enemyBikes for (var i = enemyBikes.length - 1; i >= 0; i--) { var eb = enemyBikes[i]; eb.update(); // Remove if off screen (bottom or top or far left/right) if (eb.y >= 2732 + 100 || eb.y < -200 || eb.x < -200 || eb.x > 2048 + 200) { eb.destroy(); enemyBikes.splice(i, 1); // Reset comboCount if enemy escapes if (comboCount !== 0) { comboCount = 0; updateComboText(); } continue; } // Collision detection (trigger on first intersect) if (!eb.lastWasIntersecting && eb.intersects(player)) { // Do nothing: enemy motorcycle collision has no effect, no sound, no animation, no feedback } eb.lastWasIntersecting = eb.intersects(player); // (auto-punch logic removed) } // Powerups for (var i = powerups.length - 1; i >= 0; i--) { var pu = powerups[i]; pu.update(); // Remove if off screen (bottom or top or far left/right) if (pu.y >= 2732 + 100 || pu.y < -200 || pu.x < -200 || pu.x > 2048 + 200) { pu.destroy(); powerups.splice(i, 1); continue; } // Collect powerup if (!pu.lastWasIntersecting && pu.intersects(player)) { // Play chassound immediately upon collection var powerUpSound = LK.getSound('chassound'); if (powerUpSound && typeof powerUpSound.play === "function") { powerUpSound.play(); } score += 10; scoreTxt.setText(score); pu.destroy(); powerups.splice(i, 1); continue; } pu.lastWasIntersecting = pu.intersects(player); } // --- Difficulty scaling --- difficultyTimer++; if (difficultyTimer >= difficultyInterval) { difficultyTimer = 0; // Increase enemy speed enemySpeed += enemySpeedIncrement; // Decrease spawn interval, but not below minimum enemySpawnInterval -= enemySpawnDecrement; if (enemySpawnInterval < minEnemySpawnInterval) { enemySpawnInterval = minEnemySpawnInterval; } } // --- Spawning --- obstacleTimer++; if (obstacleTimer > enemySpawnInterval) { obstacleTimer = 0; // Randomly decide to spawn an Obstacle or an EnemyBike (e.g. 70% car, 30% enemyBike) if (Math.random() < 0.3) { var laneIdx = Math.floor(Math.random() * laneCount); var eb = new EnemyBike(); eb.laneIdx = laneIdx; // Store lane index for reference eb.x = laneX[laneIdx]; eb.y = -100; eb.lastWasIntersecting = false; eb.speedY = enemySpeed; // Set current enemy speed game.addChild(eb); enemyBikes.push(eb); } else { var obs = new Obstacle(); // Place obstacle in a random lane var laneIdx = Math.floor(Math.random() * laneCount); obs.x = laneX[laneIdx]; obs.y = -100; obs.lastWasIntersecting = false; game.addChild(obs); obstacles.push(obs); } } powerupTimer++; if (powerupTimer > 120) { powerupTimer = 0; var pu = new PowerUp(); // Place powerup in a random lane var laneIdx = Math.floor(Math.random() * laneCount); pu.x = laneX[laneIdx]; pu.y = -100; pu.lastWasIntersecting = false; game.addChild(pu); powerups.push(pu); } // --- KatanaSlash Melee System --- // Instantly destroy any enemyBike within katanaSlashRadius of the player and show a glowing red beam effect var katanaHit = false; var katanaKills = 0; for (var i = enemyBikes.length - 1; i >= 0; i--) { var eb = enemyBikes[i]; var dx = eb.x - player.x; var dy = eb.y - player.y; if (dx * dx + dy * dy <= katanaSlashRadius * katanaSlashRadius) { katanaHit = true; // Draw a katana sprite image between player and enemyBike var length = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx); // Play katana slash sound var katanaSlashSound = LK.getSound('katanaslashsound'); if (katanaSlashSound && typeof katanaSlashSound.play === "function") { katanaSlashSound.play(); } // Use a katana image asset (ensure it's initialized in Assets section) var katana = LK.getAsset('katana', { anchorX: 0, // start of blade at player anchorY: 0.5 }); katana.x = player.x + 30; katana.y = player.y - 20; katana.rotation = angle; // Scale katana to match the distance between player and enemyBike // Use katana's original width for scaling if (katana.width > 0) { katana.scaleX = length / katana.width; } else { katana.scaleX = 1; } katana.scaleY = 1.0; // keep blade thickness katana.alpha = 0.92; game.addChild(katana); // Remove katana after 200ms (function (k) { var timeoutId = LK.setTimeout(function () { if (k && typeof k.destroy === "function") { k.destroy(); } }, 200); })(katana); eb.destroy(); enemyBikes.splice(i, 1); // Award score as 5 * comboCount (minimum 5 if comboCount is 0) var awardedCombo = comboCount > 0 ? comboCount : 1; score += 5 * awardedCombo; scoreTxt.setText(score); katanaKills++; } } // Only increment comboCount if at least one enemyBike was killed by katana if (katanaKills > 0) { comboCount += katanaKills; updateComboText(); playKatanaComboSound(); } // --- Distance/score --- distance += player.speedY; if (distance % 1000 < player.speedY) { score += 1; scoreTxt.setText(score); } // Endless mode: no win condition, game continues until playerHealth reaches zero }; // Play background music continuously throughout the game LK.playMusic('gamesound');
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// EnemyBike class: faster enemy type
var EnemyBike = Container.expand(function () {
var self = Container.call(this);
// Attach enemyBike asset (image, facing upward)
var bike = self.attachAsset('enemyBike', {
anchorX: 0.5,
anchorY: 0.5
});
// Set vertical speed (will be set on spawn)
self.speedY = 24;
// Track lastY for event triggers
self.lastY = self.y;
self.update = function () {
self.lastY = self.y;
self.y += self.speedY;
// EnemyBike moves straight down in its lane
};
return self;
});
// Motorcycle class: player-controlled bike
var Motorcycle = Container.expand(function () {
var self = Container.call(this);
// Attach motorcycle asset (image, facing upward)
var bike = self.attachAsset('motorcycle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial speed and direction
self.speedY = 18; // vertical speed (track scrolls down)
self.laneSpeed = 0; // horizontal speed (player control)
self.maxX = 2048 - bike.width / 2;
self.minX = bike.width / 2;
// Track lastX for event triggers
self.lastX = self.x;
// Update method: move forward, apply lane movement
self.update = function () {
self.lastX = self.x;
self.x += self.laneSpeed;
// --- Invisible collision boundaries at road edges ---
// Calculate road boundaries (match neon lines)
var roadLeftEdge = 2048 / 2 - 600;
var roadRightEdge = 2048 / 2 + 600;
var bikeHalfWidth = self.width ? self.width / 2 : 60; // fallback if width not set
// Left boundary collision
if (self.x - bikeHalfWidth < roadLeftEdge) {
self.x = roadLeftEdge + bikeHalfWidth;
// Bounce-back effect: nudge right with tween if just hit
if (self.lastX - bikeHalfWidth >= roadLeftEdge) {
tween.stop(self, {
x: true
});
tween(self, {
x: self.x + 40
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self, {
x: roadLeftEdge + bikeHalfWidth
}, {
duration: 60,
easing: tween.cubicIn
});
}
});
}
}
// Right boundary collision
if (self.x + bikeHalfWidth > roadRightEdge) {
self.x = roadRightEdge - bikeHalfWidth;
// Bounce-back effect: nudge left with tween if just hit
if (self.lastX + bikeHalfWidth <= roadRightEdge) {
tween.stop(self, {
x: true
});
tween(self, {
x: self.x - 40
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self, {
x: roadRightEdge - bikeHalfWidth
}, {
duration: 60,
easing: tween.cubicIn
});
}
});
}
}
};
return self;
});
// Obstacle class: static/dynamic hazards
var Obstacle = Container.expand(function () {
var self = Container.call(this);
// Attach obstacle asset (box, red)
var obs = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set vertical speed (scrolls with track)
self.speedY = 18;
// Track lastY for event triggers
self.lastY = self.y;
self.update = function () {
self.lastY = self.y;
self.y += self.speedY;
};
return self;
});
// PowerUp class: collectible items
var PowerUp = Container.expand(function () {
var self = Container.call(this);
// Attach powerup asset (ellipse, yellow)
var pu = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedY = 18;
self.lastY = self.y;
self.update = function () {
self.lastY = self.y;
self.y += self.speedY;
};
return self;
});
/****
* Initialize Game
****/
// Motorcycle asset (blue box)
// Obstacle asset (red box)
// PowerUp asset (yellow ellipse)
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// --- Combo Counter ---
//Minimalistic tween library which should be used for animations over time, including tinting / colouring an object, scaling, rotating, or changing any game object property.
var comboCount = 0;
var comboText = new Text2('Combo! 0', {
size: 60,
fill: "#fff",
font: "Arial, Helvetica, sans-serif"
});
comboText.anchor.set(1, 0); // Match scoreTxt anchor (right edge, top)
// comboText.x and comboText.y will be set after scoreTxt is defined
// Helper to update comboText display and position
function updateComboText() {
// Always show "Combo! 0" when comboCount is zero
comboText.setText("Combo! " + comboCount);
comboText.visible = true;
// Position below scoreTxt, right-aligned
comboText.anchor.set(1, 0);
comboText.x = scoreTxt.x;
comboText.y = scoreTxt.y + scoreTxt.height + 10;
if (comboText.parent !== LK.gui.topRight) {
if (comboText.parent) {
comboText.parent.removeChild(comboText);
}
LK.gui.topRight.addChild(comboText);
}
}
// Ensure comboText is added to the display list so it appears on screen
if (!comboText.parent) {
LK.gui.topRight.addChild(comboText);
}
// --- Katana Combo System ---
var katanaComboIndex = 0;
// --- Katana Combo Sound System ---
var katanaSounds = ["katanaslashsound", "katanaslash", "katanaslash1", "katanaslash2", "katanaslash3"];
// Track the currently playing katana sound instance
var currentKatanaSound = null;
function playKatanaComboSound() {
if (Array.isArray(katanaSounds) && katanaSounds.length > 0) {
// Use a unique sound channel for katanaSlash sounds
var katanaChannel = "katanaComboChannel";
// Stop any currently playing katana sound instance
if (currentKatanaSound && typeof currentKatanaSound.stop === "function") {
currentKatanaSound.stop();
}
var soundId = katanaSounds[katanaComboIndex];
var sound = LK.getSound(soundId);
if (sound && typeof sound.play === "function") {
// Play the sound on the unique channel
sound.play({
channel: katanaChannel
});
currentKatanaSound = sound;
} else {
currentKatanaSound = null;
}
katanaComboIndex++;
if (katanaComboIndex >= katanaSounds.length) {
katanaComboIndex = 0;
}
}
}
// --- Road Background ---
// Motorcycle asset (blue box)
// Obstacle asset (red box)
// PowerUp asset (yellow ellipse)
// --- Game State ---
// Road background: gray
// (punchProjectile logic removed)
// Robust typeof helper for sandboxed environment (no Symbol polyfill issues)
// Robust typeof helper for sandboxed environment (no Symbol polyfill issues)
function _typeof9(o) {
"@babel/helpers - typeof";
return _typeof9 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof9(o);
}
function _typeof8(o) {
"@babel/helpers - typeof";
return _typeof8 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof8(o);
}
function _typeof7(o) {
"@babel/helpers - typeof";
return _typeof7 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof7(o);
}
function _typeof6(o) {
"@babel/helpers - typeof";
return _typeof6 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof6(o);
}
function _typeof5(o) {
"@babel/helpers - typeof";
return _typeof5 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof5(o);
}
function _typeof4(o) {
// Robust typeof helper for sandboxed environment (no Symbol polyfill issues)
if (o === null) {
return "object";
}
var t = _typeof5(o);
// Defensive: avoid TypeError if t is undefined or not a string
if (typeof t === "undefined") {
return "undefined";
}
if (typeof t !== "string") {
return t;
}
// Defensive: avoid accessing t[0] if t is undefined, not a string, or empty
if (!t || typeof t !== "string" || t.length === 0) {
return t;
}
if (t[0] !== "o") {
return t;
}
// Defensive: avoid accessing properties of undefined or null
// No Symbol checks, just return "object" for objects/arrays
return t;
}
function _typeof3(o) {
// Robust typeof helper for sandboxed environment (no Symbol polyfill issues)
if (o === null) {
return "object";
}
var t = _typeof5(o);
if (t !== "object") {
return t;
}
// Array.isArray is always "object" in typeof
return t;
}
function _typeof2(o) {
// Only use native typeof, avoid Symbol checks for compatibility and sandbox errors
// This is safe in all supported environments
if (o === null) {
return "object";
}
var t = _typeof5(o);
if (t !== "object") {
return t;
}
return t;
}
function _typeof(o) {
return _typeof2(o);
}
var roadCenterX = 2048 / 2;
var roadBg1 = LK.getAsset('roadBg', {
anchorX: 0.5,
anchorY: 0,
width: 1200,
height: 2732,
x: roadCenterX,
y: 0
});
var roadBg2 = LK.getAsset('roadBg', {
anchorX: 0.5,
anchorY: 0,
width: 1200,
height: 2732,
x: roadCenterX,
y: -2732
});
// If the asset doesn't exist, create it as a gray box
if (!roadBg1) {
roadBg1 = LK.getAsset('roadBg', {
anchorX: 0.5,
anchorY: 0,
width: 1200,
height: 2732,
x: roadCenterX,
y: 0
});
roadBg2 = LK.getAsset('roadBg', {
anchorX: 0.5,
anchorY: 0,
width: 1200,
height: 2732,
x: roadCenterX,
y: -2732
});
}
game.addChild(roadBg1);
game.addChild(roadBg2);
// --- Road Boundary Lines (Neon) ---
// Define neon color (electric blue)
var boundaryColor = 0x00ffff; // Electric blue
var boundaryWidth = 12;
var boundaryHeight = 2732;
var roadLeftEdge = roadCenterX - 600; // roadBg is 1200 wide, centered
var roadRightEdge = roadCenterX + 600;
// Left boundary line
var leftBoundary = LK.getAsset('laneDash', {
width: boundaryWidth,
height: boundaryHeight,
color: boundaryColor,
anchorX: 0.5,
anchorY: 0
});
leftBoundary.x = roadLeftEdge;
leftBoundary.y = 0;
game.addChild(leftBoundary);
// Right boundary line
var rightBoundary = LK.getAsset('laneDash', {
width: boundaryWidth,
height: boundaryHeight,
color: boundaryColor,
anchorX: 0.5,
anchorY: 0
});
rightBoundary.x = roadRightEdge;
rightBoundary.y = 0;
game.addChild(rightBoundary);
// Lane definitions for 3-lane road (centered, full width 2048)
var laneCount = 3;
var laneWidth = 400;
var laneX = [2048 / 2 - laneWidth,
// left lane center
2048 / 2,
// center lane center
2048 / 2 + laneWidth // right lane center
];
// --- Lane Lines (dashed) ---
var laneLineContainers = [];
var laneLineCount = 2; // 2 lines between 3 lanes
var dashHeight = 80;
var dashGap = 80;
var lineWidth = 16;
var lineColor = 0xffffff;
var roadTop = 0;
var roadBottom = 2732;
var dashesPerLine = Math.ceil((roadBottom - roadTop) / (dashHeight + dashGap)) + 2;
for (var i = 0; i < laneLineCount; i++) {
var lineContainer = new Container();
var x = (laneX[i] + laneX[i + 1]) / 2;
for (var d = 0; d < dashesPerLine; d++) {
// Use the laneDash asset for white dashes
var dash = LK.getAsset('laneDash', {
width: lineWidth,
height: dashHeight,
anchorX: 0.5,
anchorY: 0
});
dash.x = x;
dash.y = roadTop + d * (dashHeight + dashGap);
lineContainer.addChild(dash);
}
laneLineContainers.push(lineContainer);
game.addChild(lineContainer);
}
var player = new Motorcycle();
player.x = 2048 / 2; // Center horizontally
player.y = 2732 - 520; // Move player even higher up (was 2732 - 420)
game.addChild(player);
// Defensive: always initialize arrays to avoid undefined/null errors
if (typeof obstacles === "undefined" || !obstacles || !Array.isArray(obstacles)) {
var obstacles = [];
} else if (!Array.isArray(obstacles)) {
obstacles = [];
}
if (typeof enemyBikes === "undefined" || !enemyBikes || !Array.isArray(enemyBikes)) {
var enemyBikes = [];
} else if (!Array.isArray(enemyBikes)) {
enemyBikes = [];
}
if (typeof powerups === "undefined" || !powerups || !Array.isArray(powerups)) {
var powerups = [];
} else if (!Array.isArray(powerups)) {
powerups = [];
}
// --- Score ---
var score = 0;
var distance = 0;
// --- Health ---
var playerHealth = 3;
var maxPlayerHealth = 3;
// Container for tire icons
var healthIcons = new Container();
healthIcons.x = 110;
healthIcons.y = 0;
LK.gui.top.addChild(healthIcons);
// Helper to update tire icons
function updateHealthIcons() {
// Remove all previous icons
while (healthIcons.children.length > 0) {
var c = healthIcons.children.pop();
if (c && typeof c.destroy === "function") {
c.destroy();
}
}
// Add one tire icon per health
for (var h = 0; h < playerHealth; h++) {
var tire = LK.getAsset('tireIcon', {
anchorX: 0,
anchorY: 0,
width: 90,
height: 90
});
tire.x = h * 100;
tire.y = 10;
healthIcons.addChild(tire);
}
}
updateHealthIcons();
// Score display
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
// Anchor to the right edge, top (1, 0)
scoreTxt.anchor.set(1, 0);
// Add to top-right GUI overlay
LK.gui.topRight.addChild(scoreTxt);
// Now that scoreTxt is defined, set comboText position and add to GUI if not already present
// --- Motocockpit UI (center-bottom, slightly above edge) ---
var motocockpit = LK.getAsset('motocockpit', {
anchorX: 0.5,
anchorY: 1
});
motocockpit.x = LK.gui.center.width / 2;
motocockpit.y = 10; // 30px above the bottom edge to avoid clipping
LK.gui.bottom.addChild(motocockpit);
// --- Version Label (bottom-left) ---
var versionLabel = new Text2('v_0.2', {
size: 48,
fill: "#fff",
font: "Arial, Helvetica, sans-serif"
});
versionLabel.anchor.set(0, 1); // left-bottom corner of text
// Position: bottom-left, with a small margin from the edge
versionLabel.x = 40;
versionLabel.y = -20;
LK.gui.bottomLeft.addChild(versionLabel);
// (Punch button removed)
// Touch drag to steer
var dragActive = false;
var dragOffsetX = 0;
game.down = function (x, y, obj) {
// (Punch logic on right half tap removed)
// Only start drag if touch is near the player
var dx = x - player.x;
var dy = y - player.y;
if (dx * dx + dy * dy < 300 * 300) {
dragActive = true;
dragOffsetX = player.x - x;
}
};
game.move = function (x, y, obj) {
if (dragActive) {
player.x = x + dragOffsetX;
}
};
game.up = function (x, y, obj) {
dragActive = false;
};
// --- Spawning logic ---
var obstacleTimer = 0;
var powerupTimer = 0;
// --- Difficulty scaling ---
var enemyBaseSpeed = 24; // initial EnemyBike speed
var enemySpeed = enemyBaseSpeed;
var enemyBaseSpawnInterval = 40; // initial spawn interval (frames)
var enemySpawnInterval = enemyBaseSpawnInterval;
var difficultyTimer = 0; // counts frames for difficulty increase
var difficultyInterval = 600; // every 600 frames (~10s at 60fps), increase difficulty
var enemySpeedIncrement = 2; // how much to increase speed each interval
var enemySpawnDecrement = 4; // how much to decrease spawn interval each interval (minimum capped)
var minEnemySpawnInterval = 12; // minimum allowed spawn interval
// --- KatanaSlash Melee System ---
var katanaSlashCooldown = 0; // frames until next slash (120 frames = 2s at 60fps)
var katanaSlashRadius = 200; // px radius for melee
// --- Main update loop ---
game.update = function () {
// Scroll road background downward to simulate forward motion
roadBg1.y += player.speedY;
roadBg2.y += player.speedY;
// Loop backgrounds
if (roadBg1.y >= 2732) {
roadBg1.y = roadBg2.y - 2732;
}
if (roadBg2.y >= 2732) {
roadBg2.y = roadBg1.y - 2732;
}
// Scroll and loop lane lines downward
for (var i = 0; i < laneLineContainers.length; i++) {
var lineContainer = laneLineContainers[i];
for (var j = 0; j < lineContainer.children.length; j++) {
var dash = lineContainer.children[j];
dash.y += player.speedY;
if (dash.y > 2732) {
dash.y -= (dashHeight + dashGap) * lineContainer.children.length;
// This ensures the dash loops back to the top
}
}
}
// Move player (handled in class)
player.update();
// Scroll obstacles and powerups
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.update();
// Remove if off screen (bottom or top or far left/right)
if (obs.y >= 2732 + 100 || obs.y < -200 || obs.x < -200 || obs.x > 2048 + 200) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision detection (trigger on first intersect)
if (!obs.lastWasIntersecting && obs.intersects(player)) {
// Log game over trigger
console.log("Game Over Triggered");
// Check if carcrashsound is loaded
var carCrashSound = LK.getSound('carcrashsound');
if (carCrashSound && typeof carCrashSound.play === "function") {
console.log("carcrashsound loaded successfully");
} else {
console.log("carcrashsound not loaded or invalid");
}
LK.effects.flashScreen(0xff0000, 800);
// Immediately stop all sounds and music before delay
LK.stopMusic();
// Stop any currently playing katana sound
if (currentKatanaSound && typeof currentKatanaSound.stop === "function") {
currentKatanaSound.stop();
}
// Stop all other sounds that might be playing
var testSound = LK.getSound('testSound');
if (testSound && typeof testSound.stop === "function") {
testSound.stop();
}
var motorCrashSound = LK.getSound('motorcrashsound');
if (motorCrashSound && typeof motorCrashSound.stop === "function") {
motorCrashSound.stop();
}
var katanaSlashSound = LK.getSound('katanaslashsound');
if (katanaSlashSound && typeof katanaSlashSound.stop === "function") {
katanaSlashSound.stop();
}
// Play carcrashsound immediately before showing game over
if (LK.getSound && typeof LK.getSound === "function") {
var gameOverCrashSound = LK.getSound('carcrashsound');
if (gameOverCrashSound && typeof gameOverCrashSound.play === "function") {
gameOverCrashSound.volume = 1.0;
gameOverCrashSound.play();
console.log("carcrashsound play triggered");
}
}
// Wait 0.65 seconds after sound starts, then show game over
LK.setTimeout(function () {
LK.showGameOver();
}, 650);
return;
}
obs.lastWasIntersecting = obs.intersects(player);
}
// Handle enemyBikes
for (var i = enemyBikes.length - 1; i >= 0; i--) {
var eb = enemyBikes[i];
eb.update();
// Remove if off screen (bottom or top or far left/right)
if (eb.y >= 2732 + 100 || eb.y < -200 || eb.x < -200 || eb.x > 2048 + 200) {
eb.destroy();
enemyBikes.splice(i, 1);
// Reset comboCount if enemy escapes
if (comboCount !== 0) {
comboCount = 0;
updateComboText();
}
continue;
}
// Collision detection (trigger on first intersect)
if (!eb.lastWasIntersecting && eb.intersects(player)) {
// Do nothing: enemy motorcycle collision has no effect, no sound, no animation, no feedback
}
eb.lastWasIntersecting = eb.intersects(player);
// (auto-punch logic removed)
}
// Powerups
for (var i = powerups.length - 1; i >= 0; i--) {
var pu = powerups[i];
pu.update();
// Remove if off screen (bottom or top or far left/right)
if (pu.y >= 2732 + 100 || pu.y < -200 || pu.x < -200 || pu.x > 2048 + 200) {
pu.destroy();
powerups.splice(i, 1);
continue;
}
// Collect powerup
if (!pu.lastWasIntersecting && pu.intersects(player)) {
// Play chassound immediately upon collection
var powerUpSound = LK.getSound('chassound');
if (powerUpSound && typeof powerUpSound.play === "function") {
powerUpSound.play();
}
score += 10;
scoreTxt.setText(score);
pu.destroy();
powerups.splice(i, 1);
continue;
}
pu.lastWasIntersecting = pu.intersects(player);
}
// --- Difficulty scaling ---
difficultyTimer++;
if (difficultyTimer >= difficultyInterval) {
difficultyTimer = 0;
// Increase enemy speed
enemySpeed += enemySpeedIncrement;
// Decrease spawn interval, but not below minimum
enemySpawnInterval -= enemySpawnDecrement;
if (enemySpawnInterval < minEnemySpawnInterval) {
enemySpawnInterval = minEnemySpawnInterval;
}
}
// --- Spawning ---
obstacleTimer++;
if (obstacleTimer > enemySpawnInterval) {
obstacleTimer = 0;
// Randomly decide to spawn an Obstacle or an EnemyBike (e.g. 70% car, 30% enemyBike)
if (Math.random() < 0.3) {
var laneIdx = Math.floor(Math.random() * laneCount);
var eb = new EnemyBike();
eb.laneIdx = laneIdx; // Store lane index for reference
eb.x = laneX[laneIdx];
eb.y = -100;
eb.lastWasIntersecting = false;
eb.speedY = enemySpeed; // Set current enemy speed
game.addChild(eb);
enemyBikes.push(eb);
} else {
var obs = new Obstacle();
// Place obstacle in a random lane
var laneIdx = Math.floor(Math.random() * laneCount);
obs.x = laneX[laneIdx];
obs.y = -100;
obs.lastWasIntersecting = false;
game.addChild(obs);
obstacles.push(obs);
}
}
powerupTimer++;
if (powerupTimer > 120) {
powerupTimer = 0;
var pu = new PowerUp();
// Place powerup in a random lane
var laneIdx = Math.floor(Math.random() * laneCount);
pu.x = laneX[laneIdx];
pu.y = -100;
pu.lastWasIntersecting = false;
game.addChild(pu);
powerups.push(pu);
}
// --- KatanaSlash Melee System ---
// Instantly destroy any enemyBike within katanaSlashRadius of the player and show a glowing red beam effect
var katanaHit = false;
var katanaKills = 0;
for (var i = enemyBikes.length - 1; i >= 0; i--) {
var eb = enemyBikes[i];
var dx = eb.x - player.x;
var dy = eb.y - player.y;
if (dx * dx + dy * dy <= katanaSlashRadius * katanaSlashRadius) {
katanaHit = true;
// Draw a katana sprite image between player and enemyBike
var length = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
// Play katana slash sound
var katanaSlashSound = LK.getSound('katanaslashsound');
if (katanaSlashSound && typeof katanaSlashSound.play === "function") {
katanaSlashSound.play();
}
// Use a katana image asset (ensure it's initialized in Assets section)
var katana = LK.getAsset('katana', {
anchorX: 0,
// start of blade at player
anchorY: 0.5
});
katana.x = player.x + 30;
katana.y = player.y - 20;
katana.rotation = angle;
// Scale katana to match the distance between player and enemyBike
// Use katana's original width for scaling
if (katana.width > 0) {
katana.scaleX = length / katana.width;
} else {
katana.scaleX = 1;
}
katana.scaleY = 1.0; // keep blade thickness
katana.alpha = 0.92;
game.addChild(katana);
// Remove katana after 200ms
(function (k) {
var timeoutId = LK.setTimeout(function () {
if (k && typeof k.destroy === "function") {
k.destroy();
}
}, 200);
})(katana);
eb.destroy();
enemyBikes.splice(i, 1);
// Award score as 5 * comboCount (minimum 5 if comboCount is 0)
var awardedCombo = comboCount > 0 ? comboCount : 1;
score += 5 * awardedCombo;
scoreTxt.setText(score);
katanaKills++;
}
}
// Only increment comboCount if at least one enemyBike was killed by katana
if (katanaKills > 0) {
comboCount += katanaKills;
updateComboText();
playKatanaComboSound();
}
// --- Distance/score ---
distance += player.speedY;
if (distance % 1000 < player.speedY) {
score += 1;
scoreTxt.setText(score);
}
// Endless mode: no win condition, game continues until playerHealth reaches zero
};
// Play background music continuously throughout the game
LK.playMusic('gamesound');
Create a 2D motorcycle sprite viewed from behind, positioned to ride on the road.. In-Game asset. 2d. High contrast. No shadows
Create a top-down 2D car sprite facing downward.. In-Game asset. 2d. High contrast. No shadows
Draw a 2D side-view katana with a sleek silver blade and a black-and-red hilt, in a flat cartoon style suitable for an action game. The sword should be horizontal with a transparent background. In-Game asset. 2d. High contrast. No shadows
Create a shiny golden coin (token) asset for a game. The coin should have a polished, reflective surface with subtle engravings or ridges around the edge. It should look 3D with soft highlights and shadows to give depth. The size should be suitable as a collectible power-up floating slightly above the ground. Style should be clean and vibrant, fitting a modern arcade or action game.. In-Game asset. 2d. High contrast. No shadows
Create a simple 2D animation of an enemy motorcycle falling sideways to the ground. The animation should have 5 frames showing the bike tilting and then lying flat. Use a cartoonish style matching a simple 2D game.. In-Game asset. 2d. High contrast. No shadows