지난 게시글에서는 바빌론JS 의 중력에 대해 알아보았는데요.
https://itadventure.tistory.com/709
블렌더3D에서 제작한 모델 파일도 이렇게 순순히 잘 되면 좋으련만 그게 한번에 쉽게 안되는데요.
해외 게시글을 찾아보니 오른손/왼손 좌표계 차이 문제라고도, 바빌론JS의 버그 때문일거라고도 하더군요.
그래서 최소한의 간단한 해결책을 알아냈습니다 :)
블렌더로 준비한 그라운드
평범한 바닥 대신 블랜더로 아래와 같이 바닥을 모델링하였습니다.
해당 파일은 GLB 파일로 저장하였는데요. 아래 파일을 다운받아 주시면 됩니다.
뒤집히는 문제?!
바빌론js에서 아래코드로 위 모델 파일을 불러오면, 이런 일이 발생합니다.
BABYLON.SceneLoader.ImportMeshAsync("", "./", "ground.glb")
.then((result) => {
var mesh = result.meshes[0];
mesh.scaling = new BABYLON.Vector3(1, 1, 1);
} );
뭔가 이상한 점을 발견하셨나요? :
:
:
물체들의 방향이 뒤집혀진 것이지요!
중간에 비스듬한 산 모양이 위쪽으로 가버렸습니다.
이 상태에서는 컬리젼도 제대로 작동하지 않습니다.
헤결 방법이 없을까요?
Z축 스케일을 뒤집어 !
이 문제는 근본적으로 Z축 스케일 방향을 -1 을 곱해주어야 하는데요.
결론은 아래 코드를 사용해야 합니다.
BABYLON.SceneLoader.ImportMeshAsync("", "./", "ground.glb")
.then((result) => {
var mesh = result.meshes[0];
mesh.scaling = new BABYLON.Vector3(1, 1, -1);
} );
만약 크기를 2배로 키우고 싶다면 코드도 아래와 같이 Z값으로 -2를 주어야 하는 것이지요.
mesh.scaling = new BABYLON.Vector3(2, 2, -2);
재미있는 것은 mesh.scaling 코드를 삭제해도 정상 작동합니다.
아마도 기본 스케일 값이 ( 1, 1, 1) 이 아니라 ( 1, 1, -1 ) 이기 떄문일 겁니다.
BABYLON.SceneLoader.ImportMeshAsync("", "./", "ground.glb")
.then((result) => {
} );
컬리젼 적용하기
불러온 모델 파일은 여러개의 3차원 도형으로 구성되어 있을수 있습니다.
본 바닥 모델 파일은 단 하나이긴 하지만, 모든 도형에 각각 컬리젼을 적용해야 합니다.
getChildrenMeshes()를 사용하면 자식 메시들 각각에 컬리젼을 적용할 수 있습니다.
BABYLON.SceneLoader.ImportMeshAsync("", "./", "ground.glb")
.then((result) => {
var mesh = result.meshes[0];
mesh.scaling = new BABYLON.Vector3(1, 1, -1);
const childMeshes = mesh.getChildMeshes();
for (let mesh of childMeshes) {
mesh.checkCollisions = true;
}
} );
main.js 소스
main.js 소스는 아래와 같습니다.
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas,true);
// 입력 시스템
const deviceInputSystem = new BABYLON.DeviceSourceManager(engine);
// 마우스
const mouseDeviceSource = deviceInputSystem.getDeviceSource(BABYLON.DeviceType.Mouse);
let mesh_npc = null;
let currentCamera = null;
let arc_camera;
let inputMap = {};
let walk_anim = null;
var createScene = function () {
var scene = new BABYLON.Scene(engine);
scene.gravity = new BABYLON.Vector3(0, -0.9, 0);
scene.collisionsEnabled = true;
// 키보드 이벤트
scene.actionManager = new BABYLON.ActionManager(scene);
scene.actionManager.registerAction(
new BABYLON.ExecuteCodeAction(
BABYLON.ActionManager.OnKeyDownTrigger,
function (evt) {
inputMap[evt.sourceEvent.key] = evt.sourceEvent.type == "keydown";
}));
scene.actionManager.registerAction(
new BABYLON.ExecuteCodeAction(
BABYLON.ActionManager.OnKeyUpTrigger,
function (evt) {
inputMap[evt.sourceEvent.key] = evt.sourceEvent.type == "keydown";
}));
arc_camera = new BABYLON.ArcRotateCamera(
"ArcCam",
BABYLON.Tools.ToRadians(90),
BABYLON.Tools.ToRadians(65),
2, // 카메라 거리
BABYLON.Vector3.Zero(),
scene
);
ArcCam(scene, null);
// 빛
var light = new BABYLON.HemisphericLight(
"light",
new BABYLON.Vector3(0, 1, 0),
scene
);
// 빛강도
light.intensity = 1.5;
BABYLON.SceneLoader.ImportMeshAsync("", "./", "ground.glb")
.then((result) => {
var mesh = result.meshes[0];
mesh.scaling = new BABYLON.Vector3(1, 1, -1);
const childMeshes = mesh.getChildMeshes();
for (let mesh of childMeshes) {
mesh.checkCollisions = true;
}
} );
BABYLON.SceneLoader.ImportMeshAsync("", "./", "character01.glb")
.then((result) => npc_callback(scene, result) );
// 씬 리턴
return scene;
};
function npc_callback(scene, result){
var mesh = result.meshes[0];
mesh_npc = mesh;
mesh.scaling = new BABYLON.Vector3(0.08, 0.08, 0.08);
mesh.position.y = 0.55;
mesh.position.y = 2.55;
ArcCam(scene, mesh_npc);
// 컬리전
mesh.checkCollisions = true;
mesh.ellipsoid = new BABYLON.Vector3(0.15, 0.5, 0.15); // 컬리전 크기
let step = 0.005;
let p = 0;
// 랜더 프레임별 실행 코드
scene.onBeforeRenderObservable.add(() => {
walk_anim = scene.getAnimationGroupByName("walk");
let forward_speed = 0.02;
let backward_speed = -0.01;
let turnleft_speed = -Math.PI/90;
let turnright_speed = Math.PI/90;
let keydown = false;
let vec;
if (inputMap["w"] || inputMap["W"]) {
vec = mesh.forward.scaleInPlace(forward_speed);
mesh.moveWithCollisions(vec);
keydown = true;
walk_anim.play(true);
}
if (inputMap["s"] | inputMap["S"]) {
mesh.moveWithCollisions(mesh.forward.scaleInPlace(backward_speed));
keydown = true;
walk_anim.play(true);
}
if (inputMap["a"] || inputMap["A"])
mesh.rotate(BABYLON.Axis.Y, turnleft_speed);
if (inputMap["d"] || inputMap["D"])
mesh.rotate(BABYLON.Axis.Y, turnright_speed);
if(keydown == false)
walk_anim.stop();
mesh.moveWithCollisions(new BABYLON.Vector3(0,-0.1,0.0));
});
}
function ArcCam(scene, mesh){
if(mesh!=null)arc_camera.lockedTarget = mesh;
// 대상과의 거리
arc_camera.radius = 3;
arc_camera.wheelDeltaPercentage = 0.01; // 마우스휠 속도
arc_camera.minZ = 0; // 근거리 커팅 X
camToggle(scene, arc_camera);
}
function camToggle(scene, camera){
// 카메라 캔버스 연결
if(currentCamera != camera)
{
if(currentCamera!=null) currentCamera.detachControl(canvas);
camera.attachControl(canvas, true);
currentCamera = camera;
scene.activeCamera = camera;
}
}
const scene = createScene();
engine.runRenderLoop(function () {
scene.render();
});
window.addEventListener("resize", function () {
engine.resize();
});
index.html 과 character01.glb 는 지난번과 동일하고
오늘 다운받은 ground.glb 파일까지 하면 폴더 내 파일 구성은 아래와 같습니다.
결과물 구경
index.html 페이지를 띄우면 케이군이 떠오르는 연출은 동일합니다.
케이군이 앞으로 이동하다 낭떠러지에서 뚝 떨어지는 것도 동일한데요.
경사진 곳으로 케이군을 이동해서 올라가면, 와우! 마치 계단을 오르듯 경사진 곳을 올라갈 수 있습니다.
컬리전의 묘미이지요.
아직 바닥의 끝 부분으로 전진하면 낭떠러지로 한없이 떨어지는 문제가 있긴 한데요.
이건 나중에 벽을 만들어 막아 버려야 겠습니다 :)
위 결과물은 크레이의 웹사이트에서도 확인하실 수 있습니다. ( 캐릭터 이동은 아직 PC 전용 )
http://dreamplan7.cafe24.com/babylon/ex22/
마무~리
컬리전의 매력이 느껴지셨나 모르겠습니다 :)
오늘도 방문해주신 모든 분들께 감사합니다 !
'자바스크립트와 캔버스' 카테고리의 다른 글
바빌론JS - 중력과 컬리젼 (2) | 2024.09.03 |
---|---|
바빌론JS - 타입 스크립트 + 웹팩 (0) | 2024.08.03 |
바빌론JS - 모바일웹에서 캐릭터 이동 (6) | 2024.08.01 |
바빌론JS - GUI 에디터! (0) | 2024.07.31 |
바빌론JS - 비를 피하고 싶어! (0) | 2024.07.29 |