02/02 본캠프 #29

guno park·2024년 2월 2일
0

본캠프

목록 보기
29/77

알고리즘

귤 고르기

풀이

예~~~전에 비슷한 풀이가 하나 있었는데 기억하고 응용했다.
배열을 오름차순으로 정렬해 가장 큰 값을 찾고, 그 값+1을 크기로 하는 배열을 만든다.
그리고 귤들을 크기별로 배열에 정리해준다.
이 문제를 푸는데 중요한 건 다른 종류의 수를 최소화 하는 것이지, 크기가 큰 것부터 파는게 아니라는 점이다.
고로 정리된 귤들을 큰 순서대로 정렬하고, 앞에서부터 차례대로 k에서 빼준다. K가 0보다 작아질때까지 빼주면서 그 횟수를 더하면 최소화된 서로 다른 종류의 수가 나온다.

using System;
using System.Linq;
using System.Collections.Generic;

public class Solution {
    public int solution(int k, int[] tangerine) {
         int answer = 0;
        tangerine = tangerine.OrderBy(x=>x).ToArray();
        List<int> arr = new List<int>(new int[tangerine[tangerine.Length-1]+1]);
        foreach(int num in tangerine){
            arr[num]++;
        }
        arr = arr.OrderByDescending(x=>x).ToList();
        int a = 0;
        while(k>0){
            k-=arr[a];
            a++;
            //arr.RemoveAt(0);
            answer++;
        }
        return answer;
    }
}

3D 입문 강의 정리

스태미나 사용하기

스태미나를 사용하기 위해 실제 공격하는 메서드를 변경해준다.
매개변수로 PlayerConditions를 받아와서 공격할때마다 스태미나를 사용하는 메서드를 호출해주고,
그 메서드는 bool값을 반환하기때문에 false가 나오면 공격하지 못하게 바꾼다.

 public override void OnAttackInput(PlayerConditions conditions)
  {
      if (!attacking)
      {
          if (conditions.UseStamina(useStamina)) //스태미나가 있을때만 공격할 수 있게
          {
              attacking = true;
              _animator.SetTrigger("Attack");
              Invoke("OnCanAttack", attackRate);
          }
      }
  }

적 생성과 로직

Nav Mesh를 베이킹할 때 나무나 돌같은 오브젝트가 방해가 되어 AI들이 이동을 못 할 수 있다.
그럴 때는 방해되는 오브젝트에 Navmesh Obstacle 컴포넌트를 넣어주면 이동영역에서 제외된다.

하지만 방해 오브젝트가 움직이는 오브젝트라면, 이동 시 영역 제외 처리에 연산력을 많이 사용하기 때문에 움직이지 않는 오브젝트에만 넣어주는 편이 좋다.

Finite State Machine (FSM) - 유한 상태 머신

코드 안에서 동작들을 구분하기 시작하면 FSM이 됨

유한상태머신 준비하기

동작들을 계속해서 구분해주어야 하며, 그 동작에 필요한 데이터들도 가지고 있어야하고,
동작별 함수들도 필요하기 때문에 준비과정도 매우 긴 편이다.

  1. enum으로 현재 상태 정해주기
    현재 이 머신의 상태를 enum으로 분류해준다.
  2. 상태별 동작에 필요한 값들을 헤더로 작성하기 쉽게 분리해 작성해준다.
  3. 이 스크립트에 필요한 컴포넌트들을 준비한다.
    예를 들어 NavMeshAgnet, Animator, MeshRenderer 등등이다.

