diff --git a/src/main.js b/src/main.js index c946be4..236ee40 100644 --- a/src/main.js +++ b/src/main.js @@ -15,12 +15,44 @@ class ForestScene extends Phaser.Scene { this.touchPointers = { left: new Set(), right: new Set() }; } + preload() { + this.cameras.main.setBackgroundColor('#10251d'); + + const cx = this.scale.width / 2; + const cy = this.scale.height / 2; + const label = this.add.text(cx, cy - 28, 'Growing the forest…', { + fontFamily: 'Georgia, serif', + fontSize: '20px', + color: '#e7ead9' + }).setOrigin(0.5); + + const track = this.add.rectangle(cx, cy + 12, 220, 8, 0x294235).setOrigin(0.5); + const fill = this.add.rectangle(cx - 110, cy + 12, 0, 8, 0xb6c97a).setOrigin(0, 0.5); + + this.load.on('progress', value => { + fill.width = 220 * value; + }); + + this.load.on('complete', () => { + label.destroy(); + track.destroy(); + fill.destroy(); + }); + + this.load.image('player-sheet', '/assets/player-sheet.png'); + this.load.image('flora-sheet', '/assets/flora-sheet.png'); + this.load.image('fauna-sheet', '/assets/fauna-sheet.png'); + this.load.image('terrain-sheet', '/assets/terrain-sheet.png'); + } + create() { this.physics.world.setBounds(0, 0, WORLD_WIDTH, WORLD_HEIGHT); this.cameras.main.setBounds(0, 0, WORLD_WIDTH, WORLD_HEIGHT); this.cameras.main.setBackgroundColor('#8fc7a2'); this.createTextures(); + this.createArtFrames(); + this.createAnimations(); this.createBackdrop(); this.createWorld(); this.createPlayer(); @@ -70,6 +102,105 @@ class ForestScene extends Phaser.Scene { }); } + + createArtFrames() { + const addFrame = (textureKey, name, x, y, width, height) => { + this.textures.get(textureKey).add(name, 0, x, y, width, height); + }; + + [ + ['idle0', 0, 70, 145, 185], + ['idle1', 130, 70, 150, 185], + ['idle2', 260, 70, 150, 185], + ['idle3', 385, 70, 170, 185], + ['run0', 575, 75, 165, 180], + ['run1', 715, 75, 165, 180], + ['run2', 855, 75, 165, 180], + ['run3', 995, 75, 165, 180], + ['run4', 1130, 75, 175, 180], + ['run5', 1270, 75, 178, 180], + ['jump0', 0, 315, 190, 195], + ['jump1', 175, 315, 200, 195], + ['fall0', 470, 315, 170, 195], + ['fall1', 625, 315, 175, 195], + ['land0', 955, 325, 225, 185], + ['land1', 1185, 325, 205, 185] + ].forEach(([name, x, y, w, h]) => addFrame('player-sheet', name, x, y, w, h)); + + addFrame('terrain-sheet', 'ground-main', 10, 18, 500, 132); + addFrame('terrain-sheet', 'log-platform', 10, 315, 290, 105); + addFrame('terrain-sheet', 'root-platform', 995, 650, 430, 205); + + addFrame('flora-sheet', 'bush', 5, 18, 260, 165); + addFrame('flora-sheet', 'fern-cluster', 450, 0, 390, 180); + addFrame('flora-sheet', 'bluebells', 0, 170, 365, 150); + addFrame('flora-sheet', 'mush-red', 0, 310, 185, 220); + addFrame('flora-sheet', 'mush-brown', 370, 310, 215, 220); + addFrame('flora-sheet', 'mush-orange', 545, 315, 145, 215); + addFrame('flora-sheet', 'mush-purple', 670, 310, 210, 220); + addFrame('flora-sheet', 'mush-blue', 930, 310, 310, 220); + addFrame('flora-sheet', 'acorn-art', 10, 525, 90, 120); + + const frogFrames = [ + [40, 500, 165, 150], [200, 500, 190, 150], [390, 500, 190, 150], + [570, 500, 220, 150], [810, 500, 175, 150] + ]; + frogFrames.forEach(([x, y, w, h], i) => addFrame('fauna-sheet', `frog${i}`, x, y, w, h)); + + const hedgehogFrames = [ + [0, 45, 155, 145], [145, 45, 155, 145], [285, 45, 160, 145], + [425, 45, 160, 145], [565, 45, 160, 145], [700, 45, 155, 145] + ]; + hedgehogFrames.forEach(([x, y, w, h], i) => addFrame('fauna-sheet', `hedgehog${i}`, x, y, w, h)); + } + + createAnimations() { + const frames = (texture, names) => names.map(frame => ({ key: texture, frame })); + + this.anims.create({ + key: 'squirrel-idle', + frames: frames('player-sheet', ['idle0', 'idle1', 'idle2', 'idle3']), + frameRate: 4, + repeat: -1 + }); + + this.anims.create({ + key: 'squirrel-run', + frames: frames('player-sheet', ['run0', 'run1', 'run2', 'run3', 'run4', 'run5']), + frameRate: 12, + repeat: -1 + }); + + this.anims.create({ + key: 'squirrel-jump', + frames: frames('player-sheet', ['jump0', 'jump1']), + frameRate: 8, + repeat: 0 + }); + + this.anims.create({ + key: 'squirrel-fall', + frames: frames('player-sheet', ['fall0', 'fall1']), + frameRate: 5, + repeat: -1 + }); + + this.anims.create({ + key: 'frog-hop', + frames: frames('fauna-sheet', ['frog0', 'frog1', 'frog2', 'frog3', 'frog4']), + frameRate: 7, + repeat: -1, + repeatDelay: 1400 + }); + + this.anims.create({ + key: 'hedgehog-walk', + frames: frames('fauna-sheet', ['hedgehog0', 'hedgehog1', 'hedgehog2', 'hedgehog3', 'hedgehog4', 'hedgehog5']), + frameRate: 8, + repeat: -1 + }); + } + createTextures() { const g = this.make.graphics({ add: false }); @@ -234,11 +365,16 @@ class ForestScene extends Phaser.Scene { createWorld() { this.platforms = this.physics.add.staticGroup(); - const ground = this.add.rectangle(WORLD_WIDTH / 2, GROUND_Y + 46, WORLD_WIDTH, 100, 0x263c2d); + const ground = this.add.rectangle(WORLD_WIDTH / 2, GROUND_Y + 46, WORLD_WIDTH, 100, 0x263c2d, 0); this.physics.add.existing(ground, true); this.platforms.add(ground); - this.add.rectangle(WORLD_WIDTH / 2, GROUND_Y + 2, WORLD_WIDTH, 20, 0x55714a); + for (let x = 215; x < WORLD_WIDTH + 220; x += 430) { + this.add.image(x, GROUND_Y - 5, 'terrain-sheet', 'ground-main') + .setOrigin(0.5, 0) + .setScale(0.9) + .setDepth(3); + } const branchPositions = [ [500, 545], [720, 505], [900, 465], @@ -250,24 +386,45 @@ class ForestScene extends Phaser.Scene { ]; branchPositions.forEach(([x, y], index) => { - const branch = this.platforms.create(x, y, 'branch'); - branch.setScale(index % 3 === 0 ? 1.35 : 1); - branch.refreshBody(); + const scale = index % 3 === 0 ? 0.62 : 0.55; + const visual = this.add.image(x, y + 15, 'terrain-sheet', 'log-platform') + .setScale(scale) + .setDepth(6); + + const collider = this.add.rectangle( + x, + y, + Math.max(130, visual.displayWidth * 0.82), + 16, + 0x000000, + 0 + ); + this.physics.add.existing(collider, true); + this.platforms.add(collider); }); - for (let x = 90; x < WORLD_WIDTH - 120; x += 105) { + for (let x = 120; x < WORLD_WIDTH - 120; x += 180) { const n = Math.sin(x * 0.013) + Math.cos(x * 0.021); - if (n > -0.15) { - this.add.image(x, GROUND_Y - 10, 'fern') + if (n > -0.3) { + this.add.image(x, GROUND_Y - 2, 'flora-sheet', 'fern-cluster') .setOrigin(0.5, 1) - .setScale(0.65 + ((x % 70) / 160)) - .setAlpha(0.85); + .setScale(0.2 + ((x % 90) / 900)) + .setAlpha(0.96) + .setDepth(5); } - if (x % 315 < 130) { - this.add.image(x + 34, GROUND_Y - 5, 'flower') + if (x % 540 < 190) { + this.add.image(x + 65, GROUND_Y - 2, 'flora-sheet', 'bluebells') .setOrigin(0.5, 1) - .setScale(0.7 + ((x % 50) / 170)); + .setScale(0.2) + .setDepth(5); + } + + if (x % 900 < 190) { + this.add.image(x - 65, GROUND_Y - 3, 'flora-sheet', 'bush') + .setOrigin(0.5, 1) + .setScale(0.34) + .setDepth(4); } } @@ -296,13 +453,14 @@ class ForestScene extends Phaser.Scene { } createPlayer() { - this.player = this.physics.add.sprite(160, 520, 'squirrel'); - this.player.setScale(1.12); + this.player = this.physics.add.sprite(160, 520, 'player-sheet', 'idle0'); + this.player.setScale(0.5); this.player.setCollideWorldBounds(true); this.player.setDragX(900); this.player.setMaxVelocity(330, 760); - this.player.body.setSize(34, 47); - this.player.body.setOffset(12, 7); + this.player.body.setSize(64, 105, true); + this.player.body.setOffset(40, 62); + this.player.play('squirrel-idle'); } createCollectibles() { @@ -314,28 +472,34 @@ class ForestScene extends Phaser.Scene { [3370, 345], [3610, 555], [3800, 440], [4050, 555], [4230, 365], [4470, 555], [4680, 460], [4910, 555], [5100, 370] ].forEach(([x, y], i) => { - const acorn = this.acornGroup.create(x, y, 'acorn'); + const acorn = this.acornGroup.create(x, y, 'flora-sheet', 'acorn-art'); + acorn.setScale(0.42); acorn.setBounce(0.2); + acorn.body.setSize(55, 70, true); acorn.setData('phase', i * 0.4); }); this.mushroomGroup = this.physics.add.group({ allowGravity: true }); const mushrooms = [ - [720, 575, 'mushroom-red', 'Scarlet bonnet'], - [1700, 360, 'mushroom-purple', 'Amethyst veil'], - [2860, 575, 'mushroom-gold', 'Honey lantern'], - [4170, 370, 'mushroom-red', 'Foxfire cap'], - [4860, 575, 'mushroom-purple', 'Moon gill'] + [720, 575, 'mush-red', 'Scarlet bonnet'], + [1700, 360, 'mush-purple', 'Amethyst veil'], + [2860, 575, 'mush-orange', 'Honey lantern'], + [4170, 370, 'mush-brown', 'Foxfire cap'], + [4860, 575, 'mush-blue', 'Moon gill'] ]; - mushrooms.forEach(([x, y, texture, name]) => { - const mushroom = this.mushroomGroup.create(x, y, texture); + mushrooms.forEach(([x, y, frame, name], index) => { + const mushroom = this.mushroomGroup.create(x, y, 'flora-sheet', frame); + const scale = frame === 'mush-blue' ? 0.2 : 0.24; + mushroom.setScale(scale); mushroom.setData('name', name); mushroom.setBounce(0.1); + mushroom.body.setSize(mushroom.width * 0.52, mushroom.height * 0.62, true); this.tweens.add({ targets: mushroom, - y: y - 6, - duration: 900 + (x % 400), + scaleX: scale * 1.04, + scaleY: scale * 1.04, + duration: 900 + index * 120, yoyo: true, repeat: -1, ease: 'Sine.inOut' @@ -362,15 +526,31 @@ class ForestScene extends Phaser.Scene { }); [1120, 2600, 3980].forEach((x, i) => { - const frog = this.add.image(x, GROUND_Y - 8, 'frog').setOrigin(0.5, 1); + const frog = this.add.sprite(x, GROUND_Y - 2, 'fauna-sheet', 'frog0') + .setOrigin(0.5, 1) + .setScale(0.32) + .setDepth(7); + frog.play('frog-hop'); + frog.anims.setProgress((i * 0.27) % 1); + }); + + [1450, 3450].forEach((x, i) => { + const hedgehog = this.add.sprite(x, GROUND_Y - 3, 'fauna-sheet', 'hedgehog0') + .setOrigin(0.5, 1) + .setScale(0.34) + .setDepth(7) + .setFlipX(i % 2 === 1); + hedgehog.play('hedgehog-walk'); + this.tweens.add({ - targets: frog, - y: GROUND_Y - 20, - duration: 220, - delay: 2300 + i * 900, + targets: hedgehog, + x: x + (i % 2 === 0 ? 190 : -190), + duration: 5200, yoyo: true, repeat: -1, - repeatDelay: 3000 + i * 500 + ease: 'Sine.inOut', + onYoyo: () => hedgehog.toggleFlipX(), + onRepeat: () => hedgehog.toggleFlipX() }); }); @@ -507,8 +687,7 @@ class ForestScene extends Phaser.Scene { if (!this.player?.body) return; if (this.player.body.blocked.down || this.player.body.touching.down) { this.player.setVelocityY(-520); - this.player.setAngularVelocity(this.player.flipX ? -25 : 25); - this.time.delayedCall(180, () => this.player?.setAngularVelocity(0)); + this.player.play('squirrel-jump', true); } } @@ -588,8 +767,17 @@ class ForestScene extends Phaser.Scene { const grounded = this.player.body.blocked.down || this.player.body.touching.down; if (grounded) { this.player.setAngle(0); + if (Math.abs(this.player.body.velocity.x) > 35) { + this.player.play('squirrel-run', true); + } else { + this.player.play('squirrel-idle', true); + } + } else if (this.player.body.velocity.y < 0) { + this.player.play('squirrel-jump', true); + this.player.setAngle(Phaser.Math.Clamp(this.player.body.velocity.x * 0.012, -5, 5)); } else { - this.player.setAngle(Phaser.Math.Clamp(this.player.body.velocity.x * 0.025, -8, 8)); + this.player.play('squirrel-fall', true); + this.player.setAngle(Phaser.Math.Clamp(this.player.body.velocity.x * 0.012, -5, 5)); } this.acornGroup.children.iterate(acorn => {