/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Drum class var Drum = Container.expand(function () { var self = Container.call(this); self.drumType = 'none'; // 'large', 'left', 'right', 'sit', 'stand' self.action = null; // function to call on tap // Set up drum asset self.setDrum = function (type) { self.drumType = type; var assetId = type === 'large' ? 'drum_large' : 'drum_medium'; var drum = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); // (Drum label removed) }; // Drum tap effect self.flash = function () { tween(self, { scaleX: 1.18, scaleY: 0.92 }, { duration: 80, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 120 }); } }); }; // Touch event self.down = function (x, y, obj) { self.flash(); // Play unique sound for each drum type if (self.drumType === 'large') { LK.getSound('drum_tap_center').play(); } else if (self.drumType === 'left') { LK.getSound('drum_tap_left').play(); } else if (self.drumType === 'right') { LK.getSound('drum_tap_right').play(); } else if (self.drumType === 'sit') { var sitSound = LK.getSound('drum_tap_sit'); sitSound.play(); LK.setTimeout(function () { sitSound.play(); }, 120); } else if (self.drumType === 'stand') { // Play the sound with 3 fast delay effects (echoes) var base = LK.getSound('drum_tap_stand'); base.play(); LK.setTimeout(function () { base.play(); }, 45); LK.setTimeout(function () { base.play(); }, 90); } else { // No fallback sound } if (self.action) self.action(); }; return self; }); // Puppet class: articulated body with simple kinematics var Puppet = Container.expand(function () { var self = Container.call(this); // --- Body part sizes (for positioning) --- var headH = 120 * 1.5, headW = 120 * 1.5; var bodyH = 200 * 1.5, bodyW = 80 * 1.5; var upperArmH = 100 * 1.5, upperArmW = 40 * 1.5; var lowerArmH = 90 * 1.5, lowerArmW = 32 * 1.5; var upperLegH = 120 * 1.5, upperLegW = 48 * 1.5; var lowerLegH = 110 * 1.5, lowerLegW = 40 * 1.5; // --- Create body parts --- // Head var head = self.attachAsset('puppet_head', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); // Neck (between head and body, sized to match hip) var neck = self.attachAsset('puppet_neck', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, // match hip scale scaleY: 1.5 }); // Shoulders (left and right) var lShoulder = self.attachAsset('puppet_shoulder', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); var rShoulder = self.attachAsset('puppet_shoulder', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); // Body var body = self.attachAsset('puppet_body', { anchorX: 0.5, anchorY: 0, scaleX: 1.5, scaleY: 1.5 }); // Dual hips (left and right) var lHip = self.attachAsset('puppet_hip', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); var rHip = self.attachAsset('puppet_hip', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); // Left upper arm var lUpperArm = self.attachAsset('puppet_upperarm', { anchorX: 0.5, anchorY: 0, scaleX: 1.5, scaleY: 1.5 }); // Left elbow var lElbow = self.attachAsset('puppet_elbow', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); // Left lower arm var lLowerArm = self.attachAsset('puppet_lowerarm', { anchorX: 0.5, anchorY: 0, scaleX: 1.5, scaleY: 1.5 }); // Right upper arm var rUpperArm = self.attachAsset('puppet_upperarm', { anchorX: 0.5, anchorY: 0, scaleX: 1.5, scaleY: 1.5 }); // Right elbow var rElbow = self.attachAsset('puppet_elbow', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); // Right lower arm var rLowerArm = self.attachAsset('puppet_lowerarm', { anchorX: 0.5, anchorY: 0, scaleX: 1.5, scaleY: 1.5 }); // Left lower leg var lLowerLeg = self.attachAsset('puppet_lowerleg', { anchorX: 0.5, anchorY: 0, scaleX: 1.5, scaleY: 1.5 }); // Right lower leg var rLowerLeg = self.attachAsset('puppet_lowerleg', { anchorX: 0.5, anchorY: 0, scaleX: 1.5, scaleY: 1.5 }); // Left upper leg var lUpperLeg = self.attachAsset('puppet_upperleg', { anchorX: 0.5, anchorY: 0, scaleX: 1.5, scaleY: 1.5 }); // Left knee var lKnee = self.attachAsset('puppet_knee', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); // Right upper leg var rUpperLeg = self.attachAsset('puppet_upperleg', { anchorX: 0.5, anchorY: 0, scaleX: 1.5, scaleY: 1.5 }); // Right knee var rKnee = self.attachAsset('puppet_knee', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); // --- Initial pose state --- self.state = { vx: 0, // horizontal velocity vy: 0, // vertical velocity grounded: false, pose: 'stand', // 'stand', 'sit', 'jump' headAngle: 0, // radians, for "pull head" actions x: 0, // world position y: 0, // --- Ava: Leg rotation controls --- lUpperLegRotation: 0, // additional rotation for left upper leg rUpperLegRotation: 0, // additional rotation for right upper leg lLowerLegRotation: 0, // additional rotation for left lower leg rLowerLegRotation: 0 // additional rotation for right lower leg }; // --- Positioning offsets (relative to puppet center) --- function updateBodyParts() { // --- Sıcrama pose: arms straight up, legs apart, feet apart, body slightly up --- if (self.state.pose === 'sıcrama') { // Head head.x = 0; head.y = -bodyH / 2 - headH / 2 - 30; head.rotation = 0; // Neck var hipH = 44 * 1.5; var neckH = hipH; neck.x = 0; neck.y = -bodyH / 2 - neckH / 2 + 38; neck.rotation = 0; // Body body.x = 0; body.y = -bodyH / 2; body.rotation = 0; // Shoulders var armY = -bodyH / 2 + 30; var armX = bodyW / 2 + upperArmW / 2 - 10; var lShoulderX = -armX; var lShoulderY = armY; var rShoulderX = armX; var rShoulderY = armY; lShoulder.x = lShoulderX; lShoulder.y = lShoulderY; rShoulder.x = rShoulderX; rShoulder.y = rShoulderY; // Arms straight up var lUpperArmAngle = -Math.PI / 2; var rUpperArmAngle = -Math.PI / 2; var lLowerArmAngle = 0; var rLowerArmAngle = 0; // Left arm lUpperArm.x = lShoulderX; lUpperArm.y = lShoulderY; var lElbowX = lUpperArm.x + Math.sin(lUpperArmAngle) * upperArmH; var lElbowY = lUpperArm.y + Math.cos(lUpperArmAngle) * upperArmH; lElbow.x = lElbowX; lElbow.y = lElbowY; lUpperArm.rotation = Math.atan2(lElbow.y - lUpperArm.y, lElbow.x - lUpperArm.x) - Math.PI / 2; lLowerArm.x = lElbowX; lLowerArm.y = lElbowY; lLowerArm.rotation = lLowerArmAngle; // Right arm rUpperArm.x = rShoulderX; rUpperArm.y = rShoulderY; var rElbowX = rUpperArm.x + Math.sin(rUpperArmAngle) * upperArmH; var rElbowY = rUpperArm.y + Math.cos(rUpperArmAngle) * upperArmH; rElbow.x = rElbowX; rElbow.y = rElbowY; rUpperArm.rotation = Math.atan2(rElbow.y - rUpperArm.y, rElbow.x - rUpperArm.x) - Math.PI / 2; rLowerArm.x = rElbowX; rLowerArm.y = rElbowY; rLowerArm.rotation = rLowerArmAngle; // Hips var legY = bodyH / 2 - 10; var legX = bodyW / 2 - 18; var lHipX = -legX - 30; // more apart var lHipY = legY + 10; // slightly lower var rHipX = legX + 30; // more apart var rHipY = legY + 10; lHip.x = lHipX; lHip.y = lHipY; rHip.x = rHipX; rHip.y = rHipY; // Upper legs: angled outward var lUpperLegAngle = Math.PI / 7; // ~25 deg var rUpperLegAngle = -Math.PI / 7; // Knees var lKneeX = lHip.x + Math.sin(lUpperLegAngle) * upperLegH; var lKneeY = lHip.y + Math.cos(lUpperLegAngle) * upperLegH; var rKneeX = rHip.x + Math.sin(rUpperLegAngle) * upperLegH; var rKneeY = rHip.y + Math.cos(rUpperLegAngle) * upperLegH; lKnee.x = lKneeX; lKnee.y = lKneeY; rKnee.x = rKneeX; rKnee.y = rKneeY; // Upper leg rotations lUpperLeg.x = lHip.x; lUpperLeg.y = lHip.y; lUpperLeg.rotation = Math.atan2(lKnee.y - lUpperLeg.y, lKnee.x - lUpperLeg.x) - Math.PI / 2; rUpperLeg.x = rHip.x; rUpperLeg.y = rHip.y; rUpperLeg.rotation = Math.atan2(rKnee.y - rUpperLeg.y, rKnee.x - rUpperLeg.x) - Math.PI / 2; // Lower legs: straight down from knees, feet apart var lowerLegH_px = 110 * 1.5; // Left lower leg lLowerLeg.x = lKnee.x; lLowerLeg.y = lKnee.y; var lAnkleX = lKnee.x - 18; var lAnkleY = lKnee.y + lowerLegH_px; lLowerLeg.rotation = Math.atan2(lAnkleY - lKnee.y, lAnkleX - lKnee.x) - Math.PI / 2; // Right lower leg rLowerLeg.x = rKnee.x; rLowerLeg.y = rKnee.y; var rAnkleX = rKnee.x + 18; var rAnkleY = rKnee.y + lowerLegH_px; rLowerLeg.rotation = Math.atan2(rAnkleY - rKnee.y, rAnkleX - rKnee.x) - Math.PI / 2; // Body stays upright body.rotation = 0; // Return early so other poses don't override return; } // Head head.x = 0; head.y = -bodyH / 2 - headH / 2 - 30; head.rotation = self.state.headAngle; // Neck var hipH = 44 * 1.5; var neckH = hipH; neck.x = 0; neck.y = -bodyH / 2 - neckH / 2 + 38; neck.rotation = 0; // Body body.x = 0; body.y = -bodyH / 2; // --- Symmetric Arms --- var armY = -bodyH / 2 + 30; var armX = bodyW / 2 + upperArmW / 2 - 10; // Shoulders var lShoulderX = -armX; var lShoulderY = armY; var rShoulderX = armX; var rShoulderY = armY; lShoulder.x = lShoulderX; lShoulder.y = lShoulderY; rShoulder.x = rShoulderX; rShoulder.y = rShoulderY; // Default symmetric arm pose var armAbduction = 0.32; var baseUpperArmAngle = 0.0; var baseLowerArmAngle = 0.0; var lUpperArmAngle = -armAbduction + baseUpperArmAngle; var rUpperArmAngle = armAbduction + baseUpperArmAngle; var lLowerArmAngle = baseLowerArmAngle; var rLowerArmAngle = baseLowerArmAngle; // Symmetric pose-based arm animation if (self.state.pose === 'sit') { lUpperArmAngle = -1.25 - armAbduction; rUpperArmAngle = 1.25 + armAbduction; lLowerArmAngle = 1.5; rLowerArmAngle = -1.5; } else if (self.state.pose === 'jump') { lUpperArmAngle = -1.6 - armAbduction; rUpperArmAngle = 1.6 + armAbduction; lLowerArmAngle = 2.0; rLowerArmAngle = -2.0; } else if (self.state.pose === 'clap') { // Arms up and together for clapping if (self._clapArms) { // Arms together (clap) lUpperArmAngle = -2.1; rUpperArmAngle = 2.1; lLowerArmAngle = 2.7; rLowerArmAngle = -2.7; } else { // Arms up, apart (pre/post clap) lUpperArmAngle = -1.7; rUpperArmAngle = 1.7; lLowerArmAngle = 2.0; rLowerArmAngle = -2.0; } } else if (self.state.pose === 'saga_uzat') { // Right arm extended to the right, left arm relaxed down lUpperArmAngle = -Math.PI / 2; // left arm straight down rUpperArmAngle = Math.PI / 2; // right arm straight right lLowerArmAngle = 0; rLowerArmAngle = 0; } else if (self.state.pose === 'stand') { lUpperArmAngle += self.state.vx * 0.01; rUpperArmAngle -= self.state.vx * 0.01; lLowerArmAngle += Math.sin(Date.now() * 0.003) * 0.08; rLowerArmAngle -= Math.sin(Date.now() * 0.003) * 0.08; } // Left arm lUpperArm.x = lShoulderX; lUpperArm.y = lShoulderY; var lElbowX = lUpperArm.x + Math.sin(lUpperArmAngle) * upperArmH; var lElbowY = lUpperArm.y + Math.cos(lUpperArmAngle) * upperArmH; lElbow.x = lElbowX; lElbow.y = lElbowY; lUpperArm.rotation = Math.atan2(lElbow.y - lUpperArm.y, lElbow.x - lUpperArm.x) - Math.PI / 2; lLowerArm.x = lElbowX; lLowerArm.y = lElbowY; lLowerArm.rotation = lLowerArmAngle; // Right arm (mirror of left) rUpperArm.x = rShoulderX; rUpperArm.y = rShoulderY; var rElbowX = rUpperArm.x + Math.sin(rUpperArmAngle) * upperArmH; var rElbowY = rUpperArm.y + Math.cos(rUpperArmAngle) * upperArmH; rElbow.x = rElbowX; rElbow.y = rElbowY; rUpperArm.rotation = Math.atan2(rElbow.y - rUpperArm.y, rElbow.x - rUpperArm.x) - Math.PI / 2; rLowerArm.x = rElbowX; rLowerArm.y = rElbowY; rLowerArm.rotation = rLowerArmAngle; // --- Symmetric Legs --- var legY = bodyH / 2 - 10; var legX = bodyW / 2 - 18; // Hips var lHipX = -legX; var lHipY = legY; var rHipX = legX; var rHipY = legY; lHip.x = lHipX; lHip.y = lHipY; rHip.x = rHipX; rHip.y = rHipY; // Default symmetric leg pose var legAbduction = 0.28; // Ava: Reduce outward angle of lower legs in stand pose (bring lower legs even closer together) var lowerLegOutAngle = 0.16; var lUpperLegAngle = legAbduction + (self.state.lUpperLegRotation || 0); var rUpperLegAngle = -legAbduction + (self.state.rUpperLegRotation || 0); var lLowerLegAngle = lowerLegOutAngle + (self.state.lLowerLegRotation || 0); var rLowerLegAngle = -lowerLegOutAngle + (self.state.rLowerLegRotation || 0); // Symmetric pose-based leg animation if (self.state.pose === 'sit' || self.state.pose === 'sit_mirror') { // Parallel (not cross-legged) sit pose // Hips lUpperLeg.x = lHip.x; lUpperLeg.y = lHip.y; rUpperLeg.x = rHip.x; rUpperLeg.y = rHip.y; // Thighs angled more outward, knees forward var sitThighAngle = 0.55; // radians, larger outward angle for open thighs var sitKneeForward = upperLegH * 0.95; // how far forward knees are from hips // Mirror logic: swap left/right for sit_mirror var mirror = self.state.pose === 'sit_mirror' ? -1 : 1; // Left knee var lKneeX = lUpperLeg.x + Math.sin(-sitThighAngle * mirror) * sitKneeForward; var lKneeY = lUpperLeg.y + Math.cos(-sitThighAngle * mirror) * sitKneeForward; // Right knee var rKneeX = rUpperLeg.x + Math.sin(sitThighAngle * mirror) * sitKneeForward; var rKneeY = rUpperLeg.y + Math.cos(sitThighAngle * mirror) * sitKneeForward; // Knees at same y var avgKneeY = (lKneeY + rKneeY) / 2; lKneeY = avgKneeY; rKneeY = avgKneeY; // Set knee positions if (lKnee.parent) lKnee.parent.removeChild(lKnee); if (rKnee.parent) rKnee.parent.removeChild(rKnee); lKnee.x = lKneeX; lKnee.y = lKneeY; rKnee.x = rKneeX; rKnee.y = rKneeY; // Upper leg rotations lUpperLeg.rotation = Math.atan2(lKnee.y - lUpperLeg.y, lKnee.x - lUpperLeg.x) - Math.PI / 2; rUpperLeg.rotation = Math.atan2(rKnee.y - rUpperLeg.y, rKnee.x - rUpperLeg.x) - Math.PI / 2; // Lower legs: straight down from knees, feet on ground var groundY = 2732 - 80; var lowerLegH_px = 110 * 1.5; // Left lower leg lLowerLeg.x = lKnee.x; lLowerLeg.y = lKnee.y; var lDeltaY = groundY - lLowerLeg.y; var lAngle = Math.acos(Math.max(-1, Math.min(1, lDeltaY / lowerLegH_px))) - Math.PI / 2; lLowerLeg.rotation = lAngle; // Right lower leg rLowerLeg.x = rKnee.x; rLowerLeg.y = rKnee.y; var rDeltaY = groundY - rLowerLeg.y; var rAngle = Math.acos(Math.max(-1, Math.min(1, rDeltaY / lowerLegH_px))) - Math.PI / 2; // Ava: Rotate right lower leg to the left in sit pose, and mirror for sit_mirror rLowerLeg.rotation = rAngle - 0.55 * mirror; // Now set knee positions and re-add to ensure they are on top self.addChild(lKnee); self.addChild(rKnee); body.rotation = 0; // --- Arms: for sit_mirror, open arms to sides, for sit, keep as before --- if (self.state.pose === 'sit_mirror') { // Arms open to sides, horizontally var armY = -bodyH / 2 + 30; var armX = bodyW / 2 + upperArmW / 2 - 10; var lShoulderX = -armX; var lShoulderY = armY; var rShoulderX = armX; var rShoulderY = armY; lShoulder.x = lShoulderX; lShoulder.y = lShoulderY; rShoulder.x = rShoulderX; rShoulder.y = rShoulderY; // Arms straight out to sides var lUpperArmAngle = -Math.PI / 2; var rUpperArmAngle = Math.PI / 2; var lLowerArmAngle = 0; var rLowerArmAngle = 0; // Left arm lUpperArm.x = lShoulderX; lUpperArm.y = lShoulderY; var lElbowX = lUpperArm.x + Math.sin(lUpperArmAngle) * upperArmH; var lElbowY = lUpperArm.y + Math.cos(lUpperArmAngle) * upperArmH; lElbow.x = lElbowX; lElbow.y = lElbowY; lUpperArm.rotation = Math.atan2(lElbow.y - lUpperArm.y, lElbow.x - lUpperArm.x) - Math.PI / 2; lLowerArm.x = lElbowX; lLowerArm.y = lElbowY; lLowerArm.rotation = lLowerArmAngle; // Right arm rUpperArm.x = rShoulderX; rUpperArm.y = rShoulderY; var rElbowX = rUpperArm.x + Math.sin(rUpperArmAngle) * upperArmH; var rElbowY = rUpperArm.y + Math.cos(rUpperArmAngle) * upperArmH; rElbow.x = rElbowX; rElbow.y = rElbowY; rUpperArm.rotation = Math.atan2(rElbow.y - rUpperArm.y, rElbow.x - rUpperArm.x) - Math.PI / 2; rLowerArm.x = rElbowX; rLowerArm.y = rElbowY; rLowerArm.rotation = rLowerArmAngle; } } else if (self.state.pose === 'jump') { // Both upper legs up and slightly inward, knees together, feet up, using IK // --- Keep knees and upper legs connected in jump pose --- // --- Make both legs perfectly horizontally aligned in jump pose --- var jumpThighAngle = 0.45; var jumpKneeBend = 1.25; // radians, how much the knee is bent (smaller = straighter, larger = more bent) // Set both hips at same y, and both knees at same y, for perfect horizontal alignment // Calculate the average x between hips for knee position, and set a fixed y offset for knees var avgHipY = (lHip.y + rHip.y) / 2; var avgHipX = (lHip.x + rHip.x) / 2; var kneeYOffset = -upperLegH * Math.cos(jumpThighAngle); // vertical offset from hips to knees var kneeY = avgHipY + kneeYOffset; var kneeXLeft = lHip.x + Math.sin(-jumpThighAngle - legAbduction) * upperLegH; var kneeXRight = rHip.x + Math.sin(jumpThighAngle + legAbduction) * upperLegH; // Both knees at same y lKnee.x = kneeXLeft; lKnee.y = kneeY; rKnee.x = kneeXRight; rKnee.y = kneeY; // Upper legs: hips to knees lUpperLeg.x = lHip.x; lUpperLeg.y = lHip.y; lUpperLeg.rotation = Math.atan2(lKnee.y - lUpperLeg.y, lKnee.x - lUpperLeg.x) - Math.PI / 2; rUpperLeg.x = rHip.x; rUpperLeg.y = rHip.y; rUpperLeg.rotation = Math.atan2(rKnee.y - rUpperLeg.y, rKnee.x - rUpperLeg.x) - Math.PI / 2; // Lower legs: knees to ankles, both at same y // Left lLowerLeg.x = lKnee.x; lLowerLeg.y = lKnee.y; var lJumpShinLen = lowerLegH; var lJumpAnkleX = lKnee.x + Math.sin(lUpperLeg.rotation + Math.PI / 2 + jumpKneeBend) * lJumpShinLen; var lJumpAnkleY = lKnee.y + Math.cos(lUpperLeg.rotation + Math.PI / 2 + jumpKneeBend) * lJumpShinLen; lLowerLeg.rotation = Math.atan2(lJumpAnkleY - lKnee.y, lJumpAnkleX - lKnee.x) - Math.PI / 2; // Right rLowerLeg.x = rKnee.x; rLowerLeg.y = rKnee.y; var rJumpShinLen = lowerLegH; var rJumpAnkleX = rKnee.x + Math.sin(rUpperLeg.rotation + Math.PI / 2 + jumpKneeBend) * rJumpShinLen; var rJumpAnkleY = rKnee.y + Math.cos(rUpperLeg.rotation + Math.PI / 2 + jumpKneeBend) * rJumpShinLen; rLowerLeg.rotation = Math.atan2(rJumpAnkleY - rKnee.y, rJumpAnkleX - rKnee.x) - Math.PI / 2; // Elbows more bent in jump lLowerArm.x = lElbow.x; lLowerArm.y = lElbow.y; lLowerArm.rotation = 2.0; rLowerArm.x = rElbow.x; rLowerArm.y = rElbow.y; rLowerArm.rotation = -2.0; body.rotation = 0; } else if (self.state.pose === 'stand') { // Ava: Increase outward angle of upper thighs in stand pose (make thighs more open) var standThighAbduction = 0.32; // increased from 0.18 for more outward angle // SWAP: swap left and right upper leg abduction lUpperLegAngle -= standThighAbduction; rUpperLegAngle += standThighAbduction; // Gentle walk cycle if moving var walk = Math.abs(self.state.vx) > 0.5 ? Math.sin(Date.now() * 0.008) * 0.25 : 0; lUpperLegAngle += walk; rUpperLegAngle -= walk; lLowerLegAngle += walk * 0.5; rLowerLegAngle -= walk * 0.5; // Left leg lUpperLeg.x = lHip.x; lUpperLeg.y = lHip.y; var lKneeX = lUpperLeg.x + Math.sin(lUpperLegAngle) * upperLegH; var lKneeY = lUpperLeg.y + Math.cos(lUpperLegAngle) * upperLegH; lKnee.x = lKneeX; lKnee.y = lKneeY; lUpperLeg.rotation = Math.atan2(lKnee.y - lUpperLeg.y, lKnee.x - lUpperLeg.x) - Math.PI / 2; // SWAP: Attach left lower leg using right lower leg's angle lLowerLeg.x = lKneeX; lLowerLeg.y = lKneeY; var lStandShinLen = lowerLegH; var lStandAnkleX = lKneeX + Math.sin(rLowerLegAngle) * lStandShinLen; var lStandAnkleY = lKneeY + Math.cos(rLowerLegAngle) * lStandShinLen; lLowerLeg.rotation = Math.atan2(lStandAnkleY - lKneeY, lStandAnkleX - lKneeX) - Math.PI / 2; // Right leg (mirror) rUpperLeg.x = rHip.x; rUpperLeg.y = rHip.y; var rKneeX = rUpperLeg.x + Math.sin(rUpperLegAngle) * upperLegH; var rKneeY = rUpperLeg.y + Math.cos(rUpperLegAngle) * upperLegH; rKnee.x = rKneeX; rKnee.y = rKneeY; rUpperLeg.rotation = Math.atan2(rKnee.y - rUpperLeg.y, rKnee.x - rUpperLeg.x) - Math.PI / 2; // SWAP: Attach right lower leg using left lower leg's angle rLowerLeg.x = rKneeX; rLowerLeg.y = rKneeY; var rStandShinLen = lowerLegH; var rStandAnkleX = rKneeX + Math.sin(lLowerLegAngle) * rStandShinLen; var rStandAnkleY = rKneeY + Math.cos(lLowerLegAngle) * rStandShinLen; rLowerLeg.rotation = Math.atan2(rStandAnkleY - rKneeY, rStandAnkleX - rKneeX) - Math.PI / 2; body.rotation = 0; } } // --- Public puppet actions --- // Add saga_uzat pose: right arm extended to the right, left arm relaxed down self.saga_uzat = function () { if (self.state.grounded && self.state.pose !== 'saga_uzat') { self.state.pose = 'saga_uzat'; LK.setTimeout(function () { self.stand(); }, 600); } }; self.pullHeadLeft = function () { // Pull head left, add leftward velocity if (self.state.grounded) { self.state.vx -= 12; self.state.headAngle = -0.5; tween(self.state, { headAngle: 0 }, { duration: 400, easing: tween.easeOut }); } }; self.pullHeadRight = function () { if (self.state.grounded) { self.state.vx += 12; self.state.headAngle = 0.5; tween(self.state, { headAngle: 0 }, { duration: 400, easing: tween.easeOut }); } }; self.jump = function () { if (self.state.grounded) { self.state.vy = -48; // Increased jump velocity for higher jump self.state.grounded = false; self.state.pose = 'jump'; tween(self.state, {}, { duration: 0, onFinish: function onFinish() { // After 400ms, return to stand pose LK.setTimeout(function () { self.state.pose = 'stand'; }, 400); } }); } }; self.sit = function () { if (self.state.grounded && self.state.pose !== 'sit' && self.state.pose !== 'sit_mirror') { // Randomly choose between 'sit' and 'sit_mirror' var poses = ['sit', 'sit_mirror']; var idx = Math.floor(Math.random() * poses.length); self.state.pose = poses[idx]; // After 600ms, stand up automatically LK.setTimeout(function () { self.stand(); }, 600); } }; // Sıcrama: random trigger like sit self.sıcrama = function () { if (self.state.grounded && self.state.pose !== 'sıcrama') { // Only play if not already in sıcrama self.state.pose = 'sıcrama'; // After 600ms, stand up automatically LK.setTimeout(function () { self.stand(); }, 600); } }; self.stand = function () { if (self.state.grounded && self.state.pose !== 'stand') { self.state.pose = 'stand'; } }; // --- Physics update --- self.update = function () { // --- Track last position for event triggers --- if (typeof self.lastX === "undefined") self.lastX = self.state.x; if (typeof self.lastY === "undefined") self.lastY = self.state.y; // --- Physics update --- self.state.vy += 3.2; // gravity self.state.x += self.state.vx; self.state.y += self.state.vy; // Friction if (self.state.grounded) { self.state.vx *= 0.82; } else { self.state.vx *= 0.98; } // Clamp to world bounds if (self.state.x < 0) self.state.x = 0; if (self.state.x > 2048) self.state.x = 2048; // Ground collision var groundY = 2732 - 80; var puppetFeetOffset = bodyH / 2 + upperLegH + lowerLegH - 20; var feetY = self.state.y + puppetFeetOffset; if (feetY >= groundY) { self.state.y = groundY - puppetFeetOffset; self.state.vy = 0; self.state.grounded = true; if (self.state.pose === 'jump') { self.state.pose = 'stand'; } } else { self.state.grounded = false; } // Apply to container (keep puppet at current position) self.x = self.state.x; self.y = self.state.y; updateBodyParts(); // --- Update lastX, lastY for next frame --- self.lastX = self.state.x; self.lastY = self.state.y; }; // --- Start position --- self.state.x = 600; // Moved puppet further right (was 180) // --- Ava: Set initial Y so feet are above ground visually --- // Move puppet slightly higher (raise by 40px) var puppetFeetOffset = bodyH / 2 + upperLegH + lowerLegH - 20; self.state.y = 2732 - 80 - puppetFeetOffset - 40; // Defensive: initialize lastX, lastY for event triggers self.lastX = self.state.x; self.lastY = self.state.y; updateBodyParts(); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // --- Background music playlist (4 tracks, play in random order) --- // --- World setup --- // yellow // blue // pink // green // orange // purple // teal // Wide stereo coin: left and right panned variants function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } var musicTracks = [{ id: 'musicId', volume: 0.1, start: 0, end: 1 }, { id: 'musicId2', volume: 0.1, start: 0, end: 1 }, { id: 'musicId3', volume: 0.1, start: 0, end: 1 }, { id: 'musicId4', volume: 0.1, start: 0, end: 1 }]; // Shuffle helper (Fisher-Yates) function shuffleArray(arr) { for (var i = arr.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } // Maintain a shuffled playlist order var shuffledMusicOrder = []; function reshuffleMusicOrder() { shuffledMusicOrder = []; for (var i = 0; i < musicTracks.length; ++i) shuffledMusicOrder.push(i); shuffleArray(shuffledMusicOrder); } reshuffleMusicOrder(); var currentMusicOrderIndex = 0; // Play the next music track in the shuffled order, and keep playing forever function playNextMusic() { // If we've played all, reshuffle for a new random order if (currentMusicOrderIndex >= shuffledMusicOrder.length) { reshuffleMusicOrder(); currentMusicOrderIndex = 0; } var trackIndex = shuffledMusicOrder[currentMusicOrderIndex]; var track = musicTracks[trackIndex]; // Play music and set up onFinish to play the next one LK.playMusic(track.id, { loop: false, volume: track.volume, start: track.start, end: track.end, onFinish: function onFinish() { currentMusicOrderIndex++; playNextMusic(); } }); } playNextMusic(); game.setBackgroundColor(0x000000); // --- Background --- // Ava: Add background image behind all elements var background = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, scaleX: 1, scaleY: 1 }); game.addChild(background); // Ground var ground = LK.getAsset('ground', { anchorX: 0, anchorY: 0 }); ground.x = 0; ground.y = 2732 - 80; game.addChild(ground); // Finish sign var finishSign = LK.getAsset('finish_sign', { anchorX: 0.5, anchorY: 1, scaleX: 3, scaleY: 6 // 2x as tall as before }); finishSign.x = 2048 - 120; finishSign.y = 2732 - 80; game.addChild(finishSign); // --- Puppet --- var puppet = new Puppet(); puppet.lastX = puppet.state.x; puppet.lastY = puppet.state.y; game.addChild(puppet); // --- Drums --- var drums = []; // Drum positions (relative to center) // Move drums higher: set drumCenterY above puppet's head var drumCenterX = 2048 / 2, drumCenterY = 900; // was 2732 / 2 + 220, now much higher (puppet head is around y=~1200) var drumRadius = 220 * 3; var angleList = [{ type: 'left', angle: -Math.PI / 2 - 0.92 // increased spacing (was 0.7) }, { type: 'right', angle: -Math.PI / 2 + 0.92 // increased spacing (was 0.7) }, { type: 'sit', angle: Math.PI / 2 + 0.7 }, { type: 'stand', angle: Math.PI / 2 - 0.7 }]; // --- Drum action pool --- // Each drum will have all actions, only their order will be shuffled // Ava: Increase the number of right movement actions (pullHeadRight) in the action pool var puppetActions = [function () { puppet.pullHeadLeft(); }, function () { puppet.pullHeadRight(); }, function () { puppet.pullHeadRight(); }, function () { puppet.jump(); }, function () { puppet.sit(); }, function () { puppet.sıcrama(); }, function () { puppet.stand(); }]; // Large center drum (fixed action sequence) var drumJump = new Drum(); drumJump.setDrum('large'); drumJump.x = drumCenterX; drumJump.y = drumCenterY; // Assign a fixed sequence of all 5 actions, and cycle through them drumJump._actionSequence = puppetActions.slice(); drumJump._actionIndex = 0; drumJump.action = function () { drumJump._actionSequence[drumJump._actionIndex](); drumJump._actionIndex = (drumJump._actionIndex + 1) % drumJump._actionSequence.length; }; game.addChild(drumJump); drums.push(drumJump); // Four medium drums for (var i = 0; i < angleList.length; ++i) { var d = new Drum(); d.setDrum(angleList[i].type); d.x = drumCenterX + Math.cos(angleList[i].angle) * drumRadius; d.y = drumCenterY + Math.sin(angleList[i].angle) * drumRadius; // Each drum gets a fixed sequence of all 5 actions, and cycles through them d._actionSequence = puppetActions.slice(); d._actionIndex = 0; d.action = function (drum) { return function () { drum._actionSequence[drum._actionIndex](); drum._actionIndex = (drum._actionIndex + 1) % drum._actionSequence.length; }; }(d); game.addChild(d); drums.push(d); } // --- Value Circles in Drums --- var valueCircleValues = [1, 3, 5, 10, 25, 50, 100]; var valueCircleAssets = { 1: 'circle_1', 3: 'circle_3', 5: 'circle_5', 10: 'circle_10', 25: 'circle_25', 50: 'circle_50', 100: 'circle_100' }; var valueCircleTexts = {}; var currentValueCircle = null; var currentValueDrum = null; // Helper: Remove current value circle from drum function removeValueCircle() { if (currentValueCircle && currentValueDrum) { if (currentValueCircle.parent) currentValueCircle.parent.removeChild(currentValueCircle); if (valueCircleTexts[currentValueCircle._circleValue]) { if (valueCircleTexts[currentValueCircle._circleValue].parent) { valueCircleTexts[currentValueCircle._circleValue].parent.removeChild(valueCircleTexts[currentValueCircle._circleValue]); } } currentValueCircle = null; currentValueDrum = null; } } // Helper: Spawn a value circle in a random drum (not the same as last) function spawnValueCircle() { removeValueCircle(); // Pick a random drum var drumIdx = Math.floor(Math.random() * drums.length); // Pick a random value var valueIdx = Math.floor(Math.random() * valueCircleValues.length); var value = valueCircleValues[valueIdx]; var assetId = valueCircleAssets[value]; var drum = drums[drumIdx]; // Create the circle asset var circle = LK.getAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); circle.alpha = 0.5; circle.x = drum.x; // If this is the center drum, spawn the value circle slightly above the drum center if (drum.drumType === 'large') { circle.y = drum.y - 90; } else { circle.y = drum.y; } circle._circleValue = value; // Create the text if not already created if (!valueCircleTexts[value]) { var txt = new Text2("" + value, { size: 180, fill: 0x222222 }); txt.anchor.set(0.5, 0.5); valueCircleTexts[value] = txt; } var txt = valueCircleTexts[value]; txt.x = drum.x; if (drum.drumType === 'large') { txt.y = drum.y - 90; } else { txt.y = drum.y; } // Add to game game.addChild(circle); game.addChild(txt); currentValueCircle = circle; currentValueDrum = drum; } // Timer: spawn every 1s var valueCircleTimer = LK.setInterval(function () { if (gameEnded) return; spawnValueCircle(); }, 1000); // Patch all drum actions to also spawn a new value circle in a different drum for (var i = 0; i < drums.length; ++i) { (function (drum) { var oldAction = drum.action; drum.action = function () { // If this drum is the one with the value circle, add its value to second score and main score if (!gameEnded && currentValueDrum === drum && currentValueCircle && typeof currentValueCircle._circleValue === "number") { secondScore += currentValueCircle._circleValue; secondScoreText.setText(formatSecondScore(secondScore)); // Add to main score as well LK.setScore(LK.getScore() + currentValueCircle._circleValue); // Play wide stereo coin sound LK.getSound('coin_left').play(); LK.getSound('coin_right').play(); // --- Add colored glow effect around the value circle --- // Pick color based on value var glowColor = 0xffe066; // default yellow if (currentValueCircle._circleValue === 1) glowColor = 0xffe066;else if (currentValueCircle._circleValue === 3) glowColor = 0x66c2ff;else if (currentValueCircle._circleValue === 5) glowColor = 0xff66a3;else if (currentValueCircle._circleValue === 10) glowColor = 0x7cff66;else if (currentValueCircle._circleValue === 25) glowColor = 0xffa366;else if (currentValueCircle._circleValue === 50) glowColor = 0xb366ff;else if (currentValueCircle._circleValue === 100) glowColor = 0x66ffd9; // Create a glow circle (slightly larger, fully transparent at start) var glow = LK.getAsset(valueCircleAssets[currentValueCircle._circleValue], { anchorX: 0.5, anchorY: 0.5, scaleX: 1.35, scaleY: 1.35 }); glow.x = currentValueCircle.x; glow.y = currentValueCircle.y; glow.alpha = 0.0; glow.tint = glowColor; // Add to game, behind the value circle if (currentValueCircle.parent) { var parent = currentValueCircle.parent; var idx = parent.getChildIndex(currentValueCircle); parent.addChildAt(glow, Math.max(0, idx)); } else { game.addChild(glow); } // Animate the glow: fade in and out, and scale up a bit tween(glow, { alpha: 0.7, scaleX: 1.7, scaleY: 1.7 }, { duration: 120, onFinish: function onFinish() { tween(glow, { alpha: 0.0, scaleX: 2.0, scaleY: 2.0 }, { duration: 320, onFinish: function onFinish() { if (glow.parent) glow.parent.removeChild(glow); } }); } }); } // --- Puppet arms up clapping animation --- // Only trigger clapping if a point is collected AND value is 100 if (!gameEnded && currentValueDrum === drum && currentValueCircle && typeof currentValueCircle._circleValue === "number" && currentValueCircle._circleValue === 100 && _typeof(puppet) === "object" && puppet.state && puppet.state.pose !== "clap") { // Save previous pose to restore after clapping var prevPose = puppet.state.pose; puppet.state.pose = "clap"; // Animate arms up and clapping motion // We'll use a tween to create a quick up-down-up movement var clapCount = 2; var clapDuration = 120; var totalClapTime = clapCount * 2 * clapDuration; var _clapTween = function clapTween(count) { if (count <= 0) { // Restore previous pose after clapping puppet.state.pose = prevPose === "clap" ? "stand" : prevPose; return; } // Arms together (clap) puppet._clapArms = true; tween(puppet, {}, { duration: clapDuration, onFinish: function onFinish() { // Arms apart puppet._clapArms = false; tween(puppet, {}, { duration: clapDuration, onFinish: function onFinish() { _clapTween(count - 1); } }); } }); }; _clapTween(clapCount); } if (typeof oldAction === "function") oldAction(); // Only respawn if not ended if (!gameEnded) { // Only respawn if this drum is not already the current one var prevDrum = currentValueDrum; var prevCircle = currentValueCircle; // Remove current value circle removeValueCircle(); // Pick a new drum (not the same as this one) var availableDrums = drums.filter(function (d) { return d !== drum; }); var drumIdx = Math.floor(Math.random() * availableDrums.length); var newDrum = availableDrums[drumIdx]; // Pick a random value var valueIdx = Math.floor(Math.random() * valueCircleValues.length); var value = valueCircleValues[valueIdx]; var assetId = valueCircleAssets[value]; // Create the circle asset var circle = LK.getAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); circle.alpha = 0.5; circle.x = newDrum.x; // If this is the center drum, spawn the value circle slightly above the drum center if (newDrum.drumType === 'large') { circle.y = newDrum.y - 90; } else { circle.y = newDrum.y; } circle._circleValue = value; // Create the text if not already created if (!valueCircleTexts[value]) { var txt = new Text2("" + value, { size: 180, fill: 0x222222 }); txt.anchor.set(0.5, 0.5); valueCircleTexts[value] = txt; } var txt = valueCircleTexts[value]; txt.x = newDrum.x; if (newDrum.drumType === 'large') { txt.y = newDrum.y - 90; } else { txt.y = newDrum.y; } // Add to game game.addChild(circle); game.addChild(txt); currentValueCircle = circle; currentValueDrum = newDrum; } }; })(drums[i]); } var timeLeft = 60; function formatTimeDigital(secs) { var m = Math.floor(secs / 60); var s = secs % 60; var mm = m < 10 ? "0" + m : "" + m; var ss = s < 10 ? "0" + s : "" + s; return mm + ":" + ss; } var timerText = new Text2(formatTimeDigital(60), { size: 120, fill: '#ffffff' }); timerText.anchor.set(0.5, 0); LK.gui.top.addChild(timerText); // --- Second Score Display (below timer) --- var secondScore = 0; function formatSecondScore(val) { var s = "" + Math.floor(val); while (s.length < 5) s = "0" + s; return s; } var secondScoreText = new Text2(formatSecondScore(0), { size: 160, fill: '#ffe066' }); secondScoreText.anchor.set(0.5, 0); // Position just below timerText (timerText is at top center, so y=timerText.height) secondScoreText.y = timerText.height + 10; LK.gui.top.addChild(secondScoreText); // --- Score/Win state --- var gameEnded = false; // --- Timer logic --- var timerInterval = LK.setInterval(function () { if (gameEnded) return; timeLeft -= 1; if (timeLeft < 0) timeLeft = 0; timerText.setText(formatTimeDigital(timeLeft)); if (timeLeft === 0 && !gameEnded) { gameEnded = true; LK.setScore(0); // Set final score to 0 if time runs out LK.getSound('lose').play(); LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); } }, 1000); // --- Game update --- game.update = function () { if (gameEnded) return; puppet.update(); // Win condition: puppet body collides with finish sign using intersects if (!gameEnded && puppet.intersects(finishSign)) { gameEnded = true; // Add remaining time * 10 to final score var finalScore = secondScore + timeLeft * 10; LK.setScore(finalScore); // Set score to finalScore as final score LK.getSound('win').play(); LK.effects.flashScreen(0x44c767, 800); LK.showYouWin(); } }; // --- Clean up on game over --- game.onDestroy = function () { LK.clearInterval(timerInterval); }; // --- UX: drums are always on top --- for (var i = 0; i < drums.length; ++i) { game.addChild(drums[i]); }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Drum class
var Drum = Container.expand(function () {
var self = Container.call(this);
self.drumType = 'none'; // 'large', 'left', 'right', 'sit', 'stand'
self.action = null; // function to call on tap
// Set up drum asset
self.setDrum = function (type) {
self.drumType = type;
var assetId = type === 'large' ? 'drum_large' : 'drum_medium';
var drum = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// (Drum label removed)
};
// Drum tap effect
self.flash = function () {
tween(self, {
scaleX: 1.18,
scaleY: 0.92
}, {
duration: 80,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 120
});
}
});
};
// Touch event
self.down = function (x, y, obj) {
self.flash();
// Play unique sound for each drum type
if (self.drumType === 'large') {
LK.getSound('drum_tap_center').play();
} else if (self.drumType === 'left') {
LK.getSound('drum_tap_left').play();
} else if (self.drumType === 'right') {
LK.getSound('drum_tap_right').play();
} else if (self.drumType === 'sit') {
var sitSound = LK.getSound('drum_tap_sit');
sitSound.play();
LK.setTimeout(function () {
sitSound.play();
}, 120);
} else if (self.drumType === 'stand') {
// Play the sound with 3 fast delay effects (echoes)
var base = LK.getSound('drum_tap_stand');
base.play();
LK.setTimeout(function () {
base.play();
}, 45);
LK.setTimeout(function () {
base.play();
}, 90);
} else {
// No fallback sound
}
if (self.action) self.action();
};
return self;
});
// Puppet class: articulated body with simple kinematics
var Puppet = Container.expand(function () {
var self = Container.call(this);
// --- Body part sizes (for positioning) ---
var headH = 120 * 1.5,
headW = 120 * 1.5;
var bodyH = 200 * 1.5,
bodyW = 80 * 1.5;
var upperArmH = 100 * 1.5,
upperArmW = 40 * 1.5;
var lowerArmH = 90 * 1.5,
lowerArmW = 32 * 1.5;
var upperLegH = 120 * 1.5,
upperLegW = 48 * 1.5;
var lowerLegH = 110 * 1.5,
lowerLegW = 40 * 1.5;
// --- Create body parts ---
// Head
var head = self.attachAsset('puppet_head', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// Neck (between head and body, sized to match hip)
var neck = self.attachAsset('puppet_neck', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
// match hip scale
scaleY: 1.5
});
// Shoulders (left and right)
var lShoulder = self.attachAsset('puppet_shoulder', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
var rShoulder = self.attachAsset('puppet_shoulder', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// Body
var body = self.attachAsset('puppet_body', {
anchorX: 0.5,
anchorY: 0,
scaleX: 1.5,
scaleY: 1.5
});
// Dual hips (left and right)
var lHip = self.attachAsset('puppet_hip', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
var rHip = self.attachAsset('puppet_hip', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// Left upper arm
var lUpperArm = self.attachAsset('puppet_upperarm', {
anchorX: 0.5,
anchorY: 0,
scaleX: 1.5,
scaleY: 1.5
});
// Left elbow
var lElbow = self.attachAsset('puppet_elbow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// Left lower arm
var lLowerArm = self.attachAsset('puppet_lowerarm', {
anchorX: 0.5,
anchorY: 0,
scaleX: 1.5,
scaleY: 1.5
});
// Right upper arm
var rUpperArm = self.attachAsset('puppet_upperarm', {
anchorX: 0.5,
anchorY: 0,
scaleX: 1.5,
scaleY: 1.5
});
// Right elbow
var rElbow = self.attachAsset('puppet_elbow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// Right lower arm
var rLowerArm = self.attachAsset('puppet_lowerarm', {
anchorX: 0.5,
anchorY: 0,
scaleX: 1.5,
scaleY: 1.5
});
// Left lower leg
var lLowerLeg = self.attachAsset('puppet_lowerleg', {
anchorX: 0.5,
anchorY: 0,
scaleX: 1.5,
scaleY: 1.5
});
// Right lower leg
var rLowerLeg = self.attachAsset('puppet_lowerleg', {
anchorX: 0.5,
anchorY: 0,
scaleX: 1.5,
scaleY: 1.5
});
// Left upper leg
var lUpperLeg = self.attachAsset('puppet_upperleg', {
anchorX: 0.5,
anchorY: 0,
scaleX: 1.5,
scaleY: 1.5
});
// Left knee
var lKnee = self.attachAsset('puppet_knee', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// Right upper leg
var rUpperLeg = self.attachAsset('puppet_upperleg', {
anchorX: 0.5,
anchorY: 0,
scaleX: 1.5,
scaleY: 1.5
});
// Right knee
var rKnee = self.attachAsset('puppet_knee', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// --- Initial pose state ---
self.state = {
vx: 0,
// horizontal velocity
vy: 0,
// vertical velocity
grounded: false,
pose: 'stand',
// 'stand', 'sit', 'jump'
headAngle: 0,
// radians, for "pull head" actions
x: 0,
// world position
y: 0,
// --- Ava: Leg rotation controls ---
lUpperLegRotation: 0,
// additional rotation for left upper leg
rUpperLegRotation: 0,
// additional rotation for right upper leg
lLowerLegRotation: 0,
// additional rotation for left lower leg
rLowerLegRotation: 0 // additional rotation for right lower leg
};
// --- Positioning offsets (relative to puppet center) ---
function updateBodyParts() {
// --- Sıcrama pose: arms straight up, legs apart, feet apart, body slightly up ---
if (self.state.pose === 'sıcrama') {
// Head
head.x = 0;
head.y = -bodyH / 2 - headH / 2 - 30;
head.rotation = 0;
// Neck
var hipH = 44 * 1.5;
var neckH = hipH;
neck.x = 0;
neck.y = -bodyH / 2 - neckH / 2 + 38;
neck.rotation = 0;
// Body
body.x = 0;
body.y = -bodyH / 2;
body.rotation = 0;
// Shoulders
var armY = -bodyH / 2 + 30;
var armX = bodyW / 2 + upperArmW / 2 - 10;
var lShoulderX = -armX;
var lShoulderY = armY;
var rShoulderX = armX;
var rShoulderY = armY;
lShoulder.x = lShoulderX;
lShoulder.y = lShoulderY;
rShoulder.x = rShoulderX;
rShoulder.y = rShoulderY;
// Arms straight up
var lUpperArmAngle = -Math.PI / 2;
var rUpperArmAngle = -Math.PI / 2;
var lLowerArmAngle = 0;
var rLowerArmAngle = 0;
// Left arm
lUpperArm.x = lShoulderX;
lUpperArm.y = lShoulderY;
var lElbowX = lUpperArm.x + Math.sin(lUpperArmAngle) * upperArmH;
var lElbowY = lUpperArm.y + Math.cos(lUpperArmAngle) * upperArmH;
lElbow.x = lElbowX;
lElbow.y = lElbowY;
lUpperArm.rotation = Math.atan2(lElbow.y - lUpperArm.y, lElbow.x - lUpperArm.x) - Math.PI / 2;
lLowerArm.x = lElbowX;
lLowerArm.y = lElbowY;
lLowerArm.rotation = lLowerArmAngle;
// Right arm
rUpperArm.x = rShoulderX;
rUpperArm.y = rShoulderY;
var rElbowX = rUpperArm.x + Math.sin(rUpperArmAngle) * upperArmH;
var rElbowY = rUpperArm.y + Math.cos(rUpperArmAngle) * upperArmH;
rElbow.x = rElbowX;
rElbow.y = rElbowY;
rUpperArm.rotation = Math.atan2(rElbow.y - rUpperArm.y, rElbow.x - rUpperArm.x) - Math.PI / 2;
rLowerArm.x = rElbowX;
rLowerArm.y = rElbowY;
rLowerArm.rotation = rLowerArmAngle;
// Hips
var legY = bodyH / 2 - 10;
var legX = bodyW / 2 - 18;
var lHipX = -legX - 30; // more apart
var lHipY = legY + 10; // slightly lower
var rHipX = legX + 30; // more apart
var rHipY = legY + 10;
lHip.x = lHipX;
lHip.y = lHipY;
rHip.x = rHipX;
rHip.y = rHipY;
// Upper legs: angled outward
var lUpperLegAngle = Math.PI / 7; // ~25 deg
var rUpperLegAngle = -Math.PI / 7;
// Knees
var lKneeX = lHip.x + Math.sin(lUpperLegAngle) * upperLegH;
var lKneeY = lHip.y + Math.cos(lUpperLegAngle) * upperLegH;
var rKneeX = rHip.x + Math.sin(rUpperLegAngle) * upperLegH;
var rKneeY = rHip.y + Math.cos(rUpperLegAngle) * upperLegH;
lKnee.x = lKneeX;
lKnee.y = lKneeY;
rKnee.x = rKneeX;
rKnee.y = rKneeY;
// Upper leg rotations
lUpperLeg.x = lHip.x;
lUpperLeg.y = lHip.y;
lUpperLeg.rotation = Math.atan2(lKnee.y - lUpperLeg.y, lKnee.x - lUpperLeg.x) - Math.PI / 2;
rUpperLeg.x = rHip.x;
rUpperLeg.y = rHip.y;
rUpperLeg.rotation = Math.atan2(rKnee.y - rUpperLeg.y, rKnee.x - rUpperLeg.x) - Math.PI / 2;
// Lower legs: straight down from knees, feet apart
var lowerLegH_px = 110 * 1.5;
// Left lower leg
lLowerLeg.x = lKnee.x;
lLowerLeg.y = lKnee.y;
var lAnkleX = lKnee.x - 18;
var lAnkleY = lKnee.y + lowerLegH_px;
lLowerLeg.rotation = Math.atan2(lAnkleY - lKnee.y, lAnkleX - lKnee.x) - Math.PI / 2;
// Right lower leg
rLowerLeg.x = rKnee.x;
rLowerLeg.y = rKnee.y;
var rAnkleX = rKnee.x + 18;
var rAnkleY = rKnee.y + lowerLegH_px;
rLowerLeg.rotation = Math.atan2(rAnkleY - rKnee.y, rAnkleX - rKnee.x) - Math.PI / 2;
// Body stays upright
body.rotation = 0;
// Return early so other poses don't override
return;
}
// Head
head.x = 0;
head.y = -bodyH / 2 - headH / 2 - 30;
head.rotation = self.state.headAngle;
// Neck
var hipH = 44 * 1.5;
var neckH = hipH;
neck.x = 0;
neck.y = -bodyH / 2 - neckH / 2 + 38;
neck.rotation = 0;
// Body
body.x = 0;
body.y = -bodyH / 2;
// --- Symmetric Arms ---
var armY = -bodyH / 2 + 30;
var armX = bodyW / 2 + upperArmW / 2 - 10;
// Shoulders
var lShoulderX = -armX;
var lShoulderY = armY;
var rShoulderX = armX;
var rShoulderY = armY;
lShoulder.x = lShoulderX;
lShoulder.y = lShoulderY;
rShoulder.x = rShoulderX;
rShoulder.y = rShoulderY;
// Default symmetric arm pose
var armAbduction = 0.32;
var baseUpperArmAngle = 0.0;
var baseLowerArmAngle = 0.0;
var lUpperArmAngle = -armAbduction + baseUpperArmAngle;
var rUpperArmAngle = armAbduction + baseUpperArmAngle;
var lLowerArmAngle = baseLowerArmAngle;
var rLowerArmAngle = baseLowerArmAngle;
// Symmetric pose-based arm animation
if (self.state.pose === 'sit') {
lUpperArmAngle = -1.25 - armAbduction;
rUpperArmAngle = 1.25 + armAbduction;
lLowerArmAngle = 1.5;
rLowerArmAngle = -1.5;
} else if (self.state.pose === 'jump') {
lUpperArmAngle = -1.6 - armAbduction;
rUpperArmAngle = 1.6 + armAbduction;
lLowerArmAngle = 2.0;
rLowerArmAngle = -2.0;
} else if (self.state.pose === 'clap') {
// Arms up and together for clapping
if (self._clapArms) {
// Arms together (clap)
lUpperArmAngle = -2.1;
rUpperArmAngle = 2.1;
lLowerArmAngle = 2.7;
rLowerArmAngle = -2.7;
} else {
// Arms up, apart (pre/post clap)
lUpperArmAngle = -1.7;
rUpperArmAngle = 1.7;
lLowerArmAngle = 2.0;
rLowerArmAngle = -2.0;
}
} else if (self.state.pose === 'saga_uzat') {
// Right arm extended to the right, left arm relaxed down
lUpperArmAngle = -Math.PI / 2; // left arm straight down
rUpperArmAngle = Math.PI / 2; // right arm straight right
lLowerArmAngle = 0;
rLowerArmAngle = 0;
} else if (self.state.pose === 'stand') {
lUpperArmAngle += self.state.vx * 0.01;
rUpperArmAngle -= self.state.vx * 0.01;
lLowerArmAngle += Math.sin(Date.now() * 0.003) * 0.08;
rLowerArmAngle -= Math.sin(Date.now() * 0.003) * 0.08;
}
// Left arm
lUpperArm.x = lShoulderX;
lUpperArm.y = lShoulderY;
var lElbowX = lUpperArm.x + Math.sin(lUpperArmAngle) * upperArmH;
var lElbowY = lUpperArm.y + Math.cos(lUpperArmAngle) * upperArmH;
lElbow.x = lElbowX;
lElbow.y = lElbowY;
lUpperArm.rotation = Math.atan2(lElbow.y - lUpperArm.y, lElbow.x - lUpperArm.x) - Math.PI / 2;
lLowerArm.x = lElbowX;
lLowerArm.y = lElbowY;
lLowerArm.rotation = lLowerArmAngle;
// Right arm (mirror of left)
rUpperArm.x = rShoulderX;
rUpperArm.y = rShoulderY;
var rElbowX = rUpperArm.x + Math.sin(rUpperArmAngle) * upperArmH;
var rElbowY = rUpperArm.y + Math.cos(rUpperArmAngle) * upperArmH;
rElbow.x = rElbowX;
rElbow.y = rElbowY;
rUpperArm.rotation = Math.atan2(rElbow.y - rUpperArm.y, rElbow.x - rUpperArm.x) - Math.PI / 2;
rLowerArm.x = rElbowX;
rLowerArm.y = rElbowY;
rLowerArm.rotation = rLowerArmAngle;
// --- Symmetric Legs ---
var legY = bodyH / 2 - 10;
var legX = bodyW / 2 - 18;
// Hips
var lHipX = -legX;
var lHipY = legY;
var rHipX = legX;
var rHipY = legY;
lHip.x = lHipX;
lHip.y = lHipY;
rHip.x = rHipX;
rHip.y = rHipY;
// Default symmetric leg pose
var legAbduction = 0.28;
// Ava: Reduce outward angle of lower legs in stand pose (bring lower legs even closer together)
var lowerLegOutAngle = 0.16;
var lUpperLegAngle = legAbduction + (self.state.lUpperLegRotation || 0);
var rUpperLegAngle = -legAbduction + (self.state.rUpperLegRotation || 0);
var lLowerLegAngle = lowerLegOutAngle + (self.state.lLowerLegRotation || 0);
var rLowerLegAngle = -lowerLegOutAngle + (self.state.rLowerLegRotation || 0);
// Symmetric pose-based leg animation
if (self.state.pose === 'sit' || self.state.pose === 'sit_mirror') {
// Parallel (not cross-legged) sit pose
// Hips
lUpperLeg.x = lHip.x;
lUpperLeg.y = lHip.y;
rUpperLeg.x = rHip.x;
rUpperLeg.y = rHip.y;
// Thighs angled more outward, knees forward
var sitThighAngle = 0.55; // radians, larger outward angle for open thighs
var sitKneeForward = upperLegH * 0.95; // how far forward knees are from hips
// Mirror logic: swap left/right for sit_mirror
var mirror = self.state.pose === 'sit_mirror' ? -1 : 1;
// Left knee
var lKneeX = lUpperLeg.x + Math.sin(-sitThighAngle * mirror) * sitKneeForward;
var lKneeY = lUpperLeg.y + Math.cos(-sitThighAngle * mirror) * sitKneeForward;
// Right knee
var rKneeX = rUpperLeg.x + Math.sin(sitThighAngle * mirror) * sitKneeForward;
var rKneeY = rUpperLeg.y + Math.cos(sitThighAngle * mirror) * sitKneeForward;
// Knees at same y
var avgKneeY = (lKneeY + rKneeY) / 2;
lKneeY = avgKneeY;
rKneeY = avgKneeY;
// Set knee positions
if (lKnee.parent) lKnee.parent.removeChild(lKnee);
if (rKnee.parent) rKnee.parent.removeChild(rKnee);
lKnee.x = lKneeX;
lKnee.y = lKneeY;
rKnee.x = rKneeX;
rKnee.y = rKneeY;
// Upper leg rotations
lUpperLeg.rotation = Math.atan2(lKnee.y - lUpperLeg.y, lKnee.x - lUpperLeg.x) - Math.PI / 2;
rUpperLeg.rotation = Math.atan2(rKnee.y - rUpperLeg.y, rKnee.x - rUpperLeg.x) - Math.PI / 2;
// Lower legs: straight down from knees, feet on ground
var groundY = 2732 - 80;
var lowerLegH_px = 110 * 1.5;
// Left lower leg
lLowerLeg.x = lKnee.x;
lLowerLeg.y = lKnee.y;
var lDeltaY = groundY - lLowerLeg.y;
var lAngle = Math.acos(Math.max(-1, Math.min(1, lDeltaY / lowerLegH_px))) - Math.PI / 2;
lLowerLeg.rotation = lAngle;
// Right lower leg
rLowerLeg.x = rKnee.x;
rLowerLeg.y = rKnee.y;
var rDeltaY = groundY - rLowerLeg.y;
var rAngle = Math.acos(Math.max(-1, Math.min(1, rDeltaY / lowerLegH_px))) - Math.PI / 2;
// Ava: Rotate right lower leg to the left in sit pose, and mirror for sit_mirror
rLowerLeg.rotation = rAngle - 0.55 * mirror;
// Now set knee positions and re-add to ensure they are on top
self.addChild(lKnee);
self.addChild(rKnee);
body.rotation = 0;
// --- Arms: for sit_mirror, open arms to sides, for sit, keep as before ---
if (self.state.pose === 'sit_mirror') {
// Arms open to sides, horizontally
var armY = -bodyH / 2 + 30;
var armX = bodyW / 2 + upperArmW / 2 - 10;
var lShoulderX = -armX;
var lShoulderY = armY;
var rShoulderX = armX;
var rShoulderY = armY;
lShoulder.x = lShoulderX;
lShoulder.y = lShoulderY;
rShoulder.x = rShoulderX;
rShoulder.y = rShoulderY;
// Arms straight out to sides
var lUpperArmAngle = -Math.PI / 2;
var rUpperArmAngle = Math.PI / 2;
var lLowerArmAngle = 0;
var rLowerArmAngle = 0;
// Left arm
lUpperArm.x = lShoulderX;
lUpperArm.y = lShoulderY;
var lElbowX = lUpperArm.x + Math.sin(lUpperArmAngle) * upperArmH;
var lElbowY = lUpperArm.y + Math.cos(lUpperArmAngle) * upperArmH;
lElbow.x = lElbowX;
lElbow.y = lElbowY;
lUpperArm.rotation = Math.atan2(lElbow.y - lUpperArm.y, lElbow.x - lUpperArm.x) - Math.PI / 2;
lLowerArm.x = lElbowX;
lLowerArm.y = lElbowY;
lLowerArm.rotation = lLowerArmAngle;
// Right arm
rUpperArm.x = rShoulderX;
rUpperArm.y = rShoulderY;
var rElbowX = rUpperArm.x + Math.sin(rUpperArmAngle) * upperArmH;
var rElbowY = rUpperArm.y + Math.cos(rUpperArmAngle) * upperArmH;
rElbow.x = rElbowX;
rElbow.y = rElbowY;
rUpperArm.rotation = Math.atan2(rElbow.y - rUpperArm.y, rElbow.x - rUpperArm.x) - Math.PI / 2;
rLowerArm.x = rElbowX;
rLowerArm.y = rElbowY;
rLowerArm.rotation = rLowerArmAngle;
}
} else if (self.state.pose === 'jump') {
// Both upper legs up and slightly inward, knees together, feet up, using IK
// --- Keep knees and upper legs connected in jump pose ---
// --- Make both legs perfectly horizontally aligned in jump pose ---
var jumpThighAngle = 0.45;
var jumpKneeBend = 1.25; // radians, how much the knee is bent (smaller = straighter, larger = more bent)
// Set both hips at same y, and both knees at same y, for perfect horizontal alignment
// Calculate the average x between hips for knee position, and set a fixed y offset for knees
var avgHipY = (lHip.y + rHip.y) / 2;
var avgHipX = (lHip.x + rHip.x) / 2;
var kneeYOffset = -upperLegH * Math.cos(jumpThighAngle); // vertical offset from hips to knees
var kneeY = avgHipY + kneeYOffset;
var kneeXLeft = lHip.x + Math.sin(-jumpThighAngle - legAbduction) * upperLegH;
var kneeXRight = rHip.x + Math.sin(jumpThighAngle + legAbduction) * upperLegH;
// Both knees at same y
lKnee.x = kneeXLeft;
lKnee.y = kneeY;
rKnee.x = kneeXRight;
rKnee.y = kneeY;
// Upper legs: hips to knees
lUpperLeg.x = lHip.x;
lUpperLeg.y = lHip.y;
lUpperLeg.rotation = Math.atan2(lKnee.y - lUpperLeg.y, lKnee.x - lUpperLeg.x) - Math.PI / 2;
rUpperLeg.x = rHip.x;
rUpperLeg.y = rHip.y;
rUpperLeg.rotation = Math.atan2(rKnee.y - rUpperLeg.y, rKnee.x - rUpperLeg.x) - Math.PI / 2;
// Lower legs: knees to ankles, both at same y
// Left
lLowerLeg.x = lKnee.x;
lLowerLeg.y = lKnee.y;
var lJumpShinLen = lowerLegH;
var lJumpAnkleX = lKnee.x + Math.sin(lUpperLeg.rotation + Math.PI / 2 + jumpKneeBend) * lJumpShinLen;
var lJumpAnkleY = lKnee.y + Math.cos(lUpperLeg.rotation + Math.PI / 2 + jumpKneeBend) * lJumpShinLen;
lLowerLeg.rotation = Math.atan2(lJumpAnkleY - lKnee.y, lJumpAnkleX - lKnee.x) - Math.PI / 2;
// Right
rLowerLeg.x = rKnee.x;
rLowerLeg.y = rKnee.y;
var rJumpShinLen = lowerLegH;
var rJumpAnkleX = rKnee.x + Math.sin(rUpperLeg.rotation + Math.PI / 2 + jumpKneeBend) * rJumpShinLen;
var rJumpAnkleY = rKnee.y + Math.cos(rUpperLeg.rotation + Math.PI / 2 + jumpKneeBend) * rJumpShinLen;
rLowerLeg.rotation = Math.atan2(rJumpAnkleY - rKnee.y, rJumpAnkleX - rKnee.x) - Math.PI / 2;
// Elbows more bent in jump
lLowerArm.x = lElbow.x;
lLowerArm.y = lElbow.y;
lLowerArm.rotation = 2.0;
rLowerArm.x = rElbow.x;
rLowerArm.y = rElbow.y;
rLowerArm.rotation = -2.0;
body.rotation = 0;
} else if (self.state.pose === 'stand') {
// Ava: Increase outward angle of upper thighs in stand pose (make thighs more open)
var standThighAbduction = 0.32; // increased from 0.18 for more outward angle
// SWAP: swap left and right upper leg abduction
lUpperLegAngle -= standThighAbduction;
rUpperLegAngle += standThighAbduction;
// Gentle walk cycle if moving
var walk = Math.abs(self.state.vx) > 0.5 ? Math.sin(Date.now() * 0.008) * 0.25 : 0;
lUpperLegAngle += walk;
rUpperLegAngle -= walk;
lLowerLegAngle += walk * 0.5;
rLowerLegAngle -= walk * 0.5;
// Left leg
lUpperLeg.x = lHip.x;
lUpperLeg.y = lHip.y;
var lKneeX = lUpperLeg.x + Math.sin(lUpperLegAngle) * upperLegH;
var lKneeY = lUpperLeg.y + Math.cos(lUpperLegAngle) * upperLegH;
lKnee.x = lKneeX;
lKnee.y = lKneeY;
lUpperLeg.rotation = Math.atan2(lKnee.y - lUpperLeg.y, lKnee.x - lUpperLeg.x) - Math.PI / 2;
// SWAP: Attach left lower leg using right lower leg's angle
lLowerLeg.x = lKneeX;
lLowerLeg.y = lKneeY;
var lStandShinLen = lowerLegH;
var lStandAnkleX = lKneeX + Math.sin(rLowerLegAngle) * lStandShinLen;
var lStandAnkleY = lKneeY + Math.cos(rLowerLegAngle) * lStandShinLen;
lLowerLeg.rotation = Math.atan2(lStandAnkleY - lKneeY, lStandAnkleX - lKneeX) - Math.PI / 2;
// Right leg (mirror)
rUpperLeg.x = rHip.x;
rUpperLeg.y = rHip.y;
var rKneeX = rUpperLeg.x + Math.sin(rUpperLegAngle) * upperLegH;
var rKneeY = rUpperLeg.y + Math.cos(rUpperLegAngle) * upperLegH;
rKnee.x = rKneeX;
rKnee.y = rKneeY;
rUpperLeg.rotation = Math.atan2(rKnee.y - rUpperLeg.y, rKnee.x - rUpperLeg.x) - Math.PI / 2;
// SWAP: Attach right lower leg using left lower leg's angle
rLowerLeg.x = rKneeX;
rLowerLeg.y = rKneeY;
var rStandShinLen = lowerLegH;
var rStandAnkleX = rKneeX + Math.sin(lLowerLegAngle) * rStandShinLen;
var rStandAnkleY = rKneeY + Math.cos(lLowerLegAngle) * rStandShinLen;
rLowerLeg.rotation = Math.atan2(rStandAnkleY - rKneeY, rStandAnkleX - rKneeX) - Math.PI / 2;
body.rotation = 0;
}
}
// --- Public puppet actions ---
// Add saga_uzat pose: right arm extended to the right, left arm relaxed down
self.saga_uzat = function () {
if (self.state.grounded && self.state.pose !== 'saga_uzat') {
self.state.pose = 'saga_uzat';
LK.setTimeout(function () {
self.stand();
}, 600);
}
};
self.pullHeadLeft = function () {
// Pull head left, add leftward velocity
if (self.state.grounded) {
self.state.vx -= 12;
self.state.headAngle = -0.5;
tween(self.state, {
headAngle: 0
}, {
duration: 400,
easing: tween.easeOut
});
}
};
self.pullHeadRight = function () {
if (self.state.grounded) {
self.state.vx += 12;
self.state.headAngle = 0.5;
tween(self.state, {
headAngle: 0
}, {
duration: 400,
easing: tween.easeOut
});
}
};
self.jump = function () {
if (self.state.grounded) {
self.state.vy = -48; // Increased jump velocity for higher jump
self.state.grounded = false;
self.state.pose = 'jump';
tween(self.state, {}, {
duration: 0,
onFinish: function onFinish() {
// After 400ms, return to stand pose
LK.setTimeout(function () {
self.state.pose = 'stand';
}, 400);
}
});
}
};
self.sit = function () {
if (self.state.grounded && self.state.pose !== 'sit' && self.state.pose !== 'sit_mirror') {
// Randomly choose between 'sit' and 'sit_mirror'
var poses = ['sit', 'sit_mirror'];
var idx = Math.floor(Math.random() * poses.length);
self.state.pose = poses[idx];
// After 600ms, stand up automatically
LK.setTimeout(function () {
self.stand();
}, 600);
}
};
// Sıcrama: random trigger like sit
self.sıcrama = function () {
if (self.state.grounded && self.state.pose !== 'sıcrama') {
// Only play if not already in sıcrama
self.state.pose = 'sıcrama';
// After 600ms, stand up automatically
LK.setTimeout(function () {
self.stand();
}, 600);
}
};
self.stand = function () {
if (self.state.grounded && self.state.pose !== 'stand') {
self.state.pose = 'stand';
}
};
// --- Physics update ---
self.update = function () {
// --- Track last position for event triggers ---
if (typeof self.lastX === "undefined") self.lastX = self.state.x;
if (typeof self.lastY === "undefined") self.lastY = self.state.y;
// --- Physics update ---
self.state.vy += 3.2; // gravity
self.state.x += self.state.vx;
self.state.y += self.state.vy;
// Friction
if (self.state.grounded) {
self.state.vx *= 0.82;
} else {
self.state.vx *= 0.98;
}
// Clamp to world bounds
if (self.state.x < 0) self.state.x = 0;
if (self.state.x > 2048) self.state.x = 2048;
// Ground collision
var groundY = 2732 - 80;
var puppetFeetOffset = bodyH / 2 + upperLegH + lowerLegH - 20;
var feetY = self.state.y + puppetFeetOffset;
if (feetY >= groundY) {
self.state.y = groundY - puppetFeetOffset;
self.state.vy = 0;
self.state.grounded = true;
if (self.state.pose === 'jump') {
self.state.pose = 'stand';
}
} else {
self.state.grounded = false;
}
// Apply to container (keep puppet at current position)
self.x = self.state.x;
self.y = self.state.y;
updateBodyParts();
// --- Update lastX, lastY for next frame ---
self.lastX = self.state.x;
self.lastY = self.state.y;
};
// --- Start position ---
self.state.x = 600; // Moved puppet further right (was 180)
// --- Ava: Set initial Y so feet are above ground visually ---
// Move puppet slightly higher (raise by 40px)
var puppetFeetOffset = bodyH / 2 + upperLegH + lowerLegH - 20;
self.state.y = 2732 - 80 - puppetFeetOffset - 40;
// Defensive: initialize lastX, lastY for event triggers
self.lastX = self.state.x;
self.lastY = self.state.y;
updateBodyParts();
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// --- Background music playlist (4 tracks, play in random order) ---
// --- World setup ---
// yellow
// blue
// pink
// green
// orange
// purple
// teal
// Wide stereo coin: left and right panned variants
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
var musicTracks = [{
id: 'musicId',
volume: 0.1,
start: 0,
end: 1
}, {
id: 'musicId2',
volume: 0.1,
start: 0,
end: 1
}, {
id: 'musicId3',
volume: 0.1,
start: 0,
end: 1
}, {
id: 'musicId4',
volume: 0.1,
start: 0,
end: 1
}];
// Shuffle helper (Fisher-Yates)
function shuffleArray(arr) {
for (var i = arr.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// Maintain a shuffled playlist order
var shuffledMusicOrder = [];
function reshuffleMusicOrder() {
shuffledMusicOrder = [];
for (var i = 0; i < musicTracks.length; ++i) shuffledMusicOrder.push(i);
shuffleArray(shuffledMusicOrder);
}
reshuffleMusicOrder();
var currentMusicOrderIndex = 0;
// Play the next music track in the shuffled order, and keep playing forever
function playNextMusic() {
// If we've played all, reshuffle for a new random order
if (currentMusicOrderIndex >= shuffledMusicOrder.length) {
reshuffleMusicOrder();
currentMusicOrderIndex = 0;
}
var trackIndex = shuffledMusicOrder[currentMusicOrderIndex];
var track = musicTracks[trackIndex];
// Play music and set up onFinish to play the next one
LK.playMusic(track.id, {
loop: false,
volume: track.volume,
start: track.start,
end: track.end,
onFinish: function onFinish() {
currentMusicOrderIndex++;
playNextMusic();
}
});
}
playNextMusic();
game.setBackgroundColor(0x000000);
// --- Background ---
// Ava: Add background image behind all elements
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
scaleX: 1,
scaleY: 1
});
game.addChild(background);
// Ground
var ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0
});
ground.x = 0;
ground.y = 2732 - 80;
game.addChild(ground);
// Finish sign
var finishSign = LK.getAsset('finish_sign', {
anchorX: 0.5,
anchorY: 1,
scaleX: 3,
scaleY: 6 // 2x as tall as before
});
finishSign.x = 2048 - 120;
finishSign.y = 2732 - 80;
game.addChild(finishSign);
// --- Puppet ---
var puppet = new Puppet();
puppet.lastX = puppet.state.x;
puppet.lastY = puppet.state.y;
game.addChild(puppet);
// --- Drums ---
var drums = [];
// Drum positions (relative to center)
// Move drums higher: set drumCenterY above puppet's head
var drumCenterX = 2048 / 2,
drumCenterY = 900; // was 2732 / 2 + 220, now much higher (puppet head is around y=~1200)
var drumRadius = 220 * 3;
var angleList = [{
type: 'left',
angle: -Math.PI / 2 - 0.92 // increased spacing (was 0.7)
}, {
type: 'right',
angle: -Math.PI / 2 + 0.92 // increased spacing (was 0.7)
}, {
type: 'sit',
angle: Math.PI / 2 + 0.7
}, {
type: 'stand',
angle: Math.PI / 2 - 0.7
}];
// --- Drum action pool ---
// Each drum will have all actions, only their order will be shuffled
// Ava: Increase the number of right movement actions (pullHeadRight) in the action pool
var puppetActions = [function () {
puppet.pullHeadLeft();
}, function () {
puppet.pullHeadRight();
}, function () {
puppet.pullHeadRight();
}, function () {
puppet.jump();
}, function () {
puppet.sit();
}, function () {
puppet.sıcrama();
}, function () {
puppet.stand();
}];
// Large center drum (fixed action sequence)
var drumJump = new Drum();
drumJump.setDrum('large');
drumJump.x = drumCenterX;
drumJump.y = drumCenterY;
// Assign a fixed sequence of all 5 actions, and cycle through them
drumJump._actionSequence = puppetActions.slice();
drumJump._actionIndex = 0;
drumJump.action = function () {
drumJump._actionSequence[drumJump._actionIndex]();
drumJump._actionIndex = (drumJump._actionIndex + 1) % drumJump._actionSequence.length;
};
game.addChild(drumJump);
drums.push(drumJump);
// Four medium drums
for (var i = 0; i < angleList.length; ++i) {
var d = new Drum();
d.setDrum(angleList[i].type);
d.x = drumCenterX + Math.cos(angleList[i].angle) * drumRadius;
d.y = drumCenterY + Math.sin(angleList[i].angle) * drumRadius;
// Each drum gets a fixed sequence of all 5 actions, and cycles through them
d._actionSequence = puppetActions.slice();
d._actionIndex = 0;
d.action = function (drum) {
return function () {
drum._actionSequence[drum._actionIndex]();
drum._actionIndex = (drum._actionIndex + 1) % drum._actionSequence.length;
};
}(d);
game.addChild(d);
drums.push(d);
}
// --- Value Circles in Drums ---
var valueCircleValues = [1, 3, 5, 10, 25, 50, 100];
var valueCircleAssets = {
1: 'circle_1',
3: 'circle_3',
5: 'circle_5',
10: 'circle_10',
25: 'circle_25',
50: 'circle_50',
100: 'circle_100'
};
var valueCircleTexts = {};
var currentValueCircle = null;
var currentValueDrum = null;
// Helper: Remove current value circle from drum
function removeValueCircle() {
if (currentValueCircle && currentValueDrum) {
if (currentValueCircle.parent) currentValueCircle.parent.removeChild(currentValueCircle);
if (valueCircleTexts[currentValueCircle._circleValue]) {
if (valueCircleTexts[currentValueCircle._circleValue].parent) {
valueCircleTexts[currentValueCircle._circleValue].parent.removeChild(valueCircleTexts[currentValueCircle._circleValue]);
}
}
currentValueCircle = null;
currentValueDrum = null;
}
}
// Helper: Spawn a value circle in a random drum (not the same as last)
function spawnValueCircle() {
removeValueCircle();
// Pick a random drum
var drumIdx = Math.floor(Math.random() * drums.length);
// Pick a random value
var valueIdx = Math.floor(Math.random() * valueCircleValues.length);
var value = valueCircleValues[valueIdx];
var assetId = valueCircleAssets[value];
var drum = drums[drumIdx];
// Create the circle asset
var circle = LK.getAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
circle.alpha = 0.5;
circle.x = drum.x;
// If this is the center drum, spawn the value circle slightly above the drum center
if (drum.drumType === 'large') {
circle.y = drum.y - 90;
} else {
circle.y = drum.y;
}
circle._circleValue = value;
// Create the text if not already created
if (!valueCircleTexts[value]) {
var txt = new Text2("" + value, {
size: 180,
fill: 0x222222
});
txt.anchor.set(0.5, 0.5);
valueCircleTexts[value] = txt;
}
var txt = valueCircleTexts[value];
txt.x = drum.x;
if (drum.drumType === 'large') {
txt.y = drum.y - 90;
} else {
txt.y = drum.y;
}
// Add to game
game.addChild(circle);
game.addChild(txt);
currentValueCircle = circle;
currentValueDrum = drum;
}
// Timer: spawn every 1s
var valueCircleTimer = LK.setInterval(function () {
if (gameEnded) return;
spawnValueCircle();
}, 1000);
// Patch all drum actions to also spawn a new value circle in a different drum
for (var i = 0; i < drums.length; ++i) {
(function (drum) {
var oldAction = drum.action;
drum.action = function () {
// If this drum is the one with the value circle, add its value to second score and main score
if (!gameEnded && currentValueDrum === drum && currentValueCircle && typeof currentValueCircle._circleValue === "number") {
secondScore += currentValueCircle._circleValue;
secondScoreText.setText(formatSecondScore(secondScore));
// Add to main score as well
LK.setScore(LK.getScore() + currentValueCircle._circleValue);
// Play wide stereo coin sound
LK.getSound('coin_left').play();
LK.getSound('coin_right').play();
// --- Add colored glow effect around the value circle ---
// Pick color based on value
var glowColor = 0xffe066; // default yellow
if (currentValueCircle._circleValue === 1) glowColor = 0xffe066;else if (currentValueCircle._circleValue === 3) glowColor = 0x66c2ff;else if (currentValueCircle._circleValue === 5) glowColor = 0xff66a3;else if (currentValueCircle._circleValue === 10) glowColor = 0x7cff66;else if (currentValueCircle._circleValue === 25) glowColor = 0xffa366;else if (currentValueCircle._circleValue === 50) glowColor = 0xb366ff;else if (currentValueCircle._circleValue === 100) glowColor = 0x66ffd9;
// Create a glow circle (slightly larger, fully transparent at start)
var glow = LK.getAsset(valueCircleAssets[currentValueCircle._circleValue], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.35,
scaleY: 1.35
});
glow.x = currentValueCircle.x;
glow.y = currentValueCircle.y;
glow.alpha = 0.0;
glow.tint = glowColor;
// Add to game, behind the value circle
if (currentValueCircle.parent) {
var parent = currentValueCircle.parent;
var idx = parent.getChildIndex(currentValueCircle);
parent.addChildAt(glow, Math.max(0, idx));
} else {
game.addChild(glow);
}
// Animate the glow: fade in and out, and scale up a bit
tween(glow, {
alpha: 0.7,
scaleX: 1.7,
scaleY: 1.7
}, {
duration: 120,
onFinish: function onFinish() {
tween(glow, {
alpha: 0.0,
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 320,
onFinish: function onFinish() {
if (glow.parent) glow.parent.removeChild(glow);
}
});
}
});
}
// --- Puppet arms up clapping animation ---
// Only trigger clapping if a point is collected AND value is 100
if (!gameEnded && currentValueDrum === drum && currentValueCircle && typeof currentValueCircle._circleValue === "number" && currentValueCircle._circleValue === 100 && _typeof(puppet) === "object" && puppet.state && puppet.state.pose !== "clap") {
// Save previous pose to restore after clapping
var prevPose = puppet.state.pose;
puppet.state.pose = "clap";
// Animate arms up and clapping motion
// We'll use a tween to create a quick up-down-up movement
var clapCount = 2;
var clapDuration = 120;
var totalClapTime = clapCount * 2 * clapDuration;
var _clapTween = function clapTween(count) {
if (count <= 0) {
// Restore previous pose after clapping
puppet.state.pose = prevPose === "clap" ? "stand" : prevPose;
return;
}
// Arms together (clap)
puppet._clapArms = true;
tween(puppet, {}, {
duration: clapDuration,
onFinish: function onFinish() {
// Arms apart
puppet._clapArms = false;
tween(puppet, {}, {
duration: clapDuration,
onFinish: function onFinish() {
_clapTween(count - 1);
}
});
}
});
};
_clapTween(clapCount);
}
if (typeof oldAction === "function") oldAction();
// Only respawn if not ended
if (!gameEnded) {
// Only respawn if this drum is not already the current one
var prevDrum = currentValueDrum;
var prevCircle = currentValueCircle;
// Remove current value circle
removeValueCircle();
// Pick a new drum (not the same as this one)
var availableDrums = drums.filter(function (d) {
return d !== drum;
});
var drumIdx = Math.floor(Math.random() * availableDrums.length);
var newDrum = availableDrums[drumIdx];
// Pick a random value
var valueIdx = Math.floor(Math.random() * valueCircleValues.length);
var value = valueCircleValues[valueIdx];
var assetId = valueCircleAssets[value];
// Create the circle asset
var circle = LK.getAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
circle.alpha = 0.5;
circle.x = newDrum.x;
// If this is the center drum, spawn the value circle slightly above the drum center
if (newDrum.drumType === 'large') {
circle.y = newDrum.y - 90;
} else {
circle.y = newDrum.y;
}
circle._circleValue = value;
// Create the text if not already created
if (!valueCircleTexts[value]) {
var txt = new Text2("" + value, {
size: 180,
fill: 0x222222
});
txt.anchor.set(0.5, 0.5);
valueCircleTexts[value] = txt;
}
var txt = valueCircleTexts[value];
txt.x = newDrum.x;
if (newDrum.drumType === 'large') {
txt.y = newDrum.y - 90;
} else {
txt.y = newDrum.y;
}
// Add to game
game.addChild(circle);
game.addChild(txt);
currentValueCircle = circle;
currentValueDrum = newDrum;
}
};
})(drums[i]);
}
var timeLeft = 60;
function formatTimeDigital(secs) {
var m = Math.floor(secs / 60);
var s = secs % 60;
var mm = m < 10 ? "0" + m : "" + m;
var ss = s < 10 ? "0" + s : "" + s;
return mm + ":" + ss;
}
var timerText = new Text2(formatTimeDigital(60), {
size: 120,
fill: '#ffffff'
});
timerText.anchor.set(0.5, 0);
LK.gui.top.addChild(timerText);
// --- Second Score Display (below timer) ---
var secondScore = 0;
function formatSecondScore(val) {
var s = "" + Math.floor(val);
while (s.length < 5) s = "0" + s;
return s;
}
var secondScoreText = new Text2(formatSecondScore(0), {
size: 160,
fill: '#ffe066'
});
secondScoreText.anchor.set(0.5, 0);
// Position just below timerText (timerText is at top center, so y=timerText.height)
secondScoreText.y = timerText.height + 10;
LK.gui.top.addChild(secondScoreText);
// --- Score/Win state ---
var gameEnded = false;
// --- Timer logic ---
var timerInterval = LK.setInterval(function () {
if (gameEnded) return;
timeLeft -= 1;
if (timeLeft < 0) timeLeft = 0;
timerText.setText(formatTimeDigital(timeLeft));
if (timeLeft === 0 && !gameEnded) {
gameEnded = true;
LK.setScore(0); // Set final score to 0 if time runs out
LK.getSound('lose').play();
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
}
}, 1000);
// --- Game update ---
game.update = function () {
if (gameEnded) return;
puppet.update();
// Win condition: puppet body collides with finish sign using intersects
if (!gameEnded && puppet.intersects(finishSign)) {
gameEnded = true;
// Add remaining time * 10 to final score
var finalScore = secondScore + timeLeft * 10;
LK.setScore(finalScore); // Set score to finalScore as final score
LK.getSound('win').play();
LK.effects.flashScreen(0x44c767, 800);
LK.showYouWin();
}
};
// --- Clean up on game over ---
game.onDestroy = function () {
LK.clearInterval(timerInterval);
};
// --- UX: drums are always on top ---
for (var i = 0; i < drums.length; ++i) {
game.addChild(drums[i]);
}