https://www.udemy.com/course/unreal-engine-5-the-ultimate-game-developer-course/

 

 

언리얼 엔진에서 Large World Open World

 

Large World는 하나의 맵에서 다른 맵으로 이동하면 기존의 맵을 언로드하고 다른 맵을 로드하는 방식으로 작동한다.

 

Open World는 하나의 큰맵에서 섹션을 나누고 플레이어가 있는 섹션만 로드한다. -> 월드 파티션 

언리얼에서 제공해주는 레벨 생성에에는오픈월드가 있다.

오픈월드 상의 미니맵

 

맵의 구성요소를 살펴보자 

1. 하늘 

하늘을 구성하는 요소에는

●Sky AtmosPhere(대기) 

실제 지구의 대기처럼 빛을 산란시켜 준다. 두개의 다른 광원을 통해 달과 태양을 구현할 수도 아니면 2개의 태양을 구현할 수 도 있다.

● Directional Light(지향성 광원)

한방향으로 나아가는 빛으로 보통 태양빛으로 사용된다. 

대기와 지향성광원 추가화면
광원 2개

이때 라이트의 기동성은 

static 일때는 위치나 방향,강도 색깔도 바꿀 수 없지마 연산이 빠르다

Stationary - 색, 강도 변경가능하지만 위치, 회전 변경불가 정적인 물체에 빛을 비춰 그림자생성

Movable - 동적 그림자생성, 가장 비싸지만  현실감\

 

●Sky Light - 먼거리의 부분을 포착, 게임전체에 균일한 조명 실시간 캡처가능

안개와 구름 

● Exponential height Fog

안개 효과는 높이에 따라 안개의 밀도가 기하급수적으로 증가하는 특성을 가지고 있다.

● Volumetric Clouds

동적 구름 

Fog와 Clouds 적용화면

그 다음 온도와 회전을 달리주면 이렇게 된다. 

결과화면

 

풍경- 땅 만들기

LandScape는 여러 매쉬가 펼처저 있는 것으로 이것을 수정하고 조작하는 것으로 여러 땅모양을 만들 수 있다.

랜드스케이프 모드로 들어간 후 32 x 32 짜리의 Landscape를 만들어주자

LandScape 만든 결과

 

조각과 침식등을 추가해 사막처럼 만들어보았다.

 

이후Material을 추가해주었다. 

Material은 새로운 Material을 만든 뒤 여러 Material을 조합해서 만들어주었다.

만든 Material을 LandScape 머티리얼로 넣어주고 페인트의 레이어에서 +한뒤 Weight 로 선택해서 만들어주면 된다.

 

결과화면

https://school.programmers.co.kr/learn/courses/30/lessons/160585

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

틱텍토 판이 주어질 때 이게 규칙을 지켜서 진행한 틱택토에서 나올 수 있는 상황인지 검사하면 되는 것이다

O가 선공 X가 후공이다. 

안되는 조건을 모두 검사하는 것이 중요하다. 

O의 개수가 X보다 많거나 같아야하고 많을 때는 2개이상 많아질 수 없다. 

둘 다 이길 수는 없다.

만약 O가 이겼을 때는 X의 개수보다 1개 많아야한다.

만약 X가 이겼을 때는 X가 후공이여서 O의 개수와 같은 수여야한다.

#include <string>
#include <vector>

using namespace std;
//O와 X의 개수를 세고 안되는 조건인지 검사
bool isWin(const vector<string>& board,char mark)
{
    //가로
    for(int i=0;i<3;i++)
    {
        if(board[i][0]==mark && board[i][1]==mark && board[i][2]==mark) return true;
    }
    //세로
    for(int i=0;i<3;i++)
    {
        if(board[0][i]==mark && board[1][i]==mark && board[2][i]==mark) return true;
    }
    if(board[0][0]==mark && board[1][1]==mark && board[2][2]==mark) return true;
    if(board[0][2]==mark && board[1][1]==mark && board[2][0]==mark) return true;
    
    return false;
}

int solution(vector<string> board) {
    int ocnt=0,xcnt=0;

    
     // O와 X의 개수를 센다.
    for (const string& row : board) {
        for (char cell : row) {
            if (cell == 'O') ocnt++;
            else if (cell == 'X') xcnt++;
        }
    }
    
    if(xcnt>ocnt) return 0;
    
    if(ocnt>xcnt+1) return 0;
    
    bool winO =isWin(board,'O');
    bool winX =isWin(board,'X');
    
    //둘 다 이길수는 없다
    if(winO && winX) return 0;
    //만약 O가 이겼을 때 O의 개수는 X보다 1개많아야 한다.
    if(winO && ocnt!=xcnt+1) return 0;
    //만약 x가 이겼을 때 x의 개수는 O와 같아야한다. 
    if(winX && xcnt!=ocnt) return 0;
    
    return 1;
}

 

 

https://school.programmers.co.kr/learn/courses/30/lessons/12952

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

N-Queen 문제는 대표적인 백트래킹 문제이다. 

이때 백트래킹은 

모든 가능한 경우를 탐색하기 위해 사용하는 기법 중 하나로, 탐색 과정에서 조건을 만족하지 않는 경우를 미리 배제함으로써 탐색 효율을 높인다. 

 