유한상태머신 구성하기

  1. 유한상태머신을 시작할 때는 초기 상태를 Start에서 정해주는 것부터 시작한다.
    상태를 변경하는 SetState라는 함수를 하나 만들고 시작한다고 생각해두자.

  2. update에서는 머신의 현재 상태에 따라 진행할 동작들을 정해둔 함수들을 실행시켜준다.

 private void Update()
    {        
        switch (_state)
        {
            case AIState.Idle:
                PassiveUpdate();
                break;
            case AIState.Wandering:
                PassiveUpdate();
                break;
            case AIState.Attacking:
                AttackingUpdate();
                break;
            case AIState.Fleeing:
                FleeingUpdate();
                break;
        }
    }
  1. 머신이 행동불능이 되지 않는 이상, 머신은 계속해서 이 상태들을 주고 받아야한다.
    끊기는 일이 생기면 안된다는 말이다.

공격 가능한 각도 계산하기

 private bool IsPlayerInFieldOfView()
    {
        Vector3 directionToPlayer = PlayerController.instance.transform.position 
        - transform.position;
        float angle = Vector3.Angle(transform.forward, directionToPlayer); 
        //내 정면과 플레이어 방향의 각도가 몇도냐.
        return angle < fieldOfView * 0.5f; 
        //정면이 0도라 사용할 때는 반을 나눠서 사용. fieldOfView는 전역변수
    }

플레이어와 이 오브젝트 간의 방향을 계산해서 오브젝트의 정면을 기준으로 각도를 계산한다.

포스트 프로세싱

포스트 프로세싱? 화면에 비춰지는 것에 후처리 연산을 더 하는 것

사용방법

패키지매니저에서 포스트 프로세싱 다운 받고 메인 카메라에 포스트프로세스 레이어랑 볼륨 넣어주면됨.

기본효과들

기본적으로 있는 효과들 중 쓸만한 것 몇가지만 추려봤다.

안티 에일리어싱 (Anti-Aliasing)

에일리어싱은 경계선 주변에 나타나는 계단 현상을 완화하는 기술로, 이미지의 부드럽고 깨끗한 외관을 제공합니다. Unity에서는 다양한 안티 에일리어싱 옵션을 제공합니다.

블룸 (Bloom)

블룸은 밝은 영역 주변에 흰색 휘도 효과를 추가하여 빛이 반사되거나 퍼져 나가는 것처럼 보이게 하는 효과입니다. 주로 높은 명암비를 강조하기 위해 사용됩니다.

도플러 (Dof) 포커싱

깊이 플랜을 기반으로 하여 초점을 맞추는 효과로, 전경이나 배경 중 하나에 초점을 맞출 수 있습니다. 주로 씬의 특정 부분을 강조하거나 시선을 유도하는 데 사용됩니다.

모션 블러 (Motion Blur)

물체나 카메라의 움직임에 따른 블러 효과를 추가하여 동적인 움직임의 부드러운 표현을 가능하게 합니다. 빠르게 움직이는 물체나 카메라의 움직임을 자연스럽게 보여줄 수 있습니다.

글로우 (Glow)

밝은 물체 주변에 광택이나 빛이 퍼져 나가는 효과를 제공하여 해당 물체를 강조합니다. 주로 빛나는 물체나 특별한 물체에 적용됩니다.

발걸음 소리와 뮤직 존 생성

audioSource.volume = 
Mathf.MoveTowards(audioSource.volume, _targetVolume, (maxVolume / fadeTime) * Time.deltaTime); //볼륨을 천천히 늘이거나 줄임.

MoveTowards를 Transform 이런 곳이 아니라 볼륨같은데도 적용할 수 있다.

개인과제 1 : ATM 만들기

회원가입 기능 만들기

json 파싱부터 => json말고 PlayerPrefs 사용하라고 되어있음.
근데 json으로 해보자.

전체적으로 인포 텍스트를 관리해주는 매니저를 만들어서 하이어라키창에서 켜져있을 경우 갱신해주는 방식? 아니면 꺼져있어도 갱신.
=> 데이터가 변경될 때 모든 텍스트를 갱신하는 것으로 결정

데이터 처리하기

