diff --git a/assets/.DS_Store b/assets/.DS_Store index f9cc81b16..c4f55bb4e 100644 Binary files a/assets/.DS_Store and b/assets/.DS_Store differ diff --git a/assets/DEMO_FAZA1/.DS_Store b/assets/DEMO_FAZA1/.DS_Store index 93c2f3690..a00c38aef 100644 Binary files a/assets/DEMO_FAZA1/.DS_Store and b/assets/DEMO_FAZA1/.DS_Store differ diff --git a/assets/DEMO_FAZA1/Characters/Kai_Dreads.png b/assets/DEMO_FAZA1/Characters/Kai_Dreads.png new file mode 100644 index 000000000..68cfc284e Binary files /dev/null and b/assets/DEMO_FAZA1/Characters/Kai_Dreads.png differ diff --git a/assets/DEMO_FAZA1/Environment/.DS_Store b/assets/DEMO_FAZA1/Environment/.DS_Store new file mode 100644 index 000000000..5008ddfcf Binary files /dev/null and b/assets/DEMO_FAZA1/Environment/.DS_Store differ diff --git a/assets/DEMO_FAZA1/Environment/mud_puddle.png b/assets/DEMO_FAZA1/Environment/mud_puddle.png new file mode 100644 index 000000000..0c0b9fabd Binary files /dev/null and b/assets/DEMO_FAZA1/Environment/mud_puddle.png differ diff --git a/assets/DEMO_FAZA1/Environment/obstacle_thorns.png b/assets/DEMO_FAZA1/Environment/obstacle_thorns.png new file mode 100644 index 000000000..1a804326a Binary files /dev/null and b/assets/DEMO_FAZA1/Environment/obstacle_thorns.png differ diff --git a/assets/DEMO_FAZA1/Environment/sign_danger.png b/assets/DEMO_FAZA1/Environment/sign_danger.png new file mode 100644 index 000000000..eb6259a54 Binary files /dev/null and b/assets/DEMO_FAZA1/Environment/sign_danger.png differ diff --git a/assets/DEMO_FAZA1/Environment/stream_water.png b/assets/DEMO_FAZA1/Environment/stream_water.png new file mode 100644 index 000000000..26f0e84d9 Binary files /dev/null and b/assets/DEMO_FAZA1/Environment/stream_water.png differ diff --git a/assets/DEMO_FAZA1/Environment/water_clean_patch.png b/assets/DEMO_FAZA1/Environment/water_clean_patch.png new file mode 100644 index 000000000..6854b55ea Binary files /dev/null and b/assets/DEMO_FAZA1/Environment/water_clean_patch.png differ diff --git a/assets/DEMO_FAZA1/Ground/ground_dirt_patch.png b/assets/DEMO_FAZA1/Ground/ground_dirt_patch.png new file mode 100644 index 000000000..0eb265c33 Binary files /dev/null and b/assets/DEMO_FAZA1/Ground/ground_dirt_patch.png differ diff --git a/assets/DEMO_FAZA1/Ground/ground_pebbles.png b/assets/DEMO_FAZA1/Ground/ground_pebbles.png new file mode 100644 index 000000000..79912f93f Binary files /dev/null and b/assets/DEMO_FAZA1/Ground/ground_pebbles.png differ diff --git a/assets/DEMO_FAZA1/Items/.DS_Store b/assets/DEMO_FAZA1/Items/.DS_Store new file mode 100644 index 000000000..5008ddfcf Binary files /dev/null and b/assets/DEMO_FAZA1/Items/.DS_Store differ diff --git a/assets/DEMO_FAZA1/Items/hay_drop_0.png b/assets/DEMO_FAZA1/Items/hay_drop_0.png new file mode 100644 index 000000000..b15dae1a8 Binary files /dev/null and b/assets/DEMO_FAZA1/Items/hay_drop_0.png differ diff --git a/assets/DEMO_FAZA1/Items/item_longboard_wheel.png b/assets/DEMO_FAZA1/Items/item_longboard_wheel.png new file mode 100644 index 000000000..ee670a193 Binary files /dev/null and b/assets/DEMO_FAZA1/Items/item_longboard_wheel.png differ diff --git a/assets/DEMO_FAZA1/Structures/foundation_concrete.png b/assets/DEMO_FAZA1/Structures/foundation_concrete.png new file mode 100644 index 000000000..df1cc878a Binary files /dev/null and b/assets/DEMO_FAZA1/Structures/foundation_concrete.png differ diff --git a/assets/DEMO_FAZA1/UI/badge_purchase.png b/assets/DEMO_FAZA1/UI/badge_purchase.png new file mode 100644 index 000000000..7ff76fafc Binary files /dev/null and b/assets/DEMO_FAZA1/UI/badge_purchase.png differ diff --git a/assets/DEMO_FAZA1/UI/badge_trial.png b/assets/DEMO_FAZA1/UI/badge_trial.png new file mode 100644 index 000000000..fe07ec8bc Binary files /dev/null and b/assets/DEMO_FAZA1/UI/badge_trial.png differ diff --git a/assets/DEMO_FAZA1/UI/health_critical.png b/assets/DEMO_FAZA1/UI/health_critical.png new file mode 100644 index 000000000..afb89880e Binary files /dev/null and b/assets/DEMO_FAZA1/UI/health_critical.png differ diff --git a/assets/DEMO_FAZA1/VFX/overlay_amnesia_blood.png b/assets/DEMO_FAZA1/VFX/overlay_amnesia_blood.png new file mode 100644 index 000000000..6b51b38c4 Binary files /dev/null and b/assets/DEMO_FAZA1/VFX/overlay_amnesia_blood.png differ diff --git a/assets/DEMO_FAZA1/VFX/toxic_fog.png b/assets/DEMO_FAZA1/VFX/toxic_fog.png new file mode 100644 index 000000000..4977f1654 Binary files /dev/null and b/assets/DEMO_FAZA1/VFX/toxic_fog.png differ diff --git a/assets/DEMO_FAZA1/Vegetation/bush_hiding_spot.png b/assets/DEMO_FAZA1/Vegetation/bush_hiding_spot.png new file mode 100644 index 000000000..780a8044d Binary files /dev/null and b/assets/DEMO_FAZA1/Vegetation/bush_hiding_spot.png differ diff --git a/assets/DEMO_FAZA1/Vegetation/drevo_faza_1.png b/assets/DEMO_FAZA1/Vegetation/drevo_faza_1.png index 8f1483107..683a4dcbd 100644 Binary files a/assets/DEMO_FAZA1/Vegetation/drevo_faza_1.png and b/assets/DEMO_FAZA1/Vegetation/drevo_faza_1.png differ diff --git a/assets/DEMO_FAZA1/Vegetation/drevo_faza_2.png b/assets/DEMO_FAZA1/Vegetation/drevo_faza_2.png index a986cec0c..1b69fd1ba 100644 Binary files a/assets/DEMO_FAZA1/Vegetation/drevo_faza_2.png and b/assets/DEMO_FAZA1/Vegetation/drevo_faza_2.png differ diff --git a/assets/DEMO_FAZA1/Vegetation/drevo_majhno.png b/assets/DEMO_FAZA1/Vegetation/drevo_majhno.png index 8557b83b4..655bcda57 100644 Binary files a/assets/DEMO_FAZA1/Vegetation/drevo_majhno.png and b/assets/DEMO_FAZA1/Vegetation/drevo_majhno.png differ diff --git a/assets/DEMO_FAZA1/Vegetation/drevo_srednje.png b/assets/DEMO_FAZA1/Vegetation/drevo_srednje.png index ec797e3a1..243a90f6c 100644 Binary files a/assets/DEMO_FAZA1/Vegetation/drevo_srednje.png and b/assets/DEMO_FAZA1/Vegetation/drevo_srednje.png differ diff --git a/assets/DEMO_FAZA1/Vegetation/drevo_veliko.png b/assets/DEMO_FAZA1/Vegetation/drevo_veliko.png index a8fd2d4ca..e8c092e75 100644 Binary files a/assets/DEMO_FAZA1/Vegetation/drevo_veliko.png and b/assets/DEMO_FAZA1/Vegetation/drevo_veliko.png differ diff --git a/assets/DEMO_FAZA1/Vegetation/grass_cluster_dense.png b/assets/DEMO_FAZA1/Vegetation/grass_cluster_dense.png new file mode 100644 index 000000000..c2fd14c80 Binary files /dev/null and b/assets/DEMO_FAZA1/Vegetation/grass_cluster_dense.png differ diff --git a/assets/DEMO_FAZA1/Vegetation/grass_cluster_flowery.png b/assets/DEMO_FAZA1/Vegetation/grass_cluster_flowery.png new file mode 100644 index 000000000..bbca31c97 Binary files /dev/null and b/assets/DEMO_FAZA1/Vegetation/grass_cluster_flowery.png differ diff --git a/assets/DEMO_FAZA1/Vegetation/trava_sop.png b/assets/DEMO_FAZA1/Vegetation/trava_sop.png index 71d52853b..5424f3305 100644 Binary files a/assets/DEMO_FAZA1/Vegetation/trava_sop.png and b/assets/DEMO_FAZA1/Vegetation/trava_sop.png differ diff --git a/assets/DEMO_FAZA1/Vegetation/visoka_trava.png b/assets/DEMO_FAZA1/Vegetation/visoka_trava.png index 3f2d07356..c691fe14f 100644 Binary files a/assets/DEMO_FAZA1/Vegetation/visoka_trava.png and b/assets/DEMO_FAZA1/Vegetation/visoka_trava.png differ diff --git a/assets/DEMO_FAZA1/Vegetation/visoka_trava_v2.png b/assets/DEMO_FAZA1/Vegetation/visoka_trava_v2.png index 8a3f44238..85c1ef5df 100644 Binary files a/assets/DEMO_FAZA1/Vegetation/visoka_trava_v2.png and b/assets/DEMO_FAZA1/Vegetation/visoka_trava_v2.png differ diff --git a/assets/references/GAME_DESIGN_NOTES.md b/assets/references/GAME_DESIGN_NOTES.md index d762f3ffb..d1e38bd4f 100644 --- a/assets/references/GAME_DESIGN_NOTES.md +++ b/assets/references/GAME_DESIGN_NOTES.md @@ -63,3 +63,23 @@ * **Construction:** Easy to build – just dig a hole and place a marker. + +## 8. Planned Asset Updates (To-Do) +These assets are priorities for future updates to match the "Dark Chibi Noir" style: + +### Survival & Base +* **Power Generator (Agregat):** Modern, rusty industrial look. Needs a broken state (repair quest) and working state. +* **Workbench (Delovna miza):** Cluttered with tools, blueprints, and scrap. Visual progression indicator. +* **Campfire (Ogenj):** Central resting point. Needs animated flames. +* **Sleeping Bag/Tent:** Essential for saving/sleeping. + +### Farming & Water +* **Tilled Soil (Gredica):** Basic dark earth tile for planting. +* **Well (Vodnjak):** Source of clean water (distinct from the dirty stream). +* **Water Barrel (Sod):** For collecting rainwater. + +### Defense & Structures +* **Modular Fencing:** Corner, straight, and broken pieces for custom enclosures. +* **Traps:** Bear traps or spike pits for defense. +* **Dumpster:** Rusty container for looting. +* **Compost Pile:** Organic waste management. diff --git a/scripts/clean_and_resize_batch_2.py b/scripts/clean_and_resize_batch_2.py new file mode 100644 index 000000000..1ad4d9e93 --- /dev/null +++ b/scripts/clean_and_resize_batch_2.py @@ -0,0 +1,59 @@ +import cv2 +import numpy as np +import os +import sys + +def remove_white_bg_and_resize(path, target_width=None): + if not os.path.exists(path): + print(f"File not found: {path}") + return + + img = cv2.imread(path) + if img is None: + print(f"Error loading {path}") + return + + img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA) + + # Threshold for white + gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY) + _, mask = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY) + mask_inv = cv2.bitwise_not(mask) + + b, g, r, a = cv2.split(img) + final_img = cv2.merge((b, g, r, mask_inv)) + + # Crop + coords = cv2.findNonZero(mask_inv) + if coords is not None: + x, y, w, h = cv2.boundingRect(coords) + pad = 2 + x = max(0, x - pad); y = max(0, y - pad) + w = min(final_img.shape[1] - x, w + 2*pad) + h = min(final_img.shape[0] - y, h + 2*pad) + final_img = final_img[y:y+h, x:x+w] + + # Resize + if target_width: + h_curr, w_curr = final_img.shape[:2] + if w_curr > target_width: + ratio = target_width / w_curr + new_h = int(h_curr * ratio) + final_img = cv2.resize(final_img, (target_width, new_h), interpolation=cv2.INTER_AREA) + print(f"Resized {path} to {target_width}x{new_h}") + + cv2.imwrite(path, final_img) + print(f"Processed: {path}") + +# DEFINIRAJ NOVE CILJE +TARGETS = { + 'assets/DEMO_FAZA1/Environment/sign_danger.png': 256, + 'assets/DEMO_FAZA1/UI/badge_trial.png': 512, + 'assets/DEMO_FAZA1/Items/item_longboard_wheel.png': 128, + 'assets/DEMO_FAZA1/VFX/overlay_amnesia_blood.png': 1024 # Overlay stays big +} + +if __name__ == "__main__": + base = os.getcwd() + for rel, w in TARGETS.items(): + remove_white_bg_and_resize(os.path.join(base, rel), w) diff --git a/scripts/clean_and_resize_white.py b/scripts/clean_and_resize_white.py new file mode 100644 index 000000000..d73f1bbe5 --- /dev/null +++ b/scripts/clean_and_resize_white.py @@ -0,0 +1,83 @@ +import cv2 +import numpy as np +import os +import sys + +def remove_white_bg_and_resize(path, target_width=None): + if not os.path.exists(path): + print(f"File not found: {path}") + return + + img = cv2.imread(path) + if img is None: + print(f"Error loading {path}") + return + + # Convert to RGBA + img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA) + + # Threshold for white (allowing slight off-white/compression artifacts) + # White is (255,255,255). Let's say anything > 240 in all channels. + lower_white = np.array([240, 240, 240, 255]) + upper_white = np.array([255, 255, 255, 255]) + + # Create mask: looking for white pixels + # Note: cv2.inRange checks ranges. For BGRA, A is 255. + # However, input usually doesn't have A yet if loaded as BGR. + # But we converted. + + # Alternative: Grayscale thresholding is often safer for "almost white" backgrounds in illustrations + gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY) + # Create binary mask where white is 255, else 0 + _, mask = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY) + + # Invert mask: We want to KEEP non-white. + # Non-white = 0 in mask? No, thresholded white became 255. + # So we invert. + mask_inv = cv2.bitwise_not(mask) + + # Morphological cleanup (remove small white specks inside object if necessary, or smooth edges) + # For vector art, simple threshold usually works well. + + # Assign new alpha channel + b, g, r, a = cv2.split(img) + # Use mask_inv as alpha + final_img = cv2.merge((b, g, r, mask_inv)) + + # Crop to content (Trim transparent borders) + coords = cv2.findNonZero(mask_inv) + if coords is not None: + x, y, w, h = cv2.boundingRect(coords) + # Padding + pad = 2 + x = max(0, x - pad) + y = max(0, y - pad) + w = min(final_img.shape[1] - x, w + 2*pad) + h = min(final_img.shape[0] - y, h + 2*pad) + final_img = final_img[y:y+h, x:x+w] + + # Resize if target_width provided + if target_width: + h_curr, w_curr = final_img.shape[:2] + if w_curr > target_width: # Only scale down + ratio = target_width / w_curr + new_h = int(h_curr * ratio) + final_img = cv2.resize(final_img, (target_width, new_h), interpolation=cv2.INTER_AREA) + print(f"Resized to {target_width}x{new_h}") + + cv2.imwrite(path, final_img) + print(f"Processed (Cleaned White BG + Trim): {path}") + +TARGETS = { + 'assets/DEMO_FAZA1/UI/badge_purchase.png': 512, + 'assets/DEMO_FAZA1/UI/health_critical.png': 512, + 'assets/DEMO_FAZA1/VFX/toxic_fog.png': 512, + 'assets/DEMO_FAZA1/Environment/obstacle_thorns.png': 256, + 'assets/DEMO_FAZA1/Structures/foundation_concrete.png': 512, + 'assets/DEMO_FAZA1/Items/hay_drop_0.png': 128 +} + +if __name__ == "__main__": + base = os.getcwd() + for rel, w in TARGETS.items(): + remove_white_bg_and_resize(os.path.join(base, rel), w) diff --git a/scripts/clean_pink_grass.py b/scripts/clean_pink_grass.py index 43d09d8f8..1d6883e7b 100644 --- a/scripts/clean_pink_grass.py +++ b/scripts/clean_pink_grass.py @@ -1,59 +1,59 @@ import cv2 import numpy as np +import sys import os -def remove_pink_bg(): - input_path = '/Users/davidkotnik/.gemini/antigravity/brain/63340bd3-91a9-439d-b1d9-5692ce5adaea/visoka_trava_v2_pink_bg_1769436757738.png' - output_path = 'assets/DEMO_FAZA1/Vegetation/visoka_trava_v2.png' +def clean_pink_and_soften(input_path, output_path): + print(f"Processing {input_path}...") - # Ensure directory exists - os.makedirs(os.path.dirname(output_path), exist_ok=True) - - # Read image - img = cv2.imread(input_path) + img = cv2.imread(input_path, cv2.IMREAD_UNCHANGED) if img is None: - print(f"Error: Could not read {input_path}") + print("Failed to load image.") return - # Convert to RGBA - img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA) + # Check channels + if img.shape[2] == 3: + b, g, r = cv2.split(img) + # Create alpha (255) + a = np.ones_like(b) * 255 + img = cv2.merge((b, g, r, a)) - # Define Magenta range (B, G, R) - standard magenta is (255, 0, 255) in BGR - # We'll allow a small tolerance to catch anti-aliasing edges - sensitivity = 20 - lower_magenta = np.array([255 - sensitivity, 0, 255 - sensitivity]) - upper_magenta = np.array([255, sensitivity, 255]) + # Targeted Pink Range in HSV + hsv = cv2.cvtColor(img[:,:,0:3], cv2.COLOR_BGR2HSV) + lower_pink = np.array([145, 50, 50]) + upper_pink = np.array([170, 255, 255]) - # Create mask - # Note: OpenCV reads as BGR, so Pink #FF00FF is (255, 0, 255) - # Using simple color thresholding - mask = cv2.inRange(img[:,:,:3], lower_magenta, upper_magenta) + pink_mask = cv2.inRange(hsv, lower_pink, upper_pink) - # Invert mask (we want to keep non-magenta) - mask_inv = cv2.bitwise_not(mask) - - # Apply mask to alpha channel - # Where mask is white (magenta pixels), alpha becomes 0 - # But mask_inv is white for KEEP pixels. - # So we want Alpha to be 0 where mask is 255. - - # Better approach: - # 1. Split channels + # 1. REMOVE PINK (Set alpha to 0) + # Get current alpha b, g, r, a = cv2.split(img) - # 2. Update alpha channel using the inverted mask - # If mask pixel is 255 (magenta), mask_inv is 0. We want alpha 0 there. - # If mask pixel is 0 (not magenta), mask_inv is 255. We want alpha 255 there. - # However, original alpha is 255 everywhere inside the image bounds. - # So we can just take bitwise_and or just set alpha to mask_inv. - - img[:, :, 3] = mask_inv + # Where pink_mask is 255, set alpha to 0 + # Also combine with existing alpha (if image was already transparent, keep it) + new_alpha = cv2.bitwise_and(a, cv2.bitwise_not(pink_mask)) - # Optional: Basic edge smoothing/despiking if needed, but for pixel/vector art simple cut is often better. + # 2. EDGE SOFTENING (Alpha Blur 1px) + # We blur the alpha channel slightly to soften jagged edges + soft_alpha = cv2.GaussianBlur(new_alpha, (3, 3), 0) - # Save - cv2.imwrite(output_path, img) - print(f"Successfully saved transparent image to {output_path}") + # Thresholding back to keep semi-transparency only on very edge? + # Or just keep the blur. For "Vector Style", usually we want sharp, but + # user asked for "zmehčaj robove (Alpha Blur: 1px)". + # Gaussian (3,3) is roughly 1px radius. + + # 3. COLOR BLEED REMOVAL (Halo effect) + # Often pink pixels leave a pink fringe. + # We can dilate the non-pink color into the transparent area? + # Or simpler: desaturate or darken pixels on the edge of transparency. + # For now, let's stick to the requested Blur. + + final_img = cv2.merge((b, g, r, soft_alpha)) + + cv2.imwrite(output_path, final_img) + print(f"Saved cleaned image to {output_path}") if __name__ == "__main__": - remove_pink_bg() + # 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) diff --git a/scripts/clean_single_water.py b/scripts/clean_single_water.py new file mode 100644 index 000000000..dd53760cd --- /dev/null +++ b/scripts/clean_single_water.py @@ -0,0 +1,56 @@ +import cv2 +import numpy as np +import os +import sys + +def remove_white_bg_and_resize(path, target_width=None): + if not os.path.exists(path): + print(f"File not found: {path}") + return + + img = cv2.imread(path) + if img is None: + print(f"Error loading {path}") + return + + img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA) + + # Threshold for white + gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY) + _, mask = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY) + mask_inv = cv2.bitwise_not(mask) + + b, g, r, a = cv2.split(img) + final_img = cv2.merge((b, g, r, mask_inv)) + + # Crop + coords = cv2.findNonZero(mask_inv) + if coords is not None: + x, y, w, h = cv2.boundingRect(coords) + pad = 2 + x = max(0, x - pad); y = max(0, y - pad) + w = min(final_img.shape[1] - x, w + 2*pad) + h = min(final_img.shape[0] - y, h + 2*pad) + final_img = final_img[y:y+h, x:x+w] + + # Resize + if target_width: + h_curr, w_curr = final_img.shape[:2] + if w_curr > target_width: + ratio = target_width / w_curr + new_h = int(h_curr * ratio) + final_img = cv2.resize(final_img, (target_width, new_h), interpolation=cv2.INTER_AREA) + print(f"Resized {path} to {target_width}x{new_h}") + + cv2.imwrite(path, final_img) + print(f"Processed: {path}") + +# SAMO NOVA ZADNJA SLIKA +TARGETS = { + 'assets/DEMO_FAZA1/Environment/water_clean_patch.png': 512 +} + +if __name__ == "__main__": + base = os.getcwd() + for rel, w in TARGETS.items(): + remove_white_bg_and_resize(os.path.join(base, rel), w) diff --git a/scripts/clean_water_assets.py b/scripts/clean_water_assets.py new file mode 100644 index 000000000..1c95f81dc --- /dev/null +++ b/scripts/clean_water_assets.py @@ -0,0 +1,57 @@ +import cv2 +import numpy as np +import os +import sys + +def remove_white_bg_and_resize(path, target_width=None): + if not os.path.exists(path): + print(f"File not found: {path}") + return + + img = cv2.imread(path) + if img is None: + print(f"Error loading {path}") + return + + img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA) + + # Threshold for white + gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY) + _, mask = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY) + mask_inv = cv2.bitwise_not(mask) + + b, g, r, a = cv2.split(img) + final_img = cv2.merge((b, g, r, mask_inv)) + + # Crop + coords = cv2.findNonZero(mask_inv) + if coords is not None: + x, y, w, h = cv2.boundingRect(coords) + pad = 2 + x = max(0, x - pad); y = max(0, y - pad) + w = min(final_img.shape[1] - x, w + 2*pad) + h = min(final_img.shape[0] - y, h + 2*pad) + final_img = final_img[y:y+h, x:x+w] + + # Resize + if target_width: + h_curr, w_curr = final_img.shape[:2] + if w_curr > target_width: + ratio = target_width / w_curr + new_h = int(h_curr * ratio) + final_img = cv2.resize(final_img, (target_width, new_h), interpolation=cv2.INTER_AREA) + print(f"Resized {path} to {target_width}x{new_h}") + + cv2.imwrite(path, final_img) + print(f"Processed: {path}") + +# NOVI CILJI (Voda) +TARGETS = { + 'assets/DEMO_FAZA1/Environment/mud_puddle.png': 512, + 'assets/DEMO_FAZA1/Environment/stream_water.png': 512 +} + +if __name__ == "__main__": + base = os.getcwd() + for rel, w in TARGETS.items(): + remove_white_bg_and_resize(os.path.join(base, rel), w) diff --git a/scripts/fix_pink_edges.py b/scripts/fix_pink_edges.py new file mode 100644 index 000000000..85efa75ce --- /dev/null +++ b/scripts/fix_pink_edges.py @@ -0,0 +1,43 @@ +import cv2 +import numpy as np +import os +import glob + +def fix_pink_edges(directory): + print(f"Scanning {directory} for pink edges...") + + # Target specific problematic file if needed, or all + files = glob.glob(os.path.join(directory, "*.png")) + + for path in files: + img = cv2.imread(path, cv2.IMREAD_UNCHANGED) + if img is None: + continue + + if img.shape[2] < 4: + continue + + b, g, r, a = cv2.split(img) + + # 1. Wider Pink/Magenta Range + # Catch anything with high Red/Blue and relatively lower Green + pink_mask = (r > 150) & (g < 100) & (b > 150) + + # Turn pink pixels transparent + a[pink_mask] = 0 + + # 2. Aggressive Alpha Erosion + # Increase iterations to eat 2 pixels into the edge + # This removes the "halo" left by anti-aliasing against pink + kernel = np.ones((3,3), np.uint8) + a_eroded = cv2.erode(a, kernel, iterations=1) # 1 iteration with 3x3 kernel = 1-2px border + + # Merge back + img_fixed = cv2.merge((b, g, r, a_eroded)) + + cv2.imwrite(path, img_fixed) + print(f"Aggressively Fixed: {os.path.basename(path)}") + +if __name__ == "__main__": + target_dir = "assets/DEMO_FAZA1/Vegetation" + fix_pink_edges(target_dir) diff --git a/scripts/resize_assets.py b/scripts/resize_assets.py index 207260df5..4389e4471 100644 --- a/scripts/resize_assets.py +++ b/scripts/resize_assets.py @@ -1,48 +1,48 @@ - -from PIL import Image +import cv2 import os -# Configuration -# Configuration -TARGET_DIRS = ["assets/grounds", "assets/maps/tilesets"] # Expanded targets -TARGET_SIZE = (256, 256) +# Define target widths for assets +TARGETS = { + 'assets/DEMO_FAZA1/UI/badge_purchase.png': 512, + 'assets/DEMO_FAZA1/UI/health_critical.png': 512, + 'assets/DEMO_FAZA1/VFX/toxic_fog.png': 512, + 'assets/DEMO_FAZA1/Environment/obstacle_thorns.png': 256, + 'assets/DEMO_FAZA1/Structures/foundation_concrete.png': 512, + 'assets/DEMO_FAZA1/Items/hay_drop_0.png': 128 +} -def resize_images(): - for target_dir in TARGET_DIRS: - if not os.path.exists(target_dir): - print(f"⚠️ Directory {target_dir} not found. Skipping.") - continue +def resize_image(path, target_width): + if not os.path.exists(path): + print(f"Skipping {path}, file not found.") + return - print(f"📂 Processing directory: {target_dir}...") - - files = [f for f in os.listdir(target_dir) if f.lower().endswith(('.png', '.jpg', '.jpeg'))] - - for filename in files: - filepath = os.path.join(target_dir, filename) - try: - with Image.open(filepath) as img: - width, height = img.size - - # SELECTIVE RESIZE LOGIC - # 1. Must be Square 1024x1024 (Grounds, Props, Trees) - # 2. Or if explicitly in 'grounds' folder and > 256 - - should_resize = False - - if "grounds" in target_dir and width > 256: - should_resize = True - elif width == 1024 and height == 1024: - should_resize = True - - if should_resize: - print(f"✨ Resizing {filename} ({width}x{height}) -> {TARGET_SIZE} (Lanczos)") - img_resized = img.resize(TARGET_SIZE, Image.Resampling.LANCZOS) - img_resized.save(filepath) - else: - pass # print(f"Skipping {filename} ({width}x{height}) - Criteria not met") - - except Exception as e: - print(f"❌ Error processing {filename}: {e}") + img = cv2.imread(path, cv2.IMREAD_UNCHANGED) + if img is None: + print(f"Error loading {path}") + return + + h, w = img.shape[:2] + + # Check if resize is needed + if w <= target_width: + print(f"{path} is already {w}px (Target: {target_width}px). Skipping resize.") + return + + # Calculate new height to maintain aspect ratio + ratio = target_width / w + new_h = int(h * ratio) + + resized = cv2.resize(img, (target_width, new_h), interpolation=cv2.INTER_AREA) + + cv2.imwrite(path, resized) + print(f"Resized {path}: {w}x{h} -> {target_width}x{new_h}") if __name__ == "__main__": - resize_images() + base_path = os.getcwd() + print("Starting Resize Job...") + + for rel_path, width in TARGETS.items(): + full_path = os.path.join(base_path, rel_path) + resize_image(full_path, width) + + print("Resize Job Complete.") diff --git a/scripts/utils/clean_and_move_image.py b/scripts/utils/clean_and_move_image.py new file mode 100644 index 000000000..0ca46f0ba --- /dev/null +++ b/scripts/utils/clean_and_move_image.py @@ -0,0 +1,75 @@ + +import cv2 +import numpy as np +import os +import argparse +import sys + +def clean_and_save(input_path, output_path): + # Ensure directory exists + os.makedirs(os.path.dirname(output_path), exist_ok=True) + + print(f"Processing: {input_path}") + + # Read image + img = cv2.imread(input_path) + if img is None: + print(f"Error: Could not read {input_path}") + sys.exit(1) + + # Convert to RGBA + img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA) + + # Convert to HSV for better color segmentation + hsv = cv2.cvtColor(img[:,:,:3], cv2.COLOR_BGR2HSV) + + # Define Magenta range in HSV + # Magenta is around 300 degrees. OpenCV Hue is 0-179 (degrees/2). So 300/2 = 150. + # Range: 140 - 160 approx. + # We want to catch the solid pink background. + + # Lower and Upper bounds for Magenta/Pink + # Hue: 140-170, Saturation: > 50, Value: > 50 + lower_magenta = np.array([140, 50, 50]) + upper_magenta = np.array([170, 255, 255]) + + mask = cv2.inRange(hsv, lower_magenta, upper_magenta) + + # Invert mask (keep non-magenta) + mask_inv = cv2.bitwise_not(mask) + + # Update alpha channel + # Where mask is 255 (magenta), alpha should be 0. + # We can just set the alpha channel to mask_inv directly. + # However, to avoid hard edges, we might want to erode/dilate, but for pixel/vector props hard cut is usually fine. + + # Let's perform a small morphological operation to remove pink noise on edges if any + kernel = np.ones((1,1), np.uint8) + mask_inv = cv2.morphologyEx(mask_inv, cv2.MORPH_OPEN, kernel) + + b, g, r, a = cv2.split(img) + img = cv2.merge((b, g, r, mask_inv)) + + # Crop to content (optional but good for assets) + # Find bounding box of non-transparent area + coords = cv2.findNonZero(mask_inv) + if coords is not None: + x, y, w, h = cv2.boundingRect(coords) + # Add 1px padding if possible + x = max(0, x-1) + y = max(0, y-1) + w = min(img.shape[1]-x, w+2) + h = min(img.shape[0]-y, h+2) + img = img[y:y+h, x:x+w] + + # Save + cv2.imwrite(output_path, img) + print(f"Successfully saved cleaned image to {output_path}") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Remove magenta background and save.') + parser.add_argument('input', help='Input file path') + parser.add_argument('output', help='Output file path') + args = parser.parse_args() + + clean_and_save(args.input, args.output) diff --git a/src/scenes/GrassScene_Clean.js b/src/scenes/GrassScene_Clean.js index 745f0d83d..4aeb73b60 100644 --- a/src/scenes/GrassScene_Clean.js +++ b/src/scenes/GrassScene_Clean.js @@ -1,216 +1,289 @@ -export default class GrassScene extends Phaser.Scene { - constructor() { - super({ key: 'GrassScene' }); - this.baseTime = 12; - this.timeSpeed = 0.5; - this.playerSpeed = 200; - this.hayCount = 0; - } - +export default class GrassSceneClean extends Phaser.Scene { preload() { - console.log("🌿 Loading Clean Assets..."); + this.load.path = 'assets/DEMO_FAZA1/'; - const PATHS = { - ground: 'assets/DEMO_FAZA1/Ground/', - veg: 'assets/DEMO_FAZA1/Vegetation/', - char: 'assets/references/', - items: 'assets/DEMO_FAZA1/Items/Hay/', - audio: 'assets/audio/_NEW/' - }; + // --- CHARACTERS --- + this.load.image('kai', 'Characters/Kai_Dreads.png'); - this.load.image('ground_base', PATHS.ground + 'tla_trava_tekstura.png'); - this.load.image('grass_tall', PATHS.veg + 'visoka_trava.png'); - this.load.image('hay_drop', PATHS.items + 'hay_drop_0.png'); - this.load.image('kai', PATHS.char + 'kaj.png'); - this.load.audio('step_grass', PATHS.audio + 'footstep_grass_000.ogg'); + // --- GROUND & ENVIRONMENT --- + this.load.image('ground_base', 'Ground/tla_trava_tekstura.png'); + this.load.image('ground_dirt', 'Ground/ground_dirt_patch.png'); - this.load.on('loaderror', (file) => { - console.error('FAILED TO LOAD:', file.src); - }); + // --- WATER BIOMES --- + // Layer 1 (Flat/Dirty) + this.load.image('puddle_mud', 'Environment/mud_puddle.png'); + this.load.image('stream', 'Environment/stream_water.png'); + + // Layer 2 (Interactive/Structure) + this.load.image('water_clean', 'Environment/water_clean_patch.png'); + + // --- VEGETATION --- + this.load.image('grass_low', 'Vegetation/trava_sop.png'); + this.load.image('grass_high', 'Vegetation/visoka_trava_v2.png'); + this.load.image('grass_dense', 'Vegetation/grass_cluster_dense.png'); + + // --- OBSTACLES --- + this.load.image('thorns', 'Environment/obstacle_thorns.png'); + this.load.image('hay', 'Items/hay_drop_0.png'); + this.load.image('tree_big', 'Vegetation/drevo_veliko.png'); } create() { - const { width, height } = this.scale; + // 1. WORLD SETUP + const WORLD_W = 2000; + const WORLD_H = 2000; + this.physics.world.setBounds(0, 0, WORLD_W, WORLD_H); + this.cameras.main.setBounds(0, 0, WORLD_W, WORLD_H); + this.cameras.main.setBackgroundColor('#1a3300'); - // 1. BACKGROUND - this.ground = this.add.tileSprite(0, 0, width, height, 'ground_base') - .setOrigin(0, 0); + // ZONES TRACKING (To prevent grass on water/objects) + // Array of {x, y, radius} or {rect} + this.restrictedZones = []; - // 2. GROUPS - this.tallGrassGroup = this.physics.add.group(); - this.hayGroup = this.physics.add.group(); + // --- LAYER 0: OSNOVA --- + this.add.tileSprite(WORLD_W / 2, WORLD_H / 2, WORLD_W, WORLD_H, 'ground_base').setDepth(0); - this.allGrass = []; + // --- LAYER 1: VODA & UMAZANIJA (Flat, "vtopljeno" v tla) --- + // Trava NE SME rasti tukaj + this.createGroundLiquids(WORLD_W); - // 3. GENERATE WORLD - ONLY GRASS - // "Vsa drevesa odstranjena. Samo trava ostane." - // "Nastavi neskončno travo čez celo mapo" - this.generateInfiniteGrass(width, height); + // --- LAYER 2: INTERAKTIVNI OBJEKTI & Y-SORT GROUP --- + this.gameObjects = this.add.group({ runChildUpdate: true }); + this.obstacles = this.physics.add.group(); // For thorns interaction + this.hayGroup = this.physics.add.group({ bounceX: 0.2, bounceY: 0.2, dragX: 100, dragY: 100 }); - // 4. KAI (Player) - this.player = this.physics.add.sprite(width / 2, height / 2, 'kai'); - this.player.setScale(0.5); - this.player.setOrigin(0.5, 0.92); - this.player.setCollideWorldBounds(true); - this.player.setDepth(this.player.y); + // Place Major Objects (Clean Water, Hay, Thorns, Big Trees) + // with STRICT SPACING (100-150px) + this.createMajorObjects(WORLD_W, WORLD_H); - this.lastStepTime = 0; + // --- VEGETATION FILL --- + // Fills the gaps, strictly respecting restrictedZones + this.populateVegetation(WORLD_W, WORLD_H); - // 5. INTERACTION LOGIC - this.physics.add.overlap(this.player, this.tallGrassGroup, this.handleMow, null, this); - this.physics.add.overlap(this.player, this.hayGroup, this.collectHay, null, this); + // --- PLAYER (KAI) --- + this.kai = this.physics.add.sprite(1000, 1000, 'kai'); + this.kai.setCollideWorldBounds(true); + this.kai.body.setSize(30, 30); + this.kai.body.setOffset(this.kai.width / 2 - 15, this.kai.height - 30); - // Input - this.cursors = this.input.keyboard.createCursorKeys(); - this.spaceKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE); + // Scale to 64px height strict + this.kai.setScale(64 / this.kai.height); - // 6. AMNESIA FADE IN - this.cameras.main.fadeIn(3000, 0, 0, 0); + this.kai.setDepth(this.kai.y); + this.gameObjects.add(this.kai); - // DYNAMIC VIGNETTE - const canvasTexture = this.textures.createCanvas('vignette_tex', width, height); - const ctx = canvasTexture.context; - const grd = ctx.createRadialGradient(width / 2, height / 2, height * 0.3, width / 2, height / 2, height * 0.8); - grd.addColorStop(0, "rgba(0,0,0,0)"); - grd.addColorStop(1, "rgba(0,0,0,0.9)"); + // Physics Colliders for Layer 2 objects + this.physics.add.collider(this.kai, this.hayGroup); + this.physics.add.collider(this.hayGroup, this.hayGroup); - ctx.fillStyle = grd; - ctx.fillRect(0, 0, width, height); - canvasTexture.refresh(); + // Camera + this.cameras.main.startFollow(this.kai, true, 0.08, 0.08); - this.vignette = this.add.image(width / 2, height / 2, 'vignette_tex') - .setScrollFactor(0) - .setAlpha(0) - .setDepth(8000); - - // 7. DAY/NIGHT OVERLAY - this.dayNightOverlay = this.add.rectangle(0, 0, width, height, 0x000000, 0) - .setOrigin(0, 0) - .setDepth(9000); - - // 8. UI INFO - this.infoText = this.add.text(20, 20, '', { - fontFamily: 'monospace', fontSize: '20px', fill: '#ffffff', stroke: '#000000', strokeThickness: 4 - }).setDepth(10000); - - this.inventoryText = this.add.text(width - 150, 20, 'Seno: 0', { - fontFamily: 'monospace', fontSize: '20px', fill: '#ffff00', stroke: '#000000', strokeThickness: 4 - }).setDepth(10000); - } - - generateInfiniteGrass(w, h) { - // "Density = 1.0f" -> High density, cover everything. - // We'll use a grid loop with some randomization offset - // Grass scale is 0.2, original image is ~512? - // 0.2 * 512 = ~100px width. - // Step size ~50px for overlap. - - const stepX = 40; - const stepY = 30; - - for (let y = 0; y < h; y += stepY) { - for (let x = 0; x < w; x += stepX) { - // Add some randomness - let gx = x + Phaser.Math.Between(-10, 10); - let gy = y + Phaser.Math.Between(-10, 10); - - // create(x, y, key) - let grass = this.tallGrassGroup.create(gx, gy, 'grass_tall'); - grass.setScale(0.2); - grass.setOrigin(0.5, 0.95); - grass.setDepth(gy + 1); - - grass.swaySpeed = 0.002 + Math.random() * 0.001; - grass.swayOffset = Math.random() * 100; - - this.allGrass.push(grass); - } - } - } - - handleMow(player, grass) { - if (this.spaceKey.isDown) { - let hay = this.hayGroup.create(grass.x, grass.y + 10, 'hay_drop'); - hay.setScale(0.6); - hay.setDepth(1); - hay.setAngle(Phaser.Math.Between(-20, 20)); - grass.destroy(); - const index = this.allGrass.indexOf(grass); - if (index > -1) this.allGrass.splice(index, 1); - } - } - - collectHay(player, hay) { - hay.destroy(); - this.hayCount++; - this.inventoryText.setText(`Seno: ${this.hayCount}`); - } - - update(time, delta) { - // MOVEMENT LOGIC - let currentSpeed = this.playerSpeed; - let isMoving = false; - - // HIDING CHECK (Always effectively in grass in this mode, but let's check overlap technically) - let inTallGrass = this.physics.overlap(this.player, this.tallGrassGroup); - - if (inTallGrass) { - currentSpeed *= 0.6; - // "Player.CharacterAlpha = 0.4f" - this.player.setAlpha(0.4); - if (this.vignette.alpha < 1) this.vignette.alpha += 0.05; + // AMNESIA VISUALS + if (this.cameras.main.postFX) { + const blur = this.cameras.main.postFX.addBlur(20, 2, 2, 1.0); + this.tweens.add({ targets: blur, strength: 0, duration: 5000, ease: 'Power2' }); } else { - // If he mows a clearing, he becomes visible - this.player.setAlpha(1.0); - if (this.vignette.alpha > 0) this.vignette.alpha -= 0.05; + this.cameras.main.fadeIn(5000); } - this.player.setVelocity(0); - if (this.cursors.left.isDown) { - this.player.setVelocityX(-currentSpeed); - this.player.setFlipX(true); - isMoving = true; - } else if (this.cursors.right.isDown) { - this.player.setVelocityX(currentSpeed); - this.player.setFlipX(false); - isMoving = true; - } - if (this.cursors.up.isDown) { - this.player.setVelocityY(-currentSpeed); - isMoving = true; - } else if (this.cursors.down.isDown) { - this.player.setVelocityY(currentSpeed); - isMoving = true; + console.log("Nova Farma: Logical Layering Active."); + } + + createGroundLiquids(w) { + // Mud Puddles (Flat) + for (let i = 0; i < 8; i++) { + let x = Phaser.Math.Between(200, 1800); + let y = Phaser.Math.Between(200, 1800); + // Safe zone check + if (Phaser.Math.Distance.Between(x, y, 1000, 1000) < 300) continue; + + let mud = this.add.image(x, y, 'puddle_mud'); + mud.setDepth(0.01); // Layer 1 + mud.setScale(1.2); + + // Register zone (exclude grass here) + // Radius approx width/2 * scale + this.restrictedZones.push({ x: x, y: y, radius: (mud.width / 2 * 1.2) - 10 }); } - // AUDIO - if (isMoving && inTallGrass) { - if (time > this.lastStepTime + 350) { - this.sound.play('step_grass', { volume: 0.4 }); - this.lastStepTime = time; + // Stream (Flat) + for (let i = 0; i < 5; i++) { + let x = 300 + (i * 250); + let y = 1600 + Math.sin(i) * 60; + let stream = this.add.image(x, y, 'stream'); + stream.setDepth(0.01); + stream.setRotation(0.1); + + this.restrictedZones.push({ x: x, y: y, radius: 100 }); + } + } + + createMajorObjects(w, h) { + // We will maintain a list of major object positions to enforce the 150px grid spacing + // restrictedZones handles the water overlap, duplicate this list for object spacing if needed + // but adding to restrictedZones works for both. + + // 1. Clean Water Panel (Interactive, Y-Sorted) + let wx = 1150, wy = 950; + let wClean = this.add.image(wx, wy, 'water_clean'); + wClean.setOrigin(0.5, 0.9); + wClean.setScale(0.7); // Slightly smaller + this.restrictedZones.push({ x: wx, y: wy, radius: 80 }); + this.gameObjects.add(wClean); + + // 2. Thorns + // ... (Similar logic as before, ensure spacing) + for (let i = 0; i < 12; i++) { + let pos = this.findFreeSpot(w, h, 150); + if (pos) { + let thorn = this.obstacles.create(pos.x, pos.y, 'thorns'); + thorn.setOrigin(0.5, 0.82); + thorn.body.setCircle(30); + thorn.body.setOffset(thorn.width / 2 - 30, thorn.height - 40); + thorn.setImmovable(true); + this.restrictedZones.push({ x: pos.x, y: pos.y, radius: 80 }); + this.gameObjects.add(thorn); } } - // VISUALS - this.player.setDepth(this.player.y); - this.allGrass.forEach(g => { if (g.active) g.rotation = Math.sin((time * g.swaySpeed) + g.swayOffset) * 0.15; }); + // 3. Hay + for (let i = 0; i < 6; i++) { + let pos = this.findFreeSpot(w, h, 100); + if (pos) { + let hay = this.hayGroup.create(pos.x, pos.y, 'hay'); + hay.setOrigin(0.5, 0.85); + hay.setScale(0.8); + hay.body.setCircle(15); + hay.body.setOffset(hay.width / 2 - 15, hay.height - 20); + this.restrictedZones.push({ x: pos.x, y: pos.y, radius: 50 }); + this.gameObjects.add(hay); + } + } - this.baseTime += (delta * 0.001 * this.timeSpeed); - if (this.baseTime >= 24) this.baseTime = 0; - this.updateLighting(this.baseTime); - - let hour = Math.floor(this.baseTime); - let minute = Math.floor((this.baseTime % 1) * 60).toString().padStart(2, '0'); - this.infoText.setText(`Time: ${hour}:${minute}\n[Arrows] Premik\n[Space] Košnja`); + // 4. BIG TREES + for (let i = 0; i < 10; i++) { + let pos = this.findFreeSpot(w, h, 200); + if (pos) { + let tree = this.add.image(pos.x, pos.y, 'tree_big'); + tree.setOrigin(0.5, 0.92); + tree.setScale(1.3); + this.restrictedZones.push({ x: pos.x, y: pos.y, radius: 100 }); + this.gameObjects.add(tree); + } + } } - updateLighting(hour) { - let alpha = 0; - let color = 0x000000; - if (hour >= 20 || hour < 5) { alpha = 0.7; color = 0x000022; } - else if (hour >= 5 && hour < 8) { alpha = 0.3; color = 0xFF4500; } - else if (hour >= 18 && hour < 20) { alpha = 0.4; color = 0xFF4500; } - else { alpha = 0; } - this.dayNightOverlay.setFillStyle(color, alpha); + populateVegetation(w, h) { + // Fill gaps with grass + // We use a looser spacing for grass (density) but strict check against restrictedZones + + for (let i = 0; i < 400; i++) { + let x = Phaser.Math.Between(50, w - 50); + let y = Phaser.Math.Between(50, h - 50); + + // Check strict zones (Water, Thorns, Trees) + if (this.isZoneRestricted(x, y)) continue; + + let type = ''; + let scale = 1.0; + let rng = Math.random(); + + if (rng > 0.9) { + // Dense Hiding Grass + type = 'grass_dense'; + } else if (rng > 0.6) { + type = 'grass_high'; + } else { + type = 'grass_low'; + } + + let grass = this.add.image(x, y, type); + grass.setOrigin(0.5, 1.0); // Pivot Bottom for sorting + + // Scale logic: Knee to Waist height + // Kai ~64px. Grass ~30-45px. + let hTarget = 25 + Math.random() * 20; + grass.setScale(hTarget / grass.height); + + // Tint blend + grass.setTint(0xcccccc); + + this.gameObjects.add(grass); + } + } + + // Helper: Find spot with minimal distance to others + findFreeSpot(w, h, minDistance) { + for (let i = 0; i < 50; i++) { // 50 attempts + let x = Phaser.Math.Between(100, w - 100); + let y = Phaser.Math.Between(100, h - 100); + + // Safe spawn area check + if (Phaser.Math.Distance.Between(x, y, 1000, 1000) < 300) continue; + + // Check specific restrictions + let ok = true; + for (let z of this.restrictedZones) { + if (Phaser.Math.Distance.Between(x, y, z.x, z.y) < (z.radius + minDistance)) { + ok = false; + break; + } + } + if (ok) return { x, y }; + } + return null; + } + + isZoneRestricted(x, y) { + for (let z of this.restrictedZones) { + // For grass, we just need to be outside the radius of the object (water/tree) + if (Phaser.Math.Distance.Between(x, y, z.x, z.y) < z.radius) return true; + } + return false; + } + + update() { + // 1. GLOBAL DEPTH & Y-SORT + this.gameObjects.children.each(child => { + child.setDepth(child.y); + }); + + // 2. MOVEMENT & PHYSICS + const cursors = this.input.keyboard.createCursorKeys(); + let speed = 200; + + // Interaction Checks + let inThorns = false; + let inMud = false; + + // Thorns + this.physics.overlap(this.kai, this.obstacles, () => inThorns = true); + + // Mud (Sinking Effect) + this.physics.overlap(this.kai, this.mudGroup, () => inMud = true); + + // Apply Effects + if (inThorns) { + speed = 100; + this.kai.setTint(0xffaaaa); + } else { + this.kai.clearTint(); + } + + if (inMud) { + this.kai.setAlpha(0.8); // "Ugrez" simulation + } else { + this.kai.setAlpha(1.0); + } + + // Velocity + this.kai.setVelocity(0); + if (cursors.left.isDown) this.kai.setVelocityX(-speed); + else if (cursors.right.isDown) this.kai.setVelocityX(speed); + if (cursors.up.isDown) this.kai.setVelocityY(-speed); + else if (cursors.down.isDown) this.kai.setVelocityY(speed); + + this.kai.body.velocity.normalize().scale(speed); } }