메뉴 건너뛰기

창작에 관련된 질문이나 간단한 팁, 예제를 올리는 곳

5편 : 점프

오늘은 좀 근본적으로 문제 있던 것들을 고치고 넘어가겠습니다.
바로... 캐릭터 그림이 너무 작지 않습니까?
640x480 사이즈에 맞추면서 배경은 4배 확대되었는데 캐릭터는 그대로라서
캐릭터가 너무 작아 동작이 잘 안 보입니다.
그래서 캐릭터 소스를 4배(가로 2배, 세로 2배) 키운 소스를 다시 올립니다.
덮어 쓰시고 이 그림에 따라서 출력 부분에 고쳐야 할 부분이 있을 겁니다.
일단 직접 고쳐 보시고, 안 그러면 나중에 전체 소스 보여줄 때 그대로 고치시면 됩니다.

Source3.zip

이제 캐릭터가 좀 커지니 볼만 합니다.



그런데 역시 이상합니다. 벽 위도 걷고 있습니다.
제한 범위를 바꿔야 겠습니다.
여기서 중요한 것은 캐릭터의 0,0을 기준으로 하는 것이 아니라
캐릭터의 발을 기준으로 더 이상 올라갈 수 없게 해야 자연스러워 보인다는 겁니다.
따라서 이동구역 제한을 아래와 같이 바꾸면

if(x<0)x=0;
if(x>SCREEN_X-38)x=SCREEN_X-38;
if(y<125)y=125;
if(y>SCREEN_Y-58)y=SCREEN_Y-58;

아래 그림과 같이 자연스러운 부분에서 더 이상 위로 올라가지 않게 됩니다.



이제 오늘의 본론인 점프와 대시를 구현해 봅시다.
일단 점프부터 구현해 보겠습니다.
그럴려면 z좌표를 하나 더 추가합니다.
물론 출력은 2D이므로 y좌표를 이용해서 점프를 표현하겠지만
계산에서 아래와 같은 문제가 생길 수 있으므로 z는 따로 만듭니다.



점프를 해서 위로 올라간 건데
위쪽에 있는 캐릭터의 펀치에 맞는다면 어이가 없겠죠.
사실상은 공중에 떠있는 건데...
아무튼 이런 이유로 z를 선언합시다.

int z = 0;

이제 C키에 의해서 점프가 되게 합시다.
어떻게 하면 될까요?
그냥 z를 증가시키다가 일정 delay가 지난후 부터는 줄이는 것을 생각해 볼 수 있습니다.
하지만 그러면 올라가는 속도, 내려오는 속도가 모두 일정해서 엄청나게 부자연스러워 보입니다.
올라갈때 빨리 올라가다가 최고점에 이르러서 조금 힘이 빠져
서서히 내려오다가 착지가 가까워질수록 빨리 떨어져야 자연스럽겠죠.
어떻게 구현할지 고민스럽지만 간단한 물리법칙입니다.

위로 뛰어 오르는 힘 = 위로 뛰어 오르는 힘 - 중력
z = z + 위로 튀어 오르는 힘

처음에는 위로 뛰어 오르는 힘은 +값입니다.
하지만 중력만큼 계속 줄다보면 -값이 되겠죠.
하지만 이때쯤 상당히 z가 늘어나 있을 겁니다.
위로 튀어 오르는 힘이 +에서 -로 바뀔 때가 최고점이 되는 셈이죠.
이제부터는 위로 튀어 오르는 힘이 -값이 됩니다.
사실상 아래로 떨어지는 힘이겠죠.
그리고 마찬가지로 떨어지다가... z가 0이 되는 순간을 착지로 보고 멈춰 주면 됩니다.

아래는 점프를 구현한 코드입니다.

