Files
game_development_workshop/js/avatar.js
T

205 lines
10 KiB
JavaScript

/**
* PPE SAFETY CHALLENGE - REALISTIC 3D WORKER AVATAR SYSTEM
* Uses the exact 3D realistic character image provided by the user (images/worker_base.png),
* with precision anatomical PPE overlays for both Grinding and Chemical scenarios.
*/
class WorkerAvatar {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.equippedItems = new Set();
this.render();
}
setEquipped(itemIds) {
this.equippedItems = new Set(itemIds);
this.updateLayers();
}
equip(itemId) {
this.equippedItems.add(itemId);
this.updateLayers(itemId);
}
unequip(itemId) {
this.equippedItems.delete(itemId);
this.updateLayers();
}
has(itemId) {
return this.equippedItems.has(itemId);
}
getAvatarCenterCoordinates() {
if (!this.container) return { x: 170, y: 220, eyeX: 170, eyeY: 110, handX: 230, handY: 260 };
const rect = this.container.getBoundingClientRect();
const parentRect = this.container.parentElement.getBoundingClientRect();
const relX = rect.left - parentRect.left + (rect.width * 0.5);
const relY = rect.top - parentRect.top + (rect.height * 0.5);
return {
x: relX,
y: relY,
eyeX: relX,
eyeY: relY - 135,
handX: relX + 65,
handY: relY + 25
};
}
render() {
if (!this.container) return;
this.container.innerHTML = `
<svg viewBox="0 0 682 1024" class="worker-svg" id="worker-avatar-svg" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="gearShadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="4" stdDeviation="5" flood-color="#000000" flood-opacity="0.6"/>
</filter>
<radialGradient id="softFloorShadowGrad" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#000000" stop-opacity="0.65"/>
<stop offset="35%" stop-color="#000000" stop-opacity="0.4"/>
<stop offset="65%" stop-color="#000000" stop-opacity="0.18"/>
<stop offset="85%" stop-color="#000000" stop-opacity="0.05"/>
<stop offset="100%" stop-color="#000000" stop-opacity="0"/>
</radialGradient>
<radialGradient id="footContactShadowGrad" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#000000" stop-opacity="0.75"/>
<stop offset="40%" stop-color="#000000" stop-opacity="0.45"/>
<stop offset="75%" stop-color="#000000" stop-opacity="0.12"/>
<stop offset="100%" stop-color="#000000" stop-opacity="0"/>
</radialGradient>
<filter id="softFloorBlur" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="5"/>
</filter>
</defs>
<!-- Realistic Soft Floor Contact & Ambient Occlusion Shadow -->
<g id="layer-floor-shadow" filter="url(#softFloorBlur)">
<!-- Broad Ambient Floor Shadow -->
<ellipse cx="347" cy="1008" rx="205" ry="22" fill="url(#softFloorShadowGrad)"/>
<!-- Left Foot Direct Contact Shadow -->
<ellipse cx="238" cy="1008" rx="72" ry="14" fill="url(#footContactShadowGrad)"/>
<!-- Right Foot Direct Contact Shadow -->
<ellipse cx="452" cy="1008" rx="72" ry="14" fill="url(#footContactShadowGrad)"/>
</g>
<!-- ====================================================
LAYER 1: REALISTIC 3D WORKER BASE CHARACTER IMAGE
==================================================== -->
<g id="layer-base-character">
<image href="images/worker_base.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<!-- ====================================================
LAYER 2: MODULAR PPE EQUIPMENT (TOGGLED DYNAMICALLY)
==================================================== -->
<!-- 1. FEET: Safety Shoes (Steel-Toe Boots covering bare feet) -->
<g id="layer-gear-steel_toe_boots_grind" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_safety_boots.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<g id="layer-gear-chem_safety_shoes" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_safety_boots.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<!-- 2. HANDS: Leather Grinding / Heavy Work Gloves & Chemical Gloves -->
<g id="layer-gear-leather_grinding_gloves" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_leather_gloves.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<g id="layer-gear-work_gloves_grind" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_work_gloves.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<g id="layer-gear-standard_work_gloves" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_work_gloves.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<g id="layer-gear-chem_resistant_gloves" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_chem_gloves.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<!-- 3. BODY: Lab Coat, Aprons, Reflective Vest (Overlaps glove cuffs) -->
<g id="layer-gear-cotton_coat_chem" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_cotton_coat.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<g id="layer-gear-chem_resistant_apron" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_chem_apron.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<g id="layer-gear-fire_resistant_apron_grind" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_fire_resistant_apron.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<g id="layer-gear-fire_resistant_apron" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_fire_resistant_apron.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<g id="layer-gear-reflective_vest_grind" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_reflective_vest.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<!-- 4. MASK: Dust Mask (Grinding Distractor) -->
<g id="layer-gear-dust_mask_grind" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_dust_mask.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<!-- 5. EYES: Safety Goggles (Clear Grinding Protective Glasses) -->
<g id="layer-gear-safety_goggles_grind" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_safety_glasses.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<g id="layer-gear-chem_splash_goggles" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_chem_splash_goggles.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<!-- 6. HEARING: Acoustic Earmuffs (Grinding & Chemical) -->
<g id="layer-gear-hearing_protection_grind" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_earmuffs.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<g id="layer-gear-hearing_protection_chem" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_earmuffs.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<!-- 7. HEAD: Safety Helmet (Yellow Hard Hat) -->
<g id="layer-gear-safety_helmet_grind" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_safety_helmet.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<g id="layer-gear-safety_helmet_chem" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_safety_helmet.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<!-- 8. FACE: Face Shield (Grinding & Chemical Visor) -->
<g id="layer-gear-face_shield_grind" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_face_shield.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
<g id="layer-gear-face_shield_chem" style="display: none;" filter="url(#gearShadow)">
<image href="images/gear/gear_3d_face_shield.png" x="0" y="0" width="682" height="1024" preserveAspectRatio="xMidYMid meet" />
</g>
</svg>
`;
this.updateLayers();
}
updateLayers(newlyEquippedId = null) {
if (!this.container) return;
const allGearIds = [
'safety_helmet_chem', 'safety_helmet_grind',
'chem_splash_goggles', 'safety_goggles_grind', 'face_shield_chem', 'face_shield_grind', 'tinted_sunglasses', 'dust_mask_grind',
'hearing_protection_chem', 'hearing_protection_grind',
'chem_resistant_gloves', 'standard_work_gloves', 'work_gloves_grind', 'leather_grinding_gloves', 'disposable_latex_gloves',
'chem_resistant_apron', 'cotton_coat_chem', 'flame_resistant_jacket', 'reflective_vest_grind', 'fire_resistant_apron_grind', 'fire_resistant_apron',
'chem_safety_shoes', 'steel_toe_boots_grind', 'running_sneakers'
];
allGearIds.forEach(gearId => {
const el = document.getElementById(`layer-gear-${gearId}`);
if (el) {
const isEquipped = this.equippedItems.has(gearId);
el.style.display = isEquipped ? 'block' : 'none';
if (isEquipped && gearId === newlyEquippedId) {
el.classList.remove('gear-pop-glow');
void el.offsetWidth;
el.classList.add('gear-pop-glow');
}
}
});
}
}