이전 포스팅의 내용이 너무 간단했어서 추가적인 기능들을 구현해보려고 한다.
캐릭터가 이동하면 카메라가 따라서 이동하는 기능과 몬스터를 밟아서 죽이고, 닿으면 죽는 기능을 구현할 것이다.
우선 이전의 맵은 너무 작았기때문에 카메라가 이동할 수 있을 정도로 확장시켜보려고 한다.
타일맵 그리기
사실 타일맵 그리는 법을 몰라서 이전 맵은 오브젝트의 포지션을 하나하나 맞춰서 그렸었다. 하지만 더 확장시키기엔 불편하기 때문에 타일맵 그리는 법을 찾아보았다.
타일 팔레트 만들기
Window - 2D - Tile Palette를 눌러 타일 팔레트 창을 활성화하고 Create New Palette를 해줬다. 옵션은 기본 그대로 설정하였다.
빈 팔레트에 넣고싶은 스프라이트들을 선택해 넣어주면 팔레트가 완성된다.
타일맵 그리기
2D Object - Tilemap - Rectangular를 생성하면 그리드 안에 타일맵을 그릴 수 있다.
브러쉬를 선택한 상태에서 그릴 수 있고, shift를 누르고 선택하면 지워진다. 대괄호 []로 회전도 가능하다.
Collider 설정
타일맵의 경우 TileMapCollider를 사용한다.
점프를 판정할 때 Ground와 충돌을 체크하기 때문에 벽의 경우 따로 구분하여 그려주었다.
카메라 이동
이동 스크립트
using UnityEngine;
public class CameraController : MonoBehaviour
{
[SerializeField]
Transform playerPos;
void Update()
{
// 플레이어 따라 이동
transform.position = new Vector3(playerPos.position.x, 0, -10f);
}
}
Camera에 적용해줄 CameraController를 작성하였다.
transform.position 값을 이용해 카메라가 플레이어를 쫓아간다.
이동 범위 제한
슈퍼마리오도 그렇듯, 시작할 때와 끝날 때는 카메라가 맵 밖으로 이동하지 않도록 멈추기 때문에 추가적인 코드를 작성했다.
using UnityEngine;
public class CameraController : MonoBehaviour
{
[SerializeField]
Vector3 startPos;
Transform playerPos;
Vector3 finishPos;
[SerializeField]
float offset = 7f;
void Start()
{
playerPos = GameObject.FindGameObjectWithTag("Player").transform;
finishPos = GameObject.FindGameObjectWithTag("Finish").transform.position;
startPos = playerPos.position;
}
void Update()
{
// 시작 지점 오프셋
if (playerPos.position.x < startPos.x + offset )
return;
// 최종 지점 오프셋
if (playerPos.position.x > finishPos.x - offset)
return;
// 플레이어 따라 이동
transform.position = new Vector3(playerPos.position.x, 0, -10f);
}
}
우선 시작할 때 플레이어 시작 위치와 최종 지점의 위치를 가져와 기준점으로 잡는다.
이제 플레이어가 해당 기준점보다 일정 거리 이상 넘어가면 카메라는 움직이지 않는다.
몬스터
몬스터 생성
몬스터를 생성하고 콜라이더와 리지드바디를 적용해주었다.
공격 / 피격 스크립트
처음에는 피격용 캡슐 콜라이더와 공격용 엣지 콜라이더를 같이 사용하는 방식을 떠올렸지만, 생각대로 구현되지 않아 다른 방법을 생각해봤다.
void OnCollisionEnter2D(Collision2D other)
{
// 몬스터 충돌
if (other.gameObject.CompareTag("Monster"))
{
// 위에서 밟은 경우 공격
if (other.gameObject.transform.position.y + other.gameObject.transform.localScale.y * 0.7f < transform.position.y)
{
Animator anim = GetComponent<Animator>();
Destroy(other.gameObject);
anim.SetBool("isJumping", true);
state = PlayerState.Jumping;
characterRb.AddForceY(jumpPower, ForceMode2D.Impulse);
}
else
{
// 아래나 옆에서 충돌한 경우 피격
state = PlayerState.Dead;
}
}
}
몬스터의 position.y값과 몬스터 사이즈를 더해 머리의 y값을 구하고, 밟았을 때 캐릭터의 y값과 비교해 캐릭터가 더 높이 있다면 공격으로 처리하는 방식이다. 사실 현재로서는 밟았는데도 피격처리가 되는 경우가 발생하고 있다. 0.7을 곱해 조금 범위를 여유롭게 주었지만 아마 프레임이 도는 타이밍에 따라 달라지는 것 같다. 추후 정확한 공격/피격을 처리할 방법을 찾고, 밟았을 경우 이동 속도가 유지되어 키보드를 누르지 않아도 이동되는 부분을 해결해야 한다.
몬스터 이동
몬스터의 생성이나 움직임 등에 대해 아직 배우지 않았지만, 간단하게 일정 구간을 왔다갔다 이동하는 스크립트를 작성했다.
InvokeRepeating
주기적으로 바라보는 방향을 바꿨으면 좋겠어서 일정 시간마다 반복해서 함수를 호출해주는 메소드를 찾았다.
public void InvokeRepeating(string methodName, float time, float repeatRate)
InvokeRepeating("move", 2, 3); // move라는 메소드를 2초부터 3초에 한 번씩 실행시킨다.
methodName의 메소드를, time부터 repeatRate의 주기로 실행해주는 함수이다.
스크립트
using UnityEngine;
public class MonsterController : MonoBehaviour
{
[SerializeField]
float moveSpeed = 150f;
[SerializeField]
float flipTime = 2f;
bool isRight = false;
SpriteRenderer spriteRenderer;
Rigidbody2D monsterRb;
void Start()
{
spriteRenderer = GetComponent<SpriteRenderer>();
monsterRb = GetComponent<Rigidbody2D>();
InvokeRepeating("Flip", flipTime, flipTime); // flipTime 주기로 반복
}
void FixedUpdate()
{
Move();
}
void Move()
{
Animator anim = GetComponent<Animator>();
anim.SetBool("isMoving", true);
monsterRb.linearVelocityX = isRight ? moveSpeed * Time.deltaTime : -moveSpeed * Time.deltaTime;
}
void Flip()
{
spriteRenderer.flipX = !spriteRenderer.flipX;
isRight = spriteRenderer.flipX;
}
}
몬스터에 적용한 MonsterController이다.
몬스터는 FixedUpdate()에 맞춰 계속해서 바라보는 방향으로 이동하고, flipTime마다 한번씩 Flip된다.