CODEONWORT

땅에 찰싹 달라붙기 위한 수학적 접근법 본문

Season 1/플래시

땅에 찰싹 달라붙기 위한 수학적 접근법

codeonwort 2010. 9. 10. 20:41

좌우 방향키로 이동, 위 방향키로 도약

고1 때 배운 부등식의 영역을 떠올려보자. 좌표 평면은 전구간 연속함수 f(x)의 그래프에 의해 두 영역으로 나뉜다.


※ 수학 교과서에서 배우는 거랑 y축이 정반대 방향인 거 주의하자.

삘이 좀 오시나? 땅을 함수의 그래프로 표현하는 것이다. 함수의 그래프는 땅의 경계면이 되고, 그래프의 아래쪽은(보기에는 위쪽) 공중, 위쪽(보기에는 아래쪽)은 땅 속이 된다.

그러니까 캐릭터의 좌표를 (x1, y1)이라 하면 다음과 같은 간단한 확인만으로 캐릭터가 공중에 있는지, 땅에 서 있는지, 땅 밑에 있는지 알 수 있다는 말이다.
1. 땅 밑 : y1 > f(x1)
2. 땅 위 : y1 = f(x1)
3. 공중 : y1 < f(x1)

바로 구현할 계획을 세워보자. 먼저 땅을 표현할 함수를 하나 만든다. 대충 숫자를 이리저리 끼워 맞춰서 그럴 듯한 함수를 하나 얻었다.


이제 캐릭터가 움직이는 것을 생각해보자. 임의의 순간에 캐릭터는 공중에 있거나 땅에 서 있다. state라는 문자열 변수를 정의하고 공중에 있을 땐 "fly"를, 땅에 있을 땐 "ground"를 설정하자.
* state == "fly"일 때 : 캐릭터는 떨어진다. 땅 밑으로 내려가면 땅 바로 위에 붙여주고 state에 "ground"를 넣는다.
* state == "ground"일 때 : 좌우로 이동하면 먼저 캐릭터의 x 좌표를 수정한 후 f(x)를 이용하여 그 x 좌표에서의 땅 경계에 붙여준다.

이걸 코드로 옮긴다. (필요 없는 건 조금 생략함)

// 땅(함수의 그래프)을 그린다
var graph:Shape = addChild(new Shape()) as Shape
var g:Graphics = graph.graphics
g.lineStyle(1, 0x000000, 1)
g.moveTo(0, f(0))
for(var i:int=0 ; i<500 ; i++) g.lineTo(i, f(i))


var state:String = "fly" // 공중에 떠 있느냐, 땅 위에 있느냐를 판별하기 위한 변수
var gravity:Number = 1 // 중력
var speedY:Number = 0 // 낙하 속도
var speedX:Number = 4 // 가로 이동 속도

addEventListener(Event.ENTER_FRAME, update)
function update(Event):void {
 if(key.isDown(Keyboard.LEFT)){
  crt.x = Math.max(0, crt.x-speedX)
  if(state == "ground") crt.y = f(crt.x) // 공중에 있을 땐 그냥 x축 이동만 하고 땅에 있을 땐 땅에 붙여준다
 }else if(key.isDown(Keyboard.RIGHT)){
  crt.x = Math.min(500, crt.x+speedX)
  if(state == "ground") crt.y = f(crt.x) // 공중에 있을 땐 그냥 x축 이동만 하고 땅에 있을 땐 땅에 붙여준다
 }
 // 공중에 있을 때 처리
 if(state == "fly"){
  speedY += gravity
  crt.y += speedY
  var fval:Number = f(crt.x)
  if(crt.y > fval){
   crt.y = fval
   speedY = 0
   state = "ground"
  }
 }
}


// 땅을 나타내는 함수
function f(t:Number):Number { return t*(t-200)*(t-500)/100000 + 200 }



땅을 그래프로 표현하면 이득이 하나 더 있다. 땅의 기울기에 따라 캐릭터도 기울어지게 만들어야 할 때도 있는데, f(x)의 도함수 f'(x)로 땅의 기울기를 정확하게 알아낼 수 있기 때문이다.

위에서 정의한 f(x)를 미분하면 다음 도함수가 나온다.


여기에 캐릭터의 x 좌표를 집어넣으면 캐릭터가 서 있는 지점에서의 땅의 기울기를 구할 수 있다.

땅의 특정 지점에서의 기울기라는 건 그 곳에서 땅에 그은 접선이 x축의 양의 방향과 이루는 각도의 탄젠트 값이다. 아크탄젠트 함수를 사용하면 각도의 탄젠트 값으로부터 각도를 얻을 수 있고, 이 각도는 라디안 단위이므로 도 단위로 바꿔서 캐릭터의 rotation 속성에 넣어주면 캐릭터가 정확히 땅의 기울기에 맞춰서 기울어진다.

// 도함수(땅의 기울기이자 캐릭터의 각도의 탄젠트값)
function f_prime(t:Number):Number { return (3*t*t - 1400*t + 100000)/100000 }

// 캐릭터를 회전시킨다
function calRot():void {
 var tan:Number = f_prime(crt.x)
 crt.rotation = Math.atan(tan) * 180/Math.PI
}


함수 calRot()을 좌우 이동할 때, 떨어지다 땅에 붙을 때 호출하면 된다.

예제에서는 위 방향키를 누르면 점프도 하지만 이건 구현하기도 쉽고 글의 주제와 무관하니까 설명을 생략한다.

조금 더 응용하자면, 함수 하나로는 복잡한 지형을 표현할 수 없으니 x의 범위를 나눠서 [0, 100] 구간에서는 f(x), (100, 300] 구간에서는 g(x), (300, 500] 구간에서는 h(x)를 이용한다거나, 땅이 한 층만 있는 게 아니고 2층, 3층 등 여러 층일 때는 매 순간 정의역이 캐릭터의 x 좌표를 포함하면서 화면상 캐릭터의 바로 밑에 있는 함수를 찾아서 그 함수를 가지고 작업하면 될 것이다.

4 Comments
댓글쓰기 폼