2022. 3. 2. 23:09ㆍTechnology[Front]
앞선 게시글에서 알아보았던 유리재질 만드는 법과 광원의 위치이동을 이용해 실제 미로를 만들어보겠습니다.
(1) test.js
import * as THREE from 'https://cdn.skypack.dev/three@0.129/build/three.module.js';
import {FBXLoader} from 'https://cdn.skypack.dev/three@0.129/examples/jsm/loaders/FBXLoader.js';
import {GLTFLoader} from 'https://cdn.skypack.dev/three@0.129/examples/jsm/loaders/GLTFLoader.js';
import {OrbitControls} from 'https://cdn.skypack.dev/three@0.129/examples/jsm/controls/OrbitControls.js';
import { PointerLockControls } from 'https://cdn.skypack.dev/three@0.129/examples/jsm/controls/PointerLockControls.js';
import {CannonDebugRenderer} from './data/CannonDebugRenderer.js';
class BasicCharacterControllerProxy {
constructor(animations) {
this._animations = animations;
}
get animations() {
return this._animations;
}
};
class FiniteStateMachine {
constructor() {
this._states = {};
this._currentState = null;
}
_AddState(name, type) {
this._states[name] = type;
}
SetState(name) {
const prevState = this._currentState;
if (prevState) {
if (prevState.Name == name) {
return;
}
prevState.Exit();
}
const state = new this._states[name](this);
this._currentState = state;
state.Enter(prevState);
}
Update(timeElapsed, input) {
if (this._currentState) {
this._currentState.Update(timeElapsed, input);
}
}
};
class CharacterFSM extends FiniteStateMachine {
constructor(proxy) {
super();
this._proxy = proxy;
this._Init();
}
_Init() {
this._AddState('idle', IdleState);
this._AddState('walk', WalkState);
this._AddState('run', RunState);
}
};
class State {
constructor(parent) {
this._parent = parent;
}
Enter() {}
Exit() {}
Update() {}
};
class WalkState extends State {
constructor(parent) {
super(parent);
}
get Name() {
return 'walk';
}
Enter(prevState) {
const curAction = this._parent._proxy._animations['walk'].action;
if (prevState) {
const prevAction = this._parent._proxy._animations[prevState.Name].action;
curAction.enabled = true;
if (prevState.Name == 'run') {
const ratio = curAction.getClip().duration / prevAction.getClip().duration;
curAction.time = prevAction.time * ratio;
} else {
curAction.time = 0.0;
curAction.setEffectiveTimeScale(1.0);
curAction.setEffectiveWeight(1.0);
}
curAction.crossFadeFrom(prevAction, 0.5, true);
curAction.play();
} else {
curAction.play();
}
}
Exit() {
}
Update(timeElapsed, input) {
if (moveForward || moveBackward || moveLeft || moveRight) {
if (moveShift) {
this._parent.SetState('run');
}
else {
this._parent.SetState('walk');
}
return;
}
this._parent.SetState('idle');
}
};
class RunState extends State {
constructor(parent) {
super(parent);
}
get Name() {
return 'run';
}
Enter(prevState) {
const curAction = this._parent._proxy._animations['run'].action;
if (prevState) {
const prevAction = this._parent._proxy._animations[prevState.Name].action;
curAction.enabled = true;
if (prevState.Name == 'walk') {
const ratio = curAction.getClip().duration / prevAction.getClip().duration;
curAction.time = prevAction.time * ratio;
} else {
curAction.time = 0.0;
curAction.setEffectiveTimeScale(1.0);
curAction.setEffectiveWeight(1.0);
}
curAction.crossFadeFrom(prevAction, 0.5, true);
curAction.play();
} else {
curAction.play();
}
}
Exit() {
}
Update(timeElapsed, input) {
if (moveForward || moveBackward || moveLeft || moveRight) {
if (!moveShift) {
this._parent.SetState('walk');
}
return;
}
this._parent.SetState('idle');
}
};
class IdleState extends State {
constructor(parent) {
super(parent);
}
get Name() {
return 'idle';
}
Enter(prevState) {
const idleAction = this._parent._proxy._animations['idle'].action;
if (prevState) {
const prevAction = this._parent._proxy._animations[prevState.Name].action;
idleAction.time = 0.0;
idleAction.enabled = true;
idleAction.setEffectiveTimeScale(1.0);
idleAction.setEffectiveWeight(1.0);
idleAction.crossFadeFrom(prevAction, 0.5, true);
idleAction.play();
} else {
idleAction.play();
}
}
Exit() {
}
Update(_, input) {
if(moveShift && (moveForward || moveBackward || moveLeft || moveRight)) {
this._parent.SetState('run');
}
else if ((moveForward || moveBackward || moveLeft || moveRight)) {
this._parent.SetState('walk');
}
}
};
class BasicCharacterController {
constructor(params) {
this._Init(params);
}
_Init(params) {
this._params = params;
this.domElement = params.domElement;
this._camera = params.camera;
this._decceleration = new THREE.Vector3(-0.0005, -0.0001, -5.0);
this._acceleration = new THREE.Vector3(1, 0.25, 50.0);
this._velocity = new THREE.Vector3(0, 0, 0);
this._position = new THREE.Vector3();
this._animations = {};
this._stateMachine = new CharacterFSM(
new BasicCharacterControllerProxy(this._animations));
this._LoadModels();
}
_LoadModels() {
const loader = new FBXLoader();
const glass_material = new THREE.MeshPhysicalMaterial({
color : 0xffffff,
metalness : .1,
roughness : 0.05,
ior : 2.5,
thickness : 0.2,
transmission : 1,
transparent: true,
opacity: .5,
reflectivity: 0.2,
refractionRatio: 0.985,
});
let glass_geometry = new THREE.BoxGeometry(75,20,1,256,256,256);
const glass_1 = new THREE.Mesh(glass_geometry, glass_material);
glass_1.position.set(-87.5,10,-86.1);
this._params.scene.add(glass_1);
this.glass_1 = glass_1;
const glass1Shape = new CANNON.Box(new CANNON.Vec3(glass_1.scale.x * 37.2, glass_1.scale.y*10, glass_1.scale.z));
const glass1Body = new CANNON.Body({
mass : 0,
position : new CANNON.Vec3(glass_1.position.x, glass_1.position.y, glass_1.position.z),
shape : glass1Shape,
material: concreteMaterial,
});
glass1Body.collisionResponse = 0.1;
glass1Body.addEventListener("collide", () => {
iscollide = true;
});
world.addBody(glass1Body);
glass_geometry = new THREE.BoxGeometry(1,20,19.2,256,256,256);
const glass_2 = new THREE.Mesh(glass_geometry, glass_material);
glass_2.position.set(-75.5 ,10,-96.1);
this._params.scene.add(glass_2);
const glass2Shape = new CANNON.Box(new CANNON.Vec3(glass_2.scale.x, glass_2.scale.y*10, glass_2.scale.z * 9.5));
const glass2Body = new CANNON.Body({
mass : 0,
position : new CANNON.Vec3(glass_2.position.x, glass_2.position.y, glass_2.position.z),
shape : glass2Shape,
material: concreteMaterial,
});
glass2Body.collisionResponse = 0.1;
glass2Body.addEventListener("collide", () => {
iscollide = true;
});
world.addBody(glass2Body);
//유리재질 벽이 필요한 만큼 생성하시면 됩니다.
loader.setPath('./resources/remy/');
loader.load('remy.fbx', (fbx) => {
fbx.scale.setScalar(0.01);
fbx.traverse(c => {
c.castShadow = true;
});
this._target = fbx;
this._params.scene.add(this._target);
this._mixer = new THREE.AnimationMixer(this._target);
this._manager = new THREE.LoadingManager();
this._manager.onLoad = () => {
this._stateMachine.SetState('idle');
};
const _OnLoad = (animName, anim) => {
const clip = anim.animations[0];
const action = this._mixer.clipAction(clip);
this._animations[animName] = {
clip: clip,
action: action,
};
};
const loader = new FBXLoader(this._manager);
loader.setPath('./resources/remy/');
loader.load('walk.fbx', (a) => { _OnLoad('walk', a); });
loader.load('run.fbx', (a) => { _OnLoad('run', a); });
loader.load('idle.fbx', (a) => { _OnLoad('idle', a); });
const instructions = document.getElementById( 'instructions' );
instructions.addEventListener( 'click', function () {
controls.lock();
} );
const _sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay));
const timer = async () => {
await _sleep(3000);
$("#loading").hide();
document.querySelector("#loading-after").style.display = 'block';
buttonEvt();
};
timer();
});
}
get Position() {
return this._position;
}
get Rotation() {
if (!this._target) {
return new THREE.Quaternion();
}
return this._target.quaternion;
}
Update(timeInSeconds) {
controlObject = this._target;
if (!this._stateMachine._currentState) {
return;
}
this._stateMachine.Update(timeInSeconds, this._input);
const _Q = new THREE.Quaternion();
const _A = new THREE.Vector3();
const _R = controlObject.quaternion.clone();
controlObject.getWorldPosition(this._camera.position);
// 1인칭시점
// this._camera.position.x = controlObject.position.x;
// this._camera.position.y = 10;
// this._camera.position.z = controlObject.position.z;
//3인칭시점
this._camera.position.x = controlObject.position.x + Math.sin(this._camera.rotation.y)*10;
this._camera.position.z = controlObject.position.z + Math.cos(this._camera.rotation.z)*10;
this._camera.position.y = 10;
if(!_R.equals(this._camera.quaternion)) {
var qm = this._camera.quaternion.clone();
qm.x = _R.x;
qm.z = _R.z;
controlObject.quaternion.slerp(qm, 1);
controlObject.rotateY(Math.PI);
}
let speed;
let cameraDirection = new THREE.Vector3();
const x = this._camera.getWorldDirection(cameraDirection).x;
const z = this._camera.getWorldDirection(cameraDirection).z;
const vector = new THREE.Vector3(x, 0, z);
const axis = new THREE.Vector3(0, 1, 0);
if(iscollide) {
speed = 0;
}
else if(!moveShift) {
speed = 0.1;
}
else {
speed = 0.2;
}
if (moveForward) {
objectBody.velocity.x = x*speed;
objectBody.velocity.z = z*speed;
objectBody.position.x += objectBody.velocity.x;
objectBody.position.z += objectBody.velocity.z;
if(moveLeft) {
controlObject.rotateY(-Math.PI/4);
}
if(moveRight) {
controlObject.rotateY(Math.PI/4);
}
}
if (moveBackward) {
let angle = Math.PI;
vector.applyAxisAngle(axis, angle);
objectBody.velocity.x = vector.x*speed;
objectBody.velocity.z = vector.z*speed;
objectBody.position.x += objectBody.velocity.x;
objectBody.position.z += objectBody.velocity.z;
controlObject.rotateY(-Math.PI);
if(moveForward) {
controlObject.rotateY(-Math.PI);
}
if(moveLeft) {
controlObject.rotateY(Math.PI/4);
controlObject.rotateY(-Math.PI);
}
if(moveRight) {
controlObject.rotateY(-Math.PI/4);
controlObject.rotateY(-Math.PI);
}
}
if (moveLeft) {
if (!moveBackward) {
let angle = Math.PI / 2;
vector.applyAxisAngle(axis, angle);
objectBody.velocity.x = vector.x*speed;
objectBody.velocity.z = vector.z*speed;
objectBody.position.x += objectBody.velocity.x;
objectBody.position.z += objectBody.velocity.z;
controlObject.rotateY(Math.PI/2);
}
else {
let angle = -Math.PI / 2;
vector.applyAxisAngle(axis, angle);
objectBody.velocity.x = vector.x*speed;
objectBody.velocity.z = vector.z*speed;
objectBody.position.x += objectBody.velocity.x;
objectBody.position.z += objectBody.velocity.z;
controlObject.rotateY(Math.PI/2);
}
}
if (moveRight) {
if (!moveBackward) {
let angle = -Math.PI / 2;
vector.applyAxisAngle(axis, angle);
objectBody.velocity.x = vector.x*speed;
objectBody.velocity.z = vector.z*speed;
objectBody.position.x += objectBody.velocity.x;
objectBody.position.z += objectBody.velocity.z;
controlObject.rotateY(-Math.PI/2);
}
else {
let angle = Math.PI / 2;
vector.applyAxisAngle(axis, angle);
objectBody.velocity.x = vector.x*speed;
objectBody.velocity.z = vector.z*speed;
objectBody.position.x += objectBody.velocity.x;
objectBody.position.z += objectBody.velocity.z;
controlObject.rotateY(-Math.PI/2);
}
}
controlObject.position.x = objectBody.position.x;
controlObject.position.y = objectBody.position.y - 1;
controlObject.position.z = objectBody.position.z;
if (this._mixer) {
this._mixer.update(timeInSeconds);
}
if(light) {
const smallSphere = controlObject.children[0];
smallSphere.getWorldPosition(light.position);
light.position.x = controlObject.position.x + Math.sin(this._camera.rotation.y)*5;
light.position.y = 5;
light.position.z = controlObject.position.z + Math.cos(this._camera.rotation.z)*5;
// if(LightHelper) {
// LightHelper.update();
// }
if(light.target) {
light.target.position.set(x*500, this._camera.getWorldDirection(cameraDirection).y*500, z*500);
}
}
}
};
let camera, scene, renderer, controls;
const objects = [];
let raycaster;
let moveForward = false;
let moveBackward = false;
let moveLeft = false;
let moveRight = false;
let moveShift = false;
let iscollide = false;
let new_controls;
let previousRAF = null;
let mixers = [];
let oldElapsedTime = 0;
let world;
let wall1Body;
let wall2Body;
let wall3Body;
let wall4Body;
let wall5Body;
let concreteMaterial;
let plasticMaterial;
let CannonDebugRenderer_1;
let objectShape;
let objectBody;
let controlObject;
let glass_1;
let light;
let LightHelper;
let time = 0;
let starFlag = true;
let timer;
let th;
let tm;
let ts;
let prevTime = performance.now();
const velocity = new THREE.Vector3();
const direction = new THREE.Vector3();
const vertex = new THREE.Vector3();
const color = new THREE.Color();
init();
function init() {
// 카메라 설정
const fov = 60;
const aspect = 1920 / 1080;
const near = 1.0;
const far = 100.0;
camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(25, 10, 25);
scene = new THREE.Scene();
scene.fog = new THREE.Fog(0x000000, 0, 100);
//라이트 설정
light = new THREE.SpotLight(0xFFFFFF, 2);
light.position.set(0, 100, 0);
light.target.position.set(0,0,0);
light.angle = THREE.Math.degToRad(50);
light.penumbra = 1;
scene.add(light.target);
scene.add(light);
// LightHelper = new THREE.PointLightHelper(light);
// scene.add(LightHelper);
//click 화면
controls = new PointerLockControls( camera, document.body );
const blocker = document.getElementById( 'blocker' );
controls.addEventListener( 'lock', function () {
instructions.style.display = 'none';
blocker.style.display = 'none';
} );
controls.addEventListener( 'unlock', function () {
blocker.style.display = 'block';
instructions.style.display = '';
} );
scene.add( controls.getObject() );
function onKeyDown ( event ) {
if(!iscollide) {
switch ( event.keyCode ) {
case 16 :
moveShift = true;
break;
case 87 :
moveForward = true;
break;
case 65 :
moveLeft = true;
break;
case 83 :
moveBackward = true;
break;
case 68 :
moveRight = true;
break;
}
}
};
function onKeyUp ( event ) {
switch ( event.keyCode ) {
case 16 :
moveShift = false;
break;
case 87:
moveForward = false;
iscollide = false;
break;
case 65:
moveLeft = false;
iscollide = false;
break;
case 83:
moveBackward = false;
iscollide = false;
break;
case 68:
moveRight = false;
iscollide = false;
break;
}
};
document.addEventListener( 'keydown', (e) => onKeyDown(e), false );
document.addEventListener( 'keyup', (e) => onKeyUp(e), false );
raycaster = new THREE.Raycaster( new THREE.Vector3(), new THREE.Vector3( 0, - 1, 0 ), 0, 10 );
concreteMaterial = new CANNON.Material('concrete');
plasticMaterial = new CANNON.Material('plastic');
const concretePlasticContactMaterial = new CANNON.ContactMaterial(
concreteMaterial,
plasticMaterial,
{
friction: 0.1,
restitution: 0.7
}
);
world = new CANNON.World();
world.gravity.set(0, -9.82, 0);
world.addContactMaterial(concretePlasticContactMaterial);
//도착지점
let light_2 = new THREE.SpotLight(0xFF0000, 100);
light_2.position.set(115, 1, 115);
light_2.target.position.set(115, 0, 115);
const lightShape = new CANNON.Box(new CANNON.Vec3(1, 1, 1));
const lightBody = new CANNON.Body({
mass : 0,
position : new CANNON.Vec3(light_2.position.x, light_2.position.y, light_2.position.z),
shape : lightShape,
material: concreteMaterial,
});
lightBody.collisionResponse = 0;
lightBody.addEventListener("collide", () => {
if(time != 0){
clearInterval(timer);
}
$("div").hide();
const text_location = document.createElement('div');
text_location.style.position = 'absolute';
//text2.style.zIndex = 1; // if you still don't see the label, try uncommenting this
text_location.style.width = 100;
text_location.style.height = 100;
text_location.style.backgroundColor = "black";
text_location.style.color = "white";
text_location.style.fontSize = 50 + 'px';
text_location.innerHTML = "클리어시간 " + th + ":" + tm + ":" + ts;
text_location.style.top = 800 + 'px';
text_location.style.left = 600 + 'px';
document.body.appendChild(text_location);
});
world.addBody(lightBody);
scene.add(light_2.target);
scene.add(light_2);
// floor
const plane = new THREE.Mesh(
new THREE.PlaneGeometry(250, 250, 10, 10),
new THREE.MeshStandardMaterial({
color: 0x000000,
}));
plane.castShadow = false;
plane.receiveShadow = true;
plane.rotation.x = -Math.PI / 2;
scene.add(plane);
// CannonDebugRenderer_1 = new CannonDebugRenderer(scene, world);
const floorShape = new CANNON.Box(new CANNON.Vec3(plane.scale.x*125, 0.1, plane.scale.z*125));
const floorBody = new CANNON.Body();
floorBody.mass = 0;
floorBody.addShape(floorShape);
floorBody.material = plasticMaterial;
world.addBody(floorBody);
const wall5 = new THREE.Mesh(
new THREE.BoxGeometry(250, 0.1, 250), //x가로, y높이, z길이?
new THREE.MeshStandardMaterial({
color: 0x000000
}));
wall5.position.set(0, 20, 0); // 벽 위치
wall5.castShadow = false;
wall5.receiveShadow = true;
scene.add(wall5);
objectShape = new CANNON.Box(new CANNON.Vec3(1.5, 1, 1.5));
objectBody = new CANNON.Body({
mass : 0.1,
position : new CANNON.Vec3(-115, 1, -115),
shape : objectShape,
material: plasticMaterial,
});
world.addBody(objectBody);
//background
scene.background = new THREE.Color( 0x000000 );
//벽1
const wall1 = new THREE.Mesh(
new THREE.BoxGeometry(10, 20, 250), //x가로, y높이, z길이?
new THREE.MeshStandardMaterial({
color: 0x000000
}));
wall1.position.set(-125, 10, 0); // 벽 위치
wall1.castShadow = true;
wall1.receiveShadow = true;
scene.add(wall1);
const wall1Shape = new CANNON.Box(new CANNON.Vec3(wall1.scale.x * 5, wall1.scale.y*10, wall1.scale.z*125));
wall1Body = new CANNON.Body({
mass : 0,
position : new CANNON.Vec3(wall1.position.x, wall1.position.y, wall1.position.z),
shape : wall1Shape,
material: concreteMaterial,
});
wall1Body.collisionResponse = 0.1;
wall1Body.addEventListener("collide", () => {
iscollide = true;
})
world.addBody(wall1Body);
//벽2
const wall2 = new THREE.Mesh(
new THREE.BoxGeometry(250, 20, 10), //x가로, y높이, z길이?
new THREE.MeshStandardMaterial({
color: 0x000000
}));
wall2.position.set(0, 10, -125); // 벽 위치
wall2.castShadow = true;
wall2.receiveShadow = true;
scene.add(wall2);
const wall2Shape = new CANNON.Box(new CANNON.Vec3(wall2.scale.x * 125, wall2.scale.y*10, wall2.scale.z * 5));
wall2Body = new CANNON.Body({
mass : 0,
position : new CANNON.Vec3(wall2.position.x, wall2.position.y, wall2.position.z),
shape : wall2Shape,
material: concreteMaterial,
});
wall2Body.collisionResponse = 0.1;
wall2Body.addEventListener("collide", () => {
iscollide = true;
})
world.addBody(wall2Body);
//벽3 세워봄
const wall3 = new THREE.Mesh(
new THREE.BoxGeometry(10, 20, 250), //x가로, y높이, z길이?
new THREE.MeshStandardMaterial({
color: 0x000000
}));
wall3.position.set(125, 10, 0); // 벽 위치
wall3.castShadow = true;
wall3.receiveShadow = true;
scene.add(wall3);
const wall3Shape = new CANNON.Box(new CANNON.Vec3(wall3.scale.x * 5, wall3.scale.y*10, wall3.scale.z*125));
wall3Body = new CANNON.Body({
mass : 0,
position : new CANNON.Vec3(wall3.position.x, wall3.position.y, wall3.position.z),
shape : wall3Shape,
material: concreteMaterial,
});
wall3Body.collisionResponse = 0.1;
wall3Body.addEventListener("collide", () => {
iscollide = true;
})
world.addBody(wall3Body);
//벽4 세워봄
const wall4 = new THREE.Mesh(
new THREE.BoxGeometry(250, 20, 10), //x가로, y높이, z길이?
new THREE.MeshStandardMaterial({
color: 0x000000
}));
wall4.position.set(0, 10, 125); // 벽 위치
wall4.castShadow = true;
wall4.receiveShadow = true;
scene.add(wall4);
const wall4Shape = new CANNON.Box(new CANNON.Vec3(wall4.scale.x * 125, wall4.scale.y*10, wall4.scale.z * 5));
wall4Body = new CANNON.Body({
mass : 0,
position : new CANNON.Vec3(wall4.position.x, wall4.position.y, wall4.position.z),
shape : wall4Shape,
material: concreteMaterial,
});
wall4Body.collisionResponse = 0.1;
wall4Body.addEventListener("collide", () => {
iscollide = true;
})
world.addBody(wall4Body);
renderer = new THREE.WebGLRenderer( { alpha : true, antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setClearColor( 0xffffff, 0);
document.body.appendChild( renderer.domElement );
//
window.addEventListener( 'resize', onWindowResize );
const params = {
camera: camera,
scene: scene,
domElement : renderer.domElement
}
new_controls = new BasicCharacterController(params);
RAF();
}
function buttonEvt(){
let hour = 0;
let min = 0;
let sec = 0;
if(starFlag){
starFlag = false;
timer = setInterval(function(){
time++;
min = Math.floor(time/60);
hour = Math.floor(min/60);
sec = time%60;
min = min%60;
th = hour;
tm = min;
ts = sec;
if(th<10){
th = "0" + hour;
}
if(tm < 10){
tm = "0" + min;
}
if(ts < 10){
ts = "0" + sec;
}
const text_location = document.createElement('div');
text_location.style.position = 'absolute';
text_location.style.width = 100;
text_location.style.height = 100;
text_location.style.backgroundColor = "black";
text_location.style.color = "white";
text_location.style.fontSize = 20 + 'px';
text_location.innerHTML = "경과시간 " + th + ":" + tm + ":" + ts;
text_location.style.top = 50 + 'px';
text_location.style.left = 50 + 'px';
document.body.appendChild(text_location);
}, 1000);
}
};
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function RAF() {
setTimeout( function() {
requestAnimationFrame((t) => {
if (previousRAF === null) {
previousRAF = t;
}
RAF();
renderer.render(scene, camera);
if (controls.isLocked === true) {
Step(t - previousRAF);
previousRAF = t;
};
// CannonDebugRenderer_1.update();
}, 1000 / 50 );
});
}
function Step(timeElapsed) {
const timeElapsedS = timeElapsed * 0.001;
if (mixers) {
mixers.map(m => m.update(timeElapsedS));
}
if (new_controls) {
new_controls.Update(timeElapsedS);
}
const deltaTime = timeElapsed - oldElapsedTime;
oldElapsedTime = timeElapsed;
world.step(1 / 60, deltaTime, 3);
}
위의 코드는 실제 프로젝트에서 일부 발췌한 것으로 실제 사용시 일부 수정해서 사용하시면 됩니다. 앞서 알아보았던 THREE.js에서의 코드를 이용해 캐릭터의 움직임과 카메라의 움직임을 구현하고 미로맵을 구현하기 위해 앞서 알아보았던 유리재질의 물질을 추가하고 광원의 위치가 캐릭터의 움직임을 따라가는 방법을 이용해서 미로맵을 구현하였습니다.
추가적으로 물질 간 충돌을 정의하고 collisionResponse를 0 이상으로 할당하고 캐릭터 물체에 mass를 0 이상을 부여했는데도 충돌 시 튕겨나가면서 충돌이 구현되는 방식이라 지속적으로 시도 시 벽을 뚫는 현상이 발견되어 iscollide 변수를 별도로 선언하고 각 벽에 addEventListener("collide")를 통해 충돌 시 iscollide 변수를 true로 설정하고 캐릭터의 속도와 방향을 지정하는 Update 함수에서 iscollide 변수가 true일 때 속도를 0으로 지정하여 더 이상 뚫고 지나가지 못하도록 구현하였습니다. 이후 keyUp 실행 시 iscollide 변수를 다시 false로 설정하여 해당 키를 뗐을 때 정상적으로 다른 곳으로 이동이 가능하도록 구현하였습니다.
또한 WASD, SHIFT를 인식하는 keyDown & keyUp 메서드가 중복해서 존재한 부분을 제거하였고, 로딩에 시간이 꽤 걸리기 때문에 별도로 로딩이 종료될 때까지 Loading이 화면에 출력되고 모든 로딩이 끝나면(fbx 파일 로딩이 가장 오래 걸려서 fbx 파일 로딩 후반부에 Loading이 제거될 수 있도록 배치하였고 이 또한 어느정도 로딩이 다 완료되지 않아도 실행되기에 강제로 sleep함수를 정의하여 3초정도 늦게 실행되도록 구현하였습니다.) Loading을 제거하고 Click to Play로 바뀌게 구현하였고 이 때 클릭하시면 미로맵을 체험할 수 있습니다.
마지막으로 최적화를 위해 원래 존재했던 animate() 함수를 제거하고(RAF함수와 animate함수에서 중복으로 rendering이 발생하고 있었으므로 중복제거) 끊김을 방지하기 위해 RAF함수에 setTimeout을 이용해 실행시간을 제어하여 최적화하였습니다. 이후 animate에서 카메라의 y위치를 지정하고 있었던 부분이 있어서 이를 update함수에 this._camera.position.y를 10으로 고정시켜서 카메라의 위치를 보기좋게 구현하였습니다.
이번 게시글에서는 앞서 배웠던 여러 코드를 조합하여 미로맵을 실제로 구현하는 방법에 대해 알아보았습니다.
'Technology[Front]' 카테고리의 다른 글
React 게시글을 react-table을 이용해 정렬, 페이징처리하기 (1) | 2022.03.23 |
---|---|
React-Redux 어디서든 접근가능한 저장소 사용하기 (1) | 2022.03.10 |
3D맵 구현하기 - light 제어하기 (1) | 2022.02.28 |
3D맵 구현하기 - 유리재질 벽 구현하기 (1) | 2022.02.25 |
3D맵 구현하기 - cannon.js로 물리엔진 구현하기(2) (1) | 2022.02.23 |