이문제를 백트래킹으로 푸는 과정은 다음과 같다.

 

  • 체스판의 각 열에 대해 퀸을 한 행씩 배치한다.
  • 퀸을 배치할 때마다 현재 퀸이 다른 퀸과 공격하지 않는지 확인한다.
  • 만약 조건을 만족하지 않는다면 해당 배치를 포기하고, 다른 행에 퀸을 배치해 본다. - 백트래킹
  • 모든 퀸을 배치할 수 있다면 경우의 수를 추가해준다.
  • 모든 가능한 배치를 탐색한 후 경우의 수를 반환 

abs(positions[i] - col) == abs(i - row) -> 1이나 -1 방향에 퀸이 있는지 확인 -> 대각선

#include <string>
#include <vector>
#include <cmath>

using namespace std;

bool isValid(const vector<int>& positions, int row, int col) {
    for (int i = 0; i < row; ++i) {
        // 같은 열에 퀸이 있는지, 또는 대각선에 퀸이 있는지 확인
        if (positions[i] == col || abs(positions[i] - col) == abs(i - row)) {
            return false;
        }
    }
    return true;
}

void solveNQueens(int n, int row, vector<int>& positions, int& answer) {
    if (row == n) {
        // 모든 퀸을 배치한 경우
        ++answer;
        return;
    }

    for (int col = 0; col < n; ++col) {
        if (isValid(positions, row, col)) {
            positions[row] = col;
            solveNQueens(n, row + 1, positions, answer);
            positions[row] = -1; // 백트래킹
        }
    }
}

int solution(int n) {
    int answer = 0;
    vector<int> positions(n, -1); // 각 열에 대한 퀸의 위치
    solveNQueens(n, 0, positions, answer);
    return answer;
}

 

https://www.youtube.com/watch?v=ljmOsZVrtok&list=PLiSlOaRBfgkcPAhYpGps16PT_9f28amXi&index=22

오늘은 AI가 플레이어를 감지하고 쫓아 다닐 수 있도록 만들어보자 

 

일단 AI_Controller에 컴포넌트를 추가해서 플레이어를 감지할 수 있도록 AIPerception 컴포넌트를 추가해준다.

이 컴포넌트에서 감지환경설정을 시야구성으로 바꿔준다.

이때 센스는 기본을 따르도록 하고 추후과정에서 이 값을 조절하기로 하자.

AIPerception 컴포넌트

귀속감지는 블루프린트 상의 오류방지를 위해 모두 체크해주자

 

타킷 퍼셉션 업데이트 시 이벤트 구성

 

블랙보드에 키를 추가해서 만약 감지했을 때 Target Actor이면 따라가고 아니면 돌아다니는 과정을 수행하도록 한다.

 

이제 AI 블랙보드 트리에서 Chase Target Sequence를 만들어주고 새로운 테스크를 만들어주자 

 

블랙보드 키를 가져와서 액터에 넣어주고 이를 따라가게 하자 멈추는 거리는 100정도로 설정해주자 

 

이후 블랙보드트리에서 

왼쪽에 Chase를 붙여서 이게 먼저 실행될 수 있도록 하며 BlackBoard값에 따라 해당 테스크가 수행되도록 데코레이터를 추가해준다.

전체 트리 및 Chase쪽 데코레이터 설정
Patrol쪽 데코레이터 설정

 

하지만 아직 플레이어를 감지하지 못하고 있다. 이 부분은 플레이어 쪽에 감지 자극소스를 부착해주면된다.

 

그리고 프로젝트 루트폴더에서 Config/DefaultGame.ini 파일을 수정해준다.

https://school.programmers.co.kr/learn/courses/30/lessons/42890

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

조합을 통해 키를 조합한 후 이에 대한 유일성과 최소성을 만족시키는 값을 찾아야한다.

 

유일성 : 유일한 값 

최소성 : 여러 속성을 조합해 만든 키가 유일성을 가질 때, 한 속성을 제외해도 유일성이 유지되면 최소성을 만족하지 않는다.

 

어떻게 검사할까 찾아보다가 비트마스크 방법으로 조합과 유일성 최소성 검사를 하는게 직관적이라는 것을 알게 되었다. 

조합

각 열을 하나의 비트로 나타내면, 비트마스크의 각 비트는 열의 포함 여부를 나타낼 수 있다. 

예를 들어, 열이 3개인 경우:

  • 000: 아무 열도 포함하지 않음
  • 001: 첫 번째 열만 포함
  • 010: 두 번째 열만 포함
  • 011: 첫 번째와 두 번째 열 포함
  • 100: 세 번째 열만 포함
  • 101: 첫 번째와 세 번째 열 포함
  • 110: 두 번째와 세 번째 열 포함
  • 111: 모든 열 포함

이런식으로 표현할 수 있다. 

이 방법을 통해 조합을 생성하려면 2^열의 수로 순회하면 된다. 

조합

 // 모든 가능한 속성 조합을 생성
    for (int comb = 1; comb < (1 << columns); comb++) {
        // 유일성과 최소성 검사
        if (isUnique(relation, columns, comb) && isMinimal(comb, candidateKeys)) {
            // 유일성과 최소성을 만족하는 경우 후보키로 추가
            candidateKeys.push_back(comb);
        }
    }

 

유일성 검사

유일성 검사또한 비트마스크를 통해 열 포함여부를 검사해준다. 

