[멋쟁이사자처럼 부트캠프 TIL회고] Unity 게임 개발 3기 17일차
Linq
개념
LINQ(Language Integrated Query)
- 쿼리를 갖고 데이터를 가공하는 방식ㄷ
- 검색 기능이라고 생각하면 됨
- 데이터 소스에 대한 통일된 쿼리 구문
- 컬렉션, 배열, XML등 다양한 데이터 소스 지원
- 강력한 필터링,정렬,그룹화 기능
- 코드의 가독성과 유지보수성 향상
람다를 같이쓰는 경우가 많기 때문에 람다를 알아둘 것 ! (주말공부)
예제
LINQ가 없는 경우와 있는 경우를 둘 다 보면서 편리성을 알아보자.
우리는 몬스터 객체를 여러개 만들고, 이름과 체력을 할당할 것이다.
이름 "A", 체력은 30 이상인 객체들을 오름차순으로 정렬하고 싶다면 지금까지 배운 코드로는 다음과 같다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public struct MonsterTest
{
public string name;
public int health;
}
public class LinqExample : MonoBehaviour
{
public List<MonsterTest> monsters = new List<MonsterTest>()
{
new MonsterTest() { name = "A", health = 100 },
new MonsterTest() { name = "A", health = 30 },
new MonsterTest() { name = "B", health = 100 },
new MonsterTest() { name = "B", health = 30 },
new MonsterTest() { name = "C", health = 100 },
new MonsterTest() { name = "C", health = 30 },
};
*몬스터 생성
몬스터를 생성하고, 이를 정렬하기 전에 우선 이름은 A이고 체력은 30이상인 객체들을 뽑아 배열을 만들어준다.
List<MonsterTest> list1 = new List<MonsterTest>();
//몬스터 테스트 그룹에서 A네임을 가진 hp 30이상의 오브젝트들을 리스트화해서 출력하기.
for (var i = 0; i < monseters.Count; i++)
{
if (monseters[i].name == "A" && monseters[i].health >= 30)
{
list1.Add(monseters[i]);
}
}
list1.Sort((l,r) => l.health >= r.health ? -1 :1);
그리고 Sort 함수를 통해 차례대로 정렬해준다.
* list1.Sort((l,r) => l.health >= r.health ? -1 : 1); 는 Sort 함수로 정렬하는 방법이다.
Sort 함수는 문자열을 기본적으로 오름차순으로 정렬하지만, 복잡한 객체를 정렬할 때에는 원하는 기준을 직접 만들어야 한다. 여기서는 기준을 직접 만들어야 하므로, l.health와 r.health를 비교하여 l.health가 r.health보다 크면 -1, 작으면 1을
반환한다. -1을 반환하면 그대로 위치가 있고, 1을 반환하면 위치가 바뀌어 결국 내림차순으로 정렬된다.
이제 LINQ를 통해 쉽게 정렬해본다.
var linquFilter = monsters.Where( e => e is {name: "A", health: >=30 }).
OrderByDescending( e => e.health).ToList();
var linqFilter2 = (from e in monsters
where e is { health: >= 30, name: "A"}
orderby e.health
descending
select new { e.name, e.health })
.ToList();
위의 방법과 아래 방법 모두 가능하다.
*LINQ에서 쓸 수 있는 기능들 알아보기 ( where, ascending, group, orderby 등등)
간단한 경마 게임 만들기
코루틴
- Unity상에서 작업을 다수의 프레임에 분리하는 기능
- 시간 제어, 조건 제어 등에서 활용
- 많이 사용하면 성능에 부하가 올 수 있음
IEnumerator 반환 타입과 yield 반환문으로 선언하는 함수(메서드)
- yield return null : 실행이 일시정지되고 다음 프레임에 실행
- Waitforsecond(); : 입력한 시간동안 시간이 일시정지 // 다양한 기능에 사용, Update에 사용하면 부하가 걸릴수도 있는 기능 등
- StopCorutine/StopAllCorutine : 코루틴/모든코루틴을 작동 정지(SetActive = false;로도 가능)
코루틴을 이용한 배틀타이머 만들기
1초에 한 번씩 시간초가 줄어드는 타이머를 만들 것이다. Update 함수에 넣는다면(이 기능은 아니지만) 부하가 걸릴만한 함수가 있을때 코루틴을 이용한다.
우선 기본 배틀타이머(시간)을 30초로 잡는다.
그리고 시간이 <0이면 안되므로 조건문을 설정한다.
시간이>일때, 현재 시간을 출력하고, 1초를 쉬는 동시에 타이머를 1초 줄여주는 함수를 만들고,
스타트 함수에 넣어준다.
public class GameManager : MonoBehaviour
{
public float battleTime = 30.0f;
IEnumerator BattlerTimer()
{
while (battleTime >= 0.0f)
{
Debug.Log(battleTime);
// 이 함수는 1초동안 쉰다.
yield return new WaitForSeconds(1.0f);
// 어떠한 값이 참이 될때가지 기다리는 YieldInstruction
// yield return new WaitUntil();
// 물리 적용이 끝난 시점까지 기다리는 코루틴
// yield return new FixedUpdate();
battleTime -= 1.0f;
}
}
// Start is called before the first frame update
void Start()
{
// 코루틴 함수를 시작한다.
StartCoroutine(BattlerTimer());
}
}
https://docs.unity3d.com/kr/2022.3/Manual/Coroutines.html
코루틴 - Unity 매뉴얼
코루틴을 사용하면 작업을 다수의 프레임에 분산할 수 있습니다. Unity에서 코루틴은 실행을 일시 정지하고 제어를 Unity에 반환하지만 중단한 부분에서 다음 프레임을 계속할 수 있는 메서드입니
docs.unity3d.com
경마 게임 만들기
우선 UI를 만들어준다. 우리는 Button 위에 말의 이름(Player name) 과 순위가 뜨게 할 것이다.
캔버스 위에, 버튼의 범위를 설정하기 위한 패널과, 버튼을 만든다.
패널에서 Vertical Layout Group을 설정해 정렬해준다.
Button의 text를 자가수정하게 하기 위해 RaceButton이라는 스크립트를 만들어 버튼의 컴포넌트로 추가한다.
그리고 프리팹화 시켜서 저장한다.
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
public class RaceButton : MonoBehaviour
{
public TMP_Text text;
}
*버튼의 text를 수정하기 위한 스크립트 추가
먼저 선언할 변수들을 정리해보자
1. 경마에 참여할 플레이어들의 리스트
2. 버튼들을 관리할 패널을 할당할 변수
3. 새로 생성하게될 버튼들을 넣을 리스트
4. 프리팹화시킨 버튼을 표현하기 위한 변수
// 경마에 참여할 플레이어 리스트
public List<PlayerData> Players = new List<PlayerData>();
// ui에 표현 될 버튼 프리팹
public RaceButton templateButton;
// 버튼들이 붙을 부모오브젝트
public Transform RaceButtonParent;
// 생성된 버튼들 관리
private List<RaceButton> raceButtons = new List<RaceButton>();
이제 새 오브젝트(버튼)을 생성하고 패널위치(RaceButtonParent)에 생성한다
그리고
for ( var i = 0; var < player.Count; i++)
{
var newObj = Instantiate(tempeateButton.gameObject, RaceButtonParent);
raceButtons.Add(newObj.GetComponent<RaceButton>());
}
혹은 이런식으로도 할 수있다.
for ( var i = 0; var < player.Count; i++)
{
var newObj = instantiate(templetButton, RaceButtonParent).GetComponent<RaceButton>();
raceButton.Add(newObj);
}
1. 프리팹화 한 버튼을 templetButton에 저장하고, RaceButtonParent(패널을 부모로) 생성해준다.
그리고 raceButtons라는 리스트에 새 버튼의 RaceButton 컴포넌트를 리스트화 해서 관리한다.
2. 프리팹화 한 버튼을 패널을 부모로 생성하고 RaceButton 컴포넌트를 가져와 newObj에 저장한다.
raceButton 리스트에 추가한다.
둘다 똑같은 결과를 가져온다. 1번과 2번의 차이는 순서 차이다.
이제 버튼들의 생성이 끝났으니, 1초마다 플레이어들의 위치를 바꿔준다.
while 문 안에 넣어주면 된다.
foreach ( var player in Players)
{
player.distance += Random.Range(0f,1f);
}
코루틴 안에 1초 동안 쉬는 함수가 계속 돌아가고 있으므로, 각 플레이어마다 1초에 한 번씩 0~1 위치값이 증가한다.
이제 각 플레이어의 랭킹을 오늘 배운 린큐를 이용해서 정의해본다.
var ranks = (from p in Players
orderby p.Distance descending
select p).ToList();
for (var i = 0; i < ranks.Length; i++)
{
Debug.Log($"Rank {i+1} : {rank[i].playerNamew} / distance : { ranks[i].Distance");
receButtons[i].text.text = ranks[i].playerName;
}
린큐를 사용해서 플레이어들의 리스트를 거리순(내림차순)으로 정렬해주고 ranks 라는 리스트에 할당한다.
그리고 랭크와 거리가 나오는 로그를 출력하고
각각 버튼의 이름들을 playerName으로 바꿔준다.
추가로 동적이게 하는 움직임을 할 수 있다. (추가 과제) 이건 주말에라도 도전해보자...
후기 : 딱 속도가 적당하다. 전공자분들은 좀 느리다고 느낄 수도 있겠지만 딱 수업 끝나고 2~3 시간 하면 정리도 되고, 과제까진 못하더라도 이해를 어느정도는 할 수 있다.
주말에 공부할 것들을 메모장에 계속 써 놓고, 찾아서 공부해야겠다.
참고 dictionary 키워드
https://learn.microsoft.com/ko-kr/dotnet/api/system.collections.generic.dictionary-2?view=net-8.0
Dictionary<TKey,TValue> 클래스 (System.Collections.Generic)
키 및 값의 컬렉션을 나타냅니다.
learn.microsoft.com