2015년 11월 28일 토요일

corona sdk, Million Tile Engine 로 만드는 게임(1)


스토리는 지금으로 부터 한 20년전으로 거슬러 올라갑니다.
PC가 흔하지 않던 시절 허큘레스 그래픽 카드가 점령하던 시절에 화려한 그래픽이 있는 게임을 만들기란 쉬운일이 아니었습니다. 그래서 text기반의 게임을 만들곤 했습니다.
ascii 코드로 주인공을 만들고, 적을 만들고 벽을 만들어 적을 피하고 목적을 이루는 text기반 게임을 만들었었는데....
그때의 추억을 되살려 부활 작업을 하도록 해보겠습니다.

한번에 많이 작업하지는 못하고 이번에는 tile을 화면에 나타내고 적들을 만들어 내서 화면상에서 움직이는 작업을 하겠습니다.
기본 구성은 아래와 같습니다.





타일 이미지는 아래 링크로 부터 다운 받았습니다. 그리고 크기를 두배로 확대하였습니다.
http://adamatomic.com/bomberplanet/assets.html

Million Tile Engine 엔진을 이용하겠습니다.

이미지는 text packer를 이용하여 붙여서 xml 데이터를 만들어서 level director로 불러 들였습니다.

1. 맵로딩
맵을 화면에 뿌리는건 다음의 코드가 합니다.
mte.loadMap("resource/hitnrun.tmx") 

그리고 리소를 불러들이는 코드 즉 움직이는 Sprite는 LevelDirect를 이용하였습니다.
myLevel = LD:loadLevel("LoadRes")

2. 적의 위치를 표시
tiled에서 object layer에 object를 만들고 type에 red,green등 이름을 넣습니다.
이 이름을 바탕으로 소스상에서 sprite를 생성하도록 합니다.



코드상에는 아래와 같이 object들을 모두 검색해서 좌표를바탕으로 sprite들이 생성되도록 합니다.
local function spawnEnemyObject()
 enemyTable = {}
 enemySize = 1;
 local objects = mte.getObject({})
 for key,value in pairs(objects) do
  --print(value.name, value.type, value.x, value.y, value.properties)
  if( value.type=="red" )then
   ...
  end
  if( value.type=="green" )then
                        ...
  end
 end
end

3. 적을 움직이기
gameloop 안에서는 아래와 같은 코드가 있습니다.
각각의 enemy들을 꺼내 isMoving이 아닐때만 방향전환을 합니다.

 for i=1, enemySize-1 do
  eObject = enemyTable[i]
  inputDir = tonumber(eObject.thismovement)%360
  if not eObject.isMoving then
                ...

왜냐하면 아래와 같이 moveSpriteTo 함수를 사용하게 되는 tile의 배수로 이동하게 됩니다. 다음 타일 위치까지 이동하게되면 더이상 움직이지 않고 멈추기때문에 eObject.isMoving이 flase가 됩니다. 이때는 방향을 어디로 갈것인지 새로 정해줘야 합니다.
mte.moveSpriteTo( { sprite = eObject, locX = xTile, locY = yTile, time = enemyProperty[eObject.enemyType].enemyMoveTime, transition = easing.linear } )

4. Random?
적을 무조건 random하게 움직이면 재미가 없습니다.
아래 코드는 enemy type별로 random을 구해서 방향 전환을 하는지에 대한 결정을 하게 됩니다.
thismovement에는 "0","90","180","270" 등 각도에 대한 문자가 들어가게 됩니다.
   if math_random(100) < enemyProperty[eObject.enemyType].chanceChangeDir then
    if math_random(4)<=2 then
     eObject.thismovement = tostring((tonumber(eObject.thismovement)+90+(math_random(2)-1)*180)%360)
    end
   end


5. 전체 소스


--------------------------------------------------------------------------------
display.setStatusBar(display.HiddenStatusBar)
--------------------------------------------------------------------------------
-- TODO
-- 1. Move enemy with AI

--------------------------------------------------------------------------------
-- Localize
--------------------------------------------------------------------------------
local mte = require('mte').createMTE()        --Load and instantiate MTE.
local physics = require ("physics")
local LD = require("lib.LD_LoaderG2")


local myLevel = {}
local math_ceil = math.ceil
local math_abs = math.abs
local math_random = math.random
local table_insert = table.insert
local map
local player = {}
local dir = 0
local drill = false
local gravity = 1
local speed = 60
local printMemUsageCount = 0
local usePhysics = false
local TileSize = 32
local enemyProperty = {}
enemyProperty["red"] = {}
enemyProperty["red"].enemyMoveTime = 300
enemyProperty["red"].chanceChangeDir = 0 -- 움직이면서 방향전환할 확률 0 전환하지 않음 

