개요
시간이 1주도 안남았다는걸 망각했습니다. 시간이 없었습니다.
글 쓰는것도 안써놓으면 나중에 이때의 내용을 다 잊어버릴 것 같아서 미리미리 생각나는대로 적는겁니다.
개요 쓸 시간도 없습니다. 바로 가죠.
기획 변경
매주 선생님께 프로젝트 진행 상황을 보고 하는데 이번 보고에서 제가 해온 꼬라지를 보시고선 시간이 없으니 기획을 바꾸라고 조언을 해주셨습니다.
일단 맵 디자인을 길게 하려고 하지말고 짧은 스테이지로 구성을 해서 타임어택을 목표로 하는 게임을 만들기로 했습니다.
그리고 가장 중요한 것. 포폴로 사용하기 위해선 전투 프로토타이핑이 제대로 돼야합니다.
적 FSM을 더욱 견고하게 짜고 맵 디자인을 최대한 간결하게 해서 최대한 총격전의 느낌은 살리되 기간내에 가능하도록 하기로 했습니다.
일단, 보고를 하기 전 만들어 둔 버튼을 대충 설명하고 넘어가겠습니다.
버튼
말 그대로 버튼입니다.
총으로 쏘면 작동하는 버튼이요.
당장은 문열리는거 밖에 없지만 나중에는 뭐가 추가 되진... 않을 겁니다. 왜냐하면 추가 요소 넣기엔 시간이 없거든요.
만든 이유는 이거라도 해서 맵 디자인에 조금 더 다채로움을 주기 위해섭니다.
네. 날먹입니다.
이걸 구현하기 위해선 Player의 총이 버튼을 감지해줄 필요가 있습니다.
일단 Player의 총 피격 감지 메커니즘을 봅시다.
Physics.Raycast(Camera.main.transform.position, Camera.main.transform.forward,
out hit, Camera.main.farClipPlane, _whatIsObstacle | _whatIsEnemy);
if (hit.collider != null)
{
if (hit.collider.TryGetComponent(out IDamageable health))
{
health.ApplyDamage(10, hit.point, hit.normal, 1f);
}
}
잡다한건 잘라내고 가져왔습니다.
피격을 구현하기 위해 만들었던 IDamageable이라는 인터페이스를 사용해서 피격을 감지합니다.
그러니 버튼들은 IDamageable을 상속받아서 ApplyDamage에서 버튼을 눌렀을때 작동해야 하는 코드를 적으면 되겠죠.
근데 ApplyDamage에 주렁주렁 달려있는 매개변수가 거슬리니 그냥 추상클래스로 따로 빼겠습니다.
public abstract class InteractableObject : MonoBehaviour, IDamageable
{
public void ApplyDamage(int damage, Vector3 hitPoint, Vector3 normal, float knockBackPower)
{
Interact();
}
protected abstract void Interact();
}
음... 효율적인지는 모르겠지만 보긴 좋습니다.
그리고 이제 버튼을 만들어줄 차롄데 제가 만들 버튼은 2개의 종류가 있습니다.
버튼을 활성화 하면 다시 한번 건들기 전까지 비활성화 되지 않는 ToggleButton,
버튼을 활성화 하면 일정 시간 뒤에 비활성화 되는 TimerButton이죠.
근데 아래 TimerButton. 이걸 보니 여러개 배치해두고 모두 활성화 돼야 진짜 활성화되는 그런 메커니즘이 생각나더군요.
근데 모든 버튼을 InteractableObject로 묶을순 없죠. 버튼 말고도 다른 총으로 상호작용한 것이 필요할지도 모르니까요.
그러니 인터페이스를 만들어서 하나로 묶어주겠습니다.
public interface IInteractableButton
{
public bool IsActive { get; set; }
}
I 대문자 2개가 겹쳐서 조금 보기 그런 인터페이스가 되었지만 작명법은 중요하기 때문에 어쩔 수 없습니다.
아무튼 버튼이 활성화 돼있는지 확인해줄 프로퍼티를 하나 만들어주고 인터페이스 작성을 끝내줍니다.
이제 ToggleButton부터 만들어보죠.
별거 없습니다. 둘 다 상속해주고, 인터페이스 구현 해준 다음에...
public class ToggleButton : InteractableObject, IInteractableButton
{
public bool IsActive { get; set; } = false;
}
열어줄 Door를 가져와서 열어주기만 하면 끝이죠.
[SerializeField]
private Door _door;
protected override void Interact()
{
IsActive = !IsActive;
if(_door != null)
_door.ModifyOpenStatus(IsActive);
}
Null Check를 하는 이유는 그저 열리는 Door 없이 IsActive 값만 필요할 경우를 상정하기 위함입니다.
참고로 Door의 ModifyOpenStatus 함수는 이래 생겼습니다.
문이 4개의 조각이라서 어쩔 수 없이 이따구로 코드를 씁니다. 아이고...ㅠㅠㅠㅠ
아무튼 이제 TimerButton을 만들어줍시다.
public class TimerButton : InteractableObject, IInteractableButton
{
[SerializeField]
private float _closeTime = 2f;
private float _closeTimer = 0;
[SerializeField]
private Door _door;
public bool IsActive { get; set; } = false;
protected override void Interact()
{
IsActive = true;
_closeTimer = 0;
_door.ModifyOpenStatus(true);
}
private void Update()
{
if (IsActive)
{
_closeTimer += Time.deltaTime;
if(_closeTimer > _closeTime)
{
IsActive = false;
if (_door != null)
_door.ModifyOpenStatus(false);
}
}
}
}
똑같습니다. 뭐 딱히 설명할건 없군요. _closeTimer를 총으로 건드릴때마다 0초로 바꿔줘야 하는 것 정도가 유일한 특이사항입니다.
완성품은 다음과 같습니다.
바뀐 기획
일단 봤을때 맵이 상당히 어두웠죠.
실제로 맵 컨셉이 저랬습니다. 근데 라이팅을 자연스럽게 하기도 진짜 힘들더군요.
그래서 그냥 흰색으로 바꿔버리겠습니다. 뭐 어떻습니까.
방금 쬐끔 만졌는데 훨씬 낫죠? 음 이쁘다.
그리고 이제 스테이지식 구성으로 스피드런 위주의 게임 플레이다보니 단계별로 스테이지를 쭉 나열해놓고 스테이지를 클리어한 시간만 합산해서 최종 결과를 내도 괜찮을 것 같았습니다.
기획 자체는 대충 됐습니다. 1주만 열심히 구릅시다. 최소한 게임이 돌아가긴 해야하니까요.
마치는 말
2교시가 지나가고 있습니다. 게임 엔진 응용은 3교시까집니다. 3교시까지 구르고 다시 블로그로 찾아뵙죠.
'개발 > UNNAMED' 카테고리의 다른 글
UNNAMED | 네번째 프로젝트 # Final : 완성 (1) | 2024.07.09 |
---|---|
UNNAMED | 네번째 프로젝트 # 7 : 스테이지, 튜토리얼 (1) | 2024.07.03 |
UNNAMED | 네번째 프로젝트 # 5 : 적 FSM (0) | 2024.06.08 |
UNNAMED | 네번째 프로젝트 # 4 : 총 (0) | 2024.06.02 |
UNNAMED | 네번째 프로젝트 # 3 : Player FSM (0) | 2024.06.01 |