Cub3d 학습일지 - 2 - 맵과 플레이어 움직임

대표 사진

 

Raycasting Basics with JavaScript

Learn the mathematics behind the ray casting technique used in the Wolfenstein 3D source code and implement a 3D projected scene using JavaScript

courses.pikuma.com

제 2강 Map and Player Movement 맵과 플레이어 움직임

2.1  Defining the 2D Map Grid 2D 맵 그리드 정의하기
2.2 
Coding the Map Class 맵 클래스 코딩하기!
2.3
Player Movement 플레이어 움직임
2.4
Coding the Player Movement 플레이어 움직임 코딩하기!
2.E  Exercise: Map Collision 맵 충돌 기능
2.5
Implementing Map Collision 맵 충돌 기능 구현하기!

2.1  Defining the 2D Map Grid 2D 맵 그리드 정의하기

맵 정의하기

지금 저희는 맵과 ROW, COL 등을 정의해놓았으나

cub3d 과제를 진행할 때는 .cub 파일에 넣고 row와 col도 동적으로 계산해야합니다.

const	TILE_SIZ = 32;
const	MAP_ROWS = 11;
const	MAP_COLS = 15;

class	Map {
	walls = [
        [1, 1, 1, 1, 1,  1, 1, 1, 1, 1,  1, 1, 1, 1, 1],
        [1, 0, 0, 0, 0,  0, 0, 0, 0, 0,  0, 0, 1, 0, 1],
        [1, 0, 0, 0, 0,  1, 0, 0, 0, 0,  0, 0, 1, 0, 1],
        [1, 1, 1, 1, 0,  0, 0, 0, 0, 0,  1, 0, 1, 0, 1],
        [1, 0, 0, 0, 0,  0, 0, 0, 0, 0,  1, 0, 1, 0, 1],
        [1, 0, 0, 0, 0,  0, 0, 0, 1, 1,  1, 1, 1, 0, 1],
        [1, 0, 0, 0, 0,  0, 0, 0, 0, 0,  0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0,  0, 0, 0, 0, 0,  0, 0, 0, 0, 1],
        [1, 1, 1, 1, 1,  1, 0, 0, 0, 1,  1, 1, 1, 0, 1],
        [1, 0, 0, 0, 0,  0, 0, 0, 0, 0,  0, 0, 0, 0, 1],
        [1, 1, 1, 1, 1,  1, 1, 1, 1, 1,  1, 1, 1, 1, 1],
	];
}

Javascript로 코딩 시작!

폴더 구조입니다.

지금은 화면으로 쉽게 확인하기 위해서 html방식으로 간단하게 프로토타입만 제작할 것이며,

이제부터 작성한 모든 코드는 github에 업로드 하겠습니다!

 

KKWANH/cub3d_kkim

Contribute to KKWANH/cub3d_kkim development by creating an account on GitHub.

github.com

p5.js는 그림을 쉽게 그리기 위한 js 라이브러리이며 p5js.org/에서 다운받았습니다.

 

home | p5.js

Hello! p5.js is a JavaScript library for creative coding, with a focus on making coding accessible and inclusive for artists, designers, educators, beginners, and anyone else! p5.js is free and open-source because we believe software, and the tools to lear

p5js.org


2.2  Coding the Map Class 맵 클래스 코딩하기!

github룰 참고하세요! 직접 실행시켜보시고 디버깅하시는 걸 추천드립니다.

const       TILE_SIZE       = 32;
const       MAP_NUM_ROWS    = 11;
const       MAP_NUM_COLS    = 15;

const       WINDOW_WIDTH    = MAP_NUM_COLS * TILE_SIZE; // how many columns
const       WINDOW_HEIGHT   = MAP_NUM_ROWS * TILE_SIZE; // how many rows

