Asset Generation: Campfire, Tent, Sleeping Bag, UI Elements (Health, Inventory, Dialog). Cleaned GrassScene.

This commit is contained in:
2026-01-28 07:45:24 +01:00
parent b686be33ab
commit 94565adffc
69 changed files with 5208 additions and 122 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -38,3 +38,61 @@ The "Probna Farma" scene is now a fully interactive, atmospheric prototype ready
---
*Signed: Antigravity Agent*
# DEVLOG - 2026-01-28
**Time**: 07:44:02
**Session Goal**: Asset Generation, Clean Slate, UI & Camp Setup.
## Achievements
### 1. Scene Reset
- Cleaned to a minimal robust state (Z-0 Ground + Player only).
- Verified , structure for Defold integration.
### 2. AI Asset Generation (Survival Style)
- Generated fresh, unified assets based on user references:
- **Campfire**: Simple stone ring, minimalist.
- **Tent**: Blue dome, survival/worn aesthetic.
- **Sleeping Bag**: Brown/patched survival gear.
- **UI**: Health Bar (Rust+Liquid), Inventory Slot (Wood), Action Button, Dialog Panel.
### 3. Automated Processing Pipeline
- Created scripts to detect, crop, remove magenta background, and deploy generated assets.
- Synced assets to multiple locations for compatibility:
-
- &
-
## Next Steps
- Finalize Health Bar design (Face icons vs Liquid).
- Implement Camp and UI into the new Defold .
# DEVLOG - 2026-01-28
**Time**: 07:44:02
**Session Goal**: Asset Generation, Clean Slate, UI & Camp Setup.
## Achievements
### 1. Scene Reset
- Cleaned `GrassScene_Clean.js` to a minimal robust state (Z-0 Ground + Player only).
- Verified `main.atlas`, `main.collection` structure for Defold integration.
### 2. AI Asset Generation (Survival Style)
- Generated fresh, unified assets based on user references:
- **Campfire**: Simple stone ring, minimalist.
- **Tent**: Blue dome, survival/worn aesthetic.
- **Sleeping Bag**: Brown/patched survival gear.
- **UI**: Health Bar (Rust+Liquid), Inventory Slot (Wood), Action Button, Dialog Panel.
### 3. Automated Processing Pipeline
- Created scripts to detect, crop, remove magenta background, and deploy generated assets.
- Synced assets to multiple locations for compatibility:
- `repos/novafarma/main/assets`
- `repos/novafarma/assets/DEMO_FAZA1/Environment` & `UI`
- `nova farma/main/assets`
## Next Steps
- Finalize Health Bar design (Face icons vs Liquid).
- Implement Camp and UI into the new Defold `main.collection`.

BIN
assets/.DS_Store vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 303 KiB

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 KiB

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 KiB

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 576 KiB

After

Width:  |  Height:  |  Size: 584 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 440 KiB

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

After

Width:  |  Height:  |  Size: 473 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 501 KiB

After

Width:  |  Height:  |  Size: 510 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 983 KiB

After

Width:  |  Height:  |  Size: 1014 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 684 KiB

After

Width:  |  Height:  |  Size: 712 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 KiB

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 628 KiB

After

Width:  |  Height:  |  Size: 640 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

5
game.project Normal file
View File

@@ -0,0 +1,5 @@
Title: Nova Farma
Version: 1.0
[bootstrap]
main_collection = /main/main.collection

7
ground.tilesource Normal file
View File

@@ -0,0 +1,7 @@
image: "/main/assets/ground_tileset.png"
tile_width: 256
tile_height: 256
tile_margin: 0
tile_spacing: 0
collision: ""
material_tag: "tile"

11
main.atlas Normal file
View File

@@ -0,0 +1,11 @@
images {
image: "/main/assets/tla_trava_tekstura.png"
sprite_trim_mode: SPRITE_TRIM_MODE_OFF
}
images {
image: "/main/assets/stream_water.png"
sprite_trim_mode: SPRITE_TRIM_MODE_OFF
}
margin: 0
extrude_borders: 2
inner_padding: 0

BIN
main/assets/action_btn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
main/assets/campfire.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.10" tiledversion="1.10.2" name="ground_tileset" tilewidth="256" tileheight="256" tilecount="4" columns="4">
<image source="ground_tileset.png" width="1024" height="256"/>
</tileset>

BIN
main/assets/health_bar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

BIN
main/assets/tent.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 KiB

15
main/ground.go Normal file
View File

