250127

Errata·2025년 1월 27일

개발 일지

목록 보기
64/350

✅ 오늘 한 일


  • 유튜브 강의 듣기 : GAME ASSET BEGINNER TUTORIAL
  • Project MR
  • 씹어먹는 C++


🎮 Random Study


Exporting from Blender to Unity (Part 4/5)

  • 추가했던 Sun과 Plane 삭제
  • Layout workspace 선택 > 우측 상단 Viewport 옵션에서 Material Preivew 선택 > 오브젝트 이름을 Barrel로 바꾸기 (유니티에서 사용될 이름) > 오브젝트 자식도 Barrel로 명명 > Material 탭 가서 Material 이름도 Barrel로 바꾸기
  • N > Item 탭 > Location과 Rotation이 0,0,0이고 Scale이 1,1,1인지 확인 > Alt+G로 Location reset 가능 > Alt+R로 Rotation reset 가능
  • File 메뉴 > Export > FBX (.fbx) > Selected Objects 옵션 선택하면 선택된 오브젝트만 export > Apply Transform 옵션 선택해서 유니티에서 원치 않는 회전이 일어나지 않게
  • 유니티 열기 > Meshes, Textures, Materials 폴더 만들기 > Textures 폴더에 Barrel_Albedo.png 넣기 > Meshes 폴더에 Barrel.fbx 넣기 > Barrel 선택하고 Model 탭 > 앞에 Import 붙은 옵션들 끄기 (안 꺼도 되긴 함) > lightmap을 bake할거면 Generate Lightmap UVs 옵션 켜기 > apply
  • Rig 탭 > Animation Type > None > Animation 탭 > Import Animation 끄기 > apply > Materials 탭 > Material Creation Mode > None (Barrel Material 직접 만들거라)
  • Barrel fbx 씬 뷰로 옮겨넣기 > 오브젝트 선택하고 F > Ambient Shadow가 완전히 검은색이므로 고쳐야 함 > Window > Rendering > Lighting > Scene > Lighting Settings > New > 생겨난 Lighting Setting은 Scenes 폴더에 옮겨넣는다 > Generate Lighting
  • 조명이 너무 밝다면 Edit > Project Settings > Player > Other Settings > Color Space > Linear
  • Material 폴더에 Barrel_Mat 만들기 > 머테리얼 설정 조정 (아는 거라 생략)
  • Plane 만들기 > Directional Light > (영상에선 Light > Shadow Type > Normal Bias를 직접 조정했지만, Unity 6에선 Bias가 Use settings from Render Pipeline Asset으로 돼있음. 안 건드리고 넘어감)
  • 카메라 선택하고 Ctrl + Shift + F 누르면 카메라가 현재 씬뷰에서 보고 있는 시선대로 정렬됨
  • Rigidbody 추가하고 이제 Collider를 추가해야 하는데, Mesh대로 콜라이더를 적용시키기 위해선 Blender에서의 작업이 필요함

  • Blender로 돌아가기 > Shift + D로 복사 > 우클릭으로 기본 위치에 위치시키기 > 기존 Barrel 안 보이게 하기 > 복사한 Barrel을 Barrel_Collider로 명명 (자식에 있는 Data도 이름 바꿔주는거 잊지 말기) > 머테리얼 삭제 > (Data 탭 > Auto Smooth 해제 해야 하는데 블랜더 버전 올라가서 없음. 강의에서 auto smooth 끈 결과 보니까 내가 이전에 auto smooth 했다고 생각한게 사실 적용 안된거였는듯) > 우클 > Shade Flat (optional) > UV Maps 속성에서 UVMap 삭제
  • Tab > A > 좌측 상단 Edge Select 모드 > 우클 > Clear Seam > 우클 > Clear Sharp
    • 콜라이더 생성에 불필요한 엣지들 제거한 것
  • Alt+A > 뚜껑에 대고 L > X > Vertices > Alt+좌클로 통 금속 edge loop 선택 > Sfhit+Alt+좌클로 여러 개 선택 > X > Edge Loops > 매끈해질 때까지 반복
    • 콜라이더 생성에 불필요한 폴리곤 제거
  • Ctrl+A > Rotation & Scale로 Rotation과 Scale이 000 111인지 확인 > File > Export > FBX > Selected Objects와 Apply Transform 활성화 > Barrel_Collider로 명명하고 export
  • Unity로 돌아오기 > Meshes 폴더에 Barrel_Collider.fbx import > Model 탭 > Normals > None > apply (이렇게 하면 vertices가 줄어듦. blender에서 shade flat 대신 shade smooth로 메시를 설정해도 똑같은 효과) > Import 붙은 옵션들 다 끄고 apply > Animation 탭에서 Import 끄고 apply > Materials 탭에서 Material Creation Mode None으로 설정하고 apply
  • 아까 만들었던 Barrel 오브젝트 선택 > Mesh Collider 추가 > Mesh에 Barrel_Collider 적용 > Convex 선택 (다른 매시 콜라이더와 충돌 가능) > play해보면 plane에 착지하는거 볼 수 있음 > Prefabs 폴더 만들고 barrel을 prefab으로 만들어놓기 > 오브젝트 지우고 대신 프리팹 넣기 > 씬뷰 좌측 상단 Toggle Tool Handle Position을 Pivot으로 바꾸면 중심점 기준으로 이동시킬 수 있음
    • Convex는 콜라이더를 볼록 다면체(Convex Hull) 형태로 변경하는 옵션입니다.
    • 메쉬가 오목한 형태(Concave)라면 이를 무시하고 가장 단순한 볼록 형태로 충돌 처리를 계산합니다
    • Convex 옵션이 활성화된 상태에서만 Mesh Collider를 RigidBody와 함께 사용할 수 있습니다.
    • 이는 Unity에서 충돌 계산 시 안정성을 보장하기 위해 강제됩니다.
  • Lighting > Environment > Environment Lighting > Source > Color > 원하는 밝은 색으로 바꾸기 > Directional Light 선택 > Intensity 1.2 > Realtime Shadows > Strength 0.9 > Renderer 애셋에서 Ambient Occlusion 추가 > 나머지 Post processing 설정은 버전이 달라져서 그냥 패스

