TYPeee

Character Movement in Three.js

Implementing character movement involves several key functions, including executing the model's animation, rotating the model, and positioning the model. These functions will be triggered by key presses. Let's implement character movement step by step.

typeee image

 

1. Set up

First, set up the necessary environment, including importing the required libraries and initializing the scene, renderer, camera, and controls.

javascript
1import * as THREE from "three";
2import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
3import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
4import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader";
5
6export default class Three {
7  constructor(container) {
8    //VARIABLES
9    this._container = container;
10
11    //SCENE
12    this._scene = new THREE.Scene();
13
14    //RENDERER
15    this._renderer = new THREE.WebGLRenderer({ antialias: true });
16    this._renderer.setSize(
17      this._container.offsetWidth,
18      this._container.offsetHeight
19    );
20    this._renderer.setPixelRatio(window.devicePixelRatio);
21    this._renderer.shadowMap.enabled = true;
22
23    //CAMERA
24    this._camera = new THREE.PerspectiveCamera(
25      75,
26      this._container.offsetWidth / this._container.offsetHeight,
27      0.1,
28      1000
29    );
30    this._camera.position.set(0, 3, 6);
31    this._camera.lookAt(new THREE.Vector3(0, 0, 0));
32    this._container.appendChild(this._renderer.domElement);
33
34    //ORBIT CONTROLS
35    this._orbit = new OrbitControls(this._camera, this._renderer.domElement);
36    this._orbit.enableDamping = true; // smooth movement
37    this._orbit.minDistance = 5;
38    this._orbit.maxDistance = 15;
39    this._orbit.enablePan = false; // block movement using right mouse click
40    this._orbit.maxPolarAngle = Math.PI / 2 - 0.05;
41
42    //CLOCK
43    this._clock = new THREE.Clock();
44
45    //INITIALIZE
46    const grid = new THREE.GridHelper();
47    this._scene.add(grid);
48
49    const animate = this.animate.bind(this);
50    animate();
51  }
52
53  animate() {
54    requestAnimationFrame(this.animate.bind(this));
55    if (this._orbit && this._orbit.enabled) this._orbit.update();
56    this._renderer.render(this._scene, this._camera);
57  }
58}

 

2. Add Event Listeners for Key Press

Next, add key event listeners to handle character movement based on key presses.

javascript
1constructor() {
2	this._keyMap = new Map();
3}
4
5setKeyEvent() {
6    window.addEventListener("keydown", (e) => {
7      if (e.code === "KeyW") this._keyMap.set("w", true);
8      if (e.code === "KeyA") this._keyMap.set("a", true);
9      if (e.code === "KeyS") this._keyMap.set("s", true);
10      if (e.code === "KeyD") this._keyMap.set("d", true);
11      if (e.code === "ShiftLeft") this._keyMap.set("shift", true);
12    });
13    window.addEventListener("keyup", (e) => {
14      if (e.code === "KeyW") this._keyMap.set("w", false);
15      if (e.code === "KeyA") this._keyMap.set("a", false);
16      if (e.code === "KeyS") this._keyMap.set("s", false);
17      if (e.code === "KeyD") this._keyMap.set("d", false);
18      if (e.code === "ShiftLeft") this._keyMap.set("shift", false);
19    });
20  }

 

3. Create Class for Control Model

Define a class to manage the model's animation, rotation, and positioning based on the key presses.

1) Animation

Determine the appropriate animation state based on the key presses.

javascript
1let isDirKeyPressed = false;
2
3for (const entry of keyMap) {
4  if (entry[0] !== "shift" && entry[1]) {
5    isDirKeyPressed = true;
6    break;
7  }
8}
9
10let nextActionName;
11
12
13if (isDirKeyPressed && keyMap.get("shift")) nextActionName = "Run";
14else if (isDirKeyPressed) nextActionName = "Walk";
15else nextActionName = "Idle";
16
17if (this.curActionName !== nextActionName) {
18  const curAction = this.animationMap.get(this.curActionName);
19  const nextAction = this.animationMap.get(nextActionName);
20
21  curAction.fadeOut(this.fadeDuration);
22  nextAction.reset().fadeIn(this.fadeDuration).play();
23  this.curActionName = nextActionName;
24}
25
26this.mixer.update(delta);

 

