오늘의 게임

서바이벌 프로젝트 | 아이오 엔터테인먼트 | 2001 - 2013



2000년대 초를 풍미했던 게임

 필자가 처음으로 서바이벌 프로젝트(이하 서프) 했던 것은 초등학교 때였던 것으로 기억한다당시에는 횡스크롤이나 탑다운 종류의 게임이 절대적으로 많았었는데서프도 탑다운  게임  하나였다.

서프는  다섯 가지의 다양한 게임모드를 서비스했다미션대난투,  대전퀘스트놀이가 그것이다유저들은  게임모드에따라 원하는 방을 찾아서 함께 플레이할  있었으며얼마든지 방을 만들 수도 있었다대체적으로 미션을 깨면서 게임화폐인코드와 경험치를 모아 계정을 성장시키고대난투와 대전놀이를 통해 다른  유저와 실력을 겨루었다서프가 2000년대 초에흥행할  있었던 이유로는  만든 맵과 게임모드개성있는 캐릭터들카드와 확률 시스템의 삼박자가 훌륭하게 맞아떨어졌기때문이었다고 생각한다.

다양한 게임모드 

 앞서 언급했던 것처럼 서프는 다양한 게임모드를 지원했고적게는 3많게는 16개까지 세부모드가 있었다.  때문에 유저가많던 시기에는 어딜가든 하나쯤은 원하는 모드를 찾을  있기 마련이었다그러나 2000년도 중반부터 서프가 하락세를 걷기시작하면서 줄어든 유저층에 비해 너무나 많은 맵과 게임모드는 오히려 단점으로 작용하게 되었다좋아하는 모드를 같이  사람을 구하지 못하게  유저들이 점점 이탈하게  것이다필자 역시 서프가 서비스를 종료하기 직전인 12년도 말에 게임을 잠깐 했었던 적이 있었다.함께 깨는 미션과 퀘스트는 혼자만의 게임이 되어버렸고 대전과 놀이는 수다방으로  명백을 유지하고있었다유저끼리 서로를 상대하는 듀얼이나 대전모드만이 간간히 눈에  뿐이었다.

이처럼 게임모드가 많다고 좋은 것은 유저가 그만큼 많이 접속할  뿐이다유저가 줄어들수록 서로 뭉칠  있는 게임모드가필요하고이용률이 높지 않은 모드는 과감히 삭제할 줄도 알아야 한다.


개성있는 캐릭터들

 필자가 지인들에게 서프를 언급했을  가장 먼저 화두에 오르는 것은 바로 캐릭터이다지금까지도 많이들 기억에 남아있는이유는 개성있는 외모와 스킬속성 때문이라고 생각한다 

속성은 바람으로 나누어지고  속성에 따라 캐릭터들이 존재했는데각각 원거리와 근거리 스킬이 있고 이동속도나조작감 또한 캐릭터별로  달랐다유저들은  캐릭터에 자신의 속성카드를 적용시켜 게임을 플레이했다 12개의 캐릭터와 4개의 속성이 있었으니가능한 조합 수는  48가지가 있었던 것이다.

이처럼 유저가 입맛에 맞게 캐릭터와 속성을 선택할  있다는 점이 다양한 메타를 만들어냄으로써 게임을 오래 즐길  있도록 해주었던 요소가 아닐까 싶다.


카드와 확률 시스템

 서프에서 카드는 가장 핵심적인 요소이자 과금 요소이기도 했다크게 캐릭터 프리미엄 카드와 장비 확률 카드로 구분되었는데캐릭터 프리미엄 카드는 캐릭터의 성능을 올려줌과 동시에   세련된 외관으로 바꾸어 주는 역할을 했다.

장비 카드는  속성별로 도끼방패신발펜던트  아홉 가지의 종류가 있었다 카드는 특정 효과가 확률적으로 붙어있고 확률을 높이기 위해서는 과금이 필요했는데 필자는  카드를 업그레이드한 적은 한번도 없었다대체로 랭커들이 많이들 업그레이드를 했었던  하다.

이렇게 오늘의 게임서바이벌 프로젝트에 대해 간략히 알아보았다흔히들 추억의 게임이라고 말하면 서프를 많이 언급할 만큼현재 20 중후반이 즐겨했던 게임이지 않았나 싶다아쉽게도 신규 게임모드 개발 부재값비싼 과금요소와 종류의 한계캐릭터 다양화가 되지 않아  게임성에 비해 빛을 많이 보지 못했다현재도 각종 프리서버에 접속할  있다고 하니예전의 향수를 느껴보고 싶다면 한번쯤 찾아보는 것을 권한다.


읽어주셔서 감사합니다!

칼럼 원본은 여기서 확인하실 수 있습니다.


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


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

하이라키 탭에서  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 컴포넌트를 붙여주면 실행 완료입니다.




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


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

나조차도 유니티를 잘 다루지 못하는 초급생이지만, 고등학교 아이들을 가르쳐달라는 의뢰가 와서 강의를 총 세번 진행하였다.

그동안 까먹었던 기본적인 지식들과 함수들의 응용에 대해 복습할 수 있던 좋은 시간이었다.



1주차 강의내용 : Roll a Ball 게임 제작


유니티 홈페이지에 있는 듀토리얼 시리즈 중 하나.

유니티 툴의 기본 구성과 스크립트 제작, 오브젝트의 활용법, 카메라 이동 등의 내용을 다루고 있다.


듀토리얼 링크 > https://unity3d.com/kr/learn/tutorials/s/roll-ball-tutorial


듀토리얼과 유사한 흐름으로 진행하면 큰 무리가 없는 좋은 예제다.



2주차 강의내용 : Unity-Chan 에셋을 이용한 게임 제작


유니티짱으로 널리 알려져 있는 이 에셋은 3D 캐릭터기반 게임을 만들고자 하는 사람들에게 많은 도움이 될 것이다.

물리 법칙과 애니메이션 효과, 에셋 활용법을 골고루 다루는 법에 대하여 알수 있는 시간이었다.

맵 또한 에셋에서 받아와 맵을 각자 만들어보고, 유니티짱을 그 속에서 구현해보라는 자율성을 주었다.


유니티짱 에셋 링크 > https://www.assetstore.unity3d.com/kr/#!/content/18705


3주차에는 로컬 멀티플레이를 지원하는 탑다운 뷰 슈팅게임을 제작해 보았다.

UI나 점수계산은 안되지만 고등학생 수준에서는 간단하게 제작하고 플레이하기 적합했다.


이 3주차 내용은 블로그에 제작과정을 포스팅하도록 하겠다.

'일상을 견디자' 카테고리의 다른 글

계획을 세우고 지키는 법  (0) 2017.05.31

+ Recent posts