int main(char* arg[])
{
     //창 생성
     if(!MainInitialize("Sample", TRUE, FALSE, window_mode))return 0;

     //윈도우창 이동
     if(window_mode)
     {
          jdd->OnMove(100, 100);
          SetCursor(LoadCursor(0, IDC_ARROW));
     }

     JPictureInfo jpi;
     jpi.SetColorKey(JColor(0,0,255));

     jdd->LoadPicture("배경", "던젼6.jpg", NULL, true);
     jdd->LoadPicture("걷기1", "w1.gif", &jpi, true);
     jdd->LoadPicture("걷기2", "w2.gif", &jpi, true);
     jdd->LoadPicture("걷기3", "w3.gif", &jpi, true);
     jdd->LoadPicture("펀치1", "p1.gif", &jpi, true);
     jdd->LoadPicture("펀치2", "p2.gif", &jpi, true);
     jdd->LoadPicture("킥", "k1.gif", &jpi, true);

     int x = 100;
     int y = 100;
     int z = 0;
     int delay = 0;
     int state = 0;
     bool left_punch = false;
     int jump_power;
     int gravity = 9;

     //메인 실행
     while(!GetKey(vkey_esc))
     {
          if(!ProcessMessage())break;

          if(GetKey(vkey_up,1))y-=5;
          if(GetKey(vkey_down,1))y+=5;
          if(GetKey(vkey_left,1))x-=5;
          if(GetKey(vkey_right,1))x+=5;
          if(GetKey(vkey_z))
          {
               state=1;
               delay=0;

               if( left_punch)left_punch=false;
                    else left_punch=true;
          }
          if(GetKey(vkey_x))
          {
               state=2;
               delay=0;
          }
          if(GetKey(vkey_c))
          {
               state=3;
               jump_power=100;
          }

          if(x<0)x=0;
          if(x>SCREEN_X-38)x=SCREEN_X-38;
          if(y<125)y=125;
          if(y>SCREEN_Y-58)y=SCREEN_Y-58;

          jdd->DrawPicture(backbuffer, "배경", 0, 0, NULL);

          //평상시
          if(state==0)
          {
               if(delay<30)jdd->DrawPicture(backbuffer, "걷기2", x, y-2, NULL);
                    else if(delay<60)jdd->DrawPicture(backbuffer, "걷기1", x, y, NULL);
                    else if(delay<90)jdd->DrawPicture(backbuffer, "걷기3", x, y, NULL);
                    else jdd->DrawPicture(backbuffer, "걷기1", x, y, NULL);

                    ++delay;
                    if(delay >= 120)delay=0;
          }
          //펀치
          else if(state==1)
          {
               if(left_punch)jdd->DrawPicture(backbuffer, "펀치1", x-8, y-2, NULL);
                    else jdd->DrawPicture(backbuffer, "펀치2", x-8, y-2, NULL);

               ++delay;
               if(delay>=10)state=0;
          }
          //킥
          else if(state==2)
          {
               jdd->DrawPicture(backbuffer, "킥", x-6, y, NULL);

               ++delay;
               if(delay>=10)state=0;
          }
          //점프
          else if(state==3)
          {
               jdd->DrawPicture(backbuffer, "걷기1", x, y-z, NULL);

               z+=jump_power/10;
               jump_power-=gravity;
              
               //착지
               if(z <= 0)
               {
                    z=0;
                    state=0;
               }
          }

          jdd->Render();
     }

     //정리하고 끝내기
     jdd->DeleteFont(global_font);

     return 0;
}

점프시의 그림이 있으면 좋겠는데 없어서 아쉽군요.
그냥 지금은 걷기1을 썼습니다.

일단 C키를 입력받는 부분(Getkey(vkey_c))을 봅시다.
state를 3(점프)으로 바꾸면서 초기 점프력을 100으로 설정합니다.

그럼 state가 3일때(if(state==3))는 무슨 일이 일어나는지 봅시다.
걷기1을 출력하는데 x, y가 아닌 y에서 z를 빼준 좌표에 출력합니다.
이걸로 점프한 만큼 위로 올라가게 보이겠죠.

