벡터 , 인풋시스템

이준호·2023년 11월 29일
0
post-custom-banner

📌 벡터(Vector)

➔벡터의 특징

벡터의 가장 큰 특징 : 크기와 방향을 가진다.

  • 크기랑 방향만 있고 시작과 끝점의 정보가 없다. 이 말은 즉 벡터를 어디든 이동시킬 수 있다는 뜻.
    위 사진의 두 벡터는 이동시켰을 때 완벽히 겹치시게 똑같은 벡터다. ( 크기 방향 각도 다 같음 )

  • 시작점을 원점으로 고정(0,0) 시킨다면 많은 변화가 생긴다. 도착점이 (2,3) 크기와 방향만 있던 이 벡터는 이제 (2,3) 이라는 벡터로 표현이 가능.

  • 시작점을 정해줬다는 이유 하나만으로 벡터를 숫자로 표현이 가능해진거다. (2차원, 3차원, 4차원 벡터 모두 동일)







➔벡터의 덧셈

  • 벡터의 덧셈. 각각의 좌표를 더해주면 두 벡터가 더해진 벡터가 나온다.

➔벡터의 뺄셈

  • A벡터에서 B벡터를 빼면 (-2, 2) 인데 옮겨서 보면 정확히 B벡터에서 A벡터로 향하는 벡터(방향, 길이, 거리)가 나온다.

  • 몬스터(B Vector)가 플레이어(A Vector)에게로 향하는 방향과, 떨어진 거리, 길이까지 알수가 있다.

➔벡터의 곱셈

  • 벡터의 곱셈은 방향은 바꾸지 않고 크기만 변화시킨다.

  • 방향은 같지만 크기가 다르면 1초동안을 기준이라 하면 나아가는, 이동하는 거리다 달라진다.

  • 벡터의 크기가 커지면 커질수록 속도가 빠르다고 볼 수 있다.

  • 벡터에서 벡터(Player벡터 - Monster벡터)를 빼면 그 벡터로 향하는 방향과 크기가 나오는데 문제는 Monster가 느릴수도 있고 빠를수도 있다.

  • 문제는 이 좌표값으로 계산하게 되면 항상 동일한 벡터, 크기까지 한번에 구해지기 때문에 느리거나 빠른걸 구분해서 계산하기가 힘들다.

  • 그래서 가장 간단한 방법은 방향은 유지하고 크기를 전부 1로 바꿔주게 되면 계산할 때 용이하다.
    (크기가 1이면 몬스터가 갖고있는 스피드 수치를 곱하기만 하면 그 스피드 수치에 따라서 벡터의 크기가 달라지고 속도가 달라질 것 이다.)

  • 1로 바꿔주는 방법은 정규화(Normalized)

  • 그렇게 길이가 1이된. 크기가 1짜리인 벡터는 단위벡터 라고 한다. (방향만 남아있음)

  • 정규화(Normalized)를 하면 크기는 사라지고 방햔만 남는데 magnitude를 하면 방향은 사라지고 크기만 남는다.

  • 벡터 = 크기 + 방향
    방향만 남음 Normalized
    크기만 남음 magnitude














📌 Input System

Legacy Input System

