TIL

[멋쟁이사자처럼 부트캠프 TIL회고] 46일차 : 휴식

Cadi 2025. 1. 5. 15:19

일요일은 운동과 공부에서 살짝 떨어져서 쉬는 날 !

코딩테스트 : 공 던지기

머쓱이는 친구들과 동그랗게 서서 공 던지기 게임을 하고 있습니다. 공은 1번부터 던지며 오른쪽으로 한 명을 건너뛰고 그다음 사람에게만 던질 수 있습니다. 친구들의 번호가 들어있는 정수 배열 numbers와 정수 K가 주어질 때, k번째로 공을 던지는 사람의 번호는 무엇인지 return 하도록 solution 함수를 완성해보세요.

using System;
using System.Linq;

public class Solution {
    public int solution(int[] numbers, int k) 
    {
        int answer = 0;
        int[] evenLength;
        int[] doublenumbers = numbers;
        if (numbers.Length % 2 == 0)
        {
            evenLength = numbers;
        }
        else  
        {
            evenLength = numbers.Concat(doublenumbers).ToArray();
        }

        while (evenLength.Length / 2 < k)
        {
            k -= evenLength.Length / 2;
        }
        answer = evenLength[(2*k - 2)];
        return answer;
    }
}


금방 풀긴 했으나, 다른 사람의 풀이를 보니 Linq를 사용해서 두 줄짜리로 푸신 것을 보고 다시 생각했다.

 

결국 내 방식도, 주어진 numbers의 index로 k를 조작해서 넣는 것이다.

그럴 것이라면, 굳이 짝수 배열과 홀수 배열일 때를 달리해서 할 필요가 없던 것이었다. 

 

배열이 쭉 반복된다면, k번 던졌을 때 공을 받는 사람은 2k 번째이니 2k-2번째 사람을 구해야 한다. 

그러나 배열이 한정되어 있으므로, 2k-2번째 라는 숫자를 배열의 길이로 나눠서 남은 숫자가

제한된 배열에서의 인덱스가 되고 , 답을 구할 수 있는 문제였다. 

 

나름 경우의 수를 생각한다고 홀수와 짝수를 나눴었는데, 조금 더 잘 고민했다면 코드를 간결하게 짤 수 있었을 것이다.

 

코딩테스트 : 팩토리얼

팩토리얼 (i!)은 1부터 i까지 정수의 곱을 의미합니다. 예를들어 5! = 5 * 4 * 3 * 2 * 1 = 120 입니다. 정수 n이 주어질 때 다음 조건을 만족하는 가장 큰 정수 i를 return 하도록 solution 함수를 완성해주세요.

i! ≤ n

using System;

public class Solution
{
    public int solution(int n)
    {
        int answer = 0;
        if (n == 0) return 0;
        for (int i = 1; i <= n; i++)
        {
            n = n / i;
            answer++;
        }
        
        return answer;
    }
}

 

if(n==0) return 0은 필요 없는 코드다. 

 

실행을 했을 때에 0 예외처리를 안해서 실패하는줄 알고 했던 흔적.

 

처음에는 반복문의 조건을 int i =1; i < n; i++ 로 했었다.

그러나, 4가 남았을 때 4로 나눠줄수 있다면 나눠주어야 하기 때문에 =이 들어가야 한다.

점점..채워져 간다. 21~25일차가 좀 두렵다. 

 

일단 이렇게 두고 저녁에 할 시간 생기면 리팩토링을 좀 더 해보든, 내일 강의 코드를 좀 보든 해야겠다.

 

 

리팩토링 조금

 

'데이터 직렬화'라는 것을 함 

 

[System.Serializable]
public class Sound
{
  public string Name;
  public AudioClip Clip;
}

 

 

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

public class SoundManager : Singleton<SoundManager>
{
 [SerializeField]
 private Sound[] BGMSounds, SFXSounds;
 public AudioSource BGMAudioSource, SFXAudioSource;


 void Start()
 {
   PlayBGM("MainBGM");
 }

 public void PlayBGM(string name)
 {
  Sound s = Array.Find(BGMSounds, sound=>sound.Name == name);

  if (s == null)
  {
   Debug.LogWarning("Sound Not Found: " + name);
   return;
  }
  else
  {
   BGMAudioSource.clip = s.Clip;
   BGMAudioSource.Play();
  }
 }
 
 public void PlaySFX(string name)
 {
  Sound s = Array.Find(SFXSounds,sound=>sound.Name == name);

  if (s == null)
  {
   Debug.Log("Sound Not Found: " + name);
   return;
  }
  else
  {
   SFXAudioSource.clip = s.Clip;
   SFXAudioSource.Play();
  }
 }
 
 
}

다음과 같은 방식으로 어디서든 호출할 수 있게하며, 모든 사운드를 등록해 두어서 원할때 SondManager를 호출해서 사용!

처음에는 Main씬에만 만들어서 테스트 할 때에 다른 씬에서는 어떤 방식으로 테스트 해야 하지.. ? 했지만

생각해보니 싱글톤 방식으로 만들어서 호출하는 시점에 그곳에서도 생성된다. ㅎ... 바보다 바보.

 

확실히 AudioSource를 하나하나 달고 클립을 일일히 넣어주는 것보다 훨씬 편한 방법이고 고치기도 쉬운 방법인 것 같다. 

이런 방식을 사용하면 모든 사운드를 한 번에 조절 가능하다..! 

 

원래는 버튼매니저를 따로 만들어보려 하였으나, 지금 당장 버튼은 Scene이동의 기능을 많이 담당하기에 

