벡터의 내적과 외적, 행렬에 대해 배우며 게임 수학 부분을 마무리하고, 새롭게 플랫포머 게임 제작에 들어갔다.
일반적인 RPG 게임에서 필요한 타일맵, 콤보 기술 등 다양한 스킬들을 배울 수 있을 것 같아서 기대된다. 개인적으로 하는 프로젝트에서 고민 됐던 부분들도 배울 수 있을 것 같다.
벡터
내적
두 벡터의 관계
|a||b|cos
Vecotr3.Dot(a, b)
public Vector3 vecA = new Vector3(1, 0, 0);
public Vector3 vecB = new Vector3(0, 1, 0);
void Start()
{
float result = Vector3.Dot(vecA, vecB);
Debug.Log($"벡터의 내적 {result}"); // 0
float angle = Vector3.Angle(vecA, vecB);
Debug.Log($"벡터의 끼임각 {angle}");
}
외적
두 벡터의 수직 벡터
|a||b|sin
Vector3.Cross(a, b)
public Vector3 vecA = new Vector3(1, 0, 0);
public Vector3 vecB = new Vector3(0, 1, 0);
void Start()
{
Vector3 result = Vector3.Cross(vecA, vecB);
Debug.Log($"벡터의 외적 {result}"); // (0, 0, 1)
}
반사각
Vector3.Reflect(입사각, 법선 벡터)
Vector3 reflect = Vector3.Reflect(vecA, vecB);
Debug.Log($"반사각 {reflect}");
선형 이동 (Lerp)
두 값 사이를 선형적으로 보간 → Linear Interpolation
// 0부터 10까지 천천히 움직이게
Math.Lerp(0, 10, 0.5f);
// Lerp(현재 위치, 목표 위치, 이동 비율)
transform.position = Vector3.Lerp(transform.position, target.position, smoothValue);
행렬 실습
행렬을 이용해 타일을 까는 코드를 작성해보았다.
public class SetTile : MonoBehaviour
{
public GameObject tilePrefab;
public int rows = 5, cols = 5;
IEnumerator Start()
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
var pos = new Vector3(i, 0, j);
GameObject tile = Instantiate(tilePrefab, pos, Quaternion.identity);
Renderer rend = tile.GetComponent<Renderer>();
if ( (i+j) % 2 == 0)
rend.material.color = Color.black;
else
rend.material.color = Color.white;
yield return new WaitForSeconds(0.1f);
}
}
}
}
터렛 생성
깔려지는 타일을 클릭하면 해당 칸에 터렛이 생성되는 코드이다.
public class Tile : MonoBehaviour
{
public GameObject turretPrefab;
void OnMouseDown()
{
Instantiate(turretPrefab, transform.position, Quaternion.identity);
}
}
결과
버튼
UI 버튼 클릭 시 터렛인덱스가 변경되어 다른 터렛을 설치할 수 있도록 하였다.
public class SetTile : MonoBehaviour
{
public Button[] buttons;
public static int turretIndex;
void Awake()
{
buttons[0].onClick.AddListener(()=>ChangeIndex(0));
buttons[1].onClick.AddListener(()=>ChangeIndex(1));
buttons[2].onClick.AddListener(()=>ChangeIndex(2));
buttons[3].onClick.AddListener(()=>ChangeIndex(3));
buttons[4].onClick.AddListener(()=>ChangeIndex(4));
}
void ChangeIndex(int index)
{
turretIndex = index;
}
}
public class Tile : MonoBehaviour
{
public GameObject[] turretPrefabs;
void OnMouseDown()
{
Instantiate(turretPrefabs[SetTile.turretIndex], transform.position, Quaternion.identity);
}
}
클로져(Closure) 이슈
클로저(Closure)란, 외부 변수나 필드와 같은 '환경'을 저장하고 있는 함수이다.
아래 코드처럼 for문의 변수 i를 람다식에 사용할 경우 클로져로 인식하기 때문에 의도한 결과대로 동작하지 않는다.
따라서 이를 해결하기 위해 새로운 변수 j를 만들어서 대입하는 방식을 쓸 수 있다.
buttons[0].onClick.AddListener(()=>ChangeIndex(0));
buttons[1].onClick.AddListener(()=>ChangeIndex(1));
buttons[2].onClick.AddListener(()=>ChangeIndex(2));
buttons[3].onClick.AddListener(()=>ChangeIndex(3));
buttons[4].onClick.AddListener(()=>ChangeIndex(4));
// 위처럼 실행되지 않음
for (int i=0; i<5; i++)
buttons[i].onClick.AddListener(()=>ChangeIndex(i));
// 해결
for (int i=0; i<5; i++)
{
int j = i;
buttons[j].onClick.AddListener(()=>ChangeIndex(j));
}
결과
플랫포머 게임
플레이어 이동
항상 해왔던 키보드 입력을 받는 플레이어 이동을 구현했다.
public class KnightController_Keyboard : MonoBehaviour
{
Animator animator;
Rigidbody2D knightRb;
private Vector3 inputDir;
private bool isGround;
[SerializeField]
private float moveSpeed = 3f;
[SerializeField]
private float jumpPower = 1f;
void Start()
{
animator = GetComponent<Animator>();
knightRb = GetComponent<Rigidbody2D>();
}
void Update()
{
InputKeyboard();
}
void FixedUpdate()
{
Move();
}
void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.CompareTag("Ground"))
{
animator.SetBool("isGround", true);
isGround = true;
}
}
private void OnCollisionExit2D(Collision2D other)
{
if (other.gameObject.CompareTag("Ground"))
{
animator.SetBool("isGround", false);
isGround = false;
}
}
void InputKeyboard()
{
// GexAxis : 서서히 0이 됨
// GexAxisRaw : -1, 0, 1이 나오기때문에 바로 멈춤
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
inputDir = new Vector3(h, v, 0);
Jump();
SetAnimation();
}
void Jump()
{
if (Input.GetKeyDown(KeyCode.Space) && isGround)
{
animator.SetTrigger("Jump");
knightRb.AddForceY(jumpPower, ForceMode2D.Impulse);
}
}
void Move()
{
if (inputDir.x != 0)
knightRb.linearVelocityX = inputDir.x * moveSpeed;
}
void SetAnimation()
{
if (inputDir.x != 0)
{
var scaleX = inputDir.x > 0 ? 1 : -1;
transform.localScale = new Vector3(scaleX, 1, 1);
animator.SetBool("isRun", true);
}
else
{
animator.SetBool("isRun", false);
}
}
}
조이스틱
터치스크린
UI 패널을 만들고 Event Trigger 컴포넌트를 추가한다.
이벤트들을 추가하면 다양한 UI 이벤트를 인식하는데, Image 컴포넌트의 Raycast Target이 켜져있기 때문이다.