개인개발_TIL_5일차(코딩테스트 - 삼각형의 완성조건)
- 오전
- 체조
- 그림
- 오후
- 코딩테스트
- 포트폴리오 정리
- 저녁
- 포트폴리오 정리
월요일부터 수영을 시작했더니 잠을 잘 자게 되었다.
잠을 못자는 문제로 병원비를 많이 잃었는데 수영하나로 이렇게 잘 잘수 있다니...
역시 젊은시기의 모든 문제는 운동부족인 것 같다.
꾸준히 해야지
내일 수영도 기대된다.
삼각형의 완성조건
링크 : https://school.programmers.co.kr/learn/courses/30/lessons/120889?language=csharp
프로그래머스
SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프
programmers.co.kr
내가 쓴 코드
public int solution(int[] sides)
{
int maxNum = 0;
int numSum = 0;
int answer = 0;
//제일 큰 숫자 구하기
foreach (int side in sides)
{
if (maxNum < side) { maxNum = side; }
}
//나머지 숫자 합치기
foreach (int side in sides)
{
numSum += side;
}
numSum = numSum - maxNum;
//큰 변보다 두변의 합이 높으면 1 아니면 2
if (numSum > maxNum)
{
answer = 1;
}
else
{
answer = 2;
}
return answer;
}
주석에도 써있지만 다음 코드를 해석해보자면
1. 우선 큰변의 길이를 구하고
2. 모든 변들을 합하고 그걸로 작은변들의 합을 구한다.
3. 두변의 합이 더 높을 시 1를 내보내도록 한다.
천천히 이해하기위해 길게 썼다
다른 사람의 코드
public int solution(int[] sides)
{
return sides.Max() < (sides.Sum() - sides.Max()) ? 1 : 2;
}
훨씬 짧다!
1. sides.Max()로 제일 긴 변을 안아낸 뒤.
2. sides.Sum() - sides.Max() 모든 변의 합에서 큰 변의 합을 뺴 작은 변들의 합을 구한다.
3. ? 1 : 2; 맞으면 1, 틀리면 2로 내보낸다.
상속코드 정리(LivingEntity.cs분석)
예전 Zombied-19프로젝트에서 좀비와 토끼를 맡았었다
그때 당시 실력이 없었음에도 꾸역꾸역 좀비를 만들긴 했었다
그리고 토끼를 만드는데
두개가 살아 움직이는 것들이니 살아있는 것들 관련한 클래스를 부모로 하고, 좀비와 토끼가 이를 상속받으면 좋겠다는 의견이 나왔다.
그렇게 리팩토링 시작.
토끼는 살아있는것들을 상속받도록 만들어졌지만.
실력이 모자르기도했고 시간도 없어서 좀비는 그렇게 만들지 못했다.
4개월 전 코드라 지금은 뭐가 뭔지 하나도 모르겠다.
다시 정리하며 코드를 보자.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public enum AIState
{
Idle,
Wandering,
Attacking,
AttackingFence,
Escape,
Die
}
public abstract class LivingEntity : MonoBehaviour, IDamagable
{
internal LivingEntityData data;
[Header("AI")]
protected NavMeshAgent agent;
protected AIState aiState;
protected float playerDistance;
private Animator animator;
private SkinnedMeshRenderer[] meshRenderers;
private Coroutine coroutine;
private bool isDead = false;
[SerializeField] private List<ItemSO> dropOnDeath;
//테스트용
public bool isStopped;
private void Awake()
{
data = GetComponent<LivingEntityData>();
agent = GetComponent<NavMeshAgent>();
animator = GetComponent<Animator>();
meshRenderers = GetComponentsInChildren<SkinnedMeshRenderer>();
}
private void Start()
{
SetState(AIState.Wandering);
}
void Update()
{
playerDistance = Vector3.Distance(transform.position, CharacterManager.Instance.player.transform.position);
animator.SetBool("Moving", aiState != AIState.Idle);
if (isStopped) return;
switch (aiState)
{
case AIState.Idle:
PassiveUpdate();
break;
case AIState.Wandering:
PassiveUpdate();
break;
case AIState.Attacking:
AttackingUpdate();
break;
case AIState.AttackingFence:
AttackingFenceUpdate();
break;
case AIState.Escape:
EscapeUpdate();
break;
}
}
void PassiveUpdate()
{
if (aiState == AIState.Wandering && agent.remainingDistance < 0.1f)
{
SetState(AIState.Idle);
Invoke("WanderToNewLocation", Random.Range(data.minWanderWaitTime, data.maxWanderWaitTime));
}
if (playerDistance < data.detectDistance)
{
DetectPlayer();
}
}
protected abstract void DetectPlayer();// ex : SetState(AIState.Attacking)
protected virtual void AttackingUpdate() { }
protected virtual void AttackingFenceUpdate() { }
protected virtual void EscapeUpdate() { }
public void SetState(AIState state)
{
aiState = state;
Rigidbody rb = GetComponent<Rigidbody>();
rb.velocity = Vector3.zero;
switch (aiState)
{
case AIState.Idle:
agent.speed = data.walkSpeed;
agent.isStopped = true;
break;
case AIState.Wandering:
agent.speed = data.walkSpeed;
agent.isStopped = false;
break;
case AIState.Attacking:
agent.speed = data.runSpeed;
agent.isStopped = false;
break;
case AIState.AttackingFence:
agent.speed = data.runSpeed;
agent.isStopped = false;
break;
case AIState.Escape:
agent.speed = data.runSpeed;
agent.isStopped = false;
break;
case AIState.Die:
agent.speed = 0;
agent.isStopped = true;
animator.SetTrigger("Dying");
Invoke("Die", 4.0f);
break;
}
animator.SetFloat("Speed", agent.speed);
}
void WanderToNewLocation()
{
if (aiState != AIState.Idle) return;
SetState(AIState.Wandering);
agent.SetDestination(GetWanderLocation());
}
protected Vector3 GetWanderLocation()
{
NavMeshHit hit;
float wanderRagne = Random.Range(data.minWanderDistance, data.maxWanderDistance);
NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * wanderRagne), out hit, data.maxWanderDistance, NavMesh.AllAreas);
int i = 0;
while (Vector3.Distance(transform.position, hit.position) < data.detectDistance)
{
NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * Random.Range(data.minWanderDistance, data.maxWanderDistance)), out hit, data.maxWanderDistance, NavMesh.AllAreas);
i++;
if (i == 30) break;
}
return hit.position;
}
protected bool IsPlayerInFieldOfView()
{
Vector3 directionToPlayer = CharacterManager.Instance.player.transform.position - transform.position;
float angle = Vector3.Angle(transform.forward, directionToPlayer);
return angle < data.fieldOfView * 0.5f;
}
public virtual void TakeDamage(float damage)
{
data.maxHealth -= damage;
if (data.maxHealth <= 0 && !isDead)
{
isDead = true;
SetState(AIState.Die);
}
//데미지 효과
if (coroutine != null)
{
StopCoroutine(coroutine);
}
coroutine = StartCoroutine(DamageFlash());
}
IEnumerator DamageFlash()
{
for (int i = 0; i < meshRenderers.Length; i++)
{
meshRenderers[i].material.color = new Color(1.0f, 0.6f, 0.6f);
}
yield return new WaitForSeconds(0.1f);
for (int x = 0; x < meshRenderers.Length; x++)
{
meshRenderers[x].material.color = Color.white;
}
}
void Die()
{
//아이템 드롭부분
for (int i = 0; i < dropOnDeath.Count; i++)
{
Instantiate(dropOnDeath[i].dropPrefab, transform.position + Vector3.up * 2, Quaternion.identity);
}
gameObject.SetActive(false); //data.gameObject를 파괴하는 이유가..? 죽을 때 모션이후에 죽고싶으면 리지드바디나 다른 컴포넌트들을 제거해야할지도
}
void OnDisable()
{
CancelInvoke("WanderToNewLocation");
}
}
AIState enum부분
public enum AIState
{
Idle,
Wandering,
Attacking,
AttackingFence,
Escape,
Die
}
- Idle → 가만히 있는 상태 (대기 상태)
- Wandering → 주변을 돌아다니는 상태
- Attacking → 적(플레이어 등)을 공격하는 상태
- AttackingFence → 울타리를 공격하는 상태
- Escape → 도망치는 상태
- Die → 사망 상태
총 6가지 상태로 나누어져 있으며
상태 머신(Finite State Machine, FSM)을 구현하기위해 AIState를 작성해뒀었다.
public abstract class LivingEntity : MonoBehaviour, IDamagable
{
...
abstract(추상) class란?
추상 클래스는 직접 인스턴스화할 수 없고, 상속받아서 사용해야 하는 클래스 이다.
공통 기능을 정의하면서도, 특정 기능은 자식 클래스에서 다르게 구현하도록 강제할 수 있다.
IDamagable
public interface IDamagable
{
void TakeDamage(float damage);
}
인터페이스로 구현해야하는 함수를 정의해둔다.
Awake()
유니티 생명주기 중 하나로, 오브젝트가 생성될 때 실행된다.
private void Awake()
{
data = GetComponent<LivingEntityData>();
agent = GetComponent<NavMeshAgent>();
animator = GetComponent<Animator>();
meshRenderers = GetComponentsInChildren<SkinnedMeshRenderer>();
}
LivingEntityData의 컴포넌트를 가져온다.