2015년 7월 17일 금요일

corona sdk 타워 디펜스 소스 분석 (상)




타워 디펜스
http://www.tandgapps.co.uk/resources/tutorial-tower-defense-for-ios/
소스
http://www.tandgapps.co.uk/wp-content/uploads/2013/02/TowerDefense.zip


스테이지 구성

gameinfo.lua에서 levelSetup() 함수에서 map을 구성 하게 됩니다. 맵데이터는 level?.lua 파일에서 데이터로 들어오게 됩니다.
local level = require("level"..currentLevel)
level?.lua 파일을 살펴보면 level1.lua, level2.lua, level3.lua 3개의 파일이 존재합니다.
핵심이 되는 맵을 구성하는 부분은 tileArray 입니다.
0은 녹색 타일이고, 1은 길, 2는 물이 됩니다.
sp는 적의 시작지점, p1,p2는 적을 움직이기 위한 enemypoint가 됩니다.
ep는 끝지점이 됩니다.

local tileArray = {
 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,    0,
 0,   0,  "p2", 1,   1,  "p3", 0,   0,   0,   0,   0,    0,
 0,   0,   1,   0,   0,   1,   0,   0,   0,   0,   0,    0,
 0,   0,   1,   0,   0,   1,   0,  "p6", 1,  "p7", 0,    0,
 0,   0,   1,   0,   0,   1,   0,   1,   0,   1,   0,    0,
   "sp", 1,  "p1", 0,   0,  "p4", 1,  "p5", 0,   1,   0,    0,
 0,   0,   0,   0,   0,   0,   0,   0,   0,  "p8", 1,   "ep",
 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,    0,
}
M.tileArray = tileArray

해당데이터를 가지고 levelSetup() 에서 그려주는 부분은 아래와 같습니다.
0,1,2를 제외하고는 내용이 어렵습니다.
소스에서는 moveArray[j][3] 이 groundImage로 사용되는 부분입니다.


  --Create the level based off the array..
  local i
  for i=1, #tileArray do
   local placementAllowed = false
   local groundImage
   local xPos, yPos
   local numMoveArray = #moveArray

   --Sort out placeable areas etc...
   if tileArray[i] == 0 then 
    groundImage = "images/grass.png" 
    placementAllowed = true
   elseif tileArray[i] == 1 then
    groundImage = "images/floor.png" 
   elseif tileArray[i] == 2 then 
    groundImage = "images/water_01.png" 
   else
    local j
    for j=1, numMoveArray do
     if moveArray[j][2] == i then
      groundImage = moveArray[j][3];
     end
    end
   end

   --Now create the level rectangle...
   levelRects[i] = display.newImageRect(groundImage,40,40)

해당 부분을 이해하려면 moveArray가 만들어지는 부분을 이해 해야합니다.
level?.lua로 다시돌아가면 아래와 같은 내용이 있습니다.


for i=1, #tileArray do
 --Controls whats on the ground aswell as enemy direction.
 if tileArray[i] == "sp" then moveArray[1] = { "right", i, "images/floor.png" } 
 elseif tileArray[i] == "p1" then moveArray[2] = { "up", i, "images/floor.png" } 
 elseif tileArray[i] == "p2" then moveArray[3] = { "right", i, "images/floor.png" } 
 elseif tileArray[i] == "p3" then moveArray[4] = { "down", i, "images/floor.png" }
 elseif tileArray[i] == "p4" then moveArray[5] = { "right", i, "images/floor.png" }
 elseif tileArray[i] == "p5" then moveArray[6] = { "up", i, "images/floor.png" } 
 elseif tileArray[i] == "p6" then moveArray[7] = { "right", i, "images/floor.png" } 
 elseif tileArray[i] == "p7" then moveArray[8] = { "down", i, "images/floor.png" } 
 elseif tileArray[i] == "p8" then moveArray[9] = { "right", i, "images/floor.png" } 
 elseif tileArray[i] == "ep" then moveArray[10] = { "right", i, "images/open_spot.png" } 
 end
end

tileArray를 처음부터 scan하면서 sp,p1... 순서대로 moveArray[1]...순서대로 기록이 됩니다.
{"right",i,"image/floor.png"} 3개의 배열값이 있는데 첫번째는 방향을 나타냅니다. 즉 enemy가 가야하는 방향, i 는 인덱스의 위치, 마지막은 길의 이미지입니다. p1,p2등 각각의 이미지를 달리 가져갈 수 있습니다.

다시 gameInfo.lua로 돌아와서, 아래 코드중 빨간색은 moveArray에서 어떤 항목인지 찾는 동작입니다.
for j=1, numMoveArray do
if moveArray[j][2] == i then
groundImage = moveArray[j][3];
end
end
3번째 인자인 이미지를 꺼냅니다.


적 움직임

적 움직임을 이해하기 위해서는 먼저 위의 tileArray를 이해하고 접근해야합니다. 위에서 설명이 되었지만 waypoint 에 대한 3가지 정보를 가진 배열을 가지고 있습니다.{"right",i,"image/floor.png"}
enemy가 가야하는 방향, i 는 인덱스의 위치, 마지막은 길의 이미지입니다.

적의 이동은 gameLoop에서 이루어집니다. gameLoop는 enterFrame에 의해서 매 프레임 마다 호출 됩니다.

