[멋쟁이사자처럼 부트캠프 TIL회고] 46일차 : 휴식
일요일은 운동과 공부에서 살짝 떨어져서 쉬는 날 !
코딩테스트 : 공 던지기
머쓱이는 친구들과 동그랗게 서서 공 던지기 게임을 하고 있습니다. 공은 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으로 구할수 있다.
목표를 제대로 설정하지 않고 되는대로 만들어서 생기는 문제였다.
우리의 목표는 다음과 같다.
- 버튼마다 다른 스크립트를 만드는 것을 지양
: 스크립트가 우후죽순 생겨나고, 관리가 힘들기 때문이다. - 버튼을 생성할 때마다 스크립트를 계속 달아주어야 한다거나,
버튼을 일일히 인스펙터 창에 할당하는 일이 생기지 않게 하기 .
두 가지 방법이 생각났다.
첫 번째 방법은 구조체나 클래스 형식으로 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를 하지 않았던 문제였다.
볼륨 조절까지 하려 하였으나 시간상 실패... 클로저 개념도 알아보고 싶다.
오늘은 여기까지