Creating LOD In Blender & Unity (Part 5/5)

  • Blender > Barrel_Collider 숨기기 > Barrel 보이게 하기 > Barrel 선택 > Shift + D > 우클 > 상단 Snapping > Grid > G > X > Ctrl 누르며 이동하면 그리드에 맞춰서 이동 > 좌클로 confirm
  • L로 뚜껑 선택 > X > Vertices > Alt 클릭으로 edge loop 선택하고 X로 edge loop도 삭제 > 밑면도 같은 작업

해당 모델 vertis 및 faces 정보 표시
Edit > Preferences > Interface > Status Bar > Scene Statistics 체크
그냥 나오는 정보는 씬 전체 오브젝트에 대한 정보. Edit 모드를 들어가야 해당 오브젝트에 대한 정보가 표시됨.

  • Barrel_LOD0,1,2로 명명 (_LOD0 이런 식으로 명명하면 유니티에서 import할 때 자동으로 LOD Group으로 만들어 줌) (Data들도 이름 바꿔주는 거 잊지 말기)
  • Hierarchy > A > Alt + G > Rotation과 Scale 000 111 확인 > Barrel_Collider 오브젝트 빼고 LOD 3종 오브젝트 선택 > export > Barrel.fbx로 예전 거 대체
  • Unity로 돌아오기 > 기존 오브젝트들 다 삭제 > 프리팹도 삭제 > 매시도 삭제 > 새 LOD 오브젝트 import > Inspector에서 필요 없는 설정 끄기 > 모든 LOD 오브젝트 선택 > Material 적용
  • Barrel 부모 오브젝트에 Rigidbody, Collider 추가
  • LOD 변경이 너무 티나니까 LOD Group에서 거리 설정 변경

