Refactored the WHOLE project
From a simple test file, we're now on road to create a real project Took 3 hours, but code looks nice
This commit is contained in:
14
src/cameras/PerspectiveCamera.js
Normal file
14
src/cameras/PerspectiveCamera.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as MathUtils from 'three/src/math/MathUtils.js';
|
||||||
|
|
||||||
|
export function getCamera(){
|
||||||
|
/* Function to create and place the Camera object. is freecam is true, it will enable freecam controls for easier debug*/
|
||||||
|
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
|
||||||
|
|
||||||
|
camera.position.x = 0;// <----->
|
||||||
|
camera.position.y = -4;// ^^ / vv
|
||||||
|
camera.position.z = 4;// ^-----v
|
||||||
|
camera.rotateOnWorldAxis(new THREE.Vector3(1, 0, 0), MathUtils.degToRad(60));
|
||||||
|
|
||||||
|
return camera;
|
||||||
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
//https://medium.com/geekculture/how-to-control-three-js-camera-like-a-pro-a8575a717a2
|
|
||||||
|
|
||||||
import * as THREE from 'three';
|
|
||||||
import { FlyControls } from 'three/examples/jsm/controls/FlyControls';
|
|
||||||
|
|
||||||
let controls;
|
|
||||||
export function enableFreeCam(camera, renderer){
|
|
||||||
controls = new FlyControls( camera, renderer.domElement );
|
|
||||||
controls.movementSpeed = 5;
|
|
||||||
controls.rollSpeed = Math.PI / 48;
|
|
||||||
controls.autoForward = false;
|
|
||||||
controls.dragToLook = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateFreeCamKeys(){
|
|
||||||
controls.update(0.01)
|
|
||||||
}
|
|
||||||
132
src/main.js
132
src/main.js
@@ -1,128 +1,46 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import * as MathUtils from 'three/src/math/MathUtils.js';
|
import { getCamera } from './cameras/PerspectiveCamera.js';
|
||||||
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
import {addAssetsOnScene} from './utils/addAssetsOnScene.js';
|
||||||
import {createGround} from './objects/ground.js';
|
import * as PlayerMovement from './utils/playerMovement.js';
|
||||||
import {alignGround} from './utils/placement.js';
|
import * as Animations from './utils/animateMesh.js';
|
||||||
import * as Freecam from './cameras/freecam.js';
|
|
||||||
let Clock = new THREE.Clock();
|
let Clock = new THREE.Clock();
|
||||||
console.log("OK")
|
|
||||||
|
|
||||||
|
/* --------------------- */
|
||||||
|
/* | SCENE CREATION | */
|
||||||
|
/* --------------------- */
|
||||||
const scene = new THREE.Scene();
|
const scene = new THREE.Scene();
|
||||||
scene.background = new THREE.Color(0x0EB1D2);
|
scene.background = new THREE.Color(0x0EB1D2);
|
||||||
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
|
const camera = getCamera();
|
||||||
|
|
||||||
const renderer = new THREE.WebGLRenderer();
|
const renderer = new THREE.WebGLRenderer();
|
||||||
renderer.setSize( window.innerWidth, window.innerHeight );
|
renderer.setSize( window.innerWidth, window.innerHeight );
|
||||||
document.body.appendChild( renderer.domElement );
|
document.body.appendChild( renderer.domElement );
|
||||||
|
|
||||||
//Freecam
|
//Here, we create/load every asset used, and add it in the scene
|
||||||
Freecam.enableFreeCam(camera, renderer);
|
let assets = await addAssetsOnScene(scene);
|
||||||
|
|
||||||
const ground = createGround();
|
|
||||||
scene.add(ground);
|
|
||||||
|
|
||||||
const loader = new GLTFLoader();
|
|
||||||
let mixer, clip, action, playerModel;
|
|
||||||
|
|
||||||
loader.load( './src/objects/models/player.gltf', function ( gltf ) {
|
|
||||||
playerModel = gltf;
|
|
||||||
gltf.scene.scale.set(0.2,0.2,0.2);
|
|
||||||
gltf.scene.rotateOnWorldAxis(new THREE.Vector3(1,0,0), MathUtils.degToRad(90));
|
|
||||||
alignGround(ground, gltf.scene);
|
|
||||||
scene.add( gltf.scene );
|
|
||||||
|
|
||||||
mixer = new THREE.AnimationMixer( gltf.scene );
|
|
||||||
clip = THREE.AnimationClip.findByName( gltf.animations, 'Walk' );
|
|
||||||
action = mixer.clipAction( clip );
|
|
||||||
|
|
||||||
}, undefined, function ( error ) {
|
|
||||||
|
|
||||||
console.error( error );
|
|
||||||
|
|
||||||
} );
|
|
||||||
|
|
||||||
scene.add(new THREE.AmbientLight(0x404040, 50));
|
|
||||||
|
|
||||||
camera.position.x = 0;// <----->
|
|
||||||
camera.position.y = -4;// ^^ / vv
|
|
||||||
camera.position.z = 4;// ^-----v
|
|
||||||
camera.rotateOnWorldAxis(new THREE.Vector3(1, 0, 0), MathUtils.degToRad(60));
|
|
||||||
|
|
||||||
/* ----- -*
|
|
||||||
MOVEMENT
|
|
||||||
-----------*/
|
|
||||||
|
|
||||||
const PLAYER_SPEED = 0.015;
|
|
||||||
const playerDirection = new THREE.Vector3(0, 0, 0);
|
|
||||||
const playerRotation = new THREE.Quaternion();
|
|
||||||
|
|
||||||
|
/* --------------------- */
|
||||||
|
/* | MOVEMENT | */
|
||||||
|
/* --------------------- */
|
||||||
// Events to listen keyboard inputs
|
// Events to listen keyboard inputs
|
||||||
document.addEventListener('keydown', handleKeyDown);
|
document.addEventListener('keydown', PlayerMovement.handleKeyDown);
|
||||||
document.addEventListener('keyup', handleKeyUp);
|
document.addEventListener('keyup', PlayerMovement.handleKeyUp);
|
||||||
|
|
||||||
//Managing keys
|
/* --------------------- */
|
||||||
function handleKeyDown(event) {
|
/* | ANIMATIONS | */
|
||||||
action.play();
|
/* --------------------- */
|
||||||
switch (event.code) {
|
//We can add an animationMixer on objects, and define ready to play actions on objects
|
||||||
case 'ArrowUp':
|
//https://threejs.org/docs/#api/en/animation/AnimationAction
|
||||||
case 'KeyW':
|
Animations.addAnimationMixerOnMesh(assets.player, ["Walk"]);
|
||||||
playerDirection.y = 1; // Up
|
|
||||||
break;
|
|
||||||
case 'ArrowDown':
|
|
||||||
case 'KeyS':
|
|
||||||
playerDirection.y = -1; // Back
|
|
||||||
break;
|
|
||||||
case 'ArrowLeft':
|
|
||||||
case 'KeyA':
|
|
||||||
playerDirection.x = -1; // Left
|
|
||||||
break;
|
|
||||||
case 'ArrowRight':
|
|
||||||
case 'KeyD':
|
|
||||||
playerDirection.x = 1; // Right
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleKeyUp(event) {
|
|
||||||
action.stop();
|
|
||||||
switch (event.code) {
|
|
||||||
case 'ArrowUp':
|
|
||||||
case 'KeyW':
|
|
||||||
case 'ArrowDown':
|
|
||||||
case 'KeyS':
|
|
||||||
playerDirection.y = 0; // Arrêter le mouvement vertical
|
|
||||||
break;
|
|
||||||
case 'ArrowLeft':
|
|
||||||
case 'KeyA':
|
|
||||||
case 'ArrowRight':
|
|
||||||
case 'KeyD':
|
|
||||||
playerDirection.x = 0; // Arrêter le mouvement horizontal
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updatePlayerPosition() {
|
|
||||||
const delta = playerDirection.clone().multiplyScalar(PLAYER_SPEED);
|
|
||||||
playerModel.scene.position.add(delta);
|
|
||||||
camera.position.add(delta);
|
|
||||||
const ROTATION_SPEED = 0.05; // Vitesse de rotation
|
|
||||||
if (playerDirection.lengthSq() > 0) {
|
|
||||||
const angle = Math.atan2(playerDirection.x, -playerDirection.y);
|
|
||||||
const targetRotation = new THREE.Quaternion();
|
|
||||||
targetRotation.setFromAxisAngle(new THREE.Vector3(0, 1, 0), angle);
|
|
||||||
playerRotation.slerp(targetRotation, ROTATION_SPEED);
|
|
||||||
playerModel.scene.setRotationFromQuaternion(playerRotation);
|
|
||||||
playerModel.scene.rotateOnWorldAxis(new THREE.Vector3(1, 0, 0), MathUtils.degToRad(90));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function animate() {
|
function animate() {
|
||||||
requestAnimationFrame( animate );
|
requestAnimationFrame( animate );
|
||||||
if(mixer) mixer.update( Clock.getDelta()*1.4 ); //If mixer ready
|
|
||||||
|
|
||||||
updatePlayerPosition();
|
PlayerMovement.updatePlayerPositionAnimation(assets.player, camera);//Updating player position and rotation
|
||||||
//Freecam.updateFreeCamKeys()//Freecam
|
Animations.updateAnimations(Clock.getDelta());//Playing animations
|
||||||
renderer.render( scene, camera );
|
|
||||||
|
renderer.render( scene, camera );//Calculating and redering new frame
|
||||||
|
|
||||||
}
|
}
|
||||||
animate();
|
animate();
|
||||||
|
|||||||
37
src/utils/addAssetsOnScene.js
Normal file
37
src/utils/addAssetsOnScene.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as MathUtils from 'three/src/math/MathUtils.js';
|
||||||
|
import { createGround } from '../objects/ground.js';
|
||||||
|
import {loadModel} from './import.js';
|
||||||
|
|
||||||
|
|
||||||
|
function alignGround(ground, MovingObject){
|
||||||
|
/*Function used to place an object on top of another object. In fact, this is used only to place correctly objects on the ground*/
|
||||||
|
const boundingBoxGround = new THREE.Box3().setFromObject(ground);
|
||||||
|
const boundingBoxMovingObject = new THREE.Box3().setFromObject(MovingObject);
|
||||||
|
|
||||||
|
const MovingObjectHeight = boundingBoxMovingObject.max.z - boundingBoxMovingObject.min.z;
|
||||||
|
MovingObject.position.y = MovingObjectHeight/2 + boundingBoxGround.max.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addAssetsOnScene(scene){
|
||||||
|
/*This function takes the scene as arg, and is used to load everything needed on the terrain.
|
||||||
|
Player, trees, fences, and everything displayed on the screen is added by this function, at the right place
|
||||||
|
|
||||||
|
Will return an object containing created objects*/
|
||||||
|
|
||||||
|
scene.add(new THREE.AmbientLight(0x404040, 50));//Light, required to see
|
||||||
|
|
||||||
|
const ground = createGround();
|
||||||
|
scene.add(ground);
|
||||||
|
|
||||||
|
const player = await loadModel('src/objects/models/player.gltf');//Loading player
|
||||||
|
player.scene.scale.set(0.2,0.2,0.2);
|
||||||
|
alignGround(ground, player.scene);
|
||||||
|
player.scene.rotateOnWorldAxis(new THREE.Vector3(1,0,0), MathUtils.degToRad(90));//Must be standing up
|
||||||
|
scene.add( player.scene );
|
||||||
|
|
||||||
|
return {
|
||||||
|
ground: ground,
|
||||||
|
player: player,
|
||||||
|
};
|
||||||
|
}
|
||||||
28
src/utils/animateMesh.js
Normal file
28
src/utils/animateMesh.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
const ANIMATION_SPEED_MULTIPLIER = 1.4;
|
||||||
|
let mixersToUpdateEachFrame = [];
|
||||||
|
|
||||||
|
export function addAnimationMixerOnMesh(mesh, actions){
|
||||||
|
//See https://threejs.org/docs/#manual/en/introduction/Animation-system
|
||||||
|
/*Function used to add an animationMixer on a mesh, so we can easily start or stop animations
|
||||||
|
Takes the animated mesh, and a list of actions names that should become animated */
|
||||||
|
|
||||||
|
const mixer = new THREE.AnimationMixer(mesh.scene);//We create a new mixer to manage every animations on this mesh
|
||||||
|
|
||||||
|
mesh.animationMixer = mixer;//We add this mixer on the mesh
|
||||||
|
mesh.animationActions = {};//And create an object that will hold actions
|
||||||
|
|
||||||
|
actions.forEach(element => {
|
||||||
|
const clip = THREE.AnimationClip.findByName( mesh.animations, element );//For each given action name, we get a clip
|
||||||
|
mesh.animationActions[element] = mixer.clipAction(clip);//and save it as an action
|
||||||
|
});
|
||||||
|
|
||||||
|
mixersToUpdateEachFrame.push(mixer);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateAnimations(deltaSeconds){
|
||||||
|
mixersToUpdateEachFrame.forEach(element => {
|
||||||
|
element.update(deltaSeconds * ANIMATION_SPEED_MULTIPLIER);
|
||||||
|
});
|
||||||
|
};
|
||||||
18
src/utils/import.js
Normal file
18
src/utils/import.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
||||||
|
|
||||||
|
const loader = new GLTFLoader();
|
||||||
|
|
||||||
|
export function loadModel(path) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
loader.load(
|
||||||
|
path,
|
||||||
|
(gltf) => {
|
||||||
|
resolve(gltf);
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
(error) => {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import * as THREE from 'three';
|
|
||||||
|
|
||||||
export function alignGround(ground, MovingObject){
|
|
||||||
const boundingBoxGround = new THREE.Box3().setFromObject(ground);
|
|
||||||
const boundingBoxMovingObject = new THREE.Box3().setFromObject(MovingObject);
|
|
||||||
|
|
||||||
const MovingObjectHeight = boundingBoxMovingObject.max.z - boundingBoxMovingObject.min.z;
|
|
||||||
MovingObject.position.y = MovingObjectHeight/2 + boundingBoxGround.max.z;
|
|
||||||
}
|
|
||||||
69
src/utils/playerMovement.js
Normal file
69
src/utils/playerMovement.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as MathUtils from 'three/src/math/MathUtils.js';
|
||||||
|
|
||||||
|
const PLAYER_SPEED = 0.015;
|
||||||
|
const ROTATION_SPEED = 0.05; // Vitesse de rotation
|
||||||
|
const playerDirection = new THREE.Vector3(0, 0, 0);
|
||||||
|
const playerRotation = new THREE.Quaternion();
|
||||||
|
|
||||||
|
//Managing keys
|
||||||
|
export function handleKeyDown(event) {
|
||||||
|
switch (event.code) {
|
||||||
|
case 'ArrowUp':
|
||||||
|
case 'KeyW':
|
||||||
|
playerDirection.y = 1; // Up
|
||||||
|
break;
|
||||||
|
case 'ArrowDown':
|
||||||
|
case 'KeyS':
|
||||||
|
playerDirection.y = -1; // Back
|
||||||
|
break;
|
||||||
|
case 'ArrowLeft':
|
||||||
|
case 'KeyA':
|
||||||
|
playerDirection.x = -1; // Left
|
||||||
|
break;
|
||||||
|
case 'ArrowRight':
|
||||||
|
case 'KeyD':
|
||||||
|
playerDirection.x = 1; // Right
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleKeyUp(event) {
|
||||||
|
switch (event.code) {
|
||||||
|
case 'ArrowUp':
|
||||||
|
case 'KeyW':
|
||||||
|
case 'ArrowDown':
|
||||||
|
case 'KeyS':
|
||||||
|
playerDirection.y = 0; // Arrêter le mouvement vertical
|
||||||
|
break;
|
||||||
|
case 'ArrowLeft':
|
||||||
|
case 'KeyA':
|
||||||
|
case 'ArrowRight':
|
||||||
|
case 'KeyD':
|
||||||
|
playerDirection.x = 0; // Arrêter le mouvement horizontal
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updatePlayerPositionAnimation(player, camera) {
|
||||||
|
/* Function called in the main loop
|
||||||
|
Used to calculate the position and orientation of the player
|
||||||
|
Also used to start and stop walking animation */
|
||||||
|
const delta = playerDirection.clone().multiplyScalar(PLAYER_SPEED);
|
||||||
|
player.scene.position.add(delta);
|
||||||
|
camera.position.add(delta);//The camera must follow the player
|
||||||
|
|
||||||
|
if (playerDirection.length() > 0) {//If a movement key is pressed ( or something in playerDirection )
|
||||||
|
if(!player.animationActions.Walk.isRunning()) player.animationActions.Walk.play();//Playing walk animation
|
||||||
|
|
||||||
|
const angle = Math.atan2(playerDirection.x, -playerDirection.y);//Get the angle between center (0;0) and the coordinates, which are directly provided by the pressed keys
|
||||||
|
const targetRotation = new THREE.Quaternion();
|
||||||
|
|
||||||
|
targetRotation.setFromAxisAngle(new THREE.Vector3(0, 1, 0), angle);
|
||||||
|
playerRotation.slerp(targetRotation, ROTATION_SPEED);//Slowly moving from playerRotation to targetRotation
|
||||||
|
player.scene.setRotationFromQuaternion(playerRotation);
|
||||||
|
player.scene.rotateOnWorldAxis(new THREE.Vector3(1, 0, 0), MathUtils.degToRad(90));//Used to force the player to be standing up instead of sleeping
|
||||||
|
}else{
|
||||||
|
if(player.animationActions.Walk.isRunning()) player.animationActions.Walk.stop();//Not walking, we can stop this animation
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user