Runtime:addEventListener( "enterFrame", gameLoop )

그러면 적들을 생성하는 곳부터 봐야 합니다. 아래 함수는 gameLoop에서 일정 간격으로 호출되어집니다.


 local function gameLoop(event)
  if gameIsActive == true then
   --Increase the int until it spawns an enemy..
   spawnInt = spawnInt + 1
   if spawnInt == 40 and spawned ~= spawnedMax then 
    spawnEnemy()
    spawnInt = 0
    spawned = spawned + 1

    if spawned == spawnedMax then
     print("GAME SHOULD BE ENDING SOON")
    end
   end 

아래는 실째 enemy를 스폰(생성)하는 함수 입니다. levelRects[?]는 화면에 tile형태로 출력되어진 image를 의미합니다.
아래와 같은 의미는 moveArray[1]는 sp 정보를 가진 moveArray입니다. 그리고 moveArray[1][2] 는 두번째 인자 즉 전체 tile map에서의 위치정보를 가진 index입니다.
그리고 levelRects[index].x 의 의미는 sp 타일의 위치 정보를 나타냅니다.
enemy.x = levelRects[moveArray[1][2]].x-40;
enemy.y = levelRects[moveArray[1][2]].y;
enemy.movPoint = "p1" 을 넣고 있습니다.  이것의 의미는 gameLoop안에서 사용한다고 하는데요. 다음 waypoint라고 이해하면 됩니다.

 --Enemy spawn function called from gameLoop...
 local function spawnEnemy()
  local enemy = display.newImageRect("images/enemy1_2.png",40,40)
  enemy.x = levelRects[moveArray[1][2]].x-40;
  enemy.y = levelRects[moveArray[1][2]].y;
  enemy.name = "enemy"; enemy.health = enemyHealth
  enemy.movPoint = "p1" --Used in the gameloop
  physics.addBody(enemy, { isSensor = true } )

  enemy.healthBar1 = display.newRect(0,0,20,3)
  enemy.healthBar1.x = enemy.x; enemy.healthBar1.y = enemy.y -12; 
  enemy.healthBar1:setFillColor(0,255,0)
  
  overlayGroup:insert(enemy.healthBar1)
  enemyGroup:insert(enemy)
 end


매프레임 마다 enemy 를 꺼내서 좌표를 이동해줍니다.
좌표와 이동의 정보는 gameLoopEnemyCheck 함수를 이용해 리턴을 받습니다.
처음 movPoint는 p1입니다.

 local function gameLoop(event)

   local i
   local enemyActive = false
   for i = enemyGroup.numChildren,1,-1 do
    local enemy = enemyGroup[i]
    if enemy ~= nil and enemy.x ~= nil then

     local transDirecX, transDirecY, rect, nextPoint, rotate = gameLoopEnemyCheck(enemy.movPoint)
     enemy:translate( transDirecX, transDirecY)
     enemy.healthBar1:translate( transDirecX, transDirecY)
     enemy.rotation = rotate

     if rect.x == mRound(enemy.x) and rect.y == mRound(enemy.y) then 
      enemy.movPoint = nextPoint 
     end
     enemyActive = true
     endCheckAmount = 0
    end
   end 

gameLoopEnemyCheck함수 입니다.
p1인경우 direc의 값이
rect = levelRects[moveArray[i+1][2]] 
위의 rect는 p1의 경우 i=>1이 되며 i+1=>2가되어 2 index값이 올라오며 해당array에는 p2의 내용이 들어있는데 levelRects는 p1의 tile을 출력한 이미지 정보가 있게 된다.
getDirect(moveArray[i][1])
moveArray[i][1]에는 p1 보다 앞서는 sp 의 정보가 있게되며, [1]은 첫번째 인자 방향정보가 들어있습니다. "right" 이므로 X=1,Y =0, rotate =0의 값을 가지게 됩니다.

 --Called from the gameloop to get direction etc..
 local function gameLoopEnemyCheck( movPoint )
  local transDirecX, transDirecY, rect, nextPoint, rotate

  local function getDirect( moveArray )
   local direc = moveArray
   if direc == "right" then transDirecX = 1; transDirecY = 0; rotate = 0
   elseif direc == "left" then transDirecX = -1; transDirecY = 0; rotate = -180
   elseif direc == "up" then transDirecX = 0; transDirecY = -1; rotate = -90
   elseif direc == "down" then transDirecX = 0; transDirecY = 1; rotate = 90; end
  end

  local i
  for i=1, #moveArray-1 do
   if movPoint == "p"..i then 
    rect = levelRects[moveArray[i+1][2]] 
    getDirect(moveArray[i][1])
    nextPoint = "p"..i+1
   elseif movPoint == "ep" then 
    rect = levelRects[moveArray[i+1][2]] 
    getDirect(moveArray[i][1])
    nextPoint = "ep"
   end
  end

  return transDirecX, transDirecY, rect, nextPoint, rotate
 end

gameLoop함수에서 gameLoopEnemyCheck함수로 리턴된 내용으로 enemy좌표를 이동시키고
enemy:translate( transDirecX, transDirecY)
enemy.x, enemy.y가 각각 다음 waypoint인 rect.x, rect.y 와 같아지면 movPoint에 다음번 waypoint를 넣게 됩니다.









댓글 없음:

댓글 쓰기