Code edit (15 edits merged)
Please save this source code
User prompt
remove the random color logic in gateManager; keep only the key<=>color logic
User prompt
now make gates color depend on their key
User prompt
when updating Gate, update the relative y position of boundingBox relatively to the scale ; it should be at y=2450 when scale is 1.0
Code edit (1 edits merged)
Please save this source code
User prompt
add a boundingBox to gates with size 300x30
User prompt
in GateManager, when spawning a gate, set it's corresponding key from the song; then play this key when intersecting the ball
User prompt
set debug mode on
User prompt
in debug mode, add a button at top right when pressed play all keys sounds from 0 to 14 with 600 ms delay
User prompt
when ball touches a gate, play corresponding key sound
Code edit (1 edits merged)
Please save this source code
Code edit (3 edits merged)
Please save this source code
User prompt
I've imported a song in json format; adapt the GateManager to span gates at the same rythm as songList[0] songNotes
Code edit (3 edits merged)
Please save this source code
User prompt
set debug mode false
User prompt
Ok. One point : Cubes are disabled/hidden yet because it's too complex to make the match the background virtual "movement". We migh instead use colored sections of tores as "gates"; the player will have to roll though the gate with the corresponding color. Implement the Gate class that will spawn 3 gates and animate them exactly like the tores
Code edit (5 edits merged)
Please save this source code
User prompt
add a global borderLimitAngle = Math.PI * 0.2; limit the move angle to Math.PI-borderLimitAngle
User prompt
allow movement from anywhere, do not require a tap on the sphere
User prompt
move the ball exactly like the sphere
User prompt
tint ball with currentColor
User prompt
add a ball class; then instanciate it at 1024,2000
User prompt
Set debug false
User prompt
Add facekit ↪💡 Consider importing and using the following plugins: @upit/facekit.v1
User prompt
Set debug true
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ /***********************************************************************************/ /******************************* UTILITY FUNCTIONS *********************************/ /***********************************************************************************/ var BackgroundManager = Container.expand(function () { var self = Container.call(this); // Create three background instances for smoother tunnel effect self.bg0 = self.attachAsset('background01', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 2.4, scaleY: 2.4, alpha: 1 }); self.bg1 = self.attachAsset('background01', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 1.1, scaleY: 1.1, alpha: 1 }); self.bg2 = self.attachAsset('background01', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 0.5, scaleY: 0.5, alpha: 1 }); self.bg3 = self.attachAsset('background01', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 0.22, scaleY: 0.22, alpha: 1 }); self.bg4 = self.attachAsset('background01', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 0.11, scaleY: 0.11, alpha: 1 }); self.tore0 = self.attachAsset('tore', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 1.2, scaleY: 1.2, alpha: 0 }); self.tore1 = self.attachAsset('tore', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 0.6, scaleY: 0.6, alpha: 0 }); self.tore2 = self.attachAsset('tore', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 0.26, scaleY: 0.26, alpha: 0 }); self.tore3 = self.attachAsset('tore', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 0.12, scaleY: 0.12, alpha: 0 }); // Apply different tints in debug mode if (isDebug) { self.bg1.tint = 0xFF0000; // Red tint for first background self.tore1.tint = 0x00FF00; // Green tint for first tore self.bg2.tint = 0x00FFFF; // Cyan tint for second background self.tore2.tint = 0xFF00FF; // Magenta tint for second tore self.bg3.tint = 0xfff200; // Yellow tint for third background } // Animation properties self.bgAnimationSpeed = globalSpeed / 1000; //0.002; self.bgAnimationAcceleration = 2; // Add tore assets between backgrounds for animation self.backgrounds = [self.bg0, self.tore0, self.bg1, self.tore1, self.bg2, self.tore2, self.bg3, self.tore3, self.bg4]; //self.backgrounds = [self.bg0, self.bg1, self.bg2, self.bg3]; // Define initial scale for each background/torus (tore: 0.26, bg: 0.22) //self.bgInitialScales = [0.22, 0.22, 0.22, 0.22, 0.22]; //self.bgInitialScales = [0, 0, 0, 0, 0]; //self.bgInitialScales = [0, 0, 0, 0, 0, 0, 0, 0, 0]; // Animation state: single startTime for all backgrounds/torus self.bgAnimStartTime = Date.now(); // Update method - handle background/torus scale animation self.update = function () { var now = Date.now(); var elapsed = now - self.bgAnimStartTime; var resetTriggered = false; for (var i = 0; i < self.backgrounds.length; i++) { var bg = self.backgrounds[i]; // Make the scale speed increase as the scale increases (e.g. exponential or quadratic growth) //var scaleMultiplier = bg.scaleX * self.bgAnimationAcceleration; //bg.scaleX += self.bgAnimationSpeed * scaleMultiplier; bg.scaleX += self.bgAnimationSpeed * bg.scaleX; bg.scaleY = bg.scaleX; if (bg.scaleX > 3.0) { bg.scaleX = 0.12; //self.bgInitialScales[i]; bg.scaleY = bg.scaleX; } //bg.alpha = Math.min(1, bg.scaleX + 0.66); } }; return self; }); // Initialize the game; /***********************************************************************************/ /********************************** BALL CLASS *************************************/ /***********************************************************************************/ var Ball = Container.expand(function () { var self = Container.call(this); // Create and attach ball asset var ballGraphics = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, tint: currentColor }); // Initialize ball properties self.speedX = 0; self.speedY = 0; // Track last intersecting state for each gate self.lastIntersectingGates = {}; // Update method to follow sphere's position self.update = function () { // Make ball follow sphere's exact position if (sphere) { self.x = sphere.x; self.y = sphere.y; } // Check for collisions with gates if (gateManager && gateManager.gates) { for (var i = 0; i < gateManager.gates.length; i++) { var gate = gateManager.gates[i]; var gateId = gate.spawnTime + '_' + gate.colorIndex; // Initialize tracking if needed if (self.lastIntersectingGates[gateId] === undefined) { self.lastIntersectingGates[gateId] = false; } // Check intersection with bounding box instead of gate asset var currentIntersecting = self.intersects(gate.boundingBox); // Detect transition from not intersecting to intersecting if (!self.lastIntersectingGates[gateId] && currentIntersecting) { // Play the sound based on the gate's assigned key if (gate.noteKey) { // Extract the number from the key (e.g., "Key6" -> "6") var keyNumber = gate.noteKey.replace('Key', ''); var soundKey = 'key' + keyNumber; // Play the corresponding sound LK.getSound(soundKey).play(); } } // Update last intersecting state self.lastIntersectingGates[gateId] = currentIntersecting; } // Clean up old gate tracking var currentGateIds = {}; for (var j = 0; j < gateManager.gates.length; j++) { var gate2 = gateManager.gates[j]; var gateId2 = gate2.spawnTime + '_' + gate2.colorIndex; currentGateIds[gateId2] = true; } for (var oldGateId in self.lastIntersectingGates) { if (!currentGateIds[oldGateId]) { delete self.lastIntersectingGates[oldGateId]; } } } }; return self; }); /***********************************************************************************/ /********************************** CUBE CLASS ************************************/ /***********************************************************************************/ var Cube = Container.expand(function (wRatio, hRatio, dRatio) { var self = Container.call(this); wRatio = wRatio || 1; hRatio = hRatio || 1; dRatio = dRatio || 1; self.z = 0; self.baseSize = 100; // Initialize cube faces using SimpleFace class self.frontFace = new SimpleFace({ w: self.baseSize * wRatio, h: self.baseSize * hRatio, d: self.baseSize, dx: 0, dy: 0, dz: 1 * dRatio, rx: 0, ry: 0, rz: 0, ti: 0xFFFFFF }); self.backFace = new SimpleFace({ w: self.baseSize * wRatio, h: self.baseSize * hRatio, d: self.baseSize, dx: 0, dy: 0, dz: -1 * dRatio, rx: Math.PI, ry: 0, rz: 0, ti: 0xFFFFFF }); self.leftFace = new SimpleFace({ w: self.baseSize * dRatio, h: self.baseSize * hRatio, d: self.baseSize, dx: -1 * wRatio / dRatio, dy: 0, dz: 0, rx: 0, ry: Math.PI / 2, rz: 0, ti: 0xFFFFFF }); self.rightFace = new SimpleFace({ w: self.baseSize * dRatio, h: self.baseSize * hRatio, d: self.baseSize, dx: 1 * wRatio / dRatio, dy: 0, dz: 0, rx: 0, ry: -Math.PI * 0.5, rz: 0, ti: 0xFFFFFF }); self.topFace = new SimpleFace({ w: self.baseSize * wRatio, h: self.baseSize * dRatio, d: self.baseSize, dx: 0, dy: -1 * hRatio / dRatio, dz: 0, rx: -Math.PI / 2, ry: 0, rz: 0, ti: 0xFFFFFF }); self.bottomFace = new SimpleFace({ w: self.baseSize * wRatio, h: self.baseSize * dRatio, d: self.baseSize, dx: 0, dy: 1 * hRatio / dRatio, dz: 0, rx: Math.PI / 2, ry: 0, rz: 0, ti: 0xFFFFFF }); self.faces = [self.frontFace, self.backFace, self.leftFace, self.rightFace, self.topFace, self.bottomFace]; self.faces.forEach(function (face) { self.addChild(face); }); self.speedX = 0; self.speedY = 0; self.speedZ = 0; // Rotate cube around its axes self.rotate3D = function (angleX, angleY, angleZ) { log("cube rotate3D "); self.rotation = angleZ; var zScaleFactor = 1 + self.z / 500; self.faces.forEach(function (face) { face.rotate3D(angleX, angleY, angleZ, zScaleFactor); }); }; }); /***********************************************************************************/ /********************************** CUBE MANAGER CLASS *****************************/ /***********************************************************************************/ var CubeManager = Container.expand(function () { var self = Container.call(this); // Array to hold all managed cubes self.cubes = []; // Configuration for spawning self.maxCubes = 3; // Maximum number of cubes // Spawn all cubes at once self.spawnAllCubes = function () { // Calculate spacing for equal distribution var screenWidth = 2048; var cubeSpacing = screenWidth / (self.maxCubes + 1); // Divide screen into equal sections var verticalCenter = 2732 / 2; // Vertical center of the screen // Prepare color assignment: one cube gets currentColor, others get two different neon colors (not currentColor) var colorIndices = []; for (var c = 0; c < neonColors.length; c++) { if (neonColors[c] !== currentColor) { colorIndices.push(c); } } // Shuffle colorIndices to randomize which two colors are picked for the other cubes for (var s = colorIndices.length - 1; s > 0; s--) { var j = Math.floor(Math.random() * (s + 1)); var temp = colorIndices[s]; colorIndices[s] = colorIndices[j]; colorIndices[j] = temp; } var cubeColors = [currentColor, neonColors[colorIndices[0]], neonColors[colorIndices[1]]]; // Shuffle cubeColors so currentColor is not always in the same position for (var s2 = cubeColors.length - 1; s2 > 0; s2--) { var j2 = Math.floor(Math.random() * (s2 + 1)); var temp2 = cubeColors[s2]; cubeColors[s2] = cubeColors[j2]; cubeColors[j2] = temp2; } for (var i = 0; i < self.maxCubes; i++) { // Create a new cube with equal dimensions (true cube, not rectangle) var cube = new Cube(1, 1, 1); // Set position - equally distributed horizontally at vertical center cube.x = cubeSpacing * (i + 1); // Distribute equally across the screen cube.y = verticalCenter; cube.z = 0; // Keep all cubes at same Z position // Set speeds to zero so cubes don't move cube.speedX = 0; cube.speedY = 0; cube.speedZ = 0; // Set random initial rotation cube.rotate3D(Math.random() * Math.PI * 2, Math.random() * Math.PI * 2, Math.random() * Math.PI * 2); // Assign color: one cube gets currentColor, others get two different neon colors var assignedTint = cubeColors[i % cubeColors.length]; cube.faces.forEach(function (face) { face.tint = assignedTint; }); // Add cube to the manager and the game self.cubes.push(cube); self.addChild(cube); } }; // Remove a cube from management self.removeCube = function (cube) { var index = self.cubes.indexOf(cube); if (index > -1) { self.cubes.splice(index, 1); cube.destroy(); } }; // Update all managed cubes self.updateCubes = function () { for (var i = self.cubes.length - 1; i >= 0; i--) { var cube = self.cubes[i]; // Cubes don't move, so no position updates needed // Rotate cube cube.z += 1; //cube.speedZ; cube.rotate3D(Math.PI * 0.01, Math.PI * 0.01, Math.PI * 0.01); } }; // Initialize by spawning all cubes at once self.spawnAllCubes(); // Update method called by the game loop self.update = function () { //self.updateCubes(); }; return self; }); /***********************************************************************************/ /******************************* FACE CLASS *********************************/ /***********************************************************************************/ var Face = Container.expand(function (options) { var self = Container.call(this); options = options || {}; var points = Math.max(2, Math.min(100, options.points || 4)); // Ensure points are between 2 and 10 self.baseSize = 100; self.w = options.w || self.baseSize; self.h = options.h || self.baseSize; self.d = options.d || self.baseSize; self.dx = options.dx || 0; self.dy = options.dy || 0; self.dz = options.dz || 0; self.rx = options.rx || 0; self.ry = options.ry || 0; self.rz = options.rz || 0; self.tint = options.ti || 0xFFFFFF; // Generate points for the face based on the number of points specified self.baseFaceCoordinates = []; for (var i = 0; i < points; i++) { var angle = 2 * Math.PI * (i / points); self.baseFaceCoordinates.push({ x: self.w / 2 * Math.cos(angle) + self.dx * self.w, y: self.h / 2 * Math.sin(angle) + self.dy * self.h, z: self.dz * self.d }); } self.baseFaceCoordinates.forEach(function (point) { // Update z of each face point coordinates depending on dz and rx, ry point.z += self.dz * Math.cos(self.rx) * Math.cos(self.ry); }); // Create a polygon face using the Shape class self.face = new Shape(self.baseFaceCoordinates, self.tint); // Attach the face to the Face container self.addChild(self.face); // Rotate in 3D: X = roasting chicken / Y = whirling dervish / Z = wheel of Fortune self.rotate3D = function (angleX, angleY, angleZ, scale) { scale = scale || 1; self.faceCoordinates = self.baseFaceCoordinates.map(function (coord) { var x = coord.x - self.dx * self.w, y = coord.y - self.dy * self.h, z = coord.z - self.dz * self.d; // Apply initial rotations (rx, ry, rz) var newY = y * Math.cos(self.rx) - z * Math.sin(self.rx); var newZ = y * Math.sin(self.rx) + z * Math.cos(self.rx); var newX = x * Math.cos(self.ry) + newZ * Math.sin(self.ry); newZ = -x * Math.sin(self.ry) + newZ * Math.cos(self.ry); x = newX * Math.cos(self.rz) - newY * Math.sin(self.rz); y = newX * Math.sin(self.rz) + newY * Math.cos(self.rz); // Apply X-axis rotation newY = y * Math.cos(angleX) - newZ * Math.sin(angleX); newZ = y * Math.sin(angleX) + newZ * Math.cos(angleX); // Apply Y-axis rotation newX = x * Math.cos(angleY) + newZ * Math.sin(angleY); newZ = -x * Math.sin(angleY) + newZ * Math.cos(angleY); // Apply Z-axis rotation x = newX * Math.cos(angleZ) - newY * Math.sin(angleZ); y = newX * Math.sin(angleZ) + newY * Math.cos(angleZ); return { x: (x + self.dx * self.w) * scale, y: (y + self.dy * self.h) * scale, z: (newZ + self.dz * self.d) * scale }; }); self.face.updateCoordinates(self.faceCoordinates); }; // Initialize face in 3D space self.rotate3D(0, 0, 0, 1); }); /***********************************************************************************/ /********************************** GATE CLASS *************************************/ /***********************************************************************************/ var Gate = Container.expand(function () { var self = Container.call(this); // Create gate asset with initial properties self.gateAsset = self.attachAsset('gate', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 0.26, // Start at same scale as tore2 scaleY: 0.26, alpha: 1 }); // Add bounding box for collision detection self.boundingBox = self.attachAsset('boundingBox', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 2450, width: 1224, heigh: 120, alpha: 0 // Invisible by default }); // Store the color for this gate self.gateColor = 0xFFFFFF; // Default white, will be set by manager // Set the tint to match the gate color self.setColor = function (color) { self.gateColor = color; self.gateAsset.tint = color; // Show bounding box in debug mode if (isDebug) { self.boundingBox.alpha = 0.3; self.boundingBox.tint = color; } }; // Update scale to match background animation self.updateScale = function (newScale) { /* self.scaleX = newScale; self.scaleY = newScale; self.alpha = Math.min(1, newScale + 0.66); */ self.gateAsset.scaleX = newScale; self.gateAsset.scaleY = newScale; self.gateAsset.alpha = Math.min(1, newScale + 0.66); // Scale bounding box proportionally self.boundingBox.scaleX = newScale; // 3 * newScale / 0.26; // Maintain 300px width relative to gate scale self.boundingBox.scaleY = newScale; //0.3 * newScale / 0.26; // Maintain 30px height relative to gate scale // Update boundingBox y position relative to scale (y=2450 when scale is 1.0) self.boundingBox.y = 1366 + (2450 - 1366) * newScale; }; return self; }); /***********************************************************************************/ /********************************** GATE MANAGER CLASS *****************************/ /***********************************************************************************/ var GateManager = Container.expand(function () { var self = Container.call(this); // Array to hold gates self.gates = []; // Animation timing self.gateAnimStartTime = Date.now(); self.gateAnimationSpeed = globalSpeed / 1000; // Same as background // Song timing properties self.currentSong = songsList[0]; self.songStartTime = Date.now(); self.currentNoteIndex = 0; self.noteSpawnScale = 0.12; // Initial scale for new gates matching smallest tore // Spawn a single gate at current time self.spawnGateAtTime = function () { // Get the current note's key var noteKey = null; if (self.currentNoteIndex < self.currentSong.songNotes.length) { noteKey = self.currentSong.songNotes[self.currentNoteIndex].key; } // Get the color for this key var keyColor = keyColorMap[noteKey] || currentColor; // Default to currentColor if key not found // Create a single gate with the key's color var gate = new Gate(); gate.setColor(keyColor); gate.updateScale(self.noteSpawnScale); // Store spawn time for tracking gate.spawnTime = Date.now(); gate.colorIndex = 0; // Store the note key for this gate gate.noteKey = noteKey; self.gates.push(gate); self.addChild(gate); }; // Update gates animation self.update = function () { var now = Date.now(); var songElapsed = now - self.songStartTime; // Check if we need to spawn a new gate based on song timing if (self.currentNoteIndex < self.currentSong.songNotes.length) { var nextNote = self.currentSong.songNotes[self.currentNoteIndex]; if (songElapsed >= nextNote.time) { // Spawn a new gate for this note self.spawnGateAtTime(); self.currentNoteIndex++; } } // Animate existing gates for (var i = self.gates.length - 1; i >= 0; i--) { var gate = self.gates[i]; var currentScale = gate.gateAsset.scaleX; // Increase scale with acceleration var newScale = currentScale + self.gateAnimationSpeed * currentScale; // Remove gate when too large if (newScale > 3.0) { gate.destroy(); self.gates.splice(i, 1); } else { gate.updateScale(newScale); } } // Check if song has ended and needs restart self.checkSongEnd(); }; // Reset song when it ends self.resetSong = function () { self.songStartTime = Date.now(); self.currentNoteIndex = 0; }; // Check if song has ended and restart self.checkSongEnd = function () { if (self.currentNoteIndex >= self.currentSong.songNotes.length) { // All notes have been spawned, check if we should restart var lastNoteTime = self.currentSong.songNotes[self.currentSong.songNotes.length - 1].time; var songElapsed = Date.now() - self.songStartTime; // Wait a bit after the last note before restarting if (songElapsed > lastNoteTime + 5000) { self.resetSong(); } } }; return self; }); /***********************************************************************************/ /********************************** SHAPE CLASS ************************************/ /***********************************************************************************/ var Shape = Container.expand(function (coordinates, tint) { var self = Container.call(this); self.polygon = drawPolygon(coordinates, tint); // Function to create a polygon from a list of coordinates self.tint = tint; self.attachLines = function () { // Iterate through each line in the polygon and attach it to the shape self.polygon.forEach(function (line) { self.addChild(line); }); }; self.attachLines(); self.updateCoordinates = function (newCoordinates) { log("Shape updateCoordinates ", newCoordinates); // Ensure newCoordinates is an array and has the same length as the current polygon if (!Array.isArray(newCoordinates) || newCoordinates.length !== self.polygon.length) { error("Invalid newCoordinates length"); return; } // Update each line in the polygon with new coordinates self.polygon = updatePolygon(self.polygon, newCoordinates); }; }); /***********************************************************************************/ /******************************* SIMPLE FACE CLASS *********************************/ /***********************************************************************************/ var SimpleFace = Container.expand(function (options) { var self = Container.call(this); log("SimpleFAce init options =", options); self.baseSize = 100; options = options || {}; self.w = options.w || self.baseSize; self.h = options.h || self.baseSize; self.d = options.d || self.baseSize; self.dx = options.dx || 0; self.dy = options.dy || 0; self.dz = options.dz || 0; self.rx = options.rx || 0; self.ry = options.ry || 0; self.rz = options.rz || 0; self.tint = options.ti || 0xFFFFFF; // Define faceCoordinates property self.baseFaceCoordinates = [{ x: -self.w + self.dx * self.w, y: -self.h + self.dy * self.h, z: self.dz * self.d }, // Top-left { x: self.w + self.dx * self.w, y: -self.h + self.dy * self.h, z: self.dz * self.d }, // Top-right { x: self.w + self.dx * self.w, y: self.h + self.dy * self.h, z: self.dz * self.d }, // Bottom-right { x: -self.w + self.dx * self.w, y: self.h + self.dy * self.h, z: self.dz * self.d } // Bottom-left ]; log("SimpleFAce ready to init ...", self.baseFaceCoordinates, "DX=" + self.dx); self.baseFaceCoordinates.forEach(function (point) { // Update z of each face point coordinates depending on dz and rx, ry point.z += self.dz * Math.cos(self.rx) * Math.cos(self.ry); }); // Create a square face using the Shape class self.face = new Shape(self.baseFaceCoordinates, self.tint); // Attach the face to the SimpleFace container self.addChild(self.face); // Rotate in 3d : X = roasting chicken / Y = whirling dervish / Z = wheel of Fortune self.rotate3D = function (angleX, angleY, angleZ, scale) { scale = scale || 1; log("SimpleFace rotate3D old coord=", self.faceCoordinates, Date.now()); self.faceCoordinates = self.baseFaceCoordinates.map(function (coord) { return { x: coord.x, y: coord.y, z: coord.z }; }); // Apply rotation around X-axis // Adjust initial rotation parameters before applying new rotations self.faceCoordinates = self.faceCoordinates.map(function (coord) { // Apply initial rotation around Z-axis var xZ = coord.x * Math.cos(self.rz) - coord.y * Math.sin(self.rz); var yZ = coord.x * Math.sin(self.rz) + coord.y * Math.cos(self.rz); // Apply initial rotation around Y-axis var xY = xZ * Math.cos(self.ry) + coord.z * Math.sin(self.ry); var zY = coord.z * Math.cos(self.ry) - xZ * Math.sin(self.ry); // Apply initial rotation around X-axis var yX = yZ * Math.cos(self.rx) - zY * Math.sin(self.rx); var zX = yZ * Math.sin(self.rx) + zY * Math.cos(self.rx); return { x: xY, y: yX, z: zX }; }); // Apply new rotations // Calculate center of the face var centerX = self.faceCoordinates.reduce(function (acc, coord) { return acc + coord.x; }, 0) / self.faceCoordinates.length; var centerY = self.faceCoordinates.reduce(function (acc, coord) { return acc + coord.y; }, 0) / self.faceCoordinates.length; var centerZ = self.faceCoordinates.reduce(function (acc, coord) { return acc + coord.z; }, 0) / self.faceCoordinates.length; self.faceCoordinates = self.faceCoordinates.map(function (coord) { // Translate coordinates to rotate around the center including dy and dz adjustment var translatedY = (coord.y + self.dy * self.h - centerY) * Math.cos(angleX) - (coord.z + self.dz * self.d - centerZ) * Math.sin(angleX); var translatedZ = (coord.y + self.dy * self.h - centerY) * Math.sin(angleX) + (coord.z + self.dz * self.d - centerZ) * Math.cos(angleX); return { x: coord.x + self.dx * self.w - centerX, // Keep X unchanged but translate to rotate around center y: translatedY + centerY, z: translatedZ + centerZ }; }); self.faceCoordinates = self.faceCoordinates.map(function (coord) { var translatedX = (coord.z - centerZ) * Math.sin(angleY) + (coord.x - centerX) * Math.cos(angleY); var translatedZ = (coord.z - centerZ) * Math.cos(angleY) - (coord.x - centerX) * Math.sin(angleY); return { x: translatedX + centerX, y: coord.y, // Keep Y unchanged z: translatedZ + centerZ }; }); self.faceCoordinates = self.faceCoordinates.map(function (coord) { return { x: coord.x * scale, y: coord.y * scale, z: coord.z * scale }; }); log("SimpleFace rotate3D new coord=", self.faceCoordinates, Date.now()); self.face.updateCoordinates(self.faceCoordinates); }; // initialize face in 3D space self.rotate3D(0, 0, 0, 1); log("SimpleFace end init coord=", self.baseFaceCoordinates, Date.now()); }); /***********************************************************************************/ /********************************** SPHERE CLASS ***********************************/ /***********************************************************************************/ var Sphere = Container.expand(function () { var self = Container.call(this); self.z = 0; self.radius = 100; // Sphere radius // Initialize sphere as a collection of Face instances to simulate a 3D sphere self.faces = []; var segments = 5; // Number of segments to simulate the sphere for (var i = 0; i < segments; i++) { var angle = 2 * Math.PI / segments; // Create a circular segment as a face of the sphere var face = new Face({ points: 22, w: self.radius * 2, h: self.radius * 2, d: self.radius * 2, dx: 0, dy: 0, dz: 0, rx: 0, ry: i * angle, rz: 0, ti: currentColor // Use currentColor for sphere tint }); self.faces.push(face); self.addChild(face); } self.speedX = 0; self.speedY = 0; self.speedZ = 0; // Rotate sphere around its axes self.rotate3D = function (angleX, angleY, angleZ) { log("sphere rotate3D ", angleX, angleY, angleZ); self.rotation = angleZ; var zScaleFactor = 1 + self.z / 500; for (var i = 0; i < self.faces.length; i++) { self.faces[i].rotate3D(angleX, angleY, angleZ, zScaleFactor); } }; self.rotate3D(Math.PI * 0.5, Math.PI * 0.5, 0); }); /**** * Initialize Game ****/ // Utility function to draw a polygon using drawLine var game = new LK.Game({ backgroundColor: 0x000050 // Initialize game with a black background }); /**** * Game Code ****/ // Global array of 6 neon colors var neonColors = [0x39FF14, // Neon Green 0xFF073A, // Neon Red 0x00FFFF, // Neon Cyan 0xF3F315, // Neon Yellow 0xFF61F6, // Neon Pink 0xFF9900 // Neon Orange ]; // Map keys to colors - 15 keys (0-14) mapped to neon colors var keyColorMap = { 'Key0': 0x39FF14, // Neon Green 'Key1': 0xFF073A, // Neon Red 'Key2': 0x00FFFF, // Neon Cyan 'Key3': 0xF3F315, // Neon Yellow 'Key4': 0xFF61F6, // Neon Pink 'Key5': 0xFF9900, // Neon Orange 'Key6': 0x39FF14, // Neon Green (repeat) 'Key7': 0xFF073A, // Neon Red (repeat) 'Key8': 0x00FFFF, // Neon Cyan (repeat) 'Key9': 0xF3F315, // Neon Yellow (repeat) 'Key10': 0xFF61F6, // Neon Pink (repeat) 'Key11': 0xFF9900, // Neon Orange (repeat) 'Key12': 0x39FF14, // Neon Green (repeat) 'Key13': 0xFF073A, // Neon Red (repeat) 'Key14': 0x00FFFF // Neon Cyan (repeat) }; // Global currentColor, set to a random neon color var currentColor = neonColors[Math.floor(Math.random() * neonColors.length)]; /***********************************************************************************/ /******************************* UTILITY FUNCTIONS *********************************/ /***********************************************************************************/ function drawPolygon(coordinates, tint) { log("drawPolygon ", coordinates); var lines = []; for (var i = 0; i < coordinates.length; i++) { var startPoint = coordinates[i]; var endPoint = coordinates[(i + 1) % coordinates.length]; // Loop back to the first point var line = drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y, tint); lines.push(line); } return lines; } function updatePolygon(lines, newCoordinates, scale) { log("updatePolygon ", lines, scale); // Ensure lines and newCoordinates have the same length if (lines.length !== newCoordinates.length) { error("updatePolygon error: lines and newCoordinates length mismatch"); return lines; } // Update each line with new coordinates for (var i = 0; i < lines.length; i++) { var startPoint = newCoordinates[i]; var endPoint = newCoordinates[(i + 1) % newCoordinates.length]; // Loop back to the first point for the last line updateLine(lines[i], startPoint.x, startPoint.y, endPoint.x, endPoint.y, scale); } return lines; } // Utility function to draw lines between two points function drawLine(x1, y1, x2, y2, tint) { log("drawLine ", x1, y1); var line = LK.getAsset('line', { anchorX: 0.0, anchorY: 0.0, x: x1, y: y1, tint: tint }); line.startX = x1; line.startY = y1; line.endX = x2; line.endY = y2; // Calculate the distance between the two points var distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); // Set the width of the line to the distance between the points line.width = distance; // Calculate the angle between the two points var angle = Math.atan2(y2 - y1, x2 - x1); // Correct angle calculation for all quadrants line.rotation = angle; return line; } // Utility function to draw lines between two points function updateLine(line, newX1, newY1, newX2, newY2, scale) { log("updateLine ", line); scale = scale === undefined ? 1 : scale; // Calculate midpoint of the original line var midX = (newX1 + newX2) / 2; var midY = (newY1 + newY2) / 2; // Adjust start and end points based on scale newX1 = midX + (newX1 - midX) * scale; newY1 = midY + (newY1 - midY) * scale; newX2 = midX + (newX2 - midX) * scale; newY2 = midY + (newY2 - midY) * scale; // Update line start and end coordinates after scaling line.x = newX1; line.y = newY1; line.startX = newX1; line.startY = newY1; line.endX = newX2; line.endY = newY2; // Recalculate the distance between the new scaled points var distance = Math.sqrt(Math.pow(newX2 - newX1, 2) + Math.pow(newY2 - newY1, 2)); // Update the width of the line to the new distance line.width = distance; // Recalculate the angle between the new points var angle = Math.atan2(newY2 - newY1, newX2 - newX1); // Update the rotation of the line to the new angle line.rotation = angle; return line; } function log() { if (isDebug) { console.log(arguments); } } /***********************************************************************************/ /******************************* GAME VARIABLES*********************************/ /***********************************************************************************/ var songsList = [{ "name": "Ode to Joy\r\nBeethoven", "bpm": 220, "pitchLevel": 0, "bitsPerPage": 16, "isComposed": false, "songNotes": [{ "time": 1432, "key": "Key6" }, { "time": 1855, "key": "Key6" }, { "time": 2305, "key": "Key7" }, { "time": 2788, "key": "Key8" }, { "time": 3216, "key": "Key8" }, { "time": 3666, "key": "Key7" }, { "time": 4122, "key": "Key6" }, { "time": 4567, "key": "Key5" }, { "time": 5027, "key": "Key4" }, { "time": 5479, "key": "Key4" }, { "time": 5937, "key": "Key5" }, { "time": 6397, "key": "Key6" }, { "time": 6864, "key": "Key6" }, { "time": 7583, "key": "Key5" }, { "time": 7820, "key": "Key5" }, { "time": 8816, "key": "Key6" }, { "time": 9289, "key": "Key6" }, { "time": 9778, "key": "Key7" }, { "time": 10205, "key": "Key8" }, { "time": 10672, "key": "Key8" }, { "time": 11108, "key": "Key7" }, { "time": 11564, "key": "Key6" }, { "time": 12000, "key": "Key5" }, { "time": 12455, "key": "Key4" }, { "time": 12911, "key": "Key4" }, { "time": 13339, "key": "Key5" }, { "time": 13785, "key": "Key6" }, { "time": 14370, "key": "Key5" }, { "time": 15131, "key": "Key4" }, { "time": 15341, "key": "Key4" }, { "time": 16318, "key": "Key5" }, { "time": 16760, "key": "Key5" }, { "time": 17243, "key": "Key6" }, { "time": 17711, "key": "Key4" }, { "time": 18164, "key": "Key5" }, { "time": 18607, "key": "Key6" }, { "time": 18840, "key": "Key7" }, { "time": 19107, "key": "Key6" }, { "time": 19556, "key": "Key4" }, { "time": 20007, "key": "Key5" }, { "time": 20428, "key": "Key6" }, { "time": 20634, "key": "Key7" }, { "time": 20915, "key": "Key6" }, { "time": 21375, "key": "Key5" }, { "time": 21859, "key": "Key4" }, { "time": 22325, "key": "Key5" }, { "time": 22818, "key": "Key1" }, { "time": 23809, "key": "Key6" }, { "time": 24259, "key": "Key6" }, { "time": 24725, "key": "Key7" }, { "time": 25156, "key": "Key8" }, { "time": 25597, "key": "Key8" }, { "time": 26039, "key": "Key7" }, { "time": 26496, "key": "Key6" }, { "time": 26950, "key": "Key5" }, { "time": 27413, "key": "Key4" }, { "time": 27882, "key": "Key4" }, { "time": 28309, "key": "Key5" }, { "time": 28830, "key": "Key6" }, { "time": 29319, "key": "Key5" }, { "time": 30092, "key": "Key4" }, { "time": 30343, "key": "Key4" }], "fromLibrary": true }]; var isDebug = true; var cube; var sphere; var face1; var face2; var face3; var globalSpeed = 12; var rotationSpeedX = 0; var rotationSpeedY = 0; var rotationSpeedZ = 0; var currentRotationAngle = 0; var fullLog = []; var fpsText; var lastTick; var frameCount; var debugText; var cubeManager; var isDraggingSphere = false; var sphereDragOffset = 0; var backgroundManager; var gateManager; var ball; var borderLimitAngle = Math.PI * 0.08; /***********************************************************************************/ /***************************** GAME INITIALIZATION *********************************/ /***********************************************************************************/ function gameInitialize() { // Initialize background manager first (so it's behind other elements) backgroundManager = new BackgroundManager(); game.addChild(backgroundManager); cube = new Cube(1, 1, 1); cube.x = 2048 * 0.5; // Center horizontally cube.y = 2732 / 2; // Center vertically cube.z = 0; cube.visible = false; game.addChild(cube); cube.rotate3D(Math.PI * 0.125, -Math.PI * 0.125, 0); sphere = new Sphere(); sphere.x = 1024; // Starting position at center sphere.y = 2000; // Starting y position (adjusted to match the goal) sphere.z = 0; // Set speeds to zero to stop movement sphere.speedX = 0; sphere.speedY = 0; sphere.speedZ = 0; game.addChild(sphere); // Initialize cube manager cubeManager = new CubeManager(); //game.addChild(cubeManager); // Initialize gate manager gateManager = new GateManager(); game.addChild(gateManager); // Create and position ball ball = new Ball(); ball.x = 1024; ball.y = 2000; game.addChild(ball); if (isDebug) { var debugMarker = LK.getAsset('debugMarker', { anchorX: 0.5, anchorY: 0.5, x: 2048 * 0.5, y: 2732 / 2 }); game.addChild(debugMarker); fpsText = new Text2('FPS: 0', { size: 50, fill: 0xFFFFFF }); // Position FPS text at the bottom-right corner fpsText.anchor.set(1, 1); // Anchor to the bottom-right LK.gui.bottomRight.addChild(fpsText); // Update FPS display every second lastTick = Date.now(); frameCount = 0; // Debug text to display cube information debugText = new Text2('Debug Info', { size: 50, fill: 0xFFFFFF }); debugText.anchor.set(0.5, 0); // Anchor to the bottom-right LK.gui.top.addChild(debugText); // Create sound test button var soundTestButton = new Container(); var buttonBg = LK.getAsset('line', { anchorX: 0.5, anchorY: 0.5, scaleX: 50, scaleY: 15, tint: 0x333333 }); soundTestButton.addChild(buttonBg); var buttonText = new Text2('SOUND TEST', { size: 40, fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); soundTestButton.addChild(buttonText); // Position at top right soundTestButton.x = -100; soundTestButton.y = 50; LK.gui.topRight.addChild(soundTestButton); // Add click handler soundTestButton.down = function () { // Play all key sounds with 600ms delay for (var i = 0; i <= 14; i++) { (function (index) { LK.setTimeout(function () { LK.getSound('key' + index).play(); }, index * 600); })(i); } }; } } /***********************************************************************************/ /******************************** MAIN GAME LOOP ***********************************/ /***********************************************************************************/ game.update = function () { // Rotate simpleFace in 3D on each game update rotationSpeedX += globalSpeed * Math.PI * 0.006; rotationSpeedY += 0 * Math.PI * 0.125 * 0.02; rotationSpeedZ += 0 * Math.PI * 0.125 * 0.02; // Original cube movement commented out - now handled by cubeManager // cube.x += cube.speedX; // cube.y += cube.speedY; // cube.z += cube.speedZ; // if (cube.x <= 100 || cube.x >= 2048 - 100) { // cube.speedX *= -1; // } // if (cube.z <= -250 || cube.z >= 1000) { // cube.speedZ *= -1; // } // if (cube.y <= 100 || cube.y >= 2732 - 100) { // cube.speedY *= -1; // } sphere.rotate3D(rotationSpeedX, rotationSpeedY + currentRotationAngle, rotationSpeedZ); if (isDebug) { debugText.setText("X: " + Math.round(cube.x) + ", Y: " + Math.round(cube.y) + ", Z: " + Math.round(cube.z) + ", R: " + cube.rotation.toFixed(2)); // FPS var now = Date.now(); frameCount++; if (now - lastTick >= 1000) { // Update every second fpsText.setText('FPS: ' + frameCount); frameCount = 0; lastTick = now; } } // Update cube manager cubeManager.update(); // Update background manager backgroundManager.update(); // Update gate manager gateManager.update(); }; // Add game event handlers for sphere control game.down = function (x, y, obj) { // Allow dragging from anywhere on the screen var touchX = x; var touchY = y; isDraggingSphere = true; sphereDragOffset = touchX - sphere.x; }; game.move = function (x, y, obj) { if (isDraggingSphere) { // Calculate normalized position (0 to 1) based on touch x position var touchX = x - sphereDragOffset; var normalizedX = (touchX - 100) / (1948 - 100); // Map touch position to 0-1 range normalizedX = Math.max(0, Math.min(1, normalizedX)); // Clamp to 0-1 // Calculate angle for half-circle, limited by borderLimitAngle // Instead of full PI to 0, use (PI - borderLimitAngle) to borderLimitAngle var minAngle = borderLimitAngle; var maxAngle = Math.PI - borderLimitAngle; var angleRange = maxAngle - minAngle; var angle = maxAngle - angleRange * normalizedX; // Calculate position on the ellipse/half-circle // Center of the ellipse path var centerX = 1024; var centerY = 1366; // Radii for the ellipse var radiusX = 924; // Half of (1948 - 100) to reach edges var radiusY = 634; // Distance from center to starting position (2000 - 1366) // Calculate new position sphere.x = centerX + radiusX * Math.cos(angle); sphere.y = centerY + radiusY * Math.sin(angle); // Apply rotation to simulate 3D perspective currentRotationAngle = (normalizedX - 0.5) * Math.PI * 0.5; // Rotate based on position sphere.rotate3D(Math.PI * 0.5, Math.PI * 0.5 + currentRotationAngle, 0); } }; game.up = function (x, y, obj) { isDraggingSphere = false; }; gameInitialize(); // test gate /* var testGate = new Gate(); var newScale = 1; testGate.scaleX = newScale; testGate.scaleY = newScale; testGate.gateAsset.scaleX = newScale; testGate.gateAsset.scaleY = newScale; testGate.boundingBox.scaleX = newScale; testGate.boundingBox.scaleY = newScale; testGate.boundingBox.alpha = 0.3; game.addChild(testGate); */
===================================================================
--- original.js
+++ change.js
@@ -1140,9 +1140,9 @@
var sphere;
var face1;
var face2;
var face3;
-var globalSpeed = 6;
+var globalSpeed = 12;
var rotationSpeedX = 0;
var rotationSpeedY = 0;
var rotationSpeedZ = 0;
var currentRotationAngle = 0;
remove background
remove background
Futuristic speaker in the shape of a white orb. Face view
white video camera icon
landscape of a furturistic world by night
a white music note
white sparkles emiting from the center. back background
clean red-violet beam from above
button in the shape of a protorealistic holographic futuristc Rectangle . Front view.
above the clouds by a bright night, no visible moon Photorealistic
White Clef de sol
A 20 nodes straight metalic lock chain. High definition. In-Game asset. 2d. High contrast. No shadows
a closed metalic padlock. No visible key hole.
white menu icon
in white