그 다음은 z좌표의 변화입니다.
z좌표에는 매번 jump_power의 1/10을 더해 줍니다.
왜 1/10이냐면 그대로 100을 더했다가는 저 하늘 높이 올라가 버릴 것이기 때문이죠.
그럼 처음부터 10으로 하면 되지 않냐고 하지만, 그러면 중력값 변화 처리하기에 값이 너무 적죠.
솔직히 말하자면 소수점으로 해서 점프력은 10, 중력은 0.9로 하면 해결될 일이지만
소수점은 메모리를 더 먹고, 연산시에는 같은 형태인 것들만 서로 연산이 되므로
int값인 z와 연산하려면 jump_power를 int로 바꾸는 과정이 필요하게 됩니다.
그렇다고 z까지 소수점 형태로 바꾸면 z와 연결되는 y도 소수점으로 바꿔야 하는 등 복잡해지니
엄청난 정밀 계산이 필요하지 않는 이상 소수 처리는 이런 식으로 하는 게 편합니다.

계속해서 점프력은 중력만큼 감소합니다.
언젠가 마이너스 값으로 바꿔어서 z를 0으로 다시 줄이겠죠.
그리하여 착지 부분.
z가 0보다 작거나 같으면 z를 0으로 바꾼 뒤
(z가 마이너스 값이 되어버리면 원래 y위치보다 밑에 출력되게 되므로)
상태(state)를 0으로 돌려줍니다.

실행시켜 보면 공중에서 한 번 더 점프할 수 있는 등 문제가 보이겠지만
일단은 접어두겠습니다.
그건 다음번의 최적화에서 처리하도록 하겠습니다.

A.미스릴

2008.04.23
07:15:18
(*.234.10.203)

라이브러리에서 자체적으로 그림 확대 못하나영

똥똥배

2008.04.23
18:24:55
(*.239.144.2)
그 기능이 있지만 그것을 사용할 경우 일반적인 출력보다 속도가 느려집니다.
계속해서 크게 출력할 거라면 그냥 원본을 키우는 게 좋습니다.
아니면 처음에 로딩할 때 키워서 저장해 두는 방법이 있긴 하지만 번거롭고...
아무튼 커졌다 줄었다 하는 캐릭터가 아닌 경우는 그 기능은 안 쓰는 게 속도면에서 좋습니다.

대슬

2008.04.23
22:25:32
(*.234.216.51)

키웠다 줄였다 하고 싶은데.. 다음에 그 기능 살짝 가르쳐주시면 안될까요.

똥똥배

2008.04.23
22:43:36
(*.239.144.2)
jdd->DrawStretchedPicture(backbuffer, "그림명", 출력될 크기의 RECT, 원본의 RECT)

여기서 RECT 변수는

RECT rect;
Setrect(&rect, 좌, 상, 우, 하)

식으로 씁니다. 사각형 영역인 것은 아시겠죠?
아마 RECT를 변수로 넣어줄 때는 &붙여야 될 것임.

직접 테스트 해보면 그다지 어려운 기능은 아닙니다.

A.미스릴

2008.04.24
01:01:20
(*.234.10.203)

일반적인 출력보다 어느정도 느려지길래 ㅡㅡ;;;

똥똥배

