자 이번 포스팅에서는 장애물들을 설정하고 마무리하도록 하겠습니다.


장애물을 배치하는 것은 간단합니다.

하이라키 탭에서  3D Object > Cube  이름을 Obstacle로 설정해 주시고, 프로젝트 탭에서  Create > C# Script  선택, 이름을 역시 Obstacle로 설정하겠습니다.

Obstacle에 들어갈 스크립트는 아래와 같습니다.


1
2
3
4
5
6
7
8
9
10
11
public class Obstacle : MonoBehaviour {
    private int hp = 1;
 
    void OnDamage(int damage){
        hp = hp - damage;
        if (hp <= 0) {
            Destroy (gameObject);
            return;
        }
    }
}
tistory.hanorange.com
cs


Obstacle 오브젝트는 1 만큼의 hp를 가지고 있으며 피격시 OnDamage함수가 실행되어 damage만큼 hp가 깎이게 됩니다.

hp가 0이하가 되면 큐브가 없어지게 되죠.


OnDamage 함수는 저번시간에 작성했던 Bullet 오브젝트의 컴포넌트인 Fire.cs 스크립트에 있습니다. 잠깐 가져와 볼까요?


1
2
3
if(Physics.Raycast(레이캐스트 설정)){
    hit.collider.SendMessage("OnDamage"1, 메세지 옵션);
}
tistory.hanorange.com
cs

Raycast로 광선을 쏘았을 때 맞는 오브젝트가 있다면 그 오브젝트가 hit되었을 때 OnDamage함수를 발동시키라는 내용입니다. 데미지는 명시된 1 만큼 전달됩니다.

이제 Obstacle에 스크립트와 물리적 요소를 추가하기 위한 Rigidbody를 넣어주시고, Obstacle의 크기를 2, 2, 2로 키워주겠습니다.


Obstacle 을 배치할 시간입니다. 하지만 배치하기에는 땅이 너무 좁네요. 

Plane의 크기를 4, 4, 4로 늘려줍시다.

이제 하이라키 탭에서 Obstacle 을 Plane의 자식으로 넣어서, 맵과 함께 움직이도록 설정하겠습니다.

그 뒤에 Obstacle을 선택한 뒤 Ctrl+c, Ctrl+v 로 복붙해서 맵에 10개 정도만 만들어주겠습니다. ( Ctrl+d 를 누르면 더 빨라요! )



잘 따라오셨나요? 


자, 실행해 봅시다!



정상적으로 잘 플레이되네요.

여기까지 잘 따라오셨으면 게임은 완성입니다! 수고하셨습니다. 👏👏👏

오류가 나거나, 이해가 안되는 부분은 댓글 달아주시면 답해드리도록 하겠습니다!



※ 다음 포스팅에서는 외전으로 어떻게 두명이서 플레이할 수 있는지에 대해 알아보도록 하겠습니다.

계속 개발해 보겠습니다. 

먼저 지금까지 한 Scene을 저장하겠습니다. 하이라키 탭에서  Ctrl + s  를 눌러서 Main이라고 저장해줍니다.


슈팅게임이기 때문에 플레이어가 총을 들고있어야 하겠죠? 하이라키에서  3D Object > Cube  를 두개 만들어서 모형을 적당히 잡아줍시다.

큰 큐브는 Gun으로 손잡이는 Handle로 Gun안에 자식으로 넣어줍니다. 만들어진 Gun은 또한 Player의 자식으로 넣어줍니다.

Material 도 생성해서 색깔을 입혀주시면 Gun 모양까지 완성입니다.



이제 총알과 총알을 발사하는 C# 스크립트를 제작해보겠습니다.

총알은  3D Object > Sphere  의 크기를 0.2 정도로 한 작은 구로 만들어 주시구, 이름은 Bullet 입니다. 앞과 마찬가지로 Material을 만들어서 총알에 색깔을 입혀줍니다. Bullet Color정도로 이름을 설정해주면 되겠네요.


저는 총알이 빛나게 해주고 싶기 때문에  Bullet에서  Add Component > Light  를 넣어주었습니다. 선택 사항이에요!


이제 Project에서  Create > C# Script  로 스크립트를 만들어 줍시다. 이름은 Fire로 설정해주세요.

코드는 다음과 같습니다


public class Fire : MonoBehaviour {
    public float speed = 8f;
    private RaycastHit hit;
    
