본문 바로가기
프로그래밍/게임 개발

게임 개발 「 추천 편」Threejs로 간단한 레이싱 게임 만들기

by grapedoukan 2024. 1. 18.
728x90
 

어느 날 도로에서 자동차가 빠르게 지나가는 것을 보고 레이싱 게임을 만들어야겠다고 생각했습니다.

네이티브를 사용하는 대신 threejs를 사용했습니다. 어쨌든 더 큰 3D 프로젝트의 경우 네이티브를 계속 사용하면 스스로 문제를 일으킬 것입니다 ...

이 문서에서는 이 게임의 개발 과정을 0에서 1까지 설명합니다. webgl과 threejs에 대한 특별한 소개는 없습니다. 기초가 없는 학생은 threejs 문서와 함께 읽거나, webgl의 기초 지식을 먼저 배울 수 있습니다~

방법은 다음과 같습니다.
w, 앞으로
a, d는 좌우
공간을 회전하고 속도가 느려지고 표류할 수 있습니다.

현재 게임의 충돌 감지는 완료되지 않았으며(향후 업데이트 및 개선 예정) 차량의 왼쪽과 트랙의 양쪽만 충돌 테스트를 거칩니다. 자세한 내용은 아래에 언급됩니다 ~ 직접 시도하여 양면이 무엇인지 알 수도 있습니다.

다음으로 이 레이싱 게임을 0에서 1~까지 구현하겠습니다.

 

1. 경기 준비

우선, 우리는 어떤 게임을 만들지 선택해야 합니다. 회사 단위의 게임 프로젝트라면 기본적으로 개발의 선택지가 없습니다. 혼자서 연습하면 자신의 취향에 따라 할 수 있습니다. 레이싱을 선택한 이유는 다음과 같습니다.

우선, 레이싱 게임은 비교적 간단하고 너무 많은 재료가 필요하지 않기 때문입니다. 어쨌든 그것은 개인의 개발이며 모델을 제공하는 전문 디자인은 없습니다. 모델을 직접 찾아야 합니다.

둘째, 레이싱 게임의 비용은 단순하고 폐쇄 루프입니다. 자동차와 트랙이 있는 이 게임은 실제로 가장 간단한 게임입니다.

그래서 우리는 마침내 간단한 레이싱 게임을 만들기로 결정했습니다. 다음으로 재료를 찾아야 합니다.

 

2. 재료 준비

오랫동안 인터넷으로 검색하다가 좋은 자동차 obj 파일을 찾았습니다. 텍스처 등이 있지만 일부 색상은 아직 추가되지 않았습니다. 나는 그것을 완성하기 위해 블렌더를 사용했다.

이제 자동차 재료가 있으므로 다음 단계는 트랙입니다. 트랙에 대한 초기 아이디어는 이전 미로 게임과 유사하게 동적으로 생성하는 것이었습니다.

공식적인 레이싱 게임은 트랙을 사용자 정의해야 하고 질감이 있는 풍경 등과 같은 많은 세부 사항이 있어야 하기 때문에 확실히 동적으로 생성할 수 없습니다.

우리의 연습 프로젝트는 그렇게 멋질 수 없으므로 동적 생성을 고려할 수 있습니다.

동적 생성의 장점은 새로 고칠 때마다 새로운 맵을 플레이할 수 있다는 것인데, 이는 더 신선할 수 있습니다.

동적 생성에는 두 가지 방법도 있습니다. 하나는 보드를 사용하여 연속적으로 타일링하는 것인데, 보드의 꼭짓점 정보는 [-1,0,1, 1,0,1, 1,0,-1, -1,0,-1]입니다
.

평면도에서 보면 다음과 같습니다.

그러나 이것은 곡선이 너무 거칠고 각 곡선이 직각으로 있어 그다지 보기 좋지 않다는 매우 나쁜 점이 있습니다. 계획을
변경하기만 하면 Obj는 그림과 같이 직선 도로와 회전의 두 가지 모델을 구축합니다.