enemyProperty["green"] = {}
enemyProperty["green"].enemyMoveTime = 300
enemyProperty["green"].chanceChangeDir = 50 -- 움직이면서 방향전환할 확률 0 전환하지 않음 

local enemyTable = {}
local enemySize = 0;
local atlas = {}
atlas["0"] = {0, -1}
atlas["90"] = {1, 0}
atlas["180"] = {0, 1}
atlas["270"] = {-1, 0}
atlas["360"] = {0, -1}
math.randomseed( os.time() )

--------------------------------------------------------------------------------
-- Functions
--------------------------------------------------------------------------------
local distanceBetween = function(x1, y1, x2, y2) return (((x2 - x1) ^ 2) + ((y2 - y1) ^ 2)) ^ 0.5 end
local getPointsAlongLine = function(x1, y1, x2, y2, d) local points = {} local diffX = x2 - x1 local diffY = y2 - y1 local distBetween = math_ceil(distanceBetween(x1, y1, x2, y2) / d) local x, y = x1, y1 local addX, addY = diffX / distBetween, diffY / distBetween for i = 1, distBetween do table_insert(points, {x, y}) x, y = x + addX, y + addY end return points end
local clamp = function(v, l, h) return (v < l and l) or (v > h and h) or v end

--------------------------------------------------------------------------------
-- Physics
--------------------------------------------------------------------------------
if usePhysics then
 physics.start()
 physics.setGravity(0, 9.8)
 mte.enableBox2DPhysics()
end

--------------------------------------------------------------------------------
-- Load Map
--------------------------------------------------------------------------------

mte.loadMap("resource/hitnrun.tmx")            --Load a map.
myLevel = LD:loadLevel("LoadRes")

--------------------------------------------------------------------------------
-- Physics
--------------------------------------------------------------------------------
--physics.setDrawMode( "hybrid" )

--------------------------------------------------------------------------------
-- Engine dependency code
--------------------------------------------------------------------------------
------------------------------------
local function spawnObject(pname,px,py,passetName,physicsTrue)
------------------------------------
 local function mteSpriteSetup(object,pname,px,py)
  local setup = {
   kind = "sprite", 
   layer = mte.getSpriteLayer(1), 
   levelPosX = px, 
   levelPosY = py,
   name = pname,
   level = 1
   }
  mte.addSprite(object, setup)
 end

 local objProps = 
 {
  name  = pname, 
  x  = px,
  y  = py,
  xScale  =  1,
  yScale  =  1,
  assetName = passetName,
 }
 -- For LD
 -- createObject param nil need not remove object.
 -- That is changed.
 local object = myLevel:createObject(nil, objProps).view 
 -- For Map Engine
 mteSpriteSetup(object,pname,px,py);
 if usePhysics then
  if( physicsTrue ~= nil ) then
   if( physicsTrue == true ) then
    physics.addBody(object, "dynamic", { density=10.0, friction=0.1, bounce=0.1 })
    object.isFixedRotation = true
   end
  end
 end
 return object
end

local function removeObject(displayObject)
 -- body
 mte.removeSprite(displayObject, true)
 -- For Dust map.layer["Object Layer 1"]:remove(displayObject)
end

local function getTileInfo( screenX, screenY, layer )
 local loc = mte.convert("levelPosToLoc", screenX, screenY, layer)
 print("Tile Loc",loc.x, loc.y)
 local tile = mte.getTileObj(loc.x, loc.y, layer)
 -- For Dust 
 --map.layer["Tile Layer 1"].tileByPixels(screenX, screenY)
 return tile
end

local function engineLoop(track)
 -- For MTE
 --mte.setCamera({sprite = track, scale = 1})
 mte.update() --Required to process the camera and display the map.
 mte.debug()  --Displays onscreen position/fps/memory text.

 -- For Dust 
 --map.updateView()
end

local function getTileId(tile)
 local id = tile.index
 return id
end

local function getTileXY(tile)
 local x = tile.locX
 local y = tile.locY
 return x , y
end

local function removeTile( tileX, tileY )
 mte.updateTile({locX = tileX, locY = tileY, layer = 1, tile = 0})
 -- For Dust
 --map.layer["Tile Layer 1"].lockTileErased(tile.tileX, tile.tileY)
end

local function setCamera(track)
 mte.constrainCamera({})
 mte.setCamera({sprite = track, scale = 1})
 mte.setCameraFocus(track)
 -- For Dust map.setCameraFocus(track,true,true)