// 유일성을 검사하는 함수
bool isUnique(vector<vector<string>>& relation, int columns, int comb) {
    set<string> uniqueCheck;
    for (int i = 0; i < relation.size(); i++) {
        string key = "";
        for (int j = 0; j < columns; j++) {
            // comb의 각 비트를 확인하여 해당 열을 포함하는지 검사
            if (comb & (1 << j)) {
                key += relation[i][j] + " ";
            }
        }
        // 유일성 검사: 이미 존재하는 키라면 false 반환
        if (uniqueCheck.find(key) != uniqueCheck.end()) {
            return false;
        }
        uniqueCheck.insert(key);
    }
    return true;
}

 

이때 직접적인 검사는  if (comb & (1 << j)) 부분에서 이루어진다. 

comb & (1 << j) 이 부분에서 어떻게 계산이 이루어지는지는 예시를 통해 알아보자

 

  • comb의 j번째 비트가 1인지 검사하려면, 1 << j를 사용하여 j번째 비트만 1인 숫자를 만든다.
  • comb & (1 << j)를 수행하면, comb의 j번째 비트가 1일 때만 결과가 1이 됩니다. 그렇지 않으면 결과가 0이 된다.
  • comb = 5 (0101)
  • 열의 개수 columns = 4
  • j = 0:
    • 1 << 0는 0001
    • comb & 0001은 0101 & 0001이므로 결과는 0001 (참)
  • j= 1:
    • 1 << 1은 0010
    • comb & 0010은 0101 & 0010이므로 결과는 0000 (거짓)

 

  • j = 2:
    • 1 << 2은 0100
    • comb & 0100은 0101 & 0100이므로 결과는 0100 (참)
  • j = 3:
    • 1 << 3은 1000
    • comb & 1000은 0101 & 1000이므로 결과는 0000 (거짓)

j = 0과 j = 2에서 조건이 참이므로, 첫 번째와 세 번째 열의 값이 key에 추가

 

최소성 검사 

기존 후보키로 만들어진 것이 현재 조합의 부분집합에 포함된다면 후보키가 아니다.

// 최소성을 검사하는 함수
bool isMinimal(int comb, vector<int>& candidateKeys) {
    // 기존의 후보키들에 대해 검사
    for (int key : candidateKeys) {
        // 기존의 후보키가 현재 조합에 포함되는지 검사
        if ((comb & key) == key) {
            // 기존 후보키가 현재 조합의 부분집합이면 최소성 만족하지 않음
            return false;
        }
    }

    // 모든 기존 후보키가 현재 조합의 부분집합이 아니면 최소성 만족
    return true;
}

 

전체코드 

 

#include <iostream>
#include <vector>
#include <string>
#include <set>

using namespace std;

// 유일성을 검사하는 함수
bool isUnique(vector<vector<string>>& relation, int columns, int comb) {
    set<string> uniqueCheck;

    // 각 튜플(행)에 대해 검사
    for (int i = 0; i < relation.size(); i++) {
        string key = "";

        // comb의 각 비트를 확인하여 해당 열을 포함하는지 검사
        for (int j = 0; j < columns; j++) {
            if (comb & (1 << j)) {
                // comb의 j번째 비트가 1이면 해당 열을 key에 추가
                key += relation[i][j] + " ";
            }
        }

        // 유일성 검사: 이미 존재하는 키라면 false 반환
        if (uniqueCheck.find(key) != uniqueCheck.end()) {
            return false;
        }

        // 새로운 키를 set에 추가
        uniqueCheck.insert(key);
    }

    // 모든 튜플에 대해 유일성을 만족하면 true 반환
    return true;
}

// 최소성을 검사하는 함수
bool isMinimal(int comb, vector<int>& candidateKeys) {
    // 기존의 후보키들에 대해 검사
    for (int key : candidateKeys) {
        // 기존의 후보키가 현재 조합에 포함되는지 검사
        if ((comb & key) == key) {
            // 기존 후보키가 현재 조합의 부분집합이면 최소성 만족하지 않음
            return false;
        }
    }

    // 모든 기존 후보키가 현재 조합의 부분집합이 아니면 최소성 만족
    return true;
}

int solution(vector<vector<string>> relation) {
    int rows = relation.size();     // 튜플(행)의 개수
    int columns = relation[0].size(); // 속성(열)의 개수
    vector<int> candidateKeys;     // 후보키를 저장할 벡터

    // 모든 가능한 속성 조합을 생성
    for (int comb = 1; comb < (1 << columns); comb++) {
        // 유일성과 최소성 검사
        if (isUnique(relation, columns, comb) && isMinimal(comb, candidateKeys)) {
            // 유일성과 최소성을 만족하는 경우 후보키로 추가
            candidateKeys.push_back(comb);
        }
    }

    // 후보키의 개수 반환
    return candidateKeys.size();
}

int main() {
    vector<vector<string>> relation = {
        {"100", "ryan", "music", "2"},
        {"200", "apeach", "math", "2"},
        {"300", "tube", "computer", "3"},
        {"400", "con", "computer", "4"},
        {"500", "muzi", "music", "3"},
        {"600", "apeach", "music", "2"}
    };

    cout << solution(relation) << endl;  // 출력: 2
    return 0;
}

 

 

 

 

인덱스버퍼

현재 -> 사각형- 삼각형 2개  만약 이런식이라면 그 많은 도형을 표현하기엔 메모리가 너무 많이 소모된다.

