300x250
300x250

1. 제목

- 백준 2217 로프

- BOJ 2217 로프

문제 링크 : 2217번: 로프 (acmicpc.net)

 
 

2217번: 로프

N(1 ≤ N ≤ 100,000)개의 로프가 있다. 이 로프를 이용하여 이런 저런 물체를 들어올릴 수 있다. 각각의 로프는 그 굵기나 길이가 다르기 때문에 들 수 있는 물체의 중량이 서로 다를 수도 있다. 하

www.acmicpc.net

 


2. 풀이 과정

 

1. 로프 내림차순으로 정렬
2. 가장 튼튼한 로프부터 추가하며 최대 중량 계산
3. 최대 중량 = max(로프 수 * 고른 로프 중 가장 약한 로프 중량, 최대 중량).

 


3. 코드

// 1. 로프 내림차순으로 정렬
// 2. 가장 튼튼한 로프부터 추가하며 최대 중량 계산
// 3. 최대 중량 = max(로프 수 * 고른 로프 중 가장 약한 로프 중량, 최대 중량)

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

bool Compare(int num1, int num2)
{
	if (num1 > num2)
		return true;
	return false;
}

int main()
{
	int N;
	cin >> N;

	vector<int> v(N);

	for (int index = 0; index < N; index++)
		cin >> v[index];

	// 1. 로프 내림차순으로 정렬
	sort(v.begin(), v.end(), Compare);

	int maxWeight = 0;

	// 2. 가장 튼튼한 로프부터 추가하며 최대 중량 계산
	for (int index = 0; index < N; index++)
	{
		// 3. 최대 중량 = max(로프 수 * 고른 로프 중 가장 약한 로프 중량, 최대 중량)
		maxWeight = max(maxWeight, v[index] * (index+1));
	}

	cout << maxWeight << endl;

	return 0;
}
300x250

'<C++ 백준 BOJ> > 그리디' 카테고리의 다른 글

[백준 BOJ1789] 수들의 합 (C++)  (0) 2023.07.02
[백준 BOJ1026] 보물 (C++)  (0) 2023.07.02
[백준 BOJ1931] 회의실 배정 (C++)  (0) 2022.10.10
[백준 BOJ11399] ATM (C++)  (0) 2022.10.10
[백준 BOJ2839] 설탕배달 (C++)  (0) 2022.10.10
300x250

Lerp 함수는 보간(Interpolation) 기법 중 하나인 선형 보간(Linear Interpolation)을 수행합니다. 이는 두 지점 사이의 값을 일직선 상에 있는 다른 값으로 변환하는 것을 의미합니다. 이를 좀 더 쉽게 이해하기 위해서는, 두 점을 잇는 직선상에 있는 다른 지점들을 구하는 것과 같다고 생각할 수 있습니다.

즉 위 사진처럼 A와 B사이의 좌표를 구한다고 생각하면 편합니다.

이런 식으로 무수하게 많은 좌표가 있을 텐데 Lerp 함수를 사용하면 이각각의 좌표를 쉽게 구할 수 있습니다.


Lerp 함수는 다음과 같은 형태로 사용됩니다.

public static Vector3 Lerp(Vector3 a, Vector3 b, float t);


여기서 a와 b는 각각 선형 보간을 수행할 두 지점이며, t는 a와 b 사이에서 내분되는 비율을 나타내는 값입니다. t 값은 0과 1 사이의 값을 가지며, t가 0이면 결과는 a가 되고, t가 1이면 결과는 b가 됩니다. 그리고 0과 1 사이의 값이 아닌 t 값을 사용하는 경우, Lerp 함수는 a와 b 사이의 값을 내분한 값으로 계산합니다.

예를 들어, A가 0이고 B가 1인 경우, t가 0.5인 Lerp 함수를 호출하면 다음과 같은 결과가 나옵니다.


Vector3.Lerp(a, b, 0.5f); // 결과는 (0.5)


이는 a와 b를 잇는 직선상에서 t = 0.5 지점에 해당하는 값을 반환하는 것입니다.

Lerp 함수는 보간 기법을 적용하기 때문에, 물체의 이동, 회전, 크기 변화 등을 자연스럽게 만들 수 있습니다. 또한, 보간 기법은 게임에서 많이 사용되는 기법 중 하나이므로, 게임 개발자라면 반드시 알고 있어야 하는 개념 중 하나입니다.

 

 

이걸 사용해서 0부터 10000까지 10초 동안 올라가는 코드를 작성해 보겠습니다.

    private IEnumerator LerpTestImpl()
    {
        float delta = 0;
        float duration = 10f;
        int startValue = 0;
        int endValue = 10000;

        while (delta <= duration)
        {
            float t = delta / duration;

            float currentValue = Mathf.Lerp(startValue, endValue, t);
            imgSliderFront.fillAmount = t;
            textPer.text = string.Format("{0}%", t * 100f);
            text.text = currentValue.ToString();

            delta += Time.deltaTime;
            yield return null;
        }
    }

 

 

이렇게 하면 10초 동안 각초마다 정해진 구간만큼 똑같이 이동을 하는데 상황에 따라선 처음엔 빠르게 올라가다가 후반부에 느리게 올라갔으면 하는 연출이 필요할 때가 있습니다. 이럴 때는 

Easing 함수 치트 시트 (easings.net)

 

Easing Functions Cheat Sheet

Easing functions specify the speed of animation to make the movement more natural. Real objects don’t just move at a constant speed, and do not start and stop in an instant. This page helps you choose the right easing function.

easings.net

이 사이트 같은 곳을 참고하셔서 t값에 수식을 적어주시면 됩니다. 수학적인 부분이라 왜 저런 수식이 나왔는지는 설명이 어렵습니다.

 

 

    private IEnumerator LerpTestImpl()
    {
        float delta = 0;
        float duration = 10f;
        int startValue = 0;
        int endValue = 10000;

        while (delta <= duration)
        {
            float t = delta / duration;
            t = Mathf.Sin((t * Mathf.PI) / 2);

            float currentValue = Mathf.Lerp(startValue, endValue, t);
            imgSliderFront.fillAmount = t;
            textPer.text = string.Format("{0}%", t * 100f);
            text.text = currentValue.ToString();

            delta += Time.deltaTime;
            yield return null;
        }
    }

이렇게 하면 빠르게 올라가다가 끝에 가까워질수록 느리게 올라가는 연출을 간단하게 할 수 있습니다.

 

 

그런데 이렇게 끝내면 아래와 같이 9999와 같이 딱 떨어지지 않는 경우가 발생할 수 있습니다. 왜냐하면 Lerp함수와 타임값을 사용해서 중간 좌표를 구해주는 식으로 구현을 했는데 이렇게 하면

이러한 좌표를 구해버릴 수도 있는 겁니다. 그래서 끝시간에 도달했을 때 끝값을 고정해줘야 합니다. 이것을 끝보정이라고 합니다.

    private IEnumerator LerpTestImpl()
    {
        float delta = 0;
        float duration = 10f;
        int startValue = 0;
        int endValue = 10000;

        while (delta <= duration)
        {
            float t = delta / duration;
            t = Mathf.Sin((t * Mathf.PI) / 2);

            float currentValue = Mathf.Lerp(startValue, endValue, t);
            imgSliderFront.fillAmount = t;
            textPer.text = string.Format("{0}%", t * 100f);
            text.text = currentValue.ToString();

            delta += Time.deltaTime;
            yield return null;
        }
        
        // 10초가 지났으니 끝보정
        imgSliderFront.fillAmount = 1;
        textPer.text = string.Format("{0}%", 100f);
        text.text = "10000";
    }

 

이러한식으로 마지막 부분에는 도착지에 해당하는 좌표를 넣어서 세팅을 해주면 정상적으로 작동하는 것을 볼 수 있습니다.

 

 

참고자료 :

(403) [유니티] Lerp를 프로처럼 사용하는 방법 - YouTube

https://easings.net/ko

300x250
300x250

SLG_ExcelToJson2.0Portable.zip
1.31MB

