Three.js의 1인칭 캐릭터 컨트롤러

저는 최근에 Three.js 웹 그래픽 라이브러리와 함께 사용할 1인칭 캐릭터를 제어할 수 있는 package을 만들었습니다. 실제 Three.js 장면의 컨텍스트에서 사용하는 방법을 시연하는 것이 도움이 될 것이라고 생각했기 때문에 패키지에 익숙해지도록 기본 데모 장면을 만들 것입니다.

이 작업이 끝나면 걷고, 보고, 뛰어다닐 수 있는 작은 장면이 생깁니다. 시작하겠습니다!

먼저 개발 환경을 설정해야 합니다. 나는 이 가이드에 Parcel 번들러를 사용할 것입니다. 왜냐하면 그것은 좆같고 구성이 필요하지 않기 때문입니다.

설치합시다.

npm install --save-dev parcel


다음으로 charactercontroller 패키지를 설치해야 합니다.

npm install charactercontroller


모든 패키지가 설치되면 소포 개발 서버를 시작할 수 있습니다. 이것은 페이지를 볼 수 있는 localhost의 포트를 제공할 뿐만 아니라 코드를 변경할 때 페이지를 자동으로 다시 로드합니다.

npx parcel index.html



이제 시작할 수 있습니다.

먼저 스크립트 태그의 유형을 module 로 설정했는지 확인하세요. 이는 동적 가져오기에 필요합니다(예: import { foo } from "bar" ).

캔버스 주변의 테두리를 제거하려면 여백을 0으로 설정하고 전체 너비로 만들 수 있습니다. 여기 내 index.html가 있습니다.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width">
        <title>CharacterController</title>
        <style>
            html, body, canvas {
                margin: 0;
                height: 100%;
                width: 100%;
            }
        </style>
    </head>

    <body>
        <script type="module" src="script.js"></script>
    </body>
</html>


기본 Three.js 장면을 초기화해 보겠습니다.

This guide isn't intended to teach you about the very basics of Three.js; if you don't know what the below code does, then go and learn it.



import * as THREE from "three";

const scene = new THREE.Scene();

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

function animate() {
    requestAnimationFrame(animate);

    renderer.render(scene, /* We need a camera! */);
};
animate();


카메라를 추가하지 않은 방법에 주목하십시오. 이는 캐릭터 컨트롤러에 장면을 렌더링하는 데 사용되는 카메라가 포함되기 때문입니다. 이제 캐릭터를 추가해봅시다.

const controller = new CharacterController(scene, {});
scene.add(controller);

// ...

    renderer.render(scene, controller.camera);


여기서는 캐릭터 컨트롤러를 가져온 다음 초기화합니다. 인수로 장면과 설정 및 구성을 포함하는 개체를 모두 사용합니다. 지금은 해당 설정 개체를 비워 두겠습니다. 기본값을 사용합니다. 그런 다음 장면에 캐릭터를 추가할 수 있습니다.
이제 카메라( controller.camera )가 있으므로 카메라를 사용하여 animate 메서드에서도 장면을 렌더링할 수 있습니다.

캐릭터가 준비되기 전에 해야 할 일이 하나 더 있습니다.
매 프레임마다 플레이어를 움직이거나 카메라를 회전시키는 것과 같이 프레임을 렌더링할 준비가 된 새로운 계산을 수행할 시간임을 컨트롤러에 알려야 합니다.
CharacterController 패키지는 이것을 오줌 싸게 만듭니다. 업데이트 방법에서 controller.update()를 호출하기만 하면 됩니다.

/// ...

function animate() {
    requestAnimationFrame(animate);

    controller.update();

    renderer.render(scene, controller.camera);
};
animate();


굉장한 물건. 기준 틀이 없기 때문에 지금 당장은 볼 수 없지만 우리의 캐릭터는 실제로 아래로 추락하고 있으며 영원히 그럴 것입니다. 불쌍한 잔디가 설 수 있는 바닥을 추가하지 않았기 때문입니다.
문제를 해결해 보겠습니다.

// ...