-> 인덱스 버퍼사용

정점들에 인덱스(넘버)를 부여하여 사용

 

이걸 코드로 만들어보자 일단 정점을 하나 추가해보자 그리고 정점 순서에 맞게 좌표를 수정해준다.

//13 -> 012
//02 -> 213
_vertices[0].position = Vec3(-0.5f, -0.5f, 0.f);
_vertices[0].color = Color(1.f, 0.f, 0.f, 1.f);
_vertices[1].position = Vec3(-0.5f, 0.5f, 0.f);
_vertices[1].color = Color(1.f, 0.f, 0.f, 1.f);
_vertices[2].position = Vec3(0.5f, -0.5f, 0.f);
_vertices[2].color = Color(1.f, 0.f, 0.f, 1.f);
_vertices[3].position = Vec3(0.5f, 0.5f, 0.f);
_vertices[3].color = Color(1.f, 0.f, 0.f, 1.f);

 

인덱스버퍼에 필요한 변수를 만들고 Geometry에 인덱스 버퍼 부분을 만들어주자

변수 

//인덱스버퍼 - 이거도 Geometry에 포함
vector<uint32> _indices;
ComPtr<ID3D11Buffer> _indexBuffer = nullptr;
//Index
{
	_indices = { 0,1,2,2,1,3 };
}

//IndexBuffer
{
	D3D11_BUFFER_DESC desc;
	ZeroMemory(&desc, sizeof(desc));
	desc.Usage = D3D11_USAGE_IMMUTABLE;			//gpu만 read only
	desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
	desc.ByteWidth = (uint32)(sizeof(uint32) * _indices.size());

	D3D11_SUBRESOURCE_DATA data;
	ZeroMemory(&data, sizeof(data));
	data.pSysMem = _indices.data();			//첫번째 시작주소 cpu값이 복사된다.

	HRESULT hr= _device->CreateBuffer(&desc, &data, _indexBuffer.GetAddressOf());
	CHECK(hr);
}

 

인덱스 버퍼 세팅

IA 세팅 과정 중에 -> 랜더에 있는 세팅부분에 추가해줘야한다. 

기존

	//IA - 세팅부분
	_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer.GetAddressOf(),&stride, &offset);
	_deviceContext->IASetInputLayout(_inputLayout.Get());
	_deviceContext->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);		//삼각형으로 만들어주기

 

수정

	//IA - 세팅부분
	_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer.GetAddressOf(),&stride, &offset);
	_deviceContext->IASetIndexBuffer(_indexBuffer.Get(), DXGI_FORMAT_R32_UINT,0);
	_deviceContext->IASetInputLayout(_inputLayout.Get());
	_deviceContext->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);		//삼각형으로 만들어주기
    
    
    //인덱스를 통해 그려주기
    _deviceContext->DrawIndexed(_indices.size(), 0, 0);

 

이렇게하면 Index버퍼를 사용할 준비가 완료되었다. 

 

실행화면

 

정점버퍼와 인덱스버퍼의 차이점

-> 인덱스버퍼는 삼각형에 넘어가는 정점 개수를 줄여줘 메모리적으로 효율적으로 작업할 수 있다.

 

정점버퍼와 인덱스버퍼의 조합으로 삼각형이 어떻게 만들어지는지 판단할 수 있다.

 

이제 사각에 이미지를 띄워보자 

 

중요한 점 : uv좌표, 쉐이더코드 수정

 

uv좌표 - 이미지의 좌상단 0,0 우하단 1,1로 했을 때 퍼센티지로 가져오기 위함 

uv좌표

 

Stuct.h 수정 및 정점정보 수정

#pragma once
#include "Types.h"


struct Vertex
{
	Vec3 position;		//12바이트 0부터시작
	//Color color;		//12부터시작
	Vec2 uv;			
};

 

//정점정보
{
	_vertices.resize(4);

	//13 -> 012
	//02 -> 213
	_vertices[0].position = Vec3(-0.5f, -0.5f, 0.f);
	_vertices[0].uv = Vec2(0.f, 1.f);
	//_vertices[0].color = Color(1.f, 0.f, 0.f, 1.f);
	_vertices[1].position = Vec3(-0.5f, 0.5f, 0.f);
	_vertices[1].uv = Vec2(0.f, 0.f);
	//_vertices[1].color = Color(1.f, 0.f, 0.f, 1.f);
	_vertices[2].position = Vec3(0.5f, -0.5f, 0.f);
	_vertices[2].uv = Vec2(1.f, 1.f);
	//_vertices[2].color = Color(1.f, 0.f, 0.f, 1.f);
	_vertices[3].position = Vec3(0.5f, 0.5f, 0.f);
	_vertices[3].uv = Vec2(1.f, 0.f);
	//_vertices[3].color = Color(1.f, 0.f, 0.f, 1.f);
}

 

그리고 이에 맞게 InputLayout을 고쳐줘야하는데 이 때 쉐이더와 맞춰주어야하기 때문에 

많이 사용하는 TEXCOORD - 텍스처의 좌를 사용한다. 

InputLayout 및 쉐이더 수정

struct VS_INPUT
{
    float4 position : POSITION;
    //float4 color : COLOR;
    float2 uv : TEXCOORD;
};

