7 분 소요

유데미 x 스나이퍼팩토리 10주 완성 프로젝트 캠프 - 유니티 기초 학습 3주차 내용이다.

월요일 수업

월요일 수업은 게임을 만드는 게 아니라 만들어진 게임을 코드를 살펴보며 버그를 잡는 날을 가졌다.
강사님이 말씀해주신 중요 키워드들은 protected, enum을 강조해주셨다.
protected는 접근 제한자로 상속 관계에서만 사용 할 수 있게 선언하는 제한자이다. 즉 protected로 선언 된 변수나 함수가 있으면 당연히 자식 클래스를 확인 할 필요가 있다라고 설명을 해 주셨다.

다음은 enum이다. 강사님이 말씀해주시기를 enum은 오직 가독성때문에 사용하며 팀 프로젝트를 할 때 필수적으로 사용한다고 설명해 주셨다.

다음은 코드를 보며 이 코드가 어떤 일을 하는지 알려주며 코드리뷰를 진행했다.
AI 플레이어 스크립트가 이번 수업에서는 가장 많은 시간을 차지했는데 그 중 버그가 나타난 구간이기도 하다.
버그 내용은 적과 플레이어 사이에 벽이 있어도 AI가 사격을 하는 것이다.
이 버그를 고치는게 많은 시간을 주며 생각하는 시간을 가졌는데 나는 강사님이 아주 살짝 코드를 변경하면 충분하다고 말씀을 해 기존에 있던 코드를 살짝 바꿨다.

// X 혹은 Z 방향의 거리가 가까울 경우 직선상에 있다고 판단하면 사격한다
if ((Mathf.Abs(aiSubPosition.x) < 1f) || (Mathf.Abs(aiSubPosition.z) < 1f))

이렇게 적과 플레이어의 사이가 x값 또는 z값이 1이하로 차이가 나면 사격을 하는 것 인데 적이 플레이어를 공격하는 조건은 맵 구조상 플레이어와 적이 같은 위치 즉 1f -> 0으로 변경하면 같은 직선에 있을 경우를 뜻한다고 생각해서 1f의 값을 0으로 둘 다 바꿔주었다.

이렇게 바꾸고 플레이를 해보니 생각한 대로 잘 작동하였으나 뭔가 AI가 플레이어를 인지하기가 부드럽지 못 한 느낌이였다.

또 강사님이 30분안에 풀면 잘하는 것이다 ! 라고 말하셔서 3분만에 해결해버린 이 코드가 더욱 의심스러워서 결국 다른 방법인 Ray를 적에게 붙히는 방법을 택했다.

player에게 Layer를 넣고 RaycastHit을 이용해 벽이 아닌 플레이어만 ray를 반환하도록 설정을 하여 플레이어를 발견하면 사격하도록 설정을 했다.

이 방법은 강사님이 생각하신 방법은 아닌듯 했다.
강사님은 Field 스크립트를 바꾼 방법을 말하시는 느낌이였는데 나는 그 스크립트에서는 찾을 수 없었다..

아무튼 강사님이 Hp구현, 재시작, 플레이어가 죽은 후 Ai 사격 금지 이렇게 해결하라고 문제를 내 주셨는데 코드를 보다보면 다양한 방법으로 금방 해결할 수 있는 문제들 이였다.

그 후 길찾기 알고리즘을 설명해주셨는데 바로 Dijkstra’s Alogorithm(다익스트라 알고리즘)과 A* Alogrithm(에이스타 알고리즘)이다. (알고리즘 시간에 풀었던 기억이 있는데 다시 풀어봐야겠다.)

먼저 둘의 차이는
다익스트라 : 시작 노드 기준으로 모든 노드의 최단 경로를 구함. A* : 시작 노드에서 목표 노드까지의 최단 경로만을 구하려 함.
옛날에는 컴퓨터의 사양이 떨어지기에 특정 알고리즘만 사용했다면 지금은 사실 알고리즘을 잘 사용만 한다면 크게 차이는 없을 것 이라는 말씀을 해주셨다.

유니티에서 사용하는 네비게이션에 대해서도 간단한 설명을 해주셨다. 아무래도 유니티에 있는 네비게이션은 메모리면에서 좋은 편은 아니다. 사양이 좋지 않은 기기를 가지고 있는 플레이어들에게 메모리를 생각하지 않고 사용했다가 버벅이는 게임을 만들게 된다라는 말도 해주셨다.
Flocking 알고리즘도 간단한 설명을 해주셨고 AI에 관련된 이야기도 많이 해주셨다.

