반응형

오랜만에 친구가 cpp 공부를 하고있다 하여  미로만들기를 추천하며 나역시 미로를 만들어 보았다

 

위 영상은 이번에 제작물이다 {

    1. 맵 램덤생성

    2.방향키 입력시 플레이어 움직임

    3.플레이어의 시야 제한

}

 

우선 맵 헤더 파일이다. 

class  Mazemap
{

public:
	enum PlayerMoverDirection
	{
		UP,
		DOWN,
		LEFT,
		RIGHT,
		MovendPointeEndPoint
	};
	enum MapState {
		road = 0,
		wall,
		startPoint,
		EndPoint
	};

	const char MapStateChar[4][4] = { "□","■","○","☆" };

	int MapSizeLW = 25;
	int PlayerEyesight = 5;
	int PlayerPos[2] = { 0,0 };


	std::vector<std::vector<int>> Map{};


	void init();
	void PlayerMove(PlayerMoverDirection m);
	void MapRestart();


private:
	// 맵생성 완료 확인
	bool IsGenerateMap = false;
	/// <summary>
	/// [4][2]  [1]상 [2]하 [3]좌 [4]우
	/// </summary>
	const int MoverDirection[4][2] = { {1,0} ,{-1,0} ,{0,-1} ,{0,1} };

	//start point end point  의  거리차이
	const int Sp_Ep_MDistance = 3;
	//랜덤 관련 클래스
	uniform_int_distribution<int> RandomMapPoint;
	random_device  rd;
	mt19937_64 mt;

	void AutomaticMapMaker(int MapSize);
	void ProceduralGeneration(int generatex, int generatey, PlayerMoverDirection goback = PlayerMoverDirection::MovendPointeEndPoint);

	std::vector<PlayerMoverDirection> canDoitGeneration(int x, int y, PlayerMoverDirection goback);
};

 



맵 클레스엔 맵과 플레이어의 이동을 표현할 변수들을 만들어준다.

 

우선 맵 생성을 만들었는데.

 

맵 램덤 생성에서 어떤식으로 생성할지 생각하다 가장 기본적인 생각은 DFS 였고

 

 플레이어 위치에서 시작을한뒤 

 

 

      

{

 왔던길을 제외한 갈수있는길을 탐색한다. 

               갈수 있는길은 특정 방향으로 2칸이 벽 일경우 가능

 

 상하좌우 전부 길이없을경우 도착지점 배열에 추가한다.(막다른 길을 도착지점으로 하기위함)

 


갈수 있는길의 방향을 배열로 받은뒤 섞어준다.

 

배열의 순서대로 다음 방향으로 2칸이동후 사이의 1칸도 길로 채워준다.

}

 

 

 

   

 

 

맵 초기화 시켜주기 

맵을 생성후 start point를 지정해준다.

void Mazemap::MapRestart()
{
	Map.clear();
	AutomaticMapMaker(MapSizeLW);
}

void Mazemap::AutomaticMapMaker(int MapSize)
{
	uniform_int_distribution<int> Maprange(0, MapSize-1);
	int  P_x, P_y, EndP_x, EndP_y = 0;

	if (SpareEndPoint.size() > 0) SpareEndPoint.clear();

	P_x = Maprange(mt);
	P_y = Maprange(mt);

	PlayerPos[0] = P_x;
	PlayerPos[1] = P_y;

	
    
	for (int i = 0; i < MapSize; i++) {
		Map.push_back(std::vector<int>());
		for (int j = 0; j < MapSize; j++) {
			Map[i].push_back(1);
		}
	}
	Map[P_x][P_y] = MapState::startPoint;
	//Map[EndP_x][EndP_y] =MapState::EndPoint;
	ProceduralGeneration(P_x, P_y);
	
	int randPoint = Maprange(mt) % (SpareEndPoint.size() - 1);

		Map[SpareEndPoint[randPoint][0]][SpareEndPoint[randPoint][1]] = MapState::EndPoint;

	IsGenerateMap = true;
}

 

 

 

맵생성 코드의 기본적인 아이디어는

 

1.가는 방향으로 길을 채워준다.

2. 갈수 있는길을 탐색한다

3. 왔던길을 제외한다(받은 갈수있는길 배열에서).

4.만약 갈길이 있다면 queue 길찾기 배열에 추가한다.

5. 받은 방향 배열을 섞어준다

6.재귀함수로 현재위치와 queue의 일전에 추가한 다음방향으로 이동한다.



더보기

맵 생성 코드

void Mazemap::ProceduralGeneration(int generatex, int generatey, PlayerMoverDirection goback)
{
	//길로 바꿔주기
	if (Map[generatex][generatey] == MapState::wall){
		Map[generatex - (MoverDirection[goback][0])][generatey - MoverDirection[goback][1]] = MapState::road;
		Map[generatex][generatey] = MapState::road;
	}

	else if (Map[generatex][generatey] == MapState::road) 
	{
		return;
	}


	////갈수있는 방향 받기
	////같던길 제외 해주기
	auto nextRoad = canDoitGeneration(generatex, generatey, goback);
	////섞기
	for (int i = 0; i < nextRoad.size(); i++) {
		int a = RandomMapPoint(mt) % nextRoad.size();
		if (a == i)continue;
		PlayerMoverDirection m = nextRoad[i];
		nextRoad[i] = nextRoad[a];
		nextRoad[a] = m;
	}

	if (!(nextRoad.size() > 0)) {
		SpareEndPoint.push_back({ generatex, generatey });
	}

	for (int i = 0; i < nextRoad.size(); i++) {
		ProceduralGeneration(generatex + MoverDirection[nextRoad[i]][0] * 2, generatey + MoverDirection[nextRoad[i]][1] * 2, nextRoad[i]);
	}
}

 

 

canDoitGeneration 함수 (갈수있는길을 탐색한뒤 [상하좌우] enum 배열을 반환한다)

std::vector<Mazemap::PlayerMoverDirection> Mazemap::canDoitGeneration(int x, int y, PlayerMoverDirection goback)
{
	std::vector<PlayerMoverDirection> v;

	if (x + 2 < MapSizeLW && Map[x + 2][y] == Mazemap::MapState::wall &&
		Map[x + 1][y] == Mazemap::MapState::wall &&
		goback != DOWN)v.push_back(UP);
	if (x - 2 >= 0 && Map[x - 2][y] == Mazemap::MapState::wall &&
		Map[x - 1][y] == Mazemap::MapState::wall &&
		goback != UP)v.push_back(DOWN);
	if (y + 2 < MapSizeLW && Map[x][y + 2] == Mazemap::MapState::wall &&
		Map[x][y +1] == Mazemap::MapState::wall &&
		goback != LEFT)v.push_back(RIGHT);
	if (y - 2 >= 0 && Map[x][y - 2] == Mazemap::MapState::wall &&
		Map[x][y - 1] == Mazemap::MapState::wall &&
		goback != RIGHT)v.push_back(LEFT);
	return v;
}

 

이후 움직이는 코드를 만들어준다

맵을 탈출하지 못하게 배열의 크길 방향 제한을 걸어준다.

std::vector<Mazemap::PlayerMoverDirection> Mazemap::canDoitGeneration(int x, int y, PlayerMoverDirection goback)
{
	std::vector<PlayerMoverDirection> v;

	if (x + 2 < MapSizeLW && Map[x + 2][y] == Mazemap::MapState::wall &&
		Map[x + 1][y] == Mazemap::MapState::wall &&
		goback != DOWN)v.push_back(UP);
	if (x - 2 >= 0 && Map[x - 2][y] == Mazemap::MapState::wall &&
		Map[x - 1][y] == Mazemap::MapState::wall &&
		goback != UP)v.push_back(DOWN);
	if (y + 2 < MapSizeLW && Map[x][y + 2] == Mazemap::MapState::wall &&
		Map[x][y +1] == Mazemap::MapState::wall &&
		goback != LEFT)v.push_back(RIGHT);
	if (y - 2 >= 0 && Map[x][y - 2] == Mazemap::MapState::wall &&
		Map[x][y - 1] == Mazemap::MapState::wall &&
		goback != RIGHT)v.push_back(LEFT);
	return v;
}

그리고 main에서 움직이는 코드를 사용해서 이동한다.

void MazeMain::Update()
{
	int keyInput;
	keyInput = _getch();
	if (keyInput == 224) {
		keyInput = _getch();
	}
	if (keyInput == 115 || keyInput == 80) {
		map.PlayerMove(Mazemap::PlayerMoverDirection::UP);
	}
	if (keyInput == 97 || keyInput == 75) {
		map.PlayerMove(Mazemap::PlayerMoverDirection::LEFT);
	}
	if (keyInput == 100 || keyInput == 77) {
		map.PlayerMove(Mazemap::PlayerMoverDirection::RIGHT);
	}
	if (keyInput == 119 || keyInput == 72) {
		map.PlayerMove(Mazemap::PlayerMoverDirection::DOWN);
	}

	if (keyInput == 'r' || keyInput == 'R') {
		map.MapRestart();
	}
}

 

 

이제 메인에서 각종 코드들을 실행시킨다.

 

랜더는 더블버퍼 win api 코드를 들고와서 작성했다.

 

main

더보기
class MazeMain
{
public:
	Mazemap	map;
	GameBoard GameRender;

	void init();

	void a_main();

	string Render();

	void Update();

	~MazeMain();
	bool Game = true;
private:

};

int main() {


	MazeMain m;
	m.init();
	m.a_main();
}


void MazeMain::init()
{
	map.init();

	GameRender.Map_size = map.MapSizeLW;
	GameRender.ScreenInit();

	GameRender.func = [this]() {
		return Render();
	};

}

void MazeMain::a_main()
{
	while (Game)
	{
		while (_kbhit() == 0) {
			GameRender.Render();
			Update();
		}
	}

}

 

그런다음 

원하는 시야거리를 잡아준뒤 그이상을 넘어갈경우 맵을 벽과 같은 문자로 넣어버린다.

 