그런 다음 이 두 모델은 지속적으로 바둑판식으로 배열됩니다.
2D에서는 다음과 같습니다.

가능할 것 같지만! 실제 구현 후에도 여전히 좋지 않다는 것을 알았습니다!

우선, y축이 고정되어 있고 오르막이나 내리막의 개념이 없기 때문에 트랙에서 되돌릴 수 없습니다. 트랙이 회전하고 새 도로가 기존 도로와 만나면 혼란스러워지고 갈림길이 됩니다.

둘째, 임의성에 대해 많은 제어가 이루어져야 하며, 그렇지 않으면 그림과 같이 코너가 너무 빈번할 수 있습니다.

잠깐 궁합이 좋았던 것을 깨달았는데, 엄청 엉망이 되어 버렸기 때문에, 그림과 같이 스스로 트랙 모델을 만들어, 혼자서 충분한 음식과 의복을 갖추기로 했다.

다시 한 번 말씀드리지만, 블렌더는 매우 유용합니다~

여기 트랙을 설계할 때 설계하기 너무 어려운 코너가 있습니다. 속도를 늦추지 않고 모퉁이를 돌 수 없습니다 ... 한 바퀴 해보면 어느 코너인지 확실히 알 수 있을 거라고 믿어요~

 

3. 쓰리즈

준비 작업이 완료되면 다음 단계는 코드를 작성하는 것입니다

이전 네이티브 webgl 개발을 기억하고 계신지 모르겠습니다. 매우 번거롭죠? 이번에는 훨씬 더 편리한 threejs를 사용했습니다. 그러나 threejs에 연락하기 전에 기본 webgl에 익숙해지는 것이 좋습니다., 그렇지 않으면 의존도가 높을 수 있으며 그래픽의 일부 기반이 견고하지 않을 수 있습니다.

첫 번째 단계는 전체 씬 월드를 만드는 것입니다

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(90, window.innerWidth/window.innerHeight, 0.1, 1000);
camera.position.z = 0;
camera.position.x = 0;
var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setPixelRatio(window.devicePixelRatio);
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.setClearColor(0x0077ec, 1);

threejs를 사용하기 위해 필요합니다. 프로그램, 쉐이더, 다양한 컴파일 및 바인딩을 직접 만드는 것보다 훨씬 편리합니다.

다음으로 모델을 가져와야 합니다. 지난번에 간단한 objLoader를 작성했을 때 이번에는 threejs와 함께 제공되는 objLoader를 사용합니다.

var mtlLoader = new THREE.MTLLoader();
mtlLoader.setPath('./assets/');
mtlLoader.load('car4.mtl', function(materials) {
  materials.preload();
  var objLoader = new THREE.OBJLoader();
  objLoader.setMaterials(materials);
  objLoader.setPath('./assets/');
  objLoader.load('car4.obj', function(object) {
    car = object;
    car.children.forEach(function(item) {
      item.castShadow = true;
    });
    car.position.z = -20;
    car.position.y = -5;
    params.scene.add(car);
    self.car = car;
    params.cb();
  }, function() {
    console.log('progress');
  }, function() {
    console.log('error');
  });
});

먼저 mtl 파일을 로드하고 재료를 생성한 다음 obj 파일을 로드하면 매우 편리합니다. 여기서 장면에 자동차를 추가한 후 position.zy를 조정해야 합니다. 우리 세계에서 지면의 y축 좌표는 -5입니다.

이전 코드에서 볼 수 있듯이 카메라의 시작 z 좌표는 0이고 처음에는 자동차의 z 좌표를 -20으로 설정했습니다.

같은 방법으로 트랙 파일을 다시 가져옵니다. 이때 액세스하면 그림과 같이 완전히 어둡다는 것을 알 수 있습니다.

왜 그럴까요?

하나님은 빛이 있으라고 말씀하셨습니다!

트랙과 자동차 자체에는 색상이 없으므로 색상을 만들려면 재료와 조명이 필요합니다. 네이티브 webgl에서 광원을 만드는 것도 번거롭고 셰이더를 작성해야 합니다. Threejs는 매우 편리합니다.
다음 코드만 있으면 됩니다.

