[Unity] Monster, Camera 2D Script

Lingtea_luv·2025년 5월 22일
0

Unity

목록 보기
16/30
post-thumbnail

2D Scripts


Monster

MonsterPatrol

해당 스크립트는 몬스터가 타일 위를 순찰하는 로직을 구현한 것으로, 몬스터 정면에서 아래로 Ray를 발사하여 Ground를 검사하는 것을 메인으로 한다.

public class MonsterPatrol : MonoBehaviour
{
    [Header("Number")] 
    [SerializeField] private float _moveSpeed;
    [SerializeField] private LayerMask _groundLayer;
    
    private Rigidbody2D _rigid;
    private Animator _animator;
    
    private Vector2 _moveDir;
    private readonly int Patrol_Hash = Animator.StringToHash("Patrol");
    
    
    private void Awake() => Init();

    private void Update() => Detect();

    private void FixedUpdate() => Move();

    private void Init()
    {
    	// 컴포넌트 설정
        _rigid = GetComponent<Rigidbody2D>();
        _animator = GetComponentInChildren<Animator>();
        
        // 초기 방향 설정
        _moveDir = Vector2.left;  
    }

    private void Move()
    {
        if (_moveDir != Vector2.zero)
        {
            _rigid.velocity = _moveDir * _moveSpeed;
            _animator.Play(Patrol_Hash);        
        }
    }

    private void Detect()
    {
    	// 몬스터가 바라보는 방향 앞쪽에 감지할 좌표 설정
        Vector2 rayOrigin = transform.position + new Vector3(_moveDir.x, 0);
		
        // 아래 방향으로 Ground를 감지하는 Ray구현
        RaycastHit2D hit = Physics2D.Raycast(rayOrigin, Vector2.down, 1f, _groundLayer);
        
        // 몬스터 발 밑에 아무것도 없다면(null)
        if (hit.collider == null)
        {
        	// 방향을 바꾸고
            _moveDir *= -1;
            
            // 방향에 따라 몬스터의 sprite 방향을 설정
            if (_moveDir.x > 0)
            {
                transform.localScale = new Vector3(-1, 1, 1);
            }
            else if (_moveDir.x < 0)
            {
                transform.localScale = new Vector3(1, 1, 1);
            }
        }
    }
}

MonsterHit

해당 스크립트에서는 Player의 Attack( )과 연계되어 IDamageable을 상속받아 데미지를 받고 hp가 0이 되면 몬스터가 사라지도록 구현하였다.

public class MonsterHit : MonoBehaviour, IDamageable
{
	[Header("Number")] 
    [SerializeField] private int _hp;
    
    private void Update()
    {
        if (_hp <= 0)
        {
            Die();
        }
    }
    
    private void Die()
    {
        Destroy(gameObject);
    }

    /// <summary>
    /// Player의 Attack 메서드에서 IDamageable 변수로 호출하는 메서드
    /// </summary>
    /// <param name="atk">Player의 공격력</param>
    public void Damaged(int atk)
    {
        _hp -= atk;
        Debug.Log(_hp);
    }
}

CameraController

해당 스크립트에서는 카메라가 플레이어를 따라가되, 타일이 끝나는 지점에서는 플레이어를 따라가는 것이 아닌, 고정된 시점으로 보여주는 것을 구현했다.

사실 해당 부분은 Virtual Camera에서 Add Extension에 Confiner 2D를 활용하면 매우 쉽게 구현이 가능하지만, 문제는 Pixel 기반 그래픽의 2D 게임에서는 Pixel Perfect 컴포넌트를 활용해야한다는 것이다.

Pixel Perfect 컴포넌트와 Confiner를 동시에 사용하게 될 경우 둘이 충돌을 일으켜 Confiner가 제대로 동작하지 않기 때문에 둘 중 하나를 포기해야했는데, Pixel Perfect의 퍼포먼스가 더 중요하기에 Confiner를 제거하고 스크립트로 해당 기능을 따로 구현했다.

public class CameraController : MonoBehaviour
{
    [Header("Drag&Drop")] 
    [SerializeField] private Tilemap _tilemap;
    [SerializeField] private Camera _main;
    [SerializeField] private GameObject _player;
    
    private float _heightBlank;
    private float _widthBlank;
    
    private void Awake()
    {
        Init();
    }

    private void LateUpdate()
    {
        if (_player != null)
        {
            Vector3 playerPos = _player.transform.position;
            transform.position = CalculateBound(new Vector3(playerPos.x, playerPos.y,transform.position.z));
        }
    }

    private void Init()
    {
    	// 메인 카메라 세로 길이의 절반
        _heightBlank = _main.orthographicSize;
        _widthBlank = _heightBlank * ((float)Screen.width / Screen.height);
    }

    private Vector3 CalculateBound(Vector3 cameraPos)
    {
    	// tilemap에 배치된 sprite들의 경계를 가져오는 메서드
        Bounds bounds = _tilemap.localBounds;
        
        // camera의 pivot 위치이므로 bounds.min.x로 할당x
        // 가장 왼쪽에 있는 sprite의 x좌표 + 카메라 가로 절반 길이        
        float minX = bounds.min.x + _widthBlank;
        float maxX = bounds.max.x - _widthBlank;

        float clampX = Mathf.Clamp(cameraPos.x, minX, maxX);
        
        return new Vector3(clampX, cameraPos.y, cameraPos.z);
    }
}

BackGround(무한 배경)

해당 스크립트는 무한 배경을 구현한 것이다. 단일 sprite의 Draw Mode를 Tiled로 변경한 뒤 길게 늘리는 방식도 있으나, 퍼포먼스적으로는 아래처럼 2개의 sprite를 재활용하는 방식이 더 낫다.

public class BackGround : MonoBehaviour
{
    [Header("Drag&Drop")] 
    [SerializeField] private Transform[] _backGrounds;
    [SerializeField] private Transform _playerPos;
    
    private float _spriteWidth;
    
    
    private void Awake() => Init();

    private void Update() => Relocate();

    private void Init()
    {
    	// sprite 2개를 겹쳐 놓고, 겹친 길이만큼 빼주어야한다.
        // 겹치지 않으면 정확히 sprite의 가로 길이를 계산하더라도 sprite 사이에 실선이 생긴다.
        _spriteWidth = _backGrounds[0].GetComponent<SpriteRenderer>().bounds.size.x - 0.2f;
    }

    private void Relocate()
    {
    	// 동일한 sprite 2개로 구현
        for (int i = 0; i < _backGrounds.Length; i++)
        {       
            float distance = _playerPos.position.x - _backGrounds[i].position.x; 
            
            // 플레이어의 위치로부터 멀리 떨어지면
            if (Mathf.Abs(distance) > _spriteWidth)
            {
            	// sprite의 위치를 변경
                float offset = _spriteWidth * _backGrounds.Length;
                Vector3 pos = _backGrounds[i].position;
                _backGrounds[i].position = new Vector3(pos.x + Mathf.Sign(distance) * offset, pos.y, pos.z);
            }
        }
    }
}
profile
뚠뚠뚠뚠

0개의 댓글