코드에서 포인터를 사용할 경우 해당 헤더파일을 반드시 참조하게 되어 디버그에러시 해당 파일과 잘못연결되어 에러를 뱉는 경우가 허다합니다.
메모리에 올렸다 해제했다를 잘해야 하는데, 그러지 못한 경우 해당 코드에서 문제를 일으키는 것입니다.
포인터의 개념을 잘 숙지하여 해당 코드와 문제가 일으나지 않도록 관리하는 것이 필요합니다.
자, 이제 포인터에 대해서 자세히 알아봅시다..
포인터란 무엇일까?
포인터는 **"다른 변수의 메모리 주소를 가리키는 변수"**라고 생각하면 됩니다. 조금 더 쉽게 말하면, 포인터는 다른 데이터가 저장된 위치를 기억하는 변수예요.
비유로 이해하기
포인터를 주소로, 그리고 변수를 집이라고 생각해볼게요.
변수는 데이터가 저장된 "집"이에요.
포인터는 그 집이 어디 있는지 알려주는 "주소"입니다.
예를 들어, 집(변수) 안에 사람이 살고 있다고 하면, 포인터는 그 사람이 살고 있는 집의 위치를 알려주는 주소를 기억하고 있는 거예요.
포인터 사용해 보기
기본 변수와 포인터의 차이
보통의 변수는 데이터를 직접 저장합니다.
포인터는 데이터를 저장하는 것이 아니라, **그 데이터가 어디에 있는지(메모리 주소)**를 저장합니다.
cpp
코드 복사
int number = 10; // 그냥 변수, 10이라는 숫자를 저장함int* pointer = &number; // 포인터, number의 메모리 주소를 저장함
여기서:
int number는 숫자 10을 저장하는 일반 변수입니다.
int* pointer는 number 변수의 메모리 주소를 저장하는 포인터입니다.
&number는 number의 메모리 주소를 의미해요.
포인터의 기호
* : 포인터를 선언할 때 사용합니다. 예를 들어, int*는 "정수형을 가리키는 포인터"라는 의미입니다.
& : 변수의 메모리 주소를 얻기 위해 사용합니다.
* (역참조) : 포인터가 가리키는 주소에 있는 값을 가져오기 위해 사용합니다.
cpp
코드 복사
#include<iostream>usingnamespace std; intmain(){ int number = 10; // 일반 변수int* pointer = &number; // 포인터 변수, number의 주소를 저장 cout << "number의 값: " << number << endl; // number의 값 출력 (10) cout << "pointer가 가리키는 값: " << *pointer << endl; // 포인터가 가리키는 값 출력 (10) cout << "number의 주소: " << &number << endl; // number의 메모리 주소 출력 cout << "pointer의 값: " << pointer << endl; // pointer에 저장된 주소 출력 (number의 주소와 같음)return0; }
왜 포인터를 사용할까?
포인터가 중요한 이유는 메모리의 직접적인 접근과 효율적인 데이터 처리를 가능하게 하기 때문이에요.
동적 메모리 할당: 프로그램이 실행될 때 필요한 만큼 메모리를 요청하고 사용할 수 있어요.
예를 들어, 프로그램이 실행될 때 사용자가 원하는 만큼의 데이터를 저장하려면, 포인터를 사용해 메모리를 동적으로 할당할 수 있습니다.
cpp
코드 복사
int* p = newint; // 정수형 메모리 공간을 동적으로 할당받고, 그 주소를 p에 저장 *p = 20; // 할당된 메모리 공간에 20을 저장delete p; // 사용한 메모리를 해제
데이터 공유: 함수에서 변수를 복사하지 않고, 원래 데이터를 직접 변경할 수 있어요.
예를 들어, 함수에 변수의 포인터를 전달하면 함수 내부에서 그 값을 직접 변경할 수 있습니다.
cpp
코드 복사
voidchangeValue(int* p){ *p = 100; // 포인터가 가리키는 변수의 값을 변경 } intmain(){ int number = 10; changeValue(&number); // number의 주소를 함수에 전달 cout << "number의 값: " << number << endl; // 출력 결과: 100return0; }
배열 처리: 포인터는 배열과 밀접하게 연관되어 있어요. 배열의 첫 번째 요소의 주소를 통해 전체 배열을 처리할 수 있습니다.
cpp
코드 복사
int arr[3] = {1, 2, 3}; int* p = arr; // 배열의 첫 번째 요소의 주소를 저장 cout << *p << endl; // 첫 번째 요소 출력 (1) cout << *(p + 1) << endl; // 두 번째 요소 출력 (2)
주의할 점
잘못된 포인터 접근: 초기화되지 않은 포인터를 사용하면 프로그램이 충돌할 수 있습니다. 항상 포인터를 사용할 때는 초기화하고 유효성을 확인하는 것이 중요합니다.
cpp
코드 복사
int* p = nullptr; // 포인터를 초기화하지 않으면 위험!
동적 메모리 해제: 동적으로 할당한 메모리를 해제하지 않으면 메모리 누수가 발생합니다. delete를 사용해 메모리를 꼭 해제해야 합니다.
엔진을 실행할 때 SM6를 사용할 경우 AutogenShaderHeaders.ush 파일이 Game\Intermediate\ShaderAutogen\PCD3D_SM6\
폴더에 생성되게 됩니다.
쿠킹 테스트 중 C:\Users\User\AppData\Local\Temp\UnrealShaderWorkingDir 폴더에 생성된 파일과 충돌이 발생하길래 삭제한 적이 있습니다。
그 이후 엔진 실행 중 셰이더 컴파일이 안되서 따로 백업해 둔 프로젝트가 있어서 간신히 위기를 넘어 간 적이 있습니다.
위 파일을 단순히 덮어씌운다고 해결되지도 않기 때문에 지우는 순간 프로젝트를 영영 잃게 될 수 있기 때문에 신중함이 필요합니다. 셰이더 쿠킹 문제가 터졌을 땐 캐싱 파일들을 바로 지우지 않고, 엔진과 게임 폴더내에 있는 Intermediate / DerivedDataCache / Saved 폴더를 제거후 엔진을 다시 실행하는 방식으로 접근하는 것이 가장 좋은 방법입니다.
* 1번은 엔진을 실행하는데 필수로 필요한 파일들이 있으며, 빌드 성공시에 해당 폴더의 \Engine\Binaries\Win64 폴더로 이동하여 UnrealEditor.exe 파일을 실행하면 엔진이 실행하게 된다.
* UE5.sln 파일을 실행한 후 IDE 프로그램에서 디버그나 빌드를 눌러도 실행된다.
* 아래는 Engine 폴더 내부인데, 빨간 줄을 친 부분은 엔진을 실행하는 데 필수 요소 들은 아니며 엔진을 실행하거나 하는등의 바이너리화 작업중에 발생하는 파일들이다. 따라서 엔진을 다른 컴퓨터나 폴더로 옮길수 해당 폴더들은 제외하고 옮겨도 실행하는데 문제가 없다.
* 2번은 엔진 실행후 해당 엔진을 통해 만들어진 게임 폴더들이다. 아래 폴더 구조를 통해 상세히 알아 보자. 먼저 여기서도 엔진과 마찬가지로 아래의 빨간색으로 표시된 폴더들은 구동시 반드시 필요한 폴더가 아니다.
1. Content 폴더 : 게임실행시 블루프린트와 아트어셋 데이터들이 생성되는 장소이다. 엔진코드나 플러그인을 딱히 건드리지 않았을 경우 해당 폴더만 옮겨도 게임내부의 어셋들을 다른 프로젝트에서 확인이 가능하다. 하지만, 안되는 경우도 꾀 있으니 해당 폴더만으로 모든작업된 내용이 똑같이 발동 될거라 섣불리생각하지는 말자. 예를들어 tag나 Default.ini 등에서 바뀌어진 정보는 해당 폴더에 있지 않다.
2. Plugins 폴더 : 게임실행 후 설치한 플러그인나 외부적으로 제작한 플러그인들이 생성되는 장소이다. 해당 폴더에 설치된 내용이 있고, game.uproject 파일에서 true / false 설정을 할 수 있다. 다만 언리얼에서 기본 제공하는 플러그인들은 uproject 파일에서 true 했을 경우에 거의 잘 되긴 하지만 외부에서 가져온 플러그인들은 Source에서 제어해줘야만 정상 작동하는 PlugIns들도 있다는 것을 주의하자.
3. Source 폴더 : 소스코드 빌드된 엔진에서 가장 중요한 부분이라 생각하면 될거 같다. 요 부분에서 엔진 코드 / 게임 코드 / 플러그인 코드 등을 C++ 로 제어하게 된다. IDE 프로그램에서 처리되는 모든 소스코드가 이곳을 통해 이루어진다고 보면 된다. 그만큼 민감하게 예민한 곳이라 적절하게 코드작업이 이루어지지 않을 경우 빌드에러가 발생하여 게임 / 엔진 모두가 망가질수 있음을 주의하자. Unreal C++ 코딩 카테고리에서 참고사항을 잘 확인하며 신중하게 작업을 진행해야 한다.
마지막 패키징에 실패할 때 치명적인 오류가 없었지만, 플러그인 관련 warning 만으로도 패키징을 실패할 수 있음을 알았습니다.
에러 로그 리포트에서 플러그인 관련된 내용들을 잘 서치하여 코드에서 사용되고 있지만 빠져있는 플러그인들을 .uproject 에 잘 로드해 줘야 한다.
예시 : UATHelper: Packaging (Windows): Warning: C:\UnrealEngine\GyGame\GyGame.uproject does not list plugin 'ModularGameplay' as a dependency, but module 'GyGame' depends on 'ModularGameplay'.
아래 코드를 .uprject 파일에 추가한다.
{
"Name": "ModularGameplay",
"Enabled": true
},
설치된 플러그인도 아닌데 엔진에 기본 깔려 있어서 로드하다가 실패하는 경우도 있다.. 이건 뭐..
Plastic SCM 플러그인 오류.
예시 - Warning LogStreaming Failed to read file '../../../Engine/Plugins/Developer/PlasticSourceControl/Resources/Icon128.png' error.
이 경고는 언리얼 엔진이 특정 파일(../../../Engine/Plugins/Developer/PlasticSourceControl/Resources/Icon128.png)을 읽으려고 시도했지만, 파일이 존재하지 않거나 손상되었기 때문에 실패했다는 메시지입니다. 이 경고는 주로 Plastic SCM Source Control 플러그인과 관련이 있습니다. 플러그인 자체가 동작하는 데 필요한 리소스가 없을 때 발생합니다.
컴퓨터를 재부팅한 뒤 Visual Studio와 관련된 파일이 완전히 삭제되었는지 확인합니다.
이 과정을 통해 Visual Studio를 완전히 제거할 수 있습니다.
재 설치 후 5.4.4에 타겟에 맞는 구성으로 비주얼 스튜디오를 다시 까는 방법.
언리얼 런처를 실행하고 5.4.4 엔진을 실행후 C++ 프로젝트를 새로 만든다.
이 때 visual Studio 2022 를 깔아야 한다고 안내가 나오고, 자동으로 visual studio installer 를 설치하게 된다.
인스톨러 설치 후 아래에서 두가지만 선택하여 비주얼 스튜디오를 설치한다.
이후에 샘플 프로젝트를 c++ 로 만들어 패키징까지 해보고 이상이 없는 지 체크한다.
이후에 패키징이 정상적으로 되고, 실행도 온전히 된다면 기본적인 구성은 어느정도 완료됐다고 보면 된다.
이제 기존 소스코드 받은 5.4.4 의 Setup.bat / GenerateProjectFiles 를 실행한뒤 다시 ue5.sln 파일을 실행한다. * 이 때 새 프로젝트가 비주얼스튜디오가 열릴 때 신뢰할 수 없는 소스들을 열면 불안하게 프로젝트가 열려 빌드가 되지않을 수 있어 열지 않는것이 좋다.
그런 후 UE5.Sln 파일을 실행하고 솔루션 'UE5'에 마우스 오른쪽 버튼을 누르고 솔루션 빌드를 시작합니다.
위 작업은 최종 작업으로서 최소2시간에서 컴퓨터 사양따라 반나절 이상 소모되는 경우도 있으니 주무시기전에 하거나 다른 일을 하고 오시는 것을 추천드립니다.
여기까지 진행후 빌드 성공이 뜨면 나만의 커스텀 엔진을 생성하는데 성공하게 되고, Engine/Binaries/Win64 폴더에서 UnrealEditor를 실행하면 커스텀 엔진이 실행되게 되고, 거기서 프로젝트를 새로 만들면 나만의 엔진에서 나만의 게임을 만들수 있게 됩니다.