struct VS_OUTPUT
{
    float4 position : SV_POSITION;      //시스템 VALUE  무조건 있어야한다.
    //float4 color : COLOR;
    float2 uv : TEXCOORD;
};
	//입력에 대한 정보 ~바이트부터 뛰면 칼러인지 
	D3D11_INPUT_ELEMENT_DESC layout[] =
	{
		{"POSITION",0,DXGI_FORMAT_R32G32B32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA,0},
		{"TEXCOORD",0,DXGI_FORMAT_R32G32_FLOAT,0,12,D3D11_INPUT_PER_VERTEX_DATA,0}
	};

 

이미지파일 소스를 gpu쪽에도 건네줘서 알고있도록 해야한다. 

레지스터에 등록해주기

//함수의 인자같은 존재, 오브젝트마다 달라질수있다.
Texture2D texture0 : register(t0);      //레지스터에 등록
SamplerState sampler0 : register(s0);

 

등록해준 레지스터를 통해 텍스처 색상을 가져오기 

//모든 픽셀단위 대상 - 색상관련
float4 PS(VS_OUTPUT input) : SV_Target  //랜더 타켓하는 곳으로 보내주기
{
    float4 color = texture0.Sample(sampler0, input.uv);     //텍스처0번 uv좌표를 통해 색상 빼오기
    
    return color;
}

해당 이미지파일을 cpu에서 리소스로 가지고 gpu쪽에도 만들어준 다음 파이프라인에 연결

 

픽셀쉐이더부분에서 CreateSRV를 통해 할 수 있다.

SRV 쉐이더 리소스 뷰 만들어주기 

SRV변수 추가 -이미지를 어떻게 쓸것인가 

이것을 통해 이미지를 건네준다.  

//SRV - 이미지를 어떻게 쓸것인가
ComPtr<ID3D11ShaderResourceView> _shaderResourceView = nullptr;
void Game::CreateSRV()
{
	DirectX::TexMetadata md;
	DirectX::ScratchImage img;
	HRESULT hr = ::LoadFromWICFile(L"cat.png", WIC_FLAGS_NONE, &md, img);
	CHECK(hr);

	//쉐이더리소스뷰 만들기
	hr = ::CreateShaderResourceView(_device.Get(), img.GetImages(), img.GetImageCount(), md, _shaderResourceView.GetAddressOf());
	CHECK(hr);
}

 

이제 Init부분에 함수를 넣고 Render의 PS부분에서 이미지와 연결시켜주자 

void Game::Init(HWND hwnd)
{
	_hwnd = hwnd;
	_width = GWinSizeX;
	_height = GwinSizeY;


	CreateDeviceAndSwapChain();
	CreateRenderTargetView();
	SetViewport();

	//삼각형 그리기 파트
	CreateGeometry();
	CreateVS();
	CreateInputLayout();
	CreatePS();

	CreateSRV();
}
	//PS
	_deviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0);
	_deviceContext->PSSetShaderResources(0, 1, _shaderResourceView.GetAddressOf());		//0번슬롯에 1개

 

실행화면 

 

uv 1일때 

uv 0.5일때

 

uv좌표가 1을 초과했을 때 어떻게 할지

-> 레스터라이저단계의 샘플스테이트를 통해 관리 

 

어떤게 범용적 어떤게 오브젝트에 종속적인지 생각해보기 

ex) GeoMetry - 매쉬 - 오브젝트 대상

쉐이더 - 범용적

https://www.youtube.com/watch?v=UeG9RAVE8sE&list=PLiSlOaRBfgkcPAhYpGps16PT_9f28amXi&index=21

 

오늘은 적이 가지게 될 AI행동 기초를 만들어 볼 것이다.

 

일단 AI폴더를 만들어주고 안에

여러 npc의 기본 바탕이 될 Black Board와 Behavior Tree, AIController 블루프린트 크래스를 만들어준다. 

 AIController 클래스에서는 시작될 때 , Run Behavior Tree를 실행시켜 만들어둔 Behavior Tree가 작동하도록 한

다. 

 

전에 만들어둔 더미캐릭터를 AI폴더로 가지고 온다. 

그리고 디테일 창에 ai를 검색한 후 폰부분을 바꿔주면 된다.

Place or Spawned가 오류를 줄일 수 있다. 

 

이제 본격적인 흐름도를 만들어보자 이건 트리상에 만들 것인데 Selector와 Seqeunce를 통해 트리구조를 만들고 

새 테스크를 Task폴더에 만들어주자 이번 Task는 Patrol Random Point이다. 

이 테스크는 AI플레이가 시작될 때 호출되는 Receive Execute AI라는 이벤트를 오버라이드 해주는 것으로 동작하도록 만든다. 

 

radius 안의 원의 랜덤한 위치로 움직일 수 있도록 만들어본다. 

그리고 Tree안에 Task를 추가해준다. 

 

그리고 NavMeshBoundsVolume을 통해 AI가 갈 수 있는 길을 표시해준다. 

->이것을 통해 AI가 움직일 수 있다. 

P키를 누르면 AI가 갈 수 있는 모든 길을 보여준다. 

 

이제 랜덤한 위치로 AI가 이동한다.

 

움직이는 AI

 

이제 AI가 자연스럽게 움직이도록 만들어보자 

애니메이션 블루프린트를 만들고 이전에 만들어둔 플레이어의 애니메이션 블루프린터를 참고하자. 