이번 시간에는 이 강의를 수강하고 있는 많은 학생들에게 꿀팁을 많이 주셨다.
면접부터 시작해서 포트폴리오 등 다양한 이야기를 해주셨는데 살이 되는 말들을 잘 기억해두고 강사님 말대로 포트폴리오를 더 많이 만들어보는 시간을 가져야겠다.

월요일 수업 끝 !

수요일 수업

오늘은 retro 강사님의 자료를 가지고 수업을 진행했다.

image

Dodge게임을 만들었던 시간을 가졌다. 여태 모두 했던 내용들이기에 이번 주는 과제 설명만 추가하고 모르는 내용을 추가해야겠다.
내가 수행한 과제 내용은

  1. 7초마다 회전 속도 증가, 방향 반대 (Time.deltaTime 사용)
  2. 플레이어 hp 6으로 설정 (월요일에 사용한 방식)
  3. 피격시 플레이어 색 변경 (1주차 월요일 수업 사용한 방식)
  4. 총알이 벽을 튕기면 반대 방향으로 이동 (콜라이더를 이용해 태그가 Wall인 오브젝트와 닿으면 반대 방향으로 직진)
  5. Lshift누를 때 플레이어 속도 증가 (월요일에 사용한 방식)
    이렇게 구성했다.

기존에 사용했던 기능들을 다시 사용해보며 만들어보았다.

오늘 포스팅 내용이 너무 적기 때문에 월요일에 배운 알고리즘들을 c++로 풀어보는 시간을 가져야겠다.

다익스트라


다익스트라 알고리즘은 시작점으로부터 모든 노드까지의 최소거리를 구해준다.

#include <iostream>
#define INF 100000000
#define N 5
using namespace std;

int weight[N][N] = { //노드의 이동 비용 (간선) INF = 연결되지 않음
    {0, 7, 4, 6, 1},
    {INF, 0, INF, INF, INF},
    {INF, 2, 0, 5, INF},
    {INF, 3, INF, 0, INF},
    {INF, INF, INF,1,0}
    };

bool visit[N];
int dist[N];
int min_node;


//최소비용노드 탐색 함수
int get_small_node()
{
    int min = INF;
    int minpos = 0; //최소 비용 노드 저장 변수
    for(int i =0; i < N; i++){
        if(dist[i] < min && visit[i] == false) {
            min = dist[i];
            minpos = i;
        }
    }
    return minpos;
}

void dijkstra(int start){
    for(int i =0; i<N; i++){ //dist 초기화 
        dist[i] = weight[start][i];
    }
    visit[start] = true; 
    for(int repeat = 0; repeat < N -1; repeat++){
        min_node = get_small_node(); // 최소 비용 노드 탐색
        visit[min_node] = true; // 방문처리
        for(int i = 0; i < N; i++)
        {  // 방문하지 않은 노드 중에, 시작점이 최소비용노드를 경유하는게 더 가가우면 값 갱신
            if (visit[i] == false && dist[min_node] + weight[min_node][i] < dist[i]){
                dist[i] = dist[min_node] + weight[min_node][i];
            }
        }
    }
}

int main()
{
    dijkstra(0); //시간 복잡도 O(N^2) N-1만큼 반복하고 있고, 각각의 경우 노드를 찾을때 N번만큼 반복
    for(int i =0; i < N; i++)
        cout<<dist[i] << " ";
    return 0;
}