var dirLight = new THREE.DirectionalLight(0xccbbaa, 0.5, 100);
dirLight.position.set(-120, 500, -0);
dirLight.castShadow = true;
dirLight.shadow.mapSize.width = 1000; // default
dirLight.shadow.mapSize.height = 1000; // default
dirLight.shadow.camera.near = 2;
dirLight.shadow.camera.far = 1000;
dirLight.shadow.camera.left = -50;
dirLight.shadow.camera.right = 50;
dirLight.shadow.camera.top = 50;
dirLight.shadow.camera.bottom = -50;
scene.add(dirLight);
var light = new THREE.AmbientLight( 0xccbbaa, 0.1 );
scene.add( light );

새로고침하면 온 세상이 더 밝아질 것입니다! (여기서는 주변광 + 평행광을 사용합니다. 나중에 다른 조명으로 변경하고 이유도 알려드리겠습니다)하지만 뭔가 빠진 것이 있습니까? 오른쪽! 여전히 그림자가 없습니다.

그러나 여기서 그림자 처리는 빛만큼 간단하지 않기 때문에 다음 섹션에서 그림자에 대해 이야기해 보겠습니다.

그림자를 제쳐두고, 우리는 자동차와 트랙으로 정적인 세계가 완성되었음을 이해할 수 있습니다.

document.body.addEventListener('keydown', function(e) {
  switch(e.keyCode) {
    case 87: // w
      car.run = true;
      break;
    case 65: // a
      car.rSpeed = 0.02;
      break;
    case 68: // d
      car.rSpeed = -0.02;
      break;
    case 32: // space
      car.brake();
      break;
  }
});
document.body.addEventListener('keyup', function(e) {
  switch(e.keyCode) {
    case 87: // w
      car.run = false;
      break;
    case 65: // a
      car.rSpeed = 0;
      break;
    case 68: // d
      car.rSpeed = 0;
      break;
    case 32: // space
      car.cancelBrake();
      break;
  }
});

우리는 키보드 이벤트 관련 라이브러리를 사용하지 않고 몇 가지 키를 직접 작성합니다. 코드는 여전히 이해하기 쉬워야 합니다.

w를 누르는 것은 가속 페달을 밟는 것을 의미하며, 자동차의 주행 속성이 true로 설정되고 틱에서 가속이 발생합니다. 마찬가지로 a를 누르면 rSpeed가 수정되고 자동차의 회전이 틱으로 변경됩니다.

if(this.run) {
  this.speed += this.acceleration;
  if(this.speed > this.maxSpeed) {
    this.speed = this.maxSpeed;
  }
} else {
  this.speed -= this.deceleration;
  if(this.speed < 0) {
    this.speed = 0;
  }
}
var speed = -this.speed;
if(speed === 0) {
  return ;
}
var rotation = this.dirRotation += this.rSpeed;
var speedX = Math.sin(rotation) * speed;
var speedZ = Math.cos(rotation) * speed;
this.car.rotation.y = rotation;
this.car.position.z += speedZ;
this.car.position.x += speedX;

매우 편리합니다. 몇 가지 수학적 계산으로 자동차의 회전과 위치를 수정하는 것은 괜찮습니다. 네이티브 webgl 자체에서 다양한 변환 행렬을 구현하는 것보다 훨씬 편리합니다. 그러나 threejs의 맨 아래 계층은 여전히 행렬을 통해 변경된다는 것을 알아야 합니다.

이 부분을 간략하게 요약하자면, threejs를 사용하여 전체 세계의 레이아웃을 완성한 다음 키보드 이벤트를 사용하여 자동차를 움직이게 했지만 여전히 많은 것을 놓치고 있습니다.

 

4. 특징과 기능

