인디 게임 강좌

전체보기

모바일 상단 메뉴

본문 페이지

[게임메이커] 처음 만나는 GMS2 // 7. 슈팅게임 제작 2

Zpink
댓글: 1 개
조회: 3642
추천: 2
2017-07-08 13:12:49


 저번 장에서 우리는 플레이어 캐릭터를 생성하였습니다. 하지만 플레이어 하나만으로는 게임이 매우 심심해질 것입니다. 재미있는 게임에는 어떠한 목적 같은 것이 필요합니다. 저는 이 게임의 목적을 '총을 쏘아 적을 사살하는 것'으로 정하고 싶고, 많은 여러분들도 그것을 바라실 것이라 생각합니다. 그러므로 이번에는 플레이어의 원거리 공격과 플레이어를 향해 움직이는 적을 만들어보도록 하겠습니다. 





새로운 스프라이트를 생성합니다. 이름은 sprEnemy로 짓겠습니다. 첨부파일로 올려진 스프라이트 (저번에 이미 다운 받으셨다면 그것을 그대로 쓰시면 됩니다) 중 enemy 폴더에 들어있는 12장의 스프라이트를 한번에 불러오기합니다.


- 혹시 스프라이트가 없으시다면, 다시 다운로드 받으세요! 

spriteset.zip






그리고 적 스프라이트의 중심점을 플레이어와 마찬가지로 중앙으로 잡겠습니다. 스프라이트의 크기가 48*48이므로 origin을 24 x 24로 변경합니다. 





이제 적 오브젝트를 만들어보겠습니다. 새로운 오브젝트를 생성하고 이름은 objEnemy로 짓겠습니다. 스프라이트는 방금 전에 만든 sprEnemy를 사용합니다. 



이 적 오브젝트를 플레이어가 있는 쪽으로 지속적으로 움직이게 할 것입니다. 

Add Event - Step 이벤트를 추가합니다. 





스크린샷과 같은 코드를 입력합니다.


 

move_towards_point(objPlayer.x,objPlayer.y,1.5);

image_angle = direction;




move_towards_point(x,y,speed);는 오브젝트를 x,y의 위치로 speed의 속도로 움직이게 만드는 함수입니다. x값과 y값에 플레이어 오브젝트의 x값과 y값을 입력하여 적 오브젝트가 플레이어 쪽을 계속 향하도록 합니다. 속도는 플레이어의 이동속도인 2보다 조금 낮은 1.5로 지정했습니다. 


그리고 이미지의 각도를 현재 움직이는 방향으로 지정하였습니다. 움직이는 방향이 플레이어의 위치이기 때문에 적은 플레이어를 계속 바라보게 됩니다. 





이렇게 만든 적을 룸의 적절한 위치에 배치한 후 게임을 실행하여 제대로 작동하는지 확인해봅니다.





코드를 정상적으로 작성하였다면 적이 이렇게 움직이게 됩니다. 







적이 플레이어에게 계속 다가가지만, 플레이어는 이 적을 도저히 처치할 수단이 없습니다. 그래서 플레이어가 총을 발사할 수 있도록 총알을 만들어보겠습니다.


새 스프라이트를 생성한 뒤 이름은 sprBullet으로 짓습니다. 미리 다운받은 스프라이트 중 spr_bullet을 불러옵니다.







총알 스프라이트 역시 각도에 따라 회전을 해야 합니다. 그러므로 origin을 중앙으로 맞춥니다. 총알 스프라이트의 크기가 24*24 이므로 origin을 12*12로 지정합니다.





다음으로 새 오브젝트를 생성합니다. 이름은 objBullet으로 짓겠습니다. 스프라이트는 방금 전 생성한 sprBullet으로 지정합니다. 





화면에 인스턴스의 수가 너무 많아지게 된다면 게임 내에서 연산해야할 것이 너무 많아지기 때문에 심각한 렉이 발생할 수 있습니다. 인스턴스가 화면 밖으로 나가 보이지 않는다고 해서 사라지는 것은 아닙니다. 그렇기 때문에 밖으로 나간 총알이 스스로 제거되도록 만들겠습니다.

 

Add event - other - Outside Room을 클릭하여 이벤트를 추가합니다. 이 이벤트는 룸 밖으로 나갔을 때 발생됩니다.




입력할 코드는 다음과 같습니다.


 

instance_destroy();




instance_destroy() 함수가 실행되면 인스턴스는 사라지게 됩니다. 







이제 방금 만든 총알을 플레이어가 발사할 수 있게 만들 것입니다. 그런데 여러분들은 여기서 한가지 의문점이 생기실 것입니다. 방금전에 만든 총알 오브젝트는 고작 밖으로 나갔을 때 제거되게 만든 것을 빼고는 아무 것도 만들어놓은 것이 없는데 어떻게 이걸 발사시킨다는 걸까요? 