string MazeMain::Render()
{

	string s;

	//map.PlayerPos[0] map.PlayerPos[1]
	for (int i = 0; i < map.Map.size(); i++)
	{
		for (int j = 0; j < map.Map[i].size(); j++)
		{

			if (map.PlayerPos[0] == i && map.PlayerPos[1] == j) {
				s += map.MapStateChar[2];
				continue;
			}
			if (
				map.PlayerPos[0] + map.PlayerEyesight > i &&
				map.PlayerPos[0] - map.PlayerEyesight < i &&
				map.PlayerPos[1] + map.PlayerEyesight > j &&
				map.PlayerPos[1] - map.PlayerEyesight < j
				) {
				s += map.MapStateChar[map.Map[i][j]];
			}
			else {
				s += map.MapStateChar[Mazemap::MapState::wall];

			}
			s += map.MapStateChar[map.Map[i][j]];

		}
		s += "\n";
	}
	return s;

}

'언어 > C++' 카테고리의 다른 글

cpp 전처리기  (0) 2023.09.05
c++ std::function 사용하기  (0) 2023.01.10
함수 포인터! (Function Pointer)!  (0) 2021.07.13
인라인 함수(Inline Function)  (0) 2021.06.15
네임스페이스  (9) 2021.05.18
반응형

 

전처리기 : 프로그램을 컴파일 하기전 선행 처리되는 부분.

 

소스파일->전처리기->컴파일러->링커->exe 파일 

 

 

전처리기는 

앞에 # 을쓰며 맨뒤에 세미콜론; 을 붙이지 않는다.

 

지시문: 전처리기 지시문들은 프로그램 소스를 쉽게 변경하고 다른 환경에서 컴파일하기 쉽게 만든다.

 

전처리 지시문들을 간단하게 사용법을 적어두었다. 

 

전처리기 지시문 종류: #include #define #error #import #pragma #elif #if  #undef #else #ifdef #line #using #endif #ifnedf

아래의 내용은 micro내용에 있음

 


#include

처음부터 계속해서 나오는 include은 한글로 포함하다 라는 뜻을 가지고있으며 

지시문의 선언된 지점부터 지정된 파일의 내용을 포함하도록 전처리기에 지시 하는 문장이다.

 

c언어를 처음 배웠을때 나오는 #include <stdio.h> 을 처음으로 접하는 지시문이였을겄이다. 

 stdio standard input output 의 약자로 입출력을위해 우리가 초반부터 사용해 오던 헤더파일 이다.

이처럼 다른 소스코드를 쉽게 추가하여 사용할수 있다.

 

include의 사용법으로는

 

350: #include <stdio.h>

351: #include "std.h"

 

이런식으로 사용한다.

위 코드의 경우 #include <stdio.h> 파일 내용과 "std.h" 파일내용을 350번,351번 아래부터 적용한다. 로 볼수있다.

보통은 맨위에 적어서 모를수도 있겠지만 코드의 라인에 맞추어 적용한다.

 

#include <> 와 "" 의 차이는 파일을 읽는 방법에서 차이가난다

 

 

<>  컴파일러 옵션의 경로에 따직른 위치에서 헤더파일을 찾는다.

       <d:\file\a.h> 와 같이 지정된 경로를 찾는다.

 

""     컴파일러인 경우 현재 정의한 위치의 파일을 찾아본다.

        파일에서 상위 파일로 올라가며 찾아본다.

        이후 <> 와 같이 지정된 경로에 따른 위치에서 찾는다.

         지정된 경로를 찾는다. 

    

 

지정된 경로 예시

이러한 식으로 명확한 경로를 묶은 경우 해당 경로만 검색한뒤 표준 검색을 무시한다.

 

그럼으로 직접 만든 헤더파일일 경우 " "으로 사용하는걸 볼수있다.

 

 


#define  매크로

define 은 쉽게 사용하기위한 식별자(별명) 으로 볼수있다.

 

#define IN int
#define pi 3.141592
#define multiplyPI(x)(x*pi)

 

 IN in = 50;
 cout<< multiplyPI(in);
 cout<< in*pi;

 

위 처럼 간단한 함수 상수 등을  간단한 이름으로 변경하여서 사용할수 있다.

 

위 상수와 비슷한 매크로는 상수는

간단하게 변경할수 있으며 3.14 와 같은 숫자를 보기좋게 PI 와 같이 사용하여 가독성을 높힐수 있다.

 

#define pi 3.141592

 

매크로 와 비슷한 함수의 경우 일반함수와 다른점이 몇가지 있다.

 

매크로는  코드가 기계어로 변경될때 일반 함수와 다르게 하드코딩 한것 처럼 확장되어 넘어간다.

#define PI 3.141592

 

int p = PI ;

일경우

p = 3.141592 와 같다.

 

일반함수와 다르게 함수 공간(stack frame)이 생성안되기 때문에 속도가 빠르고
변경시 매크로 부분이 선언했을때 처럼 변경되며 실행파일 크기가 늘어난다.

 

계산을 하고 나오는 함수와달리

define 함수 는 하드코딩 처럼 변경 해주는 것이기때문에 다르게 계산된다.

 

#define SQR(x) x*x

int main() {

	int x = 10;
	cout << SQR(10)<<endl;
	cout << SQR(x+10) << endl;
	cout << SQR(++x) << endl;
}

위 와 같이 실행했을경우

차례대로 

100

120

144

순서대로 출력된다.

이렇게 출력되는 이유는 일전에 계속말 한것 처럼 함수가 아니라 실행전 치환 해주는 것이기 떄문인다.

이말은 위으 코드는 결국 아래와 같이 변경된다는 뜻이다.

	cout << 10*10 << endl;
	cout << x + 10 * x+10 << endl;
	cout << ++x *++x << endl;

 

이를 방지해 주기위해

	cout << SQR((x+10)) << endl;

이런식으로 넣으면 정상적으로 400이 나온다

 

위 코드처럼 define의 경우 틀렸을때 쉽게 오류를 찾기 힘든경우가 있기에 조심해서 사용해야 한다.

 


#undef 

undef는 식별자의 정의를 제거하여 , 이후 컴파일러에서도 식별자를 인식하지 못한다.

 

undef 의경우 define 와 같이 사용하여 영역처럼 사용할수 있다.

#define SQR(x) (x)*(x)
int main() {

	int x = 10;
	cout << SQR(10)<<endl;
#undef SQR
	cout << SQR(++x) << endl; //err 식별자가 정의되어 있지 않습니다.
}

 


 

 #if     #elif   #else  #endif #ifdef #ifndef

전처리기  조건부 컴파일 지시자.

사용법의 경우 아래 이미지 처럼 일반 if , else if , else 문과 비슷하게 사용한다.

다른점은 #if문의경우 마지막에 #endif로 끝맺음을 지어줘야한다.

 

그리고 전처리기 에서 처리하는 지시문 이기때문에 

위의 이미지 처럼 회색 처리되며 컴파일 조차 되지않는다.

#if SQR(10) 처럼 define 함수를 쓸수도 있다.

 

여러 경우로 사용할수 있는데 

 

컴파일 되지않기 때문에 테스트 모드와 실행모드를 설정하여 다르게 동작하게 할때 사용할수있다.

 

 

#ifdef 와 ifndef 는 

식별자가 존재할경우 #if 와 같은 기능을 하지만 정의 되지않거나 제외되었을경우 #undef와 같은 기능을하여

제외한다.

 

이러한 기능으로 헤더파일 중복을 막을수 있다.

 

#ifndef HD_FILE

#define HD_FILE

 


#line

 

#line 은 미리 정의된 매크로 중 __LINE__  과 __FILE__을 변경한다.

22: cout << __LINE__ << endl;
23:#line 12 "hello.cpp"
24:cout << __LINE__  +","+ hello.cpp<< endl;
25:cout << __LINE__ << endl;

output : 

22

12

13

에러 났을경우 라인과 파일을 출력할수 있고 

특정 상황에 라인을 재정의 하여 마지막에 도달하였을경우

line의 수를 보고 어떤 방식으로 실행하였는지 확인할수 있다.


 

#using #import

 

 

  #import

GCC (C 컴파일러 에서는) include 의 중복을 피할수 있는 용도로 사용하지만

cpp msvc 에서의 #import 는.tlb .olb 와같이 COM 객체를 사용하기위해 형식 라이브러리 type library 를 불러오는데 사용합니다.

 

#using 은 dll 파일을 참조할수 있습니다.

#using 되는 구성 요소는 컴파일 시간에 가져온 다른 버전의 파일로 실행할 수 있으므로 클라이언트 애플리케이션에서 예기치 않은 결과를 제공할 수 있습니다.

 


#paragma 

파라그마는 많은 기능을 내포하고 있다.

 

뒤에 붙는 명령어에 따라 다른 기능을 가지고 있는데

예시로

pragma omp 의 경우 스레드 즉 병렬 처리 관련한 기능들을 가지고 있다.

#pragma omp parallel for num_threads(4) 처럼 사용하여  병렬 처리할수 있다

내부엔 mutex semapor lock 과 비슷한 기능들도 구현 되어있다.

#pragma omp parallel for num_threads(4)
    for (i = 0; i < SIZE; i++)
    {
        a[i] = i;
        printf_s("%d\n", a[i]);
    }

 

progma once  는 소스코드 파일을 컴파일할때 컴파일러에 헤더 파일이 중복되지 않도록 지정한다.

 

 

 


미리 정의된 매크로

 

__DATE__ : Mmm dd yyyy 형식의 상수 문자열

__FILE__ : 현재 소스 파일의 이름

__LINE__: 소스파일의 줄 번호

 

위 와 같은 사용하기 쉽게 미리 정의 해둔 매크로 들이 존재한다. 

 

visual studio 의 최신 미리정의된 매크로의 경우 

microsoft 의 공식문서에서 확인할수 있다.

https://learn.microsoft.com/ko-kr/cpp/preprocessor/predefined-macros?view=msvc-170 

 

미리 정의된 매크로

Microsoft C++ 컴파일러의 미리 정의된 전처리기 매크로가 나열 및 설명되어 있습니다.

learn.microsoft.com

 

 

 

 

전처리기 연산자 

#,#@,## 

 

# 문자열화 연산자

매크로 매개변수를 문자열 리터럴로 변환한다.

예제 learn.microsoft.com

 

#@ charizing 연산자

매크로 인수에서만 사용가능하며 인수를 작은따음표로 묶어 매크로가 확장될때 문자로 처리된다.

 

 

 

 

 

 

 

