본문 바로가기

IT/유니티

19. 유니티 교육 (Sebastian Lague 강의-2DRunner #1)

1. 2D Platform을 만들어 보자


한2주를 공부하고 유니티로 2D겜을 만들라고 해보니까 아무래도 내맘 대로 잘 안됐다.

왜 이렇게 어렵지? 라고 생각한후 다시 우리의 선상님 세바스찬의 강의를 보기로 했다.

 딱 2개의 강의만 듣고 역시 난 코딩은 안하는게 낫다라는 자괴감이 들정도로 잘못된 길을 가고 있었고,

세바스찬의 방식은 정말 멋지다고 느껴질정도로 깨끗하고 빈틈없는 코드을 보여주었다.


충돌체크를 단순히 유니티의 rigidBody를 이용하는게 아니라 조금더 정밀하게 따로 설정해주었다.

수직과 수평으로 여러개의 레이저로 충돌체크하고,

점프높이과 최고점 도달시간을 이용해서 중력과 점프속도를 계산하는 것이 너무 인상적이였다.


이번에도 수학의 힘은 위대하다라는 당연한 결과에 도달했다.




소스코드는 다음과 같다.


Player2D.cs


using UnityEngine;

using System.Collections;


[RequireComponent (typeof(Controller2D))]

public class Player2D : MonoBehaviour {


//최대점프높이과 최고점에 도달하는 시간

public float jumpHeight = 4;

public float timeToJumpApex = .4f;

//땅에 있을대와 공중에 떠 있을대의 가속값

float accelerationTimeAirborne = .2f;

float accelerationTimeGrounded = .1f;

float moveSpeed = 6;


float gravity;

float jumpVelocity;

Vector3 velocity;

float velociityXSmoothing;


Controller2D controller;


// Use this for initialization

void Start () {

controller = GetComponent<Controller2D> ();


//점프 높이와 최고점 도달 시간을 이용해서, 중력과 점프 속도를 구한다.

gravity = -(2 * jumpHeight) / Mathf.Pow (timeToJumpApex, 2);

jumpVelocity = Mathf.Abs (gravity) * timeToJumpApex;

print ("Gravity: "+gravity + " Jump Velocity: " + jumpVelocity);

}

// Update is called once per frame

void Update () {


//지면에 붙어 있을대는 중력값을 초기와 한다. 축척되는것을 방지

if(controller.collisions.above || controller.collisions.below){

velocity.y = 0;

}


Vector2 input = new Vector2 (Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));


//스페이스를 누르고, 지면에 있을때만 점프한다.

if (Input.GetKeyDown (KeyCode.Space) && controller.collisions.below) {

velocity.y = jumpVelocity;

}


float targetVelocityX = input.x * moveSpeed;

//지면있을때와 공중에 떠있을때 움직임을 자연스럽게 하기위해서 smoothDamp를 이용한다.

velocity.x = Mathf.SmoothDamp (velocity.x, targetVelocityX, ref velociityXSmoothing, 

(controller.collisions.below)?accelerationTimeGrounded:accelerationTimeAirborne);

velocity.y += gravity * Time.deltaTime;

controller.Move (velocity * Time.deltaTime);

}

}



Mathf.SmoothDamp를 이용해서 지면에 있을때와 공중에 떠있을때의 감도를 조절하는 부분은 

아마 나는 영원히 궁리해도 답을 얻을수 없었을 것이다.



Controller2D.cs

using UnityEngine;

using System.Collections;


[RequireComponent (typeof(BoxCollider2D))]