    // Update is called once per frame
    void Update () {
        if(Physics.Raycast(transform.position, transform.forward, out hit, speed * Time.deltaTime){
 
            hit.collider.SendMessage("OnDamage"1, SendMessageOptions.DontRequireReceiver);
 
            Destroy(gameObject);
            return;
        }
            transform.Translate(00, speed * Time.deltaTime);
    }
}
 
tistory.hanorange.com
cs


speed 는 총알이 날아가는 속도입니다. public으로 선언해주었기 때문에 inspector창에서 직접 수정이 가능하죠.


눈여겨볼 코드는 RaycastHit 메소드입니다. 

만약 오브젝트에 Collider를 붙여서 CompareTag 메소드를 사용하게 되면, 총알처럼 빠른 오브젝트는 프레임과 프레임 사이를 순식간에 지나가게 되어 업데이트 적용을 받지 못해 물체의 충돌을 감지하지 못하는 경우가 발생할 수 있습니다.


그러나 RaycastHit를 사용하게되면 보이지 않는 빔이 속성값으로 넣어준 방향으로 '한 프레임' 정도를 먼저 쬐면서 지나가게 됩니다. 이 빔에 맞는 오브젝트의 정보를 반환할 수도 있죠. 

즉, Update문에 있는 Physics.Raycast(transform.position, transform.forward, out hit, speed * Time.deltaTime

코드는 시작점과 방향, 빔과 속도를 지정해준 것입니다.


hit.collider.SendMessage("OnDamage"1, SendMessageOptions.DontRequireReceiver);

이 코드에서는 총알을 맞은 오브젝트에게 "OnDamage" 함수를 실행하라고 알려줍니다. 1 만큼 데미지를 주고, DontRequireReceiver 메소드는 "OnDamage" 함수가 없는 객체는 수행할 필요가 없다는 뜻입니다. 


작성이 완료되었으면 Bullet의 inspector에 Fire 스크립트를 붙여줍시다.

이제 총알은 게임이 시작되면 클릭할때마다 생성되어 발사될것이기 때문에 Project탭으로 드래그해서 프리팹으로 만들어 주시고, 하이라키에서는 지워줍니다.



총알을 발사할 스크립트를 만들 차례입니다.

하이라키 탭에서  Create > Create Empty  를 선택해서 Fire Position이라고 이름을 바꿔 주시구, 위치를 다음과 같이 총구 앞으로 배치해줍시다.

총알이 발사될 위치를 잡아주는 과정입니다.



Project 탭에서  Create > C# Script , 이름은 Gun으로 설정해줍니다.


public class Gun : MonoBehaviour {
    public GameObject bulletPrefab;
    public Transform firePosition;
 
    public float timeBetFire = 0.6f;
    private float lastFireTime = 0;
 
    // Update is called once per frame
    void Update () {
        if (Input.GetButton("Fire1")){
            if(Time.time >= lastFireTime + timeBetFire) {
                Fire ();
            }
        }    
    }
 
    void Fire(){
        lastFireTime = Time.time;
        Instantiate (bulletPrefab, firePosition.position, firePosition.rotation);
    }
}
 
tistory.hanorange.com

cs


먼저 프리팹으로 만든 Bullet과 총알이 발사될 위치인 firePosition을 가져와줍니다.


발사 간격도 설정해 주어야겠죠? timeBetFire 에서 설정한 쿨타임은 0.6초입니다.

lastFireTime 은 총알이 발사되었을 때 Time.time 으로 현재 시간을 저장하기 때문에, 업데이트문에서 버튼을 눌렀을 때 현재 시간이 마지막에 발사된 시간과 쿨타임을 합친 시간보다 크다면 총알이 발사되는 원리입니다.


Instantiate 메소드는 프리팹을 firePosition의 위치와 각도로 복사해 가져온다는 내용의 코드입니다.

이제 Gun의 inspector에서 Gun 스크립트를 붙여주시고, BulletPrefab과 Fire Position에 맞는 오브젝트를 넣어줍시다.



이제 카메라 뷰를 적당히 설정해주고, 구동해 봅시다!



총알까지 잘 발사가 되네요. 완성이 머지않았습니다.

저번 포스팅에 이어서, 3주차때 진행한 내용을 포스팅하겠습니다.


이번에 제작한 것은 위에서 아래로 내려다보는 탑다운 뷰 게임입니다. 로컬에서 2명이 동시에 플레이할 수 있죠.

플레이어는 맵의 끝과 끝에서 시작되고, 중간의 장애물들을 파괴하면서 서로를 잡아야 합니다.


먼저 유니티 프로젝트를 3D로 시작해 줍니다.


기본 배치로 하이라키 탭에서  3D Object > Plane  으로 땅을 깔아 줍시다.


그 후에  3D Object > Capsule  로 주인공의 몸체를 만듭니다.

또한 주인공의 앞뒤를 구분하기 위해 간단하게 선글라스를 씌워 주겠습니다. 

주인공 캡슐의 이름을 Player로 바꿔주시고, Player를 우클릭,  3D Object > Cube  로 자식 오브젝트로 넣어주겠습니다.

이제 적당히 크기와 위치를 조정해 주시면 이런 구성이 될겁니다.



색깔이 없으니 허전하네요. Project 빈 공간에서 우클릭,  Create > Material  을 두 개 추가해서 바닥과 선글라스의 색깔을 넣어주겠습니다.

각각 Plane Color, Glass Color라고 설정해 주시고 속성(inspector)탭에서 Albedo 값으로 색을 지정하겠습니다.

색깔 설정이 완료된 Material을 오브젝트로 드래그 앤 드롭해주면 색깔 설정까지 완료됩니다.



이제는 플레이어가 물리적인 힘을 받을 수 있도록 Rigidbody 컴포넌트를 추가해야겠네요. 

Player에서  Add Component > Rigidbody  추가.


또한 플레이어가 보고있는 방향을 기준으로 앞뒤로만 움직이게 할 것이고, 움직임을 직접 값으로 넘겨줄 것이기 때문에 

 Player의  Rigidbody > Constraints > Freeze Rotation  에서 x, y, z값 모두 체크해 주겠습니다.


이제 플레이어가 움직일 수 있도록 스크립트를 추가해 줍니다. 

Project 탭에서 우클릭  Create > C# Script  선택해서 이름을 PlayerController 라고 지정합시다.

코드는 다음과 같습니다.


public class PlayerController : MonoBehaviour {


    public Rigidbody myRigidbody;

    public float rotationSpeed = 180f;
    public float moveSpeed = 12f;
    

    void Start () {

    }


    void Update () {
        float inputRotation = Input.GetAxis ("Horizontal");
        float inputSpeed = Input.GetAxis ("Vertical");

        transform.Rotate (0, rotationSpeed * inputRotation * Time.deltaTime, 0);

        Vector3 moveDistance = transform.forward * moveSpeed * inputSpeed * Time.deltaTime;

        myRigidbody.MovePosition (myRigidbody.position + moveDistance);

    }
}


여기서 핵심적인 부분은, rotationSpeed 라는 회전 속도와 moveSpeed 라는 움직이는 속도를 플레이어에게 어떻게 적용시키느냐입니다.

Update() 문 안의 코드들은 1초에 대략 30번~ 60번 실행됩니다. 이것을 흔히 게임에서 '프레임'이라고 하죠.

플레이어가 원하는대로 부드럽게 움직이려면 이 프레임 단위로 수행되는 단위가 극히 짧아야 합니다.

예를 들어서, moveSpeed가 12f라는 뜻은 1초에 12f만큼 가겠다는 뜻입니다. 그러나 만약


Vector3 moveDistance = transform.forward * moveSpeed * inputSpeed;


이런 식으로 코드를 작성하게 되면 1프레임마다 12f씩 튕겨나가는 주인공을 보시게 될겁니다.

그것을 방지하기 위해서  Time.deltaTime 이라는 녀석을 곱해주는 것이죠.  

즉, 이 녀석을 곱함으로써 프레임이 다른 환경에서도 원하는 움직임을 똑같이 맞춰주는 것이다! 라고 생각하시면 되겠네요. 자세한 설명은 지금은 넘어가도록 하겠습니다.


transform.Rotate (0, rotationSpeed * inputRotation * Time.deltaTime, 0);


이 회전속도를 제어하는 코드도 역시 마찬가지입니다.

모든 3D 오브젝트는 x, y, z의 축을 가지고 있는데 저희가 만들 게임은 y축으로만 회전할것이기 때문에 x와 z값에는 0이 들어갑니다.


아참, inputRotation 과 inputSpeed 는 각각 "Horizontal" 과 "Vertical" 로 설정되어 있는데,

이것은 십자 방향키를 입력했을 때 "좌우" 와 "위아래"를 뜻합니다. 


이 스크립트를 저장한 뒤에, Player의 inspector 탭에서  Add Component > PlayerController  를 검색해 넣어줍시다.

아래와 같이 스크립트에서 myRigidbody 에 Rigidbody 컴포넌트를 붙여주면 실행 완료입니다.




노트북 사양이 딸려서 약간 끊기네요.


자 그럼 오늘은 여기까지입니다. 

+ Recent posts