플레이어 오브젝트 (objPlayer)의 Step 이벤트를 클릭하여 코드 에디터를 띄웁니다.
 







기존에 작성되어있던 코드에 새로운 코드를 추가하겠습니다. 기존 코드는 앞서 이미 설명해드렸기 때문에 새로 작성된 아래의 코드만을 설명하도록 하겠습니다.




if(mouse_check_button(mb_left)){

var missile = instance_create_layer(x,y,"Instances",objBullet);

missile.image_angle = image_angle;

missile.direction = image_angle;

missile.speed = 6;

}



mouse_check_button(마우스 버튼) 는 괄호 안에 들어있는 마우스 버튼이 눌려있는지를 확인하는 함수입니다. mb_left는 마우스 왼쪽 버튼을 의미하는 상수입니다. 왼쪽 마우스 버튼이 눌려있는 경우 대괄호 안의 코드를 실행하게 되는데, 로컬 변수 missile의 값이 instance_create_layer(x,y,"Instances",objBullet); 로 설정되어있습니다. 이 함수는 인스턴스를 생성하는 함수인데, 어째서 변수의 값으로 선언된 것일까요?


앞서 제가 함수에 대해서 설명하였던 것을 기억하신다면, 리턴값이 존재하는 함수가 있다는 것도 아실 것입니다. instance_create_layer는 인스턴스를 생성하는 기능을 가질 뿐만 아니라 특정한 값을 리턴하기도 하는데, 이 함수가 리턴하는 값은 바로 생성한 인스턴스의 ID (게임 화면에 여러개의 같은 오브젝트가 있을 때 그것을 구분지을 수 있도록 붙이는 번호) 입니다. 그러므로 missile의 값은 생성된 인스턴스의 ID가 됩니다.


 

missile.image_angle = image_angle;

missile.direction = image_angle;

missile.speed = 6;


그리고 이 코드를 보시면,  missile 로컬변수의 변수를 변경하려는 것을 확인하실 수 있습니다. 어떻게 그런게 가능하지? 라는 생각이 드신다면, 특정 오브젝트의 변수값을 오브젝트이름.변수값과 같은 식으로 표시할 수 있었던 것을 기억하세요! missile의 값이 인스턴스ID였으므로, missile.image_angle은 곧 그 인스턴스의 image_angle이 되는 것입니다. 


작성된 코드에 따르면, 생성된 총알은 이미지의 각도가 플레이어의 이미지 각도와 같고, 방향은 위에서 정해진 이미지 각도를 그대로 따르며, 속도는 6이 됩니다.




여기까지는 좋습니다. 이제 플레이어는 미사일을 발사할 것입니다. 하지만 아무런 딜레이가 없기 때문에 마우스 왼쪽 버튼을 계속 누르고 있으면 플레이어는 마치 맥도날드에서 오래 기다렸다 방금 전에 받은 따끈한 감자튀김을 바닥에 쏟아버리듯 길쭉한 총알을 미친듯이 생성해낼 것입니다. 





우리는 총알이 딜레이 없이 마구잡이로 뿌려지는 것을 막기 위해 일정한 간격을 두도록 만들 것입니다. 플레이어 오브젝트에 Add Event - Create를 클릭하여 Create 이벤트를 추가합니다.








그리고 코드 에디터에 새로운 변수를 선언하겠습니다. 


 missile_delay = 0;

이 변수는 미사일에 딜레이를 넣는 역할을 수행할 것입니다.






그리고 또 하나의 이벤트를 추가할 것인데, Add Event - Alarm - Alarm0을 추가합니다.


알람(Alarm)은 무엇일까요? 이것은 일종의 타이머로써, 일정한 시간을 정해주면 그 시간에 맞춰 이벤트가 작동하게 됩니다.

알람을 작동하는 코드는 'alarm[알람번호] = 시간;' 입니다.




Alarm 0에도 마찬가지로 missile_delay = 0; 을 입력합니다. 여기에 왜 이런 것을 넣었는지는 아래에서 확인해보실 수 있습니다.






step 이벤트의 코드 에디터입니다. 방금 전 작성하였던 마우스 왼쪽 클릭 조건문을 조금 변경할 것입니다.



 


if(mouse_check_button(mb_left) && missile_delay == 0){

var missile = instance_create_layer(x,y,"Instances",objBullet);

missile.image_angle = image_angle;

missile.direction = image_angle;

missile.speed = 6;

missile_delay = 1;

alarm[0] = 20;

}




우선 조건문에 또 다른 조건 missile_delay == 0 이 들어가있습니다. 마우스가 클릭된 상태인 동시에 해당 변수가 0일 떄에만 대괄호 안의 내용이 작동될 것입니다. 대괄호 안에 새로 추가된 코드는 



 

missile_delay = 1;

alarm[0] = 20;