2) Rotation

Rotate the model based on the direction keys pressed.

typeee image
javascript
1if (isDirKeyPressed) {
2
3  let sightAngleInZXPlane = Math.atan2( // get θ1
4    this.camera.position.x - this.model.position.x,
5    this.camera.position.z - this.model.position.z
6  );
7
8  const offsetAngle = this.getOffsetAngle(keyMap); // get θ2
9  const rotateQuarternion = new THREE.Quaternion();
10  
11  rotateQuarternion.setFromAxisAngle( // get final angle
12    new THREE.Vector3(0, 1, 0),
13    sightAngleInZXPlane + offsetAngle
14  );
15  this.model.quaternion.rotateTowards(rotateQuarternion, 0.2);
16}
17
18
javascript
1getOffsetAngle(keyMap) {
2  let offsetAngle = 0;
3
4  if (keyMap.get("w")) {
5    if (keyMap.get("a")) offsetAngle = Math.PI / 4;
6    else if (keyMap.get("d")) offsetAngle = -Math.PI / 4;
7  } else if (keyMap.get("s")) {
8    if (keyMap.get("a")) offsetAngle = Math.PI * (3 / 4);
9    else if (keyMap.get("d")) offsetAngle = -Math.PI * (3 / 4);
10    else offsetAngle = Math.PI;
11  } else if (keyMap.get("a")) {
12    offsetAngle = Math.PI / 2;
13  } else if (keyMap.get("d")) {
14    offsetAngle = -Math.PI / 2;
15  }
16
17  return offsetAngle;
18}

 

3) Position

Update the model's position based on the direction keys pressed.

javascript
1if (isDirKeyPressed) {
2
3
4  const direction = new THREE.Vector3();
5  const velocity = keyMap.get("shift") ? 0.03 : 0.016;
6
7
8  this.camera.getWorldDirection(direction);
9  direction.y = 0;
10  direction.normalize();
11  direction.applyAxisAngle(new THREE.Vector3(0, 1, 0), offsetAngle);
12
13  const movement = direction.clone().multiplyScalar(velocity);
14  this.model.position.add(movement);
15  this.camera.position.add(movement);
16}

 

4. Orbit Control

Update the orbit control target to follow the model.

javascript
1if (isDirKeyPressed) {
2  if (this.orbit.enabled) {
3    this.orbit.target = new THREE.Vector3().copy(this.model.position);
4  }
5}

4. Load Model

Load the 3D model and set up the necessary environment and animations.

javascript
1import ModelControl from "./control";
2
3loadModel() {
4    //SET ENV LIGHT
5    const rLoader = new RGBELoader();
6    rLoader.load("/HDR/MR_INT-004_BigWindowTree_Thea.hdr", (texture) => {
7      texture.mapping = THREE.EquirectangularReflectionMapping;
8      this._scene.environment = texture;
9    });
10
11    //LOAD MODEL
12    const loader = new GLTFLoader();
13    loader.load("3d/soldier/soldier.glb", (gltf) => {
14      const model = gltf.scene;
15      const mixer = new THREE.AnimationMixer(model);
16      const animationMap = gltf.animations.reduce((acc, cur) => {
17        const action = mixer.clipAction(cur);
18        acc.set(cur.name, action);
19        return acc;
20      }, new Map());
21
22      this._scene.add(model);
23      this._control = new ModelControl(
24        model,
25        mixer,
26        animationMap,
27        "Idle",
28        this._camera,
29        this._orbit
30      );
31    });
32  }

 

Related Posts