-> 스테이트를 통해 관리되고 있다.

캐릭터 애니메이션 블루프린트의 Locomotion을 복사해서 DefaultSlot에서 작동하도록 만들어 주자

그다음 AI의 매쉬에서 애니메이션 클래스를 변경해준다. 

 

그리고 이벤트 그래프에 속도를 조절하는 부분을 플레이어 부분에서 가져와서 붙여주면 움직이는 모습을 볼 수 있다.

움직이게 하기

 

https://school.programmers.co.kr/learn/courses/30/lessons/150368

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

할인율을 정하고 이에 대한 모든 이모티콘을 dfs로 탐색해서 시뮬레이션을 돌리면 된다. 

 

중요한 부분은

할인율을 정하고 dfs 돌리는 부분

  for (int i = 0; i < 4; i++) {
        price[cnt].first = (100 - dis[i]) * emoticons[cnt] / 100;  // 할인된 가격
        price[cnt].second = dis[i];  // 할인율
        dfs(cnt + 1, n, users, emoticons);
    }

한 할인율을 이모티콘들에 다 적용했을 때의 이윤계산 

if (cnt == n) {  // 모든 이모티콘에 할인율을 대입했을 때 
        int plus = 0, sum = 0;
        for (int i = 0; i < users.size(); i++) {
            int tmp = 0;
            for (int j = 0; j < n; j++)
                if (users[i][0] <= price[j].second) // 원하는 할인율 이상이면 구매
                    tmp += price[j].first;  
            if (tmp >= users[i][1]) // 구매값이 정해진 가격 이상이면 이모티콘플러스 가입
                plus++;  
            else
                sum += tmp;  // 이윤++
        }
        if (plus > answer[0]) {  // 가입자수가 더 많으면
            answer[0] = plus;
            answer[1] = sum;
        } else if (plus == answer[0] && sum >= answer[1])
            answer[1] = sum;  // 가입자수가 같고 이윤이 크면
        return;
    }

 

전체코드

#include <string>
#include <vector>
using namespace std;

int dis[4] = {40, 30, 20, 10};  // 할인율
vector<pair<int, int>> price(7, {0, 0});  // 할인된 가격, 할인율
vector<int> answer(2, 0);

void dfs(int cnt, int n, vector<vector<int>> users, vector<int> emoticons) {
    if (cnt == n) {  // 모든 이모티콘에 할인율을 대입했을 때 
        int plus = 0, sum = 0;
        for (int i = 0; i < users.size(); i++) {
            int tmp = 0;
            for (int j = 0; j < n; j++)
                if (users[i][0] <= price[j].second) // 원하는 할인율 이상이면 구매
                    tmp += price[j].first;  
            if (tmp >= users[i][1]) // 구매값이 정해진 가격 이상이면 이모티콘플러스 가입
                plus++;  
            else
                sum += tmp;  // 이윤++
        }
        if (plus > answer[0]) {  // 가입자수가 더 많으면
            answer[0] = plus;
            answer[1] = sum;
        } else if (plus == answer[0] && sum >= answer[1])
            answer[1] = sum;  // 가입자수가 같고 이윤이 크면
        return;
    }
    for (int i = 0; i < 4; i++) {
        price[cnt].first = (100 - dis[i]) * emoticons[cnt] / 100;  // 할인된 가격
        price[cnt].second = dis[i];  // 할인율
        dfs(cnt + 1, n, users, emoticons);
    }
}

vector<int> solution(vector<vector<int>> users, vector<int> emoticons) {
    dfs(0, emoticons.size(), users, emoticons);
    return answer;
}

 

오늘은 삼각형을 그려볼 것이다. 

 

삼각형 하나를 그리더라도 전체 파이프라인은 한번 거쳐야한다. 

 

 

일단 기하학적인 도형을 만들어주는 함수를 만들고 정점을 나타내줄 구조체도 선언해준다.

 

Stuct.h

#pragma once
#include "Types.h"


struct Vertex
{
	Vec3 position;		//12바이트 0부터시작
	Color color;		//12부터시작
};

 

 

정점 선언해주기 

정점은 -1,1 사이의 범위에서 선언해준다. -> 화면 상의 좌표때문에 

정점 정보까지는 아직 CPU에서의 영역, 

RAM<->CPU VRAM<->GPU

->VRAM에도 같은 정보를 만들어줘야한다. 

-> _device->CreateBuffer(); 마지막인자로 정점버퍼를 받는다 -> 이를 통해 정점버퍼 초기화

 

D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Usage = D3D11_USAGE_IMMUTABLE;			//디폴트- GPU만 읽고 쓸수있음 
IMMUTABLE GPU만 읽을 수 있다. 
DYNAMIC- GPU : 읽기 CPU: 쓰기 
STAGING - CPU->GPU 데이터전송

 

메모리에 있는 정점 넘겨주기 전체코드 

이렇게 되면 이제 _vertices가 가지고있던 정점 정보를 gpu쪽으로 넘겨줘서 이후에는 gpu만 read only할 수 있도록 한다. 