위에서부터 차례대로 입력받은 데이터의 사용가능 여부를 결정하는 코드를 작성한다.
사용이 불가능 할 시 오류 팝업을 띄우고, 팝업창이 종료 될 때 코루틴으로 2초동안 오류 텍스트를 띄우고 그 후에는 다시 빈칸으로 만든다.

코루틴 사용방법 주의점

코루틴을 사용하는 방법은 크게 두 가지로 나뉜다.

  1. 문자열 시작
StartCoroutine("WrongWarning", wrongcase);
  1. 함수명 시작
StartCoroutine(WrongWarning(wrongcase));

이 두가지의 차이점은 코루틴을 종료하는 방법에 있다.
1번의 경우 간단하게 StopCoroutine("WrongWarning")이라고 작성하면 되지만,
2번의 경우 StopCoroutine(WrongWarning())이라고 작성해도 방금 시작한 그 코루틴이 멈추는 게 아니다.
2번을 멈추게 하는 방법은 참조해서 사용하는 것이다.

private Coroutine routine;

private void Start(){
routine = WrongWarning(wrongcase);
}

StartCoroutine(routine);
StopCoroutine(routine);

이런식으로 작성해야 똑같은 코루틴을 멈출 수 있다.
꽤나 번거로운 작업이여서 보통 1번, 문자열로 시작하는 방법을 많이 쓰는데,
이 방법도 단점이 있는데 바로 디버깅이 어렵다는 점이다.
문자열로 시작할 경우, 작성한 함수의 이름이 틀려도 에러가 발생하지 않는다.
그 말인 즉슨 안되는 이유를 찾기 어렵다는 뜻이다.

그래서 이런 단점을 보완한 방식이 바로

StartCoroutine(nameof(WrongWarning),Wrongcase);        

이렇게 nameof를 사용하는 것이다. 시작, 정지도 간편하고 컴파일하는데도 지장이 없다.

앞으로 사용한다면 애용하도록 하자.

정규식으로 문자 판별하기

문자열을 판별하는 정규식이라는게 있다고 한다.
using System.Text.RegularExpressions;를 사용하는데 진짜 정규식이다.
이번에 사용한 방법은 입력된 글자가 영어 혹은 숫자인지 판단하는 식이다.

 private bool check(string Text)
    {
        return Regex.IsMatch(Text, @"^[a-zA-Z0-9\s,]+$");             
    }

문자열이 조건에 부합할 경우 true, 아니라면 false를 반환한다.
앞으로도 이번 프로젝트처럼 데이터를 걸러받아야 한다면 정규식으로 체크를 해서 넘겨주는 편이 좋다.
서버나 클라이언트에 들어가면 문제가 되는 코드들을 미리 걸러줄 수 있다.

Json 활용하기

유니티에서 사용하는 Json은 몇가지가 있다고 한다.
그 중 내가 앞으로 사용할 두 가지는 NewtonSoft.Json과 JsonUtility다
앞의 Json은 지난 과정에서도 사용해보았던 그 Json이고
JsonUitlity는 유니티에서 제공하는 json으로 일반 Json의 경우 유니티에만 있는 요소(Vector3, transform 등등)를 직렬화 하지 못하는데 이 친구는 가능하다.
하지만 다 직렬화 할 수 있다보니 용량이 커진다.
고로 이 또한 필요한 곳에만 적절히 사용해야 할 것이다.

Json 설치하기

Json 설치를 두 곳에 해줬다. 하나는 IDE, 나머지는 Unity인데 나는 Rider를 사용하고 있어서 그 기준으로 설명을 하자면, 설정 - 플러그인 - Json 검색해서 사용하고 있다.

Unity의 경우는 인터넷을 찾아보고 설치했다.
유니티에 Json 설치하는 방법

경로 설정하기