## 토큰 붙여녛기 연산자

아래 처럼 ## 을 사용하면 매크로가 확장되지않는다 

n이 인수로 전달 되는것이 아니라 실제 token9와 같이 작동한다.

숫자 10을 넣는경우 오류가 나는것을 확인할수 있다.

<cassert> 라이브러리

는 위에서 사용했던 매크로가 만들어져 있는 라이브러리다.

 

'언어 > C++' 카테고리의 다른 글

CPP 콘솔 미로 만들기  (0) 2023.11.15
c++ std::function 사용하기  (0) 2023.01.10
함수 포인터! (Function Pointer)!  (0) 2021.07.13
인라인 함수(Inline Function)  (0) 2021.06.15
네임스페이스  (9) 2021.05.18
반응형

Unity 2022버전 부터는 에디터 기능Spline이 새롭게 추가되었다.

이글은 2022.1.20f1 버전으로 spline 1.01 버전을 사용하였다.

 

이전 버전에선 직접 만들거나 에셋으로 사용하던 기능이 추가된것이다.

 

어떻게 추가되었는지 확인해 보자

 

자세한 내용과 코드 라이브러리를 확인할수 있는 주소다.

https://docs.unity3d.com/Packages/com.unity.splines@1.0/api/UnityEngine.Splines.SplineUtility.html

 

Class SplineUtility | Splines | 1.0.1

Class SplineUtility A collection of methods for extracting information about Spline types. Inheritance SplineUtility Assembly : solution.dll Syntax public static class SplineUtility : object Fields DrawResolutionDefault The default resolution used when unr

docs.unity3d.com

 

 

목차

1. 다운로드 위치

2. Spline 

2. Spline Instantiate

3. Spline Animate

4. Splline Extude

5. Spline Examples

 

6. 2.2.1 버전 업데이트

 

 

 

 

package manager ->package : unity Regisry ->splines 를 임포트 해준다.

추가를 해주면 아래처럼 Gameobject->3Dobject->Spline {Draw Spline Tool.., Square, Circle } 이 추가된다

순서대로

 

Draw Spline Tool : Spline 기본 오브젝트 생성

Square : 네모가 그려져있는 spline 오브젝트 생성

Circle : 원형이 그려져 있는 spline 오브젝트 생성

 

Square 생성시 만들어주는 Spline

 

Spline을 만들어준뒤 오브젝트를 눌러주면  Scene 좌측상단에 새로운 아이콘이 생겼다.

아이콘을 누른뒤 움직여주면  아래 사진처럼 변경이 가능하다

 

맨아레 아이콘을 눌러주면 Line 위에 새로운 점을 추가가능하다.

 

아래 화면이 InsPector 창의 Spline 이다.

 

 

 

먼저 Edit Mode Type 은 3가지로

{Catmull Rom , Bezier , Linear}

 

 

먼저 Catmull Rom  은 곡률을 수학적으로 계산해서 만들어주는 계산법이다.

점 3개를 가지고 0->2 과 1번의점을 평행하게 그어준뒤  허밋 곡선을 그어준다.

 

이미지 출처& 자세한 설명:

https://lee-seokhyun.gitbook.io/game-programming/client/easy-mathematics/gdc2012/catmull-rom-splines 

 

캣멀-롬 스플라인(Catmull-Rom Splines) - Game Programming

캣멀-롬 스플라인은 3차 허밋 스플라인에서 시작(U)과 끝(V) 지점의 속도 벡터를 결정하기 위한 특정한 값을 가진 스플라인 입니다. 새로운 스플라인 이라기 보다는 3차 허밋 스플라인을 구현하기

lee-seokhyun.gitbook.io

 

 

 

 

다음은 많이 사용하는 Bezier 형식이다

점 1,2를 지정후 가상의 1-1점과 2-1 점 총 4개의 점을 가지고 계산하는 방법으로

선위를 이동하는 점 과 이어주어 선을 그리는 방식이다.

 

마지막으로 Linear은 가장 기본적으로 점과 점을이어 선을 만드는 방식이다.

 

Knots는 점들의 정보를 저장한 리스트다.

 

Closed는 아래의 이미지처럼 시작점과 끝점을 이어주는가 아닌가에 대한 데이터다.

 

 

또한 spline의 위치는 x,y,z 모두 변경이 가능하다

 

 

 

 

 

 

오브젝트의 Add Component에서도 Splines라는 항목을 확인할수 있는데

그내부는 기본적으로 4가지가있다

 


먼저 Instantiate

아래 이미지 처럼 라인 위에 오브젝트들을 정렬생성 시킬수있다.

container에 원하는 Spline을 넣어준뒤

items to Instantiate에  { 오브젝트 , 나올 퍼센트} 를 입력해주고 맨아래의 Randomize를 누르고 Regenerate를 눌러주면

랜덤하게 배치해주는걸 볼수있다.

Instantiated Object Setup 설정을 변경해주면

배치될 오브젝트의 방향과 중점등을 세팅해 줄수있다.

Instantiation은 생성될 갯수로

Instance Count :  Dist 값이 늘어날수록 오브젝트 배치량이 늘고 간격이 좁아진다. 값은 Random과 Exact로 설정가능하다.

Spline Distance : 0.1~ 시작해 시작지점 오브젝트 부터 간격의 길이를 지정하여 배치하는 방법이다.

Linear Distance : 이 역시 간격의 길이를 지정하지만 계산을 배치할때 Linear 형식으로 배치하여 곡률이 없다.

 

 

 


이번엔 Spline Animate다

움직이길 원하는 오브젝트에 추가하여 사용한다.

 

Animated Object Setup 은 이전 Instantiate에 나온것과 같이 오브젝트의 방향 중점 등을 지정한다

 

Movement 는 기본적인 세팅을 할수있다.

 

Play on Awake : awake 실행 타이밍에 Play

 

Method : 움직이는 속도의 지정방식을 정할수 있다{ speed, time}

             time: 시간값 으로 숫자가 커질수록 느려진다.

             speed: 속도값 으로 숫자가 작아질수록 느려진다.

 

Easing :구간의 속도를 지정할수있다{None ,Ease in only , Ease Out Only , Ease In-Out}

              순서대로 {일정한속도, 들어갈때 느려짐, 나올때 느려짐, 들어갈때 나올때 느려짐}

 

Loop Mode: 반복 모드를 정할수 있다{,,, PingPong}

           Once :  한번만

          LoopContinous : 계속반복

          Ease in Then Continous : Easing 셋팅을 처음만 적용한뒤 반복 

          PingPong : 시작점->끝점->시작점으로 왕복을 반복한다.

 

세팅을 한후 Preview 에서 에니메이션을 확인할수 있다.

 


 

 

Splline Extude

 

Spline에 맞게 메쉬를 생성한다.

spline 오브젝트에 컴퍼넌트를 추가시 파일에 씬 이름과오브젝트 이름으로 메쉬파일을 만들어준다.

 

Geometry는 메쉬를 설정해줄수 있다.

           Radius : 굵기

           Profile Edfes : 메쉬의 원둘래 각의 개수를 

           Segaments Per Unit :메쉬의 한단위의 길이를 만드는 루프수

 

Advanced

    Range 는 시작 길이와 끝나는 지점을 지정할수있다.

    Auto - Regen Geometry :  true 일경우 spline이 변경되었을경우 자동 메쉬재생성 (런타임때 수정되지않으면 비활성화)

                Rebuild Frequency : 초당 최대 재생성 횟수

 

 


 

 

 

Spline Examples 

 

 Spline의 라이브러리 코드 기능들을 이용하여 만든 추가 예시 프로젝트들 이다.

이 예시 프로젝트는

Splines를 다운받은 화면 에서 확인할수있다 

아래 Samples를 import하면 확인할수 있다.

 

 

자동 도로 생성

 

 

Point Spline Data

 

추가 포인터 데이터

예시 프로젝트에선 탱크 오브젝트에 Look At 을 추가하여 움직일때 포인트를 바라보게 하였다.

 

 

 

Spline GetNearestPoint 

 

주변 Spline 찾기

 

 

또한 제공되는 예제에는 Spline 과 쉐이더를 같이 이용하는 모습도 보여준다.

spline 데이터를 GPU에 전달하는 방법

 

 

 


 

Unity 2022.3 버전이 나오면서

Splines 역시  2.2.1 버전으로 업데이트 하였다.

 

위치 변경과 기본 프리셋이 추가되었다.

3D Object 내부에 있던 Spline 이 밖으로 나왔고 프리셋이 몇가지 늘어났다.

 

 

또한 spline 설정법과 아쉬웠던 에디터에서의 조작이 편하게 바뀌었다.

 

보기 쉬워진 ui와  쉬워진 조작

spline의 UI가 바뀌어 Line 방향이 추가되었다.

또한 일전에는 Spline 컴퍼넌트에 들어있던 시스템들이 point마다 설정가능하게 변경되었다.

 

아래 이미지 처럼 하나의 spline Object에  이어지지 않은 여러개의 자유로운 spline을 만들수있다.

 

새롭게 생긴 Element inspector 창에서

기본적이 값도 변경이 가능하지만 Spline의 Knot 끼리의 상태를 설정가능하다.  

이렇게 다른 두개의 Knot 끼리 붙였다 뗄 수도 있고

중간에 위치하는 Knot을 둘로 나눠버릴수도있다.

 

 

 

 

Spline Examples 에서도 변경된 부분이 많았다.

 

라인 그리면 라인대로 spline을 그려주는 examples 이 추가되었고

 

이전 예제에서 Unity Editor 라이브러리를 이용하여 기능을 만들어서 자동차의 속도와 위치를 설정하는 부분이 생겼다. 

또한 새롭게 생긴 예제에서는 이번 업데이트로 인해  자유롭게 만들수 있는 Spline을 사용하는 방법도 보여준다.

 

'엔진 > 유니티' 카테고리의 다른 글

유니티 데칼 (decal)  (0) 2024.04.16
구글 gemini api 유니티에서 사용하기  (0) 2023.12.21
ChatGPT-Unity에서-사용하기  (5) 2023.03.22
Unity Simulator  (0) 2023.01.20
Unity ObjectPool Api  (0) 2022.11.02
반응형

