자주 사용하는 코루틴을 미리 정의해두고 사용해보자
딕셔너리에 내가 원하는 시간을key값으로, 생성할waitForSeconds를 벨류값으로 저장
코루틴캐시 클래스 생성 -> MonoBehaviour를 상속받지 않는다
딕셔너리 생성 (키, 벨류값)
키값 : float 시간
→ 내가 찾고자 하는 시간 값이 있으면 꺼내서 사용
→ 만약 찾고자하는 키 값이 없으면 새로 값 추가 new WaitForSeconds
벨류 값 : waitForSeconds 타입
(추가하면 나중에 또 쓸때도 딕셔너리에 이미 추가되어있어서 바로 꺼내서 사용)
-> 즉, 코루틴을 재사용 할 수 있다
이전에 매번 새로 new WaitForSeconds로 메모리 할당했던 방식
-> 미리 WaitForSeconds를 정의해두고 사용했던 방식
-> 에서 더 나아간 방식
딕셔너리에 키 값과 벨류값을 저장해두고, 만약 있는 값이면 사용하고 없는 값이면 딕셔너리에 추가한다
CoroutineCache 스크립트MonoBehaviour를 상속받지 않는 클래스 생성 (일반C# 스크립트) -> 정적함수를 생성하면 클래스 이름으로 바로 접근이 가능해진다
정적타입의 딕셔너리 생성 <키, 벨류값>
Key : float 타입의 시간
Value : WaitForSeconds 타입의 객체 참조(주소값)
C#에서는 컬렉션 타입들은 참조타입이다
C++과는 다르게 C#은 new를 사용하여 동적 할당한 뒤, 변수가 '할당된 객체'를 참조하는 형식이다


WaitForSeconds 타입을 반환하는 정적 함수 생성WaitForSeconds를 반환하는 함수 생성
-> 정적 함수로 생성하여 다른 스크립트에서 사용할 수 있게 한다 (매개변수로 원하는 float 시간 값)
내가 찾고자 하는 키 값이 없을때만 딕셔너리에 추가할 것이다 -> 이미 있는 키 값이 라면 바로 반환
TryGetValueTryGetValue 메서드는 해당하는 키 값이 있다면 키 값에 해당하는 벨류값을 out 매개변수에 할당하고, true를 반환한다out 매개변수에 기본값 할당한 후 false를 반환한다ex) ✅ TryGetValue를 사용하면 키 값이 만약 있다면 대입을 진행하지 않고 참조변수 waitForSeconds에 벨류값 할당한 뒤 바로 반환


ContainsKey해당하는 키 값이 존재할 경우 true를, 없으면 false를 반환한다
dictionary.TryGetValue(time, out waitForSeconds) 로 키 값이 있다면 바로 waitForSeconds 참조변수에 벨류값이 할당되고 조건문을 시행하지 않고 반환된다**
ex) ✅ ContainsKey를 사용하면 키 값이 있든 없든 일단 대입이 한번은 꼭 되기 때문에 TryGetValue와 비교해서 상대적으로 비효율적이다
만약 찾고자 하는 키가 없다면 (false라면)
-> WaitForSeconds를 담을 waitForSeconds 참조변수를 생성한다
딕셔너리에 키 값과 벨류값을 추가한다
-> ✅ 벨류값에 new WaitForSeconds를 통해 객체를 만들고 그 객체의 참조(주소값)를 담는다
-> ✅ 실제 객체는 힙에 만들어져있고, 스택에서 이 객체의 주소값을 참조한다
참조변수에 키값에 해당하는 벨류값을 담는다
-> WaitForSeconds 타입의 벨류값을 반환한다
dictionary[key]; : 키를 사용해 벨류값에 접근 가능

C#에서 클래스는 참조타입임!! new WaitForSeconds(time)는 참조(주소값)을 의미함