Json을 설치했다는 건 어디로든 데이터를 저장하겠다는 의미로 저장해줄 경로를 설정해주어야한다.
이 경로의 경우 이번 과제에서 고정 경로를 넣었지만, 원래는 고정경로를 넣는 것이 아닌
Application.persistentDataPath를 사용한다고 한다.
더 자세한 정보는 나중에 쓸 수 있도록 여기에 남기도록 한다.
유니티 Data Path

그리고 내 생각보다 경로를 많이 사용하기에 이곳 저곳에 경로를 다 남길게 아니라,
싱글턴화 된 스크립트에 Static으로 경로를 만들어 가져다 쓰는 방식으로 변경했다.

public static string savePath =
@"F:\Sparta\Github\Bak_s_homework\Sparta_Bank\Assets\SaveData\";

이렇게 작성하고 파일을 생성할 때는 savePath += "파일이름.json"으로 해주었다.

데이터 직렬화 하기

데이터를 직렬화 하는 방법도 두 가지가 있다.

  1. JsonConverter 사용하기
var saveNewName = 
(ID : $"{iD}", Name : $"{name}",Password : $"{ps}" ,  balance : 50000, Cash : 100000);
JsonConvert.SerializeObject(saveNewName);

여기서는 이렇게 사용했지만 원래는 클래스 전체를 직렬화 시킨다.
json데이터를 주로 사용하는 곳이 서버랑 통신할 때라서 클래스로 만들어서 관리하면 편하다.

하지만 클래스 전체를 직렬화 하기에 필요없는 데이터가 섞일 수도 있는데, 그런 부분들을 고려해 플레이어 클래스를 만든다고 하면
플레이어 데이터 클래스를 먼저 만들고 플레이어가 데이터 클래스를 가져오는 방식으로 작성해주면 데이터 클래스를 저장하는 것으로 가볍게 정리할 수 있다.
혹은 저장할 때 필요한 정보를 구조체로 만들어 직렬화 할 수도 있다.

  1. Jobject, JToken 사용하기
JObject SaveData = new JObject(
            new JProperty("ID", id),
            new JProperty("Name", name),
            new JProperty("Password", ps),
            new JProperty("Balance", 50000),
            new JProperty("Cash", 100000)
        ); 

이 방식은 내가 필요한 데이터만 골라서 직렬화 할 수 있다. 필요한 데이터가 적다거나 하면 사용하기 좋은 방법인 듯 하다.

  1. 저장하기
 File.WriteAllText(savePath + $"{SaveData["ID"]}.json", SaveData.ToString());

File.은 System.IO를 네임스페이스에 호출해서 쓸 수 있다.

  1. 불러오기
private StreamReader savefile;
using (savefile = File.OpenText(savePath + $"{_ID.text}.json"))
        {
            JsonTextReader _jsonTextReader = new JsonTextReader(savefile);
            JObject datas = (JObject)JToken.ReadFrom(_jsonTextReader);
            Password = datas["Password"].ToString();
            if (Password != _login_enc._password)
            {
                WarningText = "비밀번호가 틀렸습니다.";
                Warning();
                return;
            }
            setData(datas);
        };

Jobject방식으로 불러올 때는 StreamReader로 파일을 열고, jsonTextReader를 통해 JToken을 JObject로 변환 해준 후에 데이터를 꺼내 올 수 있다.
주의할 점은 StreamReader로 파일을 열고 블레이즈로 닫지 않으면 파일이 계속 열려있는 상태가 되기 때문에 그 파일을 수정할 수 없다.
고로 using을 사용해 안전하게 닫아주도록 하자.

비밀번호 별표만들기

별표로 만드는건 따로 스크립트가 필요할 듯 하다.
비밀번호 별표는 스트링빌더를 사용해볼까
입력되는 비밀번호는 따로 저장하고, 다른 부분들을 띄워줘야되지 않을까.
공통되는 부분은 입력 받은 값을 즉시 별표로 바꿔줘야한다는것
할 수 있는 방법은 두 가지, 업데이트에서 계속 감시하다가 이전 값과 달라지면 바꿔주는 함수를 호출하거나,
입력이 감지되면 바꿔주는 함수를 호출하거나
짜기 쉬운건 업데이트인데 하고 싶은건 입력감지.