//정점정보
{
	_vertices.resize(3);

	_vertices[0].position = Vec3(-0.5f, -0.5f, 0.f);
	_vertices[0].color = Color(1.f, 0.f, 0.f, 1.f);
	_vertices[1].position = Vec3(0.f, 0.5f, 0.f);
	_vertices[1].color = Color(1.f, 0.f, 0.f, 1.f);
	_vertices[2].position = Vec3(0.5f, -0.5f, 0.f);
	_vertices[2].color = Color(1.f, 0.f, 0.f, 1.f);
}

//정점버퍼
{
	D3D11_BUFFER_DESC desc;
	ZeroMemory(&desc, sizeof(desc));
	desc.Usage = D3D11_USAGE_IMMUTABLE;			//gpu만 read only
	desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	desc.ByteWidth = (uint32)(sizeof(Vertex) * _vertices.size());

	D3D11_SUBRESOURCE_DATA data;
	ZeroMemory(&data, sizeof(data));
	data.pSysMem = _vertices.data();			//첫번째 시작주소 cpu값이 복사된다.

	_device->CreateBuffer(&desc,&data, _vertexBuffer.GetAddressOf());
}

 

 

이제 입력에 대한 정보를 명시해줘야한다. 

void Game::CreateInputLayout()
{
	//입력에 대한 정보 ~바이트부터 뛰면 칼러인지 
	D3D11_INPUT_ELEMENT_DESC layout[] =
	{
		{"POSITION",0,DXGI_FORMAT_R32G32B32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA,0},
		{"COLOR",0,DXGI_FORMAT_R32G32B32A32_FLOAT,0,12,D3D11_INPUT_PER_VERTEX_DATA,0}
	};

	const int32 count = sizeof(layout)/sizeof(D3D11_INPUT_ELEMENT_DESC);
	_device->CreateInputLayout(layout,count,);
}

 

하지만 CreateInputLayout 함수의 매개변수에는 쉐이더에 관한 변수가 필요하다. 

그럼 쉐이더를 만들어주자 

쉐이더 -> 관련 코드를 넣어두는 하나의 파일

 

필터를 새로만들고 HLSL파일을 만들어보자 

쉐이더파일 생성

 

쉐이더

-> 1. 먼저 컴파일/ 빌드해서 cso파일을 사용 2.실행 순간에 코드를 읽어서 컴파일 -> 동적으로 사용

->먼저 만들어진걸 사용하는게 좋다. -> 문법적인 /오타 같은 오류를 컴파일 단계에서 알 수 있다. 

 

쉐이더 입력으로 들어오는 구조체부터 선언해주자 이때 InputLayout과 이름을 맞춰주자

struct VS_INPUT
{
    float4 position : POSITIONT;
    float4 color : COLOR;
};

 

쉐이더관련 코드를 완성해보자

정점 쉐이더와 픽셀 쉐이더 단계에 필요한 함수를 추가해준다.

struct VS_INPUT
{
    float4 position : POSITION;
    float4 color : COLOR;
};

struct VS_OUTPUT
{
    float4 position : SV_POSITION;      //시스템 VALUE  무조건 있어야한다.
    float4 color : COLOR;
};

//정점 쉐이더- 위치관련 -> 레스터라이저-정점바탕으로 도형만들고 내/외부 판단 및 보간
VS_OUTPUT VS(VS_INPUT input)
{
    VS_OUTPUT output;
    output.position = input.position;
    output.color = input.color;
    
    return output;
}

//모든 픽셀단위 대상 - 색상관련
float4 PS(VS_OUTPUT input) : SV_Target  //랜더 타켓하는 곳으로 보내주기
{
    
    
    return float4(1, 0, 0, 0);
}

 

그리고 이제 이 쉐이더를 가져올 함수를 만들자

만들기전에 정점/ 픽셀 쉐이더 관련 변수를 추가해준다. 

	//VS
	ComPtr<ID3D11VertexShader> _vertexShader = nullptr;
	ComPtr<ID3DBlob> _vsBlob = nullptr;
	//PS
	ComPtr<ID3D11PixelShader> _pixelShader = nullptr;
	ComPtr<ID3DBlob> _psBlob = nullptr;
void Game::LoadShaderFromFile(const wstring& path, const string& name, const string& version, ComPtr<ID3DBlob>& blob)
{
	const uint32 compileFlag = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;		//디버그 최적화 건너뛰기
	HRESULT hr = ::D3DCompileFromFile(
		path.c_str(),
		nullptr,
		D3D_COMPILE_STANDARD_FILE_INCLUDE,
		name.c_str(),
		version.c_str(),
		compileFlag,
		0,
		blob.GetAddressOf(),
		nullptr
	);

	CHECK(hr);
}

 

파일에 있던걸 가져와서 어떻게 작동하게 할지 건내줘야해서 가져오는 함수를

VS와 PS에서도 해야하기 때문에 관련 함수를 만들어 준다. 

void Game::CreateVS()
{
	LoadShaderFromFile(L"Default.hlsl", "VS", "vs_5_0", _vsBlob);

	HRESULT hr = _device->CreateVertexShader(_vsBlob->GetBufferPointer(),
		_vsBlob->GetBufferSize(), nullptr, _vertexShader.GetAddressOf());
	CHECK(hr);
}

void Game::CreatePS()
{
	LoadShaderFromFile(L"Default.hlsl", "PS", "ps_5_0", _psBlob);

	HRESULT hr = _device->CreatePixelShader(_psBlob->GetBufferPointer(),
		_psBlob->GetBufferSize(), nullptr, _pixelShader.GetAddressOf());
	CHECK(hr);
}

 

 