[참고 (아래에 있는 우선순위 큐로 만든 방법도 보기)] (https://charles098.tistory.com/11)

a*


다익스트라를 확장해 만든 경로 탐색 알고리즘이다.
a가 나타가ㅔ 된 것은 다익스트라의 현실 적용의 어려움이다. 현실은 아날로그이기 때문에 다익스트라처럼 거리같은 개념들을 노드화 할 수 없기 때문이다.
설명 : A
는 출발부터 현재 꼭지점 x까지의 비용을 g(x), 현재 꼭지점 x에서 목표 지점까지의 비용 h(x)라고 할 때 이 둘을 더한 f(x) = g(x) + h(x)가 최소가 되는 지점을 우선 탐색하여 경로를 찾는 방법이다.

#include <iostream>
#include <vector>
#include <math.h>
#include <queue>
#include <stdlib.h>

using namespace std;



typedef struct node
{                          // 노드
    int x, y;              // 좌표
    int G, H;              // g(node), h(node)
    pair<int, int> parent; // 역추적에 쓰일 이전 노드
} Node;

struct cmp
{ // 우선순위 큐 비교 함수
    bool operator()(const node u, const node v)
    {
        if (u.G + u.H > v.G + v.H) 
            return true; // F는 작은게 위로 오게
        else if (u.G + u.H == v.G + v.H)
        { // F가 같다면 G가 큰게 위로 오게
            if (u.G < v.G)
                return true;
            else
                return false;
        }
        else
            return false;
    }
};


//출력값이 9는 우선순위 큐에 들어간 노드들, 8은 폐쇄 리스트에 들어간 노드, 4는 역 추적
void print_map(vector<vector<int>> map)
{ // 맵 출력 함수
    for (int i = 0; i < map.size(); i++)
    {
        for (int j = 0; j < map.size(); j++)
            cout << map[i][j] << " ";
        cout << '\n';
    }
}

int Astar(vector<vector<int>> map, pair<int, int> start, pair<int, int> goal)
{
    priority_queue<Node, vector<Node>, cmp> open; // 우선순위 큐

    bool close[10][10] = {
        0,
    }; // 폐쇄 리스트(리스트&visit)
    vector<Node> close_list;
    Node s_node; // 시작 노드

    int cx[8] = {0, 1, 0, -1, 1, 1, -1, -1}; // 방향 좌표 ↑→↓←↘↗↙↖
    int cy[8] = {-1, 0, 1, 0, 1, -1, 1, -1};

    // 시작지점 초기화
    s_node.x = start.second;
    s_node.y = start.first;
    s_node.G = 0;
    s_node.H = (abs(goal.second - s_node.x) + abs(goal.first - s_node.y)) * 10;
    s_node.parent = make_pair(-1, -1); // 시작 노드의 부모 노드는 -1,-1
    open.push(s_node);
    close[s_node.y][s_node.x] = true; // 폐쇄 노드

    vector<vector<int>> result = map;
    while (open.size())
    {
        int x = open.top().x; // 우선순위 큐에서 top 정보 추출
        int y = open.top().y;
        int G = open.top().G;
        close_list.push_back(open.top());
        result[y][x] = 8;
        open.pop();
        if (x == goal.second && y == goal.first)
            break; // 도착 지점이 나오면 끝

        Node add;
        for (int i = 0; i < 4; i++)
        { // top 노드에서 상하좌우 4방향으로 탐색(i<8이면 8방향)
            int nextX = x + cx[i];
            int nextY = y + cy[i];
            if (nextX >= 0 && nextX < map.size() && nextY >= 0 && nextY < map.size())
            {
                if (map[nextY][nextX] != 1 && close[nextY][nextX] == false)
                {
                    add.x = nextX;
                    add.y = nextY;
                    add.G = i < 4 ? G + 10 : G + 14; // 상하좌우면 10, 대각선이면 14(√200)
                    add.H = (abs(goal.second - add.x) + abs(goal.first - add.y)) * 10;
                    add.parent = make_pair(y, x); // 기존 top노드를 부모 노드로 설정
                    close[nextY][nextX] = true;
                    result[nextY][nextX] = 9;
                    open.push(add); // 우선순위 큐에 삽입
                    system("cls");
                    print_map(result);
                }
            }
        }
    }

    int px = close_list.back().x;
    int py = close_list.back().y;
    while (close_list.size())
    { // close_list를 역추적해 경로 탐색
        if (px == close_list.back().x && py == close_list.back().y)
        { // 목표 노드부터 부모 노드를 탐색해 역추적
            result[py][px] = 4;
            px = close_list.back().parent.second;
            py = close_list.back().parent.first;
            system("cls");
            print_map(result);
        }
        close_list.pop_back();
    }

    return 0;
}

int main()
{
    int size = 7;
    /*
    cout << "맵 사이즈 : ";
    cin >> size;
    cout << "맵 입력(0:길, 1:벽, 5: 출발점, 6 : 도착점)\n";
    */
    vector<vector<int>> map(size, vector<int>(size, 0));
    pair<int, int> start, goal;
    start = {5, 1};
    goal = {1, 5};
    map = {
        {0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 6, 0},
        {0, 0, 0, 0, 0, 0, 0},
        {0, 1, 1, 1, 1, 0, 0},
        {0, 0, 0, 0, 0, 0, 0},
        {0, 5, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0},
    };
    print_map(map);
    Astar(map, start, goal);

    return 0;
}


참고
다익스트라에서 개선된 알고리즘이라 그런지 다익스트라에 비해 어려운 편이였다.

이렇게 2개의 알고리즘에 대해서 공부했는데 알고리즘 수업시간에 배웠던 기억이 나서 알고리즘을 이해하는데 더 빨리 이해할 수 있었다.


이번 포스팅은 여기까지 마무리 하자 !

본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다. #프로젝트캠프 #프로젝트캠프후기 #유데미 #udemy #스나이퍼팩토리 #웅진씽크빅 #인사이드아웃 #IT개발캠프 #개발자부트캠프 #unity #유니티 #게임개발 #메타버스