![[2024 SWF] Project RM 개발일지 #3](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTxGfU%2FbtsI5SrZfa0%2FAMl0R3eWgFisfpoELRBV61%2Fimg.png)
이번 주에는 전에 했던 대사 말하기랑 대사 진행을 가지고 본격적으로 튜토리얼 씬 제작에 들어갔다.
플레이어 코루틴
플레이어 이동 코루틴
전에 대사 말하기를 코루틴으로 구현했는데, 이걸 이용해서 플레이어를 이동시켜주는 것도 코루틴으로 구현해서 직접 조작하지 않고도 플레이어가 움직이도록 했다.
IEnumerator PlayerMoveX(float destinationX, float moveSpeed)
{
int direction = 0;
if (player.transform.position.x < destinationX)
{
direction = 1;
player.transform.localScale = new Vector3(3, 3, 0);
playerGun.transform.position = player.transform.position + new Vector3(-0.1f, -0.275f, 0);
}
else if (player.transform.position.x > destinationX)
{
direction = -1;
player.transform.localScale = new Vector3(-3, 3, 0);
playerGun.transform.position = player.transform.position + new Vector3(0.1f, -0.275f, 0);
}
else print("Wrong Direction!");
playerAnimator.SetBool("isWalking", true);
playerAnimator.SetBool("isJump", false);
playerAnimator.SetFloat("SpeedHandler", moveSpeed / 2.5f);
while (true)
{
if (direction == 1 && player.transform.position.x > destinationX) break;
if (direction == -1 && player.transform.position.x < destinationX) break;
player.transform.Translate(Vector3.right * direction * moveSpeed * Time.deltaTime);
yield return null;
}
playerAnimator.SetBool("isWalking", false);
playerAnimator.SetFloat("SpeedHandler", 1f);
yield return null;
}
처음에 구현할 때에는 목표지점을 받아오고, 어느 방향으로 이동할지도 같이 받아왔었다. 그런데 생각해보니 방향은 받아올 필요가 없고 현재 위치에서 목표 위치 어느 방향에 있는지 봐서 결정하면 더 편할 것 같았다. 그래서 실행에 옮겼다.
우선 목표 지점과 이동 속도를 받아오고, 플레이어가 움직일 방향에 맞춰 direction 변수를 정했다. 그리고 그에 맞게 플레이어의 스케일 값을 변경하여 그 방향을 보도록 하였다.
처음에는 SpriteRenderer를 받아와 flipX를 바꾸면서 방향을 보게 했는데, 생각해보니 플레이어 이동에서 스케일값을 건드리기에 반대 방향을 보는 상황에서 flipX를 해버리면 팔이 공중부양을 해버리는 기이한 현상이 났었다.
그래서 여기도 스케일값으로 조정하는 것으로 바꿨다.
그리고 목표 지점에 갈 때 까지 Translate를 이용하여 이동하는 무한 코루틴을 걸어두었다.
이것을 튜토리얼 진행 코루틴에서 yield return으로 반환해주면 플레이어가 이동을 완료할 때 까지 대기해준다.
아래는 결과물이다.
조작법 익히기
테스트 장소 구현
조작법을 익히려면 적을 잡아보는게 제일 좋다. 그러려면 적을 잡을 장소가 필요한데, 그냥 맨 바닥에 적을 두면 대사를 진행할 때 와서 공격할 수도 있고, 미관상 그렇게 좋지도 않다고 생각했다.
그래서 평소에는 적을 잡는 장소를 숨겨두었다가, 대사가 어느정도 진행되면 나오도록 했다.
테스트 장소는 타일맵으로 구성했다.
IEnumerator TestRoomAppear()
{
while (testRoom[0].transform.position.y != 12f)
{
for (int i = 0; i < levelOneEnemies.transform.childCount; i++)
{
GameObject levelOneEnemy = levelOneEnemies.transform.GetChild(i).gameObject;
levelOneEnemies.transform.GetChild(i).gameObject.transform.position = Vector3.MoveTowards(levelOneEnemy.transform.position, levelOneEnemy.transform.position + new Vector3(0,13,0), 3f * Time.deltaTime);
}
for (int i = 0; i < levelTwoEnemies.transform.childCount; i++)
{
GameObject levelTwoEnemy = levelTwoEnemies.transform.GetChild(i).gameObject;
levelTwoEnemies.transform.GetChild(i).gameObject.transform.position = Vector3.MoveTowards(levelTwoEnemy.transform.position, levelTwoEnemy.transform.position + new Vector3(0,13,0), 3f * Time.deltaTime);
}
for (int i = 0; i < testRoom.Length; i++)
{
testRoom[i].transform.position = Vector3.MoveTowards(testRoom[i].transform.position, new Vector3(testRoom[i].transform.position.x, 12f, testRoom[i].transform.position.z), 3f * Time.deltaTime);
}
yield return null;
}
for (int i = 0; i < levelOneEnemies.transform.childCount; i++)
{
GameObject levelOneEnemy = levelOneEnemies.transform.GetChild(i).gameObject;
levelOneEnemy.AddComponent<Rigidbody2D>();
levelOneEnemy.GetComponent<Rigidbody2D>().constraints = RigidbodyConstraints2D.FreezePosition;
levelOneEnemy.GetComponent<Rigidbody2D>().freezeRotation = true;
levelOneEnemy.GetComponent<BoxCollider2D>().enabled = true;
}
for (int i = 0; i < levelTwoEnemies.transform.childCount; i++)
{
GameObject levelTwoEnemy = levelTwoEnemies.transform.GetChild(i).gameObject;
levelTwoEnemy.AddComponent<Rigidbody2D>();
levelTwoEnemy.GetComponent<Rigidbody2D>().freezeRotation = true;
levelTwoEnemy.GetComponent<BoxCollider2D>().enabled = true;
}
yield return null;
}
간단하게 설명하면, 테스트 장소를 올라오게 하는데, 그 안의 적들은 타일맵이 아니고 스프라이트이기에 따로 제어해주었다. 아래에서 적 스프라이트들이 올라오는데 이 때 땅 타일맵과 충돌이 나서 일단 올라오기 전에는 리지드바디랑 콜라이더를 꺼두고, 다 올라왔을 때 다시 켜주었다. 리지드바디는 끄고 켜는 옵션이 없어서 Destroy했다가 AddComponent 했다.
그리고, 테스트 장소를 건물로 만들었기 때문에 올라올 때 그냥 올라오면 밋밋했다. 밋밋함을 없애기 위해 올라올 때 카메라가 진동하도록 했다.
IEnumerator CameraShake(float duration, float amount, int vibrato, bool isFadeOut)
{
Camera.main.GetComponent<CinemachineBrain>().enabled = false;
Camera.main.DOShakePosition(duration, amount, vibrato, 90, isFadeOut);
yield return new WaitForSeconds(duration);
}
DOTween을 사용했고, 진동 시간, 진동량, 진동 범위, 페이드아웃 여부를 인자로 받아 떨리게 했다.
아래는 결과물이다.
그리고, 이제 나온 적을 잡을 차례이다.
적 잡기 대기 코루틴
전에 구현했던 플레이어가 총을 잡을 때까지 기다리는 코루틴을 응용하여 플레이어가 적을 모두 잡을 때까지 기다리는 코루틴을 짰다.
IEnumerator WaitForElemenations(int amount)
{
while (deadEnemies < amount) yield return null;
deadEnemies = 0;
yield return null;
}
...말 한것 치고는 너무 뭐가 없어서 놀랐다. deadEnemies는 적을 죽일 때마다 올라가고, 적을 죽여야되는 양을 설정해 그 양과 같아질 때 코루틴이 끝난다.
그러면 적을 잡을 때 까지 대기하는 코루틴이 된다.
총 적을 두 번 잡게 하여, 조작법을 확실히 익히도록 했다.
두 번째 적은 첫 번째 적을 잡을 때 아직 활성화가 안된 적으로 간주되기에, 태그를 빼주었고, 2번째 테스트가 시작될 때 태그와 컨트롤러를 넣어주어 활성화시켰다.
아래는 결과물이다.
모두 생각한대로 잘 되어주었다.
이번에 개발한것들은 모두 게임을 게임답게 만들어주는 연출적인 부분들이었다. 사실 전체 구조는 저번 일지를 쓸 때 이미 끝난 상태였고, 한 일주일간 연출만 신경썼던 것 같다.
연출을 모두 넣고 나니 진짜 인디게임 하나를 플레이하는 느낌이 들었다. 그러면서 연출이 이렇게 중요하구나를 새삼 다시 알게되었다.
그리고 영상을 보면 알겠지만, 맵이 이뻐졌다. 타일맵 작업을 안했었는데 하고나니 훨씬 느낌이 살았다.
이제 스테이지 전투와 보스 전투, 최종 보스만 하면 전체적인 게임은 1차 완성된다.
달려보자고

'팀 프로젝트 > Project RM' 카테고리의 다른 글
[2024 SWF] Project RM 개발일지 #4 (6) | 2024.09.02 |
---|---|
[2024 SWF] Project RM 개발일지 #2 (0) | 2024.08.10 |
[2024 SWF] Project RM 개발일지 #1 (0) | 2024.07.25 |
안녕하세요! 코드 짜는 농부입니다! 경희대학교 소프트웨어융합학과 23학번 재학중입니다. 문의 : dsblue_jun@khu.ac.kr
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!