0. 들어가기 전에
이번에는 맵 프리팹과 스크립트를 수정했다. 기존 맵 프리팹은 그냥 맵에 압정의 위치가 한 곳에만 있고 변경되지 않았다. 이번에는 맵에 압정 3개를 놓은 후, 랜덤한 확률로 압정을 활성화 여부를 결정해서 압정이 다양한 곳에 생성되는 것처럼 보이도록 했다.
압정이 활성화 될 확률은 쉬움, 보통, 어려움 단계에 맞게 설정해야 했는데 이를 위해 Random.Range 를 이용했다.
1. 맵 프리팹
일단 쉬움 단계의 맵의 종류로는 4개를 만들어놨는데 더 추가할 예정이다.
일단 맵은 압정이 있는 경우, 몬스터가 있는 경우, 아무것도 없는 경우로 나뉜다.
1. 인스펙터 창
모든 맵의 인스펙터 창 컴포넌트 상태는 다음과 같다. 맵 마다 다른 부분이 있다면 Map Controller 스크립트이다. 위에서 맵은 장애물이 있는 경우와 없는 경우로 나뉜다고 했는데 그에 따라 Spikes 와 Monsters 에 들어가는 것들도 달라진다.
아래 사진의 경우는 맵에 압정이 3개 있는 경우이다.

