Categories
.glb blog officialstupid threejs

2D glb mix

new cooking… tiny achievement – https://mero.live/build/battle/

mixing GLB #mixamo#three.js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Battle</title>
    <style>
        body { margin: 0; overflow: hidden; background-color: #000; font-family: 'Arial Black', sans-serif; }
        #ui {
            position: absolute;
            top: 20px;
            left: 20px;
            color: #fff; 
            padding: 15px; 
            text-transform: uppercase;
            z-index: 10; 
            font-size: 12px;
        }
        .key-bind { color: #ffeb3b; font-weight: bold; }
    </style>
</head>
<body>

    <div id="ui">
        <h2 style="margin:0 0 10px 0">Battle</h2>
        <div><span class="key-bind">A / D :</span> Walk</div>
        <div><span class="key-bind">Shift + A/D :</span> Run</div>
        <div><span class="key-bind">A :</span> Light Slash</div>
        <div><span class="key-bind">J :</span> Downward Hit</div>
        <div><span class="key-bind">K :</span> Combo Attack</div>
        <div><span class="key-bind">L :</span> Jump Attack</div>
        <div><span class="key-bind">N :</span> Block</div>
        <div><span class="key-bind">M :</span> Crouch (Hold)</div>
        <div><span class="key-bind">, :</span> Roll</div>
        <div><span class="key-bind">. :</span> Side Kick</div>
        <div><span class="key-bind">; :</span> Backflip Uppercut</div>
    </div>

    <script type="importmap">
        {
            "imports": {
                "three": "https://unpkg.com/three@0.160.0/build/three.module.js",
                "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
            }
        }
    </script>

    <script type="module">
        import * as THREE from 'three';
        import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
        import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';

        const ANIMATIONS_MAP = {
            idle: 'Idle-main_compressed.glb', 
            walk: 'unarmed_walk_compressed.glb', 
            run: 'run_compressed.glb',
            block: 'sword_block_compressed.glb',
            crouch: 'sword_crouch_compressed.glb',
            roll: 'run_roll_compressed.glb',
            attack1: 'sword_slash_compressed.glb',
            attack2: 'sword_down_compressed.glb',
            attack3: 'sword_combo_compressed.glb',
            attack4: 'sword_jump_compressed.glb',
            kick: 'Side Kick_compressed.glb',
            uppercut: 'Back Flip To Uppercut_compressed.glb'
        };

        // --- ADJUST THIS VALUE TO FIX THE FLOATING ---
        // If character is still floating, make this more negative (e.g., -0.8)
        // If character is inside the floor, make it closer to 0 (e.g., -0.2)
        const CROUCH_Y_OFFSET = -0.5; 

        let scene, camera, renderer, clock, mixer;
        let characterGroup, model;
        let actions = {}; 
        let activeAction;
        let isActionLocked = false; 
        let keys = {};
        let facingDirection = 1;

        init();

        async function init() {
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x111111);
            clock = new THREE.Clock();

            camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.set(0, 1.5, 4); 
            camera.lookAt(0, 1, 0);

            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.shadowMap.enabled = true;
            document.body.appendChild(renderer.domElement);

            scene.add(new THREE.HemisphereLight(0xffffff, 0x444444, 3));
            const dirLight = new THREE.DirectionalLight(0xffffff, 2);
            dirLight.position.set(5, 10, 5);
            dirLight.castShadow = true;
            scene.add(dirLight);

            const floor = new THREE.Mesh(new THREE.PlaneGeometry(100, 50), new THREE.MeshPhongMaterial({ color: 0x222222 }));
            floor.rotation.x = -Math.PI / 2;
            floor.receiveShadow = true;
            scene.add(floor);

            characterGroup = new THREE.Group(); 
            scene.add(characterGroup);

            const dracoLoader = new DRACOLoader();
            dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/');

            const loader = new GLTFLoader();
            loader.setDRACOLoader(dracoLoader);

            try {
                const gltf = await loader.loadAsync(ANIMATIONS_MAP.walk);
                model = gltf.scene;
                model.scale.set(1, 1, 1); 
                model.rotation.y = Math.PI / 2; 
                characterGroup.add(model);
                
                mixer = new THREE.AnimationMixer(model);

                for (const [key, file] of Object.entries(ANIMATIONS_MAP)) {
                    const gltfAnim = await loader.loadAsync(file);
                    let clip = gltfAnim.animations[0];
                    
                    // Remove root position to prevent popping
                    clip.tracks = clip.tracks.filter(track => !track.name.includes('.position'));

                    const action = mixer.clipAction(clip);
                    if (key.startsWith('attack') || key === 'roll' || key === 'kick' || key === 'uppercut' || key === 'crouch' || key === 'block') {
                        action.setLoop(THREE.LoopOnce);
                        action.clampWhenFinished = true;
                    } else {
                        action.setLoop(THREE.LoopRepeat);
                    }
                    actions[key] = action;
                }
                snapToAction('idle');
            } catch (e) { console.error("Loading error:", e); }

            window.addEventListener('keydown', (e) => {
                const k = e.key.toLowerCase();
                keys[k] = true;
                handleCombat(k);
            });
            window.addEventListener('keyup', (e) => {
                keys[e.key.toLowerCase()] = false;
            });

            animate();
        }

        function handleCombat(key) {
            if (isActionLocked) return;
            if (key === 'j') playOneShot('attack2');
            else if (key === 'k') playOneShot('attack3');
            else if (key === 'l') playOneShot('attack4');
            else if (key === 'n') snapToAction('block');
            else if (key === 'm') snapToAction('crouch'); 
            else if (key === ',') playOneShot('roll');
            else if (key === '.') playOneShot('kick');
            else if (key === ';') playOneShot('uppercut');
            else if (key === 'a' && !keys['d']) playOneShot('attack1');
        }

        function playOneShot(name) {
            isActionLocked = true; 
            const action = actions[name];
            if(activeAction) activeAction.stop();
            action.reset().play();
            activeAction = action;

            const onFinished = (e) => {
                if (e.action === action) {
                    isActionLocked = false; 
                    if (keys['m']) snapToAction('crouch');
                    else if (keys['n']) snapToAction('block');
                    else snapToAction('idle');
                    mixer.removeEventListener('finished', onFinished);
                }
            };
            mixer.addEventListener('finished', onFinished);
        }

        function snapToAction(name) {
            if (isActionLocked && name !== 'idle') return; 
            const action = actions[name];
            if (activeAction !== action) {
                if (activeAction && !isActionLocked) activeAction.fadeOut(0.1);
                action.reset().setEffectiveWeight(1).fadeIn(0.1).play();
                activeAction = action;
            }
        }

        function updateMovement(delta) {
            if (!characterGroup) return;

            // FORCE GROUND POSITION
            characterGroup.position.y = 0;

            // DYNAMIC OFFSET FIX: 
            // If the current active animation is 'crouch', we physically push 
            // the model down relative to the group to compensate for the floating pose.
            if (activeAction === actions['crouch']) {
                model.position.y = CROUCH_Y_OFFSET;
            } else {
                model.position.y = 0;
            }

            if (!isActionLocked) {
                const walkSpeed = 1.8; 
                const runSpeed = 4.0; 
                const currentSpeed = keys['shift'] ? runSpeed : walkSpeed;
                const currentAnim = keys['shift'] ? 'run' : 'walk';

                if (keys['d']) { 
                    facingDirection = 1;
                    characterGroup.rotation.y = 0; 
                    characterGroup.position.x += currentSpeed * delta;
                    snapToAction(currentAnim);
                } else if (keys['a']) { 
                    facingDirection = -1;
                    characterGroup.rotation.y = Math.PI; 
                    characterGroup.position.x -= currentSpeed * delta;
                    snapToAction(currentAnim);
                } 
                else if (!keys['m'] && !keys['n']) {
                    snapToAction('idle');
                } 
                else if (keys['m']) {
                    snapToAction('crouch');
                }
                else {
                    snapToAction('block');
                }
            }

            characterGroup.position.x = Math.max(-10, Math.min(10, characterGroup.position.x));
        }

        function animate() {
            requestAnimationFrame(animate);
            const delta = clock.getDelta();
            if (mixer) mixer.update(delta);
            updateMovement(delta);
            renderer.render(scene, camera);
        }

        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });
    </script>
</body>
</html>