벡터의 가장 큰 특징 : 크기와 방향을 가진다.
시작점을 원점으로 고정(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
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는 유니티의 입력 시스템에서 사용되는 메서드이다.
이 메서드는 입력 축의 값을 반환한다.
입력 축은 주로 키보드나 조이스틱과 같은 입력 장치의 입력을 나타낸다.
GetAxis 메서드는 -1부터 1 사이의 값을 반환하는데, 입력 장치의 움직임에 따라 해당 값이 변경된다.
값이 0에 가까울수록 입력이 없거나 중립 상태를 나타내며, 양수 값은 양 방향 입력을, 음수 값은 음 방향 입력을 나타낸다.
GetAxis 메서드는 주로 플레이어의 움직임, 회전, 점프 등을 처리하는 데 사용된다.
예를 들어, 수평 이동을 처리하는 경우, 좌우 화살키의 입력에 따라 Input.GetAxis("Horizontal")을 사용하여 좌우 방향의 값을 얻을 수 있다.
이 값은 플레이어의 이동 속도나 회전 속도와 같은 변수에 적용하여 게임 오브젝트를 제어할 수 있다.
이전 프레임부터 현재 프레임까지의 경과 시간을 나타낸다.
deltaTime은 게임의 프레임 속도에 상관없이 일정한 시간 간격으로 동작하는 게임을 만들 때 유용하게 사용된다.
주로 움직임, 애니메이션, 물리 시뮬레이션 등에서 시간에 따른 변화를 조정하는 데 사용된다.
예를 들어, transform.Translate(Vector3.forward speed Time.deltaTime)과 같이 사용하면 프레임 속도에 관계없이 speed만큼의 일정한 이동 속도를 보장할 수 있다.
Time.deltaTime은 초 단위의 값을 반환하며, 1초에 1에 가까운 값을 가진다.
게임의 로직이 매 프레임마다 일정한 속도로 실행되어야 할 때, deltaTime을 이용하여 이동, 회전, 애니메이션 등의 연산에 일정한 시간 간격을 적용할 수 있다.
이를 통해 게임이 일정한 속도로 동작하고, 다양한 기기나 환경에서도 일관된 경험을 제공할 수 있다.
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 : 여러 플레이어가 동일한 장치에서 게임을 플레이하거나, 각각의 장치에서 게임을 플레이할 때 입력을 쉽게 처리할 수 있다.
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);
}
}
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());
}
}
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;
}
}
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);
}
}