=> 입력을 감지해서 만들어보기로 결정

의외로 간단하게 리스너를 사용해 구성할 수 있었다.

 _PS_inputfield.onValueChanged.AddListener(Onencryption);

트러블 슈팅

어떻게 리스너로 구성은 좀 해봤는데 계속 비밀번호가 별표가 됬었다.
그래서 원인을 곰곰히 생각해보자니 아무리 생각해도 안 떠올라서 그냥 함수를 두개 사용하기로 했다.
변경 전의 패스워드를 저장해주는 함수를 리스너에 먼저 등록하고, 그 후에 표시되는 비밀번호가 변경되도록 달아주었다.

 private void Start()
    {
        _PS_inputfield.onValueChanged.AddListener(SavePassword);
        _PS_inputfield.onValueChanged.AddListener(Onencryption);
    }

별표로 바꾸기

별표로 바꾸는 건 스트링 빌더가 제일 좋을 것 같아서 그대로 사용했다.

private void changePs(string password, TMP_InputField field)
    {
        int length = password.Length;
        StringBuilder passwordsb = new StringBuilder();
        for (int i = 0; i < length; i++)
        {
            passwordsb.Append("*");
        }

        field.text = passwordsb.ToString();
    }

몇자가 될 지도 모르고, 입력하는걸 기다리기도 애매한 듯하여 이렇게 작성했다.

비밀번호 값 저장하기

  public void SavePassword(string password)
    {
        if (password == "")
        {
            _password = "";
            return;
        }
        
        if (Input.GetKeyDown(KeyCode.Backspace))
        {
            _password = _password.Substring(0,_password.Length-1);
            return;
        }
            
        if (password.Length > _password.Length)
        {
            _password += password[password.Length-1];
        }
    }

코드는 짧지만 구상하는데 꽤 오랜 시간이 걸렸다.
글자를 지우는 경우도 생각해야 했고, _password = password라고 작성해버리면 비밀번호가 다 별표가 되버린다.
그래서 차례대로 한번에 지우는 경우, 한칸씩 지우는 경우(리스너로 키코드를 감지함),
그리고 글이 늘어나는 경우다. 특히 글이 늘어나는 경우 한 글자 입력할때마다 리스너가 동작하기 때문에 사용하기에 적절하다는 생각이 들었다.
입력된 password의 마지막 문자를 저장하는 방법이다.

반전

인풋필드에 별표로 바꿔주는 기능이 있다. 인풋필드에 타입이있다.


나는 사서 고생하는 타입이다. 하지만 결과물도 만족스럽고 공부도 되서 아무 생각도 안든다.

UI 로직 만들기

이번 개인 과제의 경우 비슷한 동작을 하는 버튼들이 많아서 버튼매니저라는 스크립트를 만들어서 총괄하도록 해주었다.
하지만 일반적으로 UI들을 지금처럼 버튼들을 모아서 만드는 것이 아니라
큰 패널단위로 UI를 관리해야 할 가능성이 높다. 한 UI에 데이터가 얼마나 들어갈지도 모르기 때문에 패널단위로 스크립트에서 관리하는 편이 접근성, 가독성 둘 다 좋을 것 같다.

  • 버튼에 함수 다는 건에 대하여
    명명규칙을 잘 지키자.
    ex) 버튼 클릭 이벤트라고 하면 OnClickXXXX, 닫는거면 CloseXXXX 이런식으로 해놓으면 O 나 C에 가서 찾으면 빨리 찾을 수 있다.

재로그인 만들기

