코딩테스트 : 가까운 수
정수 배열 array와 정수 n이 매개변수로 주어질 때, array에 들어있는 정수 중 n과 가장 가까운 수를 return 하도록 solution 함수를 완성해주세요.
가장 먼저 생각나는 방법은, 1씩 더해주어서 +로 더해준 쪽과 -로 더해준 쪽을 비교하는 것이다.
번갈아가면서 비교하는 것도 같은 방식이지만 조금 더 한눈에 보이는 방식인 것 같다.
using System;
using System.Linq;
public class Solution
{
public int solution(int[] array, int n)
{
int answer = 0;
if (array.Contains(n))
{
return n;
}
int Index = 0;
for (int i = 0; i < array.Length; i++)
{
Index++;
if (array.Contains(n + Index))
{
return n + Index;
}
else if (array.Contains(n - Index))
{
return n - Index;
}
}
return answer;
}
}
그런데 짜다보니 너무 비효율적이고 작동도 하지 않는다.
일단 배열을 정렬하고, 더하고 뺄 필요가 없이 절대값으로 찾은 후 가장 작은 값을 찾으면 되지 않을까.. ?
using System;
using System.Linq;
public class Solution
{
public int solution(int[] array, int n)
{
Array.Sort(array);
var answer = array.OrderBy(x=>Math.Abs(x-n)).ThenBy(x =>x).First();
return answer;
}
}
린큐를 아직 잘 하지 못해서 도움을 좀 받았다.
린큐도 익숙해질때까지 최대한 사용해보려고 해야겠다.
데이터를 골라내는 상황이나 정렬하는 상황에서 의식적으로 사용해야지.
코딩테스트 : 영어가 싫어요
영어가 싫은 머쓱이는 영어로 표기되어있는 숫자를 수로 바꾸려고 합니다. 문자열 numbers가 매개변수로 주어질 때, numbers를 정수로 바꿔 return 하도록 solution 함수를 완성해 주세요.
using System;
public class Solution {
public long solution(string numbers)
{
long answer = 0;
string[] numbersArray =
new string[10]{"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"};
numbers = numbers.Replace("zero", "0");
numbers =numbers.Replace("one", "1");
numbers = numbers.Replace("two", "2");
numbers = numbers.Replace("three", "3");
numbers = numbers.Replace("four", "4");
numbers = numbers.Replace("five", "5");
numbers = numbers.Replace("six", "6");
numbers = numbers.Replace("seven", "7");
numbers = numbers.Replace("eight", "8");
numbers = numbers.Replace("nine", "9");
answer = long.Parse(numbers);
return answer;
}
}
탭 노가다로 해결했만 반복문으로 묶어주면 될 것 같다.
코딩테스트 : 한 번 등장한 문자
문자열 s가 매개변수로 주어집니다. s에서 한 번만 등장하는 문자를 사전 순으로 정렬한 문자열을 return 하도록 solution 함수를 완성해보세요. 한 번만 등장하는 문자가 없을 경우 빈 문자열을 return 합니다.
public class Solution {
public string solution(string s) {
char[] a = s.ToArray();
Array.Sort(a);
return new string(a.GroupBy(x => x).Where(x => x.Count() == 1).Select(x => x.Key).ToArray());
}
}
린큐를 써 보겠다고 한참 걸렸다. GroupBy라는 방식을 몰라서 찾았다.
아직도 익숙하지 않아서 계속 써 봐야 할 것 같다.
다른 사람의 풀이를 보다가 Linq를 쓰지 않는 기발한 방식이 있어서 소개한다.
바로 split 을 'a'부터 'z'까지 각각 해 보고, 만일 그 길이가 2라면 ( a가 한 번만 들어있다면)
추가하는 방법이다. split한 길이를 사용할 생각은 못했어서 신기했다.
for(char ch = 'a'; ch <= 'z'; ++ch)
그리고 이렇게 해도 작동되는 것을 처음 알았다.
코딩테스트 : 7의 개수
머쓱이는 행운의 숫자 7을 가장 좋아합니다. 정수 배열 array가 매개변수로 주어질 때, 7이 총 몇 개 있는지 return 하도록 solution 함수를 완성해보세요.
린큐 사용하려고 엄청 고생했다.
using System;
using System.Linq;
public class Solution {
public int solution(int[] array)
{
var combine = string.Join("", array);
return combine.Count(c => c == '7');
}
}
스킬
자식 오브젝트의 변수가 부모 오브젝트의 변수를 갱신할 수는 없나 ?
같은 베이스의 블랙보드를 사용하지만 스피드를 다르게 하고 싶었다. 인스펙터 창에서 조절이 가능하지만 그러고 싶지 않아서 찾아봤다.
1. new 키워드의 사용
: new 키워드를 사용해서 정의하면 부모 클래스를 덮어쓴느 효과가 있다.
부모클래스를 참조로 사용할 수도, 자식 클래스를 참조로 사용할 수도 있다.
2. 변수를 직접 덮어쓰는 대신 프로퍼티를 사용하여 오버라이드 기능 구현
3. 생성자 활용
4. protected 변수와 메서드를 활용
여기서 추천되는 방식은 프로퍼티 방식이다.
스킬 시스템을 구현할 것이다.
스킬 데이터는 스크립터블 오브젝트로 만들어준다. 똑같고 오염되지 않은 데이터를 참조해서 스킬을 사용하는 것이 좋은 방식이기 때문이다.
전체적인 구조를 보자면, SkillData가 있고 ( 이름, 쿨타임 등을 저장)
SkillInstance가 있고 ( 구체적인 하나의 스킬, SkillData가 저장되어 있다)
SkillInstance들을 관리할 SkillController가 있다.
SkillController에서 생성을 해서 자신의 자식 오브젝트들로 SkillInstance의 리스트를 갖고 있고,
이 SkillController를 불러서 Skill을 사용하는 방식이다.
* 굳이 오브젝트로 하지 않아도 되지만, 보이기 편하게 하기 위해 오브젝트로 만들었다.
SkillData로 Skill들을 만드는 과정은 다음과 같다.
public static class SkillFactory
{
public static async UniTask<List<SkillInstance>> CreateSkills(Transform parent ,List<string> skillNames)
{
List<SkillData> skills = new List<SkillData>();
foreach (var skillName in skillNames)
{
var handle = Addressables.LoadAssetAsync<SkillData>($"Assets/01.Scripts/SkillDatas/{skillName}.asset");
Debug.Log($"Assets/01.Scripts/SkillDatas/{skillName}.asset");
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
skills.Add(handle.Result);
}
else
{
Debug.LogError($"Failed to load skill data: {handle.Status}");
}
}
List<SkillInstance> skillInstances = new List<SkillInstance>();
foreach (var skill in skills)
{
var go = new GameObject(skill.name);
SkillInstance skillInstance = go.AddComponent<SkillInstance>();
skillInstance.skillData = skill;
go.transform.parent = parent.transform;
skillInstances.Add(skillInstance);
}
return skillInstances;
}
}
스크립터블 오브젝트를 로드해서 리스트에 넣고, 그 리스트에 있느 것을 하나씩 빼서 새로운 오브젝트에 컴포넌트로 달아준다. 그리고 skillInstance의 리스트에도 추가해준다.
public class SkillController : MonoBehaviour
{
public List<SkillInstance> skills = new List<SkillInstance>();
public List<string> skillDataNames = new List<string>();
async void Start()
{
skills = await SkillFactory.CreateSkills(this.transform, skillDataNames);
}
}
그리고 이렇게 Controller에서 만들어주면, Controller의 GameObject에 하위 오브젝트로 스킬들이 생기게 된다.
이제 각각의 SkillInstance에서는 사용 가능한 상태인지를 체크할 것이다.
우리가 고려해야 하는 사항으로는 쿨타임, 사용 가능 거리, 스킬 시전 시간이 있다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SkillInstance : MonoBehaviour
{
public SkillData skillData;
public SkillCoolTimer coolTimer;
void Start()
{
//스크립트에 붙어있는 쿨타이머 가져옴
coolTimer = GetComponent<SkillCoolTimer>();
}
//
public void FireSkill()
{
coolTimer.StartCoolTimer();
}
// 이 세개는 결국 특정한 스킬데이터와 쿨타이머에서 데이터를 가져오는 역할.
public bool CanFireSkill()
{
return coolTimer.isReady;
}
public float CanSkillEnableDistance()
{
return skillData.skillEnableDistance;
}
public float GetSkillDuration()
{
return skillData.skillDuration;
}
}
타이머를 따로 제작한 이유는 스크립트를 깔끔하게 만들기 위해서다, 밑에 붙여도 된다.
public class SkillCoolTimer : MonoBehaviour
{
public SkillData skillData;
private float remainDuration;
//0보다 작거나 같으면 isReady가 true
public bool isReady => 0 >= remainDuration;
public void StartCoolTimer()
{
CoolTimeProcess();
}
async void CoolTimeProcess()
{
remainDuration = skillData.skillDuration;
while (remainDuration > 0.0f)
{
remainDuration -= Time.deltaTime;
await UniTask.Yield();
}
}
}
스킬을 쓸 때에 스킬 데이터에서 Duration을 가져와서 remainDuration으로 설정하고 하나씩 줄이며 0이 되었을 때
isReady를 true로 바꿔준다.
* CoolTimer의 skillData를 설정해줘야한다. (인스턴스에 스크립트로 ! )
coolTimer.skillData = skillData;
강사님과는 다른 방식으로 구현하고 싶어서 시도하던 중 계속해서 오류가 발생했다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SkillState_Monster : MonoBehaviour, Cstate
{
public CBlackBoard BlackBoard { get; set; }
public CStateMachine FSM { get; set; }
public void InitState(CBlackBoard blackBoard)
{
BlackBoard = blackBoard;
}
private int skillIndex = -1;
public void Enter()
{
var blackBoardMonster = BlackBoard as Blackboard_Monster;
Debug.Log($"블랙보드 몬스터 : blackBoardMonster");
Debug.Log($"블랙보드 몬스터의 스킬컨트롤러 : {blackBoardMonster.skillController}");
skillIndex = blackBoardMonster.skillController.InorderIndex();
blackBoardMonster.skillController.FireSkillByIndex(skillIndex);
Debug.Log($"스킬인덱스 :{skillIndex} ");
}
public void UpdateState()
{
}
public void Exit()
{
}
}
일단 완성한 코드들, 오류를 고친 코드들을 먼저나열하자면
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SkillController : MonoBehaviour
{
public List<SkillInstance> skills = new List<SkillInstance>();
public List<string> skillDataNames = new List<string>();
async void Start()
{
skills = await SkillFactory.CreateSkills(this.transform, skillDataNames);
}
public int InorderIndex()
{
Debug.Log("InorderIndex");
// 명확한 것은 var 보다 타입을 명확하게 명시해 주는 것이 좋음.
for (var i = 0; i < skills.Count; i++)
{
Debug.Log($"index: {i}");
if (skills[i].CanFireSkill())
{
Debug.Log($"캔스킬파이어 {i}");
Debug.Log(skills[i].name);
return i;
}
else
{
Debug.Log("i번째 스킬이 쿨타임 중입니다");
}
}
return -1;
}
public SkillData FireSkillByIndex(int index)
{
if (index != -1)
{
SkillInstance skill = skills[index];
return skill.skillData;
}
return null;
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
public enum StaterType
{
None,
Capsule,
Monster,
Max
}
public class CStateMachine : MonoBehaviour
{
private Cstate currentState;
//불필요하게 중복된 코드
//public CStateMachine FSM;
//이거 왜쓴거임 ? 아직 진짜 모르겠음 아하 몬스터를 만드시려고 하시는구나
private Dictionary<Type,Cstate> CStates = new Dictionary<Type, Cstate>();
//private CBlackBoard blackboard;
private IReceiveInput currentReceiveInput;
public void Run(StaterType staterType)
{
// 요 부분에서 '다형성' 개념을 헷갈려서 오류가 발생했다.
Blackboard_Monster blackboard = GetComponent<Blackboard_Monster>();
Debug.Log(blackboard);
blackboard.InitBlackBoard();
List<Cstate> cstates =StateFactory.CreateStates(this, staterType);
// Cstate[] cstates = this.GetComponents<Cstate>();
foreach (var cstate in cstates)
{
AddCState(cstate, blackboard, staterType);
}
switch (staterType)
{
case StaterType.Monster:
ChangeState(typeof(IdleState_Monster));
break;
case StaterType.Capsule:
ChangeState(typeof(IdleState));
break;
}
// currentReceiveInput = currentState as IReceiveInput;
}
public void AddCState(Cstate cstate, CBlackBoard blackboard, StaterType staterType)
{
cstate.FSM = this;
switch (staterType)
{
case StaterType.Monster:
cstate.InitState(blackboard as Blackboard_Monster);
break;
case StaterType.Capsule:
cstate.InitState(blackboard);
break;
}
CStates.Add(cstate.GetType(),cstate);
}
public void ChangeState(Type stateType)
{
currentState?.Exit();
if (!CStates.TryGetValue(stateType, out currentState)) return;
// currentReceiveInput = currentState as IReceiveInput;
currentState.Enter();
}
// 이 친구는 실행할 객체에서 Update에서 호출해 줄 핢수
public void UpdateState()
{
//이 친구는 강제로 구현하게 한(인터페이스로) 각각의 상태 안에서 구현하게 할 함수
currentState?.UpdateState();
}
// public void OnTriggered(string action, bool triggered)
// {
// currentReceiveInput?.OnTriggered(action, triggered);
// }
//
// public void OnReadValue(string action, Vector2 value)
// {
// currentReceiveInput?.OnReadValue(action, value);
// }
}
'다형성' 개념 관련해서 확실히 인지하지 못하고 있어서 CBlackBoard와 BlackBoard_Monster의 차이 떄문에 발생한 문제.
BlackBoardMonster에만 SkillController가 있었고, 그렇기에 BlackBoard_Monster 타입의 블랙보드를 가져와서 할당해 주어야 했는데, CBlackBoard만 가져와서 SkillController가 Null이 되는 문제가 계속 발생했다.
일단 임시방편으로 해결해 두었고, 클래스를 하나 더 만들어서 해결할 듯 하다.
일단 자야겠다.
'TIL' 카테고리의 다른 글
[멋쟁이사자처럼 부트캠프 TIL회고] 51일차 : 스스로 공부하기 (0) | 2025.01.11 |
---|---|
[멋쟁이사자처럼 부트캠프 TIL회고] 49일차 : 디자인 패턴 (0) | 2025.01.09 |
[멋쟁이사자처럼 부트캠프 TIL회고] 47일차 : 몬스터 상태 (0) | 2025.01.07 |
[멋쟁이사자처럼 부트캠프 TIL회고] 46일차 : 휴식 (0) | 2025.01.05 |
[멋쟁이사자처럼 부트캠프 TIL회고] 45일차 : 옵저버 패턴 + 리팩토링 (1) | 2025.01.05 |