@@ -0,0 +1,15 @@
components {
id: "tilemap"
component: "/main/ground.tilemap"
position {
x: 0.0
y: 0.0
z: 0.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
}

4208
main/ground.tilemap Normal file

File diff suppressed because it is too large Load Diff

7
main/ground.tilesource Normal file
View File

@@ -0,0 +1,7 @@
image: "/main/assets/ground_tileset.png"
tile_width: 256
tile_height: 256
tile_margin: 0
tile_spacing: 0
collision: ""
material_tag: "tile"

28
main/level1.tmx Normal file
View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.11.2" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="256" tileheight="256" infinite="0" nextlayerid="2" nextobjectid="1">
<tileset firstgid="1" source="assets/ground_tileset.tsx"/>
<layer id="1" name="Tile Layer 1" width="30" height="20">
<data encoding="csv">
1,3,1,1,1,1,1,1,1,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,4,
1,1,1,1,1,1,1,1,1,1,1,3,1,3,4,1,1,1,1,1,1,1,1,1,1,1,1,4,1,1,
1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,4,1,1,1,4,1,3,1,1,1,1,1,4,1,
1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,4,1,1,1,1,1,1,1,1,1,
1,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,4,1,1,1,1,1,1,1,1,1,1,1,1,1,4,1,1,1,
1,1,1,3,1,1,1,1,1,1,3,1,4,1,1,1,4,1,1,1,1,3,1,1,1,3,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,4,1,1,1,1,1,1,1,1,1,1,
1,1,1,3,1,1,1,1,1,1,1,1,1,1,3,1,1,4,1,1,2,2,2,2,2,2,2,2,1,1,
1,1,1,1,1,1,1,1,4,1,1,4,1,3,1,3,1,1,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,1,1,1,1,1,1,3,1,2,2,
2,2,2,2,1,1,1,1,1,1,1,4,1,2,2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,4,1,1,4,1,1,4,1,1,
1,1,1,1,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,4,1,1,3,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,3,
1,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,4,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,4,4,1,1,4,1,1,1,1,1,1,4,1,
1,4,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,4,1,1,1,1,1,1,
1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,4,1
</data>
</layer>
</map>

23
main/main.atlas Normal file
View File

@@ -0,0 +1,23 @@
images {
image: "/main/assets/tla_trava_tekstura.png"
sprite_trim_mode: SPRITE_TRIM_MODE_OFF
}
images {
image: "/main/assets/stream_water.png"
sprite_trim_mode: SPRITE_TRIM_MODE_OFF
}
images {
image: "/main/assets/campfire.png"
sprite_trim_mode: SPRITE_TRIM_MODE_OFF
}
images {
image: "/main/assets/sleeping_bag.png"
sprite_trim_mode: SPRITE_TRIM_MODE_OFF
}
images {
image: "/main/assets/tent.png"
sprite_trim_mode: SPRITE_TRIM_MODE_OFF
}
margin: 0
extrude_borders: 2
inner_padding: 0

17
main/main.collection Normal file
View File

@@ -0,0 +1,17 @@
name: "default"
scale_along_z: 0
instances {
id: "ground"
prototype: "/main/ground.go"
position {
x: 0.0
y: 0.0
z: 0.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
}

14
nova farma .tiled-project Normal file
View File

@@ -0,0 +1,14 @@
{
"automappingRulesFile": "",
"commands": [
],
"compatibilityVersion": 1100,
"extensionsPath": "extensions",
"folders": [
"."
],
"properties": [
],
"propertyTypes": [
]
}

17
nova farma .tiled-session Normal file
View File

@@ -0,0 +1,17 @@
{
"Map/SizeTest": {
"height": 4300,
"width": 2
},
"activeFile": "",
"expandedProjectPaths": [
],
"file.lastUsedOpenFilter": "All Files (*)",
"fileStates": {
},
"openFiles": [
],
"project": "nova farma .tiled-project",
"recentFiles": [
]
}

14
nova farma.tiled-project Normal file
View File

@@ -0,0 +1,14 @@
{
"automappingRulesFile": "",
"commands": [
],
"compatibilityVersion": 1100,
"extensionsPath": "extensions",
"folders": [
"."
],
"properties": [
],
"propertyTypes": [
]
}

12
nova farma.tiled-session Normal file
View File

@@ -0,0 +1,12 @@
{
"activeFile": "",
"expandedProjectPaths": [
],
"fileStates": {
},
"openFiles": [
],
"project": "nova farma.tiled-project",
"recentFiles": [
]
}

130
scripts/auto_tiled_gen.py Normal file
View File

@@ -0,0 +1,130 @@
import cv2
import numpy as np
import os
import random
def create_project_assets():
# Directories
repo_root = '/Users/davidkotnik/repos/novafarma'
target_dir = '/Users/davidkotnik/nova farma/main/assets'
# Ensure target directory exists
os.makedirs(target_dir, exist_ok=True)
# Source Paths (Repositories)
src_grass = os.path.join(repo_root, 'assets/DEMO_FAZA1/Ground/tla_trava_tekstura.png')
src_water = os.path.join(repo_root, 'assets/DEMO_FAZA1/Environment/stream_water.png')
src_high = os.path.join(repo_root, 'assets/DEMO_FAZA1/Vegetation/visoka_trava.png')
src_dense = os.path.join(repo_root, 'assets/DEMO_FAZA1/Vegetation/grass_cluster_dense.png')
# Load and Process Images
def load_resize_clean(path, clean_pink=False):
if not os.path.exists(path):
print(f"Missing source: {path}")
return np.zeros((256, 256, 4), dtype=np.uint8) # Fallback black square
img = cv2.imread(path, cv2.IMREAD_UNCHANGED)
# Ensure RGBA
if img.shape[2] == 3:
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
# Clean Pink (#FF00FF)
if clean_pink:
# Mask pink range
lower = np.array([250, 0, 250])
upper = np.array([255, 10, 255])
mask = cv2.inRange(img[:,:,:3], lower, upper)
img[mask > 0] = [0, 0, 0, 0] # Set transparent
# Resize to 256x256
return cv2.resize(img, (256, 256), interpolation=cv2.INTER_AREA)
# 1. Create Tileset Image (ground_tileset.png)
img_grass = load_resize_clean(src_grass)
img_water = load_resize_clean(src_water)
img_high = load_resize_clean(src_high, clean_pink=True)
img_dense = load_resize_clean(src_dense, clean_pink=True)
# Combine horizontally
tileset_img = np.hstack((img_grass, img_water, img_high, img_dense))
out_img_path = os.path.join(target_dir, 'ground_tileset.png')
cv2.imwrite(out_img_path, tileset_img)
print(f"Created {out_img_path}")
# 2. Create Tileset Definition (nova_farma.tsx)
# 4 tiles, 256x256
tsx_content = f"""<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.10" tiledversion="1.11.0" name="nova_farma" tilewidth="256" tileheight="256" tilecount="4" columns="4">
<image source="ground_tileset.png" width="{tileset_img.shape[1]}" height="{tileset_img.shape[0]}"/>
</tileset>
"""
out_tsx_path = os.path.join(target_dir, 'nova_farma.tsx')
with open(out_tsx_path, 'w') as f:
f.write(tsx_content)
print(f"Created {out_tsx_path}")
# 3. Create Map (mapa.tmx)
# Size 10x10
width, height = 10, 10
# GIDs:
# 1: Grass
# 2: Water
# 3: High Grass
# 4: Dense Grass
# Layer 0: Ground (All Grass)
layer0_data = [1] * (width * height)
# Layer 1: Water (Stream in middle)
# Let's say row 4 and 5 are water
layer1_data = [0] * (width * height)
for y in range(4, 6):
for x in range(width):
layer1_data[y * width + x] = 2
# Layer 2: Foliage (Random High/Dense Grass)
layer2_data = [0] * (width * height)
for i in range(len(layer2_data)):
# Only place on grass (not where water is)
if layer1_data[i] == 0:
if random.random() < 0.2: # 20% chance
layer2_data[i] = random.choice([3, 4])
# function to format CSV
def to_csv(data):
lines = []
for y in range(height):
row = data[y*width : (y+1)*width]
lines.append(",".join(map(str, row)))
return ",\n".join(lines)
tmx_content = f"""<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="{width}" height="{height}" tilewidth="256" tileheight="256" infinite="0" nextlayerid="4" nextobjectid="1">
<tileset firstgid="1" source="nova_farma.tsx"/>
<layer id="1" name="Plata" width="{width}" height="{height}">
<data encoding="csv">
{to_csv(layer0_data)}
</data>
</layer>
<layer id="2" name="Voda" width="{width}" height="{height}">
<data encoding="csv">
{to_csv(layer1_data)}
</data>
</layer>
<layer id="3" name="Foliage" width="{width}" height="{height}">
<data encoding="csv">
{to_csv(layer2_data)}
</data>
</layer>
</map>
"""
out_tmx_path = os.path.join(target_dir, 'mapa.tmx')
with open(out_tmx_path, 'w') as f:
f.write(tmx_content)
print(f"Created {out_tmx_path}")
if __name__ == "__main__":
create_project_assets()

View File

@@ -54,6 +54,20 @@ def clean_pink_and_soften(input_path, output_path):
print(f"Saved cleaned image to {output_path}")
if __name__ == "__main__":
# Hardcoded path as per instructions: Vegetation/visoka_trava_v2.png
target = 'assets/DEMO_FAZA1/Vegetation/visoka_trava_v2.png'
clean_pink_and_soften(target, target)
base_dir = 'assets/DEMO_FAZA1'
target_subdirs = ['Vegetation', 'Ground', 'Environment']
for subdir in target_subdirs:
target_dir = os.path.join(base_dir, subdir)
if not os.path.exists(target_dir):
print(f"Directory not found: {target_dir}")
continue
print(f"--- Processing {subdir} ---")
for filename in os.listdir(target_dir):
if filename.lower().endswith(".png"):
# Filter for specific files if needed, or process all to be safe as per user request "na vseh teh spritih"
# User specifically mentioned: grass, stream_water, tla_trava_tekstura
full_path = os.path.join(target_dir, filename)
clean_pink_and_soften(full_path, full_path)

View File

@@ -0,0 +1,97 @@
import cv2
import numpy as np
import os
import shutil
def process_and_replace_assets():
# Paths to the newly generated artifacts
# NOTE: In a real scenario, I would validly know these paths.
# Since I am an AI, I generated them into specific locations in the previous turn.
# I will assume they are available in a temporary location or I will use the paths returned by the generation tool.
# Based on previous output:
# Campfire: .../campfire_simple_1769580346960.png
# Tent: .../tent_new_color_1769580361961.png
# Sleeping Bag: .../sleeping_bag_asset_1769580261243.png
# I need to find them or use the strings directly.
# Let's search for them in the artifacts directory or assume I can access them.
# For this script to work on the USER's machine, the files must be there.
# But wait, 'generate_image' saves to the agent's brain artifact folder, which might be accessible via absolute path.
# Let's map the artifact paths (I'll use the filenames from the previous turn logs).
# Since I cannot magically know the absolute path structure without 'find', I will try to find them in the .gemini artifacts folder.
artifacts_dir = '/Users/davidkotnik/.gemini/antigravity/brain/07019d04-a214-43ab-9565-86f4e8f17e5b'
mapping = {
'campfire_simple': 'campfire.png',
'tent_new_color': 'tent.png',
'sleeping_bag_asset': 'sleeping_bag.png'
}
# Targets
target_repo = '/Users/davidkotnik/repos/novafarma/main/assets'
target_project = '/Users/davidkotnik/nova farma/main/assets'
for key_pattern, dest_name in mapping.items():
# Find the file in artifacts
found_path = None
for f in os.listdir(artifacts_dir):
if key_pattern in f and f.endswith('.png'):
found_path = os.path.join(artifacts_dir, f)
break # Take the first match (most recent usually if unique names)
if not found_path:
print(f"Error: Could not find generated image for {key_pattern}")
continue
print(f"Processing {found_path} -> {dest_name}")
img = cv2.imread(found_path, cv2.IMREAD_UNCHANGED)
# 1. Remove Background (Simple Gray/Solid removal)
# The generated images usually have a solid background (gray or greenish).
# Best approach: Grab corner color and remove everything similar?
if img.shape[2] == 3:
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
# Sampling corners
corners = [img[0,0], img[0, -1], img[-1, 0], img[-1, -1]]
# Take the most common color (usually bg)
bg_color = corners[0] # Naive approach
# Tolerance
tol = 30
lower = np.clip(bg_color[:3] - tol, 0, 255)
upper = np.clip(bg_color[:3] + tol, 0, 255)
mask = cv2.inRange(img[:,:,:3], lower, upper)
# Invert mask: we want to KEEP the foreground
# Set alpha=0 where mask is true (background)
img[mask > 0, 3] = 0
# 2. Resize
# Campfire: 128x128
# Sleeping Bag: 128x128
# Tent: 256x256
target_size = (128, 128)
if 'tent' in dest_name:
target_size = (256, 256)
# Resize maintaining aspect ratio? Or just fit?
# Isometric assets often square-ish. Let's just resize to fit box.
img = cv2.resize(img, target_size, interpolation=cv2.INTER_AREA)
# Save
local_dest = os.path.join(target_repo, dest_name)
cv2.imwrite(local_dest, img)
print(f"Saved cleaned {dest_name} to {local_dest}")
# Copy to project
proj_dest = os.path.join(target_project, dest_name)
shutil.copy2(local_dest, proj_dest)
print(f"Synced to {proj_dest}")
if __name__ == "__main__":
process_and_replace_assets()

114
scripts/deploy_ui_assets.py Normal file
View File

@@ -0,0 +1,114 @@
import cv2
import numpy as np
import os
import shutil
def clean_ui_assets():
# Paths to the newly generated UI artifacts (Solid Magenta #FF00FF usually)
# I have to hunt them down by pattern again
artifacts_dir = '/Users/davidkotnik/.gemini/antigravity/brain/07019d04-a214-43ab-9565-86f4e8f17e5b'
mapping = {
'ui_health_bar': 'health_bar.png',
'ui_inventory_slot': 'inventory_slot.png',
'ui_action_button': 'action_btn.png',
'ui_dialog_box': 'dialog_panel.png'
}
# Destination in new project
target_repo_dir = '/Users/davidkotnik/repos/novafarma/main/assets'
target_project_dir = '/Users/davidkotnik/nova farma/main/assets'
# Also copy to DEMO_FAZA1/UI if it exists?
demo_ui_dir = '/Users/davidkotnik/repos/novafarma/assets/DEMO_FAZA1/UI'
if not os.path.exists(demo_ui_dir):
os.makedirs(demo_ui_dir, exist_ok=True)
for key_pattern, dest_name in mapping.items():
found_path = None
# Find latest file matching pattern
candidates = []
for f in os.listdir(artifacts_dir):
if key_pattern in f and f.endswith('.png'):
candidates.append(os.path.join(artifacts_dir, f))
if not candidates:
print(f"Skipping {key_pattern} (not found)")
continue
# Sort by mtime to get latest
candidates.sort(key=os.path.getmtime, reverse=True)
found_path = candidates[0]
print(f"Processing {found_path}")
img = cv2.imread(found_path)
if img is None:
print("Failed to load")
continue
# Convert to BGRA
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
# Remove Magenta #FF00FF
# OpenCV uses BGR: Magenta is (255, 0, 255) in BGR order
# With tolerance just in case
target = np.array([255, 0, 255])
tol = 60 # strict-ish
lower = np.clip(target - tol, 0, 255)
upper = np.clip(target + tol, 0, 255)
mask = cv2.inRange(img[:,:,:3], lower, upper)
# Transparent wherever mask is true
img[mask > 0, 3] = 0
# Crop to content (non-transparent pixels)
# Find bounding box of non-zero alpha
alpha = img[:,:,3]
coords = cv2.findNonZero(alpha)
if coords is not None:
x, y, w, h = cv2.boundingRect(coords)
img = img[y:y+h, x:x+w]
# Resize if massive? Generated images are likely 1024x1024
# UI elements need to be manageable.
# Health Bar: width 300?
# Slot: 64x64 or 128x128?
# Button: 128?
# Panel: 400?
h, w = img.shape[:2]
new_w, new_h = w, h
if 'health_bar' in dest_name and w > 400:
scale = 400 / w
new_w, new_h = int(w*scale), int(h*scale)
elif 'inventory' in dest_name and w > 128:
scale = 128 / w
new_w, new_h = int(w*scale), int(h*scale)
elif 'action' in dest_name and w > 140:
scale = 128 / w
new_w, new_h = int(w*scale), int(h*scale)
elif 'dialog' in dest_name and w > 500:
scale = 500 / w
new_w, new_h = int(w*scale), int(h*scale)
if new_w != w:
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA)
# Save to Main Assets
dest_path = os.path.join(target_repo_dir, dest_name)
cv2.imwrite(dest_path, img)
print(f"Saved {dest_name} to {dest_path}")
# Sync to demo folder
shutil.copy2(dest_path, os.path.join(demo_ui_dir, dest_name))
print(f"Synced to {demo_ui_dir}")
# Sync to Project Folder
shutil.copy2(dest_path, os.path.join(target_project_dir, dest_name))
print(f"Synced to {target_project_dir}")
if __name__ == "__main__":
clean_ui_assets()

View File

@@ -0,0 +1,65 @@
import random
import math
def generate_defold_map():
width = 30
height = 20
# Defold/Tileset Indices (0-based)
# 0: Grass
# 1: Water
# 2: Tall Grass
# 3: Dense Grass
cells = []
for x in range(width):
# River logic: Sine wave center
# y goes from 0 (bottom) to height (top) in Defold usually?
# Actually Defold coords: (0,0) is bottom-left usually, but in Tilemap editor (0,0) is often bottom-left too.
# Let's assume standard grid.
river_center = 10 + int(3 * math.sin(x * 0.2))
for y in range(height):
tile_id = 0 # Default Grass
# River check
if y == river_center or y == river_center + 1:
tile_id = 1 # Water
else:
# Random foliage on grass
if random.random() < 0.10:
tile_id = random.choice([2, 3])
# Append cell string
# Defold tilemap cell format
cell_str = f""" cell {{
x: {x}
y: {y}
tile: {tile_id}
h_flip: 0
v_flip: 0
}}"""
cells.append(cell_str)
# Full file content
content = f"""tile_set: "/main/ground.tilesource"
layers {{
id: "ground"
z: 0.0
is_visible: 1
{chr(10).join(cells)}
}}
material: "/builtins/materials/tile_map.material"
blend_mode: BLEND_MODE_ALPHA
"""
output_path = 'main/ground.tilemap'
with open(output_path, 'w') as f:
f.write(content)
print(f"Generated Defold tilemap at {output_path}")
if __name__ == "__main__":
generate_defold_map()

64
scripts/generate_tmx.py Normal file
View File

@@ -0,0 +1,64 @@
import random
def generate_tmx():
width = 30
height = 20
tile_width = 256
tile_height = 256
# GIDs based on the tileset we created:
# 1: Grass (Base)
# 2: Water
# 3: Tall Grass
# 4: Dense Grass
# 1. Create Data CSV
# Initialize with Grass (1)
grid = [[1 for _ in range(width)] for _ in range(height)]
# Add a River (Water = 2)
# Simple sine wave river
import math
for x in range(width):
# vary y around center (10)
y_center = 10 + int(3 * math.sin(x * 0.2))
if 0 <= y_center < height:
grid[y_center][x] = 2
# Make river wider?
if y_center + 1 < height: grid[y_center+1][x] = 2
# Add random Foliage (3 or 4) on Grass only
for y in range(height):
for x in range(width):
if grid[y][x] == 1: # If grass
if random.random() < 0.1: # 10% chance
grid[y][x] = random.choice([3, 4])
# Convert to CSV string
data_lines = []
for row in grid:
data_lines.append(",".join(map(str, row)))
csv_data = ",\n".join(data_lines)
# 2. Construct TMX Content
# defined relative path to tileset: assets/ground_tileset.tsx (assuming level1.tmx is in main/, and assets is main/assets)
tmx_content = f"""<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.11.2" orientation="orthogonal" renderorder="right-down" width="{width}" height="{height}" tilewidth="{tile_width}" tileheight="{tile_height}" infinite="0" nextlayerid="2" nextobjectid="1">
<tileset firstgid="1" source="assets/ground_tileset.tsx"/>
<layer id="1" name="Tile Layer 1" width="{width}" height="{height}">
<data encoding="csv">
{csv_data}
</data>
</layer>
</map>
"""
output_path = 'main/level1.tmx'
with open(output_path, 'w') as f:
f.write(tmx_content)
print(f"Generated {output_path}")
if __name__ == "__main__":
generate_tmx()

View File

@@ -0,0 +1,85 @@
import cv2
import numpy as np
import os
def remove_pink_and_resize(image_path, target_size=(256, 256)):
img = cv2.imread(image_path, cv2.IMREAD_UNCHANGED)
if img is None:
print(f"Failed to load {image_path}")
return None
# Handle Alpha channel
if img.shape[2] == 3:
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
# Remove Pink (#FF00FF and range)
# Convert to HSV might be safer, but exact #FF00FF is (255, 0, 255) in BGR
# Let's target the magenta color.
# BGR: 255, 0, 255
lower_pink = np.array([250, 0, 250])
upper_pink = np.array([255, 10, 255])
mask = cv2.inRange(img[:, :, :3], lower_pink, upper_pink)
# Set alpha to 0 where mask is true
img[mask > 0] = [0, 0, 0, 0]
# Resize to fit 256x256
# For a tileset grid, we usually want it to fill the tile or be centered?
# User said "polagal čez plato". Let's simply resize to 256x256 for simplicity of the grid.
# Distortion might occur but these are nature assets (grass).
# Ideally preserve aspect ratio but for "Tileset Image" grid alignment, 256x256 is safest.
resized = cv2.resize(img, target_size, interpolation=cv2.INTER_AREA)
return resized
def create_full_tileset():
# Sources
grass_path = 'main/assets/tla_trava_tekstura.png'
water_path = 'main/assets/stream_water.png'
high_grass_path = 'assets/DEMO_FAZA1/Vegetation/visoka_trava.png'
dense_grass_path = 'assets/DEMO_FAZA1/Vegetation/grass_cluster_dense.png'
# Output
output_png = 'main/assets/ground_tileset.png' # We update the existing one
# Process
# 1. Base Grass (assumed clean)
grass = cv2.imread(grass_path, cv2.IMREAD_UNCHANGED)
if grass.shape[2] == 3: grass = cv2.cvtColor(grass, cv2.COLOR_BGR2BGRA)
grass = cv2.resize(grass, (256, 256))
# 2. Water (assumed clean)
water = cv2.imread(water_path, cv2.IMREAD_UNCHANGED)
if water.shape[2] == 3: water = cv2.cvtColor(water, cv2.COLOR_BGR2BGRA)
water = cv2.resize(water, (256, 256))
# 3. High Grass (Clean Pink)
high_grass = remove_pink_and_resize(high_grass_path)
# 4. Dense Grass (Clean Pink)
dense_grass = remove_pink_and_resize(dense_grass_path)
if high_grass is None or dense_grass is None:
print("Error processing grass images.")
return
# Combine: [Grass] [Water] [HighGrass] [DenseGrass]
tileset = np.hstack((grass, water, high_grass, dense_grass))
# Save
cv2.imwrite(output_png, tileset)
print(f"Saved combined tileset to {output_png} with shape {tileset.shape}")
# Generate .tsx content
tsx_content = f"""<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.10" tiledversion="1.10.2" name="ground_tileset" tilewidth="256" tileheight="256" tilecount="4" columns="4">
<image source="ground_tileset.png" width="{tileset.shape[1]}" height="{tileset.shape[0]}"/>
</tileset>
"""
with open('main/assets/ground_tileset.tsx', 'w') as f:
f.write(tsx_content)
print("Saved ground_tileset.tsx")
if __name__ == "__main__":
create_full_tileset()

47
scripts/make_tileset.py Normal file
View File

@@ -0,0 +1,47 @@
import cv2
import numpy as np
import os
def create_tileset():
# Paths
grass_path = 'main/assets/tla_trava_tekstura.png'
water_path = 'main/assets/stream_water.png'
output_path = 'main/assets/ground_tileset.png'
# Load images
grass = cv2.imread(grass_path, cv2.IMREAD_UNCHANGED)
water = cv2.imread(water_path, cv2.IMREAD_UNCHANGED)
if grass is None:
print(f"Error: Could not load {grass_path}")
return
if water is None:
print(f"Error: Could not load {water_path}")
return
# Check dimensions
# Assuming tiles are 256x256 based on previous instructions
# We will resize if necessary but ideally they match
print(f"Grass shape: {grass.shape}")
print(f"Water shape: {water.shape}")
# Ensure 4 channels
if grass.shape[2] == 3:
grass = cv2.cvtColor(grass, cv2.COLOR_BGR2BGRA)
if water.shape[2] == 3:
water = cv2.cvtColor(water, cv2.COLOR_BGR2BGRA)
# Force resize to 256x256 if not (just to be safe for the tilesource)
grass = cv2.resize(grass, (256, 256))
water = cv2.resize(water, (256, 256))
# Combine horizontally (Tile 1, Tile 2)
# Tilesource indexing usually starts at 1
# So Tile 1 = Grass, Tile 2 = Water
tileset = np.hstack((grass, water))
cv2.imwrite(output_path, tileset)
print(f"Created tileset at {output_path} with shape {tileset.shape}")
if __name__ == "__main__":
create_tileset()

View File

@@ -0,0 +1,60 @@
import cv2
import numpy as np
import os
import shutil
def process_camp_assets():
# Mappings: Source -> Destination Name
assets = {
'assets/references/taborni_ogenj.png': 'campfire.png',
'assets/references/spalna_vreca.png': 'sleeping_bag.png',
'assets/references/sotor_zaprt.png': 'tent.png'
}
repo_root = '/Users/davidkotnik/repos/novafarma'
target_dir_local = os.path.join(repo_root, 'main/assets')
target_dir_project = '/Users/davidkotnik/nova farma/main/assets'
# Ensure dirs exist
os.makedirs(target_dir_local, exist_ok=True)
os.makedirs(target_dir_project, exist_ok=True)
for src_rel, dest_name in assets.items():
src_path = os.path.join(repo_root, src_rel)
if not os.path.exists(src_path):
print(f"MISSING: {src_path}")
continue
# Load
img = cv2.imread(src_path, cv2.IMREAD_UNCHANGED)
if img is None:
print(f"FAILED TO LOAD: {src_path}")
continue
print(f"Processing {src_rel} (Original: {img.shape})")
# Resize logic (Max dim 256 for small items, maybe 300 for tent?)
# Let's verify size.
h, w = img.shape[:2]
max_dim = 256
if 'sotor' in src_rel: max_dim = 300 # Tent slightly bigger
scale = 1.0
if w > max_dim or h > max_dim:
scale = max_dim / max(h, w)
new_w = int(w * scale)
new_h = int(h * scale)
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA)
# Save to local repo
dest_path_local = os.path.join(target_dir_local, dest_name)
cv2.imwrite(dest_path_local, img)
print(f"Saved to {dest_path_local} ({img.shape})")
# Copy to project folder
dest_path_project = os.path.join(target_dir_project, dest_name)
shutil.copy2(dest_path_local, dest_path_project)
print(f"Synced to {dest_path_project}")
if __name__ == "__main__":
process_camp_assets()

