- 속도 관리를 위한 스피드 매니저 클래스 / 관련된 스크립트들
- 캐릭터 좌우 이동 스피드 → 캐릭터 좌우 이동과 애니메이션 스피드도 올려야함
- 도로 이동 스피드
- 장애물 이동 스피드
SpeedManager모든 속도가 싱크에 맞게 움직여야하기 때문에 동일한 스피드 값을 여러 스크립트에 적용시킨다
초기 스피드 값 30
-> 코루틴을 사용하여 5초에 2.5f 씩 속도 증가
싱글톤을 사용하여 SpeedManager에 있는 스피드 값을 읽기 전용으로 가져가서 사용한다
요약 :
1.SpeedManager생성 후 전체 속도를 제어할Speed변수 생성
2.RoadManager에서SpeedManager의Speed를 가져와서 속도 변화
3.Obstacle에서SpeedManager의Speed를 가져와서 속도 변화 및 이동
SpeedManager에서 초기 스피드 값을 설정하고, 프로퍼티 만들기
코루틴을 실행할 한계 스피드 값 설정, waitForSeconds 미리 정의하기(5초)
코루틴 함수를 사용하여 5초마다 2.5f씩 스피드 값이 증가함
-> GameManager의 bool 변수가 true여야 실행된다 (false면 스피드 증가 종료)
-> 스테이지가 시작되고 5초뒤에 스피드값이 증가되야 하기 때문에 yield return 이후에 스피드 증가

타이틀 씬에서 부터 스피드 값이 증가하고 있으면 안된다 (현재 타이틀 씬에 스크립트가 부착된 상태)

OnEnable과 OnDisable에 이벤트 등록과 해제 설정


스피드 값을 사용하는 곳에서 호출해서 사용한다

Translate 이동 방법

SpeedManager 전체 코드using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SpeedManager : Singleton<SpeedManager>
{
[SerializeField] float speed = 30;
// 읽기 전용
public float Speed { get { return speed; } }
[SerializeField] float limitspeed = 60.0f;
[SerializeField] WaitForSeconds waitForSeconds = new WaitForSeconds(5.0f);
private void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
// 속도 증가 코루틴 함수
public IEnumerator Increase()
{
while(GameManager.Instance.State && speed < limitspeed)
{
yield return waitForSeconds;
speed += 2.5f;
}
}
// 씬이 호출될때마다 실행될 이벤트
void OnSceneLoaded(Scene scene, LoadSceneMode loadSceneMode)
{
speed = 30f;
if(scene.buildIndex == 1)
{
StartCoroutine(Increase());
}
}
private void OnDisable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
}
기존에 장애물의 생성 위치가 (-4, 0, 0 ), (0, 0, 0), (4, 0, 0) 이었다
이제 장애물이 도로 맨 끝에서 생성되어 앞으로 이동하고, interactzone에 닿으면 장애물이 비활성화 되게 할 것이다
ObstacleManager에 위치 정의Transform 위치값을 유니티 에디터 내에서 할당해준다(-4, 0, 150), (0, 0, 150), (4, 0, 15)
Random을 사용하여 정한다 -> 그런 뒤 활성화

Obstacle 스크립트에서 정의ObstacleManager 전체 코드using JetBrains.Annotations;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.VisualScripting;
using UnityEngine;
public class ObstacleManager : MonoBehaviour
{
[SerializeField] List <GameObject> obstacles;
[SerializeField] List<string> obstacleNames;
[SerializeField] int createCount = 5;
[SerializeField] int random;
[SerializeField] Transform [ ] transforms;
void Start()
{
obstacles.Capacity = 10;
Create();
StartCoroutine(ActiveObstacle());
}
public void Create()
{
for(int i = 0; i < createCount; i++)
{
// ResourcesManager
GameObject prefab =
ResourcesManager.Instance.
Instantiate(obstacleNames[Random.Range(0, obstacleNames.Count)], gameObject.transform);
prefab.SetActive(false);
obstacles.Add(prefab);
}
}
// 하나라도 비활성화가 있다면 false 반환
public bool Check()
{
for(int i = 0; i < obstacles.Count; i++)
{
if (obstacles[i].activeSelf == false)
{
return false;
}
}
return true;
}
IEnumerator ActiveObstacle()
{
while(GameManager.Instance.State)
{
yield return new WaitForSeconds(2.5f);
random = Random.Range(0, obstacles.Count);
// 현재 게임 오브젝트가 활성화되어 있는 지 확인합니다.
while (obstacles[random].activeSelf == true)
{
// 현재 리스트에 있는 모든 게임 오브젝트가 활성화되어 있는 지 확인합니다.
if(Check() == true)
{
// 모든 게임 오브젝트가 활성화되어 있다면 게임 오브젝트를 새로 생성한 다음
// obstacles 리스트에 넣어줍니다.
// ResourcesManager
GameObject clone =
ResourcesManager.Instance.
Instantiate(obstacleNames[Random.Range(0, obstacleNames.Count)], gameObject.transform);
clone.SetActive(false);
obstacles.Add(clone);
}
// 현재 인덱스에 있는 게임 오브젝트가 활성화되어 있으면
// random 변수의 값을 +1을 해서 다시 검색합니다.
random = (random + 1) % obstacles.Count;
}
obstacles[random].transform.position = transforms[Random.Range(0, transforms.Length)].position;
obstacles[random].SetActive(true);
}
}
}
InteractZone에 충돌 시 호출 될 함수 구현장애물이나 도로가 interactzone에 충돌하면 호출 될 내용을 인터페이스 카테고리로 묶어보자
Activate() 함수interactzone과 충돌되었을 때 실행 될 함수
기능을 완성하진 않고, 선언만 해준다
추상클래스의 추상함수와 유사하다 (abstract)
-> 부모에서 선언만 하고 자식에서 재정의 해야한다

