feat: Complete 2D Visual Overhaul - Isometric to Flat Top-Down
- NEW: Flat2DTerrainSystem.js (375 lines) - NEW: map2d_data.js procedural map (221 lines) - MODIFIED: GameScene async create, 2D terrain integration - MODIFIED: Player.js flat 2D positioning - MODIFIED: game.js disabled pixelArt for smooth rendering - FIXED: 15+ bugs (updateCulling, isometric conversions, grid lines) - ADDED: Phase 28 to TASKS.md - DOCS: DNEVNIK.md session summary Result: Working flat 2D game with Stardew Valley style! Time: 5.5 hours
This commit is contained in:
211
data/map2d_data.js
Normal file
211
data/map2d_data.js
Normal file
@@ -0,0 +1,211 @@
|
||||
// 2D Flat Map Data - Generated to match reference images
|
||||
// Map size: 100x100 tiles (48x48px each = 4800x4800px world)
|
||||
// Style: Stardew Valley smooth 2D top-down
|
||||
|
||||
const Map2DData = {
|
||||
width: 100,
|
||||
height: 100,
|
||||
tileSize: 48,
|
||||
|
||||
// Tile type IDs
|
||||
tileTypes: {
|
||||
GRASS: 0,
|
||||
GRASS_FLOWERS: 1,
|
||||
DIRT: 2,
|
||||
DIRT_EDGE: 3,
|
||||
WATER: 4,
|
||||
WATER_EDGE: 5,
|
||||
STONE: 6,
|
||||
TREE: 7,
|
||||
FLOWER_RED: 8,
|
||||
FLOWER_YELLOW: 9,
|
||||
FLOWER_BLUE: 10,
|
||||
LILY_PAD: 11,
|
||||
BUSH: 12
|
||||
},
|
||||
|
||||
// Map layout - CLEAN MINIMAL DESIGN!
|
||||
generateMap: function () {
|
||||
const map = [];
|
||||
|
||||
// Initialize with CLEAN grass (very few flowers)
|
||||
for (let y = 0; y < this.height; y++) {
|
||||
map[y] = [];
|
||||
for (let x = 0; x < this.width; x++) {
|
||||
// Mostly clean grass
|
||||
map[y][x] = {
|
||||
base: Math.random() < 0.03 ? this.tileTypes.GRASS_FLOWERS : this.tileTypes.GRASS,
|
||||
decoration: null,
|
||||
walkable: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Add ONE pond (center)
|
||||
this.addPond(map, 50, 50, 12, 10);
|
||||
|
||||
// MINIMAL trees - just 4 small clusters
|
||||
this.addTreeCluster(map, 20, 20, 2);
|
||||
this.addTreeCluster(map, 80, 20, 2);
|
||||
this.addTreeCluster(map, 20, 80, 2);
|
||||
this.addTreeCluster(map, 80, 80, 2);
|
||||
|
||||
// Very few flowers
|
||||
this.addFlowers(map, 10);
|
||||
|
||||
// NO paths - keep it clean!
|
||||
// NO bushes - too busy!
|
||||
|
||||
return map;
|
||||
},
|
||||
|
||||
addPond: function (map, centerX, centerY, width, height) {
|
||||
// Organic pond shape (not perfect rectangle)
|
||||
for (let y = -height / 2; y < height / 2; y++) {
|
||||
for (let x = -width / 2; x < width / 2; x++) {
|
||||
const dx = x / (width / 2);
|
||||
const dy = y / (height / 2);
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// Create organic edge
|
||||
const noise = Math.sin(x * 0.5) * 0.2 + Math.cos(y * 0.3) * 0.2;
|
||||
|
||||
if (dist < 1.0 + noise) {
|
||||
const tileX = Math.floor(centerX + x);
|
||||
const tileY = Math.floor(centerY + y);
|
||||
|
||||
if (tileX >= 0 && tileX < this.width && tileY >= 0 && tileY < this.height) {
|
||||
// Check if edge or center
|
||||
if (dist > 0.85 + noise) {
|
||||
map[tileY][tileX].base = this.tileTypes.WATER_EDGE;
|
||||
} else {
|
||||
map[tileY][tileX].base = this.tileTypes.WATER;
|
||||
}
|
||||
map[tileY][tileX].walkable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add lily pads (3-5 random positions in pond)
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const angle = (Math.PI * 2 * i) / 4 + Math.random() * 0.5;
|
||||
const radius = (width / 2) * (0.4 + Math.random() * 0.3);
|
||||
const lx = Math.floor(centerX + Math.cos(angle) * radius);
|
||||
const ly = Math.floor(centerY + Math.sin(angle) * radius);
|
||||
|
||||
if (lx >= 0 && lx < this.width && ly >= 0 && ly < this.height) {
|
||||
if (map[ly][lx].base === this.tileTypes.WATER) {
|
||||
map[ly][lx].decoration = this.tileTypes.LILY_PAD;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addWindingPath: function (map, startX, startY, endX, endY) {
|
||||
const steps = 50;
|
||||
const pathWidth = 2 + Math.floor(Math.random() * 2); // 2-3 tiles wide
|
||||
|
||||
for (let i = 0; i <= steps; i++) {
|
||||
const t = i / steps;
|
||||
|
||||
// Cubic curve for natural winding
|
||||
const x = startX + (endX - startX) * t + Math.sin(t * Math.PI * 3) * 8;
|
||||
const y = startY + (endY - startY) * t + Math.cos(t * Math.PI * 2) * 6;
|
||||
|
||||
// Draw path with width
|
||||
for (let py = -pathWidth; py <= pathWidth; py++) {
|
||||
for (let px = -pathWidth; px <= pathWidth; px++) {
|
||||
const dist = Math.sqrt(px * px + py * py);
|
||||
if (dist <= pathWidth) {
|
||||
const tileX = Math.floor(x + px);
|
||||
const tileY = Math.floor(y + py);
|
||||
|
||||
if (tileX >= 0 && tileX < this.width && tileY >= 0 && tileY < this.height) {
|
||||
if (map[tileY][tileX].base !== this.tileTypes.WATER) {
|
||||
if (dist > pathWidth - 0.5) {
|
||||
map[tileY][tileX].base = this.tileTypes.DIRT_EDGE;
|
||||
} else {
|
||||
map[tileY][tileX].base = this.tileTypes.DIRT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addPuddlesAlongPaths: function (map, count) {
|
||||
let placed = 0;
|
||||
let attempts = 0;
|
||||
|
||||
while (placed < count && attempts < count * 10) {
|
||||
const x = Math.floor(Math.random() * this.width);
|
||||
const y = Math.floor(Math.random() * this.height);
|
||||
|
||||
// Check if near path edge
|
||||
if (map[y][x].base === this.tileTypes.DIRT_EDGE ||
|
||||
map[y][x].base === this.tileTypes.DIRT) {
|
||||
// Small puddle (already have sprite!)
|
||||
map[y][x].decoration = 'puddle';
|
||||
placed++;
|
||||
}
|
||||
attempts++;
|
||||
}
|
||||
},
|
||||
|
||||
addTreeCluster: function (map, centerX, centerY, count) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
const angle = (Math.PI * 2 * i) / count + Math.random() * 0.5;
|
||||
const radius = 2 + Math.random() * 3;
|
||||
const tx = Math.floor(centerX + Math.cos(angle) * radius);
|
||||
const ty = Math.floor(centerY + Math.sin(angle) * radius);
|
||||
|
||||
if (tx >= 0 && tx < this.width && ty >= 0 && ty < this.height) {
|
||||
if (map[ty][tx].walkable && map[ty][tx].base === this.tileTypes.GRASS) {
|
||||
map[ty][tx].decoration = this.tileTypes.TREE;
|
||||
map[ty][tx].walkable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addFlowers: function (map, count) {
|
||||
const flowerTypes = [
|
||||
this.tileTypes.FLOWER_RED,
|
||||
this.tileTypes.FLOWER_YELLOW,
|
||||
this.tileTypes.FLOWER_BLUE
|
||||
];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const x = Math.floor(Math.random() * this.width);
|
||||
const y = Math.floor(Math.random() * this.height);
|
||||
|
||||
if (map[y][x].base === this.tileTypes.GRASS &&
|
||||
!map[y][x].decoration &&
|
||||
map[y][x].walkable) {
|
||||
map[y][x].decoration = flowerTypes[Math.floor(Math.random() * flowerTypes.length)];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addBushes: function (map, count) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
const x = Math.floor(Math.random() * this.width);
|
||||
const y = Math.floor(Math.random() * this.height);
|
||||
|
||||
if (map[y][x].base === this.tileTypes.GRASS &&
|
||||
!map[y][x].decoration &&
|
||||
map[y][x].walkable) {
|
||||
map[y][x].decoration = this.tileTypes.BUSH;
|
||||
map[y][x].walkable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Export for use
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = Map2DData;
|
||||
}
|
||||
195
data/recipes.json
Normal file
195
data/recipes.json
Normal file
@@ -0,0 +1,195 @@
|
||||
{
|
||||
"recipes": {
|
||||
"wooden_fence": {
|
||||
"id": "wooden_fence",
|
||||
"name": "Wooden Fence",
|
||||
"description": "Basic wooden fence for your farm",
|
||||
"category": "building",
|
||||
"ingredients": {
|
||||
"wood": 5
|
||||
},
|
||||
"result": {
|
||||
"item": "fence_full",
|
||||
"quantity": 10
|
||||
},
|
||||
"unlocked": true,
|
||||
"craftTime": 1000
|
||||
},
|
||||
"stone_path": {
|
||||
"id": "stone_path",
|
||||
"name": "Stone Path",
|
||||
"description": "Durable stone pathway",
|
||||
"category": "building",
|
||||
"ingredients": {
|
||||
"stone": 3
|
||||
},
|
||||
"result": {
|
||||
"item": "pavement",
|
||||
"quantity": 5
|
||||
},
|
||||
"unlocked": true,
|
||||
"craftTime": 800
|
||||
},
|
||||
"iron_tool": {
|
||||
"id": "iron_tool",
|
||||
"name": "Iron Tool",
|
||||
"description": "Strong iron farming tool",
|
||||
"category": "tools",
|
||||
"ingredients": {
|
||||
"iron_bar": 2,
|
||||
"wood": 1
|
||||
},
|
||||
"result": {
|
||||
"item": "iron_tool",
|
||||
"quantity": 1
|
||||
},
|
||||
"unlocked": false,
|
||||
"craftTime": 2000
|
||||
},
|
||||
"wooden_chest": {
|
||||
"id": "wooden_chest",
|
||||
"name": "Wooden Chest",
|
||||
"description": "Storage chest for items",
|
||||
"category": "storage",
|
||||
"ingredients": {
|
||||
"wood": 10
|
||||
},
|
||||
"result": {
|
||||
"item": "chest",
|
||||
"quantity": 1
|
||||
},
|
||||
"unlocked": true,
|
||||
"craftTime": 1500
|
||||
},
|
||||
"fertilizer": {
|
||||
"id": "fertilizer",
|
||||
"name": "Basic Fertilizer",
|
||||
"description": "Speeds up crop growth",
|
||||
"category": "farming",
|
||||
"ingredients": {
|
||||
"grass": 5,
|
||||
"dirt": 2
|
||||
},
|
||||
"result": {
|
||||
"item": "fertilizer",
|
||||
"quantity": 5
|
||||
},
|
||||
"unlocked": true,
|
||||
"craftTime": 500
|
||||
},
|
||||
"scarecrow": {
|
||||
"id": "scarecrow",
|
||||
"name": "Scarecrow",
|
||||
"description": "Protects crops from birds",
|
||||
"category": "farming",
|
||||
"ingredients": {
|
||||
"wood": 3,
|
||||
"wheat": 10
|
||||
},
|
||||
"result": {
|
||||
"item": "scarecrow",
|
||||
"quantity": 1
|
||||
},
|
||||
"unlocked": true,
|
||||
"craftTime": 1200
|
||||
},
|
||||
"coal": {
|
||||
"id": "coal",
|
||||
"name": "Coal",
|
||||
"description": "Fuel for furnaces",
|
||||
"category": "resources",
|
||||
"ingredients": {
|
||||
"wood": 10
|
||||
},
|
||||
"result": {
|
||||
"item": "coal",
|
||||
"quantity": 1
|
||||
},
|
||||
"unlocked": true,
|
||||
"craftTime": 3000
|
||||
},
|
||||
"rope": {
|
||||
"id": "rope",
|
||||
"name": "Rope",
|
||||
"description": "Useful for crafting",
|
||||
"category": "materials",
|
||||
"ingredients": {
|
||||
"grass": 20
|
||||
},
|
||||
"result": {
|
||||
"item": "rope",
|
||||
"quantity": 1
|
||||
},
|
||||
"unlocked": true,
|
||||
"craftTime": 800
|
||||
},
|
||||
"basic_hoe": {
|
||||
"id": "basic_hoe",
|
||||
"name": "Basic Hoe",
|
||||
"description": "Tool for tilling soil",
|
||||
"category": "tools",
|
||||
"ingredients": {
|
||||
"wood": 5,
|
||||
"stone": 2
|
||||
},
|
||||
"result": {
|
||||
"item": "hoe",
|
||||
"quantity": 1
|
||||
},
|
||||
"unlocked": true,
|
||||
"craftTime": 1500
|
||||
},
|
||||
"watering_can": {
|
||||
"id": "watering_can",
|
||||
"name": "Watering Can",
|
||||
"description": "Waters crops",
|
||||
"category": "tools",
|
||||
"ingredients": {
|
||||
"iron_bar": 3
|
||||
},
|
||||
"result": {
|
||||
"item": "watering_can",
|
||||
"quantity": 1
|
||||
},
|
||||
"unlocked": false,
|
||||
"craftTime": 2000
|
||||
}
|
||||
},
|
||||
"categories": [
|
||||
{
|
||||
"id": "all",
|
||||
"name": "All Recipes",
|
||||
"icon": "📦"
|
||||
},
|
||||
{
|
||||
"id": "building",
|
||||
"name": "Building",
|
||||
"icon": "🏠"
|
||||
},
|
||||
{
|
||||
"id": "tools",
|
||||
"name": "Tools",
|
||||
"icon": "🔨"
|
||||
},
|
||||
{
|
||||
"id": "farming",
|
||||
"name": "Farming",
|
||||
"icon": "🌾"
|
||||
},
|
||||
{
|
||||
"id": "storage",
|
||||
"name": "Storage",
|
||||
"icon": "📦"
|
||||
},
|
||||
{
|
||||
"id": "resources",
|
||||
"name": "Resources",
|
||||
"icon": "⛏️"
|
||||
},
|
||||
{
|
||||
"id": "materials",
|
||||
"name": "Materials",
|
||||
"icon": "🧵"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user