Category: world

  • World – 6th THUNDER MODE – Chaos from the Sky – Building a Modular Lightning System

    https://world.officialstupid.me

    Our social 3D world was peaceful—too peaceful. To add some excitement, we decided to implement Thunder Mode, a survival challenge where players dodge procedurally generated lightning strikes. However, adding a complex “game mode” to a social engine often leads to “spaghetti code” and performance death.

    Here is how we built a high-performance, modular weather system that creates zero load on the server and stays silky smooth on mobile.


    1. The Strategy: Complete Modularity

    The biggest mistake in web game dev is cramming every new feature into the main animate() loop. Instead, we built thunder.js as a Self-Contained Module.

    When you load the script, it automatically injects its own CSS, creates its own HTML overlays (Health bars, toggle buttons), and manages its own internal state. If we remove the script, the game still runs perfectly.

    The “Zero-Server-Load” Logic:
    We don’t sync lightning positions through the server. Instead, each client calculates its own “personal” lightning. This means the server only handles player positions, while the heavy math happens locally on the user’s device.


    2. Visual Optimization: The “Double-Layer” Bolt

    Initially, we used full-screen blue flashes and camera shakes to simulate thunder. Result: FPS dropped to 10 on mobile. CSS background changes trigger “layout reflows,” which are expensive.

    We pivoted to a purely 3D visual approach using a Two-Layer Bolt system:

    1. The Core: A thin, bright white TubeGeometry.
    2. The Glow: A thicker blue tube using AdditiveBlending.

    code JavaScript

    // High-performance bolt geometry logic
    createBolt(x, z) {
        const pts = generateZigZagPoints(x, z);
        const curve = new THREE.CatmullRomCurve3(pts);
        
        // Core (White) - 4 radial segments (square tube)
        const core = new THREE.Mesh(new THREE.TubeGeometry(curve, 16, 0.04, 4), coreMat);
        
        // Glow (Blue + Additive Blending) - 6 radial segments (hexagonal tube)
        const glow = new THREE.Mesh(new THREE.TubeGeometry(curve, 16, 0.2, 6), glowMat);
        
        boltGroup.add(glow, core);
        return boltGroup;
    }

    3. Progressive Difficulty

    A static game is a boring game. We implemented a difficulty scalar that increases every second you stay alive. This variable controls four things simultaneously:

    • Spawn Rate: From one bolt every 2 seconds down to four bolts per second.
    • Warning Time: The red “danger zone” circle stays on the ground for less time.
    • Max Bolts: Increases the cap on how many bolts can be on screen at once (up to 15).
    • Spawn Radius: As you survive, lightning fills a wider area around you.

    4. Integrating GLB Death Animations

    Manual character rotation looked “fake.” We wanted to use the professional animations built into our GLB models.

    The Challenge: If you simply play the death animation in a loop, the character keeps dying and standing up repeatedly. To fix this, we implemented a State-Change Check.

    code JavaScript

    // Inside the main animation loop
    const isHit = thunder.isDead || thunder.isKnockedDown;
    
    if (isHit) {
        // Only trigger the animation ONCE when the state changes
        if (lastAnim !== 'death') {
            deathAction.reset().setLoop(THREE.LoopOnce).play();
            deathAction.clampWhenFinished = true; // Stay on the last frame
        }
        anim = 'death';
    } else {
        anim = 'idle';
    }
    lastAnim = anim;

    5. Framing the Chaos: UI Positioning and Depth

    To improve the user experience, we made two major aesthetic adjustments:

    • The 40% Rule: We adjusted the camera’s look at target to Y=3.5. This tilts the view slightly upward, pushing the character selection slider to the bottom 40% of the screen. This keeps the characters visible but leaves the top of the screen clear for the environment.
    • Atmospheric Clarity: We thinned the fog by changing the far distance from 75 to 150. This makes the world feel larger and gives players more time to see the environment while they are dodging strikes.
    • Expanded Roster: We expanded the character pool, supporting a full lineup of models from 01.glb to 09.glb, giving players more ways to express themselves before the storm hits.

    6. The “Wasted” Loop: Delayed Restart

    Survival games need a moment of impact. When a player finally loses all health, we don’t show the menu immediately. We added a 2-second delay where the camera lingers on the fallen character before the “START AGAIN” button fades in. This makes death feel meaningful and gives the player a moment to breathe before jumping back in.


    The Result

    By keeping the code modular, we managed to:

    1. Maintain 60 FPS: By avoiding CSS-based screen effects and using low-poly 3-sided tubes for bolts.
    2. Improve Visuals: The additive blue glow looks far more “electrifying” than a simple white line.
    3. Keep the Engine Clean: The main index.html only needs one or two lines to “talk” to the Thunder System.

    Next time you see a red ring on the floor, you have exactly 0.8 seconds to move—Good Luck!

    https://aistudio.google.com

    MORE NEW GLBhttps://pixabay.com/3d-models/search/glb/

  • world – 5th – The “Invisible” Mobile Controller – Gestures for 3D Worlds

    Moving a 3D character on a smartphone usually means cluttering the screen with ugly joysticks and transparent buttons. For our multiplayer project, we wanted something better. We built an Invisible Gesture Controller that turns the entire screen into a touch-sensitive pad.

    The Strategy: Mapping Keys to Gestures

    Our game engine already listens for specific keys like W (Walk), Shift (Run), and Space (Jump). Instead of rewriting the engine, we built a “Touch Wrapper” that translates finger movements into these virtual key presses.

    1. Browser Normalization (CSS)

    To make a web game feel like a native app, you have to kill the browser’s default behaviors. Without this, the phone will try to zoom or “select text” while you are trying to play.

    code CSS

    /* Add this to stop mobile browser interference */
    body { 
        touch-action: none;          /* Disable pinch-to-zoom and scroll */
        user-select: none;           /* Disable text highlighting */
        -webkit-touch-callout: none; /* Disable long-press menus */
        -webkit-tap-highlight-color: transparent; /* Remove blue tap boxes */
    }

    2. The Touch Logic (JavaScript)

    This is the heart of the mobile support. It handles rotation, toggle-walking, sprinting, and vertical swipes for actions.

    code JavaScript

    let touchStartX = 0, touchStartY = 0, lastTapTime = 0;
    
    function setupMobileControls() {
        // CAPTURE START POSITION
        window.addEventListener('touchstart', (e) => {
            touchStartX = e.touches[0].clientX;
            touchStartY = e.touches[0].clientY;
        }, { passive: false });
    
        // ROTATION: Horizontal slide
        window.addEventListener('touchmove', (e) => {
            if (!isStarted || !localPlayer) return;
            let dx = e.touches[0].clientX - touchStartX;
            
            if (Math.abs(dx) > 5) {
                localPlayer.rotation.y -= dx * 0.01; // Rotate character
                touchStartX = e.touches[0].clientX;  // Reset baseline
            }
            e.preventDefault(); // Stop page "bounce"
        }, { passive: false });
    
        // TAPS & SWIPES: Logic for Walk, Run, Jump, and Camera
        window.addEventListener('touchend', (e) => {
            let dx = e.changedTouches[0].clientX - touchStartX;
            let dy = e.changedTouches[0].clientY - touchStartY;
            let now = Date.now();
            let isDoubleTap = (now - lastTapTime) < 300;
    
            if (isStarted) {
                // SWIPE UP -> JUMP (Space)
                if (dy < -50 && Math.abs(dx) < 50) {
                    keys[' '] = true;
                    setTimeout(() => keys[' '] = false, 100);
                } 
                // SWIPE DOWN -> CAMERA VIEW (V)
                else if (dy > 50 && Math.abs(dx) < 50) {
                    toggleCameraView(); 
                }
                // TAP LOGIC
                else if (Math.abs(dx) < 10 && Math.abs(dy) < 10) {
                    if (isDoubleTap) {
                        // Double Tap: Toggle between Running and Walking
                        if (keys['shift']) {
                            keys['shift'] = false; keys['w'] = true;
                        } else {
                            keys['w'] = true; keys['shift'] = true;
                        }
                    } else {
                        // Single Tap: Start/Stop Walking
                        if (keys['w'] || keys['shift']) {
                            keys['w'] = false; keys['shift'] = false; // Stop
                        } else {
                            keys['w'] = true; // Start Walk
                        }
                    }
                    lastTapTime = now;
                }
            }
        }, { passive: false });
    }

    How the Gestures Work:

    • Rotation: Sliding your finger left or right updates the localPlayer.rotation.y. It’s sensitive and smooth, allowing for quick 360-degree turns.
    • The “Tap to Move” Toggle: On a keyboard, you hold ‘W’. On mobile, holding your finger down is tiring. We changed it to a Toggle: Tap once to start walking, tap again to stop.
    • Double-Tap to Sprint: Just like Minecraft or other mobile titles, a quick double-tap switches the state from Walking to Running (Shift).
    • Swipe Down for Selfie: We mapped the Camera Flip (V) to a downward swipe. It’s an intuitive gesture for switching perspectives.
    • Swipe Up to Jump: A vertical flick triggers the jump physics instantly.

    The Result

    By using gestures instead of buttons, we kept the screen 100% clean. This maximizes the immersion of the 3D world and makes the game feel like a premium mobile app rather than a website.

    Next time you log in from your phone, try the swipe-down to see your character’s face!

    https://chat.deepseek.com/

  • world – 4th round -Persistence in a 3D World – The “12-Hour Ghost” System

    In most multiplayer games, when a player disconnects, their character simply vanishes. This makes the world feel empty and temporary. We decided to change that by building a Persistent Ghost System.

    Now, when you leave our world, your avatar stays exactly where you left it for 12 hours, serving as a digital footprint of your visit.

    The Problem: The “Empty Room” Syndrome

    3D social spaces feel lonely if you aren’t there at the exact same time as someone else. We wanted a way for players to “leave their mark,” allowing the world to look populated even during off-peak hours.

    The Solution: Ghost Conversion

    We implemented a server-to-client bridge that handles “ghosting” in three tiny but powerful steps.

    1. Server-Side: The 12-Hour Timer

    Instead of deleting the player data on a disconnect event, the server rebrands the player. We change their ID to a ghost_ prefix and start a long-running timer.

    code JavaScript

    // On Disconnect: Convert to Ghost
    const ghostId = "ghost_" + socket.id;
    players[ghostId] = players[socket.id]; // Keep the data
    delete players[socket.id]; // Remove the active socket
    
    // Set the 12-hour expiration
    setTimeout(() => {
        delete players[ghostId];
        io.emit('player_left', ghostId);
    }, 12 * 60 * 60 * 1000);

    2. Client-Side: Visual “Offline” Cues

    To make sure active players can distinguish between live users and ghosts, we added visual shaders. When the client sees an ID starting with ghost_, it automatically:

    • Sets the model to 50% opacity.
    • Appends (OFFLINE) to the name tag.
    • Forces the model into an Idle state.

    code JavaScript

    if (id.startsWith('ghost_')) {
        child.material.transparent = true;
        child.material.opacity = 0.5; // Visual hint that the player is gone
    }

    3. Physical Presence (No Ghosting)

    Even though these characters are “offline,” they aren’t just holograms. We kept the Collision Detection active against them. You cannot walk through a ghost. This creates a sense of solid, physical history in the world—if a group of friends stood in a circle before logging off, that circle remains as a physical barrier for 12 hours.

    Why it works

    • Anti-Cloning: If a player logs back in, the server automatically finds and “kills” their old ghost before spawning their new live character. This prevents the world from becoming cluttered with clones of the same person.
    • Memory Efficiency: By using a simple Map and setTimeout, the server handles the persistence without needing a heavy database.
    • Social Proof: New players entering the world see a crowd of characters, making the project feel popular and alive.

    Conclusion

    Persistence doesn’t always require a massive database or a 24/7 server farm. With just a few lines of logic and some clever ID prefixing, we turned a temporary room into a persistent world where your presence actually matters.

    Next time you log in, say hi to the ghosts of the players who came before you!

    https://chat.deepseek.com

  • world – 3rd drama – GLB

    Building a Smooth Multiplayer 3D Character Selection World

    In our latest development sprint, we transformed a basic 3D multiplayer space into a polished, interactive character hub. We focused on three main pillars: User Experience, Animation Flexibility, and Physical Presence.

    Here is a breakdown of what we’ve built.

    1. The “Carousel” Selection System

    Instead of a traditional 2D menu, we moved character selection into the 3D world.

    • Mouse-Scroll Interaction: Users can cycle through characters by simply scrolling their mouse wheel.
    • The Centering Effect: We used linear interpolation (Lerp) to smoothly slide the selected character to the center of the screen while rotating it, giving the player a 360-degree preview of their avatar.

    2. Dynamic Animation Mapping (Fuzzy Search)

    Different GLB models often come with wildly different animation naming conventions. A Robot might have RobotArmature|Walk, while a human might just have Walk.

    • Keyword Matching: We implemented a “Fuzzy Search” logic that looks for keywords like Idle, Walk, Run, or Jump.
    • The Skeleton Fallback: Some models, like our Skeleton, are missing specific animations (like a Walk). We built a fallback system that automatically detects a missing Walk and replaces it with the Run animation, ensuring the character never “glides” in a T-pose.

    3. Social Interaction: Proximity Waving

    To make the world feel alive, we added a social layer.

    • Proximity Detection: When a player stands still near one or more other players, the system triggers a “Wave” or “Thumbs Up” animation automatically.
    • Social Immersion: This small detail transforms the space from a lonely void into a social gathering hub.

    4. No More “Ghosting”: Collision Detection

    In early versions, players could walk through each other like ghosts. We solved this by implementing a Circle-Circle Collision system:

    • Physical Boundaries: Every player now has a collision radius.
    • The “Push-Back” Logic: If two players get too close, the math calculates the overlap and gently pushes the local player back, giving everyone a solid physical presence in the world.

    5. Enhanced Camera & Physics

    We kept the core gameplay tight with:

    • Jump Physics: Gravity-based jumping with force and landing detection.
    • View Toggling: Players can press ‘V’ or Right-Click to flip the camera between a standard third-person follow and a front-facing “Selfie” view.

    Technical Highlights:

    • Engine: Three.js (WebGL)
    • Networking: Socket.io for real-time position and animation syncing.
    • Assets: Optimized CC0 GLTF models.

    What’s Next?
    We are looking into adding customizable textures and a chat system to further enhance the multiplayer social experience!


    Looking for the logic?

    The secret to the animation flexibility was this helper function we developed:

    code JavaScript

    function findAction(mixer, clips, keywords) {
        const clip = clips.find(c => keywords.some(k => c.name.toLowerCase().includes(k.toLowerCase())));
        return clip ? mixer.clipAction(clip) : null;
    }

    This allows us to drop in almost any GLB model, and the game “figures out” which animation is which!

    https://chat.deepseek.com , https://aistudio.google.com , https://claude.ai (free)

  • world – round 2 – The Animation Evolution: Breathing Life into Our 3D World


    Introduction: From Static to Living

    In our previous deep dive, we explored the architectural foundation of our 3D multiplayer world—the GLB container format, the public/player/ folder structure, and the dynamic loading system that allows us to scale from 3 to 100 characters seamlessly. We built a robust backend, but a game world is more than just code; it’s an experience.

    Round 2 was about one thing: character personality.

    Players don’t just want to control a 3D model; they want to feel connected to their avatar. They want to see it breathe, react, and express itself—even when standing still. This blog post chronicles our journey through advanced animation sequencing, UX improvements, and the technical decisions that transformed a functional game into an emotionally engaging experience.


    Part 1: The Idle Revolution – Why Standing Still Matters

    The Problem: The Frozen Avatar Syndrome

    In our initial implementation, when a player stopped moving, their character would simply freeze in place—a static, lifeless pose that screamed “unfinished.” This is what we call the Frozen Avatar Syndrome, and it’s a silent killer of immersion.

    Players spend a significant portion of their time not moving: reading chat, inspecting their surroundings, or simply taking a moment to breathe. In those moments, their avatar should still feel alive.

    The Solution: Idle Animation Sequencing

    We took inspiration from the character selection screen, where hovering over a model triggered a delightful No → Wave animation loop. Why not bring that same magic into the game world?

    The Concept: When a player stops moving, their character automatically transitions into a personality-driven animation loop that repeats until they take action.

    javascript

    // The idle sequence logic
    function updateLocalIdleSequence(delta) {
        const isMoving = keys['w'] || keys['s'] || keys['a'] || keys['d'];
        
        if (isMoving || isJumping || keys[' ']) {
            // Player is active - stop idle sequence
            if (isIdleSequenceRunning) {
                if (noAction) noAction.stop();
                if (waveIdleAction) waveIdleAction.stop();
                if (idleAction) idleAction.stop();
                isIdleSequenceRunning = false;
                idleSequenceState = 0;
            }
            return;
        }
    
        // Player is idle - start the No → Wave loop
        if (!isIdleSequenceRunning) {
            isIdleSequenceRunning = true;
            idleSequenceState = 1;
            if (idleAction) idleAction.stop();
            if (noAction) {
                noAction.reset();
                noAction.play();
            }
            return;
        }
    }

    The UX Impact: This small addition transformed the feel of the game. Suddenly, characters had personality. The “No” gesture felt playful and confident; the “Wave” felt welcoming and interactive. Players reported feeling more connected to their avatars—not just as tools, but as extensions of themselves.


    Part 2: The Hover Experience – Turning Selection into a Showcase

    The Problem: Static Character Selection

    Our character selection screen was functional but boring. Three models stood in a row, frozen in idle poses, waiting to be clicked. Users had no way to preview a character’s personality or animation style before committing.

    The Solution: Interactive Hover Sequences

    We implemented a hover-triggered animation sequence that showcases each character’s personality:

    1. Hover → Character plays the “No” gesture (confident, playful)
    2. After No finishes → Character transitions to “Wave” (friendly, welcoming)
    3. After Wave finishes → Loop back to “No” (continuous performance)

    The Technical Execution:

    javascript

    renderer.domElement.addEventListener('mousemove', (event) => {
        // Raycast to detect which model is being hovered
        const intersects = raycaster.intersectObjects(selectionModels, true);
        let hitModel = null;
        if (intersects.length > 0) {
            let obj = intersects[0].object;
            while (obj.parent && !obj.userData.file) obj = obj.parent;
            if (obj.userData.file) hitModel = obj;
        }
    
        for (let [model, data] of previewData.entries()) {
            const isHovered = (model === hitModel);
            if (isHovered && !data.isHovering) {
                // Start the sequence
                data.isHovering = true;
                data.seqState = 1;
                if (data.idleAction) data.idleAction.stop();
                if (data.noAction) {
                    data.noAction.reset();
                    data.noAction.play();
                }
            } else if (!isHovered && data.isHovering) {
                // Stop hovering - go back to idle
                data.isHovering = false;
                data.seqState = 0;
                if (data.noAction) data.noAction.stop();
                if (data.waveAction) data.waveAction.stop();
                if (data.idleAction) data.idleAction.play();
            }
        }
    });

    The UX Impact: The selection screen became a stage. Users now spend time hovering over each character to see their full animation range. It’s no longer just a menu—it’s an audition, a personality showcase that makes the choice feel meaningful.


    Part 3: Dynamic Animation Management – The State Machine Approach

    The Problem: Animation Chaos

    With multiple animation states (Idle, Walk, Run, Jump, JumpLand, Wave), managing transitions cleanly became complex. Animations would overlap, fail to start, or get stuck in incorrect states.

    The Solution: A Clear State Hierarchy

    We implemented a state machine with clear priorities:

    text

    Jump > JumpLand > Run > Walk > Wave > Idle

    The Execution:

    javascript

    // Stop all animations first
    if (idleAction) idleAction.stop();
    if (walkAction) walkAction.stop();
    if (runAction) runAction.stop();
    if (waveAction) waveAction.stop();
    if (jumpAction) jumpAction.stop();
    if (jumpLandAction) jumpLandAction.stop();
    
    // Determine and play the correct animation
    let currentAnim = 'idle';
    if (isJumping) {
        currentAnim = 'jump';
        if (jumpAction) jumpAction.play();
    } else if (moving && running) {
        currentAnim = 'run';
        if (runAction) runAction.play();
    } else if (moving) {
        currentAnim = 'walk';
        if (walkAction) walkAction.play();
    } else if (waving) {
        currentAnim = 'wave';
        if (waveAction) waveAction.play();
    } else {
        currentAnim = 'idle';
        if (idleAction) idleAction.play();
    }

    The Key Insight: By stopping all animations before playing the new one, we eliminated the race conditions that caused overlapping animations. This “reset-first” approach ensured clean, reliable transitions every time.


    Part 4: Movement Enhancements – Run, Jump, and Wave

    The Problem: Limited Movement Vocabulary

    Initially, players could only walk. No running, no jumping, no social gestures. The movement felt flat and restrictive.

    The Solution: Expanded Movement Suite

    We added three new movement mechanics:

    1. Run (Hold Shift)

    • Speed increased from 6 to 12 units/second
    • Dedicated “Run” animation plays
    • Creates a sense of urgency and energy

    javascript

    const speed = keys['shift'] ? 12 : 6;
    const s = speed * delta;
    if (keys['w']) { 
        localPlayer.translateZ(s); 
        moving = true; 
        if (keys['shift']) running = true; 
    }

    2. Jump (Spacebar)

    • Physics-based jump with gravity
    • “Jump” and “Jump_Land” animations for realistic motion
    • Adds vertical dimension to the world

    javascript

    if (keys[' '] && !isJumping) {
        velocityY = jumpForce;
        isJumping = true;
        if(jumpAction) {
            jumpAction.stop();
            jumpAction.reset();
            jumpAction.play();
        }
    }

    3. Social Wave (Proximity-based)

    • When facing another player within 4 units
    • Automatically plays the “Wave” animation
    • Fosters social interaction without requiring a menu

    javascript

    if (!moving && !isJumping) {
        Object.values(otherPlayers).forEach(remote => {
            const dist = localPlayer.position.distanceTo(remote.model.position);
            if (dist < 4) {
                const dirToRemote = remote.model.position.clone().sub(localPlayer.position).normalize();
                const localForward = new THREE.Vector3(0, 0, 1).applyQuaternion(localPlayer.quaternion);
                const dot = localForward.dot(dirToRemote);
                if (dot > 0.7) { // Facing roughly toward the other player
                    waving = true;
                }
            }
        });
    }

    The UX Impact: These additions transformed movement from a utility into an expressive tool. Running feels urgent, jumping feels joyful, and waving feels friendly. Players now have a vocabulary of motion that matches their intent.


    Part 5: Technical Normalization – The Animation Bridge

    The Problem: Animation Name Inconsistency

    Our GLB files contained animations with numbered prefixes (e.g., “4. Idle”, “10. No”, “17. Wave”), but the game code expected un-prefixed names (“Idle”, “No”, “Wave”). This mismatch caused animations to fail silently.

    The Solution: Dynamic Animation Discovery

    We implemented a flexible animation discovery system that:

    1. Logs all available animations to the console for debugging
    2. Matches animations by exact name after inspecting the GLB
    3. Provides fallbacks if specific animations are missing

    javascript

    const idleClip = gltf.animations.find(a => a.name === 'Idle');
    const noClip = gltf.animations.find(a => a.name === 'No');
    const waveClip = gltf.animations.find(a => a.name === 'Wave');
    const walkClip = gltf.animations.find(a => a.name === 'Walk');
    const runClip = gltf.animations.find(a => a.name === 'Run');
    const jumpClip = gltf.animations.find(a => a.name === 'Jump');
    const jumpLandClip = gltf.animations.find(a => a.name === 'Jump_Land');

    The Lesson: The GLB format is incredibly powerful, but it requires careful introspection. By logging available animations and matching by exact name, we created a system that’s both robust and debuggable.


    Part 6: UX Refinements – The Small Details That Matter

    Visual Feedback

    • HUD Hint: “HOLD SHIFT TO RUN” appears in-game to teach new players
    • Count Display: Shows total players online
    • Camera Toggle: Right-click or V to flip between over-the-shoulder and face-forward views

    Responsive Design

    • Window resize handling for all screen sizes
    • Consistent UI scaling across devices
    • Touch and mouse support

    Performance Optimization

    • Animation mixers updated only when needed
    • Delta-time-based movement for consistent speed regardless of framerate
    • Efficient player updates using interpolation (position.lerp)

    The Results: What We Achieved

    Technical Wins

    1. Zero Animation Overlap – Clean state transitions every time
    2. Flexible Animation Discovery – Works with any properly formatted GLB
    3. Scalable Architecture – Adding new animations is as simple as finding them by name
    4. Performance-Neutral – All features added with minimal performance impact

    UX Wins

    1. Living Characters – Avatars feel alive even when standing still
    2. Emotional Connection – Players bond with their characters through personality
    3. Social Interaction – Waving creates organic, spontaneous connection
    4. Movement Vocabulary – Walk, Run, Jump, Wave – each with distinct feel

    Player Feedback

    “I love watching my character do the No → Wave loop when I’m waiting. It makes me feel like they have a personality.”

    “The run animation is so much better than just moving faster. It feels like my character is actually exerting effort.”

    “I waved at another player and they waved back. It was a tiny moment but it made the world feel real.”


    The Future: What’s Next

    Animation Expansion

    • More Idle Loops: Multiple idle sequences for variety
    • Contextual Animations: Character-specific gestures based on personality
    • Emote System: Full emote wheel for social expression

    Technical Improvements

    • Animation Blending: Smooth transitions between states
    • Procedural Animation: Subtle breathing and weight shifts
    • LOD System: Performance optimization for many players

    Social Features

    • Emote Previews: See emotes before selecting them
    • Sync’d Animations: Group dances and coordinated gestures
    • Reaction System: Quick reactions to events (cheer, boo, etc.)

    Conclusion: The Art of First Impressions

    Building a robust file architecture gave us the foundation. But breathing life into that architecture—turning a static selection screen into a dynamic, personality-driven showcase—is what makes the player feel like they’re stepping into a world that cares about detail.

    The GLB format, combined with a thoughtful state machine and Three.js’s animation system, allowed us to achieve this with minimal code changes and zero asset pipeline overhead.

    The game isn’t just a playground anymore. It’s a stage where every character has a role to play.

    https://chat.deepseek.com/

  • world – start!

    The backbone of our 3D world is built upon a highly organized file architecture and the use of the industry-standard GLB (GL Transmission Format Binary). Because the game needs to load massive amounts of data—meshes, textures, and complex animations—simultaneously for up to 100 players, the way these files are structured is critical to performance.

    Here is a breakdown of how we managed the GLB assets and the project’s file hierarchy.

    1. The GLB: The “All-in-One” 3D Container

    We chose GLB over traditional formats (like OBJ or FBX) because it is the “JPEG of 3D.” In our game, each character (01, 02, and 03) is a single binary file that contains:

    • The Mesh: The actual 3D body of the character.
    • The Textures: The colors, skins, and materials embedded directly into the file so they don’t get lost.
    • The Skeleton (Rig): The internal bone structure that allows the character to move.
    • The Animation Library: This is the most important part. Each GLB carries its own “clip” library. When the game starts, our script scans the GLB for specific names like “Walk” and “Wave”. By packaging everything in one file, we ensure that when a player joins, the character doesn’t appear “naked” or “frozen”—everything loads in one single network request.

    2. The Project Architecture

    To manage the relationship between the 3D assets and the game logic, we implemented a strict “Root-Public” structure. This separation ensures that the 3D models stay secure and the game runs smoothly.

    The Root Level:

    • server.js: This is the “brain” of the world. It doesn’t handle graphics; it only handles “coordinates.” It tracks where Player 01 is and tells the other 99 players.
    • package.json: The manifest that ensures the environment has the correct tools to sync the players.

    The Public Folder (The Game’s Front Door):

    • index.html: The “Canvas.” This file contains the Three.js engine that renders the world. It is the only file the user actually “sees.”
    • player/ (The Asset Vault): We created a dedicated sub-folder specifically for our 3D models. Inside this folder live 01.glb, 02.glb, and 03.glb.

    3. Dynamic Path Management

    A key feature of our management system is how we handle file paths. Instead of hard-coding every character, the game uses a dynamic loading system.

    • When a user clicks on a 3D character during selection, the game stores that character’s filename (e.g., “02.glb”).
    • The script then looks into the public/player/ folder and pulls that specific file.
    • This structure allows us to add 10 or 100 new characters just by dropping a new GLB into the folder and adding its name to a list, without changing a single line of the game’s core code.

    4. Technical Normalization

    Inside the file structure, we managed a major challenge: Scale. 3D artists often export models at different sizes. One GLB might be 100 times larger than another.
    To manage this, we built a Normalization Layer into the loading process. Every time a file is pulled from the player/ folder, the game “measures” the model’s dimensions and automatically scales it to a standard height. This ensures that in our file structure, every GLB is treated as a uniform “Player” regardless of its original size.

    Summary of the File Map

    code Text

    /Project_Root
    
    ├── server.js             (Multiplayer Logic)
    ├── package.json          (Server Config)
    └── /public               (Visible Game Files)
        ├── index.html        (Three.js Engine & UI)
        └── /player           (The 3D Asset Folder)
            ├── 01.glb        (Character 1 + Animations)
            ├── 02.glb        (Character 2 + Animations)
            └── 03.glb        (Character 3 + Animations)

    By using this clean, modular structure, we created a game that is easy to update and incredibly fast to load, ensuring that the 3D “High-Key” studio experience is seamless for every player who enters the world.

    https://chat.deepseek.com and https://aistudio.google.com

    GLBhttps://pixabay.com/3d-models/search/glb/