end

local function setLocalCamera()
 local mapObj = mte.setCamera({locX = 15, locY = 15, scale = 1}) 
end

--DETECT OBSTACLES ------------------------------------------------------------
local obstacle = function(level, locX, locY)
 local detect = mte.getTileProperties({level = level, locX = locX, locY = locY})
 for i = 1, #detect, 1 do
  if detect[i].properties then
   if detect[i].properties.solid and i == 2 then
    detect = "stop"
    return detect
   end
  end
 end
end
--------------------------------------------------------------------------------
-- Engine dependency code END
--------------------------------------------------------------------------------





------------------------------------
local monitorMem = function()
------------------------------------
 collectgarbage()
 print( "MemUsage: " .. collectgarbage("count") )
 local textMem = system.getInfo( "textureMemoryUsed" ) / 1000000
 print( "TexMem: " .. textMem )
end


------------------------------------
------------------------------------
------------------------------------
------------------------------------
local gameLoop = function(event)
------------------------------------
------------------------------------
------------------------------------
------------------------------------
 local eObject
 local inputDir
 
 for i=1, enemySize-1 do
  eObject = enemyTable[i]
  inputDir = tonumber(eObject.thismovement)%360
  if not eObject.isMoving then
   if math_random(100) < enemyProperty[eObject.enemyType].chanceChangeDir then
    if math_random(4)<=2 then
     eObject.thismovement = tostring((tonumber(eObject.thismovement)+90+(math_random(2)-1)*180)%360)
    end
   end

   --MOVE eObject CHARACTER
   if eObject.thismovement then
    local result
    local xTile, yTile
    for i=1,4 do
     xTile, yTile = eObject.locX + atlas[eObject.thismovement][1], eObject.locY + atlas[eObject.thismovement][2]
     result = obstacle( eObject.level, xTile, yTile )
     if( result ~= "stop" ) then
      break
     end
     eObject.thismovement = tostring(math_random(4)*90)
     if( inputDir%180 == tonumber(eObject.thismovement)%180 ) then
      eObject.thismovement = tostring(math_random(4)*90)
      if( inputDir%180 == tonumber(eObject.thismovement)%180 ) then
       eObject.thismovement = tostring(math_random(4)*90)
       if( inputDir%180 == tonumber(eObject.thismovement)%180 ) then
        eObject.thismovement = tostring(math_random(4)*90)
       end
      end
     end
    end
    if not result then
     if eObject.sequence ~= eObject.thismovement then
      eObject:setSequence( eObject.thismovement )
     end
     eObject:play()
     mte.moveSpriteTo( { sprite = eObject, locX = xTile, locY = yTile, time = enemyProperty[eObject.enemyType].enemyMoveTime, transition = easing.linear } )
    end
   else
    eObject:pause()
   end
  end
 end


 
 engineLoop(player)

 printMemUsageCount = printMemUsageCount + 1;
 if( printMemUsageCount > 100 ) then
  printMemUsageCount = 0
  --monitorMem()
 end
end


local function spawnEnemyObject()
 enemyTable = {}
 enemySize = 1;
 local objects = mte.getObject({})
 for key,value in pairs(objects) do
  --print(value.name, value.type, value.x, value.y, value.properties)
  if( value.type=="red" )then
   local enemy = spawnObject("red",value.x+TileSize/2,value.y+TileSize/2,"enemy_32_1_red.png")
   enemy:setSequence("move")
   enemy:play()
   enemy.enemyType = "red"
   enemy.thismovement = tostring(math_random(4)*90)
   enemyTable[enemySize] = enemy;
   enemySize=enemySize+1
  end
  if( value.type=="green" )then
   local enemy = spawnObject("green",value.x+TileSize/2,value.y+TileSize/2,"enemy_32_1.png")
   enemy:setSequence("move")
   enemy:play()
   enemy.enemyType = "green"
   enemy.thismovement = tostring(math_random(4)*90)
   enemyTable[enemySize] = enemy;
   enemySize=enemySize+1
  end
 end
end


------------------------------------
local function spawnPlayerObject()
------------------------------------

end
--------------------------------------------------------------------------------

Runtime:addEventListener("enterFrame", gameLoop)

spawnPlayerObject()

spawnEnemyObject()

--setCamera(player)
setLocalCamera()

소스 링크
https://drive.google.com/file/d/0B9vAKDzHthQITFRPRmtRb2lLQW8/view?usp=sharing

댓글 없음:

댓글 쓰기