최적화 팁
: 지금 import한 texture의 resolution은 2048x2048(2k)인데, Max Size를 1024(1K)로 줄이면 퀄리티의 차이는 거의 없으면서 용량은 줄어든다



🎮 Project MR


아트 프로토타이핑

  • 초기엔 맵 + 전투 화면만 있었음. 근데 너무 휑함.
    • 노드 대신 아트를 추가함
    • 옆에 퀘스트나 아이템창 만듬
      • 만들고 나니 게임 플레이에 직접적으로 연관 없는 창이 옆에 떠 있으니까 뭔가 위화감이 듦
        • 그래서 화면을 키워봤더니 이젠 real time based game 같아져서 아래에 버튼이 왜 있는지 모르겠음
        • 그리고 내가 처음에 생각했던 거랑 너무 멀어져서 이젠 이걸로 무슨 게임을 만들어야 할지도 모르겠음
  • 옆에 창이 있어야 되는 건 맞음. 대화는 출력을 해야 스토리를 풀어나갈 수 있음.
  • 그냥 지금까지 만든 거 다 쓰면 될듯? 노드 기반 맵, 포인트 앤 클릭, 횡스크롤, turn-based 다 넣으면 연습도 되고 좋을듯?


💻 씹어먹는 c++


3. C++ 의 세계로 오신 것을 환영합니다. (new, delete)

c++에서도 mallocfree 쓸 수 있는데
언어 차원에서 newdelete를 지원함.

#include <iostream>

int main() {
	int* p = new int;
	*p = 10;

	std::cout << *p << std::endl;

	delete p;
	return 0;
}

배열도 할당 가능

int main() {
	int arr_size;
	std::cout << "array size : ";
	std::cin >> arr_size;
	int* list = new int[arr_size];
	for (int i = 0; i < arr_size; i++) {
		std::cin >> list[i];
	}
	for (int i = 0; i < arr_size; i++) {
		std::cout << i << "the element of list : " << list[i] << std::endl;
	}
	delete[] list;
	return 0;
}

4 - 1. 이 세상은 객체로 이루어져 있다

class Animal {
private:
	int food;
	int weight;

public:
	void set_animal(int _food, int _weight) {
		food = _food;
		weight = _weight;
	}
	void increase_food(int inc) {
		food += inc;
		weight += (inc / 3);
	}
	void view_stat() {
		std::cout << "이 동물의 food  : " << food << std::endl;
		std::cout << "이 동물의 weight : " << weight << std::endl;
	}
};

int main() {
	Animal animal;
	animal.set_animal(100, 50);
	animal.increase_food(30);

	animal.view_stat();
	return 0;
}

c++에선 new 이런거 없이 그냥 선언 가능함?

C++에서는 클래스 객체를 생성할 때 기본적으로 스택 메모리에 할당됩니다. 따라서 new를 사용하지 않아도 자동으로 생성됩니다. 이것이 C#과는 큰 차이점 중 하나입니다.

C++에서는:
1. 스택 메모리에 객체를 생성(기본 방식).
2. 필요한 경우에만 new를 사용해 힙 메모리에서 동적 생성.

C#에서는 객체 생성 시 무조건 힙 메모리에 동적 할당됩니다. 따라서 new 키워드 없이 객체를 생성할 수 없으며, 메모리는 가비지 컬렉터(GC)가 자동으로 관리합니다.

4 - 2. 클래스의 세계로 오신 것을 환영합니다. (함수의 오버로딩, 생성자)

함수의 오버로딩 (Overloading)

void print(int x) { std::cout << "int : " << x << std::endl; }
void print(char x) { std::cout << "char : " << x << std::endl; }
void print(double x) { std::cout << "double : " << x << std::endl; }

int main() {
	int a = 1;
	char b = 'c';
	double c = 3.2f;
	
	print(a);
	print(b);
	print(c);

	return 0;
}