원래 목표에는 없는 기능인데, 짜다보니 쉬울 것 같아서 만들어보게 되었다.
그냥 뒤로 로그인 화면으로 돌아가는 기능이다.
하던중에 발생한 문제를 적어보자면, A아이디로 로그인 했다가 B로 로그인하려하면 A의 비밀번호가 그대로 남아있어 비밀번호 오류가 발생한다는 점이였다.
그래서 입력한 비밀번호를 로그인하는 함수가 종료될 즈음에 초기화 해주도록 함수를 만들어서 넣어줬더니 잘 돌아간다.

코딩 팁

  • 코드를 작성할 때 "이런 함수 만들어야지" 하면서 이름만 적어두면 빨간 줄로 에러가 발생한다. 이 때 Alt + Enter 눌러서 메서드 생성 누르면 바로 반환값까지 고려해서 만들어진다.

  • Ctrl + - 누르면 이전에 커서 있던곳으로 이동한다. 또 누르면 또 간다.

  • 코딩 컨벤션 좀 지키면서 하자
    클래스, 함수 - PascalCase 사용 (e.g., MyClass, MyMethod).
    변수 - camelCase 사용 (e.g., myVariable, myParameter).

튜플

  1. public (string, string, string, int, int) targetData;
    일반적으로 아는 튜플과 다르다. 그나마 최근에 추가된 내장 튜플로 기존 튜플과 차이점이 있다.
    그 이름도 값튜플로 값타입이니까 전역변수로 사용하기보다 지역 내에서 잠깐 사용하는고 사라지는게 좋다. 사용목적상으로.

  2. public Tuple<string, string, string, int, int> Data;
    일반적인 튜플로 이 친구는 힙에서 동작한다. 이번 과제의 경우 나는 데이터 저장용으로 튜플을 계속해서 사용했으니까 이걸로 접근하는 편이 메모리 효율상 좋다.

문자열 비교하기

문자열 비교하는 방법 중에 ==이 있고 CompareTo라는 메서드가 있다.
그냥 ==로 비교하는 것보다 Password.text.CompareTo(PSconfirm.text)
로 비교하는 편이 조금 더 효율적이긴 하나 가독성이 떨어진다.

캐싱

자주 사용되는 것들이면 아예 참조하고 시작하는게 낫고,
고정된 값이면 스태틱으로 선언해주는 것도 좋다.
스태틱은 메모리 자체를 정적 메모리로 바꾼거다.

변수인데 상수로 바꿨다는 의미는 그 값을 바꾸지 않겠다.
static const도 가능하다. 이 경우에는 그냥 readonly와 같다. 사용목적상으로

if (_agent.CalculatePath(PlayerController.instance.transform.position,path))
==================================================================
PlayerController.instance.GetComponent<IDamagable>().TakePhysicalDamage(damage); 

이렇게 하면 안된다. 자주 사용하는 참조들은 꼭 캐싱해서 사용할 것.

요즘에는 GetComponent가 최적화가 많이 되어있다고 한다. 그래서 써도 되긴 하지만
캐싱하는 버릇을 들이기 위해서라도 이렇게 작성하는 것은 좋지 않음.

캐싱이 귀찮지만 최적화에 대한 이해를 나타내는 척도로 작용한다.
캐싱하지 않고 쓴다는 것 자체가 전문가의 입장에서 버릇이 잘못들엇다는 느낌을 줄 수 있다.
결론 : 캐싱하는 버릇을 들이자.

  • 거리를 잴 때는 바라보고 싶은 애를 빼야됨.
    Vector3 directionToPlayer = PlayerController.instance.transform.position - transform.position;

  • 시간 이렇게 재는게 좋을 듯 하니 기억해두자.

if (Time.time - _lastFootStepTime > footsteopThreshold)
{
	_lastFootStepTime = Time.time;                    
}

Time.time으로 현재 시간 기록해주고 사용하는 방법이다.

  • if (other.CompareTag("Player"))
    태그는 그냥 같냐고 비교하는 것보다 메서드를 이용하는 것이 성능적으로 더 좋다.

0개의 댓글