코드카타 알고리즘 '문자열 내림차순으로 배치하기' 문제를 풀다가 ToString()에 대해 궁금증이 생겼다.

문제 내용은 아래와 같다.

 

문자열을 배열에 넣어 Array.Sort()를 이용하여 풀었는데, 문자열로 출력하는 부분에서 왜 ToString으로 배열을 전환할 수 없는지 궁금해졌다.

using System;
using System.Linq;
public class Solution {
    public string solution(string s) 
    {
        char[] arr = s.ToCharArray();
        Array.Sort(arr);
        Array.Reverse(arr);
        string answer = new string(arr);
        return answer;
    }
}

 

ToString()을 사용할 경우에 나오는 출력값은 System.Char[]이다.

C#에서 다루는 대부분은 System.Object라는 클래스를 상속받았다. 그 중 ToString()은 '이 개체가 어떤 개체인가'를 string으로 반환하는 문자열이다. Object를 상속받은 모든 클래스는 ToString()을 오버라이딩할 수 있는데, 예시로  System.Int32는 ToString()을 오버라이딩해서 정수값을 string으로 보여주라고 정의 되어있다.

즉, ToString()을 재정의하냐 안하냐에 따라 결과가 달라지는데 배열은 재정의되어 있지 않은 것이다.

쉽게 말하면 배열에서 ToString으로 문자열 변환은 안된다!

 

ToString을 이것저것 문자열 변환 할 때 사용했다가 안되는 경우가 많아서 그냥 단순히 숫자만 가능한가보다...하고 넘겼는데 상속과 오버라이딩이 관련 있었다. 더 자세한 내용은 아래에서 확인해보면 좋을 것 같다.

https://learn.microsoft.com/ko-kr/dotnet/api/system.object.tostring?view=net-8.0

충돌을 처리하기 위해서는 Collider와 Rigidbody가 필요하다.

Collider는 충돌을 감지하는 기능을 한다. 모양에 따라 Box, Sphere, Mesh 등 여러가지 형태를 가지고 있다. Rigidbody는 물리적인 법칙을 적용하기 위해 달아주어야한다. Rigidbody는 최소한의 사용을 지향하는 것이 좋기 때문에 주로 변화가 많은 캐릭터에 주는 편이다.

Rigidbody는 Rigidbody가 달려있는 물체가 다른 Collider가 달려있는 물체와 충돌했을 때 충돌에 대해 소통할 수 있게 해준다.

충돌이 발생하면 유니티에서는 OnCollisionEnter, OnCollisionStay, OnCollisionExit 등의 이벤트를 발생시킨다.

 

타일맵(Tilemap)

이미지들을 바둑판 배열 방식으로 맵을 구성하는 것.

 

Hierarchy에 우클릭해서 2D Object - Tilemap에서 Rectangular로 만들어준다.

 

Window - 2D - Tile Palette에서 Create New Pallete를 해준 뒤, 타일 이미지 Assets을 추가한다.

 

적당히 꾸며주고 Collision을 따로 만들어 Tilemap Collider 2D를 추가해준 후, 충돌범위를 지정해주고 투명도를 조절해서 보이지 않게 해준다.

Color에서 투명도 조절

 

플레이어에 Box Collider 2D를 Component 하여 방금 만들어둔 벽면 밖으로 나가지 못하도록 한다.

 

 

조준(Aim) 시스템

무기 Asset을 유니티에 끌어온 뒤, 스크립트를 작성해준다. flipY는 Y축을 기준으로 뒤집는 코드로, 캐릭터를 기준으로 무기가 90도를 넘어가면 뒤집도록 한다. 즉, 마우스 방향으로 캐릭터를 뒤집어준다.

[SerializeField] private SpriteRenderer armRenderer;
[SerializeField] private Transform armPivot;

[SerializeField] private SpriteRenderer characterRenderer;

private TopDownCharacterController _controller; // 캐릭터가 바라보도록 한다

private void Awake()
{
    _controller = GetComponent<TopDownCharacterController>();
}

void Start()
{
    _controller.OnLookEvent += OnAim;
}

public void OnAim(Vector2 newAimDirection)
{
    RotateArm(newAimDirection);
}

private void RotateArm(Vector2 direction)
{
	float rotZ = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
    
    armRenderer.flipY = Mathf.Abs(rotZ) > 90f; // y축을 기준으로 뒤집는 코드
    characterRenderer.flipX = armRenderer.flipY; // 캐릭터를 기준으로 무기가 90도를 넘어가게 되면 뒤집는다
    armPivot.rotation = Quaternion.Euler(0, 0, rotZ); // 무기 회전
}

 

rotation이라고 하는 회전 값은 Quaternion이라고 하는 4원소 값을 쓰고 있기 때문에 우리가 일반적으로 사용하기 쉽지 않다. 아크 탄젠트로 세타 값이 나오면 Pi값, 즉 라디안 값이 나온다. 그러므로 라디안 값을 오일러(Euler) 값인 degree(도)로 바꾸는 값을 곱해준다.

 

Atan2(x, y)는 아크 탄젠트(역탄젠트)를 구하는 것이다. x와 y 값을 구해서 아크 탄젠트를 구하면 세타( θ)이 나온다. 즉, 벡터의 각도를 구하는 것이다.

 

캐릭터에 스크립트를 Component 하여 각각 지정해준다.

 

 

공격 시스템

공격을 구현하기 위한 스크립트를 생성한다.

