✨ implement unified tile system (Issue #14)
- Tree seedlings: plant tree_seed on grass via farming tool; two-stage growth (sprout → sapling → young tree, ~1 min/stage); matures into a harvestable FOREST resource tile - Tile recovery: Nisse chops start a 5-min DARK_GRASS→GRASS timer; terrain canvas updated live via WorldSystem.refreshTerrainTile() - New TreeSeedlingSystem manages sprites, growth ticking, maturation - BootScene generates seedling_0/1/2 textures procedurally - FarmingSystem adds tree_seed to tool cycle (F key) - Stockpile panel shows tree_seed (default: 5); panel height adjusted - StateManager v5: treeSeedlings + tileRecovery in WorldState - WorldSystem uses CanvasTexture for live single-pixel updates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ export class BootScene extends Phaser.Scene {
|
||||
this.buildResourceTextures()
|
||||
this.buildPlayerTexture()
|
||||
this.buildCropTextures()
|
||||
this.buildSeedlingTextures()
|
||||
this.buildUITextures()
|
||||
this.buildVillagerAndBuildingTextures()
|
||||
this.generateWorldIfNeeded()
|
||||
@@ -287,6 +288,40 @@ export class BootScene extends Phaser.Scene {
|
||||
g3.generateTexture('crop_carrot_3', W, H); g3.destroy()
|
||||
}
|
||||
|
||||
// ─── Tree seedling textures (3 growth stages) ────────────────────────────
|
||||
|
||||
/**
|
||||
* Generates textures for the three tree-seedling growth stages:
|
||||
* seedling_0 – small sprout
|
||||
* seedling_1 – sapling with leaves
|
||||
* seedling_2 – young tree (about to mature into a FOREST tile)
|
||||
*/
|
||||
private buildSeedlingTextures(): void {
|
||||
// Stage 0: tiny sprout
|
||||
const g0 = this.add.graphics()
|
||||
g0.fillStyle(0x6D4C41); g0.fillRect(10, 20, 4, 10)
|
||||
g0.fillStyle(0x66BB6A); g0.fillEllipse(12, 16, 12, 8)
|
||||
g0.fillStyle(0x4CAF50); g0.fillEllipse(12, 13, 8, 6)
|
||||
g0.generateTexture('seedling_0', 24, 32); g0.destroy()
|
||||
|
||||
// Stage 1: sapling
|
||||
const g1 = this.add.graphics()
|
||||
g1.fillStyle(0x6D4C41); g1.fillRect(9, 15, 5, 16)
|
||||
g1.fillStyle(0x4CAF50); g1.fillCircle(12, 12, 8)
|
||||
g1.fillStyle(0x66BB6A, 0.7); g1.fillCircle(7, 16, 5); g1.fillCircle(17, 16, 5)
|
||||
g1.fillStyle(0x81C784); g1.fillCircle(12, 8, 5)
|
||||
g1.generateTexture('seedling_1', 24, 32); g1.destroy()
|
||||
|
||||
// Stage 2: young tree (mature, ready to become a resource)
|
||||
const g2 = this.add.graphics()
|
||||
g2.fillStyle(0x000000, 0.15); g2.fillEllipse(12, 28, 16, 6)
|
||||
g2.fillStyle(0x6D4C41); g2.fillRect(9, 14, 6, 14)
|
||||
g2.fillStyle(0x2E7D32); g2.fillCircle(12, 9, 10)
|
||||
g2.fillStyle(0x388E3C); g2.fillCircle(7, 13, 7); g2.fillCircle(17, 13, 7)
|
||||
g2.fillStyle(0x43A047); g2.fillCircle(12, 6, 7)
|
||||
g2.generateTexture('seedling_2', 24, 32); g2.destroy()
|
||||
}
|
||||
|
||||
// ─── UI panel texture ─────────────────────────────────────────────────────
|
||||
|
||||
private buildUITextures(): void {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Phaser from 'phaser'
|
||||
import { AUTOSAVE_INTERVAL, TILE_SIZE } from '../config'
|
||||
import { TileType } from '../types'
|
||||
import type { BuildingType } from '../types'
|
||||
import { stateManager } from '../StateManager'
|
||||
import { LocalAdapter } from '../NetworkAdapter'
|
||||
@@ -10,6 +11,7 @@ import { BuildingSystem } from '../systems/BuildingSystem'
|
||||
import { FarmingSystem } from '../systems/FarmingSystem'
|
||||
import { VillagerSystem } from '../systems/VillagerSystem'
|
||||
import { DebugSystem } from '../systems/DebugSystem'
|
||||
import { TreeSeedlingSystem } from '../systems/TreeSeedlingSystem'
|
||||
|
||||
export class GameScene extends Phaser.Scene {
|
||||
private adapter!: LocalAdapter
|
||||
@@ -20,6 +22,7 @@ export class GameScene extends Phaser.Scene {
|
||||
private farmingSystem!: FarmingSystem
|
||||
villagerSystem!: VillagerSystem
|
||||
debugSystem!: DebugSystem
|
||||
private treeSeedlingSystem!: TreeSeedlingSystem
|
||||
private autosaveTimer = 0
|
||||
private menuOpen = false
|
||||
|
||||
@@ -37,9 +40,10 @@ export class GameScene extends Phaser.Scene {
|
||||
this.resourceSystem = new ResourceSystem(this, this.adapter)
|
||||
this.buildingSystem = new BuildingSystem(this, this.adapter)
|
||||
this.farmingSystem = new FarmingSystem(this, this.adapter)
|
||||
this.villagerSystem = new VillagerSystem(this, this.adapter, this.worldSystem)
|
||||
this.villagerSystem = new VillagerSystem(this, this.adapter, this.worldSystem)
|
||||
this.villagerSystem.init(this.resourceSystem, this.farmingSystem)
|
||||
this.debugSystem = new DebugSystem(this, this.villagerSystem, this.worldSystem)
|
||||
this.treeSeedlingSystem = new TreeSeedlingSystem(this, this.adapter, this.worldSystem)
|
||||
this.debugSystem = new DebugSystem(this, this.villagerSystem, this.worldSystem)
|
||||
|
||||
this.worldSystem.create()
|
||||
this.renderPersistentObjects()
|
||||
@@ -57,8 +61,12 @@ export class GameScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
this.farmingSystem.create()
|
||||
this.farmingSystem.onMessage = (msg) => this.events.emit('toast', msg)
|
||||
this.farmingSystem.onToolChange = (tool, label) => this.events.emit('farmToolChanged', tool, label)
|
||||
this.farmingSystem.onMessage = (msg) => this.events.emit('toast', msg)
|
||||
this.farmingSystem.onToolChange = (tool, label) => this.events.emit('farmToolChanged', tool, label)
|
||||
this.farmingSystem.onPlantTreeSeed = (tileX, tileY, tile) =>
|
||||
this.treeSeedlingSystem.plantSeedling(tileX, tileY, tile)
|
||||
|
||||
this.treeSeedlingSystem.create()
|
||||
|
||||
this.villagerSystem.create()
|
||||
this.villagerSystem.onMessage = (msg) => this.events.emit('toast', msg)
|
||||
@@ -70,6 +78,8 @@ export class GameScene extends Phaser.Scene {
|
||||
this.adapter.onAction = (action) => {
|
||||
if (action.type === 'CHANGE_TILE') {
|
||||
this.worldSystem.setTile(action.tileX, action.tileY, action.tile)
|
||||
} else if (action.type === 'SPAWN_RESOURCE') {
|
||||
this.resourceSystem.spawnResourcePublic(action.resource)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,9 +112,17 @@ export class GameScene extends Phaser.Scene {
|
||||
|
||||
this.resourceSystem.update(delta)
|
||||
this.farmingSystem.update(delta)
|
||||
this.treeSeedlingSystem.update(delta)
|
||||
this.villagerSystem.update(delta)
|
||||
this.debugSystem.update()
|
||||
|
||||
// Tick tile-recovery timers; refresh canvas for any tiles that reverted to GRASS
|
||||
const recovered = stateManager.tickTileRecovery(delta)
|
||||
for (const key of recovered) {
|
||||
const [tx, ty] = key.split(',').map(Number)
|
||||
this.worldSystem.refreshTerrainTile(tx, ty, TileType.GRASS)
|
||||
}
|
||||
|
||||
this.events.emit('cameraMoved', this.cameraSystem.getCenterTile())
|
||||
this.buildingSystem.update()
|
||||
|
||||
@@ -144,6 +162,7 @@ export class GameScene extends Phaser.Scene {
|
||||
this.resourceSystem.destroy()
|
||||
this.buildingSystem.destroy()
|
||||
this.farmingSystem.destroy()
|
||||
this.treeSeedlingSystem.destroy()
|
||||
this.villagerSystem.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { stateManager } from '../StateManager'
|
||||
|
||||
const ITEM_ICONS: Record<string, string> = {
|
||||
wood: '🪵', stone: '🪨', wheat_seed: '🌱', carrot_seed: '🥕',
|
||||
wheat: '🌾', carrot: '🧡',
|
||||
wheat: '🌾', carrot: '🧡', tree_seed: '🌲',
|
||||
}
|
||||
|
||||
export class UIScene extends Phaser.Scene {
|
||||
@@ -119,14 +119,14 @@ export class UIScene extends Phaser.Scene {
|
||||
/** Creates the stockpile panel in the top-right corner with item rows and population count. */
|
||||
private createStockpilePanel(): void {
|
||||
const x = this.scale.width - 178, y = 10
|
||||
this.stockpilePanel = this.add.rectangle(x, y, 168, 165, 0x000000, 0.72).setOrigin(0, 0).setScrollFactor(0).setDepth(100)
|
||||
this.stockpilePanel = this.add.rectangle(x, y, 168, 187, 0x000000, 0.72).setOrigin(0, 0).setScrollFactor(0).setDepth(100)
|
||||
this.stockpileTitleText = this.add.text(x + 10, y + 7, '⚡ STOCKPILE', { fontSize: '11px', color: '#aaaaaa', fontFamily: 'monospace' }).setScrollFactor(0).setDepth(101)
|
||||
const items = ['wood','stone','wheat_seed','carrot_seed','wheat','carrot'] as const
|
||||
const items = ['wood','stone','wheat_seed','carrot_seed','tree_seed','wheat','carrot'] as const
|
||||
items.forEach((item, i) => {
|
||||
const t = this.add.text(x + 10, y + 26 + i * 22, `${ITEM_ICONS[item]} ${item}: 0`, { fontSize: '13px', color: '#88dd88', fontFamily: 'monospace' }).setScrollFactor(0).setDepth(101)
|
||||
this.stockpileTexts.set(item, t)
|
||||
})
|
||||
this.popText = this.add.text(x + 10, y + 145, '👥 Nisse: 0 / 0', { fontSize: '11px', color: '#aaaaaa', fontFamily: 'monospace' }).setScrollFactor(0).setDepth(101)
|
||||
this.popText = this.add.text(x + 10, y + 167, '👥 Nisse: 0 / 0', { fontSize: '11px', color: '#aaaaaa', fontFamily: 'monospace' }).setScrollFactor(0).setDepth(101)
|
||||
}
|
||||
|
||||
/** Refreshes all item quantities and colors in the stockpile panel. */
|
||||
|
||||
Reference in New Issue
Block a user