이 섹션에서는 threejs가 구현할 수 없거나 threejs로 쉽게 구현할 수 없는 기능에 대해 주로 설명합니다. 먼저 3분기 이후에도 여전히 부족한 역량을 정리해 보겠습니다.
a. 카메라 추종
b. 타이어 세부 정보
c. 그림자
d. 충돌 감지
e. 드리프트

하나씩 가자.

카메라 팔로잉

방금 우리는 차를 성공적으로 움직였지만 우리의 관점은 움직이지 않았고 차는 우리에게서 점점 멀어지는 것 같았습니다. 원근은 카메라에 의해 제어됩니다. 이전에는 카메라를 만들었고 이제는 자동차의 움직임을 따라가기를 원합니다. 카메라와 자동차의 관계는 아래 두 사진과 같습니다.

카메라의 회전은 자동차의 회전에 해당하지만 자동차가 회전(회전)하든 움직이든(위치) 카메라의 위치도 변경해야 합니다! 이 서신을 명확히 할 필요가 있습니다.

camera.rotation.y = rotation;
camera.position.x = this.car.position.x + Math.sin(rotation) * 20;
camera.position.z = this.car.position.z + Math.cos(rotation) * 20;

자동차의 틱 방식에서는 자동차 자체의 위치와 회전을 기준으로 카메라의 위치를 계산합니다. 20은 자동차가 회전하지 않을 때 카메라와 자동차 사이의 거리입니다(섹션 3의 시작 부분에서 언급). 위의 그림과 함께 코드를 이해하는 것이 좋습니다. 이렇게 하면 카메라가 따라갈 수 있습니다.

타이어 세부 정보

타이어 세부 사항은 요 각도의 진정성을 경험하기 위해 필요합니다. 요 각도를 몰라도 상관 없으며 아래 그림과 같이 드리프트의 진위로 이해하십시오.

사실 정상적으로 회전할 때는 타이어가 먼저 가고 차체가 두 번째로 움직여야 하는데, 원근감 문제로 여기서는 생략합니다.

여기서 핵심은 차체 방향과 타이어 방향 사이의 불일치입니다. 그러나 여기에 트릭이 있습니다. threejs의 회전은 비교적 엄격합니다. 회전축을 지정할 수 없습니다. rotation.xyz 를 사용하여 좌표 축을 회전하거나 rotateOnAxis를 사용하여 회전을 위해 원점을 통과하는 축을 선택합니다. 따라서 타이어는 차량과 함께 회전할 수만 있고 자체적으로 회전할 수는 없습니다. 그림과 같이.

그런 다음 회전하려면 먼저 타이어 모델을 별도로 추출해야 하며 그림과 같이 다음과 같습니다.

그런 다음 회전은 정상이지만 자동차 회전이 사라진 것을 발견했습니다. 그런 다음 부모 관계를 설정해야 합니다. 자동차의 회전은 부모에 의해 이루어지고 회전은 타이어 자체에 의해 수행됩니다.

mtlLoader.setPath('./assets/');
mtlLoader.load(params.mtl, function(materials) {
  materials.preload();
  var objLoader = new THREE.OBJLoader();
  objLoader.setMaterials(materials);
  objLoader.setPath('./assets/');
  objLoader.load(params.obj, function(object) {
    object.children.forEach(function(item) {
      item.castShadow = true;
    });
    var wrapper = new THREE.Object3D();
    wrapper.position.set(0,-5,-20);
    wrapper.add(object);
    object.position.set(params.offsetX, 0, params.offsetZ);
    scene.add(wrapper);
    self.wheel = object;
    self.wrapper = wrapper;
  }, function() {
    console.log('progress');
  }, function() {
    console.log('error');
  });
});
……
this.frontLeftWheel.wrapper.rotation.y = this.realRotation;
this.frontRightWheel.wrapper.rotation.y = this.realRotation;
this.frontLeftWheel.wheel.rotation.y = (this.dirRotation - this.realRotation) / 2;
this.frontRightWheel.wheel.rotation.y = (this.dirRotation - this.realRotation) / 2;

그림의 데모는 다음과 같습니다.

그림자

