123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442 |
- -- Copyright (c) 2011-2012 Casey Baxter
- -- See LICENSE file for details
- ---------------------------------------------------------------------------------------------------
- -- -= TileLayer =-
- ---------------------------------------------------------------------------------------------------
- -- Setup
- --local PATH = (...):gsub("[\\/]", ""):match(".+%.") or ''
- local math = math
- local type = type
- local love = love
- --local Grid = require(PATH .. "Grid")
- local TileLayer = {}
- TileLayer.class = "TileLayer"
- TileLayer.__index = TileLayer
- --TileLayer.__call = Grid.__call
- ----------------------------------------------------------------------------------------------------
- -- GRID INTERFACE
- function TileLayer:get(x,y)
- return self.cells[x] and self.cells[x][y]
- end
- TileLayer.__call = TileLayer.get
- function TileLayer:set(x,y,value)
- if not self.cells[x] then self.cells[x] = {} end
- self.cells[x][y] = value
- end
- function TileLayer:clear()
- self.cells = {}
- end
- function TileLayer:rectangle(startX, startY, width, height, includeNil)
- local x, y = startX, startY
- return function()
- while y <= startY + height do
- while x <= startX + width do
- x = x+1
- if self(x-1,y) ~= nil or includeNil then
- return x-1, y, self(x-1,y)
- end
- end
- x = startX
- y = y+1
- end
- return nil
- end
- end
- ----------------------------------------------------------------------------------------------------
- -- Returns a new TileLayer
- function TileLayer.init(o)
- local tl = setmetatable(o, TileLayer)
- tl.cells = {}
- tl.parallaxX = tl.properties.parallaxx or 1 -- The horizontal speed of the parallax. 1 is normal
- tl.parallaxY = tl.properties.parallaxy or 1 -- The vertical speed of the parallax. 1 is normal
- --tl.offsetX = 0 -- Drawing offset X
- --tl.offsetY = 0 -- Drawing offset Y
-
- -- Private:
- tl._redraw = true -- If true then the layer needs to redraw tis sprite batches.
- tl._tileRange = {0,0,0,0} -- Keeps the drawn tile range for the layer
- tl._previousTileRange = {0,0,0,0} -- Previous _tileRange
- tl._batches = {} -- Keeps track of the sprite batches for each tileset
- tl._flippedTiles = {} -- Stores the flipped tile locations.
- -- 1 = flipped X, 2 = flipped Y, 3 = both
- tl._afterTileFunction = nil -- A function that handles drawing things after individual
- -- tiles. This will not work with SpriteBatches.
- tl._afterTileArgs = nil -- Starting arguments for the after tile function
- tl._previousUseSpriteBatch = false -- The previous useSpriteBatch. If this is different then we
- -- need to force a special redraw
- return tl
- end
- ----------------------------------------------------------------------------------------------------
- -- Clears the tile layer of its after tile function.
- function TileLayer:clearAfterTileFunction()
- self._afterTileFunction = nil
- self._afterTileArgs = nil
- end
- ----------------------------------------------------------------------------------------------------
- -- Sets a function that will be called after every tile. funct(layer, x, y, drawX, drawY ...)
- function TileLayer:setAfterTileFunction(funct, ...)
- self._afterTileFunction = funct
- local args = {...}
- if #args > 0 then self._afterTileArgs = args end
- end
- ----------------------------------------------------------------------------------------------------
- -- These are used in TileLayer:draw() but since that function is called so often we'll define them
- -- outside to prevent them from being created and destroyed all the time.
- local map, tile, tiles, postDraw, useSpriteBatch, tile, width, height
- local at, drawX, drawY, flipX, flipY, r, g, b, a, halfW, halfH, rot
- local x1, y1, x2, y2
- -- Draws the TileLayer.
- function TileLayer:draw() --FIXME: holycow!
- -- Early exit of the layer is not visible
- if not self.visible then return end
- -- We access these a lot so we'll shorted them a bit.
- map, tiles = self.map, self.map.tiles
- postDraw = self.postDraw
-
- -- If useSpriteBatch was changed then we need to force the sprite batches to redraw.
- if self.useSpriteBatch ~= self._previousUseSpriteBatch then map:forceRedraw() end
-
- -- Set the previous useSpriteBatch
- self._previousUseSpriteBatch = self.useSpriteBatch
-
- -- If useSpriteBatch is set for this layer then use that, otherwise use the map's setting.
- useSpriteBatch = self.useSpriteBatch ~= nil and self.useSpriteBatch or map.useSpriteBatch
-
- -- We'll blend the set alpha in with the current alpha
- r,g,b,a = love.graphics.getColor()
- love.graphics.setColor(r,g,b, a*self.opacity)
-
- -- Clear sprite batches if the screen has changed.
- if self._redraw and useSpriteBatch then
- for k,v in pairs(self._batches) do
- v:clear()
- end
- end
-
- -- Get the tile range
- x1, y1, x2, y2 = self._tileRange[1], self._tileRange[2], self._tileRange[3], self._tileRange[4]
-
- -- Translate for the parallax
- if self.parallaxX ~= 1 or self.parallaxY ~= 1 then
- love.graphics.push()
- love.graphics.translate(math.floor(map.viewX - map.viewX*self.parallaxX),
- math.floor(map.viewY - map.viewY*self.parallaxY))
- end
-
- -- Only draw if we're not using sprite batches or we need to update the sprite batches.
- if self._redraw or not useSpriteBatch then
-
- -- Bind the sprite batches
- if useSpriteBatch then
- --for k, batch in pairs(self._batches) do
- -- batch:bind()
- --end
- end
-
- -- Orthogonal tiles
- if map.orientation == "orthogonal" then
-
- -- Go through each tile
- for x,y,tile in self:rectangle(x1,y1,x2,y2) do
-
- -- Get the half-width and half-height
- halfW, halfH = tile.width*0.5, tile.height*0.5
-
- -- Draw the tile from the bottom left corner
- drawX, drawY = (x)*map.tileWidth, (y+1)*map.tileHeight
-
- -- Apply the offset
- drawX = drawX - map.offsetX - self.offsetX
- drawY = drawY - map.offsetY - self.offsetY
-
- -- Get the flipped tiles
- local ft = self._flippedTiles[x]
- ft = ft and ft[y]
- if ft then
- rot = (ft % 2) == 1 and true or false
- flipY = (ft % 4) >= 2 and -1 or 1
- flipX = ft >= 4 and -1 or 1
- if rot then flipX, flipY = -flipY, flipX end
- else
- rot, flipX, flipY = false, 1, 1
- end
-
- -- If we are using spritebatches
- if useSpriteBatch then
- -- If we dont have a spritebatch for the current tile's tileset then make one
- if not self._batches[tile.tileset] then
- self._batches[tile.tileset] = love.graphics.newSpriteBatch(
- tile.tileset.image, map.width * map.height)
- --self._batches[tile.tileset]:bind()
- end
- -- Add the quad to the spritebatch
- self._batches[tile.tileset]:add(tile.quad, drawX + halfW,
- drawY - halfH,
- rot and math.pi*1.5 or 0,
- flipX, flipY, halfW, halfH)
-
- -- If we are not using spritebatches
- else
- -- Draw the tile
- tile:draw(drawX + halfW,
- drawY - halfH,
- rot and math.pi*1.5 or 0,
- flipX, flipY, halfW, halfH)
-
- -- Call the after tile function
- if self._afterTileFunction then
- if self._afterTileArgs then
- self._afterTileFunction(self, x, y, drawX, drawY,
- unpack(self._afterTileArgs))
- else
- self._afterTileFunction(self, x, y, drawX, drawY)
- end
- end
- end
- end
- end
-
- -- Isometric tiles
- if map.orientation == "isometric" then
- local x,y
-
- -- Get the starting x drawing location
- local draw_start = map.height * map.tileWidth/2
-
- -- Draw each tile starting from the top left tile. Make sure we have enough
- -- room to draw the widest and tallest tile in the map.
- for down=0,y2 do
- for layer=0,1 do
- for right=0,x2 do
- x = x1 + right + down + layer - 1
- y = y1 - right + down - 1
-
- -- If there is a tile row
- tile = self(x,y)
- if tile then
-
- -- Check and see if the tile is flipped
- local ft = self.__flippedTiles[x]
- ft = ft and ft[y]
- if ft then
- rot = (ft % 2) == 1 and true or false
- flipY = (ft % 4) >= 2 and -1 or 1
- flipX = ft >= 4 and -1 or 1
- if rot then flipX, flipY = -flipY, flipX end
- else
- rot, flipX, flipY = false, 1, 1
- end
-
- -- Get the tile
- --tile = self(x,y)
-
- -- If the tile exists then draw the tile
- --if tile then
-
- -- Get the half-width and half-height
- halfW, halfH = tile.width*0.5, tile.height*0.5
-
- -- Get the tile draw location
- drawX = math.floor(draw_start + map.tileWidth/2 * (x - y-2))
- drawY = math.floor(map.tileHeight/2 * (x + y+2))
-
- -- Apply the offset
- drawX = drawX - map.offsetX - self.offsetX
- drawY = drawY - map.offsetY - self.offsetY
-
- -- Using sprite batches
- if useSpriteBatch then
- -- If we dont have a spritebatch for the current tile's tileset
- -- then make one
- if not self._batches[tile.tileset] then
- self._batches[tile.tileset] = love.graphics.newSpriteBatch(
- tile.tileset.image,
- map.width * map.height)
- -- Bind the sprite batch
- self._batches[tile.tileset]:bind()
- end
- -- Add the tile to the sprite batch.
- self._batches[tile.tileset]:add(tile.quad, drawX + halfW +
- (rot and halfW or 0),
- drawY-halfH+(rot and halfW or 0),
- rot and math.pi*1.5 or 0,
- flipX, flipY, halfW, halfH)
-
- -- Not using sprite batches
- else
- tile:draw(drawX + halfW + (rot and halfW or 0),
- drawY - halfH + (rot and halfW or 0),
- rot and math.pi*1.5 or 0,
- flipX, flipY, halfW, halfH)
-
- -- Call the after tile function
- if self._afterTileFunction then
- if self._afterTileArgs then
- self._afterTileFunction(self, x, y, drawX, drawY,
- unpack(self._afterTileArgs))
- else
- self._afterTileFunction(self, x, y, drawX, drawY)
- end
- end
-
- end
- --end
- end
- end
- end
- end
- end
-
- -- Unbind the sprite batches
- if useSpriteBatch then
- --for k, batch in pairs(self._batches) do
- -- batch:unbind()
- --end
- end
-
- end
-
- -- We finished redrawing
- self._redraw = false
-
- -- If sprite batches are turned on then render them
- if useSpriteBatch then
- for k, batch in pairs(self._batches) do
- love.graphics.draw(batch)
- end
- end
-
- -- If we applied a translation for our parallax then remove it
- if self.parallaxX ~= 1 or self.parallaxY ~= 1 then
- love.graphics.pop()
- end
-
- -- Change the color back
- love.graphics.setColor(r,g,b,a)
- end
- ----------------------------------------------------------------------------------------------------
- -- This copies a tile so that you can paste it in another spot. The pasted tile will keep the
- -- rotation and flipped status. You can copy and paste between layers.
- local flippedVal = 2^29
- function TileLayer:tileCopy(x,y)
- if not self(x,y) then
- self.map._tileClipboard = 0
- else
- local ft = self._flippedTiles[x]
- self.map._tileClipboard = self(x,y).id + ((ft and ft[y]) or 0) * flippedVal
- end
- end
- ----------------------------------------------------------------------------------------------------
- -- Paste a copied tile.
- function TileLayer:tilePaste(x,y)
- self._redraw = true
- if not self.map._tileClipboard then
- error("TileLayer:tilePaste() - A tile must be copied with tileCopy() before pasting")
- end
- local clip = self.map._tileClipboard
- if clip / flippedVal > 0 then
- local fts = self._flippedTiles
- if not fts[x] then fts[x] = {} end
- fts[x][y] = math.floor(clip / flippedVal)
- end
- self:set(x, y, self.map.tiles[clip % flippedVal])
- end
- ----------------------------------------------------------------------------------------------------
- -- Flip the tile's X. If doFlip is not specified then the flip is toggled.
- function TileLayer:tileFlipX(x, y, doFlip)
- local fts = self._flippedTiles
- if not fts[x] then fts[x] = {} end
- self._redraw = true
- local flip = fts[x][y] or 0
- if doFlip ~= false and flip < 4 then
- flip = flip + 4
- elseif doFlip ~= true and flip >= 4 then
- flip = flip - 4
- end
- fts[x][y] = flip ~= 0 and flip or nil
- end
- ----------------------------------------------------------------------------------------------------
- -- Flip the tile's Y. If doFlip is not specified then the flip is toggled.
- function TileLayer:tileFlipY(x, y, doFlip)
- local fts = self._flippedTiles
- if not fts[x] then fts[x] = {} end
- self.redraw = true
- local flip = fts[x][y] or 0
- if doFlip ~= false and flip % 4 < 2 then
- flip = flip + 2
- elseif doFlip ~= true and flip % 4 >= 2 then
- flip = flip - 2
- end
- fts[x][y] = flip ~= 0 and flip or nil
- end
- ----------------------------------------------------------------------------------------------------
- -- Rotate the tile.
- function TileLayer:tileRotate(x, y, rot)
- local fts = self._flippedTiles
- if not fts[x] then fts[x] = {} end
-
- local flip = fts[x][y] or 0
- if rot then flip = rot % 8
- elseif flip == 0 then flip = 5
- elseif flip == 1 then flip = 4
- elseif flip == 2 then flip = 1
- elseif flip == 3 then flip = 0
- elseif flip == 4 then flip = 7
- elseif flip == 5 then flip = 6
- elseif flip == 6 then flip = 3
- elseif flip == 7 then flip = 2
- end
-
- fts[x][y] = flip ~= 0 and flip or 0
- end
- ----------------------------------------------------------------------------------------------------
- -- Private
- ----------------------------------------------------------------------------------------------------
- local FLIPPED = 0x20000000
- function TileLayer:_populate(t)
- self:clear()
-
- local map = self.map
- local width, height = map.width, map.height
- local tiles = map.tiles
- local tid
- for x = 0, width - 1 do
- for y = 0, height - 1 do
- tid = t[width * y + x + 1]
- if tid then
- if tid >= FLIPPED then
- local fts = self._flippedTiles
- if not fts[x] then fts[x] = {} end
- fts[x][y] = math.floor(tid / FLIPPED)
- tid = tid % FLIPPED
- end
- self:set(x, y, tiles[tid])
- end
- end
- end
- end
- return TileLayer
|