추가된 기능이나 api는 이후 추가 예정입니다. 해당방식은 api를 사용하는 방식을 보여준것입니다.

해당 방식을 사용해 그대로 key를 하드코딩한다면 어플리케이션을 제작할 경우 api key의 보안에 문제가 생길수 있습니다.

더보기

최근 OpenAI 에서 ChatGPT를 드디어 테스트를 끝내고 상용화 하였다고 하여서
Unity로 사용할수 있게 하였다. 


 
제작버전 2023.3.11f1
 
 
 

https://platform.openai.com/docs/api-reference/chat/create

 

OpenAI API

An API for accessing new AI models developed by OpenAI

platform.openai.com

ChatGPT API 는 기본 ChatGPT 웹 에서 사용하는것과는 달리
API 사용 요금이 존재한다 한달에  무료 18$의 테스트 할수있는 요금을 받는다.
 
사용요금은 토큰으로 계산하는데

0.002 / 1천 토큰이다.
1byte당 1천 토큰이니 영어로 천단어당 0.002달러
한글로는 한글자당 3바이트 가량 사용하니 3자당 0.002달러씩 사용한다.
한달에 약 3,000,000글자 를 한국어로 사용할수 있다.
 

 
 
2023.12월 기준 모델당 가격

더 자세한 모델마다의 가격은 gpt 홈페이지의 settings-Limits에서 확인가능하다
 
 
Rest API를 사용하기때문에 사용법만 알아두고 사용하면 어렵지않다.
 
 
OpenAPI의 API reference를 참고하여 설정한다.
https://platform.openai.com/docs/api-reference/chat
 
먼저 OpenAPI  웹 페이지에서 키를 받아준다.
 
 

 
 

이창은 한번닫으면 다시 볼수없고 다시 생성해야 하니 잘 복사해둔다.
키값을 찾을수 없으면 옆의 휴지통 버튼을 눌러 삭제후 다시 생성하여 사용할수있다
 
 
 
먼저 포스트맨 이라는 프로그램으로 우선 RestAPI를 테스트 해볼것이다.

 
설정은 post 주소는 https://api.openai.com/v1/chat/completions 

 
 
 
그리고 헤더세팅

Content-type 의 값을 application/json 
Authorization을 bearer (openai키값) 
을 넣어준다.
 
모델은 gpt-3.5-turbo 버전으로 세팅한뒤
보낼 json body를 작성한다.

 
 
 

 
 
 
 
 
 
 
 
 
post 이후 받은데이터

 
 
 
 
 
 
 
 
 
 
 
api가 어떤식으로 보내지고 받아지는지 확인했다.
이제 Unity code를 짜보자
 
 
 
 
우선 class를 json형식에 맞게 형식을 잡아준다.

받은 json형식의 string을 알맞게 디시리얼라이징을 해줘야 하기때문에  Response class 역시 만들어준다 


 

Http 요청방법은 마이크로 소프트의 HttpCilent 를 사용하였다.
 
CreateHttpClient 함수에서 client를 생성해주며 헤더설정과 주소를 세팅 
body를 이전에 만들었던 class를 json 형식으로 serializeing 해준뒤 Client.postAsync로 전송
순서
1. cilent 생성및 해더 세팅
2.일전에 설정했던 데이터class 를 json형식으로 변경
3.Post로 전송후 대기
4.받아온 데이터를 다시 만들어두었던 형식으로 디시리얼라이징

  
    private string API_Url = "";

    private const string AuthorizationHeader = "Bearer";
    private const string UserAgentHeader = "User-Agent";

//httpclient 세팅
  private void CreateHttpClient()
    {
        client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(AuthorizationHeader, OpenAI_Key);
        client.DefaultRequestHeaders.Add(UserAgentHeader, "okgodoit/dotnet_openai_api");
    }