우리는 이전에 그림자가 빛만큼 단순하지 않다고 말하면서 그림자를 건너 뛰었습니다. 사실, 그림자는 threejs로 구현되며, 이는 webgl의 네이티브 구현보다 몇 단계 더 간단합니다.
threejs에서 그림자를 구현하는 방법을 살펴 보겠습니다. 세 단계가 필요합니다.
1. 광원 계산 그림자
2. 개체 계산 그림자
3. 오브젝트에 그림자가 있음 이 세 단계를 통해 장면에 그림자가
나타나게 할 수 있습니다.

dirLight.castShadow = true;
dirLight.shadow.mapSize.width = 1000;
dirLight.shadow.mapSize.height = 1000;
dirLight.shadow.camera.near = 2;
dirLight.shadow.camera.far = 1000;
dirLight.shadow.camera.left = -50;
dirLight.shadow.camera.right = 50;
dirLight.shadow.camera.top = 50;
dirLight.shadow.camera.bottom = -50;
……
objLoader.load('car4.obj', function(object) {
  car = object;
  car.children.forEach(function(item) {
    item.castShadow = true;
  }
);
……
objLoader.load('ground.obj', function(object) {
  object.children.forEach(function(item) {
    item.receiveShadow = true;
});

하지만! 여기에 있는 것은 전체 장면이 지속적으로 변화하는 것으로 이해할 수 있는 동적 그림자입니다. 이런 식으로 threejs의 그림자는 더 번거롭고 약간의 추가 처리가 필요합니다.

우선, 우리는 우리의 빛이 평행 빛이라는 것을 알고 있습니다. 평행 조명은 전체 장면을 덮는 햇빛으로 간주 될 수 있습니다. 그러나 그림자는 작동하지 않습니다. 그림자는 직교 행렬을 통해 계산해야 합니다! 그런 다음 여기에 문제가 있습니다. 우리의 전체 장면은 매우 큽니다. 전체 장면을 정사행렬로 덮고 싶다면 프레임 버퍼 이미지도 매우 커지고, 그렇지 않으면 그림자가 매우 비현실적입니다. 사실, 프레임 버퍼 이미지가 전혀 클 수 없고 확실히 멈출 것이기 때문에 이 단계를 고려할 필요가 없습니다.
무엇을해야합니까? 우리는 직교 행렬을 동적으로 변경해야합니다!

var tempX = this.car.position.x + speedX;
var tempZ = this.car.position.z + speedZ;
this.light.shadow.camera.left = (tempZ-50+20) >> 0;
this.light.shadow.camera.right = (tempZ+50+20) >> 0;
this.light.shadow.camera.top = (tempX+50) >> 0;
this.light.shadow.camera.bottom = (tempX-50) >> 0;
this.light.position.set(-120+tempX, 500, tempZ);
this.light.shadow.camera.updateProjectionMatrix();

지면에 있는 자동차의 그림자만 고려했기 때문에 orthomatrix는 자동차를 완전히 포함할 수 있는지만 확인합니다. 벽은 고려되지 않았습니다. 사실, 완벽에 따라 벽에도 그림자가 있어야합니다. 직교 행렬을 확대해야 합니다.

threejs에는 평행광의 정반사 효과가 없고 차 전체가 충분히 선명하지 않기 때문에 평행광을 포인트 광원으로 변경하고(가로등 같은 느낌?) 포인트 광원이 항상 차를 따라가도록 했습니다.

var pointLight = new THREE.PointLight(0xccbbaa, 1, 0, 0);
pointLight.position.set(-10, 20, -20);
pointLight.castShadow = true;
scene.add(pointLight);
……
this.light.position.set(-10+tempX, 20, tempZ);
this.light.shadow.camera.updateProjectionMatrix();

이것은 전반적으로 훨씬 좋아 보입니다. 이것이 앞서 언급한 조명 유형을 변경하는 이유입니다~

충격 점검

충돌 감지 기능이 있는 가장자리를 찾았는지 모르겠지만 실제로는 이 가장자리입니다~

빨간색 가장자리와 자동차의 오른쪽 사이에 충돌 감지가 있지만 충돌 감지는 매우 무작위입니다. 한 번 부딪히면 충돌로 간주됩니다 ... 속도가 0으로 직접 설정되고 다시 나타납니다.

충돌 감지는 쉽기 때문에 실제로 게으르지만 이러한 종류의 레이싱 충돌 피드백은 물리 엔진에 연결하지 않고는 정말 어렵습니다. 고려해야 할 사항이 많습니다. 단순히 원으로 보면 훨씬 더 편리할 것입니다.
그래서 먼저 충돌 감지에 대해 알려 드리겠습니다. 좋은 피드백을 받고 싶다면... 성숙한 물리 엔진에 연결하는 것이 좋습니다.

자동차와 트랙 사이의 충돌 감지를 위해 먼저 3D를 2D로 변환해야 볼 수 있는데, 여기에는 오르막이나 내리막길에 장애물이 없기 때문에 간단합니다.

2D 충돌, 자동차의 왼쪽과 오른쪽과 장애물의 측면을 감지할 수 있습니다.

먼저 트랙의 2D 데이터를 얻은 다음 검사를 위해 자동차의 왼쪽과 오른쪽을 동적으로 얻습니다.

var tempA = -(this.car.rotation.y + 0.523);
this.leftFront.x = Math.sin(tempA) * 8 + tempX;
this.leftFront.y = Math.cos(tempA) * 8 + tempZ;
tempA = -(this.car.rotation.y + 2.616);
this.leftBack.x = Math.sin(tempA) * 8 + tempX;
this.leftBack.y = Math.cos(tempA) * 8 + tempZ;
……
Car.prototype.physical = function() {
  var i = 0;
  for(; i < outside.length; i += 4) {
    if(isLineSegmentIntr(this.leftFront, this.leftBack, {
      x: outside[i],
      y: outside[i+1]
    }, {
      x: outside[i+2],
      y: outside[i+3]
    })) {
      return i;
    }
  }
  return -1;
};

이것은 카메라의 개념과 다소 유사하지만 수학이 조금 더 번거롭습니다.

라인 간 충돌 감지의 경우 가장 빠른 라인 간 충돌 감지인 삼각형 영역 방법을 사용합니다.

function isLineSegmentIntr(a, b, c, d) {
  // console.log(a, b);
  var area_abc = (a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x);
  var area_abd = (a.x - d.x) * (b.y - d.y) - (a.y - d.y) * (b.x - d.x);
  if(area_abc * area_abd > 0) {
    return false;
  }
  var area_cda = (c.x - a.x) * (d.y - a.y) - (c.y - a.y) * (d.x - a.x);
  var area_cdb = area_cda + area_abc - area_abd ;
  if(area_cda * area_cdb > 0) {
    return false;
  }
    return true;
  }
}

그들이 만난 후에는 어떻게 됩니까? 완벽한 피드백은 없지만 기본적인 피드백은 있어야 합니다. 속도를 0으로 설정하고 다시 나타나면 자동차의 방향을 올바르게 재설정해야 합니다. 그렇지 않으면 플레이어가 계속 충돌합니다... 방향을 재설정하려면 자동차의 원래 방향 벡터를 사용하여 충돌 가장자리에 투영합니다. 결과 벡터는 재설정 방향입니다.

function getBounceVector(obj, w) {
  var len = Math.sqrt(w.vx * w.vx + w.vy * w.vy);
  w.dx = w.vx / len;
  w.dy = w.vy / len;
  w.rx = -w.dy;
  w.ry = w.dx;
  w.lx = w.dy;
  w.ly = -w.dx;
  var projw = getProjectVector(obj, w.dx, w.dy);
  var projn;
  var left = isLeft(w.p0, w.p1, obj.p0);
  if(left) {
    projn = getProjectVector(obj, w.rx, w.ry);
  } else {
    projn = getProjectVector(obj, w.lx, w.ly);
  }
  projn.vx *= -0.5;
  projn.vy *= -0.5;
  return {
    vx: projw.vx + projn.vx,
    vy: projw.vy + projn.vy,
  };
}
function getProjectVector(u, dx, dy) {
  var dp = u.vx * dx + u.vy * dy;
  return {
    vx: (dp * dx),
    vy: (dp * dy)
  };
}

드리프트

자동차가 표류하지 않고 온라인 게임을 열고 네트워크 케이블이 끊어진 것을 찾는 것과 같습니다.

우리는 어느 것이 더 빠른지, 드리프트 또는 일반 코너링인지는 고려하지 않습니다. 관심 있는 학생들은 확인할 수 있습니다. 꽤 흥미 롭습니다.
먼저 세 가지 결론을 설명하겠습니다 . 1. 드리프트 레이싱 게임의 핵심 부분 중 하나(잘생김)는 하지 않을 수 없습니다.

2. 드리프트의 핵심은 차량 앞쪽을 비틀지 않고 출구 방향이 더 좋다는 것입니다(시각적으로 가장 직관적이기 때문에 다른 장점과 단점은 생략합니다).

3. 인터넷에는 쉽게 사용할 수 있는 드리프트 알고리즘이 없으므로(단일성에 관계없이) 드리프트를 시뮬레이션해야 합니다.

시뮬레이션을 위해서는 먼저 드리프트의 원리를 알아야 합니다. 아까 얘기했던 요각을 기억하시나요? 요 각도는 드리프트의 시각적 경험입니다.

좀 더 구체적으로 말하면 요 각도는 자동차의 이동 방향이 자동차 앞쪽 방향과 일치하지 않을 때 그 차이를 요 각도라고 함을 의미합니다.
따라서 시뮬레이션된 드리프트는 두 단계로 수행해야 합니다:
1. 플레이어가 드리프트를 시각적으로 느낄 수 있도록 요 각도를 생성합니다.

2. 코너를 빠져나가는 방향이 정확하여 플레이어가 진정성을 느낄 수 있습니다. 플레이어는 표류 후 코너링하는 것이 더 불편하지 않을 것입니다...

아래에서는 이 두 점을 시뮬레이션합니다. 사실, 목적을 알면 여전히 쉽게 시뮬레이션할 수 있습니다.

요 각도가 생성되면 두 방향을 유지해야 하는데, 하나는 차체의 실제 회전 방향인 realRotation이고 다른 하나는 자동차의 실제 이동 방향인 dirRotation입니다(카메라가 따라가는 방향입니다!).

이 두 값은 일반적으로 동일하지만 사용자가 스페이스바를 누르면 변경되기 시작합니다.

var time = Date.now();
this.dirRotation += this.rSpeed;
this.realRotation += this.rSpeed;
var rotation = this.dirRotation;
if(this.isBrake) {
  this.realRotation += this.rSpeed * (this.speed / 2);
}
this.car.rotation.y = this.realRotation;
this.frontLeftWheel.wrapper.rotation.y = this.realRotation;
this.frontRightWheel.wrapper.rotation.y = this.realRotation;
this.frontLeftWheel.wheel.rotation.y = (this.dirRotation - this.realRotation) / 2;
this.frontRightWheel.wheel.rotation.y = (this.dirRotation - this.realRotation) / 2;
camera.rotation.y = this.dirRotation;

이때, 요 각도가 생성되었습니다.

사용자가 공간을 해제하면 두 방향이 통합되기 시작해야 합니다. 이때 dirRotation 은 realRotation 쪽으로 통일되어야하며, 그렇지 않으면 코너에서 표류하는 의미가 손실된다는 것을 기억하십시오.

var time = Date.now();
if(this.isBrake) {
  this.realRotation += this.rSpeed * (this.speed / 2);
} else {
  if(this.realRotation !== this.dirRotation) {
    this.dirRotation += (this.realRotation - this.dirRotation) / 20000 * (this.speed) * (time - this.cancelBrakeTime);
  }
}
 
728x90