class       Map {
    constructor()
    {
        this.grid = [
            [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
            [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1],
            [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
            [1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
            [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
        ];
    }

    render()
    {
        for (var i=0; i<MAP_NUM_ROWS; i++)
        {
            for (var j=0; j<MAP_NUM_COLS; j++)
            {
                var tileX = j * TILE_SIZE;
                var tileY = i * TILE_SIZE;
                var tileColor = this.grid[i][j] == 1 ? "#222" : "#fff";
                stroke("#222");
                fill(tileColor);
                rect(tileX, tileY, TILE_SIZE, TILE_SIZE);
            }
        }
    }
}

var         grid = new Map();

// TODO: initialize all objects 모든 오브젝트 초기화하기
function    setup()
{
    createCanvas(WINDOW_WIDTH, WINDOW_HEIGHT);
}

// TODO: update all game objects before we render the next frame 다음 프레임을 렌더링 하기 전에 모든 게임 오브젝트를 업데이트
function    update()
{

}

// TODO: render all objects frame by frame 모든 개체를 프레임 별로 렌더링
function    draw()
{
    // 언제나 업데이트 후 시작
    update();

    grid.render();
}

2.3 Player Movement 플레이어 움직임
// Player의 클래스 정의

class       Player
{
    x;
    y;
    // radius
    rad;
    // turnDirection : -1(left), 1(right), 0(nothing to update) : 실제 각도가 아니라 왼쪽인지 오른쪽인지만.
    dir_turn = 0;
    // walkDirection : -1(back), 1(front), 0(nothing to update) : 실제 각도가 아니라 앞인지 뒤인지만.
    dir_walk = 0;
    // rotationAngle : PI = 180도, PI/2 = 90도
    ang_rota = Math.PI / 2;
    // moveSpeed 움직이는 속도
    spd_move = 3.0;
    // rotationSpeed : 3도씩 회전
    spd_rota = 3 * (MATH.PI / 180);
}

2.E  Exercise: Map Collision 맵 충돌 기능
2.5 
Implementing Map Collision 맵 충돌 기능 구현하기!

github.com/KKWANH/cub3d_kkim/tree/main/P2_Collision

 

KKWANH/cub3d_kkim

Contribute to KKWANH/cub3d_kkim development by creating an account on GitHub.

github.com

const       TILE_SIZ    = 32;
const       MAP_ROWS    = 11;
const       MAP_COLS    = 15;

const       WIND_WID    = MAP_COLS * TILE_SIZ; // how many columns
const       WIND_HEI    = MAP_ROWS * TILE_SIZ; // how many rows

var         x_now = 5;
var         y_now = 8;

class       Map {
    constructor()
    {
        this.grid = [
            [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
            [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1],
            [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
            [1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
            [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
        ];
    }

    hasWallAt(posX, posY)
    {
        var mapIdxX = Math.floor(posX / TILE_SIZ);
        var mapIdxY = Math.floor(posY / TILE_SIZ);

        return (this.grid[mapIdxY][mapIdxX] != 0);
    }

    render()
    {
        for (var i=0; i<MAP_ROWS; i++)
        {
            for (var j=0; j<MAP_COLS; j++)
            {
                var tileX = j * TILE_SIZ;
                var tileY = i * TILE_SIZ;
                var tileColor = this.grid[i][j] == 1 ? "#222" : "#fff";
                stroke("#222");
                fill(tileColor);
                rect(tileX, tileY, TILE_SIZ, TILE_SIZ);
            }
        }
    }
}

class       Player
{
    constructor()
    {
        this.x = WIND_WID / 2;
        this.y = WIND_HEI / 2;
        this.rad = 3;
        this.dir_turn = 0;                      // turnDirection : -1(left), 1(right), 0(nothing to update) : 실제 각도가 아니라 왼쪽인지 오른쪽인지만.
        this.dir_walk = 0;                      // walkDirection : -1(back), 1(front), 0(nothing to update) : 실제 각도가 아니라 앞인지 뒤인지만.
        this.ang_rota = Math.PI / 2;            // rotationAngle : PI = 180도, PI/2 = 90도s
        this.spd_move = 3.0;                    // moveSpeed 움직이는 속도
        this.spd_rota = 3 * (Math.PI / 100);    // rotationSpeed : 3도씩 회전
    }

    // TODO: update player position based on turnDirection and walkDirection
    update()
    {
        this.ang_rota += this.dir_turn * this.spd_rota;

        var moveStep = this.dir_walk * this.spd_move;
        var newPosiX = this.x + Math.cos(this.ang_rota) * moveStep;
        var newPosiY = this.y + Math.sin(this.ang_rota) * moveStep;

        // 벽이 아닐 경우 (충돌하지 않을 경우) 에만 플레이어 놓기
        if (!grid.hasWallAt(newPosiX, newPosiY))
        {
            this.x = newPosiX;
            this.y = newPosiY;
        }
    }

    render()
    {
        noStroke();
        // 위치
        fill("red");
        circle(this.x, this.y, this.rad);
        // 라인
        stroke("red");
        line(
            this.x,
            this.y,
            this.x + Math.cos(this.ang_rota) * 30,
            this.y + Math.sin(this.ang_rota) * 30
        );
    }
}

var         grid = new Map();
var         player = new Player();

// TODO :
function    keyPressed()
{
    if (keyCode == UP_ARROW) {
        player.dir_walk = +1;
    } else if (keyCode == DOWN_ARROW) {
        player.dir_walk = -1;
    } else if (keyCode == RIGHT_ARROW) {
        player.dir_turn = +1;
    } else if (keyCode == LEFT_ARROW) {
        player.dir_turn = -1;
    }
}

function    keyReleased()
{
    if (keyCode == UP_ARROW) {
        player.dir_walk = 0;
    } else if (keyCode == DOWN_ARROW) {
        player.dir_walk = 0;
    } else if (keyCode == RIGHT_ARROW) {
        player.dir_turn = 0;
    } else if (keyCode == LEFT_ARROW) {
        player.dir_turn = 0;
    }
}

// TODO: initialize all objects 모든 오브젝트 초기화하기
function    setup()
{
    createCanvas(WIND_WID, WIND_HEI);
}

// TODO: update all game objects before we render the next frame 다음 프레임을 렌더링 하기 전에 모든 게임 오브젝트를 업데이트
function    update()
{
    player.update();
}

// TODO: render all objects frame by frame 모든 개체를 프레임 별로 렌더링
function    draw()
{
    // 언제나 업데이트 후 시작
    update();

    grid.render();
    player.render();
}

여기까진 P2_Collision에 최종 구현되었습니다.