Asset Generation: Campfire, Tent, Sleeping Bag, UI Elements (Health, Inventory, Dialog). Cleaned GrassScene.
58
DEVLOG.md
@@ -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
BIN
assets/DEMO_FAZA1/.DS_Store
vendored
|
Before Width: | Height: | Size: 303 KiB After Width: | Height: | Size: 306 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 220 KiB |
BIN
assets/DEMO_FAZA1/Environment/sotor.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 387 KiB After Width: | Height: | Size: 390 KiB |
BIN
assets/DEMO_FAZA1/Environment/taborni_ogenj.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 247 KiB After Width: | Height: | Size: 252 KiB |
|
Before Width: | Height: | Size: 576 KiB After Width: | Height: | Size: 584 KiB |
|
Before Width: | Height: | Size: 440 KiB After Width: | Height: | Size: 447 KiB |
|
Before Width: | Height: | Size: 360 KiB After Width: | Height: | Size: 473 KiB |
BIN
assets/DEMO_FAZA1/Items/spalna_vreca.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
assets/DEMO_FAZA1/UI/action_btn.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
assets/DEMO_FAZA1/UI/dialog_panel.png
Normal file
|
After Width: | Height: | Size: 437 KiB |
BIN
assets/DEMO_FAZA1/UI/health_bar.png
Normal file
|
After Width: | Height: | Size: 351 KiB |
BIN
assets/DEMO_FAZA1/UI/inventory_slot.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 501 KiB After Width: | Height: | Size: 510 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 235 KiB After Width: | Height: | Size: 244 KiB |
|
Before Width: | Height: | Size: 983 KiB After Width: | Height: | Size: 1014 KiB |
|
Before Width: | Height: | Size: 684 KiB After Width: | Height: | Size: 712 KiB |
|
Before Width: | Height: | Size: 336 KiB After Width: | Height: | Size: 342 KiB |
|
Before Width: | Height: | Size: 628 KiB After Width: | Height: | Size: 640 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.4 MiB |
BIN
assets/environment/stream_reference.png
Normal file
|
After Width: | Height: | Size: 321 KiB |
|
Before Width: | Height: | Size: 523 B |
|
Before Width: | Height: | Size: 264 B |
BIN
assets/vegetation/ugly_grass_ref.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
5
game.project
Normal file
@@ -0,0 +1,5 @@
|
||||
Title: Nova Farma
|
||||
Version: 1.0
|
||||
[bootstrap]
|
||||
main_collection = /main/main.collection
|
||||
|
||||
7
ground.tilesource
Normal 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
@@ -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
|
After Width: | Height: | Size: 44 KiB |
BIN
main/assets/campfire.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
main/assets/dialog_panel.png
Normal file
|
After Width: | Height: | Size: 437 KiB |
BIN
main/assets/ground_tileset.png
Normal file
|
After Width: | Height: | Size: 442 KiB |
4
main/assets/ground_tileset.tsx
Normal 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
|
After Width: | Height: | Size: 351 KiB |
BIN
main/assets/inventory_slot.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
main/assets/sleeping_bag.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
main/assets/stream_water.png
Normal file
|
After Width: | Height: | Size: 390 KiB |
BIN
main/assets/tent.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
main/assets/tla_trava_tekstura.png
Normal file
|
After Width: | Height: | Size: 473 KiB |
15
main/ground.go
Normal 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
7
main/ground.tilesource
Normal 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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"automappingRulesFile": "",
|
||||
"commands": [
|
||||
],
|
||||
"compatibilityVersion": 1100,
|
||||
"extensionsPath": "extensions",
|
||||
"folders": [
|
||||
"."
|
||||
],
|
||||
"properties": [
|
||||
],
|
||||
"propertyTypes": [
|
||||
]
|
||||
}
|
||||
17
nova farma .tiled-session
Normal 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
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"automappingRulesFile": "",
|
||||
"commands": [
|
||||
],
|
||||
"compatibilityVersion": 1100,
|
||||
"extensionsPath": "extensions",
|
||||
"folders": [
|
||||
"."
|
||||
],
|
||||
"properties": [
|
||||
],
|
||||
"propertyTypes": [
|
||||
]
|
||||
}
|
||||
12
nova farma.tiled-session
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"activeFile": "",
|
||||
"expandedProjectPaths": [
|
||||
],
|
||||
"fileStates": {
|
||||
},
|
||||
"openFiles": [
|
||||
],
|
||||
"project": "nova farma.tiled-project",
|
||||
"recentFiles": [
|
||||
]
|
||||
}
|
||||
130
scripts/auto_tiled_gen.py
Normal 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()
|
||||
@@ -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)
|
||||
|
||||
|
||||
97
scripts/deploy_generated_assets.py
Normal 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
@@ -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()
|
||||
65
scripts/generate_defold_tilemap.py
Normal 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
@@ -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()
|
||||
85
scripts/make_full_tileset.py
Normal 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
@@ -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()
|
||||
60
scripts/prepare_camp_assets.py
Normal 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()
|
||||
48
scripts/process_new_assets.py
Normal 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()
|
||||
@@ -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);
|
||||
|
||||
if (this.kai.body.velocity.length() > 0) {
|
||||
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();
|
||||
}
|
||||
// --- Z-SORTING SYSTEM ---
|
||||
// Player
|
||||
this.kai.setDepth(this.kai.y);
|
||||
}
|
||||
}
|
||||
|
||||