2008.04.24
03:23:53
(*.239.144.2)
물론 하나 가지곤 눈에 보이게 느려지진 않을 겁니다.
그런데 만약 캐릭터 수가 늘어나서 누적되면 어떻게 될지 모르는 것이고.
이왕이면 빠른 쪽으로 만들어 주는 것이 게임 프로그래밍의 길입니다.
List of Articles
번호 제목 글쓴이 조회 수sort 추천 수 날짜 최근 수정일
165 멘탈붕괴의 절정을 부르는 파일입출력 노루발 273   2013-09-17 2013-09-17 08:18
Love2D의 love.filesystem.load는 훼이크입니다. 파일을 로드하는 것 같지만 사실 lua 파일을 불러옴. love.filesystem.read도 그리 믿음직하지 못함. (고정 크기의 파일만 읽어옴) 우리가 믿을 수 있는 최후의 보루는 love.filesystem.lines 이었던 것입니다....  
164 [Lua] Split 노루발 361   2013-09-17 2013-09-17 08:16
상황을 가정해보자. 웹 서버에 부탁해서 유희왕 덱의 리스트를 가져왔다. 원문은 대략 이렇다고 가정하자.   셰이프스내치x3 모린팬x3 트렌트x3 ...    보기엔 이렇지만 이걸 가져오면 아래와 같이 뜰 것이다. (웹 서버니까 HTML로 줌) 셰이프스내치x3<br>모...  
163 Love2d 게임 중간에 광고 표시 [1] 노루발 377   2015-11-12 2015-11-17 00:16
http://love2d.org/forums/viewtopic.php?f=11&t=81224  
162 김프로 이미지 맵 만들기 노루발 393   2015-11-11 2015-11-11 08:05
https://docs.gimp.org/en/plug-in-imagemap.html  
161 love.graphics.print 한국어 출력 노루발 431   2013-09-17 2013-09-17 08:24
Love2D 사용 시 한국어가 ㅁㅁㅁ이나 ??? 등으로 깨져 나와 해결책을 수소문하여 겨우 찾았다. 원래 중국어 등을 출력하기 위한 것이었으니 적절한 폰트를 쓴다면 한자나 히라가나등도 출력할 수 있을 것이다. function love.load() korfont = love.graphics....  
160 턴 기반 시스템 구현에 대한 글 [4] 노루발 440   2020-11-14 2020-11-18 05:49
플레이어와 NPC들의 모든 행동에 1턴이 소요된다면 턴 기반 시스템의 구현이 쉽겠지만 (사실 엄청 쉽지는 않다... 게임엔진은 실시간으로 돌아가는데 행동은 턴으로 제약해야 하니) 전략성을 요구하기 위해 행동에 소모되는 턴을 다르게 설정한다면 다소 생각...  
159 cocos2d-x CCMenuItem 자신을 지웠을 때 생기는 에러 똥똥배 442   2013-09-13 2013-09-13 07:28
CCMenuItem으로 메뉴를 생성하고 타겟과 셀렉터를 정해서 그 명령 안에서 자신을 지우게 되었을 경우 activate() 안에서 에러가 발생한다. 아직 activate()를 실행해야 되는데 remove 당했기 때문에 생긴 문제인데, 구글에서 검색해 본 결과, https://github.c...  
158 Love2D를 사용하기 위한 Lua 강좌: 변수형 노루발 446   2013-09-17 2013-09-17 08:23
안녕하세요, 노루발입니다. 저번에 변수에 대해 다뤄보았습니다. 변수는 값을 담는 그릇이라고 했습니다. 그리고 루아는 변수 안에 있는 값이 숫자던 문자던 그냥 변수로 취급한다고 했고 "몇개를 살까요?" 라는 질문에 "ㄹ개" 라고 답할 수도 있다고 했습니...  
157 [Love2D] 일단 뭔가 해보자 노루발 457   2013-09-17 2013-09-17 08:27
감기를 딛고 다시 재기... Lua로 초 간단한 원시적인 게임을 만들려고 합니다. 뭐냐하면 소코반이라고, 이렇게 말하시면 모르지만 푸쉬푸쉬라면 아시는 분들이 엄청 많으실겁니다. 이걸 그래픽조차 씌우지 않은 아스키로. ########## #    ..      # #    o  o...  
156 Love2D? 개요, 설치, 실행. 노루발 465   2013-09-17 2013-09-17 08:20
love2d.org/Love2D 홈페이지. Lua로 2D 게임을 만드는 프레임워크입니다. 무료고, 오픈 소스이며, 윈도우, 맥, 리눅스에서 동작하며, 안드로이드에서 동작하게 하는 논의도 활발하다 합니다. 그럼 일단 받아봅시다. 아무리 좋아도 내 컴퓨터에 못 깔면 소용없...  
155 Love2d 여러 플랫폼으로 빌드 자동화 노루발 470   2014-11-12 2014-11-12 17:35
https://github.com/MisterDA/love-release/blob/master/README.md http://www.ambience.sk/lua-love2d-game-distribution/  
154 PHP로 웹게임 만드는 영상 [1] 노루발 473   2021-06-25 2022-01-28 03:40
Simple PHP Strategy Game - YouTube  
153 cocos2d-x에서 schedule_selector 정의 변화 [1] 똥똥배 522   2013-08-19 2013-09-13 07:28
예전에 적은 글에는 터치와 업데이트를 활성화 시키는 법을 적어놨다. http://hondoom.com/zbxe/index.php?mid=study&document_srl=385031 이글은 cocos2d-1.0.1-x-0.9.1를 기준으로 적은 글인데, 최신버전으로 오면서 update의 정의에 대해서 변화가 생겼...  
152 Love2D: 콜백 함수들 노루발 538   2013-09-17 2013-09-17 08:25
Love의 콜백 함수들은 love.run(가장 메인이 되는 겁니다.)에 의해 호출되며, 많은 작업들을 수행합니다. 이것들이 없다고 게임이 실행되지 않는다거나 하지는 않지만 좋은 게임을 만들기 위해선 이것들을 적재적소에 활용해야 합니다. 이미 아시는 분들도 있...  
151 cocos2d-x 게임을 iOS에 이식할 때 생기는 문제들 똥똥배 543   2013-08-26 2013-09-13 07:28
1. 윈도우 한정 명령어는 사용할 수 없다. 너무도 당연한 문제. 2. 한글 인코딩 문제 최신 VS을 사용하면 겪지 않을 문제일지 모르겠지만, 보통은 h나 cpp를 ANISI형식 문서로 만들 것이다. XCode에서는 Unicode나 UTF-8 형식을 쓰므로 소스에 박아둔 한글은 ...  
150 Love2d 안드로이드 게임 패키징하기 [3] 노루발 548   2014-12-15 2021-01-11 12:11
이 문서는 개발 환경이 갖추어져 있는 상태이고, 빌드를 무사히 마친 뒤라고 가정합니다. 또한 이 문서는 https://bitbucket.org/MartinFelis/love-android-sdl2/wiki/Game%20Packaging#markdown-header-how-to-package-the-apk-with-your-own-love-090-game ...  
149 Love2d 안드로이드 빌드하기 노루발 572   2014-12-15 2014-12-15 00:59
Love2d의 안드로이드 포트는 알파 단계입니다. 차차 개선되어 나가긴 하겠지만 아직 불안정한 부분이 많으며, 일어날 수 있는 오작동과 그로 인한 결과는 일절 책임지지 않습니다.   이 문서는 Windows 사용자 기준입니다. Linux나 Mac을 사용할 정도의 내공...  
148 구글 인앱 구매 Soomla로 구현해본 후 팁 똥똥배 577   2014-09-20 2014-09-20 18:45
1. 일단 알파 테스트라도 앱을 출시시켜야 인앱이 작동한다. 그리고 APK 업로드 후에 바로 인앱이 적용되지 않는다. 구글에 게시되는 데, 시간이 걸리므로 인앱 등록 - APK 등록을 마친 후 다음날부터 구현하는 게 깔끔하다. 이걸로 모르고 하루종일 왜 안되나...  
147 Love2D를 사용하기 위한 Lua 강좌: 코딩 스타일, 변수 노루발 585   2013-09-17 2013-09-17 08:22
안녕하세요, 노루발입니다. 어제는 Lua에 대해 대강 알아보고 입력과 출력을 다뤄봤습니다. 출력은 print([출력할 것]), 입력은 io.read()였습니다. 일단 본격적인 프로그램 작성에 앞서 Lua의 코딩 스타일에 대해 알아보겠습니다. 코드를 작성하는 것을 코딩...  
146 소코반: 맵을 초기화하자. 노루발 588   2013-09-17 2013-09-17 08:27
8*8 맵의 데이터가 담길 2차원 배열(테이블)을 초기화하는 코드이다. Map = {} for a = 1, 8 do     Map[a] = {}     for b = 1, 8 do         Map[a][b] = 0     end end Map 테이블의 [1][1]부터 [8][8]까지 모두 0이라는 값이 담기고 Map[0][0]이나 Map[1][...