입니다. 딜레이 변수를 1로 만들고, 알람 0을 20스텝(룸 스피드 60을 기준으로 대략 3분의 1초) 후에 실행하도록 합니다. 우선 딜레이 변수가 0일 때 미사일을 생성한 후 바로 딜레이 변수가 1이 되었으므로, 왼쪽 클릭과 딜레이 변수가 0인 경우를 모두 만족해야 하는 조건이 걸려있는 이 괄호 안의 코드는 더 이상 실행되지 않습니다. 여기에서 중요한 것은 바로 알람 실행 부분입니다. 앞서 우리는 알람 0에 missile_delay가 0이 되도록 하는 코드를 작성했었지요. 그러므로 20스텝이 지나 알람 0 이벤트가 작동된다면 딜레이 변수가 0이 되어 미사일을 생성할 수 있는 조건이 다시 만족될 것입니다. 


이런 식으로 약 0.3초의 간격으로 총알을 발사하는 코드가 완성되었습니다. 코드가 정상적으로 작성되었다면 마우스 왼쪽 버튼을 꾹 누르고 있어도 일정한 간격을 두고 총알이 발사될 것입니다. 






이제 적이 총알에 닿으면 제거되도록 만들어보도록 하겠습니다.


Add Event - Collision - objBullet을 순서대로 클릭합니다.

Collision은 충돌과 관련된 이벤트로, 지정된 오브젝트와 해당 오브젝트가 부딪혔을 때 발생하게 됩니다.




충돌 이벤트에는 이런 코드를 입력하겠습니다



 

with (other){

instance_destroy();

}

instance_destroy();




우리는 이미 instance_destroy()를 써보았습니다. 그것은 인스턴스를 제거하는 함수이지요. 그런데 한번도 써보지 못한 것이 나왔습니다. with (other)는 대체 무엇일까요?


with(인스턴스 혹은 오브젝트){ }를 이용하면 괄호 안에 들어간 인스턴스 혹은 오브젝트에게 대괄호 안에 들어간 코드와 같은 명령을 내릴 수 있습니다. 지금 괄호 안에는 other가 들어가있는데, 이 other는 적 오브젝트와 충돌을 일으킨 다른 대상, 즉 이 경우에는 총알 인스턴스가 됩니다. 이 총알 인스턴스를 instance_destroy()를 이용해 제거합니다. 즉, 총알이 적을 관통하지 못하고 적과 함께 사라지도록 만드는 것입니다. 어쩌면 총알이 적에게 부딪힌 것이니까 괄호 안에는 other가 아니라 objBullet을 써야 하는 게 아닌가? 라고 생각하실 수도 있습니다. 그러나 그렇게 된다면 단순히 부딪힌 총알 뿐만이 아니라 게임 화면에 나와있는 모든 총알들이 대괄호 안의 코드같은 영향을 받게 됩니다. 즉 그 괄호 안에 objBullet을 넣는다면 적이 총알에 맞았을 때 단순히 그 총알 뿐만 아니라 이미 발사된 나머지 총알들까지 모두 사라져버리는 불상사가 발생할 것입니다. 







모든 코드를 작성한 후에 테스트해봅니다.

플레이어는 총알을 일정한 간격으로 발사합니다. 적은 총알에 맞고 사라집니다. 총알도 함께 사라질 것입니다.



하지만 스크린샷으로는 이게 죽은 것인지 살은 것인지 확인할 턱이 없습니다. 왜냐면 적이 말 그대로 아무런 흔적도 없이 사라져버리기 때문이지요. 그래서 우리는 적이 제거되면 혈흔을 남기도록 만들 것입니다. 또한 혈흔이 딱 1가지이면 재미없으므로 3가지의 임의의 모양이 되도록 하겠습니다.





새 스프라이트를 생성합니다. 이름은 sprBlood로 짓겠습니다. Import 버튼을 클릭한 후 미리 다운받은 스프라이트 중 blood 폴더에 있는 혈흔 3개를 모두 불러옵니다. 혈흔의 크기는 64*64인데, 마찬가지로 이 스프라이트도 중심점을 중앙으로 잡기 위해 origin을 32*32로 조절하겠습니다.





이제 오브젝트를 생성할 것입니다. 새 오브젝트를 만든 후 이름은 objBlood로 짓고 스프라이트는 아까 만든 sprBlood를 사용합니다. 혈흔이 화면에 계속 남으면 과도한 인스턴스 갯수 때문에 게임이 느려질 우려가 있으므로 일정 시간 후에 사라지도록 만들 것입니다. Add Event - Alarm - Alarm0 을 순서대로 클릭하여 알람 이벤트를 추가합니다. 






알람 0 이벤트의 코드는 


 instance_destroy();