void Update() // 물리 연산은 FixedUpdate에서 하는게 좋음
{
// 캐릭터의 이동
  float x = Input.GetAxis("Horizontal"); // 수평(Horizontal) - 오른쪽D(1) 왼쪽 A(-1)
  float y = Input.GetAxisRaw("Vertical"); // 수직(Vertical) - 위W(1) 아래S(-1)

  transform.position += (new Vector3(x ,y)).normalized * Time.deltaTime * speed;
  // normalized로 정규화 단위벡터로 만들어 주어야 대각선으로 움직여도 속도가 같다. (피타고라스를 보면 대각선은 값이 더 큼 )
  // Time.deltaTime 으로 프레임 시간을 쪼개줘야 모든 컴퓨터에도 같은 속도 적용 가능

  transform.position += (new Vector3(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"))).normalized * Time.deltaTime * speed;
  // 위 처럼 줄일수도 있다. 권장x

// 마우스의 움직임
// 마우스 위치에 따른 캐릭터 스프라이트의 반전 
  Vector3 mousePos = Input.mousePostion;
  if(mousePos.x < Screen.widhth / 2)
  {
  	// 캐릭터를 뒤집는다.
    sprite.flipX = true;
  }
  else
  {
    sprite.flipX = false;
  }
  // mousePos가 마우스의 취치를 받아와서 Screen.width(스크린 상의 넓이)의 절반 즉, 화면 왼쪽이면 캐릭터 x반전 아니면 그대로.
  
  sprite.flipX = (Input.mousePosition.x < Screen.width / 2) ? true : false;
  // 위처럼 줄일수 있다. 권장x

// 활의 움직임
// 마우스 위치에 맞는 스프라이트의 회전  
// Input.mousePosition은 스크린상의 좌표 ( 게임 오브젝트들은 월드좌표에 존재 )
bow.LookAt(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector3.forward);
// LookAt은 그쪽을 바라보겠다는 제공 메서드
// ScreenToWorld : 스크린상의 좌표를 월드좌표로 바꿔줌
// Vector3.forward : 2D에선 Z축을 말함. 2D에서 원의 움직임의 회전은 z축을 고정하고 회전해야함
}

GetAxis : 0부터 시작해서 올라감

GetAxisRaw : 바로 1 or -1로 시작

테스트 할 때에나 취직 할 때에는 위처럼 코드를 줄이거나 레거시 방식이 좋지 않다. 빠르게 잠깐 시험을 볼 때에나 정식으로 만들기 전 테스트를 할 때에만 사용을 권장.

또한 한 스크립트에 3가지의 기능이 들어가 솔리드원칙의 단일 원칙이 지켜지지 않는다. 입력,

절차지향이 아니라 객체지향을 연습하자.

➔ Input.GetAxis

  • input.GetAxis는 유니티의 입력 시스템에서 사용되는 메서드이다.

  • 이 메서드는 입력 축의 값을 반환한다.

  • 입력 축은 주로 키보드나 조이스틱과 같은 입력 장치의 입력을 나타낸다.

  • GetAxis 메서드는 -1부터 1 사이의 값을 반환하는데, 입력 장치의 움직임에 따라 해당 값이 변경된다.

  • 값이 0에 가까울수록 입력이 없거나 중립 상태를 나타내며, 양수 값은 양 방향 입력을, 음수 값은 음 방향 입력을 나타낸다.

  • GetAxis 메서드는 주로 플레이어의 움직임, 회전, 점프 등을 처리하는 데 사용된다.

  • 예를 들어, 수평 이동을 처리하는 경우, 좌우 화살키의 입력에 따라 Input.GetAxis("Horizontal")을 사용하여 좌우 방향의 값을 얻을 수 있다.

  • 이 값은 플레이어의 이동 속도나 회전 속도와 같은 변수에 적용하여 게임 오브젝트를 제어할 수 있다.

➔ Time.deltaTime

  • 이전 프레임부터 현재 프레임까지의 경과 시간을 나타낸다.

  • deltaTime은 게임의 프레임 속도에 상관없이 일정한 시간 간격으로 동작하는 게임을 만들 때 유용하게 사용된다.

  • 주로 움직임, 애니메이션, 물리 시뮬레이션 등에서 시간에 따른 변화를 조정하는 데 사용된다.

  • 예를 들어, transform.Translate(Vector3.forward speed Time.deltaTime)과 같이 사용하면 프레임 속도에 관계없이 speed만큼의 일정한 이동 속도를 보장할 수 있다.

  • Time.deltaTime은 초 단위의 값을 반환하며, 1초에 1에 가까운 값을 가진다.

  • 게임의 로직이 매 프레임마다 일정한 속도로 실행되어야 할 때, deltaTime을 이용하여 이동, 회전, 애니메이션 등의 연산에 일정한 시간 간격을 적용할 수 있다.

  • 이를 통해 게임이 일정한 속도로 동작하고, 다양한 기기나 환경에서도 일관된 경험을 제공할 수 있다.













New Input System

New Input System의 핵심 개념들

  • Input Action : 입력 행동을 정의한다. 예를 들어 "점프", "공격" 등의 행동을 정의하고, 이러한 행동을 트리거하는 키 또한 버튼을 지정할 수 있다.
  • Input Action Asset : 여러 개의 입력 행동을 그룹화하는 방법이다. 이를 통해 재사용 가능한 입력 설정을 만들어 게임 내의 다른 캐릭터나 메뉴에 적용할 수 있다.
  • Player Input Component : Unity의 New Input System에 추가된 새로운 컴포넌트로, 자동으로 입력 행동을 처리하고 해당 게임 오브젝트에 메세지를 보낸다.

New Input System의 장점

  • Cross-Platform Compatibillity : New Input System은 다양한 플랫폼 입력 장치에 대해 일관된 방식으로 작동한다.
  • Rebinding : 플레이어가 게임 내에서 자신의 입력 설정을 변경할 수 있도록 지원한다.
  • Multiplayer Support : 여러 플레이어가 동일한 장치에서 게임을 플레이하거나, 각각의 장치에서 게임을 플레이할 때 입력을 쉽게 처리할 수 있다.

➔ Package 추가

  • Window → Package Manager → Unity Register → Input System → Install

➔ New Input System 준비

  • Create → Input Action 클릭 → Top Down Controller 2D로 수정

➔ Input Action 수정

  • InputAction 더블클릭
  • No Control Schemes → Add Control Scheme
  • 이름 수정 → + 버튼 → Keyboard 와 Mouse 추가
  • Action Maps → + → Player
  • Move Action 수정 → Add Up Down Left Right Composite 추가 → WASD 설정
  • Look Action 수정
  • Fire Action 수정

➔ TopDownChacterController

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class TopDownCharacterController : MonoBehaviour
{
	// evnet : 외부에서 호출하지 못하게 막음
    public event Action<Vector2> OnMoveEvent;
    public event Action<Vector2> OnLookEvent;


    public void CallMoveEvent(Vector2 direction)
    {
    	// OnMoveEvent가 Null이 아니라면?. 실행(Invoke)
        OnMoveEvent?.Invoke(direction);
    }

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

}

➔ PlayerInputController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerInputController : TopDownCharacterController
{
    private Camera _camera;
    private void Awake()
    {
    	// 카메라의 클래스에서 메인이란? 지금 이 씬에 존재하는 태그가 메인카메라인 얘를 찾아온다.
        _camera = Camera.main;
    }

	// SendMessage방식 Move, Look, Fire 라는 3가지 Action을 만들어놨는데 그 앞에다 On을 붙이면 그 애들이 실행됐을 때 돌려받는 함수를 만들어 주는 것 이다.
    public void OnMove(InputValue value)
    {
        // Debug.Log("OnMove" + value.ToString());
        Vector2 moveInput = value.Get<Vector2>().normalized;
        CallMoveEvent(moveInput);
    }

    public void OnLook(InputValue value)
    {
		// Mouse의 좌표 ( Screen상의 좌표 )
        Vector2 newAim = value.Get<Vector2>();
        // Screen의 좌표(newAim) -> World좌표로 변환
        Vector2 worldPos = _camera.ScreenToWorldPoint(newAim);
        // 벡터에서 벡터를 빼면 그 벡터를 향하는 방향,값이 나온다.
        newAim = (worldPos - (Vector2)transform.position).normalized;
		
        // .magnitude는 그 힘을 의미 위에서 normalized를 했기에 1이 나올거다.
        if (newAim.magnitude >= .9f)
        {
            CallLookEvent(newAim);
        }
    }

    public void OnFire(InputValue value)
    {
        Debug.Log("OnFire" + value.ToString());
    }
}

➔ TopDownMovement

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TopDownMovement : MonoBehaviour
{
    private TopDownCharacterController _controller;

    private Vector2 _movementDirection = Vector2.zero;
    private Rigidbody2D _rigidbody;

    private void Awake()
    {
    	// Player에게 달려있는 PlayerInputController.cs Component의 상위 부모가 TopDownCharacterController
        _controller = GetComponent<TopDownCharacterController>();
        _rigidbody = GetComponent<Rigidbody2D>();
    }

    private void Start()
    {
        _controller.OnMoveEvent += Move;
    }

    private void FixedUpdate()
    {
        ApplyMovment(_movementDirection);
    }

    private void Move(Vector2 direction)
    {
        _movementDirection = direction;
    }

    private void ApplyMovment(Vector2 direction)
    {
        direction = direction * 5;

        _rigidbody.velocity = direction;
    }
}

➔ TopDownAimRotation

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TopDownAimRotation : MonoBehaviour
{
    [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)
    {
      /* Mathf.Atan2 (아크탄젠트를 구하는 메서드) : 어떤 벡터의 y, x를 가지고 아크탄젠트르 하면 그 사이의 세타값이 나온다. ( 벡터의 각도를 구는 것 )
         Mathf.Atan2(direction.y, direction.x) 를 하여 "세타"값이 나오면 "파이값. 라디안 값이 나온다.(0 ~ 3.14)" 
         그 라디안값을 디그리값(0 ~ 180도) 으로 바꿔주는 값(Mathf.Rad2Deg)을 곱해준다. */
        
        //입력으로 Vector2를 받는다
		//Mathf.Atan2를 하여 그 벡터의 각도를 얻는다 ( 라디안 값) ( 라디안와 각도는 다름) ( kg과 파운드처럼 )
		//Mathf.Rad2Deg를 하여 라디안을 각도로 바꿔준다.
        float rotZ = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
        
        // flipY : SpriteRenderer클래스가 제공하는 Y축을 기준으로 뒤집는 코드
        // (X축이 기준) 절댓값 90도를 기준으로  넘어가면 무기는 위아래 전환(flip.Y), 캐릭터는 오른쪽왼쪽 전환(flip.X)
        armRenderer.flipY = Mathf.Abs(rotZ) > 90f;
        characterRenderer.flipX = armRenderer.flipY;
        // Rotation이라는 회전값은 Quaternion이라는 4원소 값을 쓴다. 그래서 일반적으로 사용하기에는 어렵다.
        // 그래서 쓰기쉬운 디그리계도(0 ~ 360도) 라디안값이 아닌 디그리도를 사용하는 얘로 만들어줌
        // rotZ는 Euler각도이다.
        armPivot.rotation = Quaternion.Euler(0, 0, rotZ);
    }
}

Unity UI 참고 주소

profile
No Easy Day
post-custom-banner

0개의 댓글