User prompt
How to fix it Here’s a checklist to get perfect centering: 1. Match screen size to comic page size If your comic pages are 2048 × 2732, make sure your canvas or game screen is also that size: var game = new LK.Game({ width: 2048, height: 2732, backgroundColor: 0x87ceeb }); 2. Ensure consistent scaling If you're scaling comicPage1, you should scale the others too—or remove scaling altogether for consistency: scaleX: 0.67, scaleY: 0.89
User prompt
center graphic asset on comicPage1
User prompt
center the comic page image inside the comic page box
User prompt
scale the comic page graphic box so that the whole image fits in it properly and can be seen
User prompt
keep aimoriginshape visible at all times, not just when player clicks move aimoriginshape up by 100 pixels
User prompt
move aim origin shape up by 100 pixels. move power bar to visible screen: powerBarBG.x = 1024; powerBarBG.y = 200; // top center instead of bottom powerBarFill.x = powerBarBG.x - 150; powerBarFill.y = powerBarBG.y;
User prompt
Aiming visuals not visible in front of the slingshot The reason it still disappears is that you’re creating the slingshot after the aiming visuals. Even if you set the index once, later additions (like houses/mailboxes) can push it behind. The safest approach is to force the draw order every frame so the trajectory and aim origin are always on top. That way, no matter what gets added, the aiming line + dot stay visible above the slingshot and mailboxes. Power Bar (already partly coded in your file!) 👉 But to make it more obvious, you should color it dynamically (green → yellow → red as power increases).// replace the fill every frame var color = 0x00ff00; // green if (percent > 0.66) { color = 0xff0000; // red } else if (percent > 0.33) { color = 0xffff00; // yellow } // re-create the fill shape powerBarFill.destroy(); powerBarFill = LK.getAsset('powerBarFillShape', { width: percent * 300, color: color, anchorX: 0, anchorY: 0.5 }); powerBarFill.x = powerBarBG.x - 150; powerBarFill.y = powerBarBG.y; LK.gui.top.addChild(powerBarFill); ⚡ So: Add the setChildIndex calls in game.update to force aiming visuals on top. Expand the power bar logic to change color + size as you pull back.
User prompt
display aimOriginShape in front of mothman graphic for better visibilitity
User prompt
draw the aim origin shape in front of slingshot grpahic so that it is visible to player.
User prompt
fix for aiming and power bar: 👉 Fix: Define dedicated small shapes for UI. // Add new assets once, near the asset init section LK.init.shape('powerBarBGShape', { width: 300, height: 40, color: 0x333333, shape: 'box' }); LK.init.shape('powerBarFillShape', { width: 300, height: 30, color: 0x00ff00, shape: 'box' }); LK.init.shape('aimOriginShape', { width: 30, height: 30, color: 0xff0000, shape: 'ellipse' }); // circle Then use them: var pullOriginIndicator = LK.getAsset('aimOriginShape', { anchorX: 0.5, anchorY: 0.5 }); pullOriginIndicator.visible = false; game.addChild(pullOriginIndicator);
User prompt
1. ensure aiming visuals are displaying in front of mothman so they player can see them. 2. the mailbox can load anywhere within the mailbox zone EXCEPT where house graphic is loaded
User prompt
1. Create the pull-back origin indicator Add this near your global variables (where trajectoryLine etc. are created): // Pull-back origin indicator var pullOriginIndicator = LK.getAsset('trajectory', { width: 30, height: 30, color: 0xff0000, // red circle anchorX: 0.5, anchorY: 0.5 }); pullOriginIndicator.visible = false; game.addChild(pullOriginIndicator); 2. Create the power bar UI Add this also in globals (after paycheckText for example): // Power bar background + fill var powerBarBG = LK.getAsset('mailboxZone', { width: 300, height: 40, color: 0x333333, anchorX: 0.5, anchorY: 0.5 }); powerBarBG.x = 1024; powerBarBG.y = 2500; LK.gui.top.addChild(powerBarBG); var powerBarFill = LK.getAsset('mailboxZone', { width: 0, // start empty height: 30, color: 0x00ff00, anchorX: 0, anchorY: 0.5 }); powerBarFill.x = powerBarBG.x - 150; // align left inside bg powerBarFill.y = powerBarBG.y; LK.gui.top.addChild(powerBarFill); 3. Update aiming logic Inside your game.down: game.down = function (x, y, obj) { if (y > streetZoneY - 200) { isAiming = true; aimStartX = x; aimStartY = y; // Show pull origin indicator at slingshot center pullOriginIndicator.x = 1024; pullOriginIndicator.y = 2200; pullOriginIndicator.visible = true; } }; Inside game.move (update power bar + trajectory): game.move = function (x, y, obj) { if (isAiming) { updateTrajectory(1024, 2200, x, y); // Compute pull distance for power var dx = 1024 - x; var dy = 2200 - y; var pullDistance = Math.sqrt(dx * dx + dy * dy); var maxPull = 600; // clamp so bar doesn’t overflow var percent = Math.min(1, pullDistance / maxPull); // Update power bar fill powerBarFill.width = percent * 300; // full width } }; Inside game.up (reset visuals): game.up = function (x, y, obj) { if (!isAiming || hasThrownThisTurn) return; var originX = 1024, originY = 2200; var pullX = x - originX; var pullY = y - originY; var launchX = -pullX; var launchY = -pullY; var pullDistance = Math.sqrt(launchX * launchX + launchY * launchY); var powerScale = 0.15; var power = pullDistance * powerScale; var angle = Math.atan2(launchY, launchX); if (power > 2) { shootNewspaper(power, angle); hasThrownThisTurn = true; } // Reset aiming visuals isAiming = false; trajectoryLine.removeChildren(); pullOriginIndicator.visible = false; powerBarFill.width = 0; }; ✅ This gives: A red dot at the slingshot base when aiming starts. A green power bar that fills as you pull back, proportional to distance. Bar resets after release.
User prompt
move the paycheck counter down 100 pixels, make it dark green and change the font to comic sans
User prompt
for the comic buttoms: 🧩 1. Positioning Off-Screen You're placing both buttons at y = 2500, which is quite far down the screen. If your stage or viewport height is smaller (e.g., 1366 or 1920), the buttons are likely rendering below the visible area. Fix: Try adjusting the y values to something within the visible range: var nextBtn = makeButton("▶ Next", 1700, 1200, 0x3333aa, ...); var skipBtn = makeButton("✖ Skip", 400, 1200, 0xaa3333, ...); 🧩 2. Asset Visibility or Alpha If mailboxZone or Text2 assets aren't rendering properly (e.g., due to missing textures or zero alpha), the buttons might technically exist but appear invisible. Check: - Is mailboxZone a valid asset with visible fill? - Does Text2 render correctly elsewhere? - Try adding btnContainer.alpha = 1; to ensure full opacity. 🧩 3. Layering or Masking You’re already using setChildIndex to push the buttons to the top, which is good. But if any parent container or mask is clipping content, that could hide them. Check: - Is comicIntro or any parent container masked or clipped? - Try logging comicIntro.children to confirm the buttons are present. 🧩 4. Interactive Area Not Registered If the buttons are technically visible but not responding, it could be an issue with interaction registration. Try: btnContainer.interactive = true; btnContainer.buttonMode = true; // adds cursor feedback 🧪 Debug Tip Add a temporary debug rectangle to visualize button placement: var debugBox = new Graphics(); debugBox.beginFill(0xff0000); debugBox.drawRect(-200, -75, 400, 150); debugBox.endFill(); btnContainer.addChild(debugBox);
User prompt
Fixes Bring buttons to the top layer → use setChildIndex so they render over pages. Adjust placement → set anchors and coordinates clearly within the screen (your game seems to be 2048x2732). Add background boxes → so they look like comic-style buttons instead of floating text. Updated Code /**** * Buttons (always visible above comic pages) ****/ function makeButton(label, x, y, color, onClick) { var btnContainer = new Container(); // background box var bg = LK.getAsset('mailboxZone', { // re-use a simple shape width: 400, height: 150, color: color, anchorX: 0.5, anchorY: 0.5 }); btnContainer.addChild(bg); // text var txt = new Text2(label, { size: 72, fill: 0xffffff, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); txt.anchor.set(0.5); btnContainer.addChild(txt); // position btnContainer.x = x; btnContainer.y = y; // interaction btnContainer.interactive = true; btnContainer.on('pointertap', onClick); comicIntro.addChild(btnContainer); // make sure buttons are above pages comicIntro.setChildIndex(btnContainer, comicIntro.children.length - 1); return btnContainer; } // NEXT button (bottom right) var nextBtn = makeButton("▶ Next", 1700, 2500, 0x3333aa, function () { comicPages[currentPageIndex].visible = false; currentPageIndex++; if (currentPageIndex < comicPages.length) { comicPages[currentPageIndex].visible = true; } else { startGame(); } }); // SKIP button (bottom left) var skipBtn = makeButton("✖ Skip", 400, 2500, 0xaa3333, function () { startGame(); }); Notes makeButton() creates a colored rectangle + text so the button is visible. Buttons are placed at x=1700, y=2500 (right) and x=400, y=2500 (left) — safely inside a 2048x2732 layout. setChildIndex ensures they’re always above comic images.
User prompt
ensure that they next page and skip buttons are visible assets loading in front of the comic pages and not behind them. display in the bottom right hand corner
User prompt
Full Comic Intro With Buttons Add this before gameplay starts (after your game object is created but before any gameplay logic runs): /**** * Comic Intro ****/ var comicIntro = new Container(); LK.gui.addChild(comicIntro); // Preload comic pages (replace with your real asset IDs) LK.init.image('comicPage1', {width:2048, height:2732, id:'comic1id'}); LK.init.image('comicPage2', {width:2048, height:2732, id:'comic2id'}); LK.init.image('comicPage3', {width:2048, height:2732, id:'comic3id'}); var comicPages = [ LK.getAsset('comicPage1', {anchorX:0.5, anchorY:0.5}), LK.getAsset('comicPage2', {anchorX:0.5, anchorY:0.5}), LK.getAsset('comicPage3', {anchorX:0.5, anchorY:0.5}) ]; var currentPageIndex = 0; // Position all pages and hide them except first comicPages.forEach(function(page){ page.x = 1024; page.y = 1366; // center screen for 2048x2732 page.visible = false; comicIntro.addChild(page); }); comicPages[0].visible = true; /**** * Buttons ****/ // Next button var nextBtn = new Text2("▶ Next", { size: 80, fill: 0xffffff, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); nextBtn.anchor.set(1,1); nextBtn.x = 1900; nextBtn.y = 2600; nextBtn.interactive = true; nextBtn.on('pointertap', function(){ comicPages[currentPageIndex].visible = false; currentPageIndex++; if (currentPageIndex < comicPages.length) { comicPages[currentPageIndex].visible = true; } else { startGame(); } }); comicIntro.addChild(nextBtn); // Skip button var skipBtn = new Text2("✖ Skip", { size: 80, fill: 0xff4444, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); skipBtn.anchor.set(0,1); skipBtn.x = 150; skipBtn.y = 2600; skipBtn.interactive = true; skipBtn.on('pointertap', function(){ startGame(); }); comicIntro.addChild(skipBtn); /**** * Start Game Function ****/ function startGame(){ comicIntro.visible = false; game.visible = true; // optional: reset timer, pennies, etc. here if needed } game.visible = false; // hide gameplay until intro is done
User prompt
We’ll need to add a pre-game state for the comic intro. Here’s how you could structure it:Steps to Add Comic Intro Create a comic intro container Holds 3 full-screen images (your comic pages). Add “Next” and “Skip” buttons. Show intro first Don’t initialize gameplay until the player finishes the comic or presses skip. Hide the game container until after the intro. Advance pages Clicking “Next” cycles through the 3 pages. After the last page, hide the intro and start the game.// Comic Intro Container var comicIntro = new Container(); LK.gui.addChild(comicIntro); // Array of comic page images (replace IDs with your asset IDs) var comicPages = [ LK.getAsset('comicPage1', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('comicPage2', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('comicPage3', { anchorX: 0.5, anchorY: 0.5 }) ]; var currentPageIndex = 0; // Position and add first page comicPages.forEach(function(page) { page.x = 1024; page.y = 1366; // center screen (assuming 2048x2732 game res) page.visible = false; comicIntro.addChild(page); }); comicPages[0].visible = true; // Next Button var nextBtn = new Text2("Next ▶", { size: 72, fill: 0xffffff }); nextBtn.x = 1800; nextBtn.y = 2500; nextBtn.interactive = true; nextBtn.on('pointertap', function () { comicPages[currentPageIndex].visible = false; currentPageIndex++; if (currentPageIndex < comicPages.length) { comicPages[currentPageIndex].visible = true; } else { startGame(); } }); comicIntro.addChild(nextBtn); // Skip Button var skipBtn = new Text2("Skip ❌", { size: 72, fill: 0xff4444 }); skipBtn.x = 200; skipBtn.y = 2500; skipBtn.interactive = true; skipBtn.on('pointertap', function () { startGame(); }); comicIntro.addChild(skipBtn); // Function to start game function startGame() { comicIntro.visible = false; game.visible = true; game.start(); // run your main game loop } // Initially hide game until comic is done game.visible = false; remove goal: 650 pennies text increase reward for mailbox hit from 1 penny to .25 cents Increase the money earned
User prompt
play Mothman Boogie for background music. play MailboxHit sfx when newspaper hits mailbox successfully play Paperthrow sfx when mewspaper is released from slingshot
User prompt
PLay backgroundmusic on loop when game starts
User prompt
Adding Buffer Tiles for Seamless Treeline Coverage To guarantee your treeline always spans beyond the screen edges, you need extra “buffer” tiles on both sides. Below are two approaches: one-time static setup and a dynamic “ensure coverage” routine. eplace your three-tile static setup with a loop that spawns a buffer tile on each side: // —– replace this block: // var treelineContainer = new Container(); // game.addChild(treelineContainer); // var treeline1 = treelineContainer.addChild(LK.getAsset('treeline', { anchorX: 0, anchorY: 1 })); // var treeline2 = treelineContainer.addChild(LK.getAsset('treeline', { anchorX: 0, anchorY: 1 })); // var treeline3 = treelineContainer.addChild(LK.getAsset('treeline', { anchorX: 0, anchorY: 1 })); // treeline1.x = 0; // treeline2.x = treeline1.x + treeline1.width; // treeline3.x = treeline2.x + treeline2.width; // treelineContainer.x = 0; // treelineContainer.y = mailboxZoneY - 350; // // Array to manage treeline pieces for infinite scroll // var treelines = [treeline1, treeline2, treeline3]; // —– with this: var treelineContainer = new Container(); game.addChild(treelineContainer); // compute how many tiles we need to cover the screen plus one extra on each side var tileWidth = LK.getAsset('treeline').width; var screenWidth = 2048; // your game width var sideBuffers = 1; // one extra tile each side var tileCount = Math.ceil(screenWidth / tileWidth) + sideBuffers * 2; var treelines = []; for (var i = 0; i < tileCount; i++) { var tile = treelineContainer.addChild( LK.getAsset('treeline', { anchorX: 0, anchorY: 1 }) ); // position so that the first tile starts at –tileWidth tile.x = (i - sideBuffers) * tileWidth; tile.y = 0; treelines.push(tile); } // lock the whole strip at the correct vertical position treelineContainer.x = 0; treelineContainer.y = mailboxZoneY - 350;
User prompt
slow the speed of the treeline movement by 1. ensure that the treeline wrap is seemless between turns ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Implementing Parallax Scrolling for Your Treeline To give depth to your scene, move each layer at its own speed. Background trees scroll slower than the foreground house, creating a convincing distance illusion. 1. Define Speeds for Each Layer base_speed = 5 # pixels per frame for the house tree_factor = 0.4 # trees move at 40% of base_speed 2. Track Individual Offsets Maintain a separate horizontal offset for each layer tree_offset = 0 mountain_offset = 0 # Every frame before drawing: tree_offset = (tree_offset - base_speed * tree_factor) % tree_width - The modulo operation wraps each layer seamlessly. - Convert to integers if you see sub-pixel gaps: tree_offset = int(tree_offset) 3. Draw Layers Back-to-Front - Trees - House # Pseudocode draw sequence for x in [tree_offset - tree_width, tree_offset]: screen.blit(tree_image, (x, tree_y)) # Draw the house at its own position screen.blit(house_image, (house_x, house_y)) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
for the treeline, Draw Two Tiles Depending on your screen and image widths, you need enough copies to cover the entire horizontal span: # Python-style pseudocode W = treeline_image.get_width() offset = (offset - speed) % W x_positions = [offset - W, offset] # If your screen_width > 2*W, add more: # count = math.ceil(screen_width / W) + 1 # x_positions = [offset + i*W for i in range(-1, count)] for x in x_positions: screen.blit(treeline_image, (x, y)) - One tile at offset - W - One tile at offset - If screen_width > 2*W, generate extra positions by adding multiples of W.
User prompt
1. Infinite-Scroll Treeline Wrap Drive the scroll entirely via updateTreeline, using the per-frame delta of the house’s X position. - Remove any direct assignment to treelineContainer.x. - Track the house’s previous X in a variable (prevHouseX) and, each frame, call updateTreeline(speed), where // At top‐level, initialize: var prevHouseX = 1024; // house starts centered game.update = function() { // ... existing logic ... // Infinite‐scroll the treeline every frame if (currentHouse) { var speed = prevHouseX - currentHouse.x; updateTreeline(speed); prevHouseX = currentHouse.x; } // Remove or comment out any line that re-sets treelineContainer.x: // treelineContainer.x = houseOffset; // ... rest of update ... };With this change, your three treeline sprites stay within their container at x=0, and updateTreeline handles both movement and wrap-around seamlessly—even when the house tween stops. 2. Enlarging & Simplifying the Mailbox Hitbox - Add a tolerance around the mailbox bounds so “close” throws still register. - Guarantee a minimum payout on any overlap, rather than skipping the hit entirely. // Inside your newspaper‐mailbox collision loop: var gfx = m.children[0] || m; var halfW = gfx.width * 0.5; var fullH = gfx.height; var left = m.x - halfW; var right = m.x + halfW; var top = m.y - fullH; var bottom = m.y; // loosen bounds by 20px in all directions: var tol = 20; var insideX = newspaper.x >= left - tol && newspaper.x <= right + tol; var insideY = newspaper.y >= top - tol && newspaper.y <= bottom + tol; if (!insideX || !insideY) continue; // compute accuracy off the mailbox “mouth” var mouthY = m.y - fullH * 0.6; var dx = newspaper.x - m.x; var dy = newspaper.y - mouthY; var dist = Math.sqrt(dx*dx + dy*dy); // if payoutFor returns null (too far), still give a glancing hit: var res = payoutFor(dist) || { cents:1, label: '+1¢ Close enough' }; // apply scoring & effects... pennies += res.cents; updatePaycheck(); m.hit = true; newspaper.active = false; // ... remainder of your hit logic ... By expanding the bounds and falling back to at least a 1¢ hit for any overlap, you’ll capture those near-misses as intended.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var House = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('house', { anchorX: 0.5, anchorY: 1.0 }); self.active = true; // Remove scrolling from update, handled by tweens self.update = function () {}; return self; }); var Mailbox = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('mailbox', { anchorX: 0.5, anchorY: 1.0 }); self.hit = false; self.active = true; self.update = function () {}; // no auto-scrolling return self; }); var Newspaper = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('newspaper', { anchorX: 0.5, anchorY: 0.5 }); // Physics properties self.vx = 0; self.vy = 0; self.gravity = 0.4; self.active = true; // Store starting position for scaling math self.startY = null; self.update = function () { if (!self.active) { return; } // Apply velocity self.x += self.vx; self.y += self.vy; // Apply gravity self.vy += self.gravity; // Record starting Y on first update if (self.startY === null) { self.startY = self.y; } // Scale down as it rises (depth effect) var travel = self.startY - self.y; var scaleFactor = Math.max(0.2, 1 - travel / 1500); graphics.scaleX = graphics.scaleY = scaleFactor; // Cleanup when it goes "into the distance" if (self.y < mailboxZoneY - 300 || self.y > 2732 || self.x < -200 || self.x > 2300) { self.active = false; // Trigger turn end if this was the last shot if (currentHouse && !isHouseTransitioning) { removeHouse(currentHouse, function () { currentHouse = null; hasThrownThisTurn = false; // reset throw flag // Clear leftover mailboxes for (var k = mailboxes.length - 1; k >= 0; k--) { mailboxes[k].destroy(); mailboxes.splice(k, 1); } }); } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87ceeb }); /**** * Game Code ****/ // green // orange game.setBackgroundColor(0x87ceeb); // Comic Intro Container var comicIntro = new Container(); LK.gui.addChild(comicIntro); // Array of comic page images var comicPages = [LK.getAsset('comicPage1', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('comicPage2', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('comicPage3', { anchorX: 0.5, anchorY: 0.5 })]; var currentPageIndex = 0; // Position and add pages for (var p = 0; p < comicPages.length; p++) { var page = comicPages[p]; page.x = 1024; // center horizontally page.y = 1366; // center vertically page.visible = false; comicIntro.addChild(page); } comicPages[0].visible = true; // Function to create button with background function makeButton(label, x, y, color, onClick) { var btnContainer = new Container(); // background box with proper shape asset var bg = LK.getAsset('mailboxZone', { width: 400, height: 150, color: color, anchorX: 0.5, anchorY: 0.5 }); btnContainer.addChild(bg); // text var txt = new Text2(label, { size: 72, fill: 0xffffff, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); txt.anchor.set(0.5); btnContainer.addChild(txt); // position btnContainer.x = x; btnContainer.y = y; // interaction btnContainer.interactive = true; btnContainer.buttonMode = true; btnContainer.alpha = 1; btnContainer.down = onClick; // Add debug rectangle to visualize button area var debugBox = LK.getAsset('trajectory', { width: 400, height: 150, color: 0xff0000, anchorX: 0.5, anchorY: 0.5 }); debugBox.alpha = 0.3; btnContainer.addChild(debugBox); return btnContainer; } // Next Button (bottom right) - positioned within visible screen area var nextBtn = makeButton("▶ Next", 1700, 1200, 0x3333aa, function () { comicPages[currentPageIndex].visible = false; currentPageIndex++; if (currentPageIndex < comicPages.length) { comicPages[currentPageIndex].visible = true; } else { startGame(); } }); // Skip Button (bottom left) - positioned within visible screen area var skipBtn = makeButton("✖ Skip", 400, 1200, 0xaa3333, function () { startGame(); }); // Add buttons after pages to ensure they render on top comicIntro.addChild(nextBtn); comicIntro.addChild(skipBtn); // Ensure buttons are always above pages using setChildIndex comicIntro.setChildIndex(nextBtn, comicIntro.children.length - 1); comicIntro.setChildIndex(skipBtn, comicIntro.children.length - 1); // Make buttons fully interactive with button mode nextBtn.interactive = true; nextBtn.buttonMode = true; nextBtn.alpha = 1; skipBtn.interactive = true; skipBtn.buttonMode = true; skipBtn.alpha = 1; // Function to start game function startGame() { comicIntro.visible = false; game.visible = true; // Start background music when game actually starts LK.playMusic('MothmanBoogie'); } // Initially hide game until comic is done game.visible = false; /**** * Color Interpolation Helper ****/ function lerpColor(c1, c2, t) { var r1 = c1 >> 16 & 0xff, g1 = c1 >> 8 & 0xff, b1 = c1 & 0xff; var r2 = c2 >> 16 & 0xff, g2 = c2 >> 8 & 0xff, b2 = c2 & 0xff; var r = Math.round(r1 + (r2 - r1) * t); var g = Math.round(g1 + (g2 - g1) * t); var b = Math.round(b1 + (b2 - b1) * t); return r << 16 | g << 8 | b; } /**** * Sun Path Parameters ****/ // Sun path parameters var sunStartX = 100; // left side var sunEndX = 1948; // right side var sunBaseY = 600; // baseline (sky zone level) var sunArcHeight = 400; // how high the sun climbs /**** * Zone Setup ****/ // Define Y positions for zones (adjust as needed to match your reference image) var streetZoneY = 2000; // Green bottom zone where slingshot sits var mailboxZoneY = 1200; // Orange middle zone where mailbox spawns var houseZoneY = 1000; // Houses aligned just above mailbox zone // Ground reference (for houses) var groundY = 2400; /**** * Visual Zone Placeholders ****/ // Street zone background (green) var streetZone = game.addChild(LK.getAsset('streetZone', { anchorX: 0.5, anchorY: 0.5 })); streetZone.x = 1024; streetZone.y = streetZoneY; // Mailbox zone background (orange) var mailboxZone = game.addChild(LK.getAsset('mailboxZone', { anchorX: 0.5, anchorY: 0.5 })); mailboxZone.x = 1024; mailboxZone.y = mailboxZoneY; // Treeline container to move with houses var treelineContainer = new Container(); game.addChild(treelineContainer); // compute how many tiles we need to cover the screen plus one extra on each side var tileWidth = LK.getAsset('treeline', {}).width; var screenWidth = 2048; // your game width var sideBuffers = 1; // one extra tile each side var tileCount = Math.ceil(screenWidth / tileWidth) + sideBuffers * 2; var treelines = []; for (var i = 0; i < tileCount; i++) { var tile = treelineContainer.addChild(LK.getAsset('treeline', { anchorX: 0, anchorY: 1 })); // position so that the first tile starts at –tileWidth tile.x = (i - sideBuffers) * tileWidth; tile.y = 0; treelines.push(tile); } // lock the whole strip at the correct vertical position treelineContainer.x = 0; treelineContainer.y = mailboxZoneY - 350; // Parallax scrolling parameters var baseSpeed = 5; // Base movement speed for reference var treeParallaxFactor = 0.2; // Trees move at 20% of house speed for depth effect // Update function for treeline infinite scroll function updateTreeline(speed) { for (var i = 0; i < treelines.length; i++) { var t = treelines[i]; t.x -= speed; } // After moving, check for wrap and re-align all treelines to be flush // Find the leftmost treeline var leftMostIndex = 0; var leftMostX = treelines[0].x; for (var i = 1; i < treelines.length; i++) { if (treelines[i].x < leftMostX) { leftMostX = treelines[i].x; leftMostIndex = i; } } // Now, starting from leftmost, set each treeline's x so they are flush var startX = treelines[leftMostIndex].x; for (var i = 0; i < treelines.length; i++) { var idx = (leftMostIndex + i) % treelines.length; if (i === 0) { treelines[idx].x = startX; } else { treelines[idx].x = treelines[(idx - 1 + treelines.length) % treelines.length].x + treelines[(idx - 1 + treelines.length) % treelines.length].width; } } // If any treeline is now off the left edge, move it to the right of the last one for (var i = 0; i < treelines.length; i++) { var t = treelines[i]; if (t.x + t.width < 0) { // Find the rightmost treeline var rightMost = Math.max.apply(Math, treelines.map(function (tr) { return tr.x; })); t.x = rightMost + t.width; } } } // Game variables var mothman; var sun; var slingshot; var newspapers = []; var mailboxes = []; var houses = []; var trajectoryDots = []; // Create one reusable graphics line for the trajectory var trajectoryLine = new Container(); // Pull-back origin indicator will be created after slingshot for proper layering var pullOriginIndicator; var pennies = 0; var targetPennies = 650; var gameTime = 0; var maxGameTime = 18000; // 5 minutes at 60fps var isAiming = false; var aimStartX = 0; var aimStartY = 0; var aimPower = 0; var aimAngle = 0; var hasThrownThisTurn = false; // Create UI var paycheckText = new Text2('Paycheck: $0.00', { size: 65, fill: 0x006400, font: "'Comic Sans MS', cursive" }); paycheckText.anchor.set(0.5, 0); paycheckText.y = 100; LK.gui.top.addChild(paycheckText); function updatePaycheck() { paycheckText.setText('Paycheck: $' + (pennies / 100).toFixed(2)); } // Power bar background + fill var powerBarBG = LK.getAsset('powerBarBGShape', { anchorX: 0.5, anchorY: 0.5 }); powerBarBG.x = 1024; powerBarBG.y = 200; // top center instead of bottom LK.gui.top.addChild(powerBarBG); var powerBarFill = LK.getAsset('powerBarFillShape', { width: 0, // start empty anchorX: 0, anchorY: 0.5 }); powerBarFill.x = powerBarBG.x - 150; powerBarFill.y = powerBarBG.y; LK.gui.top.addChild(powerBarFill); // Create mothman mothman = game.addChild(new Container()); var mothmanGraphics = mothman.attachAsset('mothman', { anchorX: 0.5, anchorY: 0.5 }); mothman.x = 200; mothman.y = 400; // Hide mothman (no sprite during gameplay) mothman.visible = false; // Create pull origin indicator after mothman to ensure proper layering in front pullOriginIndicator = LK.getAsset('aimOriginShape', { anchorX: 0.5, anchorY: 0.5 }); // Position the aim origin indicator at slingshot position, moved up by 100 pixels pullOriginIndicator.x = 1024; pullOriginIndicator.y = 2000; // streetZoneY (2000) + 100 - 100 = 2000 pullOriginIndicator.visible = true; // Always visible // Add aiming visuals after mothman to ensure they render on top game.addChild(trajectoryLine); game.addChild(pullOriginIndicator); // Create sun sun = game.addChild(new Container()); var sunGraphics = sun.attachAsset('sun', { anchorX: 0.5, anchorY: 0.5 }); sun.x = sunStartX; sun.y = sunBaseY - sunArcHeight; /**** * Draw Sun Arc Path ****/ // Draw sun trajectory arc with dotted path function drawSunPath() { for (var t = 0; t <= 1; t += 0.05) { var x = sunStartX + (sunEndX - sunStartX) * t; var y = sunBaseY - Math.sin(t * Math.PI) * sunArcHeight; var dot = game.addChild(LK.getAsset('trajectory', { width: 6, height: 6, color: 0xffff66, // pale yellow anchorX: 0.5, anchorY: 0.5 })); dot.x = x; dot.y = y; dot.alpha = 0.3; // semi-transparent } } drawSunPath(); // Create slingshot area - positioned in street zone center slingshot = game.addChild(new Container()); var slingshotGraphics = slingshot.attachAsset('slingshot', { anchorX: 0.5, anchorY: 0.5 }); slingshot.x = 1024; // center of screen slingshot.y = streetZoneY + 100; // Slingshot bands (for stretch feedback) var bandLeft = new Container(); var bandRight = new Container(); game.addChild(bandLeft); game.addChild(bandRight); // Ground level var groundY = 2400; // Turn-based house cycle variables var currentHouse = null; var isHouseTransitioning = false; var turnComplete = true; var prevHouseX = 1024; // Track previous house X position for treeline scroll function ensureMailboxForTurn() { // Clear leftover mailboxes for (var k = mailboxes.length - 1; k >= 0; k--) { mailboxes[k].destroy(); mailboxes.splice(k, 1); } createMailbox(); // always create (no RNG) } function createHouse() { var house = new House(); // Start just off-screen to the right house.x = 2200; house.y = mailboxZoneY + 250; // roughly center mailbox zone houses.push(house); game.addChild(house); currentHouse = house; isHouseTransitioning = true; turnComplete = false; hasThrownThisTurn = false; // Tween it into center of mailbox zone tween(house, { x: 1024 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { isHouseTransitioning = false; ensureMailboxForTurn(); // mailbox priority } }); return house; } function removeHouse(house, onComplete) { if (!house) { return; } isHouseTransitioning = true; tween(house, { x: -200 }, { duration: 1000, easing: tween.easeIn, onFinish: function onFinish() { house.destroy(); var index = houses.indexOf(house); if (index !== -1) { houses.splice(index, 1); } isHouseTransitioning = false; turnComplete = true; if (onComplete) { onComplete(); } } }); } function createMailbox() { if (!currentHouse) { return; } var mailbox = new Mailbox(); // Calculate house position and width to avoid overlap var houseX = currentHouse.x; var houseWidth = 900; // house asset width var houseLeftEdge = houseX - houseWidth * 0.5; var houseRightEdge = houseX + houseWidth * 0.5; // Generate random position that doesn't overlap with house var mailboxX; var attempts = 0; do { mailboxX = 400 + Math.random() * 1200; // stays in safe horizontal range attempts++; // Safety check to prevent infinite loop if (attempts > 50) { // If we can't find a spot, place it far from house if (houseX > 1024) { mailboxX = 500; // place on left side } else { mailboxX = 1500; // place on right side } break; } } while (mailboxX >= houseLeftEdge - 100 && mailboxX <= houseRightEdge + 100); mailbox.x = mailboxX; mailbox.y = mailboxZoneY + (Math.random() * 200 - 100); // Random horizontal flip if (Math.random() < 0.5) { mailbox.scaleX = -1; } else { mailbox.scaleX = 1; } mailboxes.push(mailbox); game.addChild(mailbox); // Ensure mailbox is drawn in front of house game.setChildIndex(mailbox, game.getChildIndex(currentHouse) + 1); return mailbox; } function shootNewspaper(power, angle) { var newspaper = new Newspaper(); newspaper.x = 1024; // slingshot center newspaper.y = 2200; // bottom // Use power + angle for velocity newspaper.vx = Math.cos(angle) * power; newspaper.vy = Math.sin(angle) * power; newspapers.push(newspaper); game.addChild(newspaper); LK.getSound('Paperthrow').play(); } function updateSlingshotBands(endX, endY) { bandLeft.removeChildren(); bandRight.removeChildren(); // Don't draw visible bands - keep containers for positioning only } function calculateScore(distance) { if (distance < 15) { return 5; } // Perfect hit if (distance < 25) { return 4; } // Great hit if (distance < 35) { return 3; } // Good hit if (distance < 45) { return 2; } // OK hit return 1; // Glancing hit } function payoutFor(distancePx) { if (distancePx <= 25) return { cents: 25, label: '+25¢ Perfect!' }; if (distancePx <= 60) return { cents: 15, label: '+15¢ Very close' }; if (distancePx <= 120) return { cents: 5, label: '+5¢ Close' }; return null; } function updateTrajectory(originX, originY, pullX, pullY) { // Clear any previous drawing trajectoryLine.removeChildren(); if (!isAiming) return; // Launch vector = opposite of pull var dx = originX - pullX; var dy = originY - pullY; var power = Math.sqrt(dx * dx + dy * dy) * 0.1; var angle = Math.atan2(dy, dx); // Launch velocity var vX = Math.cos(angle) * power; var vY = Math.sin(angle) * power; // Choose a projection distance (how long the line should be) var projectionTime = 80; // tweak for how far line extends var g = 0.3; // Predict end point of the line var endX = originX + vX * projectionTime; var endY = originY + vY * projectionTime - 0.5 * g * projectionTime * projectionTime; // Create line graphic var lineGraphic = trajectoryLine.addChild(LK.getAsset('trajectory', { width: Math.sqrt((endX - originX) * (endX - originX) + (endY - originY) * (endY - originY)), height: 4, color: 0x00ff00, anchorX: 0, anchorY: 0.5 })); lineGraphic.x = originX; lineGraphic.y = originY; lineGraphic.rotation = Math.atan2(endY - originY, endX - originX); lineGraphic.alpha = 0.7; } // Game input handlers - natural slingshot controls game.down = function (x, y, obj) { // Only allow aiming if player clicks/touches near the slingshot if (y > streetZoneY - 200) { isAiming = true; aimStartX = x; aimStartY = y; // Pull origin indicator is already positioned and visible // No need to reposition it during aiming } }; game.move = function (x, y, obj) { if (isAiming) { // Draw a preview trajectory updateTrajectory(1024, 2000, x, y); // Compute pull distance for power var dx = 1024 - x; var dy = 2200 - y; var pullDistance = Math.sqrt(dx * dx + dy * dy); var maxPull = 600; // clamp so bar doesn't overflow var percent = Math.min(1, pullDistance / maxPull); // Dynamic color based on power level var color = 0x00ff00; // green if (percent > 0.66) { color = 0xff0000; // red } else if (percent > 0.33) { color = 0xffff00; // yellow } // Re-create the fill shape with new color and width powerBarFill.destroy(); powerBarFill = LK.getAsset('powerBarFillShape', { width: percent * 300, color: color, anchorX: 0, anchorY: 0.5 }); powerBarFill.x = powerBarBG.x - 150; powerBarFill.y = powerBarBG.y; LK.gui.top.addChild(powerBarFill); } }; game.up = function (x, y, obj) { if (!isAiming || hasThrownThisTurn) return; var originX = 1024, originY = 2000; // slingshot base moved up by 100 pixels var pullX = x - originX; var pullY = y - originY; // Launch vector = opposite of pull var launchX = -pullX; var launchY = -pullY; // Power is proportional to pull distance var pullDistance = Math.sqrt(launchX * launchX + launchY * launchY); var powerScale = 0.15; // tweak this number to control speed var power = pullDistance * powerScale; // Angle from pull-back vector var angle = Math.atan2(launchY, launchX); if (power > 2) { shootNewspaper(power, angle); hasThrownThisTurn = true; } // Reset aiming visuals isAiming = false; trajectoryLine.removeChildren(); // Pull origin indicator stays visible powerBarFill.width = 0; }; game.update = function () { gameTime++; // Update sun position (moves across sky as timer) var timeProgress = gameTime / maxGameTime; sun.x = sunStartX + (sunEndX - sunStartX) * timeProgress; sun.y = sunBaseY - Math.sin(timeProgress * Math.PI) * sunArcHeight; // Sky color progression with smooth transitions var skyColors = [{ t: 0.0, color: 0xE6E6FA }, // Lavender { t: 0.2, color: 0xADD8E6 }, // Light Blue { t: 0.4, color: 0x87CEEB }, // Bright Blue { t: 0.7, color: 0xFFA500 }, // Orange { t: 1.0, color: 0x4B0082 } // Deep Purple ]; // Find the two colors to interpolate between for (var i = 0; i < skyColors.length - 1; i++) { var c1 = skyColors[i]; var c2 = skyColors[i + 1]; if (timeProgress >= c1.t && timeProgress <= c2.t) { var localT = (timeProgress - c1.t) / (c2.t - c1.t); var blended = lerpColor(c1.color, c2.color, localT); game.setBackgroundColor(blended); break; } } // Turn-based house cycle management if (turnComplete && !isHouseTransitioning && !currentHouse) { createHouse(); // mailbox is guaranteed in onFinish } // Watchdog: if for any reason a mailbox isn't present, create one. if (currentHouse && !isHouseTransitioning && mailboxes.length === 0) { ensureMailboxForTurn(); } // Update newspapers for (var i = newspapers.length - 1; i >= 0; i--) { var newspaper = newspapers[i]; if (!newspaper.active) { newspaper.destroy(); newspapers.splice(i, 1); continue; } // Check if newspaper went off screen without hitting (miss) if (newspaper.active && (newspaper.x > 2048 || newspaper.y > 2732)) { newspaper.active = false; // End the turn if no hit occurred if (currentHouse && !isHouseTransitioning) { removeHouse(currentHouse, function () { currentHouse = null; hasThrownThisTurn = false; // reset throw flag // Clear mailboxes for this turn for (var k = mailboxes.length - 1; k >= 0; k--) { mailboxes[k].destroy(); mailboxes.splice(k, 1); } }); } continue; } // Check mailbox collisions for (var j = 0; j < mailboxes.length; j++) { var m = mailboxes[j]; if (m.hit) continue; // enlarge hitbox with tolerance for better hit detection var gfx = m.children && m.children[0] ? m.children[0] : m; var halfW = (gfx.width || 100) * 0.5; var fullH = gfx.height || 100; var left = m.x - halfW; var right = m.x + halfW; var top = m.y - fullH; var bottom = m.y; // loosen bounds by 20px in all directions var tol = 20; var insideX = newspaper.x >= left - tol && newspaper.x <= right + tol; var insideY = newspaper.y >= top - tol && newspaper.y <= bottom + tol; if (!insideX || !insideY) continue; // compute accuracy off the mailbox "mouth" var mouthY = m.y - fullH * 0.6; var dx = newspaper.x - m.x; var dy = newspaper.y - mouthY; var dist = Math.sqrt(dx * dx + dy * dy); // if payoutFor returns null (too far), still give a glancing hit var res = payoutFor(dist) || { cents: 5, label: '+5¢ Close enough' }; pennies += res.cents; updatePaycheck(); m.hit = true; newspaper.active = false; // floating green payout text that fades away (large and bold) var _float = new Text2(res.label, { size: 72, fill: 0x00aa00, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); _float.anchor.set(0.5, 1); _float.x = m.x; _float.y = top - 10; game.addChild(_float); tween(_float, { y: _float.y - 80, alpha: 0 }, { duration: 800, onFinish: function onFinish() { _float.destroy(); } }); LK.effects.flashObject(m, 0x00ff00, 500); LK.getSound('MailboxHit').play(); // end turn after scoring (keep existing cleanup behavior) if (currentHouse && !isHouseTransitioning) { removeHouse(currentHouse, function () { currentHouse = null; hasThrownThisTurn = false; // reset throw flag for (var k = mailboxes.length - 1; k >= 0; k--) { mailboxes[k].destroy(); mailboxes.splice(k, 1); } }); } break; } } // Update and clean up mailboxes for (var i = mailboxes.length - 1; i >= 0; i--) { var mailbox = mailboxes[i]; if (!mailbox.active) { mailbox.destroy(); mailboxes.splice(i, 1); } } // Update and clean up houses for (var i = houses.length - 1; i >= 0; i--) { var house = houses[i]; if (!house.active) { house.destroy(); houses.splice(i, 1); } } // Check win condition if (pennies >= targetPennies) { LK.showYouWin(); } // Infinite-scroll treeline every frame with parallax effect if (currentHouse) { var houseSpeed = prevHouseX - currentHouse.x; // Apply parallax factor to create depth - trees move slower than houses var parallaxSpeed = houseSpeed * treeParallaxFactor; updateTreeline(parallaxSpeed); prevHouseX = currentHouse.x; } // Force aiming visuals to always render on top game.setChildIndex(trajectoryLine, game.children.length - 1); game.setChildIndex(pullOriginIndicator, game.children.length - 2); // Check lose condition if (gameTime >= maxGameTime) { LK.showGameOver(); } };
===================================================================
--- original.js
+++ change.js
@@ -96,11 +96,9 @@
LK.gui.addChild(comicIntro);
// Array of comic page images
var comicPages = [LK.getAsset('comicPage1', {
anchorX: 0.5,
- anchorY: 0.5,
- scaleX: 0.67,
- scaleY: 0.89
+ anchorY: 0.5
}), LK.getAsset('comicPage2', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('comicPage3', {