일단 SceneManager를 만들어서 Scene을 이동하는 것을 관리하고, 그 다음 버튼에 기능을 달아주어야겠다. 

 

 

 

라고 생각했으나 생각해보니 LoadScene은 이미 어디서나 접근 가능한 함수였고, 단순히 씬 이동만을 위해서

따로 클래스를 만드는 것은 적합하지 않은 것 같았다.

(나중에 이동 효과를 줄 때에는 몰라도)

그래서 버튼 매니저를 만드는 방식을 고안해 보았는데 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class StageButtonManager : Singleton<StageButtonManager>
{
 private GameObject obj;
 private String objName;

 void Awake()
 {
   objName = obj.name;
 }
 
 public void LoadStage()
 {
  SceneManager.LoadScene(objName);
  SoundManager.Instance.PlaySFX("Button");
 }
 
 
}

 

딱 봐도 이상하지 않은가 ? awake가 일어나지 않으면 제대로 자기 자신의 objName을 저장하지 않아서 문제가 발생하고

애초에 자기 자신의 이름을 쓸 거라면 굳이 저런 방식 말고도 this.name으로 구할수 있다. 

 

목표를 제대로 설정하지 않고 되는대로 만들어서 생기는 문제였다.

우리의 목표는 다음과 같다. 

  1. 버튼마다 다른 스크립트를 만드는 것을 지양
    : 스크립트가 우후죽순 생겨나고, 관리가 힘들기 때문이다. 
  2. 버튼을 생성할 때마다 스크립트를 계속 달아주어야 한다거나, 
    버튼을 일일히 인스펙터 창에 할당하는 일이 생기지 않게 하기 .

 

두 가지 방법이 생각났다. 

 

첫 번째 방법은 구조체나 클래스 형식으로 string name과 button button을 갖고 있는 것을 만들고

그 객체들에 하나하나 name에 따라 LoadScene을 하는 기능을 추가해 주는 것이다.

이 방법을 쓰면 버튼을 할당하기만 하면 알아서 기능이 추가된다.

다만 아직 GetComponentsInChildren으로 모두 가져올 수 있는지 미지수, 안된다면 직접 손으로 할당해 주어야 함

 

두 번째 방법은 버튼을 만들 때마다 스크립트를 추가하긴 하지만 버튼 이름만 바꿔주면  자동으로 AddListner를 하게 해주는 것이다. 

 

두 방법 모두 버튼의 이름을 바꿔주어야 하기 때문에 비슷한 노동력이 드는 것 같다. 

최적화 측면을 알아보기 위해 두 가지 방식을 모두 사용해보고 질문해볼 예정이다.

 

1번 방법

public class StageButtonManager : MonoBehaviour
{
    private class StageButton
    {
        public Button button;
        public String name;
    }
    
    private StageButton[] stageButtons;

    void Awake()
    {
        stageButtons = GetComponentsInChildren<StageButton>();
    }

    void Start()
    {
        foreach (var stageButton in stageButtons)
        {
            stageButton.button.onClick.AddListener(()=>SceneManager.LoadScene(stageButton.name));
        }
    }
        
    
}

이었으나 짜다보니 StageButton이라는 컴포넌트는 없다.

Button을 갖고 와서 그 버튼을 소유하고 있는 GameObject의 이름으로 접근해야 한다.

 

 

작동도 잘 되는 모습.

public class StageButtonManager : MonoBehaviour
{

    private Button[] stageButtons;

    void Awake()
    {
        stageButtons = GetComponentsInChildren<Button>();
    }

    void Start()
    {
        foreach (var stageButton in stageButtons)
        {
            stageButton.onClick.AddListener(()=>SceneManager.LoadScene(stageButton.name));
        }
    }
}

2번 방법 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class StageButtonManager : MonoBehaviour
{
 private Button button;
 void Start()
 {
   button = GetComponent<Button>();
   button.onClick.AddListener(() => SceneManager.LoadScene(this.gameObject.name));
 }
 
}

2번도 작동이 잘 된다. 

 

 

어쩌피 복사해서 이름만 바꿔주는 것은 똑같으므로 굳이 이 둘 사이의 효율성을 따질 필요는 없을 듯하다. 

 

성능을 비교해야 하는데 2번 코드는 버튼마다 컴포넌트로 계속해서 추가되므로 1번 코드가 성능상 조금이나마 우위다.

 

 

다만 1번 코드도 수정해야 할 부분이 있다. 

 

public class StageButtonManager : MonoBehaviour
{

    public Button[] stageButtons;

    void Awake()
    {
        stageButtons = GetComponentsInChildren<Button>();
    }

    void Start()
    {
        foreach (var stageButton in stageButtons)
        {
            string stageButtonName = stageButton.name;
            stageButton.onClick.AddListener(() => LoadScene(stageButtonName));
        }
    }

    void LoadScene(string sceneName)
    {
        SceneManager.LoadScene(sceneName);
        SoundManager.Instance.PlaySFX("Button");
    }

}

클로저 문제가 발생할 수 있다고 해 다음과 같이 수정 완료 !!

 

그런데 사운드가 나오지 않는 문제가 발생했다.

문제의 원인을 찾다 저번처럼 씬이 진행된 후 AudioSource가 남아있지 않았다.

(BGM이 들리지 않았는데도 한참 찾았다)

알고보니 사용한 싱글톤에서 DontDestroyOnLoad를 하지 않았던 문제였다.

볼륨 조절까지 하려 하였으나 시간상 실패... 클로저 개념도 알아보고 싶다.

 

오늘은 여기까지