- Enforced 'Style 32 - Dark Chibi Vector' for all ground assets. - Fixed critical Prologue-to-Game crash (function renaming). - Implemented Tiled JSON/TMX auto-conversion. - Updated Asset Manager to visualize 1800+ assets. - Cleaned up project structure (new assets/grounds folder). - Auto-Ground logic added to GameScene.js.
690 lines
25 KiB
JavaScript
690 lines
25 KiB
JavaScript
// Preload Scene - Nalaganje assetov
|
|
class PreloadScene extends Phaser.Scene {
|
|
constructor() {
|
|
super({ key: 'PreloadScene' });
|
|
}
|
|
|
|
preload() {
|
|
console.log('⏳ PreloadScene: Loading MINIMAL assets for DEMO...');
|
|
|
|
this.createLoadingBar();
|
|
|
|
// 🛠️ Asset Fix: Create placeholder for missing particles
|
|
// This prevents crash in WaterPhysicsSystem if 'particle_white' is used before load
|
|
if (!this.textures.exists('particle_white')) {
|
|
const g = this.make.graphics({ x: 0, y: 0, add: false });
|
|
g.fillStyle(0xffffff, 1);
|
|
g.fillCircle(4, 4, 4);
|
|
g.generateTexture('particle_white', 8, 8);
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// 🎵 AUDIO PRELOAD - Cinematic Voices
|
|
// ═══════════════════════════════════════════════════════════════
|
|
this.preloadAudio();
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// 🌍 LOCALIZATION
|
|
// ═══════════════════════════════════════════════════════════════
|
|
this.load.json('localization', 'assets/localization.json');
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// 🎨 CHARACTER SPRITES - Demo Characters
|
|
// ═══════════════════════════════════════════════════════════════
|
|
this.preloadCharacterSprites();
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// 🎮 DEMO MODE - ALL OLD ASSETS DISABLED!
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// DemoSceneEnhanced has its own preload() - ne rabimo ničesar!
|
|
// This PreloadScene samo pokazuje loading bar in gre v DemoSceneEnhanced
|
|
|
|
console.log('✅ Minimal preload complete - DemoSceneEnhanced will load its own assets!');
|
|
}
|
|
|
|
preloadCharacterSprites() {
|
|
console.log('🎨 Preloading ALL ASSETS from AssetManifest...');
|
|
|
|
// Dynamic import logic (requires module system, but we'll use direct reference if global or shim)
|
|
// Since we are in Setup phase, we assume AssetManifest is loaded via script tag or bundler
|
|
|
|
// Check if AssetManifest exists globally or we need to rely on the file we created
|
|
// For this environment, we'll manually iterate the data structure if it's not imported.
|
|
// But since we just created src/AssetManifest.js, let's try to assume it's available.
|
|
|
|
// NOTE: In standard Phaser/Webpack, we would import { AssetManifest } from '../AssetManifest'.
|
|
// Here we will inject logic to load it. For now, let's assume we can fetch it or it's hardcoded.
|
|
|
|
// Load Manifest Images
|
|
// Since we are using script tags in index.html, window.AssetManifest is already available!
|
|
if (window.AssetManifest) {
|
|
const manifest = window.AssetManifest;
|
|
|
|
console.log(`📦 Loading Asset Manifest (Phased)...`);
|
|
|
|
// Iterate over phases (farm, basement_mine, etc.)
|
|
if (manifest.phases) {
|
|
Object.keys(manifest.phases).forEach(phaseName => {
|
|
console.log(`Phase: ${phaseName} - ${manifest.phases[phaseName].length} items`);
|
|
manifest.phases[phaseName].forEach(img => {
|
|
this.loadImageSafe(img.key, img.path);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Iterate over spritesheets
|
|
if (manifest.spritesheets) {
|
|
manifest.spritesheets.forEach(sheet => {
|
|
this.load.spritesheet(sheet.key, sheet.path, sheet.frameConfig);
|
|
});
|
|
}
|
|
|
|
console.log('✅ AssetManifest Queue Complete');
|
|
|
|
// 🗺️ LOAD MAP
|
|
this.load.tilemapTiledJSON('NovaFarma', 'assets/maps/NovaFarma.json');
|
|
console.log('🗺️ Preloading NovaFarma.json map...');
|
|
} else {
|
|
console.error('❌ Critical: window.AssetManifest not found! Check index.html imports.');
|
|
}
|
|
}
|
|
|
|
loadImageSafe(key, path) {
|
|
try {
|
|
this.load.image(key, path);
|
|
} catch (error) {
|
|
console.warn(`⚠️ Image skipped: ${key}`);
|
|
}
|
|
}
|
|
|
|
preloadAudio() {
|
|
console.log('🎵 Preloading audio assets...');
|
|
|
|
const basePath = 'assets/audio/voices/';
|
|
|
|
// 🎬 INTRO CUTSCENE VOICES (NEW!)
|
|
this.loadAudioSafe('intro_flyover', 'assets/audio/voiceover/intro/01_narrator_flyover.mp3');
|
|
this.loadAudioSafe('intro_awakening', 'assets/audio/voiceover/intro/02_kai_awakening.mp3');
|
|
this.loadAudioSafe('intro_truth_1', 'assets/audio/voiceover/intro/03_kai_truth_part1.mp3');
|
|
this.loadAudioSafe('intro_truth_2', 'assets/audio/voiceover/intro/04_kai_truth_part2.mp3');
|
|
|
|
// Narrator (cinematic)
|
|
this.loadAudioSafe('narrator_intro', basePath + 'narrator/intro_cutscene.mp3');
|
|
this.loadAudioSafe('narrator_memory', basePath + 'narrator/kai_memory_ana.mp3');
|
|
this.loadAudioSafe('narrator_discovery', basePath + 'narrator/discovery_church.mp3');
|
|
|
|
// Kai voices
|
|
this.loadAudioSafe('kai_voice_1', basePath + 'kai/kai_01.mp3');
|
|
this.loadAudioSafe('kai_voice_2', basePath + 'kai/kai_02.mp3');
|
|
this.loadAudioSafe('kai_voice_3', basePath + 'kai/kai_03.mp3');
|
|
this.loadAudioSafe('kai_voice_4', basePath + 'kai/kai_04.mp3');
|
|
this.loadAudioSafe('kai_voice_5', basePath + 'kai/kai_05.mp3');
|
|
|
|
// Ana voices
|
|
this.loadAudioSafe('ana_voice_1', basePath + 'ana/ana_01.mp3');
|
|
this.loadAudioSafe('ana_voice_2', basePath + 'ana/ana_02.mp3');
|
|
this.loadAudioSafe('ana_voice_3', basePath + 'ana/ana_03.mp3');
|
|
this.loadAudioSafe('ana_voice_4', basePath + 'ana/ana_04.mp3');
|
|
|
|
console.log('🎵 Audio preload queued');
|
|
}
|
|
|
|
loadAudioSafe(key, path) {
|
|
try {
|
|
this.load.audio(key, path);
|
|
} catch (error) {
|
|
console.warn(`⚠️ Audio skipped: ${key}`);
|
|
}
|
|
}
|
|
|
|
createAnimations() {
|
|
if (this.anims.exists('protagonist_walk')) return;
|
|
|
|
// Zombie animations (only if texture exists)
|
|
if (this.textures.exists('zombie_walk')) {
|
|
this.anims.create({
|
|
key: 'zombie_walk_anim',
|
|
frames: this.anims.generateFrameNumbers('zombie_walk', { start: 0, end: 5 }),
|
|
frameRate: 8,
|
|
repeat: -1
|
|
});
|
|
} else {
|
|
console.warn('⚠️ Texture "zombie_walk" not found - skipping animation');
|
|
}
|
|
|
|
this.anims.create({
|
|
key: 'zombie_worker_walk',
|
|
frames: this.anims.generateFrameNumbers('zombie_worker', { start: 0, end: 5 }),
|
|
frameRate: 8,
|
|
repeat: -1
|
|
});
|
|
|
|
// KRVAVA ŽETEV: Protagonist 4-directional walking animations
|
|
// Row 1: DOWN (frames 0-3)
|
|
this.anims.create({
|
|
key: 'protagonist_walk_down',
|
|
frames: this.anims.generateFrameNumbers('player_protagonist', { start: 0, end: 3 }),
|
|
frameRate: 8,
|
|
repeat: -1
|
|
});
|
|
|
|
// Row 2: LEFT (frames 4-7)
|
|
this.anims.create({
|
|
key: 'protagonist_walk_left',
|
|
frames: this.anims.generateFrameNumbers('player_protagonist', { start: 4, end: 7 }),
|
|
frameRate: 8,
|
|
repeat: -1
|
|
});
|
|
|
|
// Row 3: RIGHT (frames 8-11)
|
|
this.anims.create({
|
|
key: 'protagonist_walk_right',
|
|
frames: this.anims.generateFrameNumbers('player_protagonist', { start: 8, end: 11 }),
|
|
frameRate: 8,
|
|
repeat: -1
|
|
});
|
|
|
|
// Row 4: UP (frames 12-15)
|
|
this.anims.create({
|
|
key: 'protagonist_walk_up',
|
|
frames: this.anims.generateFrameNumbers('player_protagonist', { start: 12, end: 15 }),
|
|
frameRate: 8,
|
|
repeat: -1
|
|
});
|
|
|
|
// IDLE: Use first frame of each direction
|
|
this.anims.create({
|
|
key: 'protagonist_idle_down',
|
|
frames: [{ key: 'player_protagonist', frame: 0 }],
|
|
frameRate: 1
|
|
});
|
|
|
|
this.anims.create({
|
|
key: 'protagonist_idle_left',
|
|
frames: [{ key: 'player_protagonist', frame: 4 }],
|
|
frameRate: 1
|
|
});
|
|
|
|
this.anims.create({
|
|
key: 'protagonist_idle_right',
|
|
frames: [{ key: 'player_protagonist', frame: 8 }],
|
|
frameRate: 1
|
|
});
|
|
|
|
this.anims.create({
|
|
key: 'protagonist_idle_up',
|
|
frames: [{ key: 'player_protagonist', frame: 12 }],
|
|
frameRate: 1
|
|
});
|
|
|
|
console.log('🎞️ Animations created!');
|
|
}
|
|
|
|
processAllTransparency() {
|
|
// Process ALL sprites to remove backgrounds
|
|
const spritesToProcess = [
|
|
'player_sprite',
|
|
'zombie_sprite',
|
|
'merchant_sprite',
|
|
'house_sprite',
|
|
'stone_sprite',
|
|
'tree_sprite',
|
|
'grass_sprite',
|
|
'leaf_sprite',
|
|
'wheat_sprite',
|
|
'stone_texture',
|
|
// New pixel art assets
|
|
'flowers',
|
|
'tree_green',
|
|
'tree_blue',
|
|
'tree_dead',
|
|
'rock_asset',
|
|
|
|
// FINAL TREES
|
|
'tree_green_final',
|
|
'tree_blue_final',
|
|
'tree_dead_final',
|
|
|
|
// STARDEW VALLEY FOREST TREES
|
|
'tree_purple',
|
|
'tree_apple',
|
|
'tree_pear',
|
|
'tree_cherry',
|
|
'tree_sapling',
|
|
|
|
// NEW transparent assets
|
|
'tree_blue_new',
|
|
'tree_green_new',
|
|
'rock_1',
|
|
'rock_2',
|
|
'rock_small',
|
|
'merchant_new',
|
|
'elite_zombie',
|
|
'tree_dead_new',
|
|
'flowers_new',
|
|
'hill_sprite',
|
|
'fence',
|
|
'fence_old',
|
|
'gravestone',
|
|
// City content
|
|
'chest',
|
|
'spawner',
|
|
'signpost_city',
|
|
'signpost_farm',
|
|
'signpost_both',
|
|
// Voxel stil
|
|
'tree_voxel_green',
|
|
'tree_voxel_blue',
|
|
'tree_voxel_dead',
|
|
'rock_voxel',
|
|
|
|
// NEW ISOMETRIC 2.5D ASSETS - Remove transparency
|
|
'barn_isometric',
|
|
'farmhouse_isometric',
|
|
'fence_isometric',
|
|
'bridge_isometric',
|
|
'blacksmith_workshop',
|
|
'ruins_building',
|
|
'soil_tilled',
|
|
'carrots_stages',
|
|
'flowers_pink_isometric',
|
|
// NOTE: player_dreadlocks and zombie_worker are SPRITESHEETS - don't process!
|
|
'grave_zombie',
|
|
|
|
// NEW FENCE PIECES
|
|
'fence_post',
|
|
'fence_horizontal',
|
|
'fence_vertical',
|
|
'fence_corner',
|
|
|
|
// ANIMALS & NPCs
|
|
'chicken',
|
|
'cow',
|
|
'cow_mutant',
|
|
'elf',
|
|
'troll',
|
|
'villager',
|
|
'npc_merchant',
|
|
'npc_zombie',
|
|
|
|
// STRUCTURES
|
|
'structure_house',
|
|
'wall_damaged',
|
|
'city_wall',
|
|
|
|
// MISC OBJECTS
|
|
'wheat_sprite',
|
|
'grass_sprite',
|
|
'leaf_sprite',
|
|
'stone_sprite'
|
|
|
|
// 🌳 TREES REMOVED - already have transparent PNG!
|
|
// (green removal would delete green leaves!)
|
|
];
|
|
|
|
spritesToProcess.forEach(spriteKey => {
|
|
this.processSpriteTransparency(spriteKey);
|
|
});
|
|
|
|
// ULTRA AGGRESSIVE: Fence Post only
|
|
if (this.textures.exists('fence_post')) {
|
|
this.ultraRemoveBackground('fence_post');
|
|
}
|
|
|
|
// ULTRA REMOVED - new tree sprites already have transparency!
|
|
// if (this.textures.exists('cesnjevo_drevo')) {
|
|
// this.ultraRemoveBackground('cesnjevo_drevo');
|
|
// }
|
|
|
|
console.log('✅ All sprites transparency processed!');
|
|
}
|
|
|
|
processSpriteTransparency(spriteKey) {
|
|
if (!this.textures.exists(spriteKey)) return;
|
|
|
|
const texture = this.textures.get(spriteKey);
|
|
const source = texture.getSourceImage();
|
|
|
|
// Create canvas to process image
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = source.width;
|
|
canvas.height = source.height;
|
|
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
|
|
// Draw original image
|
|
ctx.drawImage(source, 0, 0);
|
|
|
|
// Get image data
|
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
const data = imageData.data;
|
|
|
|
// Remove backgrounds - ULTRA AGGRESSIVE MODE!
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
const r = data[i];
|
|
const g = data[i + 1];
|
|
const b = data[i + 2];
|
|
const a = data[i + 3];
|
|
|
|
// Skip if already transparent
|
|
if (a === 0) continue;
|
|
|
|
const brightness = (r + g + b) / 3;
|
|
|
|
// 🟢 GREEN REMOVAL - Only BRIGHT greens!
|
|
// Pink (255,105,180) has g=105 - MUST KEEP!
|
|
// Bright green (0,255,0) has g=255 - REMOVE!
|
|
const isGreen = (
|
|
g > 180 && // Only very bright green
|
|
g > r * 1.3 && // Green dominates red
|
|
g > b * 1.3 // Green dominates blue
|
|
);
|
|
if (isGreen) {
|
|
data[i + 3] = 0;
|
|
continue;
|
|
}
|
|
|
|
// 1. Remove ALL grayscale colors (ANY shade of gray)
|
|
const isGrayscale = Math.abs(r - g) < 20 && Math.abs(g - b) < 20 && Math.abs(r - b) < 20;
|
|
if (isGrayscale && brightness > 80) {
|
|
data[i + 3] = 0; // Make transparent
|
|
continue;
|
|
}
|
|
|
|
// 2. Remove ALL light/bright backgrounds (AI-generated sprites)
|
|
if (brightness > 150) {
|
|
data[i + 3] = 0; // Make transparent
|
|
continue;
|
|
}
|
|
|
|
// 3. Remove PURE WHITE
|
|
if (r > 240 && g > 240 && b > 240) {
|
|
data[i + 3] = 0;
|
|
continue;
|
|
}
|
|
|
|
// 4. Remove OFF-WHITE / CREAM / BEIGE
|
|
if (brightness > 200 && Math.abs(r - g) < 30 && Math.abs(g - b) < 30) {
|
|
data[i + 3] = 0;
|
|
continue;
|
|
}
|
|
|
|
// 5. Remove PASTEL colors (low saturation, high brightness)
|
|
const maxRGB = Math.max(r, g, b);
|
|
const minRGB = Math.min(r, g, b);
|
|
const saturation = maxRGB === 0 ? 0 : (maxRGB - minRGB) / maxRGB;
|
|
|
|
if (saturation < 0.3 && brightness > 120) {
|
|
data[i + 3] = 0; // Remove low-saturation backgrounds
|
|
continue;
|
|
}
|
|
|
|
// Special: Remove brown/tan backgrounds (merchant sprite)
|
|
if (spriteKey === 'merchant_sprite') {
|
|
// Brown detection: R > G > B, warm tones
|
|
const isBrown = r > 100 && r > g && g > b && (r - b) > 40;
|
|
if (isBrown) {
|
|
data[i + 3] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Put processed data back
|
|
ctx.putImageData(imageData, 0, 0);
|
|
|
|
// Create new texture from processed canvas
|
|
this.textures.remove(spriteKey);
|
|
this.textures.addCanvas(spriteKey, canvas);
|
|
}
|
|
|
|
createLoadingBar() {
|
|
const width = this.cameras.main.width;
|
|
const height = this.cameras.main.height;
|
|
|
|
// Warm background (Stardew Valley style)
|
|
const bg = this.add.graphics();
|
|
bg.fillStyle(0x2d1b00, 1);
|
|
bg.fillRect(0, 0, width, height);
|
|
|
|
// Simple "LOADING" text
|
|
const title = this.add.text(width / 2, height / 2 - 80, 'LOADING', {
|
|
fontFamily: 'Georgia, serif',
|
|
fontSize: '36px',
|
|
fontStyle: 'bold',
|
|
fill: '#f4e4c1',
|
|
stroke: '#2d1b00',
|
|
strokeThickness: 4
|
|
}).setOrigin(0.5);
|
|
|
|
// Progress Bar container (wooden style)
|
|
const barWidth = 400;
|
|
const barHeight = 30;
|
|
const barX = width / 2 - barWidth / 2;
|
|
const barY = height / 2;
|
|
|
|
const progressBox = this.add.graphics();
|
|
progressBox.fillStyle(0x4a3520, 0.9);
|
|
progressBox.fillRoundedRect(barX, barY, barWidth, barHeight, 5);
|
|
progressBox.lineStyle(3, 0xd4a574, 1);
|
|
progressBox.strokeRoundedRect(barX, barY, barWidth, barHeight, 5);
|
|
|
|
const progressBar = this.add.graphics();
|
|
|
|
// Zombie sprite walking on the bar
|
|
const zombie = this.add.text(barX, barY + barHeight / 2, '🧟', {
|
|
fontSize: '32px'
|
|
}).setOrigin(0.5);
|
|
|
|
// Percentage text
|
|
const percentText = this.add.text(width / 2, barY + barHeight / 2, '0%', {
|
|
font: '16px Georgia',
|
|
fill: '#f4e4c1',
|
|
fontStyle: 'bold'
|
|
}).setOrigin(0.5);
|
|
|
|
this.load.on('progress', (value) => {
|
|
percentText.setText(parseInt(value * 100) + '%');
|
|
progressBar.clear();
|
|
progressBar.fillStyle(0x6b4423, 1);
|
|
|
|
// Smooth fill
|
|
const w = (barWidth - 10) * value;
|
|
if (w > 0) {
|
|
progressBar.fillRoundedRect(barX + 5, barY + 5, w, barHeight - 10, 2);
|
|
}
|
|
|
|
// Move zombie along the bar (moderate speed)
|
|
const zombieX = barX + (barWidth * value);
|
|
zombie.setX(zombieX);
|
|
|
|
// Gentle bounce animation
|
|
const bounce = Math.sin(value * 20) * 3;
|
|
zombie.setY(barY + barHeight / 2 + bounce);
|
|
});
|
|
|
|
this.load.on('complete', () => {
|
|
// Fade out animation
|
|
this.tweens.add({
|
|
targets: [progressBar, progressBox, percentText, title, zombie, bg],
|
|
alpha: 0,
|
|
duration: 800,
|
|
onComplete: () => {
|
|
progressBar.destroy();
|
|
progressBox.destroy();
|
|
percentText.destroy();
|
|
title.destroy();
|
|
zombie.destroy();
|
|
bg.destroy(); // Ensure bg is destroyed here
|
|
console.log('✅ PreloadScene: Assets loaded!');
|
|
|
|
// 🌍 INITIALIZE LOCALIZATION
|
|
if (!window.i18n) {
|
|
try {
|
|
window.i18n = new LocalizationSystem();
|
|
const locData = this.cache.json.get('localization');
|
|
if (locData) {
|
|
window.i18n.setExternalData(locData);
|
|
console.log('🌍 Localization initialized with external data');
|
|
} else {
|
|
console.warn('⚠️ Localization JSON not found in cache');
|
|
}
|
|
} catch (e) {
|
|
console.error('❌ Localization init failed:', e);
|
|
}
|
|
}
|
|
|
|
window.gameState.currentScene = 'PreloadScene';
|
|
|
|
// ✅ Starting INTRO SEQUENCE (NEW! Jan 10, 2026)
|
|
this.time.delayedCall(500, () => {
|
|
console.log('🎬 Starting IntroScene (Cinematic Intro)...');
|
|
this.scene.start('IntroScene'); // ← NEW INTRO SEQUENCE
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
create() {
|
|
// The actual scene start logic has been moved to the 'load.on(complete)' callback
|
|
// to ensure all assets are fully loaded and the loading bar has faded out.
|
|
// This 'create' method now primarily serves as a placeholder or for any
|
|
// immediate setup that doesn't depend on asset loading completion.
|
|
}
|
|
|
|
processPlayerSpritesheet() {
|
|
const spriteKey = 'player_protagonist';
|
|
|
|
if (!this.textures.exists(spriteKey)) {
|
|
console.warn('⚠️ Player protagonist texture not found!');
|
|
return;
|
|
}
|
|
|
|
console.log('🎨 Processing player spritesheet transparency...');
|
|
|
|
const texture = this.textures.get(spriteKey);
|
|
const source = texture.getSourceImage();
|
|
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = source.width;
|
|
canvas.height = source.height;
|
|
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
|
|
ctx.drawImage(source, 0, 0);
|
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
const data = imageData.data;
|
|
|
|
// ULTRA AGGRESSIVE: Remove ALL checkerboard patterns and gray backgrounds
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
const r = data[i];
|
|
const g = data[i + 1];
|
|
const b = data[i + 2];
|
|
const a = data[i + 3];
|
|
|
|
// Skip if already transparent
|
|
if (a === 0) continue;
|
|
|
|
// 1. Remove PERFECT GRAY (checkerboard pattern: RGB 204,204,204 and 153,153,153)
|
|
if (r === g && g === b) {
|
|
if (r === 204 || r === 153 || r === 192 || r === 128 || (r >= 180 && r <= 210)) {
|
|
data[i + 3] = 0;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// 2. Remove ANY grayscale (R≈G≈B within 15 units)
|
|
const isGrayscale = Math.abs(r - g) < 15 && Math.abs(g - b) < 15 && Math.abs(r - b) < 15;
|
|
const brightness = (r + g + b) / 3;
|
|
|
|
if (isGrayscale && brightness > 100) {
|
|
data[i + 3] = 0;
|
|
continue;
|
|
}
|
|
|
|
// 3. Remove LIGHT backgrounds (brightness > 150)
|
|
if (brightness > 150) {
|
|
data[i + 3] = 0;
|
|
continue;
|
|
}
|
|
|
|
// 4. Keep ONLY colored pixels (character skin, clothing, hair)
|
|
// Character has: blue hoodie, brown pants, brown skin
|
|
const maxChannel = Math.max(r, g, b);
|
|
const minChannel = Math.min(r, g, b);
|
|
const saturation = maxChannel === 0 ? 0 : (maxChannel - minChannel) / maxChannel;
|
|
|
|
// If low saturation AND bright = background
|
|
if (saturation < 0.15 && brightness > 80) {
|
|
data[i + 3] = 0;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
ctx.putImageData(imageData, 0, 0);
|
|
|
|
// Replace texture
|
|
this.textures.remove(spriteKey);
|
|
this.textures.addCanvas(spriteKey, canvas);
|
|
|
|
console.log('✅ Player spritesheet transparency processed!');
|
|
}
|
|
|
|
ultraRemoveBackground(spriteKey) {
|
|
if (!this.textures.exists(spriteKey)) return;
|
|
|
|
const texture = this.textures.get(spriteKey);
|
|
const source = texture.getSourceImage();
|
|
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = source.width;
|
|
canvas.height = source.height;
|
|
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
|
|
ctx.drawImage(source, 0, 0);
|
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
const data = imageData.data;
|
|
|
|
// Different logic for different sprites
|
|
if (spriteKey === 'cesnjevo_drevo') {
|
|
// CHERRY BLOSSOM: Keep only PINK (flowers) and BROWN (trunk)
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
const r = data[i];
|
|
const g = data[i + 1];
|
|
const b = data[i + 2];
|
|
|
|
// Pink detection: R > G, R > B, pinkish tones
|
|
const isPink = r > 100 && r > g && r > b && (r - g) > 30;
|
|
|
|
// Brown detection: R > G > B, warm earthy tones
|
|
const isBrown = r > g && g > b && r > 80 && r < 200;
|
|
|
|
// Keep pink OR brown, remove everything else
|
|
if (!isPink && !isBrown) {
|
|
data[i + 3] = 0; // Make transparent
|
|
}
|
|
}
|
|
} else {
|
|
// FALLBACK: Fence post logic (keep only brown)
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
const r = data[i];
|
|
const g = data[i + 1];
|
|
const b = data[i + 2];
|
|
|
|
// Keep only brown/wood colors: R > G > B and R is dominant
|
|
const isBrown = r > g && g > b && r > 80 && r < 200;
|
|
|
|
if (!isBrown) {
|
|
data[i + 3] = 0; // Make transparent
|
|
}
|
|
}
|
|
}
|
|
|
|
ctx.putImageData(imageData, 0, 0);
|
|
|
|
// Remove old texture and create new one from canvas
|
|
this.textures.remove(spriteKey);
|
|
this.textures.addCanvas(spriteKey, canvas);
|
|
|
|
console.log(`🔥 ULTRA removed background from ${spriteKey}`);
|
|
}
|
|
}
|