new cooking… tiny achievement – https://mero.live/build/battle/
<!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>