입니다. 이 함수가 무엇인지 여러분들은 이미 다 알고 있기 때문에 설명은 생략하겠습니다.





 적이 혈흔을 남기도록 만들기에 앞서, 룸에 새로운 레이어를 추가할 것입니다. 만들어둔 룸의 레이어 창의 아래에 새로운 인스턴스 레이어를 생성하는 버튼을 클릭합니다.  





생성된 레이어의 이름을 effect로 짓고, Instances 레이어의 아래에 오도록 설정하겠습니다. effect 레이어에 위치한 오브젝트는 Instances 레이어에 위치한 오브젝트보다 아래에 깔리게 됩니다. 






(스크린샷에 표시된 var blood...부분에는 오타가 존재합니다. "effect"대신 "Instances"가 들어가있는데, 이 부분은 아래 따로 적은 코드로 대체하시면 됩니다.)


이제 적 오브젝트 objEnemy의 속성화면에서 총알 오브젝트 objBullet과의 충돌 이벤트에 새로운 코드를 추가하겠습니다.

새로이 추가된 코드는 아래와 같습니다. 


 
var blood = instance_create_layer(x,y,"effect",objBlood);
blood.image_single = choose(0,1,2);
blood.alarm[0] = 120;


로컬변수 blood를 선언하고 그 값을 instance_create_layer(x,y,"effect",objBlood);의 리턴값으로 지정합니다.
이제 blood를 통해 새로 생겨난 objBlood의 변수나 이벤트를 조종할 수 있습니다.

blood의 변수 image_single은 여러 장의 이미지를 가진 스프라이트의 이미지 넘버를 의미하는 내장변수입니다. 정확히 이 image_single 변수는 이미지의 재생 속도를 나타내는 변수 image_speed = 0; 과 마찬가지로 여러장의 이미지를 가진 스프라이트의 이미지 넘버를 의미하는 변수인 image_index = 값 을 동시에 선언한 효과를 가지고 있습니다. 그래서 이 변수로 움직이는 스프라이트의 이미지 번호를 지정해주면 스프라이트가 해당 이미지를 표시한 상태로 움직임이 멈추게 됩니다. 만약에 움직임을 멈추지 않으면서 스프라이트의 현재 이미지 넘버를 바꾸고 싶다면 image_index의 값을 조절해야 합니다.  

이 image_single 변수의 값을 choose(0,1,2); 로 지정하였는데, choose(0,1,2...16)은 괄호 안에 들어가있는 값 중 임의의 하나를 리턴하는 기능을 가지고 있습니다. 그러므로 blood의 이미지 번호는 0,1,2 중 하나가 됩니다. 스프라이트의 이미지 수가 3장이었는데 0,1,2 라고 하니 뭔가 이상해보이겠지만, 게임메이커의 스프라이트 이미지 숫자는 0부터 시작합니다. 




이해를 돕기위한 스크린샷. 3장의 이미지의 넘버는 앞에서부터 순서대로 0,1,2입니다. 


그보다 choose의 괄호 안에 들어갈 수 있는 값은 한정되어있는데, 만약에 우리가 얻고자 하는 임의의 값의 범위가 넓은 경우에는 어떤 것을 써야 할까요? 바로 irandom(값) 입니다. irandom은 0에서부터 괄호 안에 넣은 값-1 사이의 임의의 정수값을 리턴해주는 기능을 갖고 있습니다. 그래서 위의 choose를 irandom으로 변환하자면 이러할 것입니다.

  
var blood = instance_create_layer(x,y,"effect",objBlood);
blood.image_single = irandom(2);
blood.alarm[0] = 120;


참고로 irandom(값)이 아니라 그냥 random(값) 이라는 함수도 존재합니다. 그렇지만 이것은 임의의 실수를 리턴하기 때문에 결과가 소수가 될 수도 있습니다. 앞서 확인하셨다시피 스프라이트의 이미지 번호가 소수는 아니기 때문에 우리는 irandom을 사용한 것입니다. 

이렇게 blood의 이미지 번호를 정해준 후, blood의 alarm[0]을 120스텝(60프레임 기준 대략 2초) 후에 작동시킵니다. 
이제 생성된 혈흔 오브젝트는 3장의 이미지 중 하나의 모습으로 나타났다가 2초 후에 사라지게 될 것입니다.








게임을 실행해본 후 직접 결과를 확인합니다. 피가 터집니다!




이번 글은 몹시 길었군요. 그럼 여기까지 하고, 다음 파트에 이어서 설명하도록 하겠습니다. 

다음 장에서는 플레이어의 체력과 킬 수를 표시할 수 있는 아주 간단한 수준의 UI를 구현해볼 것입니다. 

Lv1 Zpink

모바일 게시판 하단버튼

댓글

새로고침
새로고침

모바일 게시판 하단버튼

지금 뜨는 인벤

더보기+

모바일 게시판 리스트

모바일 게시판 하단버튼

글쓰기

모바일 게시판 페이징