[C#] ExcelToJsonConvert 엑셀파일 Json파일로 바꾸기 :: 별빛상자 (tistory.com)

 

[C#] ExcelToJsonConvert 엑셀파일 Json파일로 바꾸기

압축푸시고 SLG_ExcelToJsonConverter.exe 실행하시면 나와요. 폴더열기 누르셔서 변환할 엑셀파일 있는 경로를 선택해 주세요. 폴더만 선택할 수 있는 다이얼로그라서 아무것도 안보이실 텐데 정상이

starlightbox.tistory.com

 

기존에 만들어 뒀던 게 있었는데 이건 시트가 2개 이상 있을 경우 첫 번째 시트 데이터만 처리를 합니다.

그래서 데이터 저장을 여러 개 해야 하는 경우

이렇게 데이터 엑셀파일을 여러 개 만들어야 했는데 이렇게 사용하다 보니

이런 구조보다는

 

 

이렇게 파일 하나에 시트별로 관리하는 게 더 효율적이란 생각이 들어서 기존 프로그램을 바꿨습니다.

 

 

프로그램을 실행하고 '파일 선택' 클릭

 

데이터파일을 선택하고

 

'변환' 버튼을 누르면

 

아래처럼 나옵니다

 

 

앞으로는 엑셀파일 하나로 관리 해보려고 합니다.

이후에는 로컬 엑셀파일이 아닌 구글 엑셀 데이터시트를 읽어오는 것도 만들어볼 예정입니다.

 

프로젝트 소스파일은 깃에 올려놨어요.

자유 수정은 가능하지만 출처만 남겨주세요.

starlight97/SLG_ExcelToJson (github.com)

 

GitHub - starlight97/SLG_ExcelToJson

Contribute to starlight97/SLG_ExcelToJson development by creating an account on GitHub.

github.com

 

=================================================================================

2023.07.30 json 파일 하나로 통합

300x250
300x250

1. 제목

- 프로그래머스(17686) : 2018 KAKAO BLIND RECRUITMENT 오픈채팅방

문제 링크 : 코딩테스트 연습 - 오픈채팅방 | 프로그래머스 스쿨 (programmers.co.kr)

 


2. 풀이 과정

 

uidMap이라는 map 자료구조는 유저 아이디를 key로, 유저의 닉네임을 value로 저장합니다. uidRecord라는 벡터는 각 로그마다 유저 아이디와 해당 로그 메시지를 pair로 묶어 저장하는 용도로 사용됩니다.

for 루프에서는 record 벡터의 각 로그를 분리하여 command, uid, nickname 변수에 저장합니다. command 변수는 로그 유형을, uid 변수는 유저 아이디를, nickname 변수는 유저의 닉네임을 나타냅니다.

로그 유형에 따라 다음과 같은 처리를 수행합니다.

Enter: uidMap에 해당 유저 아이디가 이미 존재하는 경우에는 닉네임을 업데이트하고, 존재하지 않는 경우에는 새로 추가합니다. 그리고 uidRecord 벡터에 유저 아이디와 "님이 들어왔습니다."라는 메시지를 pair로 묶어 추가합니다.
Leave: uidRecord 벡터에 유저 아이디와 "님이 나갔습니다."라는 메시지를 pair로 묶어 추가합니다.
Change: uidMap에 해당 유저 아이디가 존재하는 경우에는 닉네임을 업데이트합니다.
uidRecord 벡터에 추가된 pair들은 마지막 for 루프에서 uidMap을 참조하여 출력 문자열을 생성하고, answer 벡터에 추가됩니다.


코드를 자세히 살펴보면, 우선 record라는 vector<string>을 입력값으로 받아들입니다. 이 record는 각 로그를 문자열 형태로 저장하고 있습니다. 이후 uidMap이라는 map 자료구조와 uidRecord라는 vector<pair> 자료구조를 선언해줍니다.

이후 record의 크기만큼 반복문을 실행하면서, split 함수를 이용하여 로그를 공백 단위로 분리해줍니다. 이 분리된 로그를 기반으로 각 로그에 따른 처리를 해줍니다.

만약 Enter 명령어가 들어온다면, 해당 유저가 기존에 존재하는 유저인지 신규 유저인지를 판단하여 uidMap에 저장합니다. 또한, uidRecord에는 해당 유저가 들어왔음을 기록해줍니다.

만약 Leave 명령어가 들어온다면, 해당 유저가 나갔음을 uidRecord에 기록해줍니다.

만약 Change 명령어가 들어온다면, uidMap에 해당 유저의 닉네임을 변경해줍니다.

이렇게 각 로그에 대한 처리를 마친 후, uidRecord에 저장된 로그 정보와 uidMap에 저장된 유저 정보를 기반으로 최종적인 출력 값을 만들어냅니다. uidRecord의 크기만큼 반복문을 실행하면서, uidMap에서 해당 유저의 닉네임을 가져와 로그와 함께 출력 값을 만들어 answer vector에 push_back 해줍니다.

 

 


3. 코드

#include <string>
#include <vector>
#include <map>
#include <sstream>

using namespace std;

//https://www.lifencoding.com/language/22#post?p=1
vector<string> split(string str, char Delimiter) 
{
    istringstream iss(str);             // istringstream에 str을 담는다.
    string buffer;                      // 구분자를 기준으로 절삭된 문자열이 담겨지는 버퍼

    vector<string> result;

    // istringstream은 istream을 상속받으므로 getline을 사용할 수 있다.
    while (getline(iss, buffer, Delimiter)) 
    {
        result.push_back(buffer);               // 절삭된 문자열을 vector에 저장
    }

    return result;
}

vector<string> solution(vector<string> record) 
{
    vector<string> answer;

    map<string, string> uidMap;
    vector<pair<string, string>> uidRecord;

    int recordSize = record.size();
    for (int i = 0; i < recordSize; i++)
    {
        vector<string> splitStr = split(record[i], ' ');
        string command = splitStr[0];
        string uid = splitStr[1]; 
        
        if (command == "Enter")
        {
            string nickname = splitStr[2];
            // 기존 유저
            if (uidMap.find(uid) != uidMap.end())
                uidMap[uid] = nickname;
            // 신규 유저
            else
                uidMap.insert({uid, nickname});

            uidRecord.push_back({uid, "님이 들어왔습니다."});
        }
        else if (command == "Leave")
        {
            uidRecord.push_back({ uid, "님이 나갔습니다." });
        }
        else if (command == "Change")
        {
            string nickname = splitStr[2];
            if (uidMap.find(uid) != uidMap.end())
                uidMap[uid] = nickname;
        }
    }

    int uidRecordSize = uidRecord.size();
    for (int i = 0; i < uidRecordSize; i++)
    {
        answer.push_back(uidMap[uidRecord[i].first] + uidRecord[i].second);
    }

    return answer;
}
300x250
300x250

1. XLua란?

 

XLua는 C#과 Lua 스크립트를 결합하여 Unity 프로젝트에서 스크립트를 작성할 수 있도록 지원하는 라이브러리예요. XLua는 Lua 스크립트 엔진과 C# 언어 간의 인터페이스를 제공하여, Lua 스크립트와 C# 클래스 간의 데이터 전송 및 상호 작용을 쉽게 할 수 있도록 도와줘요. 이를 통해 C# 언어의 장점과 Lua 스크립트 언어의 장점을 모두 활용하여 Unity 게임 개발을 더욱 효율적으로 할 수 있어요.

 

 

2.Unity에서 XLua를 사용하기 위한 설치 및 설정하기

 

2.1 xLua 프로젝트 다운로드하기

Tencent/xLua: xLua is a lua programming solution for C# ( Unity, .Net, Mono) , it supports android, ios, windows, linux, osx, etc. (github.com)

 

GitHub - Tencent/xLua: xLua is a lua programming solution for C# ( Unity, .Net, Mono) , it supports android, ios, windows, linu

xLua is a lua programming solution for C# ( Unity, .Net, Mono) , it supports android, ios, windows, linux, osx, etc. - GitHub - Tencent/xLua: xLua is a lua programming solution for C# ( Unity, .N...

github.com

 

xLua 페이지에서 프로젝트를 다운받아요.

 

 

2.2 다운로드한 프로젝트 Unity로 열기

 

압축파일을 풀고 해당프로젝트를 Unity로 열면

이렇게 에디터 버전이 누락됐다고 나오는데 전 그냥 설치되어 있는 2021.3.5f1 버전으로 열었어요.

 

'Continue' 클릭

 

이렇게 잘 열렸다면 성공이에요.

 

2.3 프로젝트 유니티패키지로 뽑아내기

 

에셋 폴더에서 우클릭을 해서 'Export Package...' 을 클릭해서 유니티 패키지를 생성해요.

준비는 끝났어요. 이제 저희 프로젝트에 이 패키지를 임포트 하면 돼요.

 

 

2.4 새 프로젝트 생성

저는 3D 프로젝트를 만들어서 테스트했어요.

 

 

2.5 xLua 패키지 임포트 하기

생성된 프로젝트에 아까 뽑아낸 유니티 패키지를 임포트 해줘요.

 

상단에 XLua가 표시되면 정상적으로 설치가 된 거예요.

 

 

 

3. Lua 스크립트 작성하기

 

이제 Lua 스크립트를 작성해야 하는데 마우스 우클릭 했을 때 C# 스크립트처럼 Create 메뉴에 Lua스크립트가 없어서 이거 먼저 메뉴버튼을 만들어 줬어요.

 

 

XLuaMenuEditor.cs

using UnityEditor;
using System.IO;

public class XLuaMenuEditor : Editor
{
    [MenuItem("Assets/Create/Lua Script")]
    private static void CreateLuaScript()
    {
        string path = AssetDatabase.GetAssetPath(Selection.activeObject);
        if (path == "")
        {
            path = "Assets";
        }
        else if (Path.GetExtension(path) != "")
        {
            path = path.Replace(Path.GetFileName(AssetDatabase.GetAssetPath(Selection.activeObject)), "");
        }

        // 새 텍스트 파일을 생성
        string fileName = "NewLuaScript.lua.txt";
        int fileIndex = 0;
        while (File.Exists(Path.Combine(path, fileName)))
        {
            fileIndex++;
            fileName = "NewLuaScript" + fileIndex + ".lua.txt";
        }

        // 파일 생성 후 이름 변경
        string newFilePath = Path.Combine(path, fileName);
        File.Create(newFilePath).Dispose();
        AssetDatabase.Refresh();
    }
}

이제 'Lua Script'를 클릭하면 루아 스크립트 파일이 생성돼요. 

간단하게 유니티 Debug.Log()를 찍는 코드를 작성해볼게요.

 

 

HelloWorld.cs와, HelloWorld.lua.txt 파일을 만들어요.

 

Helloworld.cs

using UnityEngine;
using XLua;
using System;

[LuaCallCSharp]
public class HelloWorld : MonoBehaviour
{
    public TextAsset luaScript;
    internal static LuaEnv luaEnv = new LuaEnv(); //all lua behaviour shared one luaenv only!

    private Action luaStart;
    private Action luaUpdate;
    private Action luaOnDestroy;
    float delta = 0;
    private LuaTable scriptEnv;

    void Start()
    {
        Init();
    }

    
    void Update()
    {
        if (luaUpdate == null && Input.GetKeyDown(KeyCode.G))
        {
            Init();
        }
        if (luaUpdate != null)
        {
            delta += Time.deltaTime;
            luaUpdate();
        }

        if (luaUpdate != null && delta >= 3)
        {
            DestroyLua();
        }
    }

    void Init()
    {
        delta = 0;
        scriptEnv = luaEnv.NewTable();

        LuaTable meta = luaEnv.NewTable();
        meta.Set("__index", luaEnv.Global);
        scriptEnv.SetMetaTable(meta);
        meta.Dispose();

        scriptEnv.Set("self", this);

        luaEnv.DoString(luaScript.text, "HelloWorld", scriptEnv);

        scriptEnv.Get("start", out luaStart);
        scriptEnv.Get("update", out luaUpdate);
        scriptEnv.Get("ondestroy", out luaOnDestroy);


        if (luaStart != null)
        {
            luaStart();
        }
    }

    void DestroyLua()
    {
        if (luaOnDestroy != null)
        {
            luaOnDestroy();
        }
        luaOnDestroy = null;
        luaUpdate = null;
        luaStart = null;
        scriptEnv.Dispose();
    }
}

 

저도 공부하는 중이라 간단하게 공부한 바로는

 

 

[LuaCallCSharp] : 이 클래스가 Lua 스크립트에서 호출될 수 있는 C# 메서드를 가지고 있음을 XLua에 알려줘요.


TextAsset luaScript: 이 변수는 실행할 Lua 스크립트를 저장하는 텍스트 파일을 참조해요.


static LuaEnv luaEnv: 이 변수는 모든 Lua 동작이 하나의 LuaEnv를 공유할 수 있도록 하는 XLua의 LuaEnv 클래스의 인스턴스를 저장해요. internal로 선언되어 있기 때문에, 같은 어셈블리 내에서만 사용 가능해요.


Action luaStart, luaUpdate, luaOnDestroy: 이 변수들은 각각 Lua 스크립트에서 가져온 start(), update(), ondestroy() 함수를 저장해요. 이 변수들은 Lua 스크립트에서 해당 함수가 정의되어 있지 않은 경우에도 null 값을 가질 수 있으며, 이러한 경우에는 해당 함수를 호출하지 않아요.


LuaTable scriptEnv: 스크립트가 실행될 때 생성된 Lua 환경을 저장하기 위한 LuaTable 변수를 선언해요

 

 

HelloWorld.lua.txt

function start()
	print("lua start...")
end

function update()
	print("lua update...")
end

function ondestroy()
    print("lua destroy")
end

 

 

4. 테스트해보기

 

이제 씬에 게임오브젝트를 하나 만들고 HelloWorld.cs를 넣어준 다음 인스팩터 창에서 Lua Script부분에 작성한 Lua Scirpt를 어싸인해줘요.

 

 

그리고 프로젝트를 실행하면

아래처럼 Lua스크립트에서 print 메서드를 잘 호출하고 있음을 확인할 수 있어요.

또한 루아가 가진 타입스크립트에 특징인데 중간에 루아 스크립트를 수정하고 저장하면 프로젝트를 껐다키지 않아도 바로 반영이 돼요.

이걸 확인하려고 'G'키를 누를 때마다 루아스크립트가 실행 중이지 않다면 실행되게 코드를 작성했어요.

 

이 기능이 루아가 가진 큰 장점 중 하나라서 지금은 간단한 디버그만 찍어봤지만 다양한 작업을 할 수 있을 거라 공부해 볼 가치가 있다고 생각해요.

 

 

출처 및 참고문헌

Tencent/xLua: xLua is a lua programming solution for C# ( Unity, .Net, Mono) , it supports android, ios, windows, linux, osx, etc. (github.com)

300x250
300x250

안녕하세요.

 

오늘은 오브젝트 풀링에 대해서 알아볼게요.

 

1. 왜 오브젝트 풀링을 써야 할까요?

 

유니티에서 오브젝트를 생성하기 위해서는 Instantiate를 사용하고 삭제할 때는 Destroy를 사용해요.

하지만 Instantiate, Destroy 이 두 함수는 상당히 비용을 크게 먹는다는 것을 구글링을 해보면 알 수 있어요.

Instantiate(오브젝트 생성)은 메모리를 새로 할당하고 리소스를 로드하는 등의 초기화 과정이 필요하고, Destroy(오브젝트 파괴)는 파괴 이후에 발생하는 가비지 컬렉팅으로 인한 프레임 드랍이 발생할 수 있어요.

 

그렇다면 플레이어의 총알과 같이 자주 생성되고 삭제되는 오브젝트들이 있을 경우 치명적일 수 있는 거죠. 이때 사용할 수 있는 것이 오브젝트 풀링이에요. 자주 사용하는 오브젝트를 미리 생성해 놓고 이걸 사용할 때마다 새로 생성 삭제 하는 것 이 아닌 사용할 때는 오브젝트풀한테 빌려서 사용하고 삭제할 때는 오브젝트풀한테 돌려줌으로써 단순하게 오브젝트를 활성화 비활성화 만 하는 개념이에요.

 

 

유니티에서 오브젝트 풀링을 처음부터 지원하진 않았어요. 그렇기에 오브젝트 풀링을 구현하는 방법이 사람마다 달랐으며 성능도 달랐을 거라고 생각돼요. 하지만 지금은 유니티에서 오브젝트 풀링을 공식적으로 지원해요. 이 글은 유니티에서 공식적으로 제공하는 오브젝트 풀링 사용방법에 대해서 정리하는 글이에요.

 

 

2. 기본 프로젝트 세팅

 

간단하게 플레이어가 움직이고 플레이어가 총알을 발사하는 프로젝트를 만들어줘요.

 

Player.cs

using UnityEngine;

public class Player : MonoBehaviour
{
    public float speed = 1f;
    public Transform bulletSpawnPoint;
    public GameObject bulletPrefab;

    void Update()
    {

        if (Input.GetKeyDown(KeyCode.Space))
        {
            var bulletGo = Instantiate(bulletPrefab);

            bulletGo.transform.position = this.bulletSpawnPoint.position;
        }

        // 가로 이동 반환값 : LeftArrow = -1 RightArrow = 1
        var h = Input.GetAxisRaw("Horizontal");
        // 세로 이동 반환값 : DownArrow = -1 UpArrow = 1        
        var v = Input.GetAxisRaw("Vertical");

        //단위 벡터 (크기가 1인 벡터)
        var dir = new Vector3(h, v, 0).normalized;

        this.transform.Translate(dir * this.speed * Time.deltaTime);
    }
}

플레이어가 이동하면서 space키를 누르면 총알을 발사하는 간단한 코드예요. 

 

 

Bullet.cs

using UnityEngine;

public class Bullet : MonoBehaviour
{
    public float speed = 5f;

    void Update()
    {

        // 총알이 많이 날라가면 삭제 해주기
        if (this.transform.position.y > 5)
        {
            Destroy(this.gameObject);
        }

        this.transform.Translate(Vector3.up * this.speed * Time.deltaTime);
    }
}

총알이 생성되고 나서 위로 계속 올라가다가 일정 위치 이상으로 올라가면 삭제되는 간단한 코드예요.

 

 

 

간단하게 Circle 오브젝트에 빨간색을 넣어 만든 Bullet오브젝트는 프리팹으로 만들고 씬에선 삭제해 줘요

 

다음으로 Square 오브젝트에 검은색을 넣고 총알이 스폰될 위치를 잡아주고 이 위치와 총알 프리팹을 어싸인해 줘요.

 

 

 

 

3. 유니티 오브젝트 풀링 사용하기

이제 이 기존 코드를 유니티에서 제공하는 오브젝트 풀링을 사용하는 방식으로 바꿔볼게요.

 

바꾸기 전 오브젝트 풀링을 구현해 줘요.

ObjectPoolManager.cs

using UnityEngine;
using UnityEngine.Pool;

public class ObjectPoolManager : MonoBehaviour
{

    public static ObjectPoolManager instance;

    public int defaultCapacity = 10;
    public int maxPoolSize = 15;
    public GameObject bulletPrefab;

    public IObjectPool<GameObject> Pool { get; private set; }

    private void Awake()
    {
        if (instance == null)
            instance = this;
        else
            Destroy(this.gameObject);


        Init();
    }

    private void Init()
    {
        Pool = new ObjectPool<GameObject>(CreatePooledItem, OnTakeFromPool, OnReturnedToPool,
        OnDestroyPoolObject, true, defaultCapacity, maxPoolSize);

        // 미리 오브젝트 생성 해놓기
        for (int i = 0; i < defaultCapacity; i++)
        {
            Bullet bullet = CreatePooledItem().GetComponent<Bullet>();
            bullet.Pool.Release(bullet.gameObject);
        }
    }

    // 생성
    private GameObject CreatePooledItem()
    {
        GameObject poolGo = Instantiate(bulletPrefab);
        poolGo.GetComponent<Bullet>().Pool = this.Pool;
        return poolGo;
    }

    // 사용
    private void OnTakeFromPool(GameObject poolGo)
    {
        poolGo.SetActive(true);
    }

    // 반환
    private void OnReturnedToPool(GameObject poolGo)
    {
        poolGo.SetActive(false);
    }

    // 삭제
    private void OnDestroyPoolObject(GameObject poolGo)
    {
        Destroy(poolGo);
    }
}

 

 

이제 Bullet 오브젝트는 ObjectPoolManager가 생성하고 사용할 때마다 ObjectPoolManager가 관리하고 있는 Bullet 오브젝트를 받아서 사용할 수 있는 코드예요.

 

기존 Bullet 코드를 바꿔줘요.

Bullet.cs

using UnityEngine;
using UnityEngine.Pool;

public class Bullet : MonoBehaviour
{
    public IObjectPool<GameObject> Pool { get; set; }
    public float speed = 5f;

    void Update()
    {

        // 총알이 많이 날라가면 삭제 해주기
        if (this.transform.position.y > 5)
        {
            // 이제 자신이 Destroy를 하지 않는다.
            //Destroy(this.gameObject);

            // 오브젝트 풀에 반환
            Pool.Release(this.gameObject);
        }

        this.transform.Translate(Vector3.up * this.speed * Time.deltaTime);
    }
}

 

Player도 바꿔줘요.

using UnityEngine;

public class Player : MonoBehaviour
{
    public float speed = 1f;
    public Transform bulletSpawnPoint;

    void Update()
    {

        if (Input.GetKeyDown(KeyCode.Space))
        {
            // 총알생성을 플레이어가 하지 않는다.
            //var bulletGo = Instantiate(bulletPrefab);

            // 오브젝트풀 에서 빌려오기
            var bulletGo = ObjectPoolManager.instance.Pool.Get();

            bulletGo.transform.position = this.bulletSpawnPoint.position;
        }

        // 가로 이동 반환값 : LeftArrow = -1 RightArrow = 1
        var h = Input.GetAxisRaw("Horizontal");
        // 세로 이동 반환값 : DownArrow = -1 UpArrow = 1        
        var v = Input.GetAxisRaw("Vertical");

        //단위 벡터 (크기가 1인 벡터)
        var dir = new Vector3(h, v, 0).normalized;

        this.transform.Translate(dir * this.speed * Time.deltaTime);
    }
}

 

이제 프로젝트를 시작하면 오브젝트를 미리 최소 정해진 수만큼 만들어 놓고 플레이어가 요청할떄마다 대여해서 사용할 거예요.

 

 

 

4. 오브젝트 여러 개 관리하기

 

지금까지는 총알 1개만 관리하는 거였다면 여러 종류의 오브젝트를 관리하는 오브젝트풀을 관리할 수 있어야 실용성이 있을 거예요.

 

그래서 저는 오브젝트풀을 관리하는 딕셔너리를 선언해서 외부에서는 오브젝트풀매니저에게 오브젝트를 요청만 하면 매니저가 해당 오브젝트에 해당하는 오브젝트풀에 접근해서 오브젝트를 받아서 넘겨주는 방식으로 구현했어요.

 

먼저 오브젝트풀을 관리하기 위해선 오브젝트풀에서 사용할 프리팹과 몇 개를 생성할지 count를 알아야 해요. 이걸 딕셔너리로 관리할 거 기 때문에 key값으로 사용할 오브젝트네임까지 받는 클래스를 선언했어요.

    [System.Serializable]
    private class ObjectInfo
    {
        // 오브젝트 이름
        public string objectName;
        // 오브젝트 풀에서 관리할 오브젝트
        public GameObject perfab;
        // 몇개를 미리 생성 해놓을건지
        public int count;
    }

 

이렇게 하고 오브젝트 매니저에게 할당하면 아래처럼 오브젝트풀에 등록할 오브젝트들을 등록할 수 있어요.

 

그리고 저는 딕셔너리를 2개와 String 변수 하나를 선언했는데

    // 생성할 오브젝트의 key값지정을 위한 변수
    private string objectName;
    
    // 오브젝트풀들을 관리할 딕셔너리
    private Dictionary<string, IObjectPool<GameObject>> 
    ojbectPoolDic = new Dictionary<string, IObjectPool<GameObject>>();

    // 오브젝트풀에서 오브젝트를 새로 생성할때 사용할 딕셔너리
    private Dictionary<string, GameObject> poolGoDic = new Dictionary<string, GameObject>();

 

objectPoolDic 은 오브젝트풀을 관리하기 위한 딕셔너리라면 poolGoDIc은 왜 필요하냐면요

 

    // 생성
    private GameObject CreatePooledItem()
    {
        GameObject poolGo = Instantiate(poolGoDic[objectName]);
        poolGo.GetComponent<PoolAble>().Pool = ojbectPoolDic[objectName];
        return poolGo;
    }

처음에 오브젝트풀에서 오브젝트를 생성할 때 어떤 오브젝트를 생성해야 하는지를 알 수가 없어요. 그렇기 때문에 생성해야 하는 오브젝트들을 objecgtName을 key값으로 갖게 하고 프리팹들을 저장해 놓고 풀에서 오브젝트를 새로 생성할 때 생성할 objecgtName key값에 해당하는 poolGoDic에 접근해서 오브젝트를 생성하는 거예요.

 

또한 보시면 이젠 Bullet 스크립트가 아니라 PoolAble 스크립트에 접근해서 Pool을 지정해주고 있는데 PoolAble 스크립트는 오브젝트풀을 사용할 오브젝트들이 모두 상속받게 해서 이 스크립트를 상속받은 오브젝트들만 오브젝트풀에 등록할 수 있게 했어요.

그래서 PoolAble 스크립트엔 그냥 자신이 돌려줘야 할 Pool을 저장할 프로퍼티와 자신의 게임 오브젝트를 넘겨줄 ReleaseObject 메서드만 정의를 해두면 돼요.

 

 

PoolAble.cs

using UnityEngine;
using UnityEngine.Pool;

public class PoolAble : MonoBehaviour
{
    public IObjectPool<GameObject> Pool { get; set; }

    public void ReleaseObject()
    {
        Pool.Release(gameObject);
    }
}

 

Bullet.cs

using UnityEngine;

public class Bullet : PoolAble
{
    public float speed = 5f;

    void Update()
    {
        // 총알이 많이 날라가면 삭제 해주기
        if (this.transform.position.y > 5)
        {
            // 오브젝트 풀에 반환
            ReleaseObject();
        }

        this.transform.Translate(Vector3.up * this.speed * Time.deltaTime);
    }
}

 

ObjectPoolManager.cs

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;

public class ObjectPoolManager : MonoBehaviour
{
    [System.Serializable]
    private class ObjectInfo
    {
        // 오브젝트 이름
        public string objectName;
        // 오브젝트 풀에서 관리할 오브젝트
        public GameObject perfab;
        // 몇개를 미리 생성 해놓을건지
        public int count;
    }


    public static ObjectPoolManager instance;

    // 오브젝트풀 매니저 준비 완료표시
    public bool IsReady { get; private set; }

    [SerializeField]
    private ObjectInfo[] objectInfos = null;

    // 생성할 오브젝트의 key값지정을 위한 변수
    private string objectName;

    // 오브젝트풀들을 관리할 딕셔너리
    private Dictionary<string, IObjectPool<GameObject>> ojbectPoolDic = new Dictionary<string, IObjectPool<GameObject>>();

    // 오브젝트풀에서 오브젝트를 새로 생성할때 사용할 딕셔너리
    private Dictionary<string, GameObject> goDic = new Dictionary<string, GameObject>();

    private void Awake()
    {
        if (instance == null)
            instance = this;
        else
            Destroy(this.gameObject);

        Init();
    }


    private void Init()
    {
        IsReady = false;

        for (int idx = 0; idx < objectInfos.Length; idx++)
        {
            IObjectPool<GameObject> pool = new ObjectPool<GameObject>(CreatePooledItem, OnTakeFromPool, OnReturnedToPool,
            OnDestroyPoolObject, true, objectInfos[idx].count, objectInfos[idx].count);

            if (goDic.ContainsKey(objectInfos[idx].objectName))
            {
                Debug.LogFormat("{0} 이미 등록된 오브젝트입니다.", objectInfos[idx].objectName);
                return;
            }

            goDic.Add(objectInfos[idx].objectName, objectInfos[idx].perfab);
            ojbectPoolDic.Add(objectInfos[idx].objectName, pool);

            // 미리 오브젝트 생성 해놓기
            for (int i = 0; i < objectInfos[idx].count; i++)
            {
                objectName = objectInfos[idx].objectName;
                PoolAble poolAbleGo = CreatePooledItem().GetComponent<PoolAble>();
                poolAbleGo.Pool.Release(poolAbleGo.gameObject);
            }
        }

        Debug.Log("오브젝트풀링 준비 완료");
        IsReady = true;
    }

    // 생성
    private GameObject CreatePooledItem()
    {
        GameObject poolGo = Instantiate(goDic[objectName]);
        poolGo.GetComponent<PoolAble>().Pool = ojbectPoolDic[objectName];
        return poolGo;
    }

    // 대여
    private void OnTakeFromPool(GameObject poolGo)
    {
        poolGo.SetActive(true);
    }

    // 반환
    private void OnReturnedToPool(GameObject poolGo)
    {
        poolGo.SetActive(false);
    }

    // 삭제
    private void OnDestroyPoolObject(GameObject poolGo)
    {
        Destroy(poolGo);
    }

    public GameObject GetGo(string goName)
    {
        objectName = goName;

        if (goDic.ContainsKey(goName) == false)
        {
            Debug.LogFormat("{0} 오브젝트풀에 등록되지 않은 오브젝트입니다.", goName);
            return null;
        }

        return ojbectPoolDic[goName].Get();
    }
}

 

 

 

Player.cs

using UnityEngine;

public class Player : MonoBehaviour
{
    public float speed = 1f;
    public Transform bulletSpawnPoint;

    void Update()
    {

        if (Input.GetKeyDown(KeyCode.Alpha1))
        {
            // 오브젝트풀 에서 빌려오기
            var bulletGo = ObjectPoolManager.instance.GetGo("BulletRed");

            bulletGo.transform.position = this.bulletSpawnPoint.position;
        }
        if (Input.GetKeyDown(KeyCode.Alpha2))
        {
            // 오브젝트풀 에서 빌려오기
            var bulletGo = ObjectPoolManager.instance.GetGo("BulletGreen");

            bulletGo.transform.position = this.bulletSpawnPoint.position;
        }
        if (Input.GetKeyDown(KeyCode.Alpha3))
        {
            // 오브젝트풀 에서 빌려오기
            var bulletGo = ObjectPoolManager.instance.GetGo("BulletBlue");

            bulletGo.transform.position = this.bulletSpawnPoint.position;
        }

        // 가로 이동 반환값 : LeftArrow = -1 RightArrow = 1
        var h = Input.GetAxisRaw("Horizontal");
        // 세로 이동 반환값 : DownArrow = -1 UpArrow = 1        
        var v = Input.GetAxisRaw("Vertical");

        //단위 벡터 (크기가 1인 벡터)
        var dir = new Vector3(h, v, 0).normalized;

        this.transform.Translate(dir * this.speed * Time.deltaTime);
    }
}

 

 

5. 완성

 

이렇게 한번 만들어두면 나중엔 ObjectPoolManager.instance.GetGo(가져올 오브젝트이름)으로 접근해서 사용할 수 있어요

 

 

 

 

참고자료 :

Unity - Scripting API: ObjectPool<T0> (unity3d.com)

유니티 - 오브젝트 풀링(Object pooling) (tistory.com)

300x250
300x250

유니티에서 파일을 생성하면 meta파일이 같이 생성돼요.

아래 사진처럼 Bulles.cs 파일을 생성하면 Bulles.cs.meta파일이 같이 생성되는 거처럼요.

 

편집기에선 굳이 볼 필요가 없는데 보이니까 난잡한 거 같아서 숨기는 방법을 찾아봤어요. 대부분 영문버전을 사용하고 있으셔서 한글버전용으로 올렸어요.

 

 

 

 

 

1. 파일 -> 기본 설정 -> 설정

 

 

2. 텍스트 편집기 -> 파일 -> 'Exclude' -> '패턴 추가'

 

3. '**/*.meta' 입력 -> '확인'

 

 

4. 확인

 

훨씬 보기 편해졌네요.

300x250

'잡동사니' 카테고리의 다른 글

[Visual Code] 열려있던 탭 저장  (0) 2023.03.03
Animal Rescue(동물구조대)  (0) 2022.12.12
300x250

1. 제목

- 프로그래머스(17686) : 2018 KAKAO BLIND RECRUITMENT [3차] 파일명 정렬 

문제 링크 : 코딩테스트 연습 - [3차] 파일명 정렬 | 프로그래머스 스쿨 (programmers.co.kr)


2. 풀이 과정

 

쉽게 풀려고 하면 쉽게 풀 수 있는 쉬운 정렬 문제이지만 제가 푼 방법이 좋은 방법은 아닌 거 같네요.

저는 문제에서 정렬 문제이지만 구현문제처럼 문제에서 요구하는 대로 그대로 풀었어요.

문자열을 Head, Number 부분을 나눠서 저장한 다음 이 둘을 먼저 각각 비교하고 같다면 들어온 순서를 유지했어요.

구조체를 만들어서 작업을 했는데 

struct File
{
	string fullName;
	string head;
	int number;
	int inputNumber;
};

그냥 처음부터 들어온 문자열을 그대로 저장하는 fullName 변수

숫자를 만나기 전까지를 저장할 head 변수

head 이후에 숫자를 저장하는 number 변수

마지막으로 들어온 순서를 유지하기 위해 들어온 순서를 기억하는 inputNumber 변수 에요.

 

처음에 입력으로 받는 files 벡터를 순회하며 fullName을 바로 저장해요.

for (auto file : files)
{
    File tempFile;
    tempFile.fullName = file;
}

 

이다음부터 file 문자열의 index를 돌며 head 부분을 분리해 냈어요.

int idx = 0;
for (; idx < file.size(); idx++)
{
    // 문자열 체크
    if (!IsNumber(file[idx]))
    {
        if (file[idx] >= 'A' && file[idx] <= 'Z')
            tempFile.head += (file[idx] + 32);
        else
            tempFile.head += file[idx];
    }
    else
    {
        break;
    }
}

 

그다음 number 부분을 분리하기 위해 숫자가 아닌 문자를 만날 때까지 strNumber 임시 변수에 넣어놓고 stoi() 함수로 문자를 숫자로 바꿔 저장한 후에 tempFile을 sortFiles 벡터에 넣어 줘요.

string strNumber;
for (; idx < file.size(); idx++)
{
    if (IsNumber(file[idx]))
        strNumber += file[idx];
    else
        break;
}
tempFile.number = stoi(strNumber);
tempFile.inputNumber = cnt;

cnt++;
sortFiles.push_back(tempFile);

 

마지막으로 sortFiles를 정렬한 후에 fullName만 따로 빼서 return 하면 정답처리가 돼요.

bool Compare(File file1, File file2)
{
	// 헤드 먼저 비교
	if (file1.head > file2.head)
		return false;
	else if (file1.head < file2.head)
		return true;

	// 헤드가 같다면 number 비교
	if (file1.number > file2.number)
		return false;
	else if (file1.number < file2.number)
		return true;

	// number 까지 같다면 들어온 순서 유지
	return file1.inputNumber < file2.inputNumber;
}

3. 함수 설명

bool Compare(File file1, File file2)

문제에서 요구하는 정렬 순서를 정의하기 위한 함수예요.

 

bool IsNumber(char c)

해당 문자가 숫자인지 체크하는 함수예요.


4. 코드

#include <algorithm>
#include <string>
#include <vector>

using namespace std;

struct File
{
	string fullName;
	string head;
	int number;
	int inputNumber;
};

bool Compare(File file1, File file2)
{
	// 헤드 먼저 비교
	if (file1.head > file2.head)
		return false;
	else if (file1.head < file2.head)
		return true;

	// 헤드가 같다면 number 비교
	if (file1.number > file2.number)
		return false;
	else if (file1.number < file2.number)
		return true;

	// number 까지 같다면 들어온 순서 유지
	return file1.inputNumber < file2.inputNumber;
}

// 문자가 숫자인지 체크하는 함수
bool IsNumber(char c)
{
	if (c >= '0' && c <= '9')
		return true;
	else
		return false;
}

vector<string> solution(vector<string> files)
{
	vector<File> sortFiles;
	vector<string> answer;

	int cnt = 0;
	for (auto file : files)
	{
		File tempFile;
		tempFile.fullName = file;

		int idx = 0;
		for (; idx < file.size(); idx++)
		{
			// 문자열 체크
			if (!IsNumber(file[idx]))
			{
				if (file[idx] >= 'A' && file[idx] <= 'Z')
					tempFile.head += (file[idx] + 32);
				else
					tempFile.head += file[idx];
			}
			else
			{
				break;
			}
		}

		string strNumber;
		for (; idx < file.size(); idx++)
		{
			if (IsNumber(file[idx]))
				strNumber += file[idx];
			else
				break;
		}
		tempFile.number = stoi(strNumber);
		tempFile.inputNumber = cnt;


		cnt++;
		sortFiles.push_back(tempFile);
	}
	

	sort(sortFiles.begin(), sortFiles.end(), Compare);

	for (auto sortFile : sortFiles)
		answer.push_back(sortFile.fullName);

	return answer;
}
300x250
300x250

코드편집툴로 Visual Studio community를 정말 오래 썼는데 회사에선 Rider나 VSCode를 사용하더라고요.

익숙한 Visual Studio community를 쓰려고 하였지만 개인이 사용할 때는 무료인데 회사에선 완전무료가 아니고 조건이 붙어요. 아래는 'MICROSOFT VISUAL STUDIO COMMUNITY 2022' 라이선스 설명이에요.

 

 

개별 라이선스 : 판매 또는 다른 목적으로 고유한 응용 프로그램에 개별적으로 작업하는 경우 이러한 응용 프로그램의 개발 및 테스트를 목적으로 소프트웨어를 사용할 수 있습니다.

 

조직 라이선스 :  조직인 경우 귀사의 사용자는 다음과 같이 소프트웨어를 사용할 수 있습니다:

i. 귀하의 사용자는 사용자의 수와 상관없이 OSI(오픈 소스 이니셔티브)에서 승인한 오픈 소스 소프트웨어 라이선스에 따라 출시된 응용 프로그램의 개발 및 테스트를 목적으로 소프트웨어를 사용할 수 있습니다.

ii. 귀사의 사용자는 사용자의 수와 상관없이 Visual Studio에 대한 확장 기능의 개발 및 테스트를 목적으로 소프트웨어를 사용할 수 있습니다.

iii. 귀사의 사용자는 사용자의 수와 상관없이 Windows 운영 체제에 대한 장치 드라이버의 개발 및 테스트를 목적으로 소프트웨어를 사용할 수 있습니다.

iv. 귀사의 사용자는 사용자의 수와 상관없이 Microsoft SQL Server Data Tools 또는 "Microsoft Analysis Services Projects", "Microsoft Reporting Services Projects" 또는 "SQL Server Integration Services Projects" 확장 기능을 사용하여 Microsoft SQL Server 데이터베이스 프로젝트 또는 Analysis Services, Reporting Services, Power BI Report Server 또는 Integration Services 프로젝트를 개발할 때 Microsoft SQL Server 개발에만 해당 소프트웨어를 사용할 수 있습니다.

v. 귀사의 사용자는 사용자의 수와 상관없이 강의실 교육(온라인 또는 직접)의 일환으로 응용 프로그램의 개발 및 테스트를 목적으로 또는 교육 연구 수행을 목적으로 소프트웨어를 사용할 수 있습니다.

vi. 위의 사항에 해당하지 않으며 귀사가 기업(아래에 정의됨)이 아닌 경우 최대 5명의 귀사의 개별 사용자가 응용 프로그램의 개발 및 테스트를 목적으로 동시에 소프트웨어를 사용할 수 있습니다.

vii. 귀하가 기업인 경우에는 그 직원 및 계약자가 응용 프로그램의 개발 또는 테스트를 목적으로 소프트웨어를 사용할 수 없습니다. 단, 상기 허용된 범위 내에서 (i) 오픈 소스, (ii) Visual Studio 확장, (iii) Windows 운영 체제를 위한 장치 드라이버, (iv) SQL Server 개발, 그리고 (v) 교육 목적은 여기서 제외됩니다.

"기업"이란 통칭 (a) 250대보다 많은 PC 또는 250명보다 많은 사용자가 있거나 (b) 연간 매출이 미화 1백만 달러(또는 다른 통화에서 이와 동등한 금액) 이상을 초과하는 조직 및 그 계열사이며, "계열사"란 조직을 통제하거나(과반수 소유권을 통해) 조직의 통제를 받거나 조직과 공동 통제권을 가지고 있는 모든 업체를 의미합니다.

License Terms | Microsoft Visual Studio Community 2022 - Visual Studio

 

 

이왕이면 회사와 집에서 같은 툴을 쓰는게 좋을 거 같아서 무료 코드 편집툴인 VSCode에 익숙해지기 위해서 유니티 스크립트 편집을 VsCode로 해보기로 했어요. 

 

근데 작업을 하고 난 후에 

이렇게 열린 탭 들 이 있는 상태로 VSCode를 닫기를 하면 

 

나중에 다시 열 때 기존에 열려 있던 창들이 다 닫혀 있더라고요.

 

 

찾아내느라 정말 힘들었어요.

 

1. '파일' -> '기본설정' -> '설정'

 

 

2. 창 -> 'Restore Windows' (모든 프로필에 적용) 찾기

기본값 'all'에서 'preserve'로 바꿔주기

 

 

 

이제 VSCode를 껐다 켜도 탭들이 잘 저장돼요.

 

 

출처 및 참고문헌

License Terms | Microsoft Visual Studio Community 2022 - Visual Studio

 

License Terms | Microsoft Visual Studio Community 2022 - Visual Studio

 

visualstudio.microsoft.com

 

300x250
300x250

 

저는 Visual Studio Code로 작업했어요.

 

 

1. Express 서버 열기 

 

1-1. Visual Studio Code 실행

 

1-2. 작업 폴더 설정

 

1-3. 'Open Folder' 클릭

 

1-4. App.js 파일 생성

 

1-5. Express 서버 설정

커맨드창에 아래 명령어 입력

npm init –y 
npm install express
npm i nodemon

 

커맨드 창이 없다면?

'Terminal' -> 'New Terminal' 클릭

 

파워쉘 말고 

 

'Command Prompt' 추가해주기

 

이제 여기서 명령어들 실행해주면 돼요.

 

1-6. 설치확인

1-7. App.js

const express = require('express');

const app = express();
const port = 3000;

app.use(express.json());

app.get('/', (req, res)=>
{
    res.send(`<h2>welcome to server</h2>`);
});

app.listen(port, ()=>
{
   console.log(`SERVER 실행됨 ${port}`); 
});

command

node app.js

 

1-8. 서버 열린거 확인

 

2. DB(MySQL) 연동

 

2.1 mysql2 모듈 설치

 

npm install -S mysql2

 

 

2.2 userDBC.js 추가

userDBC.js

const mysql = require('mysql2');

// Create the connection pool. The pool-specific settings are the defaults
const pool = mysql.createPool
({
  host: 'localhost',
  user: 'root',
  database: 'test',
  password: 'q142753',
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0
});

const getUsers = async ()=>
{
    const promisePool = pool.promise();
    const [rows] = await promisePool.query('select * from users;');
    console.log(rows);
    return rows;
};

module.exports = 
{
    getUsers
};

 

2.3 usersRouter.js 추가

usersRouter.js

const express = require('express');
const userDBC = require('./usersDBC');
const router = express.Router();

router.get('/getUsers', async (req, res)=>
{
    let res_get_users = 
    {
        status_code : 500,
        users : [] 
    };

    try
    {
        const rows = await userDBC.getUsers();
        res_get_users.status_code = 200;
        if(rows.length > 0)
        {
            rows.forEach((user)=>
            {
                res_get_users.users.push
                ({
                    userId : user.userId,
                    userPassword : user.userPassword,
                    userName : user.userName,
                    userSignUpDate : user.userSignUpDate
                });
            });
        }
        else
        {
            console.log('사용자 없음');
        }
    }
    catch(error)
    {
        console.log(error.message);
    }
    finally
    {

        //응답 
        //res.json(res_get_users);
        var result = '';

        for(var i=0; i < res_get_users.users.length; i++)
        {
        result += res_get_users.users[i].userId;
        result += res_get_users.users[i].userPassword;
        result += res_get_users.users[i].userName;
        result += res_get_users.users[i].userSignUpDate;
        
        result += "<br>";
        }

        res.send(result);
    }
});


module.exports = router;

App.js

const express = require('express');
const usersRouter = require('./usersRouter');

const app = express();
const port = 3000;

app.use(express.json());

app.use(`/users`, usersRouter);

app.get('/', (req, res)=>
{
    res.send(`<h2>welcome to server</h2>`);
});

app.listen(port, ()=>
{
   console.log(`SERVER 실행됨 ${port}`); 
});

 

300x250

'[개인공부] > Node.Js' 카테고리의 다른 글

[Node.Js ERROR] connect ECONNREFUSED ::1:3306  (0) 2023.11.27
[Node.Js] 설치 (18.14.2 LTS)  (0) 2023.02.26
300x250

1-1. 사용할 데이터베이스 설정

USE TEST;

 

1-2. 'Tables' 우 클릭 'Create Table' 클릭

1-3. 테이블 명, Charset/Collation 설정

1-4. 컬럼설정하고 'Apply' 클릭

 

1-5. 'Apply' 클릭

 

1-6. 'Finish' 클릭

 

1-7. 쿼리창에서 'SHOW TABLES;' 입력

SHOW TABLES;

생성된 테이블 확인

 

 

2.1 데이터 추가

 

기본 문법

1. INSERT INTO 테이블이름(필드이름1, 필드이름2, 필드이름3, ...)

   VALUES (데이터값1, 데이터값2, 데이터값3, ...)

2. INSERT INTO 테이블이름

   VALUES (데이터값1, 데이터값2, 데이터값3, ...)

 

INSERT INTO users(userId, userPassword, userName)
VALUES('gildong', 'p1234', '홍길동');

 

2.2 추가된 데이터 확인

SELECT * FROM users;

 

2.3 정상작동 확인 했으면 다른 데이터도 추가

 

INSERT INTO users(userId, userPassword, userName)
VALUES('mango', 'q3214', '망고');
INSERT INTO users(userId, userPassword, userName)
VALUES('yuri', 'w1234', '유리');
INSERT INTO users(userId, userPassword, userName)
VALUES('human', 'e1234', '사람');
INSERT INTO users(userId, userPassword, userName)
VALUES('cat', 'P@ssw0rd', '고양이');
INSERT INTO users(userId, userPassword, userName)
VALUES('puppy', 'P@ssw0rd', '강아지');

SELECT * FROM users;

 

 

 

Reference : 

코딩의 시작, TCP School

300x250
300x250

Node.Js 홈페이지를 보시면 'Node.js®는 Chrome V8 JavaScript 엔진으로 빌드된 JavaScript 런타임입니다.' 라고 설명하고 있어요.

이러한 Node.Js를 이용하여 서버를 간단하게 만들 수 있어요.

 

Node.Js 18.14.2 LTS 버전 설치 방법입니다.

 

1. 사이트 접속

Node.js (nodejs.org)

 

Node.js

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

 

2.  버전선택

 

저는 안정적인 LTS 버전으로 설치했어요.

 

 

3. node-v18.14.2-x64.msi 실행

 

4. 'Next' 클릭

 

 

5. 라이센스 동의하고 'Next' 클릭

 

 

6. 설치 경로 지정하고 'Next' 클릭

 

 

7. 'Next' 클릭

 

8. 'Next' 클릭

네이티브 모듈용 도구

 

선택적으로 네이티브 모듈을 컴파일하는 데 필요한 도구를 설치합니다.

일부 npm 모듈은 설치 시 c/C++용으로 컴파일해야 합니다. 이러한 모듈을 설치하려면 일부 도구(Python 및 Visual Studio 빌드 도구)를 설치해야 합니다.

필요한 도구를 자동으로 설치할 거냐고 묻는 건데 저는 자동설치 안할거기 때문에 그냥 'Next' 눌렀어요.

 

 

9.  'Install' 클릭

 

10. 'Finish' 클릭

 

11. 설치확인

 

 

cmd 키신 후 nove -v를 입력했을 때 위 화면이 나오면 정상 설치 된 거예요.

300x250
300x250

 

1. 커넥션 추가하기

 

커넥션 세팅

테스트 버튼 눌렀을 때 정상적으로 되었다면 위 화면이 나와요.

생성한 커넥션으로 접속해 주세요.

2. 스키마 생성하기

아래에 'Schemas' 탭 클릭

왼쪽에 빈 공간 영역 우클릭 후 스키마 생성 버튼 클릭

 

스키마 세팅하고 'Apply' 클릭

 

'Apply' 클릭

 

'Finish' 클릭

 

생성된 스키마 확인

 

 

3. 생성된 데이터베이스 확인

쿼리탭 클릭 후 'SHOW DATABASES' 입력 후 CTRL + ENTER

아래쪽에 결과창에 생성한 test 있으면 정상이에요.

 

300x250
300x250

0. 설치하기 전 이전에 설치된 게 있다면 삭제하고 설치해 주세요.

[MySQL] MySQL 삭제 :: 별빛상자 (tistory.com)

 

[MySQL] MySQL 삭제

C:\Program Files (x86)\MySQL\MySQL Installer for Windows MySQLInstaller.exe 실행 다 삭제하면 됩니다. 'Next' 클릭 'Next' 클릭 'Execuute' 클릭 삭제 정상적으로 된 거 확인하셨으면 'Finish' 클릭 깨끗하네요. appwiz.cpl에서

starlightbox.tistory.com

 

 

1. MySQL 인스톨러 다운

MySQL :: Download MySQL Installer (Archived Versions)

 

 

2. 'Download' 클릭

 

 

3. 'No thanks, just start my download' 클릭

 

4. 'mysql-installer-web-community-5.7.41.0.msi' 실행

 

 

5. 'Sever only' 체크하고 'Next' 클릭

 

6. 'Execute' 클릭

 

7. 다운로드가 완료 됐다면 'Next' 클릭

 

 

8. 'Execute' 클릭

 

9. 완료 됐다면 'Next' 클릭

 

10. 'Next' 클릭

 

 

11. 저는 기본값으로 내버려 두고 'Next' 클릭했어요.

 

 

12. Root 비밀번호 설정 하시고 'Next' 클릭

이거 비밀번호 잊어버리면 안 돼요.

 

 

13. 서비스창에 표시될 이름

이름 입력 후 'Start the MySQL Server at System Startup 체크 해제 후 'Next' 클릭

 

14. MySQL Installer는 다음 위치에 있는 파일 및 폴더의 권한을 업데이트하여 서버의 데이터 디렉터리를 보호할 수 있습니다.

 

예, Windows 서비스를 실행하는 사용자(해당하는 경우) 및 관리자 그룹에만 전체 액세스 권한을 부여합니다. 다른 사용자 및 그룹은 액세스 권한이 없습니다.

예, 하지만 액세스 수준을 검토하고 구성하겠습니다.

아니요, 서버 구성 후 권한을 관리하겠습니다.

 

 

전 그냥 기본값으로 설정해 주고 'Next' 클릭했어요

 

 

15. 'Execute' 클릭

 

 

16. 'Finish' 클릭

 

 

17. 'Next' 클릭

 

 

18. 'Finish' 클릭

 

 

C:\Program Files (x86)\MySQL\MySQL Installer for Windows

 

19. MySQLInstaller.exe 실행

 

 

20. 설치된 거 확인하셨으면 우측에 'Add' 클릭

 

 

21. Applications -> MySQL Workbench -> MySQL Workbench 8.0 -> MySQL Workbench 8.0.32 체크 후 화살표 클릭

 

 

22. 'Next' 클릭

 

 

23. 'Execute' 클릭

 

 

24. 'Next' 클릭

 

 

25. 'Execute' 클릭

 

 

26. 'Next' 클릭

 

 

27. 'Finish' 클릭

 

 

28. 위 화면이 나오면 정상적으로 설치가 된 겁니다.

 

 

29. MySQL Workbench 8.0 CE 실행

 

30. 연결해 보기

 

위 커넥션을 클릭하면 

 

 

아까 입력한 root 비밀번호 입력해서 접속되는지 확인

 

잘되네요!

300x250
300x250

C:\Program Files (x86)\MySQL\MySQL Installer for Windows

 

 

MySQLInstaller.exe 실행

 

다 삭제하면 됩니다.

 

 

'Next' 클릭

 

 

'Next' 클릭

 

'Execuute' 클릭

 

 

삭제 정상적으로 된 거 확인하셨으면 'Finish' 클릭

 

 

깨끗하네요.

 

appwiz.cpl에서도 MySQL Installer - Community 삭제해 주세요.

 

 

 

삭제가 완료 됐으면

 

C:\Program Files -> mysql

C:\Program Files (x86) -> mysql

C:\ProgramData -> mysql

C:\Users\사용자\AppData\Roaming

이 경로들 확인해 보시고

혹시라도 이렇게 남아 있으면 폴더까지 삭제하시면 돼요.

 

마지막으로 혹시 모르니 재부팅 한 번 하시는 게 좋지 않을까 생각이 되네요.

300x250
300x250

 

SLG_ExcelToJsonPortable.zip
1.31MB

 

압축푸시고 SLG_ExcelToJsonConverter.exe 실행하시면

 

 

 

나와요. 폴더열기 누르셔서 변환할 엑셀파일 있는 경로를 선택해 주세요.

 

 

폴더만 선택할 수 있는 다이얼로그라서 아무것도 안보이실 텐데 정상이에요.

 

 

실제로는 이렇게 엑셀파일들이 있어야 해요.

 

폴더 선택을 하셨다면

 

이렇게 해당폴더에 있는 엑셀파일들이 리스트박스에 표시되는데 이 중 변환할 파일들만 선택해서 변환 버튼을 눌러주시면 돼요.

 

 

변환이 완료되면  

 

 

엑셀파일이 있던 폴더에. cs파일 하고 json파일이 생성된 걸 확인할 수 있어요.

 

student_data.xlsx

StudentData.cs

student_data.json

 

엑셀 양식은 1 번열에 데이터이름, 2 번열에 데이터타입, 3 번열부터 데이터들 넣어주시면 돼요.

데이터 타입은

 

이것들만 사용해 주세요.

 

 

 

 

 

2023-04-08 아래 오류 해결 했어요 안 보셔도 돼요.

 

 

가끔 가다가

이런 에러가 나올 때 가 있는데 이때는 당황하지 마시고 엑셀에서 빈칸에 공백 같은 게 들어갔다는 거니까

엑셀에서

이동 옵션 선택 하시고

빈 셀 옵션 선택하신 다음 확인 눌러보시면

이렇게 숨어있는 칸들이 있어요 이것들 Delete 해주시고 다시 변환 눌러주시면 잘돼요.

 

프로젝트 소스파일은 깃에 올려놨어요.

starlight97/SLG_ExcelToJsonConverter: ExcelToJsonConverter (github.com)

300x250
300x250

1. 제목

- 백준 1916 최소비용 구하기

- BOJ 1916 최소비용 구하기

문제 링크 : 1916번: 최소비용 구하기 (acmicpc.net)

 

1916번: 최소비용 구하기

첫째 줄에 도시의 개수 N(1 ≤ N ≤ 1,000)이 주어지고 둘째 줄에는 버스의 개수 M(1 ≤ M ≤ 100,000)이 주어진다. 그리고 셋째 줄부터 M+2줄까지 다음과 같은 버스의 정보가 주어진다. 먼저 처음에는 그

www.acmicpc.net


2. 풀이 과정

 

BOJ1753이랑 정말 똑같은 문제예요. 골드문제인데 다익스트라만 구현하면 그냥 바로 풀리는 문제예요. 풀이과정보다는 다익스트라 개념을 공부하는 게 이문제를 쉽게 푸는데 도움이 된다고 생각해요.

 

[알고리즘] 다익스트라 (C++) :: 별빛상자 (tistory.com)

 

[알고리즘] 다익스트라 (C++)

다익스트라 알고리즘은 음의 간선이 없을 때 사용할 수 있는 하나의 정점에서 다른 모드 정점으로 가는 최단 경로를 구할 때 사용하는 알고리즘이에요. 현실 세계에선 음의 간선이 없기 때문에

starlightbox.tistory.com


3. 코드

#define INF 1000000000

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

int N, M;
int startIndex, endIndex;
vector <pair<int, int>> graph[1001];
int distances[1001];

void Dijkstra(int start)
{
	distances[start] = 0;
	priority_queue<pair<int, int>> pq;

	pq.push(make_pair(start, 0));
	while (!pq.empty())
	{
		int current = pq.top().first;
		int distance = -pq.top().second;
		pq.pop();
		
		//최단 거리가 아닌 경우 스킵
		if (distances[current] < distance) continue;
		for (int i = 0; i < graph[current].size(); i++)
		{
			// 선택된 노드의 인접 노드
			int next = graph[current][i].first;
			// 선택된 노드를 인접 노드로 거쳐서 가는 비용
			int nextDistance = distance + graph[current][i].second;
			// 기존의 최소 비용보다 더 저렴하다면 교체.
			if (nextDistance < distances[next])
			{
				distances[next] = nextDistance;
				pq.push(make_pair(next, -nextDistance));
			}
		}
	}
}

void Solution()
{
	for (int i = 1; i <= N; i++)
		distances[i] = INF;

	Dijkstra(startIndex);

	cout << distances[endIndex] << endl;	
}


void Input()
{
	cin >> N >> M;

	for (int i = 0; i < M; i++)
	{
		int from;
		int to;
		int distance;
		cin >> from >> to >> distance;
		graph[from].push_back(make_pair(to, distance));
	}
	cin >> startIndex >> endIndex;
}

int main()
{
	Input();
	Solution();

	return 0;
}
300x250

'<C++ 백준 BOJ> > DP' 카테고리의 다른 글

[백준 BOJ1753] 최단경로 (C++)  (0) 2023.02.18
300x250

1. 제목

- 백준 1753 최단경로

- BOJ 1753 최단경로

문제 링크 : 1753번: 최단경로 (acmicpc.net)

 

1753번: 최단경로

첫째 줄에 정점의 개수 V와 간선의 개수 E가 주어진다. (1 ≤ V ≤ 20,000, 1 ≤ E ≤ 300,000) 모든 정점에는 1부터 V까지 번호가 매겨져 있다고 가정한다. 둘째 줄에는 시작 정점의 번호 K(1 ≤ K ≤ V)가

www.acmicpc.net


2. 풀이 과정

 

풀이과정이랄 게 따로 없네요. 골드문제인데 다익스트라만 구현하면 그냥 바로 풀리는 문제예요. 다익스트라 개념만 알고 있으면 매우 쉽게 풀 수 있을 거 같아요.

 

[알고리즘] 다익스트라 (C++) :: 별빛상자 (tistory.com)

 

[알고리즘] 다익스트라 (C++)

다익스트라 알고리즘은 음의 간선이 없을 때 사용할 수 있는 하나의 정점에서 다른 모드 정점으로 가는 최단 경로를 구할 때 사용하는 알고리즘이에요. 현실 세계에선 음의 간선이 없기 때문에

starlightbox.tistory.com


3. 코드

#define INF 1000000000

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

int V, E;
int startIndex;
vector <pair<int, int>> graph[20001];
int distances[20001];

void Dijkstra(int start)
{
	distances[start] = 0;
	priority_queue<pair<int, int>> pq;

	pq.push(make_pair(0, start));
	while (!pq.empty())
	{
		int distance = -pq.top().first;
		int current = pq.top().second;
		pq.pop();
		
		//최단 거리가 아닌 경우 스킵
		if (distances[current] < distance) continue;

		for (int i = 0; i < graph[current].size(); i++)
		{
			// 선택된 노드를 인접 노드로 거쳐서 가는 비용
			int nextDistance = distance + graph[current][i].first;
			// 선택된 노드의 인접 노드
			int next = graph[current][i].second;
			// 기존의 최소 비용보다 더 저렴하다면 교체.
			if (nextDistance < distances[next])
			{
				distances[next] = nextDistance;
				pq.push(make_pair(-nextDistance, next));
			}
		}
	}
}

void Solution()
{
	for (int i = 1; i <= V; i++)
		distances[i] = INF;

	Dijkstra(startIndex);
	
	for (int i = 1; i <= V; i++)
	{
		if (distances[i] != INF)
			cout << distances[i] << "\n";
		else
			cout << "INF" << "\n";
	}
}


void Input()
{
	cin >> V >> E;

	cin >> startIndex;

	for (int i = 0; i < E; ++i)
	{
		int from;
		int to;
		int distance;
		cin >> from >> to >> distance;
		graph[from].push_back(make_pair(distance, to));
	}
}

int main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	Input();
	Solution();

	return 0;
}
300x250

'<C++ 백준 BOJ> > DP' 카테고리의 다른 글

[백준 BOJ1916] 최소비용 구하기 (C++)  (0) 2023.02.18
300x250

코루틴은 은근히 쓰레기 메모리(가비지)를 자주 생성해요. 코루틴을 자주 사용하는 로직에서 이를 최적화하지 않는다면 GC의 collect에서 프레임이 상당히 떨어질 수 있어요..

코루틴에서 가비지가 생성되는 주요한 부분으로는 크게 두 가지가 있어요..

 

- StartCoroutine

- YieldInstruction

StartCoroutine  메서드를 호출할 때 유니티가 해당 코루틴을 관리하기 위해 엔진내부에서 인스턴스가 생성되며 이때 이것이 가비지가 된다고 해요. StartCoroutine은 유니티 엔진 내부의 코드이기 때문에 이를 최적화하는 것은 쉽지 않기 때문에 코루틴 기능을 직접 제작하거나 비슷한 기능을 제공하는 에셋 More Effective Coroutine을 사용해야 한다고 해요.

 

웬만하면 최소한으로 StartCoroutine을 사용해야 할 것 같아요.

 

그렇다면 저희가 최적화를 건드릴 수 있는 부분은 YieldInstruction가 남아있어요. YieldInstruction 이거는 아래와 같은 종류들이 있어요.

 

여기서 yield구문 자체는 가비지를 생성하지 않지만 new가 붙는 yield return new WaitForSeconds(float) 같은 것들이 가비지가 생성이 되기 때문에 캐싱을 해주는 게 좋아요.

 

using System.Collections;
using UnityEngine;

public class Test2Main : MonoBehaviour
{
    private void Start()
    {
        StartCoroutine(this.WaitForSecondsTestImpl());
        StartCoroutine(this.WaitForEndOfFrameTestImpl());
        StartCoroutine(this.WaitForFixedUpdateTestImpl());
    }

    private IEnumerator WaitForSecondsTestImpl()
    {
        while (true)
        {
            yield return new WaitForSeconds(0.01f);
        }
    }

    private IEnumerator WaitForEndOfFrameTestImpl()
    {
        while (true)
        {
            yield return new WaitForEndOfFrame();
        }
    }

    private IEnumerator WaitForFixedUpdateTestImpl()
    {
        while (true)
        {
            yield return new WaitForFixedUpdate();

        }
    }
}

스샷을 보시면 프레임이 끝날 때마다 WaitForEndOfFrameTestImpl에서 가비지가 생성되고, FixedFrame이 끝날 때마다 WaitForFixedUpdateTestImpl에서 가비지가 생성되고, 지정한 시간이 지날 때마다 WaitForSecondsTestImpl에서 가비지가 생성이 되고 있는 것을 볼 수 있어요.

 

이때 캐싱을 할 때 WaitForEndOfFrame, WaitForFixedUpdate은 고정된 값이기에 한 번만 생성해 놓으면 되지만 WaitForSeconds처럼 여러 개의 Seconds가 필요할 때엔 고정된 값이 아니어서 제가 참고한 사이트에선 딕셔너리에 Seconds를 Key로 넣어놓고 값에 Seconds에 해당하는 WaitForSeconds를 넣어놓는 형식으로 캐싱을 했어요.

 

이때 기존에 있는 Key인지 검사하는 과정에서 딕셔너리에 TryGetValue를 사용할 때 박싱이 일어나는 것 때문에 IEqualityComparer를 구현해서 딕셔너리를 생성할 때 object형식으로 비교하는 게 아닌 제네릭형식으로 비교하는 것을 직접 만드셨던데

 

[Unity] Dictionary.TryGetValue() 박싱테스트 :: 별빛상자 (tistory.com)

 

제가 테스트해본 결과 제가 작업하고 있는 환경에선 TryGetValue에서 박싱이 일어나고 있지 않기 때문에 IEqualityComparer는 구현을 하지 않아도 됐어요.

using System.Collections.Generic;
using UnityEngine;

public class YieldInstructionCache
{
    class FloatComparer : IEqualityComparer<float>
    {
        bool IEqualityComparer<float>.Equals(float x, float y)
        {
            return x == y;
        }
        int IEqualityComparer<float>.GetHashCode(float obj)
        {
            return obj.GetHashCode();
        }
    }

    public static readonly WaitForEndOfFrame WaitForEndOfFrame = new WaitForEndOfFrame();
    public static readonly WaitForFixedUpdate WaitForFixedUpdate = new WaitForFixedUpdate();

    private static readonly Dictionary<float, WaitForSeconds> timeInterval = new Dictionary<float, WaitForSeconds>();
    private static readonly Dictionary<float, WaitForSecondsRealtime> timeIntervalReal = new Dictionary<float, WaitForSecondsRealtime>();


    public static WaitForSeconds WaitForSeconds(float seconds)
    {
        WaitForSeconds wfs;
        
        if (!timeInterval.TryGetValue(seconds, out wfs))
            timeInterval.Add(seconds, wfs = new WaitForSeconds(seconds));

        return wfs;
    }

    public static WaitForSecondsRealtime WaitForSecondsRealTime(float seconds)
    {
        WaitForSecondsRealtime wfsReal;

        if (!timeIntervalReal.TryGetValue(seconds, out wfsReal))
            timeIntervalReal.Add(seconds, wfsReal = new WaitForSecondsRealtime(seconds));

        return wfsReal;
    }
}

 

이 부분이 전 필요 없었어요. 혹시라도 나중에 버전이 달라졌을 때 박싱이 발생한다면 IEqualityComparer를 사용해야 할 것 같네요.

 

using System.Collections;
using UnityEngine;

public class Test2Main : MonoBehaviour
{
    private void Start()
    {
        StartCoroutine(this.WaitForSecondsTestImpl());
        StartCoroutine(this.WaitForEndOfFrameTestImpl());
        StartCoroutine(this.WaitForFixedUpdateTestImpl());
    }


    private IEnumerator WaitForSecondsTestImpl()
    {
        while (true)
        {
            yield return YieldInstructionCache.WaitForSeconds(0.1f);
        }
    }

    private IEnumerator WaitForEndOfFrameTestImpl()
    {
        while (true)
        {
            yield return YieldInstructionCache.WaitForEndOfFrame;
        }
    }

    private IEnumerator WaitForFixedUpdateTestImpl()
    {
        while (true)
        {
            yield return YieldInstructionCache.WaitForFixedUpdate;

        }
    }

}

더 이상 가비지가 생성이 안되고 있는 모습을 볼 수 있어요.

 

저는 유니티 사용할 때 코루틴을 엄청 많이 사용했었는데 지금까지 이런 걸 하나도 고려 안 한 상태에서 했었어요. 많이 반성하게 되네요! 

 

저는 유니티 버전 2021.3.5f1, .NET Standard 2.1, Visual Studio 2019 환경에서 테스트했어요.

 

 

참고자료 :

유니티 코루틴 최적화 (ejonghyuck.github.io)

그때는 맞고 지금은 틀리다 - 제네릭 컬렉션도 박싱이 발생할 수 있다 | Overworks’ lab in GitHub

C# Dictionary ContainsKey와 TryGetValue 뭐가 더 효율적인가? (tistory.com)

[Unity] yield return 종류 — KKIMSSI (tistory.com)

 

300x250
300x250

이테스트를 한 이유는 코루틴 최적화를 알아보던 도중 WaitForSeconds(seconds)를 캐싱하려면 딕셔너리에 seconds를 넣어두고 기존에 사용한 적이 있으면 캐싱한 WaitForSeconds를 반환하고 사용한 적이 없으면 딕셔너리에 해당 seconds를 넣어서 캐싱하는 기법을 사용한다고 해요.

 

그때는 맞고 지금은 틀리다 - 제네릭 컬렉션도 박싱이 발생할 수 있다 | Overworks’ lab in GitHub

 

이때 사용한 적이 있는지를 확인하기 위해 딕셔너리에 TryGetValue를 사용하는 과정 중 박싱이 발생할 수 있다고 하여 IEqualityComparer 인터페이스를 새로 구현하여 딕셔너리에 비교 연산자로 넣어줘야 한다고 해서 object 형식이 아닌 비교하려는 자료형을 선언하여 박싱이 일어나지 않게 해야 한다고 하는데 이 부분이 이해가 가지 않아서 테스트를 해봤어요.

 

 

C# 도큐먼트를 봤을 때는 object가 아닌 제네릭형식 TKey를 받아서 하고 있기 때문에 박싱이 일어나지 않을까 생각했어요.

 

제네릭 컬렉션도 방식이 발생할 수 있다 글에서 테스트한 프로파일링 결과예요 해당결과를 보면

DefaultComparer를 사용할 때 가비지 컬렉션이 발생하는것을 알 수 있어요.

 

저도 동일한 코드를 작성해서 테스트 해봤어요.

using System.Collections.Generic;
using UnityEngine;

public class StructTest : MonoBehaviour
{
    public struct Struct
    {
        public int value;

        public Struct(int value)
        {
            this.value = value;
        }
    }

    private Dictionary<Struct, string> structDic = new Dictionary<Struct, string>();

    private void Start()
    {
        for (int i = 0; i < 5; ++i)
        {
            structDic.Add(new Struct(i), i.ToString());
        }
    }


    void Update()
    {
        string str;
        for (int i = 0; i < 5; ++i)
        {
            structDic.TryGetValue(new Struct(i), out str);
        }
    }
}

 

using System.Collections.Generic;
using UnityEngine;

public class IntTest : MonoBehaviour
{
    private Dictionary<int, string> intDic = new Dictionary<int, string>();

    void Start()
    {
        for (int i = 0; i < 5; ++i)
        {
            intDic.Add(i, i.ToString());
        }
    }

    void Update()
    {
        string str;
        for (int i = 0; i < 5; ++i)
        {
            intDic.TryGetValue(i, out str);
        }
    }
}

 

using System.Collections.Generic;
using UnityEngine;

public class EnumTest : MonoBehaviour
{
    public enum Enums
    {
        Enums0,
        Enums1,
        Enums2,
        Enums3,
        Enums4,
    }

    Dictionary<Enums, string> enumDic = new Dictionary<Enums, string>();

    void Start()
    {
        for (int i = 0; i < 5; ++i)
        {
            enumDic.Add((Enums)i, i.ToString());
        }
    }

    void Update()
    {
        string str;
        for (int i = 0; i < 5; ++i)
        {
            enumDic.TryGetValue((Enums)i, out str);
        }
    }
}

 

저 또한 가비지가 생기고 있으나 Enum에선 생기지 않고 있는 것을 볼 수 있고 DefaultComparer는 확인할 수 없었어요. 다만 이때 생기는 가비지 또한 박싱때문에 생긴 다기보단 

비교를 할 때 new 키워드를 사용해서 인스턴스를 새로 만들고 있어서 가비지가 생기는 게 아닌가란 생각이 들었어요. 아마 위에 테스트 당시보다 유니티 버전이 오르고 C# 버전도 올라서 그런 게 아닐까 조심스럽게 예상해 봐요.

이제 코루틴 최적화도 테스트해 볼 건데 제 예상엔 IEqualityComparer는 구현 안 해도 캐싱하는데 문제가 없지 않을까 미리 예상해 봐요.

 

저는 유니티 버전 2021.3.5f1, .NET Standard 2.1, Visual Studio 2019 환경에서 테스트했어요.

 

참고자료:

그때는 맞고 지금은 틀리다 - 제네릭 컬렉션도 박싱이 발생할 수 있다 | Overworks’ lab in GitHub

 

 

300x250
300x250