/**** * 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) { // --- SCALE CALCULATION --- var eyeDistance = Math.abs(facekit.rightEye.x - facekit.leftEye.x); var newScaleFactor = eyeDistance / 300; // Base scale factor from eye distance scaleHistory[scaleIndex] = newScaleFactor; scaleIndex = (scaleIndex + 1) % scaleHistory.length; var avgScale = scaleHistory.reduce(function (a, b) { return a + b; }, 0) / scaleHistory.length; var targetScaleValue = avgScale; // Smoothed target scale currentScale = currentScale * 0.85 + targetScaleValue * 0.15; // Final smoothed currentScale // --- POSITION CALCULATION WITH Y-COMPENSATION --- var eyeMidX = (facekit.leftEye.x + facekit.rightEye.x) / 2; var eyeMidY = (facekit.leftEye.y + facekit.rightEye.y) / 2; targetX = eyeMidX; // Target X is the midpoint between eyes // Base target Y positions the anchor (0.4 from top of graphic) relative to eyes var baseTargetY = eyeMidY - self.maskGraphics.height * 0.20 - 50; // Y-compensation: lower the mask when below standard scale (< 1.0), raise it slightly when above (> 1.0) var yOffsetCompensation = 0; if (currentScale < 1) { yOffsetCompensation = self.maskGraphics.height * 0.4 * (1 - currentScale); } else if (currentScale > 1) { // Raise mask slightly as it grows above base scale (gentle effect) yOffsetCompensation = -self.maskGraphics.height * 0.08 * (currentScale - 1); } targetY = baseTargetY + yOffsetCompensation; // Final targetY including compensation // Initialize previous positions if not set if (prevX === null) { prevX = targetX; prevY = targetY; } // Weighted average 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; // --- APPLY SCALE TO MASK GRAPHICS --- 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.63, value: "G" }, { start: 6.63, 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: 17.79, value: "N" }, { start: 17.79, 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 = 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.8, 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(); } } };
/****
* 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) {
// --- SCALE CALCULATION ---
var eyeDistance = Math.abs(facekit.rightEye.x - facekit.leftEye.x);
var newScaleFactor = eyeDistance / 300; // Base scale factor from eye distance
scaleHistory[scaleIndex] = newScaleFactor;
scaleIndex = (scaleIndex + 1) % scaleHistory.length;
var avgScale = scaleHistory.reduce(function (a, b) {
return a + b;
}, 0) / scaleHistory.length;
var targetScaleValue = avgScale; // Smoothed target scale
currentScale = currentScale * 0.85 + targetScaleValue * 0.15; // Final smoothed currentScale
// --- POSITION CALCULATION WITH Y-COMPENSATION ---
var eyeMidX = (facekit.leftEye.x + facekit.rightEye.x) / 2;
var eyeMidY = (facekit.leftEye.y + facekit.rightEye.y) / 2;
targetX = eyeMidX; // Target X is the midpoint between eyes
// Base target Y positions the anchor (0.4 from top of graphic) relative to eyes
var baseTargetY = eyeMidY - self.maskGraphics.height * 0.20 - 50;
// Y-compensation: lower the mask when below standard scale (< 1.0), raise it slightly when above (> 1.0)
var yOffsetCompensation = 0;
if (currentScale < 1) {
yOffsetCompensation = self.maskGraphics.height * 0.4 * (1 - currentScale);
} else if (currentScale > 1) {
// Raise mask slightly as it grows above base scale (gentle effect)
yOffsetCompensation = -self.maskGraphics.height * 0.08 * (currentScale - 1);
}
targetY = baseTargetY + yOffsetCompensation; // Final targetY including compensation
// Initialize previous positions if not set
if (prevX === null) {
prevX = targetX;
prevY = targetY;
}
// Weighted average 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;
// --- APPLY SCALE TO MASK GRAPHICS ---
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.63,
value: "G"
}, {
start: 6.63,
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: 17.79,
value: "N"
}, {
start: 17.79,
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 = 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.8,
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();
}
}
};