Interactzone 스크립트에서 interactzone과 충돌되면 발생하는 함수
Obstacle 스크립트와 Road 스크립트는 IHitable 인터페이스를 상속받는다

인터페이스 → 그냥 껍데기 느낌
인터페이스 분리원칙
클래스는 하나의 부모 클래스만 상속할 수 있는데, 클래스는 인터페이스를 여러개 상속받을 수 있다 (다중 상속)
인터페이스 내부에는 함수와 프로퍼티, 이벤트, 인덱서만 작성할 수 있다 (변수 불가능)
→ 기능 완성x 선언만o
인터페이스끼리의 상속도 가능하다
ex) interface ITest2 : ITest1;
→ 원하는 스크립트에서 ITest2를 상속받으면 ITest1것도 사용가능하다
카테고리 역할로써, 부모에 함수를 정의만 해두고 자식에서 다 구현해주어야한다
위 코드에서도 충돌과 관련된 함수를 인터페이스로 정의하였고, 상속받은 자식들에게서 충돌되면 발생할 로직을 구현하였다

인터페이스를 상속받은 게임 오브젝트의 컴포넌트(스크립트)를 가져온다
-> 부모 인터페이스 x
-> 가져온 스크립트를 통해 정의해둔 인터페이스 함수를 호출
예를 들면, Obstacle 스크립트가 인터페이스 IHitable을 상속받고 Activate()함수에 게임 오브젝트 비활성화 로직을 정의해두었다면
-> 충돌된 장애물 오브젝트의 IHitable을 통해(상속받았다는 거) 해당 스크립트(Obstacle) 가져오기
-> 스크립트에 정의된 인터페이스 함수 호출 (Activate();)



만약 Human 인터페이스가 있을 경우, 매개변수로 Human타입을 받을 수 있다
인터페이스는 클래스는 아니고 그 자체로 인터페이스 타입이다
ex) 인터페이스 타입, 클래스 타입, 델리게이트 타입 등
인터페이스를 상속받은 자식들은 Human 타입이기 때문에 매개변수로 들어올 수 있다

개방폐쇄원칙(OCP) : 기존의 코드를 변경하지 않으면서, 기능을 추가할 수 있도록 설계가 되어야 한다는 원칙
개방폐쇄 원칙은 소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에는 열려 있고, 수정에는 닫혀 있어야 한다
기존 코드 변경 없이 확장 가능
-> 한번 정의해둔 인터페이스는 여러 클래스에 적용이 가능하다 (상속)
-> 기존 코드를 변경하지 않고 새로운 클래스에 인터페이스 추가 가능 (확장에 열려있다)
기존 코드 변경 없이 기능 추가 가능
-> 기존 코드를 변경하지 않고 상속받은 자식의 메서드에 기능 추가 (수정에는 닫혀있다)
다형성 활용
유지 보수 용이
결론은, 인터페이스는 함수를 정의만 해두고 기능은 구현해두지 않기 때문에, 부모의 코드가 변경될 일이없다. 자식 클래스는 부모 인터페이스에 정의된 메서드를 필요에 맞게 구현한다
터널링 현상 : 게임 오브젝트와 다른 게임 오브젝트와의 콜라이더 충돌에서 속도가 너무 빠를 경우, 서로 충돌되지 않고 뚫고 넘어가지는 현상
Collision Detection)리지드 바디 컴포넌트의 Collision Detection 프로퍼티(연속 충돌 검사)를 조정한다
Discrete : 기본값이며 CPU를 가장 적게 사용한다
매우 빠르게 움직이는 물체끼리 충돌 시 감지하지 못하는 '터널링' 발생
Continuous : CPU를 더 많이 사용한다. 대신에 터널링 방지. 정적 콜라이더(움직이지 않는 물체)에 대해서만 확인
Continuous Dynamic : Continuous와 유사하지만, 움직이는 콜라이더와 충돌 (CPU 많이 사용)
Continuous Speculative : 여러 게임오브젝트가 가지고 있는 속도와 콜라이더를 계산해서 추측성 충돌을 일으킨다

참고 자료 : 객체지향 프로그래밍 & 인터페이스
https://youtu.be/yQowAn2xyPg?si=lJcEanABuurWzfEQ