View File

@@ -0,0 +1,48 @@
import cv2
import numpy as np
import os
def process_stream_image():
# 1. Load the stream image
img = cv2.imread("assets/environment/stream_reference.png", cv2.IMREAD_UNCHANGED)
# If no alpha channel, add one
if img.shape[2] == 3:
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
# Remove white/black background if present (assuming white based on typical reference images, or do refined masking)
# The user image "uploaded_media_0" looks like an isometric block. It might have a white background.
# Simple thresholding for white background removal:
gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY)
# Create mask for whiteish pixels
_, mask = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY)
# Invert mask (keep non-white)
mask_inv = cv2.bitwise_not(mask)
# Apply to alpha channel
# Note: This is a rough removal. Ideally we'd use the provided transparent asset if user gave PNG with alpha.
# But often users upload screenshots.
# Let's check if the corners are white.
# Actually, let's just use the image as is if it has alpha, else remove white.
# But to be safe for "systematic" placement, let's resize it to a standard tile size or keep it prop-sized.
# It looks like a big chunk. Let's keep resolution but ensure background is clean.
img[:, :, 3] = cv2.bitwise_and(img[:, :, 3], mask_inv)
# Save as the actual game asset
cv2.imwrite("assets/environment/potok_segment.png", img)
print("Stream segment processed and saved.")
def identify_and_remove_ugly_grass():
# The user wants "the other grass image" removed.
# Reference saved at assets/vegetation/ugly_grass_ref.png
# We don't need to process it, just know that we need to stop using the asset that looks like this.
# The user said "jo odstrani iz igre".
# I suspect it matches 'trava_rob.png' or similar.
# In the code, I will switch usage of 'trava_rob' to one of the better grasses.
pass
process_stream_image()