매개변수 달라지면 함수 작동 방식도 달라지게 하는 오버로딩 c++ 구현 예시

void print(int x) { std::cout << "int : " << x << std::endl; }
void print(char x) { std::cout << "char : " << x << std::endl; }

int main() {
	int a = 1;
	char b = 'c';
	double c = 3.2f;
	
	print(a);
	print(b);
	print(c);

	return 0;
}

정확히 일치하는거 못 찾으면 컴파일러가 비슷한 자료형으로 바꾸는데,
이 경우엔 c를 int로도 바꿀 수 있고 char로도 바꿀 수 있어서 애매해지니까 오류 발생.

생성자 (Constructor)

#include <iostream>

class Date {
	int year_;
	int month_;
	int day_;

public:
	void ShowDate() {
		std::cout << "year_ : " << year_ << std::endl;
		std::cout << "month_ : " << month_ << std::endl;
		std::cout << "day_ : " << day_ << std::endl;
	}

	Date(int year, int month, int day) {
		year_ = year;
		month_ = month;
		day_ = day;
	}
};

int main() {
	Date day(2011, 3, 1);
	day.ShowDate();
	return 0;
}

생성 후 초기화해주는 생성자 함수

Date() {
  year = 2012;
  month = 7;
  day = 12;
}

이런 디폴트 생성자 정의해놓고

Date day = Date();
Date day2;

이렇게 하면 무리없이 객체 생성되는데

Date day3();

이렇게 하면 Date 자료형을 반환하는 day3() 함수를 선언하게 되니까 조심

4 - 3. 스타크래프트를 만들자 ① (복사 생성자, 소멸자)

#include <iostream>

class Marine {
	int hp;
	int coord_x, coord_y;
	int damage;
	bool is_dead;

public:
	Marine();
	Marine(int x, int y);

	int attack();
	void be_attacked(int damage_earn);
	void move(int x, int y);

	void show_status();
};
Marine::Marine() {
	hp = 50;
	coord_x = coord_y = 0;
	damage = 5;
	is_dead = false;
}
Marine::Marine(int x, int y) {
	coord_x = x;
	coord_y = y;
	hp = 50;
	damage = 5;
	is_dead = false;
}
void Marine::move(int x, int y) {
	coord_x = x;
	coord_y = y;
}
int Marine::attack() { return damage; }
void Marine::be_attacked(int damage_earn) {
	hp -= damage_earn;
	if (hp <= 0) is_dead = true;
}
void Marine::show_status() {
	std::cout << " *** Marine *** " << std::endl;
	std::cout << " Location : ( " << coord_x << " , " << coord_y << " ) "
		<< std::endl;
	std::cout << " HP : " << hp << std::endl;
}

int main() {
	Marine marine1(2, 3);
	Marine marine2(3, 5);

	marine1.show_status();
	marine2.show_status();

	std::cout << std::endl << "마린 1 이 마린 2 를 공격! " << std::endl;
	marine2.be_attacked(marine1.attack());

	marine1.show_status();
	marine2.show_status();
}

왜 함수 선언 따로, 구현 따로 하는 거임?

C++에서 클래스 내부에 함수 구현을 작성하면 그 함수는 기본적으로 inline 함수로 간주됩니다.

  • inline 함수: 컴파일러가 해당 함수의 코드를 호출하는 위치에 삽입(inline expansion)하여 성능을 최적화하려고 합니다.
  • 문제는, 복잡한 함수의 경우 inline을 남용하면 코드 크기가 커지고 성능 저하를 일으킬 수 있습니다.

Marine::는 범위 지정 연산자로, 클래스 외부에서 멤버 함수의 정의를 작성할 때 사용됩니다.

헤더와 소스 파일로 분리

  • 일반적으로 클래스 선언은 헤더 파일(.h)에 작성하고, 구현은 소스 파일(.cpp)에 작성합니다.
  • 선언과 구현을 분리하면 여러 파일에서 해당 클래스를 참조할 때, 구현 세부사항을 노출하지 않고 헤더만 포함시킬 수 있습니다.
  • 변경 사항이 발생해도 선언부(헤더 파일)와 구현부를 각각 독립적으로 유지 관리할 수 있습니다.
