User prompt
As the mask scale grows above base scale, raise Y position very slightly to compensate.
User prompt
As the mask scale grows above base scale, raise Y position to compensate.
User prompt
As the mask scale is reduced below standard scale, lower it on the face the compensate to keep proper orientation.
User prompt
As the mask scale is reduced as the face get smaller, lower it on the face to compensate.
User prompt
Remove the scale limits from the mask and mouth animator so they can continue to shrink and grow based on face size.
User prompt
Update as needed with: // In Mask class, replace the scale tracking section: // Scale tracking variables - MADE MORE RESPONSIVE var scaleHistory = new Array(3).fill(1); // Reduced from 5 to 3 for faster response var scaleIndex = 0; var currentScale = 1; // In the update function, replace the scale section: if (facekit.leftEye && facekit.rightEye) { var eyeDistance = Math.abs(facekit.rightEye.x - facekit.leftEye.x); var newScale = eyeDistance / 280; // Reduced from 300 for more sensitivity // Update rolling average for scale smoothing scaleHistory[scaleIndex] = newScale; scaleIndex = (scaleIndex + 1) % scaleHistory.length; // Calculate average scale var avgScale = scaleHistory.reduce(function (a, b) { return a + b; }, 0) / scaleHistory.length; // Apply with MORE RESPONSIVE smoothing and WIDER range var targetScale = Math.max(0.4, Math.min(2.0, avgScale)); // Expanded from 0.7-1.3 to 0.4-2.0 currentScale = currentScale * 0.7 + targetScale * 0.3; // Increased from 0.15 to 0.3 for faster response // Apply scale to mask self.maskGraphics.scaleX = currentScale; self.maskGraphics.scaleY = currentScale; } ``` **For the MouthAnimator:** ```javascript // In MouthAnimator class, replace the scale tracking section: // Scale tracking variables - MADE MORE RESPONSIVE var scaleHistory = new Array(3).fill(1); // Reduced from 5 to 3 var scaleIndex = 0; var currentScale = 1; // In the update function, replace the scale section: if (facekit.leftEye && facekit.rightEye) { var eyeDistance = Math.abs(facekit.rightEye.x - facekit.leftEye.x); var newScale = eyeDistance / 230; // Reduced from 250 for more sensitivity scaleHistory[scaleIndex] = newScale; scaleIndex = (scaleIndex + 1) % scaleHistory.length; var avgScale = scaleHistory.reduce(function (a, b) { return a + b; }, 0) / scaleHistory.length; // WIDER range and MORE RESPONSIVE smoothing var targetScale = Math.max(0.3, Math.min(2.5, avgScale)); // Expanded from 0.6-1.4 to 0.3-2.5 currentScale = currentScale * 0.65 + targetScale * 0.35; // Increased from 0.15 to 0.35 // Apply to all visemes Object.keys(self.visemes).forEach(function (key) { self.visemes[key].scaleX = currentScale; self.visemes[key].scaleY = currentScale; }); } ``` ### **2. Add Distance-Based Scaling Enhancement** Add this additional scale factor based on overall face size: ```javascript // Add this function to both Mask and MouthAnimator classes: function calculateFaceSize() { if (facekit.leftEye && facekit.rightEye && facekit.mouthCenter) { // Calculate face height (eye to mouth distance) var eyeMidY = (facekit.leftEye.y + facekit.rightEye.y) / 2; var faceHeight = Math.abs(facekit.mouthCenter.y - eyeMidY); // Calculate face width (eye distance) var faceWidth = Math.abs(facekit.rightEye.x - facekit.leftEye.x); // Use both dimensions for more accurate scaling return (faceWidth + faceHeight * 0.7) / 2; // Weight face width more than height } return 150; // Default size } // Then in your scale update section, replace the eyeDistance calculation: var faceSize = calculateFaceSize(); var newScale = faceSize / 200; // Adjust this divisor to fine-tune sensitivity ``` ### **3. Optimize Smoothing for Responsiveness** For even more responsive tracking, you can reduce the position smoothing as well: ```javascript // In both classes, update the smoothing factors: var smoothingFactor = 0.25; // Increased from 0.15/0.18 for faster position response // And for the scale smoothing, make it even more responsive: currentScale = currentScale * 0.6 + targetScale * 0.4; // Even more responsive ``` ### **4. Optional: Add Velocity-Based Scaling** For the most responsive experience, add velocity detection: ```javascript // Add these variables to both classes: var previousFaceSize = null; var sizeVelocity = 0; // In the update function: var currentFaceSize = calculateFaceSize(); if (previousFaceSize !== null) { sizeVelocity = (currentFaceSize - previousFaceSize) * 0.1; // Damping factor newScale += sizeVelocity; // Add velocity component } previousFaceSize = currentFaceSize; ↪💡 Consider importing and using the following plugins: @upit/facekit.v1
Code edit (6 edits merged)
Please save this source code
User prompt
Start the first subtitles slightly sooner.
Code edit (1 edits merged)
Please save this source code
User prompt
After the song is completed, do not restart it. Stop the music.
Code edit (2 edits merged)
Please save this source code
User prompt
Update with: self.subtitleData = [ {start: 5.5, end: 7.5, text: "From the ashes of tragedy"}, {start: 10.0, end: 12.5, text: "A guardian rises"}, {start: 14.0, end: 16.0, text: "Not the hero they deserve"}, {start: 17.5, end: 19.5, text: "But the one they need"}, {start: 22.5, end: 24.5, text: "Parents fallen in an alley cold"}, {start: 25.0, end: 29.0, text: "A boy's heart turned to stone"}, {start: 33.0, end: 35.0, text: "Fortune built on others' pain"}, {start: 36.0, end: 40.0, text: "Now I claim the night as my own"}, {start: 43.5, end: 46.0, text: "Gotham calls to me in whispers dire"}, {start: 47.0, end: 51.0, text: "Criminals scatter at my arrival"}, {start: 53.5, end: 56.0, text: "The cape becomes my second skin"}, {start: 57.0, end: 62.0, text: "This mask - my true survival"}, // Chorus {start: 63.5, end: 69.0, text: "From the darkness I emerge"}, {start: 75.0, end: 79.0, text: "Justice is what I serve"}, {start: 85.5, end: 90.0, text: "When evil plagues these streets"}, {start: 90.5, end: 92.0, text: "I'll be there"}, {start: 93.5, end: 94.5, text: "I'll be there"}, {start: 96.0, end: 97.0, text: "I'm Batman"}, {start: 98.5, end: 100.0, text: "I'm Batman"}, {start: 101.5, end: 102.5, text: "I'm Batman"}, {start: 103.0, end: 105.0, text: "I'm Batman"}, // Verse 2 {start: 109.0, end: 112.5, text: "A gallery of madness I've faced"}, {start: 114.0, end: 116.5, text: "The Joker with his twisted grin"}, {start: 120.0, end: 123.8, text: "Riddler's puzzles, Penguin's schemes"}, {start: 124.0, end: 128.5, text: "Two-Face's coin, the battle within"}, {start: 130.0, end: 132.0, text: "My allies few, my burden great"}, {start: 133.0, end: 137.0, text: "The cave below, my sanctuary"}, {start: 140.5, end: 144.5, text: "Alfred's wisdom guides my path"}, {start: 145.0, end: 150.5, text: "Through this life so solitary"}, // Chorus repeat {start: 151.5, end: 157.0, text: "From the darkness I emerge"}, {start: 163.5, end: 167.5, text: "Justice is what I serve"}, {start: 173.5, end: 178.0, text: "When evil plagues these streets"}, {start: 178.5, end: 180.0, text: "I'll be there"}, {start: 181.0, end: 182.5, text: "I'll be there"}, {start: 184.0, end: 185.0, text: "I'm Batman"}, {start: 186.5, end: 188.0, text: "I'm Batman"}, {start: 189.0, end: 190.5, text: "I'm Batman"}, {start: 191.5, end: 193.0, text: "I'm Batman"}, // Outro {start: 194.0, end: 197.0, text: "Na na na na na na na na"}, {start: 197.0, end: 200.0, text: "Na na na na na na na na"}, {start: 198.0, end: 198.5, text: "Batman"}, {start: 199.0, end: 202.0, text: "Na na na na na na na na"}, {start: 202.0, end: 205.0, text: "Na na na na na na na na"}, {start: 203.0, end: 204.5, text: "Batman!"} ];
User prompt
Move the subtitle Y position up 10%
User prompt
Make the subtitles twice the size and adjust positioning to compensate.
User prompt
Update with: var SubtitleDisplay = Container.expand(function () { var self = Container.call(this); // Create text display properties self.currentText = ""; self.textDisplay = null; self.subtitleData = [ {start: 5.5, end: 7.5, text: "From the ashes of tragedy"}, {start: 10.0, end: 12.5, text: "A guardian rises"}, {start: 14.0, end: 16.0, text: "Not the hero they deserve"}, {start: 17.5, end: 19.5, text: "But the one they need"}, {start: 22.5, end: 24.5, text: "Parents fallen in an alley cold"}, {start: 25.0, end: 29.0, text: "A boy's heart turned to stone"}, {start: 33.0, end: 35.0, text: "Fortune built on others' pain"}, {start: 36.0, end: 40.0, text: "Now I claim the night as my own"}, {start: 43.5, end: 46.0, text: "Gotham calls to me in whispers dire"}, {start: 47.0, end: 51.0, text: "Criminals scatter at my arrival"}, {start: 53.5, end: 56.0, text: "The cape becomes my second skin"}, {start: 57.0, end: 62.0, text: "This mask - my true survival"}, // Add chorus sections {start: 63.5, end: 69.0, text: "From the darkness I emerge"}, {start: 75.0, end: 79.0, text: "Justice is what I serve"}, {start: 85.5, end: 90.0, text: "When evil plagues these streets"}, {start: 90.5, end: 92.0, text: "I'll be there"}, {start: 93.5, end: 94.5, text: "I'll be there"}, {start: 96.0, end: 97.0, text: "I'm Batman"}, {start: 98.5, end: 100.0, text: "I'm Batman"}, {start: 101.5, end: 102.5, text: "I'm Batman"}, {start: 103.0, end: 105.0, text: "I'm Batman"} // Continue with remaining lyrics... ]; self.songStartTime = null; self.isActive = false; self.currentSubtitleIndex = 0; // Text styling properties self.textStyle = { fontFamily: 'Arial, sans-serif', fontSize: 48, fontWeight: 'bold', color: '#FFFFFF', stroke: '#000000', strokeWidth: 3, textAlign: 'center', textBaseline: 'middle', shadowColor: '#000000', shadowBlur: 8, shadowOffsetX: 2, shadowOffsetY: 2 }; // Create text graphics using canvas text self.createTextGraphics = function(text) { if (self.textDisplay) { self.removeChild(self.textDisplay); } if (!text || text.trim() === "") { self.textDisplay = null; return; } // Create a canvas-based text display var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); // Set canvas size canvas.width = GAME_WIDTH; canvas.height = 120; // Apply text styling ctx.font = `${self.textStyle.fontWeight} ${self.textStyle.fontSize}px ${self.textStyle.fontFamily}`; ctx.textAlign = self.textStyle.textAlign; ctx.textBaseline = self.textStyle.textBaseline; // Add shadow ctx.shadowColor = self.textStyle.shadowColor; ctx.shadowBlur = self.textStyle.shadowBlur; ctx.shadowOffsetX = self.textStyle.shadowOffsetX; ctx.shadowOffsetY = self.textStyle.shadowOffsetY; // Draw stroke ctx.strokeStyle = self.textStyle.stroke; ctx.lineWidth = self.textStyle.strokeWidth; ctx.strokeText(text, canvas.width / 2, canvas.height / 2); // Draw fill ctx.fillStyle = self.textStyle.color; ctx.fillText(text, canvas.width / 2, canvas.height / 2); // Convert to texture and create sprite var texture = PIXI.Texture.from(canvas); self.textDisplay = new PIXI.Sprite(texture); self.textDisplay.anchor.set(0.5, 0.5); self.addChild(self.textDisplay); }; self.startSubtitles = function() { self.songStartTime = Date.now(); self.isActive = true; self.currentSubtitleIndex = 0; console.log("Subtitles started"); }; self.updateSubtitles = function() { if (!self.isActive || !self.songStartTime || self.subtitleData.length === 0) { return; } var currentTimeSeconds = (Date.now() - self.songStartTime) / 1000.0; var newText = ""; // Find current subtitle for (var i = self.currentSubtitleIndex; i < self.subtitleData.length; i++) { var subtitle = self.subtitleData[i]; if (currentTimeSeconds >= subtitle.start && currentTimeSeconds < subtitle.end) { newText = subtitle.text; self.currentSubtitleIndex = i; break; } if (currentTimeSeconds < subtitle.start) { break; } } // Update display if text changed if (newText !== self.currentText) { self.currentText = newText; self.createTextGraphics(newText); } }; self.setPosition = function(x, y) { self.x = x; self.y = y; }; self.update = function() { self.updateSubtitles(); }; return self; }); ``` ## Integration with Your Existing Code Add this to your game initialization in the `handleStartButtonPressed` function: ```javascript function handleStartButtonPressed() { if (isGameActive) { return; } isGameActive = true; // ... existing code ... // Create subtitle display subtitleInstance = new SubtitleDisplay(); subtitleInstance.setPosition(GAME_WIDTH / 2, GAME_HEIGHT - 150); // Position at bottom game.addChild(subtitleInstance); // ... existing mask and mouth animator code ... // Start music, lip-sync, and subtitles LK.setTimeout(function () { LK.playMusic('batmansong'); if (mouthAnimatorInstance) { mouthAnimatorInstance.startLipSync(); } if (subtitleInstance) { subtitleInstance.startSubtitles(); } }, 1200); } ``` ## Add to Game Update Loop Update your game update function: ```javascript game.update = function () { if (isGameActive) { if (mouthAnimatorInstance) { mouthAnimatorInstance.update(); } if (maskInstance) { maskInstance.update(); } if (subtitleInstance) { subtitleInstance.update(); } } }; ``` ## Add Global Variable Add this at the top with your other game state variables: ```javascript var subtitleInstance;
Code edit (1 edits merged)
Please save this source code
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var facekit = LK.import("@upit/facekit.v1"); /**** * Classes ****/ var Mask = Container.expand(function () { var self = Container.call(this); // Store maskGraphics on self to access its properties like height if needed later self.maskGraphics = self.attachAsset('maskImage', { anchorX: 0.5, anchorY: 0.4 }); self.animationFinished = false; // Flag to track if the intro animation is done var targetX = GAME_WIDTH / 2; var targetY = GAME_HEIGHT / 3; var smoothingFactor = 0.15; // Higher value for more responsive tracking var prevX = null; var prevY = null; // Scale tracking variables (from dragon example) var scaleHistory = new Array(5).fill(1); // Start with default scale of 1 var scaleIndex = 0; var currentScale = 1; // Rotation variables - EXACTLY from working example var targetTilt = 0; var tiltSmoothingFactor = 0.11; var tiltScaleFactor = 0.09; self.show = function (targetX, targetY, duration) { self.x = targetX; self.y = -self.maskGraphics.height / 2 - 50; tween(self, { y: targetY }, { duration: duration, easing: tween.easeOutSine, onFinish: function onFinish() { self.animationFinished = true; } }); }; // Function copied EXACTLY from working DragonHead example function calculateFaceTilt() { if (facekit.leftEye && facekit.rightEye && facekit.mouthCenter) { // Calculate midpoint between eyes var eyeMidX = (facekit.leftEye.x + facekit.rightEye.x) / 2; var eyeMidY = (facekit.leftEye.y + facekit.rightEye.y) / 2; // Calculate angle between eye midpoint and mouth, negated to fix direction var dx = facekit.mouthCenter.x - eyeMidX; var dy = facekit.mouthCenter.y - eyeMidY; var angle = -(Math.atan2(dx, dy) * (180 / Math.PI)); // Reduced angle impact return Math.max(-15, Math.min(15, angle * 0.15)); } return 0; // Default to straight when face points aren't available } // Enhanced update method with smooth position tracking and dynamic scaling self.update = function () { if (!self.animationFinished) { return; } // Smooth position tracking (from dragon example) if (facekit.leftEye && facekit.rightEye) { // Use midpoint between eyes for X, and slightly above for Y targetX = (facekit.leftEye.x + facekit.rightEye.x) / 2; targetY = (facekit.leftEye.y + facekit.rightEye.y) / 2 - self.maskGraphics.height * 0.20 - 50; // Initialize previous positions if not set if (prevX === null) { prevX = targetX; prevY = targetY; } // Weighted average between previous and target position for smooth tracking var newX = prevX * (1 - smoothingFactor) + targetX * smoothingFactor; var newY = prevY * (1 - smoothingFactor) + targetY * smoothingFactor; self.x = newX; self.y = newY; // Update previous positions prevX = newX; prevY = newY; } // Dynamic scale adjustment based on face size (from dragon example) if (facekit.leftEye && facekit.rightEye) { var eyeDistance = Math.abs(facekit.rightEye.x - facekit.leftEye.x); var newScale = eyeDistance / 300; // Adjust divisor for mask size // Update rolling average for scale smoothing scaleHistory[scaleIndex] = newScale; scaleIndex = (scaleIndex + 1) % scaleHistory.length; // Calculate average scale var avgScale = scaleHistory.reduce(function (a, b) { return a + b; }, 0) / scaleHistory.length; // Apply with gentle smoothing (limited range) var targetScale = Math.max(0.7, Math.min(1.3, avgScale)); currentScale = currentScale * 0.85 + targetScale * 0.15; // Apply scale to mask self.maskGraphics.scaleX = currentScale; self.maskGraphics.scaleY = currentScale; } // Rotation tracking - COPIED EXACTLY from working example if (facekit.leftEye && facekit.rightEye) { targetTilt = calculateFaceTilt() * tiltScaleFactor; // Limit rotation to ±15 degrees - DON'T convert to radians here targetTilt = Math.max(-15, Math.min(15, targetTilt)); self.rotation += (targetTilt - self.rotation) * tiltSmoothingFactor; } }; return self; }); var MouthAnimator = Container.expand(function () { var self = Container.call(this); // Load all viseme assets (keeping your existing setup) self.visemes = { closed: self.attachAsset('visemeClosed', { anchorX: 0.5, anchorY: 0.5, visible: false }), AEI: self.attachAsset('visemeAEI', { anchorX: 0.5, anchorY: 0.5, visible: false }), BMP: self.attachAsset('visemeBMP', { anchorX: 0.5, anchorY: 0.5, visible: false }), CDGKNRSTXYZ: self.attachAsset('visemeCDGKNRSTXYZ', { anchorX: 0.5, anchorY: 0.5, visible: false }), CHJSH: self.attachAsset('visemeCHJSH', { anchorX: 0.5, anchorY: 0.5, visible: false }), EE: self.attachAsset('visemeEE', { anchorX: 0.5, anchorY: 0.5, visible: false }), FV: self.attachAsset('visemeFV', { anchorX: 0.5, anchorY: 0.5, visible: false }), L: self.attachAsset('visemeL', { anchorX: 0.5, anchorY: 0.5, visible: false }), N: self.attachAsset('visemeN', { anchorX: 0.5, anchorY: 0.5, visible: false }), O: self.attachAsset('visemeO', { anchorX: 0.5, anchorY: 0.5, visible: false }), TH: self.attachAsset('visemeTH', { anchorX: 0.5, anchorY: 0.5, visible: false }), U: self.attachAsset('visemeU', { anchorX: 0.5, anchorY: 0.5, visible: false }), QW: self.attachAsset('visimeQW', { anchorX: 0.5, anchorY: 0.5, visible: false }) }; self.currentViseme = 'closed'; self.visemes.closed.visible = true; // Automated lip sync data (converted from your JSON) self.automatedLipSync = [{ start: 0.00, end: 5.03, value: "X" }, { start: 5.03, end: 5.16, value: "B" }, { start: 5.16, end: 5.23, value: "E" }, { start: 5.23, end: 5.30, value: "F" }, { start: 5.30, end: 5.40, value: "A" }, { start: 5.40, end: 5.54, value: "C" }, { start: 5.54, end: 5.61, value: "B" }, { start: 5.61, end: 5.82, value: "C" }, { start: 5.82, end: 5.96, value: "B" }, { start: 5.96, end: 6.03, value: "C" }, { start: 6.03, end: 6.31, value: "B" }, { start: 6.31, end: 6.52, value: "F" }, { start: 6.52, end: 6.73, value: "E" }, { start: 6.73, end: 7.43, value: "E" }, { start: 7.43, end: 9.85, value: "X" }, { start: 9.85, end: 10.03, value: "E" }, { start: 10.03, end: 10.24, value: "C" }, { start: 10.24, end: 10.38, value: "A" }, { start: 10.38, end: 10.52, value: "F" }, { start: 10.52, end: 10.59, value: "D" }, { start: 10.59, end: 11.15, value: "C" }, { start: 11.15, end: 11.23, value: "A" }, { start: 11.23, end: 11.54, value: "D" }, { start: 11.54, end: 11.62, value: "C" }, { start: 11.62, end: 12.77, value: "G" }, { start: 12.77, end: 13.77, value: "X" }, { start: 13.77, end: 13.83, value: "N" }, { start: 13.83, end: 14.44, value: "O" }, { start: 14.44, end: 14.79, value: "C" }, { start: 14.79, end: 15.01, value: "F" }, { start: 15.01, end: 15.09, value: "A" }, { start: 15.09, end: 15.13, value: "B" }, { start: 15.13, end: 15.38, value: "F" }, { start: 15.38, end: 15.45, value: "B" }, { start: 15.45, end: 16.08, value: "E" }, { start: 16.08, end: 16.29, value: "B" }, { start: 16.29, end: 17.16, value: "X" }, { start: 17.16, end: 17.23, value: "C" }, { start: 17.23, end: 17.29, value: "B" }, { start: 17.29, end: 18.99, value: "E" }, { start: 18.99, end: 19.53, value: "G" }, { start: 19.53, end: 22.08, value: "X" }, { start: 22.08, end: 22.42, value: "E" }, { start: 22.42, end: 22.77, value: "B" }, { start: 22.77, end: 22.85, value: "A" }, { start: 22.85, end: 23.02, value: "N" }, { start: 23.02, end: 23.30, value: "C" }, { start: 23.30, end: 23.65, value: "C" }, { start: 23.65, end: 23.86, value: "B" }, { start: 23.86, end: 24.49, value: "E" }, { start: 24.49, end: 24.91, value: "L" }, { start: 24.91, end: 24.98, value: "E" }, { start: 24.98, end: 25.05, value: "N" }, { start: 25.05, end: 25.33, value: "E" }, { start: 25.33, end: 25.75, value: "B" }, { start: 25.75, end: 25.82, value: "D" }, { start: 25.82, end: 25.96, value: "B" }, { start: 25.96, end: 26.17, value: "F" }, { start: 26.17, end: 26.45, value: "E" }, { start: 26.45, end: 27.08, value: "B" }, { start: 27.08, end: 27.43, value: "E" }, { start: 27.43, end: 27.57, value: "F" }, { start: 27.57, end: 27.71, value: "C" }, { start: 27.71, end: 28.48, value: "O" }, { start: 28.48, end: 32.70, value: "X" }, { start: 32.70, end: 32.82, value: "B" }, { start: 32.82, end: 33.03, value: "O" }, { start: 33.03, end: 33.45, value: "E" }, { start: 33.45, end: 33.66, value: "E" }, { start: 33.66, end: 33.73, value: "C" }, { start: 33.73, end: 33.87, value: "B" }, { start: 33.87, end: 34.01, value: "E" }, { start: 34.01, end: 34.29, value: "C" }, { start: 34.29, end: 34.36, value: "B" }, { start: 34.36, end: 34.50, value: "E" }, { start: 34.50, end: 34.71, value: "B" }, { start: 34.71, end: 34.85, value: "E" }, { start: 34.85, end: 35.13, value: "C" }, { start: 35.13, end: 35.20, value: "C" }, { start: 35.20, end: 35.48, value: "B" }, { start: 35.48, end: 35.55, value: "C" }, { start: 35.55, end: 35.76, value: "E" }, { start: 35.76, end: 35.90, value: "F" }, { start: 35.90, end: 36.60, value: "B" }, { start: 36.60, end: 36.71, value: "A" }, { start: 36.71, end: 36.89, value: "O" }, { start: 36.89, end: 36.94, value: "N" }, { start: 36.94, end: 37.47, value: "B" }, { start: 37.47, end: 37.55, value: "A" }, { start: 37.55, end: 37.65, value: "E" }, { start: 37.65, end: 37.72, value: "B" }, { start: 37.72, end: 37.92, value: "F" }, { start: 37.92, end: 38.00, value: "E" }, { start: 38.00, end: 40.10, value: "O" }, { start: 40.10, end: 43.42, value: "X" }, { start: 43.42, end: 43.67, value: "C" }, { start: 43.67, end: 43.74, value: "O" }, { start: 43.74, end: 43.88, value: "F" }, { start: 43.88, end: 44.23, value: "A" }, { start: 44.23, end: 44.44, value: "C" }, { start: 44.44, end: 44.58, value: "B" }, { start: 44.58, end: 44.79, value: "C" }, { start: 44.79, end: 45.07, value: "O" }, { start: 45.07, end: 45.35, value: "L" }, { start: 45.35, end: 45.63, value: "C" }, { start: 45.63, end: 45.70, value: "B" }, { start: 45.70, end: 46.12, value: "E" }, { start: 46.12, end: 46.40, value: "E" }, { start: 46.40, end: 46.45, value: "B" }, { start: 46.45, end: 47.13, value: "F" }, { start: 47.13, end: 47.27, value: "C" }, { start: 47.27, end: 47.34, value: "B" }, { start: 47.34, end: 47.55, value: "E" }, { start: 47.55, end: 47.76, value: "C" }, { start: 47.76, end: 47.83, value: "B" }, { start: 47.83, end: 48.04, value: "D" }, { start: 48.04, end: 48.11, value: "B" }, { start: 48.11, end: 48.81, value: "F" }, { start: 48.81, end: 48.88, value: "A" }, { start: 48.88, end: 48.95, value: "L" }, { start: 48.95, end: 49.02, value: "E" }, { start: 49.02, end: 50.28, value: "B" }, { start: 50.28, end: 50.61, value: "A" }, { start: 50.61, end: 53.74, value: "X" }, { start: 53.74, end: 54.23, value: "C" }, { start: 54.23, end: 54.31, value: "A" }, { start: 54.31, end: 54.51, value: "B" }, { start: 54.51, end: 54.79, value: "E" }, { start: 54.79, end: 54.90, value: "A" }, { start: 54.90, end: 55.01, value: "B" }, { start: 55.01, end: 55.12, value: "A" }, { start: 55.12, end: 55.18, value: "C" }, { start: 55.18, end: 55.52, value: "B" }, { start: 55.52, end: 55.59, value: "C" }, { start: 55.59, end: 55.87, value: "B" }, { start: 55.87, end: 56.22, value: "F" }, { start: 56.22, end: 56.36, value: "B" }, { start: 56.36, end: 56.66, value: "F" }, { start: 56.66, end: 56.74, value: "E" }, { start: 56.74, end: 56.89, value: "C" }, { start: 56.89, end: 57.17, value: "A" }, { start: 57.17, end: 57.69, value: "N" }, { start: 57.69, end: 57.92, value: "C" }, { start: 57.92, end: 58.20, value: "B" }, { start: 58.20, end: 59.04, value: "F" }, { start: 59.04, end: 59.18, value: "B" }, { start: 59.18, end: 59.39, value: "E" }, { start: 59.39, end: 59.53, value: "C" }, { start: 59.53, end: 59.98, value: "O" }, { start: 59.98, end: 60.06, value: "N" }, { start: 60.06, end: 60.37, value: "B" }, { start: 60.37, end: 60.51, value: "C" }, { start: 60.51, end: 60.65, value: "E" }, { start: 60.65, end: 62.19, value: "N" }, { start: 62.19, end: 62.33, value: "X" }, { start: 62.33, end: 62.55, value: "B" }, { start: 62.55, end: 63.24, value: "X" }, { start: 63.24, end: 63.38, value: "B" }, { start: 63.38, end: 63.73, value: "E" }, { start: 63.73, end: 63.87, value: "C" }, { start: 63.87, end: 63.99, value: "A" }, { start: 63.99, end: 64.11, value: "C" }, { start: 64.11, end: 64.46, value: "B" }, { start: 64.46, end: 64.60, value: "F" }, { start: 64.60, end: 64.67, value: "B" }, { start: 64.67, end: 64.88, value: "F" }, { start: 64.88, end: 64.95, value: "C" }, { start: 64.95, end: 65.88, value: "B" }, { start: 65.88, end: 66.43, value: "X" }, { start: 66.43, end: 66.62, value: "D" }, { start: 66.62, end: 66.81, value: "C" }, { start: 66.81, end: 66.86, value: "E" }, { start: 66.86, end: 67.11, value: "C" }, { start: 67.11, end: 68.93, value: "B" }, { start: 68.93, end: 69.14, value: "X" }, { start: 69.14, end: 70.99, value: "A" }, { start: 70.99, end: 71.13, value: "F" }, { start: 71.13, end: 72.63, value: "A" }, { start: 72.63, end: 72.71, value: "C" }, { start: 72.71, end: 73.16, value: "B" }, { start: 73.16, end: 75.30, value: "X" }, { start: 75.30, end: 75.57, value: "B" }, { start: 75.57, end: 75.71, value: "E" }, { start: 75.71, end: 75.99, value: "B" }, { start: 75.99, end: 76.27, value: "C" }, { start: 76.27, end: 76.34, value: "B" }, { start: 76.34, end: 76.83, value: "F" }, { start: 76.83, end: 77.04, value: "E" }, { start: 77.04, end: 77.11, value: "C" }, { start: 77.11, end: 77.18, value: "H" }, { start: 77.18, end: 77.37, value: "E" }, { start: 77.37, end: 77.42, value: "C" }, { start: 77.42, end: 77.81, value: "B" }, { start: 77.81, end: 79.14, value: "F" }, { start: 79.14, end: 79.48, value: "B" }, { start: 79.48, end: 80.18, value: "E" }, { start: 80.18, end: 80.46, value: "C" }, { start: 80.46, end: 83.12, value: "B" }, { start: 83.12, end: 84.24, value: "E" }, { start: 84.24, end: 84.45, value: "B" }, { start: 84.45, end: 85.37, value: "X" }, { start: 85.37, end: 85.55, value: "F" }, { start: 85.55, end: 86.08, value: "B" }, { start: 86.08, end: 86.62, value: "F" }, { start: 86.62, end: 86.67, value: "C" }, { start: 86.67, end: 87.00, value: "E" }, { start: 87.00, end: 87.28, value: "F" }, { start: 87.28, end: 87.42, value: "D" }, { start: 87.42, end: 87.70, value: "C" }, { start: 87.70, end: 88.54, value: "B" }, { start: 88.54, end: 89.42, value: "D" }, { start: 89.42, end: 89.49, value: "C" }, { start: 89.49, end: 89.73, value: "B" }, { start: 89.73, end: 90.46, value: "X" }, { start: 90.46, end: 90.81, value: "E" }, { start: 90.81, end: 91.27, value: "F" }, { start: 91.27, end: 91.34, value: "E" }, { start: 91.34, end: 91.41, value: "F" }, { start: 91.41, end: 92.11, value: "B" }, { start: 92.11, end: 93.07, value: "X" }, { start: 93.07, end: 93.33, value: "C" }, { start: 93.33, end: 93.40, value: "E" }, { start: 93.40, end: 93.78, value: "F" }, { start: 93.78, end: 93.85, value: "B" }, { start: 93.85, end: 94.16, value: "D" }, { start: 94.16, end: 94.24, value: "C" }, { start: 94.24, end: 95.04, value: "B" }, { start: 95.04, end: 95.79, value: "X" }, { start: 95.79, end: 95.89, value: "C" }, { start: 95.89, end: 95.96, value: "B" }, { start: 95.96, end: 96.05, value: "A" }, { start: 96.05, end: 96.31, value: "C" }, { start: 96.31, end: 96.49, value: "B" }, { start: 96.49, end: 97.18, value: "D" }, { start: 97.18, end: 97.26, value: "C" }, { start: 97.26, end: 98.43, value: "X" }, { start: 98.43, end: 98.54, value: "E" }, { start: 98.54, end: 98.61, value: "F" }, { start: 98.61, end: 98.68, value: "C" }, { start: 98.68, end: 98.75, value: "B" }, { start: 98.75, end: 98.82, value: "F" }, { start: 98.82, end: 99.10, value: "C" }, { start: 99.10, end: 99.18, value: "A" }, { start: 99.18, end: 99.64, value: "C" }, { start: 99.64, end: 99.85, value: "B" }, { start: 99.85, end: 101.13, value: "X" }, { start: 101.13, end: 101.26, value: "D" }, { start: 101.26, end: 101.33, value: "B" }, { start: 101.33, end: 101.45, value: "A" }, { start: 101.45, end: 101.86, value: "C" }, { start: 101.86, end: 102.38, value: "B" }, { start: 102.38, end: 102.46, value: "A" }, { start: 102.46, end: 103.19, value: "N" }, { start: 103.19, end: 103.47, value: "B" }, { start: 103.47, end: 104.52, value: "A" }, { start: 104.52, end: 105.15, value: "C" }, { start: 105.15, end: 105.57, value: "B" }, { start: 105.57, end: 108.41, value: "X" }, { start: 108.41, end: 108.56, value: "A" }, { start: 108.56, end: 108.63, value: "C" }, { start: 108.63, end: 108.84, value: "B" }, { start: 108.84, end: 108.98, value: "A" }, { start: 108.98, end: 109.05, value: "L" }, { start: 109.05, end: 109.33, value: "A" }, { start: 109.33, end: 109.68, value: "B" }, { start: 109.68, end: 109.82, value: "E" }, { start: 109.82, end: 109.89, value: "B" }, { start: 109.89, end: 110.03, value: "O" }, { start: 110.03, end: 110.17, value: "F" }, { start: 110.17, end: 110.24, value: "B" }, { start: 110.24, end: 110.52, value: "A" }, { start: 110.52, end: 110.59, value: "D" }, { start: 110.59, end: 111.01, value: "N" }, { start: 111.01, end: 111.08, value: "E" }, { start: 111.08, end: 111.57, value: "C" }, { start: 111.57, end: 111.64, value: "B" }, { start: 111.64, end: 111.99, value: "E" }, { start: 111.99, end: 113.77, value: "X" }, { start: 113.77, end: 114.16, value: "B" }, { start: 114.16, end: 114.65, value: "E" }, { start: 114.65, end: 114.93, value: "C" }, { start: 114.93, end: 115.00, value: "C" }, { start: 115.00, end: 115.24, value: "A" }, { start: 115.24, end: 115.46, value: "N" }, { start: 115.46, end: 115.60, value: "C" }, { start: 115.60, end: 115.74, value: "B" }, { start: 115.74, end: 115.88, value: "E" }, { start: 115.88, end: 116.09, value: "A" }, { start: 116.09, end: 116.17, value: "B" }, { start: 116.17, end: 116.24, value: "E" }, { start: 116.24, end: 116.73, value: "B" }, { start: 116.73, end: 117.57, value: "X" }, { start: 117.57, end: 117.80, value: "B" }, { start: 117.80, end: 118.81, value: "X" }, { start: 118.81, end: 119.28, value: "B" }, { start: 119.28, end: 119.35, value: "F" }, { start: 119.35, end: 119.49, value: "E" }, { start: 119.49, end: 119.65, value: "A" }, { start: 119.65, end: 119.88, value: "C" }, { start: 119.88, end: 119.95, value: "E" }, { start: 119.95, end: 120.16, value: "B" }, { start: 120.16, end: 120.30, value: "E" }, { start: 120.30, end: 120.37, value: "B" }, { start: 120.37, end: 120.65, value: "E" }, { start: 120.65, end: 120.79, value: "F" }, { start: 120.79, end: 120.93, value: "B" }, { start: 120.93, end: 121.00, value: "E" }, { start: 121.00, end: 121.28, value: "L" }, { start: 121.28, end: 121.49, value: "O" }, { start: 121.49, end: 121.56, value: "F" }, { start: 121.56, end: 121.70, value: "B" }, { start: 121.70, end: 121.98, value: "A" }, { start: 121.98, end: 122.19, value: "E" }, { start: 122.19, end: 122.61, value: "C" }, { start: 122.61, end: 123.56, value: "B" }, { start: 123.56, end: 123.77, value: "X" }, { start: 123.77, end: 123.87, value: "B" }, { start: 123.87, end: 124.02, value: "A" }, { start: 124.02, end: 124.16, value: "L" }, { start: 124.16, end: 124.30, value: "F" }, { start: 124.30, end: 124.72, value: "B" }, { start: 124.72, end: 125.00, value: "E" }, { start: 125.00, end: 125.99, value: "D" }, { start: 125.99, end: 126.06, value: "C" }, { start: 126.06, end: 126.34, value: "I" }, { start: 126.34, end: 126.48, value: "B" }, { start: 126.48, end: 126.62, value: "E" }, { start: 126.62, end: 126.69, value: "F" }, { start: 126.69, end: 126.83, value: "B" }, { start: 126.83, end: 128.26, value: "A" }, { start: 128.26, end: 128.43, value: "C" }, { start: 128.43, end: 129.70, value: "X" }, { start: 129.70, end: 129.85, value: "A" }, { start: 129.85, end: 129.96, value: "L" }, { start: 129.96, end: 130.03, value: "F" }, { start: 130.03, end: 130.17, value: "B" }, { start: 130.17, end: 130.38, value: "E" }, { start: 130.38, end: 130.52, value: "D" }, { start: 130.52, end: 130.59, value: "B" }, { start: 130.59, end: 130.87, value: "I" }, { start: 130.87, end: 131.08, value: "B" }, { start: 131.08, end: 131.15, value: "D" }, { start: 131.15, end: 131.29, value: "O" }, { start: 131.29, end: 131.36, value: "B" }, { start: 131.36, end: 131.50, value: "B" }, { start: 131.50, end: 131.81, value: "A" }, { start: 131.81, end: 131.89, value: "C" }, { start: 131.89, end: 132.20, value: "B" }, { start: 132.20, end: 132.48, value: "E" }, { start: 132.48, end: 132.62, value: "X" }, { start: 132.62, end: 132.69, value: "B" }, { start: 132.69, end: 132.90, value: "U" }, { start: 132.90, end: 133.25, value: "B" }, { start: 133.25, end: 133.45, value: "F" }, { start: 133.45, end: 133.53, value: "E" }, { start: 133.53, end: 133.88, value: "B" }, { start: 133.88, end: 134.02, value: "U" }, { start: 134.02, end: 134.14, value: "X" }, { start: 134.14, end: 134.29, value: "B" }, { start: 134.29, end: 134.36, value: "A" }, { start: 134.36, end: 134.50, value: "B" }, { start: 134.50, end: 134.71, value: "D" }, { start: 134.71, end: 134.85, value: "E" }, { start: 134.85, end: 135.06, value: "N" }, { start: 135.06, end: 135.20, value: "C" }, { start: 135.20, end: 135.27, value: "B" }, { start: 135.27, end: 135.69, value: "A" }, { start: 135.69, end: 135.83, value: "B" }, { start: 135.83, end: 136.53, value: "E" }, { start: 136.53, end: 136.67, value: "B" }, { start: 136.67, end: 140.08, value: "X" }, { start: 140.08, end: 140.22, value: "D" }, { start: 140.22, end: 140.29, value: "B" }, { start: 140.29, end: 140.57, value: "E" }, { start: 140.57, end: 141.20, value: "F" }, { start: 141.20, end: 141.41, value: "B" }, { start: 141.41, end: 141.76, value: "A" }, { start: 141.76, end: 142.11, value: "C" }, { start: 142.11, end: 142.18, value: "N" }, { start: 142.18, end: 142.25, value: "E" }, { start: 142.25, end: 142.74, value: "C" }, { start: 142.74, end: 142.93, value: "B" }, { start: 142.93, end: 142.98, value: "A" }, { start: 142.98, end: 143.21, value: "C" }, { start: 143.21, end: 143.97, value: "D" }, { start: 143.97, end: 144.11, value: "B" }, { start: 144.11, end: 144.27, value: "X" }, { start: 144.27, end: 144.40, value: "B" }, { start: 144.40, end: 145.11, value: "X" }, { start: 145.11, end: 145.58, value: "B" }, { start: 145.58, end: 145.72, value: "F" }, { start: 145.72, end: 145.80, value: "A" }, { start: 145.80, end: 145.87, value: "C" }, { start: 145.87, end: 146.18, value: "D" }, { start: 146.18, end: 146.25, value: "C" }, { start: 146.25, end: 146.49, value: "B" }, { start: 146.49, end: 146.56, value: "A" }, { start: 146.56, end: 146.77, value: "L" }, { start: 146.77, end: 146.85, value: "A" }, { start: 146.85, end: 147.26, value: "E" }, { start: 147.26, end: 147.47, value: "B" }, { start: 147.47, end: 147.75, value: "F" }, { start: 147.75, end: 147.82, value: "E" }, { start: 147.82, end: 148.15, value: "D" }, { start: 148.15, end: 148.20, value: "C" }, { start: 148.20, end: 148.38, value: "B" }, { start: 148.38, end: 148.45, value: "F" }, { start: 148.45, end: 150.48, value: "E" }, { start: 150.48, end: 150.90, value: "B" }, { start: 150.90, end: 151.41, value: "X" }, { start: 151.41, end: 151.76, value: "E" }, { start: 151.76, end: 151.90, value: "F" }, { start: 151.90, end: 152.15, value: "A" }, { start: 152.15, end: 152.40, value: "E" }, { start: 152.40, end: 152.54, value: "F" }, { start: 152.54, end: 152.61, value: "B" }, { start: 152.61, end: 152.89, value: "E" }, { start: 152.89, end: 153.13, value: "F" }, { start: 153.13, end: 153.19, value: "C" }, { start: 153.19, end: 153.38, value: "E" }, { start: 153.38, end: 153.45, value: "F" }, { start: 153.45, end: 154.69, value: "X" }, { start: 154.69, end: 154.96, value: "C" }, { start: 154.96, end: 155.16, value: "A" }, { start: 155.16, end: 155.54, value: "F" }, { start: 155.54, end: 155.62, value: "B" }, { start: 155.62, end: 156.49, value: "E" }, { start: 156.49, end: 156.63, value: "B" }, { start: 156.63, end: 157.19, value: "E" }, { start: 157.19, end: 157.47, value: "L" }, { start: 157.47, end: 157.68, value: "O" }, { start: 157.68, end: 158.18, value: "B" }, { start: 158.18, end: 158.22, value: "B" }, { start: 158.22, end: 159.21, value: "A" }, { start: 159.21, end: 159.83, value: "E" }, { start: 159.83, end: 160.25, value: "C" }, { start: 160.25, end: 160.81, value: "B" }, { start: 160.81, end: 163.32, value: "X" }, { start: 163.32, end: 163.41, value: "B" }, { start: 163.41, end: 163.49, value: "A" }, { start: 163.49, end: 163.68, value: "L" }, { start: 163.68, end: 163.73, value: "F" }, { start: 163.73, end: 163.91, value: "B" }, { start: 163.91, end: 164.12, value: "E" }, { start: 164.12, end: 164.71, value: "D" }, { start: 164.71, end: 164.92, value: "C" }, { start: 164.92, end: 165.13, value: "I" }, { start: 165.13, end: 165.48, value: "D" }, { start: 165.48, end: 165.62, value: "O" }, { start: 165.62, end: 165.90, value: "M" }, { start: 165.90, end: 165.97, value: "C" }, { start: 165.97, end: 167.23, value: "A" }, { start: 167.23, end: 167.68, value: "X" }, { start: 167.68, end: 168.61, value: "A" }, { start: 168.61, end: 168.91, value: "L" }, { start: 168.91, end: 168.98, value: "F" }, { start: 168.98, end: 169.40, value: "B" }, { start: 169.40, end: 169.73, value: "E" }, { start: 169.73, end: 172.56, value: "D" }, { start: 172.56, end: 172.64, value: "C" }, { start: 172.64, end: 173.40, value: "X" }, { start: 173.40, end: 173.53, value: "B" }, { start: 173.53, end: 174.56, value: "U" }, { start: 174.56, end: 174.63, value: "F" }, { start: 174.63, end: 174.70, value: "G" }, { start: 174.70, end: 174.98, value: "E" }, { start: 174.98, end: 175.26, value: "H" }, { start: 175.26, end: 175.68, value: "C" }, { start: 175.68, end: 176.10, value: "B" }, { start: 176.10, end: 176.24, value: "E" }, { start: 176.24, end: 176.45, value: "F" }, { start: 176.45, end: 176.52, value: "B" }, { start: 176.52, end: 177.64, value: "O" }, { start: 177.64, end: 177.85, value: "X" }, { start: 177.85, end: 177.97, value: "C" }, { start: 177.97, end: 178.27, value: "X" }, { start: 178.27, end: 178.40, value: "B" }, { start: 178.40, end: 178.54, value: "A" }, { start: 178.54, end: 178.68, value: "R" }, { start: 178.68, end: 179.17, value: "E" }, { start: 179.17, end: 179.94, value: "B" }, { start: 179.94, end: 181.13, value: "X" }, { start: 181.13, end: 181.25, value: "D" }, { start: 181.25, end: 181.39, value: "B" }, { start: 181.39, end: 181.94, value: "E" }, { start: 181.94, end: 182.33, value: "A" }, { start: 182.33, end: 182.41, value: "B" }, { start: 182.41, end: 182.86, value: "B" }, { start: 182.86, end: 182.98, value: "X" }, { start: 182.98, end: 183.17, value: "B" }, { start: 183.17, end: 183.81, value: "X" }, { start: 183.81, end: 183.96, value: "C" }, { start: 183.96, end: 184.18, value: "A" }, { start: 184.18, end: 184.63, value: "N" }, { start: 184.63, end: 184.71, value: "A" }, { start: 184.71, end: 185.44, value: "B" }, { start: 185.44, end: 186.32, value: "X" }, { start: 186.32, end: 186.49, value: "B" }, { start: 186.49, end: 186.56, value: "A" }, { start: 186.56, end: 186.63, value: "T" }, { start: 186.63, end: 186.78, value: "B" }, { start: 186.78, end: 187.00, value: "A" }, { start: 187.00, end: 188.12, value: "N" }, { start: 188.12, end: 189.10, value: "X" }, { start: 189.10, end: 189.33, value: "N" }, { start: 189.33, end: 189.47, value: "A" }, { start: 189.47, end: 189.66, value: "N" }, { start: 189.66, end: 189.71, value: "A" }, { start: 189.71, end: 190.80, value: "N" }, { start: 190.80, end: 191.22, value: "A" }, { start: 191.22, end: 191.46, value: "N" }, { start: 191.46, end: 191.50, value: "A" }, { start: 191.50, end: 191.54, value: "N" }, { start: 191.54, end: 192.52, value: "A" }, { start: 192.52, end: 192.69, value: "N" }, { start: 192.69, end: 192.83, value: "A" }, { start: 192.83, end: 193.25, value: "B" }, { start: 193.25, end: 193.50, value: "A" }, { start: 193.50, end: 193.71, value: "T" }, { start: 193.71, end: 193.82, value: "B" }, { start: 193.82, end: 194.03, value: "A" }, { start: 194.03, end: 194.09, value: "N" }, { start: 194.09, end: 195.53, value: "B" }, { start: 195.53, end: 195.77, value: "X" }, { start: 195.77, end: 195.95, value: "N" }, { start: 195.95, end: 196.65, value: "A" }, { start: 196.65, end: 198.41, value: "N" }, { start: 198.41, end: 198.66, value: "A" }, { start: 198.66, end: 198.80, value: "N" }, { start: 198.80, end: 199.08, value: "A" }, { start: 199.08, end: 199.15, value: "N" }, { start: 199.15, end: 199.43, value: "A" }, { start: 199.43, end: 199.85, value: "N" }, { start: 199.85, end: 199.99, value: "A" }, { start: 199.99, end: 200.20, value: "N" }, { start: 200.20, end: 200.41, value: "A" }, { start: 200.41, end: 201.15, value: "B" }, { start: 201.15, end: 202.25, value: "A" }, { start: 202.25, end: 202.33, value: "T" }, { start: 202.33, end: 203.09, value: "B" }, { start: 203.09, end: 203.19, value: "A" }, { start: 203.19, end: 203.26, value: "N" }, { start: 203.26, end: 203.33, value: "B" }, { start: 203.33, end: 203.89, value: "A" }, { start: 203.89, end: 206.72, value: "X" }]; // Viseme mapping self.visemeMapping = { 'X': 'closed', 'A': 'AEI', 'B': 'BMP', 'C': 'CDGKNRSTXYZ', 'D': 'AEI', 'E': 'EE', 'F': 'FV', 'G': 'CDGKNRSTXYZ', 'H': 'TH', // Added TH viseme 'L': 'L', // Added L viseme 'N': 'N', // Added N viseme 'O': 'O', // Added O viseme 'Q': 'QW', // Added QW viseme 'U': 'U' // Added U viseme }; self.songStartTime = null; self.isLipSyncActive = false; self.currentLipSyncIndex = 0; // Position tracking variables (keeping your existing setup) var targetX = GAME_WIDTH / 2; var targetY = GAME_HEIGHT / 2 + 200; var smoothingFactor = 0.18; var prevX = null; var prevY = null; // Scale tracking variables (keeping your existing setup) var scaleHistory = new Array(5).fill(1); var scaleIndex = 0; var currentScale = 1; // Rotation variables (keeping your existing setup) var targetTilt = 0; var tiltSmoothingFactor = 0.11; var tiltScaleFactor = 0.09; function calculateFaceTilt() { if (facekit.leftEye && facekit.rightEye && facekit.mouthCenter) { var eyeMidX = (facekit.leftEye.x + facekit.rightEye.x) / 2; var eyeMidY = (facekit.leftEye.y + facekit.rightEye.y) / 2; var dx = facekit.mouthCenter.x - eyeMidX; var dy = facekit.mouthCenter.y - eyeMidY; var angle = -(Math.atan2(dx, dy) * (180 / Math.PI)); return Math.max(-15, Math.min(15, angle * 0.15)); } return 0; } self.startLipSync = function () { self.songStartTime = Date.now(); self.isLipSyncActive = true; self.currentLipSyncIndex = 0; }; self.setViseme = function (visemeName) { if (self.currentViseme !== visemeName && self.visemes[visemeName]) { if (self.visemes[self.currentViseme]) { self.visemes[self.currentViseme].visible = false; } self.visemes[visemeName].visible = true; self.currentViseme = visemeName; } }; self.updateAutomatedLipSync = function () { if (!self.isLipSyncActive || !self.songStartTime || self.automatedLipSync.length === 0) { return; } var currentTimeSeconds = (Date.now() - self.songStartTime) / 1000.0; var targetVisemeKey = 'closed'; // Start searching from currentLipSyncIndex for optimization for (var i = self.currentLipSyncIndex; i < self.automatedLipSync.length; i++) { var cue = self.automatedLipSync[i]; if (currentTimeSeconds >= cue.start && currentTimeSeconds < cue.end) { var automatedCode = cue.value; targetVisemeKey = self.visemeMapping[automatedCode] || 'closed'; self.currentLipSyncIndex = i; break; } if (currentTimeSeconds >= cue.end) { if (i === self.automatedLipSync.length - 1) { var automatedCode = cue.value; targetVisemeKey = self.visemeMapping[automatedCode] || 'closed'; } } else if (currentTimeSeconds < cue.start) { break; } } if (self.currentLipSyncIndex === self.automatedLipSync.length - 1 && currentTimeSeconds >= self.automatedLipSync[self.automatedLipSync.length - 1].end) { targetVisemeKey = 'closed'; } self.setViseme(targetVisemeKey); }; self.update = function () { // Update automated lip sync self.updateAutomatedLipSync(); // Smooth position tracking if (facekit.mouthCenter) { targetX = facekit.mouthCenter.x; targetY = facekit.mouthCenter.y; if (prevX === null) { prevX = targetX; prevY = targetY; } var newX = prevX * (1 - smoothingFactor) + targetX * smoothingFactor; var newY = prevY * (1 - smoothingFactor) + targetY * smoothingFactor; self.x = newX; self.y = newY; prevX = newX; prevY = newY; } // Dynamic scale adjustment if (facekit.leftEye && facekit.rightEye) { var eyeDistance = Math.abs(facekit.rightEye.x - facekit.leftEye.x); var newScale = eyeDistance / 250; scaleHistory[scaleIndex] = newScale; scaleIndex = (scaleIndex + 1) % scaleHistory.length; var avgScale = scaleHistory.reduce(function (a, b) { return a + b; }, 0) / scaleHistory.length; var targetScale = Math.max(0.6, Math.min(1.4, avgScale)); currentScale = currentScale * 0.85 + targetScale * 0.15; Object.keys(self.visemes).forEach(function (key) { self.visemes[key].scaleX = currentScale; self.visemes[key].scaleY = currentScale; }); } // Rotation tracking if (facekit.leftEye && facekit.rightEye) { targetTilt = calculateFaceTilt() * tiltScaleFactor; targetTilt = Math.max(-15, Math.min(15, targetTilt)); self.rotation += (targetTilt - self.rotation) * tiltSmoothingFactor; } }; return self; }); var StartButton = Container.expand(function () { var self = Container.call(this); self.buttonGraphics = self.attachAsset('startButtonImage', { anchorX: 0.5, anchorY: 0.5 }); self.down = function () { // This will call the globally defined startGame function // when the button is pressed. if (typeof handleStartButtonPressed === 'function') { handleStartButtonPressed(); } }; return self; }); var SubtitleDisplay = Container.expand(function () { var self = Container.call(this); // Create text display properties self.currentText = ""; self.textDisplay = null; self.subtitleData = [{ start: 4.5, end: 7.5, text: "From the ashes of tragedy" }, { start: 10.0, end: 12.5, text: "A guardian rises" }, { start: 14.0, end: 16.0, text: "Not the hero they deserve" }, { start: 17.5, end: 19.5, text: "But the one they need" }, { start: 22.5, end: 24.5, text: "Parents fallen in an alley cold" }, { start: 25.0, end: 29.0, text: "A boy's heart turned to stone" }, { start: 33.0, end: 35.0, text: "Fortune built on others' pain" }, { start: 36.0, end: 40.0, text: "Now I claim the night as my own" }, { start: 43.5, end: 46.0, text: "Gotham calls to me in whispers dire" }, { start: 47.0, end: 51.0, text: "Criminals scatter at my arrival" }, { start: 53.5, end: 56.0, text: "The cape becomes my second skin" }, { start: 57.0, end: 62.0, text: "This mask - my true survival" }, // Chorus { start: 63.5, end: 69.0, text: "From the darkness I emerge" }, { start: 75.0, end: 79.0, text: "Justice is what I serve" }, { start: 85.5, end: 90.0, text: "When evil plagues these streets" }, { start: 90.5, end: 92.0, text: "I'll be there" }, { start: 93.5, end: 94.5, text: "I'll be there" }, { start: 96.0, end: 97.0, text: "I'm Batman" }, { start: 98.5, end: 100.0, text: "I'm Batman" }, { start: 101.5, end: 102.5, text: "I'm Batman" }, { start: 103.0, end: 105.0, text: "I'm Batman" }, // Verse 2 { start: 109.0, end: 112.5, text: "A gallery of madness I've faced" }, { start: 114.0, end: 116.5, text: "The Joker with his twisted grin" }, { start: 120.0, end: 123.8, text: "Riddler's puzzles, Penguin's schemes" }, { start: 124.0, end: 128.5, text: "Two-Face's coin, the battle within" }, { start: 130.0, end: 132.0, text: "My allies few, my burden great" }, { start: 133.0, end: 137.0, text: "The cave below, my sanctuary" }, { start: 140.5, end: 144.5, text: "Alfred's wisdom guides my path" }, { start: 145.0, end: 150.5, text: "Through this life so solitary" }, // Chorus repeat { start: 151.5, end: 157.0, text: "From the darkness I emerge" }, { start: 163.5, end: 167.5, text: "Justice is what I serve" }, { start: 173.5, end: 178.0, text: "When evil plagues these streets" }, { start: 178.5, end: 180.0, text: "I'll be there" }, { start: 181.0, end: 182.5, text: "I'll be there" }, { start: 184.0, end: 185.0, text: "I'm Batman" }, { start: 186.5, end: 188.0, text: "I'm Batman" }, { start: 189.0, end: 190.5, text: "I'm Batman" }, { start: 191.5, end: 193.0, text: "I'm Batman" }, // Outro { start: 194.0, end: 197.0, text: "Na na na na na na na na" }, { start: 197.0, end: 200.0, text: "Na na na na na na na na" }, { start: 198.0, end: 198.5, text: "Batman" }, { start: 199.0, end: 202.0, text: "Na na na na na na na na" }, { start: 202.0, end: 205.0, text: "Na na na na na na na na" }, { start: 203.0, end: 204.5, text: "Batman!" }]; self.songStartTime = null; self.isActive = false; self.currentSubtitleIndex = 0; // Text styling properties self.textStyle = { fontFamily: 'Arial', // Font family for Text2 fontSize: 96, // Font size (doubled) // fontWeight: 'bold', // fontWeight is part of font string in Text2 if specific bold font is used color: '#FFFFFF', // Fill color stroke: '#000000', // Stroke color strokeWidth: 3, // Mapped to strokeThickness in Text2 textAlign: 'center' // Mapped to align in Text2 // textBaseline: 'middle', // Handled by anchor in Text2 // shadowColor: '#000000', // Shadow not directly supported by LK Text2 // shadowBlur: 8, // shadowOffsetX: 2, // shadowOffsetY: 2 }; self.createTextGraphics = function (text) { if (self.textDisplay) { // If a previous text object exists self.textDisplay.destroy(); // Destroy it self.textDisplay = null; // Clear the reference } if (!text || text.trim() === "") { // If new text is empty, do nothing further return; } // Create new Text2 object self.textDisplay = new Text2(text, { font: self.textStyle.fontFamily, size: self.textStyle.fontSize, fill: self.textStyle.color, stroke: self.textStyle.stroke, strokeThickness: self.textStyle.strokeWidth, align: self.textStyle.textAlign }); self.textDisplay.anchor.set(0.5, 0.5); // Center the text within its bounds self.addChild(self.textDisplay); // Add the new text display to the container }; self.startSubtitles = function () { self.songStartTime = Date.now(); self.isActive = true; self.currentSubtitleIndex = 0; }; self.updateSubtitles = function () { if (!self.isActive || !self.songStartTime || self.subtitleData.length === 0) { return; } var currentTimeSeconds = (Date.now() - self.songStartTime) / 1000.0; var newText = ""; var foundSubtitle = false; // Find current subtitle // Iterate from the current index for efficiency or from start if reset needed for (var i = self.currentSubtitleIndex; i < self.subtitleData.length; i++) { var subtitle = self.subtitleData[i]; if (currentTimeSeconds >= subtitle.start && currentTimeSeconds < subtitle.end) { newText = subtitle.text; self.currentSubtitleIndex = i; // Keep track of the current subtitle index foundSubtitle = true; break; } // If current time is less than the start of this subtitle, means no future subtitle will match yet. if (currentTimeSeconds < subtitle.start) { break; } // If time has passed this subtitle's end, it might be the one if nothing else matches if (currentTimeSeconds >= subtitle.end) { // If it's the last subtitle and we've passed its start time, it might still be active until next change or song end // This logic clears the subtitle if we've passed its end time and it's not picked up by another. } } // If no subtitle is active for the current time (e.g., between cues), clear the text. if (!foundSubtitle && currentTimeSeconds > 0) { // Check currentTimeSeconds > 0 to avoid clearing at very start var shouldClear = true; // Check if we are before the first subtitle or after the last one if (self.subtitleData.length > 0) { if (currentTimeSeconds < self.subtitleData[0].start) { shouldClear = true; } else { // Check if we are between subtitles for (var k = 0; k < self.subtitleData.length; k++) { if (currentTimeSeconds >= self.subtitleData[k].start && currentTimeSeconds < self.subtitleData[k].end) { shouldClear = false; // We are in a subtitle, don't clear break; } } } } if (shouldClear) { newText = ""; } } // Update display if text changed if (newText !== self.currentText) { self.currentText = newText; self.createTextGraphics(newText); } }; self.setPosition = function (x, y) { self.x = x; self.y = y; }; self.update = function () { self.updateSubtitles(); }; return self; }); /**** * Initialize Game ****/ // Facekit provides the camera feed as background, so no explicit backgroundColor needed. var game = new LK.Game({}); /**** * Game Code ****/ // Game state variables var isGameActive = false; var startButtonInstance; var maskInstance; var mouthAnimatorInstance; var subtitleInstance; var songDuration = 206.72; // Song duration in seconds based on lip sync data var musicStartTime = null; var songCompleted = false; // Game constants var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var AUTOMATED_VISEME_MAPPING = { 'X': 'closed', // Silence/rest position 'A': 'AEI', // Open vowels (father, palm) 'B': 'BMP', // Lip closure (b, m, p) 'C': 'CDGKNRSTXYZ', // Consonants (c, d, g, k, n, r, s, t, x, y, z) 'D': 'AEI', // Mid vowels (day, face) 'E': 'EE', // High front vowels (see, fleece) 'F': 'FV', // Lip-teeth contact (f, v) 'G': 'CDGKNRSTXYZ', // Back consonants (g, ng) 'H': 'CHJSH' // Fricatives (h, ch, j, sh) }; function handleStartButtonPressed() { if (isGameActive) { return; } isGameActive = true; // Remove start button if (startButtonInstance && startButtonInstance.parent) { startButtonInstance.destroy(); startButtonInstance = null; } // Create and show mask maskInstance = new Mask(); game.addChild(maskInstance); var maskGraphicHeight = maskInstance.maskGraphics.height; var yOffset = -50; var maskTargetX, maskTargetY; if (facekit.leftEye && facekit.rightEye && facekit.leftEye.x !== 0 && facekit.rightEye.x !== 0) { maskTargetX = (facekit.leftEye.x + facekit.rightEye.x) / 2; maskTargetY = (facekit.leftEye.y + facekit.rightEye.y) / 2 - maskGraphicHeight * 0.20 + yOffset; } else { maskTargetX = GAME_WIDTH / 2; maskTargetY = GAME_HEIGHT / 3 + yOffset; } // Create mouth animator mouthAnimatorInstance = new MouthAnimator(); var mouthInitialX = facekit.mouthCenter && facekit.mouthCenter.x !== 0 ? facekit.mouthCenter.x : GAME_WIDTH / 2; var mouthInitialY = facekit.mouthCenter && facekit.mouthCenter.y !== 0 ? facekit.mouthCenter.y : GAME_HEIGHT / 2 + 200; mouthAnimatorInstance.x = mouthInitialX; mouthAnimatorInstance.y = mouthInitialY; game.addChild(mouthAnimatorInstance); // Create subtitle display subtitleInstance = new SubtitleDisplay(); subtitleInstance.setPosition(GAME_WIDTH / 2, GAME_HEIGHT - 124 - GAME_HEIGHT * 0.1); // Position moved up 10% from previous position game.addChild(subtitleInstance); // Show mask with callback to start music and lip-sync when animation completes maskInstance.show(maskTargetX, maskTargetY, 1200); // Start music, lip-sync, and subtitles after mask animation completes (1.2 seconds) LK.setTimeout(function () { LK.playMusic('batmansong'); musicStartTime = Date.now(); // Track when music started songCompleted = false; // Reset completion flag if (mouthAnimatorInstance) { // Ensure mouthAnimatorInstance still exists mouthAnimatorInstance.startLipSync(); } if (subtitleInstance) { // Ensure subtitleInstance still exists subtitleInstance.startSubtitles(); } }, 1200); } // Initial game setup function initializeGameScreen() { isGameActive = false; // Reset game state // Create and position the start button startButtonInstance = new StartButton(); // Center the button horizontally startButtonInstance.x = GAME_WIDTH / 2; // Position it towards the bottom of the screen var buttonHeight = startButtonInstance.buttonGraphics.height; startButtonInstance.y = GAME_HEIGHT - buttonHeight / 2 - 100; // 100px padding from bottom game.addChild(startButtonInstance); } // Call initial setup initializeGameScreen(); // Game update loop game.update = function () { if (isGameActive) { // Check if song has completed and stop music if (musicStartTime && !songCompleted) { var currentMusicTime = (Date.now() - musicStartTime) / 1000.0; if (currentMusicTime >= songDuration) { LK.stopMusic(); songCompleted = true; } } // Update mouth animator with enhanced tracking if (mouthAnimatorInstance) { // mouthAnimatorInstance.updateViseme(facekit.volume); // Viseme update is now handled by updateLipSync within MouthAnimator's update mouthAnimatorInstance.update(); // This now includes smooth position tracking, scaling, and lip-sync } // Update mask with enhanced tracking if (maskInstance) { maskInstance.update(); // This now includes smooth position tracking and scaling } if (subtitleInstance) { subtitleInstance.update(); } } };
===================================================================
--- original.js
+++ change.js
@@ -235,9 +235,9 @@
value: "E"
}, {
start: 6.73,
end: 7.43,
- value: "B"
+ value: "E"
}, {
start: 7.43,
end: 9.85,
value: "X"
@@ -279,9 +279,9 @@
value: "C"
}, {
start: 11.62,
end: 12.77,
- value: "B"
+ value: "G"
}, {
start: 12.77,
end: 13.77,
value: "X"
@@ -338,14 +338,14 @@
end: 17.29,
value: "B"
}, {
start: 17.29,
- end: 17.99,
- value: "A"
+ end: 18.99,
+ value: "E"
}, {
- start: 17.99,
+ start: 18.99,
end: 19.53,
- value: "B"
+ value: "G"
}, {
start: 19.53,
end: 22.08,
value: "X"
@@ -435,9 +435,9 @@
value: "C"
}, {
start: 27.71,
end: 28.48,
- value: "B"
+ value: "O"
}, {
start: 28.48,
end: 32.70,
value: "X"
@@ -555,9 +555,9 @@
value: "E"
}, {
start: 38.00,
end: 40.10,
- value: "N"
+ value: "O"
}, {
start: 40.10,
end: 43.42,
value: "X"
@@ -2518,9 +2518,9 @@
// Create text display properties
self.currentText = "";
self.textDisplay = null;
self.subtitleData = [{
- start: 5.5,
+ start: 4.5,
end: 7.5,
text: "From the ashes of tragedy"
}, {
start: 10.0,