2. 스크립트
이번에 수정한 스크립트는 MapLoop 와 Monster 이다.
2.1 MapLoop 스크립트
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Tilemaps;
public class MapController : MonoBehaviour
{
[Header("Map Info")]
public float curTime = 0;
public float targetTime = 2f; // 활성화된지 2초가 지나면 활성화 끄기
public int mapLevel; // 맵 단계에 따라 압정이 최대로 활성화 되는 개수가 달라짐
// 맵에 장애물 없으면 활성화 안 됨
public GameObject[] spikes; // 압정 개수(맵이 활성화 될 때마다 압정의 위치 변경)
public GameObject[] monsters; // 몬스터 개수
private void OnEnable()
{
// 활성화 되면 자동으로 호출됨
ResetMap();
}
private void Update()
{
curTime += Time.deltaTime;
if (curTime >= targetTime)
gameObject.SetActive(false); // 비활성화!
}
public void ResetMap()
{
curTime = 0;
transform.localPosition = Vector3.zero; // 지역 위치를 (0, 0, 0) 으로 설정해주기..
// 장애물을 랜덤으로 활성화하기
for (int i =0; i<spikes.Length; i++)
{
int count = 0;
// 쉬움 단계
if (mapLevel == 0)
count = 5;
// 보통 단계
else if (mapLevel == 1)
count = 3;
// 어려움 단계
else
count = 2;
int tmp = Random.Range(0, count); // 0, 1, 2, ... count-1 중 랜덤
if (tmp == 0)
{
// 활성화
spikes[i].SetActive(true);
}
else
{
// 비활성화
spikes[i].SetActive(false);
}
}
for (int i=0; i<monsters.Length; i++)
{
monsters[i].SetActive(true); // 몹 활성화하기
}
}
}
2.2 MapLoop 스크립트 변경 사항 설명
1. 변수
새로 추가한 변수는 다음과 같다. 맵 단계에 따라 압정이 최대로 활성화 되는 개수가 달라지기 때문에 mapLevel 변수를 만들었다.
그리고 맵의 자식 오브젝트인 압정과 몬스터를 할당하기 위한 배열을 선언했다.
public int mapLevel; // 맵 단계에 따라 압정이 최대로 활성화 되는 개수가 달라짐
// 맵에 장애물 없으면 활성화 안 됨
public GameObject[] spikes; // 압정 개수(맵이 활성화 될 때마다 압정의 위치 변경)
public GameObject[] monsters; // 몬스터 개수
2. OnEnable()
새로 추가한 메서드이다. 게임 오브젝트가 활성화될 때 자동으로 호출되는 메서드이다.
맵이 활성화 되면 정보를 초기화 하도록 ResetMap 메서드를 호출했다.
private void OnEnable()
{
// 활성화 되면 자동으로 호출됨
ResetMap();
}
3. ResetMap()
맵의 정보를 초기화하는 메서드이다. 기존 메서드에 로직을 더 추가했다.
현재 야생 난이도에 따라 압정이 활성화 되는 확률이 달라져야 한다. 지금은 임의로 설정한 값이고 다음에 직접 플레이해보면서 난이도 조정을 할 생각이다.
일단 맵 레벨에 맞게 count 값을 설정해준 후 Random.Range 로 확률 계산을 했다. 일단 count = 5 면 각 압정이 활성화 될 확률이 20% 이다. 쉬움 단계인데도 불구하고 압정이 활성화 될 확률이 높은 것 같아서 다음에 변경해야 할 것 같다.
몬스터는 딱히 활성화 여부를 확률로 계산하지 않고 그냥 맵에 자식으로 존재하는 모든 몹을 활성화 하도록 했다. 얘는 알아서 움직이니까 이렇게 해도 될 것 같다는 생각이 들었다..
public void ResetMap()
{
curTime = 0;
transform.localPosition = Vector3.zero; // 지역 위치를 (0, 0, 0) 으로 설정해주기..
// 장애물을 랜덤으로 활성화하기
for (int i =0; i<spikes.Length; i++)
{
int count = 0;
// 쉬움 단계
if (mapLevel == 0)
count = 5;
// 보통 단계
else if (mapLevel == 1)
count = 3;
// 어려움 단계
else
count = 2;
int tmp = Random.Range(0, count); // 0, 1, 2, ... count-1 중 랜덤
if (tmp == 0)
{
// 활성화
spikes[i].SetActive(true);
}
else
{
// 비활성화
spikes[i].SetActive(false);
}
}
for (int i=0; i<monsters.Length; i++)
{
monsters[i].SetActive(true); // 몹 활성화하기
}
}
2.3 Monster 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Monster : MonoBehaviour
{
public enum MoveDir
{
Left = -1, Idle = 0, Right = 1
}
[Header("Monster info")]
public float speed = 1f; // 이동 속도
public MoveDir dir = MoveDir.Idle; // 점토 몬스터가 움직이는 방향
public Coroutine monsterMoveCoroutine; // 몬스터는 계속해서 움직이는데 그 동작을 코루틴으로 만들 것. 코루틴 시작한 거 저장하기 위한 용도
public bool isDie = false;
private Animator anim;
private Rigidbody2D rigid;
private SpriteRenderer spriteRenderer;
private AudioSource monsterAudio;
private void Start()
{
// 컴포넌트 할당
anim = GetComponent<Animator>();
rigid = GetComponent<Rigidbody2D>();
spriteRenderer = GetComponent<SpriteRenderer>();
monsterAudio = GetComponent<AudioSource>();
}
private void OnEnable()
{
// 활성화 될 때 몬슽터 정보 리셋
MonsterReset();
}
private void Update()
{
if (!isDie)
{
// 몬스터가 땅에 있으면 이동 방향에 따라 움직일 수 있도록
transform.Translate(new Vector2((int)dir, 0) * speed * Time.deltaTime * 0.5f);
// 애니메이션 수행 & 스프라이트 관리
SetMonsterSprite();
// 몬스터 레이캐스트
MonsterRaycast();
}
}
private void MonsterReset()
{
isDie = false;
if (monsterMoveCoroutine != null)
StopCoroutine(monsterMoveCoroutine); // 코루틴 종료
monsterMoveCoroutine = StartCoroutine(MonsterMove()); // 코루틴 다시 시작
}
private void MonsterRaycast()
{
// 방향에 맞게 레이 쏴야함
// 근데 아래 방향으로 쏴야함 -> 몬스터보다 조금 더 앞에서 밑에 방향으로 레이 쏠 것
Vector2 front = new Vector2(rigid.position.x + (int)dir, rigid.position.y);
Debug.DrawRay(front, Vector2.down, new Color(0, 1, 0));
RaycastHit2D rayHit = Physics2D.Raycast(front, Vector3.down, 1, LayerMask.GetMask("Ground"));
if (rayHit.collider == null)
{
Debug.Log("밑에 땅이 업어여;;");
// 자기 앞이 낭떠러지면 move 코루틴 중지하고 GotoReverse 시작
StopCoroutine(monsterMoveCoroutine);
StartCoroutine(GotoReverse());
}
}
private IEnumerator MonsterMove()
{
while (true)
{
dir = (MoveDir)Random.Range(-1, 2);
yield return new WaitForSeconds(2f); // 2초마다 방향 바꿀 수 있도록..
}
}
private IEnumerator GotoReverse()
{
// 이동 방향을 반대로 바꾸는 코루틴
int reverseDir = -(int)dir; // 현재 이동 방향에 반대
// 점토가 가는 방향의 밑이 낭떠러지면 방향 바꿔야함
dir = (MoveDir)reverseDir;
yield return new WaitForSeconds(2f); // 2초 기다령
monsterMoveCoroutine = StartCoroutine(MonsterMove()); // 코루틴 다시 시작
}
private void SetMonsterSprite()
{
// 애니메이션 수행
if ((int)dir == 0)
anim.SetBool("Move", false);
else
{
anim.SetBool("Move", true);
// 움직이는 방향이 오른쪽이면 스프라이트 뒤집도록..
if ((int)dir == -1)
spriteRenderer.flipX = false;
else
spriteRenderer.flipX = true;
}
}
public void Die()
{
StartCoroutine(DieAnim()); // 코루틴 시작
}
private IEnumerator DieAnim()
{
monsterAudio.Play(); // 오디오 재생
isDie = true; // 죽은 상태 알리기
anim.SetTrigger("Die"); // 죽는 애니메이션 수행
yield return new WaitForSeconds(1f); // 2초 기다려여
gameObject.SetActive(false); // 활성화 꺼야함
}
}
2.4 Monster 스크립트 변경 사항 설명
1. OnEnable()
새로 추가한 메서드이다. 게임 오브젝트가 활성화될 때 몬스터 정보를 리셋하기 위한 메서드를 호출하도록 했다.
private void OnEnable()
{
// 활성화 될 때 몬슽터 정보 리셋
MonsterReset();
}
2. MonsterReset()
새로 만든 메서드이다. isDie 변수의 값을 다시 false 로 설정해주고, 기존 코루틴을 종료한 후 다시 시작하도록 했다.
private void MonsterReset()
{
isDie = false;
if (monsterMoveCoroutine != null)
StopCoroutine(monsterMoveCoroutine); // 코루틴 종료
monsterMoveCoroutine = StartCoroutine(MonsterMove()); // 코루틴 다시 시작
}
3. 결과물