const floorMaterial = new THREE.MeshBasicMaterial({ color: 0x00FF00 });
const floorGeometry = new THREE.PlaneGeometry(10, 10);
const floorMesh = new THREE.Mesh(floorGeometry, floorMaterial);
scene.add(floorMesh);

function animate() {
// ..


점프하기 전에는 방금 만든 바닥을 실제로 볼 수 없다는 것을 알 수 있습니다. 스폰할 때 z 레벨 0에서 스폰되기 때문에 실제로 바닥과 교차하지만 바닥도 마찬가지입니다. 땅이 아닌 땅에서 산란하도록 합시다.
나는 캐릭터를 z 레벨 1에 배치하고 있습니다. 그것이 플레이어의 높이이기 때문입니다(기본floorDistance 값은 1).

const controller = new CharacterController(scene, {});
controller.player.position.z = 1;
scene.add(controller.player);


You want to know what's actually happening? You've come to the right fella.
When the character controller is initialised, a Three.js Group is created, which the camera is then added to. Neither the Group or the camera have any inherent volume or height to them, but the player's apparent height is actually created by an invisible ray being sent out pointing down from the Group's location. That ray is floorDistance long (an option you can pass into the character controller options object), and if the ray intersects with an object in the scene, the character stops falling. You can change the character's "height" by adjusting this value, because the character will stop being pulled down and "stand" closer/further away from the ground. Neat stuff, I know.
When the character spawns inside the floor plane, you can't see it because the camera is perfectly aligned with the infinitely thin floor. The camera doesn't see any geometry, so doesn't render pixels to the screen. The player doesn't fall down as the ray is still technically intersecting with the floor, even if it is only 0 units away.



마무리하기 위해 몇 가지 삶의 질 변화를 추가할 수 있습니다. 먼저 창의 크기가 조정될 때 렌더러의 크기를 조정합니다. 그런 다음 플레이어가 마우스를 움직일 때 포인터를 제자리에 고정합니다.

이벤트 리스너에서 이 두 가지를 모두 구현할 것입니다. 저는 스크립트의 맨 아래에 이벤트 리스너를 배치하여 정리된 상태를 유지하는 것을 좋아하지만 이벤트 리스너 내에서 참조되는 값이 초기화되고 선택한 지점의 범위 내에 있는 한 원하는 위치에 배치할 수 있습니다.

다음은 포인터 잠금입니다.

document.addEventListener("click", () => {
    const canvas = renderer.domElement;
    canvas.requestPointerLock =
        canvas.requestPointerLock ||
        canvas.mozRequestPointerLock;
    canvas.requestPointerLock()
});


캔버스 크기 조정:

window.addEventListener("resize", () => {
    controller.camera.aspect = window.innerWidth / window.innerHeight;
    controller.camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
}, false);


그게 지금 작동하는 모든 것입니다. 장면에서 움직이는 CharacterController 캐릭터가 있는 최소한의 Three.js 장면이 있습니다! 가서 멋진 똥을 만드십시오. 나는 사람들이 이것으로 무엇을 만들 것인지에 관심이 있으므로 반쯤 괜찮은 것을 만들면 DM을 보내주세요.

다음은 참조용 전체 코드입니다.

import * as THREE from "three";
import CharacterController from "charactercontroller";

const scene = new THREE.Scene();
const controller = new CharacterController(scene, {});
scene.add(controller);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const floorMaterial = new THREE.MeshBasicMaterial({ color: 0x00FF00 });
const floorGeometry = new THREE.PlaneGeometry(10, 10);
const floorMesh = new THREE.Mesh(floorGeometry, floorMaterial);
scene.add(floorMesh);

function animate() {
    requestAnimationFrame(animate);

    controller.update();

    renderer.render(scene, controller.camera);
};
animate();

document.addEventListener("click", () => {
    const canvas = renderer.domElement;
    canvas.requestPointerLock =
        canvas.requestPointerLock ||
        canvas.mozRequestPointerLock;
    canvas.requestPointerLock()
});

window.addEventListener("resize", () => {
    controller.camera.aspect = window.innerWidth / window.innerHeight;
    controller.camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
}, false);

좋은 웹페이지 즐겨찾기