int main() {
	Marine* marines[100];

	marines[0] = new Marine(2, 3);
	marines[1] = new Marine(3, 5);

	marines[0]->show_status();
	marines[1]->show_status();

	std::cout << std::endl << "마린 1 이 마린 2 를 공격! " << std::endl;

	marines[0]->be_attacked(marines[1]->attack());

	marines[0]->show_status();
	marines[1]->show_status();

	delete marines[0];
	delete marines[1];
}

여러 개의 객체들을 배열에 넣어서 관리하는 예시

객체 생성할 때 new 쓰면 생성자 자동 호출해준다고 얘기하는데
생성자는 원래 자동으로 호출하지 않나? gpt한테 물어봤더니 자동 호출이 맞다고 하고, 이전 내용하고도 모순되는 내용이라 그냥 넘어가면 될듯?

소멸자

Marine::Marine(int x, int y, const char* marine_name) {
	name = new char[strlen(marine_name) + 1];
	strcpy_s(name, strlen(marine_name) + 1, marine_name);

	coord_x = x;
	coord_y = y;
	hp = 50;
	damage = 5;
	is_dead = false;
}

marine의 이름을 지정해주는 오버로드 추가

strcpy 썼더니 오류 남

  • 새로운 C++ 코드라면 std::string을 사용하는 것이 가장 안전하고 현대적인 방법입니다.
  • 기존 코드와 호환성을 유지해야 하거나 레거시 코드를 수정하는 경우라면, strcpy_s를 사용하세요.
  • 절대로 버퍼 오버플로우를 방지해야 하는 경우 _CRT_SECURE_NO_WARNINGS는 최후의 수단으로만 사용하는 것이 좋습니다.

https://seewol.tistory.com/163

이름 생성한 건 좋은데, Marine이 사라져도 new로 할당했던 name은 정리가 되질 않는다.

객체가 사라질 때 자동으로 호출되어 메모리 정리를 도와주는 함수가 소멸자.

Marine::~Marine() {
	std::cout << name << " 의 소멸자 호출!" << std::endl;
	if (name != NULL) {
		delete[] name;
	}
}

복사 생성자

Photon_Cannon::Photon_Cannon(const Photon_Cannon& pc) {
  std::cout << "복사 생성자 호출 !" << std::endl;
  hp = pc.hp;
  shield = pc.shield;
  coord_x = pc.coord_x;
  coord_y = pc.coord_y;
  damage = pc.damage;
}

const로 인자를 받았기 때문에 기존 포톤 캐논을 변경할 순 없고, 값을 가져올 수만 있다.

Photon_Cannon pc1(3, 3);
Photon_Cannon pc2(pc1);

이렇게 하면 pc1이 복사되는 건 당연하고

Photon_Cannon pc3 = pc2;

이렇게 해도 pc2가 복사된다

Photon_Cannon pc3;
pc3 = pc2;

이렇게 하면 복사 생성자 호출 안된다.

그리고 사실 Photon_Cannon(const Photon_Cannon& pc) 이거 구현 안 해도 그냥 Photon_Cannon pc3 = pc2; 이렇게 적으면 복사 생성자 자동으로 만들어져서 대응되는 원소들이 복사된다.

근데 소멸자 때문에 오류 발생할 수 있다. 같은 char *을 멤버로 갖고 있는데 소멸자에서 메모리 할당 취소해버리면 다시 접근해서 할당 해제 하려다 오류 나버림.

name = new char[strlen(pc.name) + 1];
strcpy(name, pc.name);

이럴 땐 주소값 말고 값을 아예 복사해버리면 된다.
당연히 디폴트 복사 생성자는 안될 것.



profile
Penser, c'est réapprendre à voir

0개의 댓글