동구의_C# & Unity_개발일지
2024.02.01 내일배움캠프 29일차 TIL_Unity (숙련, 알고리즘) 본문
오늘은 미루어 왔던 입문 주차 때 지급되었던 강의를 완강하였다.
UI을 만들었고 물약과 사운드 등을 넣어보았다.
중간중간 어려운 부분이 많았지만 천천히 복습해가며 학습해갈 예정이다!
추후에 사용에 숙달해져 시간이 남으면 추가 구현을 더 해보고 싶다.
알고리즘 코드카타 15일차
정수 제곱근 판
문제 설명
임의의 양의 정수 n에 대해, n이 어떤 양의 정수 x의 제곱인지 아닌지 판단하려 합니다.
n이 양의 정수 x의 제곱이라면 x+1의 제곱을 리턴하고, n이 양의 정수 x의 제곱이 아니라면 -1을 리턴하는 함수를 완성하세요.
2D 게임 심화 개발 / 2D 게임 로직 및 상태 관리
사운드 컨트롤
UI 만들기
로직 구현하기
스텟 계산하기
아이템
로직 강화하기
01. 핵심 내용
1-1) 사운드를 처리하는 주요 컴포넌트
AudioClip:
- AudioClip은 사운드 파일을 Unity에서 사용할 수 있도록 하는 데이터 타입입니다.
- .wav, .mp3, .ogg 등 다양한 형식의 오디오 파일을 지원합니다.
AudioSource:
- AudioSource 컴포넌트는 사운드를 재생하는 데 사용됩니다.
- AudioSource에 AudioClip을 연결하여 재생할 수 있습니다.
- AudioSource는 3D 사운드 설정, 볼륨 조절, 사운드 반복 재생 등의 설정을 제공합니다.
AudioListener:
- AudioListener 컴포넌트는 사운드를 듣는 포인트를 나타냅니다.
- 일반적으로 주요 카메라에 AudioListener가 위치합니다.
- 게임에는 하나의 AudioListener만 있어야 합니다.
02. 핵심 내용
- UGUI (Unity's User Interface):
- Unity의 기본 UI 시스템으로 게임 내의 사용자 인터페이스를 구축하는 데 사용됩니다.
- UGUI에서 모든 UI 요소는 Canvas라는 컴포넌트 내에 배치됩니다.
- Canvas는 스크린 공간, 월드 공간, 카메라 공간의 3가지 렌더 모드를 지원합니다.
- Unity의 기본 Transform 대신 UI 요소에는 Rect Transform이 사용됩니다.
- 위치, 크기, 회전, 스케일을 지정하는데 사용되며, 앵커 및 피벗을 사용하여 부모와의 상대적인 위치를 지정합니다.
- UGUI는 다양한 UI 요소들을 제공합니다: 버튼, 이미지, 텍스트, 슬라이더, 스크롤 바 등.
- UGUI의 이벤트 시스템은 UI 상호작용을 관리합니다.
- 마우스 클릭, 드래그, 키보드 입력 등 다양한 입력 이벤트를 처리합니다.
2-1) TextMeshPro
- TextMeshPro는 원래 독립적인 개발자에 의해 Unity 에셋 스토어에서 판매되었습니다. 그러나 이후 Unity Technologies에 인수되어 Unity의 기본 기능으로 포함되었습니다. 이러한 변화는 2017년에 발생했으며, 그 이후로 TextMeshPro는 Unity 사용자들에게 무료로 제공되고 있습니다.
- TextMeshPro는 Unity에서 제공하는 고급 텍스트 렌더링 시스템입니다. 기본 텍스트 구성 요소보다 훨씬 더 많은 기능과 정확성을 제공합니다.
03. 핵심 내용
3-1) 코루틴(Coroutine)
- 코루틴은 비동기적으로 실행되는 함수로, 특정 코드 블럭의 실행을 일시적으로 중지하고 다시 시작할 수 있게 해줍니다.
- IEnumerator 리턴 타입의 함수에서 yield return을 사용하여 코루틴을 구현할 수 있습니다.
- StartCoroutine 함수를 통해 코루틴을 시작할 수 있고, StopCoroutine 함수를 통해 코루틴을 중지할 수 있습니다.
- 코루틴은 프레임 간의 지연, 비동기 작업, 시간에 따른 애니메이션 등의 작업에 주로 사용됩니다.
- yield return null은 다음 프레임까지 대기를 의미하고, yield return new WaitForSeconds(n)은 n초 동안 대기를 의미합니다.
- 코루틴은 별도의 스레드에서 실행되지 않습니다. 따라서 Unity의 메인 스레드에서 안전하게 Unity API를 호출할 수 있습니다.
- 코루틴은 일반 함수와는 다르게, 실행을 일시 중단하고 나중에 다시 시작할 수 있어, 시간 지연, 반복, 조건부 대기 등의 작업을 수행할 때 매우 유용합니다.
04. 핵심 내용
4-1) switch문 & 패턴 매칭
- Switch문: 프로그래밍에서 조건에 따라 다른 동작을 수행하도록 하는 제어 구조. 다양한 경우의 수를 가진 조건을 처리할 때 효율적.
- 패턴 매칭: C# 7.0부터 추가된 기능으로, 일치하는 패턴에 따라 코드를 실행. switch문을 이용하여 객체의 타입이나 값에 따라 처리를 다르게 할 수 있다.
- 패턴 매칭 in switch문 : 코드의 가독성을 높이고 유지 보수를 쉽게 하는데 도움을 준다. 객체의 타입을 확인하거나 특정 조건을 충족하는지 여부를 확인하면서 동시에 변수에 값을 할당할 수 있다.
- 예제:
- case RangedAttackData _:: CurrentStats.attackSO가 RangedAttackData 타입일 경우에 실행.
- case MeleeAttackConfig _:: CurrentStats.attackSO가 MeleeAttackConfig 타입일 경우에 실행.
- 이런 방식을 이용하면, 여러 타입의 객체를 적절하게 처리할 수 있어 확장성과 유연성이 향상된다.
중요한 기능들을 정리해서 알아보자!
스텟 구현 코드
더보기
-------------------------- 생략 --------------------------
using System.Linq;
public class CharacterStatsHandler : MonoBehaviour
{
private const float MinAttackDelay = 0.03f;
private const float MinAttackPower = 0.5f;
private const float MinAttackSize = 0.4f;
private const float MinAttackSpeed = .1f;
private const float MinSpeed = 0.8f;
private const int MinMaxHealth = 5;
-------------------------- 생략 --------------------------
public void AddStatModifier(CharacterStats statModifier)
{
statsModifiers.Add(statModifier);
UpdateCharacterStats();
}
public void RemoveStatModifier(CharacterStats statModifier)
{
statsModifiers.Remove(statModifier);
UpdateCharacterStats();
}
private void UpdateCharacterStats()
{
AttackSO attackSO = null;
if (baseStats.attackSO != null)
{
attackSO = Instantiate(baseStats.attackSO);
}
CurrentStats = new CharacterStats { attackSO = attackSO };
// TODO
CurrentStats.statsChangeType = baseStats.statsChangeType;
CurrentStats.maxHealth = baseStats.maxHealth;
CurrentStats.speed = baseStats.speed;
UpdateStats((a, b) => b, baseStats);
if (CurrentStats.attackSO != null)
{
CurrentStats.attackSO.target = baseStats.attackSO.target;
}
foreach (CharacterStats modifier in statsModifiers.OrderBy(o => o.statsChangeType))
{
if (modifier.statsChangeType == StatsChangeType.Override)
{
UpdateStats((o, o1) => o1, modifier);
}
else if (modifier.statsChangeType == StatsChangeType.Add)
{
UpdateStats((o, o1) => o + o1, modifier);
}
else if (modifier.statsChangeType == StatsChangeType.Multiple)
{
UpdateStats((o, o1) => o * o1, modifier);
}
}
LimitAllStats();
}
private void UpdateStats(Func<float, float, float> operation, CharacterStats newModifier)
{
CurrentStats.maxHealth = (int)operation(CurrentStats.maxHealth, newModifier.maxHealth);
CurrentStats.speed = operation(CurrentStats.speed, newModifier.speed);
if (CurrentStats.attackSO== null || newModifier.attackSO == null)
return;
UpdateAttackStats(operation, CurrentStats.attackSO, newModifier.attackSO);
if (CurrentStats.attackSO.GetType() != newModifier.attackSO.GetType())
{
return;
}
switch (CurrentStats.attackSO)
{
case RangedAttackData _:
ApplyRangedStats(operation, newModifier);
break;
}
}
private void UpdateAttackStats(Func<float, float, float> operation, AttackSO currentAttack, AttackSO newAttack)
{
if (currentAttack == null || newAttack == null)
{
return;
}
currentAttack.delay = operation(currentAttack.delay, newAttack.delay);
currentAttack.power = operation(currentAttack.power, newAttack.power);
currentAttack.size = operation(currentAttack.size, newAttack.size);
currentAttack.speed = operation(currentAttack.speed, newAttack.speed);
}
private void ApplyRangedStats(Func<float, float, float> operation, CharacterStats newModifier)
{
RangedAttackData currentRangedAttacks = (RangedAttackData)CurrentStats.attackSO;
if (!(newModifier.attackSO is RangedAttackData))
{
return;
}
RangedAttackData rangedAttacksModifier = (RangedAttackData)newModifier.attackSO;
currentRangedAttacks.multipleProjectilesAngel =
operation(currentRangedAttacks.multipleProjectilesAngel, rangedAttacksModifier.multipleProjectilesAngel);
currentRangedAttacks.spread = operation(currentRangedAttacks.spread, rangedAttacksModifier.spread);
currentRangedAttacks.duration = operation(currentRangedAttacks.duration, rangedAttacksModifier.duration);
currentRangedAttacks.numberofProjectilesPerShot = Mathf.CeilToInt(operation(currentRangedAttacks.numberofProjectilesPerShot,
rangedAttacksModifier.numberofProjectilesPerShot));
currentRangedAttacks.projectileColor = UpdateColor(operation, currentRangedAttacks.projectileColor, rangedAttacksModifier.projectileColor);
}
private Color UpdateColor(Func<float, float, float> operation, Color currentColor, Color newColor)
{
return new Color(
operation(currentColor.r, newColor.r),
operation(currentColor.g, newColor.g),
operation(currentColor.b, newColor.b),
operation(currentColor.a, newColor.a));
}
private void LimitStats(ref float stat, float minVal)
{
stat = Mathf.Max(stat, minVal);
}
private void LimitAllStats()
{
if (CurrentStats == null || CurrentStats.attackSO == null)
{
return;
}
LimitStats(ref CurrentStats.attackSO.delay, MinAttackDelay);
LimitStats(ref CurrentStats.attackSO.power, MinAttackPower);
LimitStats(ref CurrentStats.attackSO.size, MinAttackSize);
LimitStats(ref CurrentStats.attackSO.speed, MinAttackSpeed);
LimitStats(ref CurrentStats.speed, MinSpeed);
CurrentStats.maxHealth = Mathf.Max(CurrentStats.maxHealth, MinMaxHealth);
}
}
캐릭터의 스텟을 계산할 수 있게 하는 코드이다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class PickupItem : MonoBehaviour
{
[SerializeField] private bool destroyOnPickup = true;
[SerializeField] private LayerMask canBePickupBy;
[SerializeField] private AudioClip pickupSound;
private void OnTriggerEnter2D(Collider2D other)
{
if (canBePickupBy.value == (canBePickupBy.value | (1 << other.gameObject.layer)))
{
OnPickedUp(other.gameObject);
if (pickupSound)
SoundManager.PlayClip(pickupSound);
if (destroyOnPickup)
{
Destroy(gameObject);
}
}
}
protected abstract void OnPickedUp(GameObject receiver);
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PickupStatModifiers : PickupItem
{
[SerializeField] private List<CharacterStats> statsModifier;
protected override void OnPickedUp(GameObject receiver)
{
CharacterStatsHandler statsHandler = receiver.GetComponent<CharacterStatsHandler>();
foreach (CharacterStats stat in statsModifier)
{
statsHandler.AddStatModifier(stat);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PickupHeal : PickupItem
{
[SerializeField] int healValue = 10;
private HealthSystem _healthSystem;
protected override void OnPickedUp(GameObject receiver)
{
_healthSystem = receiver.GetComponent<HealthSystem>();
_healthSystem.ChangeHealth(healValue);
}
}
if(currentWaveIndex % 5 ==0)
{
CreateReward();
}
--------------- 생략 ---------------
void CreateReward()
{
int idx = Random.Range(0, rewards.Count);
int posIdx = Random.Range(0, spawnPostions.Count);
GameObject obj = rewards[idx];
Instantiate(obj, spawnPostions[posIdx].position, Quaternion.identity);
}
아이템을 생성하고 먹을 수 있는 코드이다.
피를 채울 수 있고 화살이 강화되며 이동속도가 증가하는 아이템 물약을 추가할 수 있는 코드이다.
게임 개발 입문 과정
TopDownShooting 완성!
'Unity' 카테고리의 다른 글
2024.02.06 내일배움캠프 31일차 TIL_Unity (숙련, 알고리즘) (0) | 2024.02.06 |
---|---|
2024.02.05 내일배움캠프 30일차 TIL_Unity (숙련, 알고리즘) (0) | 2024.02.05 |
2024.01.30 내일배움캠프 27일차 TIL_Unity (입문, 알고리즘) (0) | 2024.01.30 |
2024.01.29 내일배움캠프 26일차 TIL_Unity (입문, 알고리즘) (0) | 2024.01.29 |
2024.01.26 내일배움캠프 25일차 TIL_Unity (입문, 알고리즘) (0) | 2024.01.26 |