보통 Awake에서 Component들을 준비하고 Start나 이후에 그 코드를 활용하는 식으로 구성한다.

 

일단 CharacterController 스크립트에서 Attack을 처리를 추가해준다.

public class TopDownCharacterController : MonoBehaviour
{
    // event : 외부에서 호출하지 못하게 막아주는 기능
    public event Action<Vector2> OnMoveEvent;
    public event Action<Vector2> OnLookEvent;
    public event Action OnAttackEvent;

    private float _timeSinceLastAttack = float.MaxValue; // 마지막으로 공격한 시간
    protected bool IsAttacking { get; set; } // Attack에 대한 프로퍼티

    protected virtual void Update() // virtual: 하위에서 상속받아 오버라이드에서 쓸 수 있도록 함
    {
        HandleAttackDelay();
    }

    private void HandleAttackDelay() // 공격에 대한 시스템 구현만
    {
        if (_timeSinceLastAttack <= 0.2f) // 나중에 수정
        {
            _timeSinceLastAttack += Time.deltaTime;
        }
        
        if (IsAttacking && _timeSinceLastAttack > 0.2f)
        {
            _timeSinceLastAttack = 0;
            CallAttackEvent();
        }
    }

    public void CallMoveEvent(Vector2 direction)
    {
        OnMoveEvent?.Invoke(direction); //?. : OnLookEvent이 Null이 아닐 때만 동작
    }

    public void CallLookEvent(Vector2 direction)
    {
        OnLookEvent?.Invoke(direction);
    }

    public void CallAttackEvent()
    {
        OnAttackEvent?.Invoke();
    }
}

 

 

CharacterInputController에 OnFire 키 입력값을 받으면 CharacterController의 HandleAttackDelay() 공격을 실행한다.

 private void OnFire(InputValue value)
 {
     IsAttacking = value.isPressed;
 }

 

이제 아까 만들어둔 실제 공격하는 스크립트를 작성한다.

public class TopDown : MonoBehaviour
{
    private TopDownCharacterController _controller;

    [SerializeField] private Transform projectileSpawnPosition;
    private Vector2 _aimDirection = Vector2.right;

    private void Awake()
    {
        _controller = GetComponent<TopDownCharacterController>();
    }

    // Start is called before the first frame update
    void Start()
    {
        _controller.OnAttackEvent += OnShoot;
        _controller.OnLookEvent += OnAim;
    }

    private void OnAim(Vector2 newAimDirection) // 에임 위치를 잡아준다
    {
        _aimDirection = newAimDirection;
    }

    private void OnShoot() // 실제 공격
    {
        CreateProjectile();
    }

    private void CreateProjectile()
    {
        Debug.Log("Fire");
    }
}


유니티에서 Prefabs 파일을 만든 뒤, 발사체 GameObject를 만들어서 파일에 넣어준다.

Prefab은 유니티에서 오브젝트들을 한 번 만들어놓고 재사용하여 관리하기 쉽게 만들어진 것으로 미리 설정된 값을 그대로 쓸 수 있다. 현재 복제가 되어 나왔기 때문에 원본과는 별개로 동작할 수 있다. 모든 인스턴스를 일괄적으로 업데이트 할 수 있고 다양한 바리에이션을 만들 수 있기 때문에 Prefab으로 대부분의 오브젝트를 만든다.

 

Player에서 임시로 발사체를 받은 뒤, 복사체가 잘 생성되나 테스트하기 위해 TopDownShooting 스크립트에 아래 코드를 추가한다.

public GameObject testPrefab; // 임시 코드

 private void CreateProjectile()
 {
     Instantiate(testPrefab, projectileSpawnPosition.position, Quaternion.identity);
 }

Instantiate()는 동적 생성으로 원본을 받은 것을 복제본으로 만든다. 그런데 현재 원본의 형태가 유니티 오브젝트 형식이다. 오브젝트 형식은 유니티에서 사용하는 대부분의 오브젝트를 최상위로 가지고 있기 때문에 Asset, Material 등 모두 사용할 수 있다.

 

 

오늘의 계획

1. 마지막 수정 및 점검

2. 발표

3. 개인 알고리즘 공부

 

  • 발표

이번 프로젝트의 발표자 분을 위하여 작성한 PPT를 바탕으로 대본을 작성하였고, 발표 시간을 체크하기 위해 리허설을 진행했다.

PPT도 대본도 다같이 작성하고 체크하여 이번 프로젝트로 배운 것과 협업에 대해 다시 한 번 생각해볼 수 있었다.

팀프로젝트가 처음인 팀원들과 팀장이 처음인 내가 서로 부족한 점을 채우며 협동할 수 있는 시간이었다.

<회의 사항>

  1. 오류 수정 및 텍스트 수정
  2. PPT 만들기

 

 

  • 수정 사항

오류

- 로비에서 메인화면으로 돌아갔다가 재실행이 되지 않는 현상- 장비 착용하여 추가된 옵션이 상태창에서만 적용되고 던전에서는 적용되지 않는 현상- 다음 스테이지로 넘어갈 때, 숫자가 증가하지 않는 현상

 

텍스트 및 설정

- 전사, 마법사 등 판타지 직업들을 현대 판타지 스토리에 맞춰 소방관, 군인 등으로 수정- 스테이지를 탑에 오르는 내용에서 지하로 내려가는 컨셉으로 수정

 

 

  • PPT

+ Recent posts