CoroutineCache 스크립트using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class CoroutineCache
{
// 참조타입이라 new로 생성 (C++)
static Dictionary<float, WaitForSeconds> dictionary = new Dictionary<float, WaitForSeconds>();
// 정적 함수 생성하여 다른 클래스에서 사용
// time = 키 값
// WaitForSeconds 타입을 반환
public static WaitForSeconds WaitForSeconds(float time)
{
// 내부에는 정적인 것만 가능 (정적변수)
// 딕셔너리 키값에 해당 시간만큼의 WaitForSeconds 생성
// TryGetValue 사용하여 키 값이 없다면 추가한뒤 반환
// 키 값이 있다면 그냥 반환
// 참조변수
WaitForSeconds waitForSeconds;
if(dictionary.TryGetValue(time, out waitForSeconds) == false)
{
dictionary.Add(time, new WaitForSeconds(time));
// 추가 한 뒤에 참조해야한다 -> 추가 하기 전까지는 키벨류가 null 상태
waitForSeconds = dictionary[time];
}
return waitForSeconds;
}
}
플라이웨이트 패턴 : 객체의 중복을 줄이기 위해 공유 가능한 객체를 사용하여 메모리 소비를 최소화하는 패턴

쉐이더에 대해서는 나중에 더 알아보도록 하자
Standard Surface Shader 선택

내가 쉐이더를 적용하고 싶은 Materials에 Shader 타입을 생성한 쉐이더의 이름(Custom)으로 설정

만약 3D모델링에 textures는 없고 Materials만 적용되어 있는 경우, Materials에 쉐이더를 적용하면 텍스쳐가 다 사라져서 하얗게 보인다
-> 되도록 텍스쳐 파일도 있는 에셋을 가져오자
Surface.ShaderShader "Custom/Surface"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Curvature("Curvature", float) = 0.001
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Lambert vertex:vert addshadow
uniform sampler2D _MainTex;
uniform float _Curvature;
struct Input
{
float2 uv_MainTex;
};
void vert(inout appdata_full v)
{
float4 worldSpace = mul(unity_ObjectToWorld, v.vertex);
worldSpace.xyz -= _WorldSpaceCameraPos.xyz;
worldSpace = float4(0.0f, (worldSpace.z * worldSpace.z) * -_Curvature, 0.0f, 0.0f);
v.vertex += mul(unity_WorldToObject, worldSpace);
}
void surf(Input IN, inout SurfaceOutput o)
{
half4 c = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Mobile/Diffuse"
}
플레이 시간이 지날수록 장애물이 활성화 되는 간격이 점점 줄어들게 만들어보자
전체적인 스피드는 5초마다 2.5씩 증가
5초마다 장애물의 생성 속도가 -0.25씩 감소 (0.25만큼 더 빨리 생성)
나중에 확장을 대비해서 TimeManager는 싱글톤을 상속받는다
초기 시간 2.5 / 한계 시간 0.5
activetime 변수를 프로퍼티를 사용하여 읽기전용으로 만든다
-> 캡슐화 적용

코루틴을 사용하여 GameManger의 상태가 true이고, 초기 시간이 한계 시간보다 크다면 반복문을 실행
-> 5초마다 activetime -= 0.25f;
위에서 만들어본 CoroutineCache 스크립트의 WaitForSeconds 함수를 사용하여 반환된 waitForSeconds를 사용할 수 있다
-> **WaitForSeconds는 MonoBehaviour를 상속받지 않는 클래스의 정적 함수기 때문에 클래스 이름으로 함수를 바로 호출할 수 있다
게임씬(1)이 로드될때 마다 코루틴을 실행
-> 타이틀씬으로 갔다가 게임씬이 다시 로드될때 activetime은 초기화 되어야 하기 때문에 2.5로 다시 설정한다

씬 이벤트를 등록한다
원하는 곳에서 해당 activetime을 호출해서 사용한다
장애물이 생성되는 ObstacleManager에서 호출
-> TimeManager가 싱글톤이기 때문에 인스턴스를 통해 접근 가능

TimeManager 스크립트코루틴의 인자로 들어가는 시간을 일정 시간이 지나면서 점점 줄이기 위해 사용되는 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class TimeManager : Singleton<TimeManager>
{
[SerializeField] float activetime = 2.5f;
[SerializeField] float limittime = 0.5f;
// 읽기 전용
public float activeTime { get { return activetime; } }
private void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
IEnumerator Decrease()
{
while(GameManager.Instance.State && activeTime > limittime)
{
yield return CoroutineCache.WaitForSeconds(5.0f);
activetime -= 0.25f;
}
}
// 게임씬(1)이 활성화 되면 함수 실행
void OnSceneLoaded(Scene scene, LoadSceneMode loadSceneMode)
{
activetime = 2.5f;
if (scene.buildIndex == 1)
{
StartCoroutine(Decrease());
}
}
private void OnDisable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
}