View File

@@ -6,158 +6,80 @@ export default class GrassSceneClean extends Phaser.Scene {
preload() {
this.load.path = 'assets/';
// --- CORE ASSETS ---
this.load.image('trava_osnova', 'tiles/trava_osnova.png');
this.load.image('blato', 'environment/blato.png');
this.load.image('cesta', 'environment/cesta_svetla.png');
this.load.image('voda_cista', 'environment/voda_cista.png');
this.load.image('potok_segment', 'environment/potok_segment.png');
// --- ASSETS ---
// 1. Podlaga (Foundation)
this.load.image('ground_base', 'DEMO_FAZA1/Ground/tla_trava_tekstura.png');
// --- VEGETATION ---
this.load.image('trava_zelena', 'vegetation/trava_zelena.png');
this.load.image('trava_suha', 'vegetation/trava_suha.png');
this.load.image('trava_divja', 'vegetation/trava_divja.png');
this.load.image('drevo', 'vegetation/drevo_navadno.png');
// 2. Vodni kanali (Water)
this.load.image('stream_water', 'DEMO_FAZA1/Environment/stream_water.png');
// 3. Foliage
this.load.image('grass_dense', 'DEMO_FAZA1/Vegetation/grass_cluster_dense.png');
this.load.image('grass_tall', 'DEMO_FAZA1/Vegetation/visoka_trava.png');
// 4. Items & Charts
this.load.image('hay', 'DEMO_FAZA1/Items/hay_drop_0.png');
this.load.image('kai', 'characters/kai.png');
this.load.image('campfire', 'items/campfire.png');
this.load.image('sleeping_bag', 'items/sleeping_bag.png');
// Eraser brush (dynamically created if needed, or use blato)
}
create() {
const WORLD_W = 2500;
const WORLD_H = 2500;
this.physics.world.setBounds(0, 0, WORLD_W, WORLD_H);
this.cameras.main.setBounds(0, 0, WORLD_W, WORLD_H);
this.cameras.main.setBackgroundColor('#2e3b20'); // Dark earthy background for the "hole"
this.cameras.main.setBackgroundColor('#1a1a1a');
// --- GROUPS ---
this.waterLayer = this.add.group(); // Z: 0
this.groundLayer = this.add.group(); // Z: 1 (The Masked Layer)
this.objectLayer = this.add.group(); // Z: 2
// --- 1. PODLAGA (The Foundation) ---
// Level 0, Locked to Z = -100
this.ground = this.add.tileSprite(WORLD_W / 2, WORLD_H / 2, WORLD_W, WORLD_H, 'ground_base');
this.ground.setTileScale(1, 1);
this.ground.setDepth(-100);
// --- 1. WATER LAYER (The "Bottom" of the Trench) ---
// We calculate the path first so we know where to put water
const startX = 0, startY = 800;
const endX = 2500, endY = 1800;
const dist = Phaser.Math.Distance.Between(startX, startY, endX, endY);
const count = Math.ceil(dist / 60); // High density for smoothness
// --- 2. VODNI KANALI (Water Integration) ---
// Removed as requested
// Place water along the path (at depth 0)
for (let i = 0; i <= count; i++) {
let t = i / count;
let x = Phaser.Math.Linear(startX, endX, t);
let y = Phaser.Math.Linear(startY, endY, t);
y += Math.sin(t * 12) * 60; // Meander
// --- 3. FOLIAGE (Trava - Šopi) ---
// Removed as requested
// Water Segment
let seg = this.add.image(x, y, 'potok_segment');
seg.setScale(0.9);
seg.setDepth(1); // Layer 1 internally
seg.setAlpha(0.7); // Transparency
seg.setBlendMode(Phaser.BlendModes.NORMAL);
this.waterLayer.add(seg);
// --- 4. ITEMS (Seno) ---
// Removed as requested
// Mud Underlay (Darker depth)
let mud = this.add.image(x, y + 10, 'blato');
mud.setScale(1.0);
mud.setTint(0x1a1a0d); // Very dark/black
mud.setDepth(0); // Layer 0
this.waterLayer.add(mud);
}
// --- 2. GROUND LAYER (The "Top" with a Hole) ---
// We use a RenderTexture to "Stamp out" the river
let rt = this.add.renderTexture(0, 0, WORLD_W, WORLD_H);
rt.setDepth(100); // Visually above water
// Fill RT with the grass tile texture
// Since clear+fill with tileSprite isn't direct in RT, we draw a huge tileSprite once
let hugeBg = this.make.tileSprite({ x: WORLD_W / 2, y: WORLD_H / 2, width: WORLD_W, height: WORLD_H, key: 'trava_osnova' }, false);
hugeBg.setTileScale(0.15);
hugeBg.setTint(0xccffcc);
rt.draw(hugeBg, WORLD_W / 2, WORLD_H / 2);
// NOW ERASE THE RIVER CHANNEL
// "Subtract Mask" logic using erase()
for (let i = 0; i <= count; i++) {
let t = i / count;
let x = Phaser.Math.Linear(startX, endX, t);
let y = Phaser.Math.Linear(startY, endY, t);
y += Math.sin(t * 12) * 60;
// We use 'blato' sprite as an eraser brush because it has a soft alpha shape
let eraser = this.make.image({ x: 0, y: 0, key: 'blato' }, false);
eraser.setScale(0.7); // Slightly narrower than water to show bank edge?
// Actually, if we erase slightly LESS than the water width, the water "slides under" the ground -> Bank Effect!
rt.erase(eraser, x, y);
// Add a visual "Shadow" on top of the cut edge to simulate depth
let shadow = this.add.image(x, y - 5, 'blato');
shadow.setScale(0.8);
shadow.setTint(0x000000);
shadow.setAlpha(0.4);
shadow.setDepth(101); // Right on top of the RT hole
shadow.setBlendMode(Phaser.BlendModes.MULTIPLY);
// This shadow needs to be masked too? No, it sits in the trench.
this.waterLayer.add(shadow);
}
// --- 3. OBJECT LAYER (Y-Sorted) ---
// --- 5. CHAR (Kai) ---
this.kai = this.physics.add.sprite(WORLD_W / 2, WORLD_H / 2, 'kai');
this.kai.setScale(64 / this.kai.height);
this.kai.setCollideWorldBounds(true);
this.kai.setOrigin(0.5, 0.9); // Anchor at feet
this.kai.body.setSize(24, 20);
this.kai.body.setOffset(this.kai.width / 2 - 12, this.kai.height - 20);
this.kai.setOrigin(0.5, 0.9);
this.objectLayer.add(this.kai);
// Camera
this.cameras.main.startFollow(this.kai, true, 0.08, 0.08);
this.cameras.main.setZoom(1.3);
// Control Keys
this.cameras.main.startFollow(this.kai, true, 0.1, 0.1);
this.cameras.main.setZoom(1.5);
this.cursors = this.input.keyboard.createCursorKeys();
// Debug Text
this.add.text(20, 20, 'VISUAL FIX APPLIED:\nSubtraction Mask (RenderTexture Erase)', {
font: '16px Monospace', fill: '#00ff00', backgroundColor: '#000000aa'
}).setScrollFactor(0).setDepth(2000);
// Info
this.add.text(20, 20, 'PROBNA FARMA: Base Only', {
font: '16px Monospace', fill: '#ffffff', backgroundColor: '#000000aa'
}).setScrollFactor(0).setDepth(3000);
}
update() {
// Depth Sort Objects
this.objectLayer.children.each(child => {
child.setDepth(child.y + 2000); // Ensure objects are always above the ground RT (Depth 100)
});
// Controls
const speed = 250;
this.kai.setVelocity(0);
if (this.cursors.left.isDown) this.kai.setVelocityX(-speed);
else if (this.cursors.right.isDown) this.kai.setVelocityX(speed);
if (this.cursors.up.isDown) this.kai.setVelocityY(-speed);
else if (this.cursors.down.isDown) this.kai.setVelocityY(speed);
this.kai.body.velocity.normalize().scale(speed);
// Sunken Legs Logic
// Check overlap with water layer images (crude but effective)
let inWater = false;
this.waterLayer.children.each(w => {
if (this.physics.world.overlap(this.kai, w)) {
// Wait, water images don't have bodies. Use distance.
if (Phaser.Math.Distance.Between(this.kai.x, this.kai.y, w.x, w.y) < 40) inWater = true;
}
});
if (inWater) {
this.kai.setOrigin(0.5, 0.75); // Sunken
this.kai.setTint(0xaaccff);
} else {
this.kai.setOrigin(0.5, 0.9);
this.kai.clearTint();
if (this.kai.body.velocity.length() > 0) {
this.kai.body.velocity.normalize().scale(speed);
}
// --- Z-SORTING SYSTEM ---
// Player
this.kai.setDepth(this.kai.y);
}
}