스토리는 지금으로 부터 한 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
댓글 없음:
댓글 쓰기