public class Controller2D : MonoBehaviour {


//충돌체크할 레이어

public LayerMask collisionMask;


//자연스럽게 보이기위해서 실제 크기보다 조금 안쪽에서 충돌체크한다.

const float skinWidth = .015f;

//수평과 수직 충돌체크할 갯수

public int horizontalRayCount = 4;

public int verticalRayCount = 4;


float horizontalRaySpacing;

float verticalRaySpacing;


BoxCollider2D collider;

RaycastOrigins raycastOrigins;

public CollisionInfo collisions;


// Use this for initialization

void Start () {

collider = GetComponent<BoxCollider2D> ();

//충돌체크할때 얼마나 떨어저서 할지 결정

CalculateRaySpacing ();

}


public void Move(Vector3 velocity){

//물리체크할 범위를 계산한다.

UpdateRaycastOrigins ();

//충돌체크결과 초기화

collisions.Reset ();


//좌우충돌체크를 한다음에 상하충돌체크를 해야한다.

//왜냐하면 VerticalCollisions함수에서 rayOrigin에 velocity.x를 더하기때문이다. 

if(velocity.x != 0){

HorizontalCollisions (ref velocity);

}


if(velocity.y != 0){

VerticalCollisions (ref velocity);

}


  transform.Translate (velocity);

}

//수평방향으로 정해진 갯수만큼의 레이저를 쏘아서 충돌체크를 한다.

void HorizontalCollisions(ref Vector3 velocity){

float directionX = Mathf.Sign (velocity.x);

float rayLenth = Mathf.Abs (velocity.x) + skinWidth;

for(int i = 0; i < horizontalRayCount; i++){

Vector2 rayOrigin = (directionX == -1) ? raycastOrigins.bottomLeft : raycastOrigins.bottomRight;

rayOrigin += Vector2.up * (horizontalRaySpacing * i);

RaycastHit2D hit = Physics2D.Raycast (rayOrigin, Vector2.right * directionX, rayLenth, collisionMask);

Debug.DrawRay (rayOrigin, Vector2.right* directionX *rayLenth, Color.red);

//만약 다음이동에 충돌한다면 충돌하는 곳까지만 이동한다.

if(hit){

velocity.x = (hit.distance - skinWidth) * directionX;

rayLenth = hit.distance;


collisions.left = directionX == -1;

collisions.right = directionX == 1;

}


}

}


//수직방향으로 정해진 갯수만큼의 레이저를 쏘아서 충돌체크를 한다.

void VerticalCollisions(ref Vector3 velocity){

float directionY = Mathf.Sign (velocity.y);

float rayLenth = Mathf.Abs (velocity.y) + skinWidth;

for(int i = 0; i < verticalRayCount; i++){

Vector2 rayOrigin = (directionY == -1) ? raycastOrigins.bottomLeft : raycastOrigins.topLeft;

rayOrigin += Vector2.right * (verticalRaySpacing * i + velocity.x);

RaycastHit2D hit = Physics2D.Raycast (rayOrigin, Vector2.up * directionY, rayLenth, collisionMask);

Debug.DrawRay (rayOrigin, Vector2.up* directionY *rayLenth, Color.red);


if(hit){

velocity.y = (hit.distance - skinWidth) * directionY;

rayLenth = hit.distance;


collisions.below = directionY == -1;

collisions.above = directionY == 1;

}


}

}



void UpdateRaycastOrigins(){

Bounds bounds = collider.bounds;

bounds.Expand (skinWidth*-2);


raycastOrigins.bottomLeft = new Vector2 (bounds.min.x, bounds.min.y);

raycastOrigins.bottomRight = new Vector2 (bounds.max.x, bounds.min.y);

raycastOrigins.topLeft = new Vector2 (bounds.min.x, bounds.max.y);

raycastOrigins.topRight = new Vector2 (bounds.max.x, bounds.max.y);

}


void CalculateRaySpacing(){

Bounds bounds = collider.bounds;

bounds.Expand (skinWidth*-2);


horizontalRayCount = Mathf.Clamp (horizontalRayCount, 2, int.MaxValue);

verticalRayCount = Mathf.Clamp (verticalRayCount, 2, int.MaxValue);


horizontalRaySpacing = bounds.size.y /  (horizontalRayCount - 1);

verticalRaySpacing = bounds.size.x /  (verticalRayCount - 1);

}



struct RaycastOrigins{

public Vector2 topLeft, topRight;

public Vector2 bottomLeft, bottomRight;

}


public struct CollisionInfo{

public bool above, below;

public bool left, right;


public void Reset(){

above = below = false;

left = right = false;

}


}

}





위의 그림을 보면, 최고점프위치와 최고점도달시간을 이용해서 중력과 점프속도를 구하는 식을  구할수 있다.




Trigger의 종류

OnTriggerEnter

OnTriggerStay

OnTriggerExit

2d의경우에는 뒤에 2D가 붙는다.



Tip. Mathf.Sign(float f)

f가 0이거나 양수이면 1을 음의값이면 -1을 리턴한다.

결국 1이나 -1을 리턴하게 된다.


Tip. Mathf.Abs(float f)

음의 값이던 양의 값이던 무조건 절대값을 리턴한다.

여기서 절대값은 양수를 의미한다.


Tip. 이미지 스프라이트 시트 만들기

packing tag을 이용해서 만들수 있다.

태그 이름이 같은 것들을 모아서 스프라이트 시트를 만들어 준다.

window의 sprite Packer를 선택해서 팩킹한다.


Tip. 이미지의 pivot변경하기

이미지의 인스펙터를 보면 pivot을 정할수 있다.