Web Dev/Game Development :: 게임개발

JavaSciprt로 3D 게임을 만들어보자 :: 맵 구성 (2/?)

HJPlumtree 2022. 8. 7. 21:06

JavaScript로 간단한 3D 게임을 만들며 배운 점

 

 

저번 포스트에서 기획을 위주로 알아봤고,

이번주에는 참고자료를 보며 주요 기능을 파악하고 구현을 해봤습니다.

 

 

주요 내용

3D 게임을 만들기 위한 필요한 내용 다음과 같이 추려봤습니다.

 

1. 맵과 캐릭터 그리기

2. 키보드 이벤트(이동, 점프)

3. 충돌 이벤트(벽, 코인)

 

 

3D 렌더링 세팅

3D 렌더링 세팅 완성본 👇

 

맵을 만들기 전에 3D를 렌더링 할 화면을 구성합니다.

 

HTML로 구조 구성

 

index.html

<div id="viewport">
  <div id="camera">
    <div id="scene">
      <div id="floor"></div>
    </div>
  </div>
</div>

 

  • viewport
    • 3D 렌더링을 감싸주는 TV같은 녀석
  • camera
    • 상하좌우 카메라 위치 
  • scene
    • 3D 렌더링이 될 장소
  • floor
    • 렌더링 될 맵, 히어로의 시작 장소

 

 

CSS로 3D 변환

3D를 만들어 주는 부분은 CSS가 담당합니다.

그 중에 필요한 속성들은 transfrom-style, perspective, translate3D 입니다.

 

viewer.css

/* 3D scene */
#viewport {
  width: 700px;
  height: 600px;
  overflow: hidden;
  border: 1px solid;
  position: relative;
  perspective: 500px;
}
#viewport * {
  transform-style: preserve-3d;
  box-sizing: border-box;
}
#camera {
  width: 0;
  height: 0;
  position: absolute;
  top: 50%;
  left: 50%;
}
#scene {
  position: fixed;
  transform: translate3D(-50%, -50%, -3000px) rotateX(60deg) rotateZ(-2deg);
}
#floor {
  position: fixed;
  background: #31087b;
}

 

  • transfrom-style
    • preserve-3d이 엘리먼트들을 3D에 위치
  • perspective
    • z=0에서 부터의 거리 설정 
  • translate3D
    • 3D 공간에서 엘리먼트 위치 조정
  • rotateX, rotateZ
    • 3D로 바뀐 상태에서 x와 z축을 기준으로 회전
    • 2D에서도 회전할 때 사용

 

 

JavaScript로 공간 크기 결정 및 그리기

이제는 맵과 히어로가 만들어질 공간의 크기를 정하고 그려줄 차례입니다.

scene과 floor <div>를 가져와서 값을 정해줍니다.

function init() {
  drawScene();
  const $scene = document.querySelector("#scene");
  const $floor = document.querySelector("#floor");
  const mapWidth = 10;
  const mapHeight = 10;
  $scene.style.width = $floor.style.width = mapWidth * 200 + "px";
  $scene.style.height = $floor.style.height = mapHeight * 200 + "px";
}

init()

 

BREAK TIME!!
3D 공간 완성 축하드립니다🎉
별 내용 안쓴거 같은데 참 오래걸리네요.

 

 

맵 렌더링

이제는 만든 3D 공간에 맵을 만들 차례입니다.

 

맵 렌더링 완성본 👇

 

큐브 스타일 작성

맵을 구성할 큐브의 스타일을 먼저 작성합니다.

뒤에 있는 엘리먼트를 숨긴 목적인 backface-visiblity 빼고는,

위에서 이미 사용한 CSS 속성으로 cube를 구성했습니다. 

 

pieces.css

// Pieces

.face {
  width: 200px;
  height: 200px;
  position: fixed;
  font-size: 100px;
  transform-origin: 50% 100%;
  backface-visibility: hidden;
}

/* Cube */
.up {
  transform: translateZ(200px);
}
.left {
  transform: translate3D(-100px, -100px, 0) rotateZ(90deg) rotateX(-90deg);
}
.right {
  transform: translate3D(100px, -100px, 0) rotateZ(-90deg) rotateX(-90deg);
}
.front {
  transform: rotateX(-90deg);
}
.back {
  transform: translate3D(0, -200px, 0) rotateZ(180deg) rotateX(-90deg);
}


/* textures */
.cube * {
  background: #bbb;
}
.cube .front {
  background: #ddd;
}
.cube .left,
.cube .right {
  background: #999;
}

 

map 구조

0은 빈공간이고, 1은 블럭입니다.

이 다음 스크립트에서 아래 맵을 토대로 블럭을 만들어 줍니다 

 

map.js

// Map
const map = {
  layers: [
    // Layer 0:
    [
      "0000000000",
      "0000000000",
      "0000000000",
      "0000100000",
      "0000100000",
      "0011111000",
      "0000100000",
      "0000100000",
      "0000000000",
      "0000000000",
    ],

    // Layer 1:
    [
      "0000000000",
      "0000000000",
      "0000000000",
      "0000000000",
      "0000000000",
      "0000100000",
      "0000000000",
      "0000000000",
      "0000000000",
      "0000000000",
    ]
  ],
};

 

드디어 맵 만들기!

여기서는 두 개의 함수를 만들어서 이미 만든 init()에 넣어줍니다

 

첫 번째 함수 drawCube

x, y, z 좌표를 받아서 <div> 태그로 큐브를 만들어 반환하는 함수입니다

const drawCube = (x, y, z) => {
  let cube = `<div class='cube' 
  style='transform: translate3d(${ x * 200 }px, ${y * 200}px, ${z * 200}px)'>`;
  
  cube += `<div class="face up"></div>`;
  cube += `<div class="face left"></div>`;
  cube += `<div class="face back"></div>`;
  cube += `<div class="face right"></div>`;
  cube += `<div class="face front"></div></div>`;
  return cube;
};

 

두 번째 함수 drawScene

이제는 앞에서 구성한 map을 토대로 맵에 찍어줄 차례입니다

삼중 for문이 들어가서 조금 헷깔릴 수 있지만 하나씩 따라가면

z는 각 Layer,

y는 Layer의 각 줄,

x는 각 줄의 각 문자인 것을 알 수 있습니다.

// Draw scene
const drawScene = () => {
  let token = " ";
  const $scene = document.querySelector("#scene");
  for (Z in map.layers) {
    for (Y in map.layers[Z]) {
      for (X in map.layers[Z][Y]) {
        token = map.layers[Z][Y][X];
        if (token == "1") {
          // 1 = cube
          $scene.insertAdjacentHTML("beforeEnd", drawCube(X, Y, Z));
        }
      }
    }
  }
};

 

그리고

앞서 언급한대로 drawScene을 init() 함수에 넣어주면 완성!

 

BREAK TIME!
3D 맵 완성 축하드립니다🎉🎉

 

 

다음 포스트 예고

원래는 히어로 렌더링과 키보드 이벤트까지 한 포스트에 담아보려고 했는데

쓰다보니 코드 때문에 길어졌고, 작성하는 시간도 길어져서

오늘은 여기까지 해야겠네요

 

다음 포스트는 

히어로 렌더링과 키보드 눌렀을 때 이동하는 방법에 대해 알아볼 것 같습니다.

사용되는 기술은 

setInterval, onkeydown, onkeyup, insertAdjacentHTML가 주가 될 것 같습니다