//받은 class를 post 하는 함수
       private async Task<string> ClieantResponse<SendData>(SendData request)
    {
        if (client == null)
        {
            CreateHttpClient();
        }

		//class마다 URL 이라는 인터페이스로 공통 url불러올수있는 함수사용
        API_Url = ((URL)request).Get_API_Url();
		
        //json 시리얼라이징
        string jsonContent = JsonConvert.SerializeObject(request, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
        var stringContent = new StringContent(jsonContent, UnicodeEncoding.UTF8, "application/json");
		
        Debug.Log(API_Url);
        Debug.Log(stringContent);
        //데이터 전송후 대기
        using (var response = await client.PostAsync(API_Url, stringContent))
        {

            if (response.IsSuccessStatusCode)
            {
            //데이터 정상적으로 받은경우 json string 반환
                return await response.Content.ReadAsStringAsync();
            }
            else
            {
                throw new HttpRequestException("Error calling OpenAi API to get completion.  HTTP status code: " + response.StatusCode.ToString() + ". Request body: " + jsonContent);
            }
        }
    }

 
 
 
만든 CilentResponse 를 좀더 편하게 사용하기위한 함수 제작 및 캡슐화


 
테스트용 코드
 
만들었던 Request class 생성후 세팅
보내는 함수 실행후 로그확인 

chat의 경우

api는 Role이 3가지로 나뉜다
우선 system Gpt의 기본 설정을 해주는 message이다
User는 사람 assistant는 대답이다.



 
 
 
 
 


대화한 내용을 user와 assistant로 계속 추가해서 보내면 대화내용을 전부 확인하고 대답을 출력하기에
대답을 기억하게 하고싶은경우 chatRequest class에 ChatMessage를 계속 추가해주면서 보내면 된다.
 
미리 대화를 어느정도 입력하는것으로 학습시킬수있다. 위의 예시처럼 대화를 미리 넣어두는경우
다음대화를 어느정도 추출이가능하다.


 
 
 새로운 기능들이 추가되어 코드를 수정하였다..

일전의 베이스 대화세팅, 대화 데이터 유지 기능은 다른기능을 포함하며 삭제하였다.
하나의 기능으로 묶어버릴시 너무 난잡해지기 때문이다.
 
 
 
위의 코드로 테스트한 음성생성과 이미지 생성이다.
 
이들도 크게 다르지않게 보내는 class 받는 class를 만든뒤
그에맞게 설정하고 json으로 파싱하여 사용하는 방식이다.
 
음성생성

 
이미지 생성

 
unity오브젝트에 테스트 코드 추가

 
 
 
 
추출된 이미지

 

 

 

 

또한 비슷한 테스트를 필요로 하는경우

 

https://programing-note.tistory.com/entry/%EA%B5%AC%EA%B8%80-gemini-api-%EC%9C%A0%EB%8B%88%ED%8B%B0%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0

 

구글 gemini api 유니티에서 사용하기

일전에 유니티로 짠 코드를 재사용해 gemini를 사용해보고 Chat GPT와 비교 해 볼것이다. https://programing-note.tistory.com/entry/ChatGPT-Unity%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0 ChatGPT Unity에서 사용하기

programing-note.tistory.com

 gemini 도 가능하다 

테스트는 gemini가 더욱 추천한다


전체코드

더보기

OpenAI_Rest.cs

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using OpenAiFormet;
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using UnityEditor.PackageManager.Requests;
using UnityEngine;


public class OpenAI_Rest
{
    public string OpenAI_Key = "sk-key...";

    private static HttpClient client;
    private static string JsonFilelocation = Application.streamingAssetsPath + "/Json/JsonData.json";

    public delegate void StringEvent(string _string);
    public StringEvent CompletedRepostEvent;

    private string API_Url = "";

    private const string AuthorizationHeader = "Bearer";
    private const string UserAgentHeader = "User-Agent";

    public string speechFile = Application.streamingAssetsPath + "/TestSound.mp3";
    public void Init()
    {
        CreateHttpClient();
    }
    private void CreateHttpClient()
    {
        client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(AuthorizationHeader, OpenAI_Key);
        client.DefaultRequestHeaders.Add(UserAgentHeader, "okgodoit/dotnet_openai_api");
    }

    private async Task<string> ClieantResponse<SendData>(SendData request)
    {
        if (client == null)
        {
            CreateHttpClient();
        }

        API_Url = ((URL)request).Get_API_Url();

        string jsonContent = JsonConvert.SerializeObject(request, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
        var stringContent = new StringContent(jsonContent, UnicodeEncoding.UTF8, "application/json");

        Debug.Log(API_Url);
        Debug.Log(stringContent);
        using (var response = await client.PostAsync(API_Url, stringContent))
        {

            if (response.IsSuccessStatusCode)
            {
                return await response.Content.ReadAsStringAsync();
            }
            else
            {
                throw new HttpRequestException("Error calling OpenAi API to get completion.  HTTP status code: " + response.StatusCode.ToString() + ". Request body: " + jsonContent);
            }
        }
    }
    public async Task<bool> ClieantResponseTTS(TextToSpeechRequest textToSpeechRequest)
    {

        bool result = false;
        try
        {
            API_Url = ((URL)textToSpeechRequest).Get_API_Url();

            string jsonContent = JsonConvert.SerializeObject(textToSpeechRequest, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
            var stringContent = new StringContent(jsonContent, UnicodeEncoding.UTF8, "application/json");


            using (HttpResponseMessage response = await client.PostAsync(API_Url, stringContent))
            {
                if (response.IsSuccessStatusCode)
                {

                    byte[] mp3Bytes = await response.Content.ReadAsByteArrayAsync();

                    try
                    {
                        using (FileStream fs = File.OpenWrite(speechFile))
                        {

                            await fs.WriteAsync(mp3Bytes, 0, mp3Bytes.Length);
                            Debug.Log($"Speech file saved to {speechFile}");
                        }
                        result = true;
                    }
                    catch (MissingReferenceException ex)
                    {
                        File.Create(speechFile).Close();
                        Debug.LogError(ex);
                        using (FileStream fs = File.OpenWrite(speechFile))
                        {
                            await fs.WriteAsync(mp3Bytes, 0, mp3Bytes.Length);
                            Debug.Log($"Speech file saved to {speechFile}");
                        }
                        result = true;
                    }

                }
                else
                {
                    Debug.LogError($"Error: {response.StatusCode} - {response.ReasonPhrase}");

                }
            }
        }
        catch (Exception ex)
        {
            Debug.LogError(ex);
        }

        return result;
    }
    public async Task<ChatResponse> ClieantResponseChat(ChatRequest r)
    {
        return JsonConvert.DeserializeObject<ChatResponse>(await ClieantResponse(r));
    }
    public async Task<ImageGenerateResponseURL> ClieantResponseimageGenerat(ImageGenerateRequest r)
    {
        return JsonConvert.DeserializeObject<ImageGenerateResponseURL>(await ClieantResponse(r));
    }

    public async Task<ImageGenerateResponseb64json> ClieantResponseimageGeneratb64(ImageGenerateRequest r)
    {
        return JsonConvert.DeserializeObject<ImageGenerateResponseb64json>(await ClieantResponse(r));
    }

    public async Task<ChatResponse> ClieantResponseChatAnalyze(ChatMessageAnalyze r)
    {
        return JsonConvert.DeserializeObject<ChatResponse>(await ClieantResponse(r));
    }


}



namespace OpenAiFormet
{
    interface URL
    {
        public string Get_API_Url();
    }

    #region Chat
    public class ChatRequest : URL
    {
        public const string API_Url = "https://api.openai.com/v1/chat/completions";

        [JsonProperty("model")]
        public string Model { get; set; } = "gpt-3.5-turbo";

        [JsonProperty("messages")]
        public List<ChatMessage> Messages { get; set; }

        public string Get_API_Url()
        {
            return API_Url;
        }
    }
    public enum role
    {
        [EnumMember(Value = "system")]
        system,
        [EnumMember(Value = "user")]
        user,
        [EnumMember(Value = "assistant")]
        assistant
    }

    public class ChatMessage
    {
        [JsonProperty("role"), JsonConverter(typeof(StringEnumConverter)), XmlAttribute("role")]
        public role Role;
        [JsonProperty("content"), XmlAttribute("content")]
        public string Message = "";
    }

    public class ChatResponse
    {
        [JsonProperty("id")]
        public string Id { get; set; }

        [JsonProperty("object")]
        public string Object { get; set; }

        [JsonProperty("created")]
        public int Created { get; set; }

        [JsonProperty("model")]
        public string Model { get; set; }

        [JsonProperty("system_fingerprint")]
        public string SystemFingerprint { get; set; }

        [JsonProperty("choices")]
        public List<ChatChoice> Choices { get; set; }

        [JsonProperty("usage")]
        public ChatUsage Usage { get; set; }
    }
    public class ChatChoice
    {
        [JsonProperty("index")]
        public int Index { get; set; }

        [JsonProperty("message")]
        public ChatMessage Message { get; set; }

        [JsonProperty("finish_reason")]
        public string FinishReason { get; set; }
    }
    public class ChatUsage
    {
        [JsonProperty("prompt_tokens")]
        public int PromptTokens { get; set; }

        [JsonProperty("completion_tokens")]
        public int CompletionTokens { get; set; }

        [JsonProperty("total_tokens")]
        public int TotalTokens { get; set; }
    }



    public class ImageAnalyzeRequest : URL
    {

        private const string API_Url = "https://api.openai.com/v1/chat/completions";
        [JsonProperty("model")]

        public const string model = "gpt-4-vision-preview";
        [JsonProperty("messages")]
        public List<ChatMessageAnalyze> Messages { get; set; }

        public string Get_API_Url()
        {
            return API_Url;
        }
    }

    public class ChatMessageAnalyze
    {
        [JsonProperty("role")]
        public string Role { get; set; }

        [JsonProperty("content")]
        public ContentAnalyze Content { get; set; }
    }

    public class ContentAnalyze
    {

        [JsonProperty("text")]
        public string text { get; set; }

        [JsonProperty("image_url")]
        public string image_url { get; set; }
    }


    #endregion

    #region imageGenerate
    public class ImageGenerateRequest : URL
    {
        private const string API_Url = "https://api.openai.com/v1/images/generations";

        [JsonProperty("prompt")]
        public string Prompt { get; set; } // Required

        [JsonProperty("model")]
        public string Model { get; set; } = "dall-e-2"; // Optional, defaults to dall-e-2

        [JsonProperty("n")]
        public int N { get; set; } = 1; // Optional, defaults to 1

        [JsonProperty("quality")]
        public string Quality { get; set; } = "standard"; // Optional, defaults to standard

        [JsonProperty("response_format")]
        public string ResponseFormat { get; set; } = "b64_json"; // Optional, defaults to url or  b64_json

        [JsonProperty("size")]
        public string Size { get; set; } = "1024x1024"; // Optional, defaults to 1024x1024

        [JsonProperty("style")]
        public string Style { get; set; } = "vivid"; // Optional, defaults to vivid or natural

        [JsonProperty("user")]
        public string User { get; set; } // Optional

        public string Get_API_Url()
        {
            return API_Url;
        }
    }


    public class ImageGenerateResponseURL
    {
        [JsonProperty("created")]
        public long Created { get; set; }

        [JsonProperty("data")]
        public List<ImageData> Data { get; set; }
    }

    public class ImageData
    {
        [JsonProperty("url")]
        public string Url { get; set; }
    }
    public class ImageGenerateResponseb64json
    {
        [JsonProperty("created")]
        public long Created { get; set; }

        [JsonProperty("data")]
        public List<ImageDatab64json> Data { get; set; }
    }

    public class ImageDatab64json
    {
        [JsonProperty("b64_json")]
        public string b64_json { get; set; }
    }
    #endregion

    #region TTS
    public class TextToSpeechRequest : URL
    {
        public const string API_Url = "https://api.openai.com/v1/audio/speech";

        [JsonProperty("model")]
        public string Model { get; set; } = "tts-1";

        [JsonProperty("voice")]
        public string Voice { get; set; } = "alloy";

        [JsonProperty("input")]
        public string Input { get; set; }
        public string Get_API_Url()
        {
            return API_Url;
        }
    }
    #endregion


}

테스트 코드

더보기
using OpenAiFormet;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using UnityEngine;
using static UnityEditor.Experimental.GraphView.GraphView;
using static UnityEngine.Networking.UnityWebRequest;

public class OpenAI_RestTest : MonoBehaviour
{

    public  string KeyCode ="sk-";
    OpenAI_Rest api;
    AudioSource source;
    SpriteRenderer spriteRenderer;
    public async void test()
    {

        source = GetComponent<AudioSource>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        api = new OpenAI_Rest();
        api.OpenAI_Key = KeyCode;
        api.Init();


        //chat 
        ChatRequest chatRequest = new ChatRequest();


        chatRequest.Messages = new List<ChatMessage>
        {
            new ChatMessage() { Role = role.system, Message = "고양이 말투로말한다" },
            new ChatMessage() { Role = role.user, Message = "반갑다" },
            new ChatMessage() { Role = role.assistant, Message = "반갑다냥" },
            new ChatMessage() { Role = role.user, Message = "오늘밥 뭐먹을까" },
        };
        var data = ((await (api.ClieantResponseChat(chatRequest))).Choices);
        foreach (var choice in data)
        {
            Debug.Log(choice.Message.Message);
        }



        //tts
        TextToSpeechRequest textToSpeech = new TextToSpeechRequest() { Input = "테스트용 데이터입니다" };
        api.speechFile = Application.streamingAssetsPath + "/aaa.mp3";
        if (await (api.ClieantResponseTTS(textToSpeech)))
        {
            try
            {
                Ienumrator = LoadPath();
                StartCoroutine(Ienumrator);
                source.Play();
            }
            catch (Exception e)
            {
                Debug.Log(e);
            }

        }

        //text to image
        ImageGenerateRequest imageGenerateResponseb64Json = new ImageGenerateRequest() { Prompt = "cat in the box,cartoon drawing style" };
        var imagedata  = await (api.ClieantResponseimageGeneratb64(imageGenerateResponseb64Json));

        //byte를 sprite로 변경
        byte[] imageBytes = Convert.FromBase64String(imagedata.Data[0].b64_json);
        Texture2D tex = new Texture2D(2, 2);
        tex.LoadImage(imageBytes);
        Sprite sprite = Sprite.Create(tex, new Rect(0.0f, 0.0f, tex.width, tex.height), new Vector2(0.5f, 0.5f), 100.0f);
        spriteRenderer.sprite = sprite;
    }

    IEnumerator Ienumrator;
    IEnumerator LoadPath()
    {
        WWW www = new WWW(api.speechFile);
        yield return www;
        source.clip = www.GetAudioClip();
        source.Play();
        StopCoroutine(Ienumrator);
    }

    void Start()
    {
        test();
    }
}



 

'엔진 > 유니티' 카테고리의 다른 글

구글 gemini api 유니티에서 사용하기  (0) 2023.12.21
Unity Spline 기능 추가!  (1) 2023.06.01
Unity Simulator  (0) 2023.01.20
Unity ObjectPool Api  (0) 2022.11.02
unity Redis  (0) 2022.07.13
반응형

유니티 2021 버전부터는 Unity Simulator라는 것을 제공한다.

Game 창의 좌상단의 화살표를 클릭하면 Game화면과 Simulator화면을 선택할 수 있다.

Simulator로 변경하면

 

위 사진처럼 변경되는 모습을 볼 수 있다.

 

 

상단 목록

창의 상단에는 위 같은 목록들을 확인할 수 있는데

좌측부터 순서대로

1. 화면 실행 뷰 선택[Game, Simulator]

2. 원하는 기종 선택

3. 화면 확대, 축소

4. (3) 번의 확대, 축소 한 화면을 원상복귀

5.Rotate [좌, 우] 화면 돌리기

6.Safe Area 

 

와 같은 6가지 항목들이 존재한다.

이중에 6번 항목을 키는 경우

 

위 사진처럼 노란 테두리가 생기는데

이 역시 새롭게 추가된 기능으로 노치 디자인에 ui가 가려 버리는 사태를 막기 위해 있는 기능이다.

 

위 사진처럼 ui를 잡으라고 라인을 배치해 준다.

 

코드에서도 Safe Area가 생겨서 라인 내부로 ui들을 안나가게 할 수 있다.

https://docs.unity3d.com/ScriptReference/Screen-safeArea.html

 

Unity - Scripting API: Screen.safeArea

On some displays, certain areas of the screen might not be visible to the user. This might be because the display's shape is non-rectangular or in the case of TV displays, it can be caused by overscan. Avoid placing user interface elements in areas outside

docs.unity3d.com

 

또한 좌측에 application Settings라는 항목이 있어서 세팅을 추가로 해줄 수 있는데

 

시스템 언어 설정 

장치에의 네트워크 도달 옵션

이 기기의 인터넷 연결가능 옵션 설정

 

 

연결 불가능 

이동통신사 데이터 네트워크 연결

근거리 통신망 연결

 

On Low Memory

먼저 Low Memory는 foreground 말 그대로 앞에서 작동할 때

장치의 메모리부족일 때 앱이 종료되는 것을 방지하기 위해 중요하지 않은 자산(텍스쳐, 오디오)을 해제할 수 있다.

이러한 기능을 테스트하기 위한 버튼이다.

 

 

하지만 역시 안드로이드 시뮬레이터는 진짜 안드로이드로 돌리는게 아니기 때문에 여러가지 제한사항이 존재한다.

1. 기기의 성능 특성(기기의 프로세서 속도,메모리)

2. 기기의 렌더링 능력

3. 에디터에서 작동하지 않는 네이티브 플러그인

4. UNITY_IOS와 같은 시물레이션된 기기에 대한 플랫폼 #define 지시문

5. 자이로스코프 회전 센서

4.멀티터치 (한손가락 터치만 가능)

 

 

https://docs.unity3d.com/kr/2021.1/Manual/device-simulator.html

 

기기 시뮬레이터 - Unity 매뉴얼

이 문서 섹션은 Unity의 기기 시뮬레이터에 대한 정보를 포함합니다.

docs.unity3d.com

'엔진 > 유니티' 카테고리의 다른 글

Unity Spline 기능 추가!  (1) 2023.06.01
ChatGPT-Unity에서-사용하기  (5) 2023.03.22
Unity ObjectPool Api  (0) 2022.11.02
unity Redis  (0) 2022.07.13
unity _Prefab Variants  (0) 2022.03.17
반응형

C++ 의 std::function 을 이용하여 코드를 사용하기 쉽게 변경하기.


std::Function은 무엇인가

함수 포인터    =  반환값이 같은 타입이여야 하고 코드위치를 가르키기 때문에 메모리를 할당,회수가 불가하지만 빠르다.

 

std::Function  =  암시적 형변환이 가능하고

                           일반적인 함수포인터와 다르게 람다함수나 bind 또한 맴버함수에 대한 포인터도 사용가능하다

 

선언방법

int Func(int n){ cout<<n<<endl; };

std::function<void(int)> funcName1 = Func; //함수 대입
std::function<void(int)> funcName2 = [](int a) {}; // 람다함수
std::function<void(int)> funcName3 = std::bind(Func, 1); //std::bind

 

사용방법은 일반 함수와 같다

funcName1(10);

 

사용하는 이유

cpp로 player을 개발하던 도중 enum swich case 를 여러번 사용하는 경우를 보았다.

 메인업데이트 부분의 player 상태 처리와 렌더링부분의 player 상태처리 처럼  업데이트 상황을 나눠서 계산할때

state를 계속 추가해주었다.

업데이트 루프마다 플레이어의 상태들을 관리해 주는것은 player의 상황이 늘어났을경우 각종업데이트에서

swich를 써둬서 상황을 생성할때마다 swich문을 추가하는것과 커플링의 문제가보였다.

 이러한 문제는 코딩을 할때 일이 많아지고 코드의 길이가 길어져 비효율적이다.

state 패턴

 swich case를 state 패턴처럼 이용하며 함수들을 좀더 유동적으로 다루기위해

객체를 등록하여 사용이 필요했다.

위의 사진처럼 swich 문과 update를 하나의 루프에 묶어서 돌린다.

 

하나의 클래스에서  처음시작할때 원하는 위치에 함수를 받아서 저장 해둔뒤 실행하면 state에 따라 실행하게 고안하였다.

다른 오브젝트를 새로 생성하거나 제작할때 오브젝트의 init 함수에 특정 event를 걸어주게 만들면 알아서 돌아갈것이다.

 

장점

이런식으로 구조를 제작하면 새로운 상황을 추가할때 추가해주는 코드가 줄어들고

한쪽에서 코드를 지워도 문제없이 코드가 실행될것이다.

또한 하드코딩이 아니라 프로그램 내에서도 코드를 유동적으로 사용가능할것이다.

 

단점

유동적인 코드가 한번에 확인하기 어렵고, 필요한 순간 할당 제거를 할수있기에

나뉘어서 동작하는 함수들을 많이 추가하는 경우 서순이 꼬여서 생기는 찾기 어려운 오류가 생길수있다.

 

 

테스트 코드

//테스트 함수 추가를 위한 player 클래스
class Player
{
	
public:
	Player(int c) : b(c) { cout << a<<endl; };
	int b = 21;
	void playerMove( );
	void playerIdle( );
private:
	int a =10;

};

void Player::playerMove( )
{
	this->a+=this->b;
}

void Player::playerIdle( ) {
	cout << a<<endl;
}
//std::function 을 이차원 배열로 생성
class Deleg {
public:
	std::vector<std::function<void()>> Deleg;
};

//플레이어의 상태만큼 미리 event update의 크기를 만들어둘 예정
enum PlayerState 
{
	Move,
	Idel,
	Skill,
	StateCount
};



int main() {
	//Render update,LateUpdate,등 업데이트 순서
	const int UpdateRate= 2; 
	Player p = {Player(4)};
  	Player* _P = &p;
  
  Deleg StateDelg[PlayerState::StateCount][UpdateRate] = { { {} ,{} } ,{ {} ,{} } ,{ {}, {} } };
	StateDelg[0][0].Deleg.push_back([=]() {cout << endl; _P->playerMove(); cout << endl;  });
	StateDelg[0][1].Deleg.push_back([_P]() {_P->playerMove(); _P->playerIdle(); });


	StateDelg[1][0].Deleg.push_back([_P]() {_P->playerMove(); _P->playerIdle(); });
	StateDelg[1][1].Deleg.push_back([_P]() {_P->playerMove(); _P->playerIdle(); });


	StateDelg[2][0].Deleg.push_back([_P]() {_P->playerMove(); cout << "1Update End" << endl; });
	StateDelg[2][0].Deleg.push_back([_P]() {_P->playerMove(); cout << "1Update End" << endl; });
	StateDelg[2][0].Deleg.push_back([_P]() {_P->playerMove(); cout << "1Update End" << endl; });
	StateDelg[2][0].Deleg.push_back([_P]() {_P->playerMove(); cout << "1Update End" << endl; });
	StateDelg[2][0].Deleg.push_back([_P]() {_P->playerMove(); cout << "1Update End" << endl; });

	StateDelg[2][1].Deleg.push_back([_P]() {_P->playerMove(); _P->playerIdle(); });
  
  
  //전체 코드를 실행하지만 i나 j를 바꿔줌으로써 특정 state만 실행시켜줄수있다
	for (int i = 0; i < StateCount; i++)
		for (int j = 0; j < UpdateRate;j++) {
			for (int k = 0; k < StateDelg[i][j].Deleg.size(); k++) {
				(StateDelg[i][j].Deleg[k]());
			}

		}
  
 }

 

위 코드는 테스트 코드로써

1. 이차원 배열[플레이어 상태][ 업데이트 ] 를 만들어주었다.

    역으로 업데이트종류 마다 지정된 플레이어 상태에 맞는 함수를 실행시켜 주어도 좋다.

2. 함수(애니메이션 스크립트,스킬등)를 시작할때 원하는 [상태 또는 업데이트] 에 맞는 함수 리스트에 넣어준다.

3. 상황에 맞게 돌린다. 

 

위 소스코드의 결과 값이다.

 

이제 다른 함수들이 특정 state부분만 잠시 실행시키기 원해도 배열의 위치만 맞게 실행시켜주면 간단하게 사용가능하다.

'언어 > C++' 카테고리의 다른 글

CPP 콘솔 미로 만들기  (0) 2023.11.15
cpp 전처리기  (0) 2023.09.05
함수 포인터! (Function Pointer)!  (0) 2021.07.13
인라인 함수(Inline Function)  (0) 2021.06.15
네임스페이스  (9) 2021.05.18
반응형

unity ObjectPool API 
(Version:2021)

이 글을 읽기 전에 자신이 쓰는 유니티 버전을 확인해보라

유니티가 지원하는 objectpool은 2021년 이후 버전부터 추가되었다.

 

1.ObjectPool 이란?

2.Unity ObjectPool

3. 글쓴이가 응용한 방식


 

1.ObjectPool

우리가 사용 하려는 오브젝트 풀은 간단하게 설명하면

오브젝트를 담아두고 재활용하기 위한 디자인 패턴이다.

 

어째서 재활용을 해야할까? 라는 생각을 할 수 있다.

이 물음에 답하자면

 

 생성과 파괴보다 재사용이 빠르고 가볍다.

 

생각해보라 우리가 오브젝트를 "생성" 한다는것은 그냥 그 자리에 나오는 것이 아닌

생성-> class에 맞는 데이터 크기만큼 메모리 할당-> 재생성시 재 대입해줘야 하는 데이터 값

그리고 파괴할 때 가비지 메모리 제거 등 많은 작업이 필요하다.

 

이러한 작업이 적을 경우에는 크게 상관이 없을 수 있지만 많은 데이터를 생성과 제거를 반복한다 생각하면 끔찍하다.

 

이러한 작업을 적게 할 방법이 ObjectPool이라는 방식이다.

 

ObjectPool은 오브젝트를 (생성-> 사용-> 제거)반복 에서  생성-> 사용-> (보관->재사용)반복 ->제거 식으로 사용하여

메모리를 아끼는 방식이다.


 

2.Unity ObjectPool

 

 

 

 

 

 

 

 

 

 

 

Pool 관련 클래스는 몇 가지가 있지만. 대부분 비슷한 사용법을 가진다.

그중에서 ObjectPool <T0>을 사용할 것이다.

namespace UnityEngine.Pool
{
    //
    // 요약:
    //     A stack based Pool.IObjectPool_1.
    public class ObjectPool<T> : IDisposable, IObjectPool<T> where T : class
    {
        public ObjectPool(Func<T> createFunc, Action<T> actionOnGet = null, Action<T> actionOnRelease = null, Action<T> actionOnDestroy = null, bool collectionCheck = true, int defaultCapacity = 10, int maxSize = 10000);

        public int CountAll { get; }
        public int CountActive { get; }
        public int CountInactive { get; }

        public void Clear();
        public void Dispose();
        public T Get();
        public PooledObject<T> Get(out T v);
        public void Release(T element);
    }
}

 

이게 내부 구성과 ObjectPool의 매개변수다.

ObjectPool(생성할 때 함수, 사용할 때 함수, 사용 끝났을 때 함수, 제거 함수, 충돌 체크, 기본적으로 생성해둘 양, 최대 양)

으로 생각하면 쉽다


 

총과 총알로 설명하겠다.

먼저 총알 prefab에 IobjectPool <GameObject> pool {get;set;}을 생 성해준 뒤 Update에 날아가는 코드를 작성해준다.

using UnityEngine.Pool;

public class Bullet : MonoBehaviour
{
    public IObjectPool<GameObject> Pool { get; set; }
    
    public virtual void ReleaseObject() => Pool.Release(this.gameObject);
    public virtual void OnTakeFromPool(GameObject obj) { }
    public virtual void OnReturnedToPool(GameObject obj) => obj.SetActive(false);
    public virtual void OnDestroyPoolObject(GameObject obj) => Destroy(obj);


}


public class Bullet_1 : BulletBase
{
    public float speed = 7;
    private void Update()
    {
        if (transform.position.y >= 10)
        {
            ReleaseObject();
        }
        transform.Translate(speed * Time.deltaTime * Vector3.up);
    }

    public override void OnTakeFromPool(GameObject obj)
    {
        obj.SetActive(true);
    }
}

 

그 후 BulletSpawner.cs를 만들어주어  ObjectPool <Gameobject>  Pool를 만들어준 뒤

ObjectPool내부에 생성할 Bullet의 함수들을 넣어둔다.

그리고 bullet 생성 함수에서 bullet 내부의 IObjcetPool pool에 spawn의  Pool을 넣어준다.

( bullet 내부에서 spawn의  Pool을  사용해야 하기 때문에) 

 

그리고 특정 상황마다 BulletSpawner 에서  Pool.Get();을 해준다.

 Pool.Get()은  Pool 내부 함수로 자동으로 현재 재사용 가능한 오브젝트가 있는지 없는지를 판단하여

생성해준다.

public class BulletSpawner : MonoBehaviour
{
 
    public BulletBase poolablePrefab;
    public IObjectPool<GameObject> Pool { get; private set; }
    

    public void Init()
    {
        
              Pool = new ObjectPool<GameObject>(
              delegate {
        // 오브젝트 생성함수
        // 오브젝트 생성
        GameObject obj = Instantiate(poolablePrefab.gameObject);
        //생성한 오브젝트의 IObjectPool Pool 에 Spawner의 ObjectPool을 넣어준다.
        obj.GetComponent<BulletBase>().Pool = Pool;
            return obj;
        },
        // Bullet에 구현된 사용할 함수들 입력
        poolablePrefab.OnTakeFromPool, poolablePrefab.OnReturnedToPool,
        poolablePrefab.OnDestroyPoolObject,
        true, poolablePrefab.defaultCapacity, 50));

    }
    
    public void Shoot()
    {
       var a= poolablePrefab.Pool.Get();
        a.transform.position = this.gameObject.transform.position;
    }
    private void Start()
    {
        Init();
        InvokeRepeating("Shoot", 0f,5f);

    }
}

 

 

 

 

 

아래는 유니티에서 제공한 예제다

더보기
using UnityEngine.Pool;

// This component returns the particle system to the pool when the OnParticleSystemStopped event is received.
[RequireComponent(typeof(ParticleSystem))]
public class ReturnToPool : MonoBehaviour
{
    public ParticleSystem system;
    public IObjectPool<ParticleSystem> pool;

    void Start()
    {
        system = GetComponent<ParticleSystem>();
        var main = system.main;
        main.stopAction = ParticleSystemStopAction.Callback;
    }

    void OnParticleSystemStopped()
    {
        // Return to the pool
        pool.Release(system);
    }
}

// This example spans a random number of ParticleSystems using a pool so that old systems can be reused.
public class PoolExample : MonoBehaviour
{
    public enum PoolType
    {
        Stack,
        LinkedList
    }

    public PoolType poolType;

    // Collection checks will throw errors if we try to release an item that is already in the pool.
    public bool collectionChecks = true;
    public int maxPoolSize = 10;

    IObjectPool<ParticleSystem> m_Pool;

    public IObjectPool<ParticleSystem> Pool
    {
        get
        {
            if (m_Pool == null)
            {
                if (poolType == PoolType.Stack)
                    m_Pool = new ObjectPool<ParticleSystem>(CreatePooledItem, OnTakeFromPool, OnReturnedToPool, OnDestroyPoolObject, collectionChecks, 10, maxPoolSize);
                else
                    m_Pool = new LinkedPool<ParticleSystem>(CreatePooledItem, OnTakeFromPool, OnReturnedToPool, OnDestroyPoolObject, collectionChecks, maxPoolSize);
            }
            return m_Pool;
        }
    }

    ParticleSystem CreatePooledItem()
    {
        var go = new GameObject("Pooled Particle System");
        var ps = go.AddComponent<ParticleSystem>();
        ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);

        var main = ps.main;
        main.duration = 1;
        main.startLifetime = 1;
        main.loop = false;

        // This is used to return ParticleSystems to the pool when they have stopped.
        var returnToPool = go.AddComponent<ReturnToPool>();
        returnToPool.pool = Pool;

        return ps;
    }

    // Called when an item is returned to the pool using Release
    void OnReturnedToPool(ParticleSystem system)
    {
        system.gameObject.SetActive(false);
    }

    // Called when an item is taken from the pool using Get
    void OnTakeFromPool(ParticleSystem system)
    {
        system.gameObject.SetActive(true);
    }

    // If the pool capacity is reached then any items returned will be destroyed.
    // We can control what the destroy behavior does, here we destroy the GameObject.
    void OnDestroyPoolObject(ParticleSystem system)
    {
        Destroy(system.gameObject);
    }

    void OnGUI()
    {
        GUILayout.Label("Pool size: " + Pool.CountInactive);
        if (GUILayout.Button("Create Particles"))
        {
            var amount = Random.Range(1, 10);
            for (int i = 0; i < amount; ++i)
            {
                var ps = Pool.Get();
                ps.transform.position = Random.insideUnitSphere * 10;
                ps.Play();
            }
        }
    }

 

 

3.

내가 구현한 방식이다.

 

 

Spawn에도 IObjectPool로 PoolManager에 BulletPool을 받아서 사용한다.
Manager 에서 새로운 오브젝트가 아닐경우 서로 공유해서 사용하게 한다.

 

글쓴이는 싱글톤으로 PoolManager을 만들어서 같은 총알은 서로 공유하여 사용하게 만들어보았다.

이런식으로 만들경우 Spawn컴퍼넌트를 오브젝트에 붙여주고 원하는 BulletPrefab을 넣어주면 완성이다.

 

참고자료 : https://docs.unity3d.com/ScriptReference/Pool.ObjectPool_1.html 

 

Unity - Scripting API: ObjectPool<T0>

Object Pooling is a way to optimize your projects and lower the burden that is placed on the CPU when having to rapidly create and destroy new objects. It is a good practice and design pattern to keep in mind to help relieve the processing power of the CPU

docs.unity3d.com

외 여러 영상들과 블로그

'엔진 > 유니티' 카테고리의 다른 글

ChatGPT-Unity에서-사용하기  (5) 2023.03.22
Unity Simulator  (0) 2023.01.20
unity Redis  (0) 2022.07.13
unity _Prefab Variants  (0) 2022.03.17
Unity Inspector 창 정리  (2) 2022.02.24
반응형

Unity 에서 Redis 서버를 사용해보자

 

Unity 버전 2019.4.15f1

 

NuGet의 StackExchange.Redis를 사용했다.

 


 설치&세팅

 

visual studio의 NuGet에서 Plugin을 다운받고 사용하면 사용이 안된다.

사용이 안되는 이유는 두가지로 

 

1. 유니티 내부에 있어야한다.

2. 유니티에서 호완하는 버전이 아니다

 

 

 

 

1. 누겟 페키지를 다운받았으면 다운받은 파일에 들어가서 

 

 

 

 

 

dll 파일과 xml 파일을 

 

unity Assets 파일에 Plugin 파일을 만들어서 넣어준다

 

 

 

 

 

 

 

 

 

 

 

2. 유니티 2019버전에서는 Redis 2버전대가 호환이 안된다

그럼으로 1.5버전을 다운받아서 넣어주면 된다.

 

 


사용법

 

 

그뒤로 unity 내부에서 사용하는 방법은 매우간단하다.

    private ConnectionMultiplexer redisConnection;
    private IDatabase DB;
    private ISubscriber sub;

3가지를 선언해 준다.

 

 첫줄은 StackExchange.Redis의 메인 객체다

 두번째 줄은 DB 데이터 를 주고받을 객체다.

 마지막줄 sub역시 데이터를 전달하기위해 있는객체다.

 

백문이 불여일견 코드로 보겠다.

Init Code

 public bool Init(string host, int port)
    {

        this.redisConnection = ConnectionMultiplexer.Connect(host + ":" + port);
        if (redisConnection.IsConnected)
        {
            this.DB = this.redisConnection.GetDatabase();
            sub = redisConnection.GetSubscriber();
            return true;
        }

        return false;
    }

    public bool Init(string host, int port, Events LoopEvents)
    {

        T = new Thread(new ThreadStart(LoopEvents));
        
        try
        {
            ConfigurationOptions option = new ConfigurationOptions
            {
                ConnectTimeout = 1000,
                AbortOnConnectFail = false,
                EndPoints = { $"{host}:{port}" },
            };
            redisConnection = ConnectionMultiplexer.Connect(option);
        }
        catch (Exception e)
        {
            Debug.Log(e);
        }

        if (redisConnection.IsConnected)
        {
            this.DB = this.redisConnection.GetDatabase();
            sub = redisConnection.GetSubscriber();
            T.Start();

            return true;
        }

        return false;
    }

 

위 코드의

        this.redisConnection = ConnectionMultiplexer.Connect(host + ":" + port);  줄에서 알수있듯

       "ip주소:포트넘버: 로 이루어져 있다. 

 

로컬로 테스트 할때는 아래 코드처럼 "localhost" 를 넣어서 사용하면된다.

this.redisConnection = ConnectionMultiplexer.Connect("localhost")

 

 그냥 주소로 connect를 시도 해도 좋지만

 

   ConfigurationOptions option = new ConfigurationOptions
            {
                ConnectTimeout = 1000,
                AbortOnConnectFail = false,
                EndPoints = { $"{host}:{port}" },
            };
            redisConnection = ConnectionMultiplexer.Connect(option);

이 부분처럼 ConfigurationOptions 를 사용하여 전달해도 좋다.

 그러면 구성 옵션을 세팅을 한번에 해줄수도 있다.

   

 

      this.redisConnection = ConnectionMultiplexer.Connect(host + ":" + port);

           ㄴ주소로 연결


        if (redisConnection.IsConnected) //연결 완료되었는가
        { 

       //연결 완료후 데이터들을 받는다.

            this.DB = this.redisConnection.GetDatabase();

            sub = redisConnection.GetSubscriber();

        }

 

 

 

 

이제 정상적으로 넘어간다면 다 끝났다. 

데이터를 주고받는 방법은 간단하다.

 

 

 

 

 

 

 

 

 

 

 

아까 선언해주었던 DB 객체의 내부에 전부 들어있다. 

아래 코드처럼 자신의 프로젝트에서 사용하기 편하게 만들어주면 끝이다.

 

   public string GetString(string key)
    {
        return this.DB.StringGet(key);
    }
    public bool SetString(string key, string val)
    {
        return this.DB.StringSet(key, val);
    }
    public RedisValue GetHASH(string key, string val)
    {
        return this.DB.HashGet(key, val);
    }
    public bool GetHASH_Bool(string key, string val)
    {
        var data = this.DB.HashGet(key, val);
        if (data.ToString()[0] == 't' || data.ToString()[0] == 'T')
        {
            return true;
        }
        return false;
    }
    public int GetHASH_Int(string key, string val)
    {
        var data = this.DB.HashGet(key, val);
        int Idata = 0;
        if (data.HasValue == true)
        {
            int DataSize = data.ToString().Length - 1;
            int sizenum = (int)Math.Pow(10, DataSize);
            foreach (var d in data.ToString())
            {
                if ('0' <= d && d <= '9')
                {
                    Idata += ((d - 48) * sizenum);
                    sizenum /= 10;
                }
                else
                {
                    return -int.MaxValue;
                }
            }
        }
        return Idata;
    }
    public double GetHASH_Double(string key, string val)
    {
        var data = this.DB.HashGet(key, val);
        if (data.HasValue == true)
        {
            double Idata = Double.Parse(data);
            return Idata;
        }
        return -Double.MaxValue;
    }
    public void SetHASH(string key, HashEntry[] val)
    {
        this.DB.HashSet(key, val);
    }
    public void DestroyHash(string key, RedisValue field)
    {
        this.DB.HashDelete(key, field);
    }

또한 Redis 서버에 메시지를 따로보내는 방법이 있는데 

그게 아까 보여주었던 sub 로 가능하다

    public void Meseage(string Channel, string Meseage)
    {
        sub.Publish(Channel, Meseage);
    }

특정 채널에 메시지를 보낼수있다.

또한 채널의 메시지를 구독하여 올라올때마다 확인받을수 있다.

 

    public void Setsubscrib(string _channel, CallBack_Funtc _CallBack_Funtc)
    {
        if (redisConnection.IsConnected)
        {
            sub.Subscribe(_channel, (channel, message) =>
            {
                try
                {
                    _CallBack_Funtc?.Invoke(channel+" "+message);
                    Debug.Log(message);
                }
                catch
                {

                }
            });
        }
    }

 

 

 

 

 

 

위에서 만들었던 클래스 사용

  private string Key;
    public string RedisIP_Num;
    public int PortNum;
    public bool GetDataLoop;
    public int ThreadDelay;

    private void Start()
    {
     // 따로 XMl로 받아오는 데이터들
        Key = Datas.data.DataKey;
        RedisIP_Num = Datas.data.RedisIP_Num;
        PortNum = Datas.data.PortNum;
        GetDataLoop = Datas.data.GetDataLoop;
        ThreadDelay = Datas.data.ThreadDelay;

        redis = new Redis();
        if (redis.Init(RedisIP_Num, PortNum, delegate
         {
             while (GetDataLoop)
             {
                 GEtD(); // redis.GetHASH_Bool(Key, "KeyData") 데이터들을 받아오는 함수
                 Thread.Sleep(ThreadDelay);

             }
         }))
        {
            Debug.Log("connect");
        }
        else
        {
            Debug.Log("Non_connect");
        }

 GEtD()함수 내부 코드는 이렇게 데이터를 받는다.

 

또한 Init 의 델리게이트에 데이터를 받아오는 함수와 스레드.Sleep(변수)

를 이용하여 연결이 완료되었을때 데이터를 주기적으로 받아오게 만들었다.

 

 

 

전체코드

더보기

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using StackExchange.Redis;
using System.Threading;
using System;
using System.Threading.Tasks;

namespace TStory
{
    static public class Global
    {
        public static bool Dnet_Connection;
        public static bool Dnet_readwrite;
        public static bool Dnet_error;
        public static double server_cache_alive;
        public static int client_connection_count;

    }
}

public class RDS : MonoBehaviour
{
    private Redis redis;
    public RedisValue redisValue;

    private string Key;
    public string RedisIP_Num;
    public int PortNum;
    public bool GetDataLoop;
    public int ThreadDelay;

    private void Start()
    {
        Key = Datas.data.DataKey;
        RedisIP_Num = Datas.data.RedisIP_Num;
        PortNum = Datas.data.PortNum;
        GetDataLoop = Datas.data.GetDataLoop;
        ThreadDelay = Datas.data.ThreadDelay;

        redis = new Redis();
        if (redis.Init(RedisIP_Num, PortNum, delegate
        {
            while (GetDataLoop)
            {
                GEtD();
                Thread.Sleep(ThreadDelay);

            }
        }))
        {
            Debug.Log("connect");
        }
        else
        {
            Debug.Log("Non_connect");
        }


    }

    private void OnApplicationQuit()
    {
        GetDataLoop = false;
        //redis.T.Join();
    }

    public void GEtD()
    {
        TStory.Global.Dnet_Connection = (redis.GetHASH_Bool(Key, "TStory-connection"));
        TStory.Global.Dnet_readwrite = (redis.GetHASH_Bool(Key, "TStory-readwrite"));
        TStory.Global.Dnet_error = (redis.GetHASH_Bool(Key, "device-net-error"));
        TStory.Global.server_cache_alive = (redis.GetHASH_Double(Key, "server-cache-alive"));
        TStory.Global.client_connection_count = (redis.GetHASH_Int(Key, "client-connection-count"));

    }

}

public class Redis
{
    private ConnectionMultiplexer redisConnection;
    private IDatabase DB;
    private ISubscriber sub;
    public delegate void Events();
    public Thread T;
    public bool Init(string host, int port)
    {

        this.redisConnection = ConnectionMultiplexer.Connect(host + ":" + port);
        if (redisConnection.IsConnected)
        {
            this.DB = this.redisConnection.GetDatabase();
            sub = redisConnection.GetSubscriber();
            return true;
        }

        return false;
    }

    public bool Init(string host, int port, Events LoopEvents)
    {


        T = new Thread(new ThreadStart(LoopEvents));
        try
        {
            ConfigurationOptions option = new ConfigurationOptions
            {
                ConnectTimeout = 1000,
                AbortOnConnectFail = false,
                EndPoints = { $"{host}:{port}" },
            };
            redisConnection = ConnectionMultiplexer.Connect(option);
        }
        catch (Exception e)
        {
            Debug.Log(e);
        }

        if (redisConnection.IsConnected)
        {
            this.DB = this.redisConnection.GetDatabase();
            sub = redisConnection.GetSubscriber();
            T.Start();

            return true;
        }

        return false;
    }
    public string GetString(string key)
    {
        return this.DB.StringGet(key);
    }
    public bool SetString(string key, string val)
    {
        return this.DB.StringSet(key, val);
    }
    public RedisValue GetHASH(string key, string val)
    {
        return this.DB.HashGet(key, val);
    }
    public bool GetHASH_Bool(string key, string val)
    {
        var data = this.DB.HashGet(key, val);
        if (data.ToString()[0] == 't' || data.ToString()[0] == 'T')
        {
            return true;
        }
        return false;
    }
    public int GetHASH_Int(string key, string val)
    {
        var data = this.DB.HashGet(key, val);
        int Idata = 0;
        if (data.HasValue == true)
        {
            int DataSize = data.ToString().Length - 1;
            int sizenum = (int)Math.Pow(10, DataSize);
            foreach (var d in data.ToString())
            {
                if ('0' <= d && d <= '9')
                {
                    Idata += ((d - 48) * sizenum);
                    sizenum /= 10;
                }
                else
                {
                    return -int.MaxValue;
                }
            }
        }
        return Idata;
    }
    public double GetHASH_Double(string key, string val)
    {
        var data = this.DB.HashGet(key, val);
        if (data.HasValue == true)
        {
            double Idata = Double.Parse(data);
            return Idata;
        }
        return -Double.MaxValue;
    }
    public void SetHASH(string key, HashEntry[] val)
    {
        this.DB.HashSet(key, val);
    }
    public void DestroyHash(string key, RedisValue field)
    {
        this.DB.HashDelete(key, field);
    }
    public void Meseage(string Channel, string Meseage)
    {
        sub.Publish(Channel, Meseage);
    }

}

 

'엔진 > 유니티' 카테고리의 다른 글

ChatGPT-Unity에서-사용하기  (5) 2023.03.22
Unity Simulator  (0) 2023.01.20
Unity ObjectPool Api  (0) 2022.11.02
unity _Prefab Variants  (0) 2022.03.17
Unity Inspector 창 정리  (2) 2022.02.24

+ Recent posts