이제  Init 부분에 만든 함수들을 실행시키도록 넣어준다 .

 

void Game::Init(HWND hwnd)
{
	_hwnd = hwnd;
	_width = GWinSizeX;
	_height = GwinSizeY;


	CreateDeviceAndSwapChain();
	CreateRenderTargetView();
	SetViewport();

	//삼각형 그리기 파트
	CreateGeometry();
	CreateVS();
	CreateInputLayout();
	CreatePS();
}

 

 

그리고 실제로 그리기 부분은 렌더부분에서 이루어 지기때문에 파이프라인 단계에 맞게 채워준다.

void Game::Render()
{
	RenderBegin();			//초기화

	// IA - VS - RS - PS -OM
	//TODO : 그리기
	{
		uint32 stride = sizeof(Vertex);
		uint32 offset = 0;
		//IA - 세팅부분
		_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer.GetAddressOf(),&stride, &offset);
		_deviceContext->IASetInputLayout(_inputLayout.Get());
		_deviceContext->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);		//삼각형으로 만들어주기

		//VS
		_deviceContext->VSSetShader(_vertexShader.Get(), nullptr, 0);		//이걸로 일하게 

		//RS

		//PS
		_deviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0);
		//OM

		_deviceContext->Draw(_vertices.size(), 0);
	}

	RenderEnd();			//제출
}

 

실행화면 

 

 

만약 PS 단계에서 입력의 색을 따라 만들고 세 점을 RGB 하나씩 가지고 있도록 한다면 

 

//모든 픽셀단위 대상 - 색상관련
float4 PS(VS_OUTPUT input) : SV_Target  //랜더 타켓하는 곳으로 보내주기
{
    
    
    return input.color;
}

 

이런식으로 그라데이션 삼각형이 나오게 된다. 

-> 보간이 잘 이루어졌다. 

 

정점 / 자원 -> cpu만 가지고 있었는데 createVertexBuffer을 통해 gpu로 복사가 이루어진다.

gpu도 해당 자원을 가지고 있게 된다. 

IA - createVertexBuffer를 통해 자원을 사용하겠다는 의미

VS- 정점을 gpu에서 연산 처리 -> 정점에 대한 정보가 확정되어서 나가게 된다.

Rasterize- 삼각형있을 때 내/외부 판단, 보간 진행

PS- 모든 픽셀에 대한 색상 - 빛 / 그림자

랜더타켓 - 어디에다 그려줘

 

VS,PS- 세부적인 옵션을 건드릴 수 있도록하는 툴로 만들면 -> 머테리얼 

 

'게임공부 > Directx11' 카테고리의 다른 글

[Directx11]6. Constant Buffer(상수 버퍼)  (0) 2024.07.09
[Directx11][C++]5. 텍스처와 UV  (0) 2024.07.01
[Directx11]3.장치초기화  (0) 2024.06.27
[Directx11]2.초기설정  (0) 2024.06.26
[Directx11]그래픽스OT  (1) 2024.06.25

https://www.youtube.com/watch?v=-5jCh22feJg&list=PLiSlOaRBfgkcPAhYpGps16PT_9f28amXi&index=20

1. 인벤토리 카테고리에 해당하는 텍스트 만들어주기 

각 칸의 카테고리에 해당하는 텍스트를 옆에 붙여준다.

 

2. 활, 방패같은 장비는 등에 먼저 보이도록 로직 수정 

일단 활과 방패의 fbx 파일을 import 해주자 

https://sketchfab.com/3d-models/medieval-shield-b98b8f64d935415aab0fe9b70074511f#download

 

Medieval Shield - Download Free 3D model by Artem Mykhailov - Sketchfab

Artstation post: https://www.artstation.com/artwork/e012eY Based on the concept by Artyom Vlaskin: https://artyomvlaskin.cgsociety.org/okkw/928308

sketchfab.com

https://sketchfab.com/3d-models/wooden-bow-free-a762abcffc27478caf19dfaac086485d#download

 

Wooden Bow Free - Download Free 3D model by Red_Ilya - Sketchfab

Not the strongest bow, but when there is nothing at hand, it is the best

sketchfab.com

 

Fbx파일을 다운 받은 뒤 import 해주고 텍스처 설정을 해줘야한다. 

Shield 텍스처 설정
Bow 텍스처 설정

 

캐릭터 매쉬 골격에 소캣 추가해주기

등에 활과 방패가 들어올 수 있도록 소캣을 추가해준다. 

소캣
결과

이제 실제 매쉬에 스태틱 매쉬 컴포넌트를 추가해서 실제로 적용될 수 있도록 한다. 

스태틱매쉬를 만든 뒤 부모소캣을 위에서 만들어둔 소캣으로 적용하면 된다.

 

소캣 설정

 

이제 만든 매쉬에 들어갈 수  있도록 커스텀 이벤트를 만들어주자 

그리고 들어오는 아이템 타입에 따라 다른 이벤트가 호출되도록 스위치문을 구성해준다. 현재 아머는 없어서 그냥 스트링만 출력하도록 했다.

이벤트 구성 및 흐름도

 

DB 테이블에 방패를 추가해주고 스태틱매쉬와 소캣을 맞게 수정해준다.

DB테이블

 

결과화면

 

+ Recent posts