123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 |
- -- Copyright (c) 2011-2012 Casey Baxter
- -- See LICENSE file for details
- ---------------------------------------------------------------------------------------------------
- -- -= Map =-
- ---------------------------------------------------------------------------------------------------
- -- Setup
- -- Import the other classes
- local PATH = (...):gsub("[\\/]", ""):match(".+%.") or ''
- --local Tile = require(PATH .. "Tile")
- local TileSet = require(PATH .. "TileSet")
- local TileLayer = require(PATH .. "TileLayer")
- local Object = require(PATH .. "Object")
- local ObjectLayer = require(PATH .. "ObjectLayer")
- local ceil = math.ceil
- local floor = math.floor
- -- Make our map class
- local Map = {class = "Map"}
- Map.__index = Map
- ---------------------------------------------------------------------------------------------------
- -- Returns a new map
- function Map.init(o)
- local map = setmetatable(o, Map)
-
- map.viewX, map.viewY = 0, 0
- map.viewW, map.viewH = love.graphics.getDimensions()
- map.viewScaling = 1
- map.viewPadding = 10
-
- map.offsetX, map.offsetY = 0, 0
-
- --map.layers = {} -- Layers of the map indexed by name
- --map.tilesets = {} -- Tilesets indexed by name
- --map.tiles = {} -- Tiles indexed by id
- --map.layerOrder = {} -- The order of the layers. Callbacks are called in this order.
- -- Private:
- map._widestTile = 0 -- The widest tile on the map.
- map._highestTile = 0 -- The tallest tile on the map.
- map._forceRedraw = false -- If true then the next redraw is forced
- map._previousUseSpriteBatch = false -- The previous useSpiteBatch.
- map._tileClipboard = nil -- The value that stored for tile copying and pasting.
-
- -- Return the new map
- return map
- end
- ---------------------------------------------------------------------------------------------------
- -- Creates a new tileset and adds it to the map. The map will then auto-update its tiles.
- --function Map:newTileSet(...)
- -- local tileset = TileSet:new(...)
- -- local name = tileset.name
- -- if self.tilesets[name] then
- -- error( string.format("Map:newTileSet - A tile set named \"%s\" already exists.", name) )
- -- end
- -- self.tilesets[name] = TileSet:new(...)
- -- self:updateTiles()
- -- return tileset
- --end
- ---------------------------------------------------------------------------------------------------
- -- Creates a new TileLayer and adds it to the map. The position parameter is the position to insert
- -- the layer into the layerOrder.
- --function Map:newTileLayer(position, ...)
- -- local layer = TileLayer:new(self, ...)
- -- local name = layer.name
- -- if self.layers[name] then
- -- error( string.format("Map:newTileLayer - A layer named \"%s\" already exists.", name) )
- -- end
- -- self.layers[name] = layer
- -- table.insert(self.layerOrder, position or #self.layerOrder + 1, layer)
- -- return layer
- --end
- ---------------------------------------------------------------------------------------------------
- -- Creates a new ObjectLayer and inserts it into the map
- --function Map:newObjectLayer(position, ...)
- -- local layer = ObjectLayer:new(self, ...)
- -- local name = layer.name
- -- if self.layers[name] then
- -- error( string.format("Map:newObjectLayer - A layer named \"%s\" already exists.", name) )
- -- end
- -- self.layers[name] = layer
- -- table.insert(self.layerOrder, position or #self.layerOrder + 1, layer)
- -- return layer
- --end
- ---------------------------------------------------------------------------------------------------
- -- Add a custom layer to the map. You can include a predefined layer table or one will be created.
- function Map:newCustomLayer(name, position, layer)
- if self.layers[name] then
- error( string.format("Map:newCustomLayer - The layer name \"%s\" already exists.", name) )
- end
- self.layers[name] = layer or {name=name}
- self.layers[name].class = "CustomLayer"
- table.insert(self.layerOrder, position or #self.layerOrder + 1, self.layers[name])
- return self.layers[name]
- end
- ---------------------------------------------------------------------------------------------------
- -- Cuts tiles out of tilesets and stores them in the tiles tables under their id
- -- Call this after the tilesets are set up
- function Map:updateTiles()
- self.tiles = {}
- self._widestTile = 0
- self._highestTile = 0
- for _, ts in pairs(self.tilesets) do
- if ts.tileWidth > self._widestTile then self._widestTile = ts.tileWidth end
- if ts.tileHeight > self._highestTile then self._highestTile = ts.tileHeight end
- for id, val in pairs(ts:getTiles()) do
- self.tiles[id] = val
- end
- end
- end
- ---------------------------------------------------------------------------------------------------
- -- Forces the map to redraw the sprite batches.
- function Map:forceRedraw()
- self._forceRedraw = true
- end
- ---------------------------------------------------------------------------------------------------
- -- Performs a callback on all map layers.
- --local layer
- --function Map:callback(cb, ...)
- -- if cb == "draw" then self:_updateTileRange() end
- -- for i = 1, #self.layerOrder do
- -- layer = self.layerOrder[i]
- -- if layer[cb] then layer[cb](layer, ...) end
- -- end
- --end
- ---------------------------------------------------------------------------------------------------
- -- Draw the map.
- function Map:draw()
- if not self.visible then return end
-
- self:_updateTileRange()
- for i, v in ipairs(self.layerOrder) do
- if v.draw then v.draw(v) end
- end
- end
- ---------------------------------------------------------------------------------------------------
- -- returns layer's index inside layerOrder (or nil).
- -- layer can be layerObject or string
- function Map:layerPosition(layer)
- if type(layer) == "string" then layer = self.layers[layer] end
- if not (type(layer) == "table" and layer.class) then return end
- for i, v in ipairs(self.layerOrder) do
- if v == layer then return i end
- end
- end
- ---------------------------------------------------------------------------------------------------
- -- layer1-2 can be layerObject, number or string
- function Map:swapLayers(layer1, layer2)
- if type(layer1) ~= "number" then layer1 = self:layerPosition(layer1) end
- if type(layer2) ~= "number" then layer2 = self:layerPosition(layer2) end
- local lo = self.layerOrder
- local l1, l2 = lo[layer1], lo[layer2]
- if not l1 or not l2 then return end
- lo[layer1], lo[layer2] = l2, l1
- end
- ---------------------------------------------------------------------------------------------------
- -- layer can be layerObject, number or string
- function Map:removeLayer(layer)
- if type(layer) ~= "number" then layer = self:layerPosition(layer) end
- local lo = self.layerOrder
- if not lo[layer] then return end
- layer = table.remove(lo, layer)
- self.layers[layer.name] = nil
- return layer
- end
- ---------------------------------------------------------------------------------------------------
- -- Turns an isometric location into a world location. The unit length for isometric tiles is always
- -- the map's tileHeight. This is both for width and height.
- function Map:fromIso(x, y)
- return ((x - y) / self.tileHeight + self.height - 1) * self.tileWidth / 2, (x + y) / 2
- end
- ---------------------------------------------------------------------------------------------------
- -- Turns a world location into an isometric location
- function Map:toIso(a, b)
- a, b = a or 0, b or 0
- a = b - ((self.height - 1) / 2 + a / self.tileWidth) * self.tileHeight
- b = 2 * b - a
- return a, b
- end
- ---------------------------------------------------------------------------------------------------
- -- Sets the draw range
- function Map:setDrawRange(vx, vy, vw, vh, vp, vs)
- self.viewX, self.viewY, self.viewW, self.viewH = vx, vy, vw, vh
- self.viewPadding = vp or 0
- self.viewScaling = vs or 1
- end
- ---------------------------------------------------------------------------------------------------
- -- Automatically sets the draw range to fit the display
- function Map:autoDrawRange(tx, ty, vs, vp)
- self.viewX = tx and -tx or 0
- self.viewY = ty and -ty or 0
- self.viewW, self.viewH = love.graphics.getDimensions()
- vs = vs and (vs > 0.001 and vs or 0.001) or 1
- self.viewScaling = vs
- self.viewPadding = vp or 0
- end
- ---------------------------------------------------------------------------------------------------
- -- Returns the normal draw range -- FIXME: where is parallax?
- function Map:getDrawRange()
- local vp, vs = self.viewPadding, self.viewScaling
- return self.viewX - vp, self.viewY - vp, self.viewW/vs + vp * 2, self.viewH/vs + vp * 2
- end
- ----------------------------------------------------------------------------------------------------
- -- Private Functions
- ----------------------------------------------------------------------------------------------------
- -- This is an internal function used to update the map's _tileRange, _previousTileRange, and
- -- _specialRedraw
- local x1, y1, x2, y2, heightOffset, widthOffset, tr, ptr, layer
- function Map:_updateTileRange()
-
- -- Offset to make sure we can always draw the highest and widest tile
- heightOffset = self._highestTile - self.tileHeight
- widthOffset = self._widestTile - self.tileWidth
-
- -- Go through each layer
- for i = 1,#self.layerOrder do
- layer = self.layerOrder[i]
- -- If the layer is a TileLayer
- if layer.class == "TileLayer" then
-
- -- Get the draw range.
- x1 = self.viewX * layer.parallaxX - self.viewPadding + layer.offsetX
- y1 = self.viewY * layer.parallaxY - self.viewPadding + layer.offsetY
- x2 = self.viewW/self.viewScaling + self.viewPadding*2
- y2 = self.viewH/self.viewScaling + self.viewPadding*2
-
- -- Apply the offset
- x1 = x1 - self.offsetX - layer.offsetX
- y1 = y1 - self.offsetY - layer.offsetY
-
- -- Calculate the _tileRange for orthogonal tiles
- if self.orientation == "orthogonal" then
-
- -- Limit the drawing range. We must make sure we can draw the tiles that are bigger
- -- than the self's tileWidth and tileHeight.
- if x1 and y1 and x2 and y2 then
- x2 = ceil(x2/self.tileWidth)
- y2 = ceil((y2+heightOffset)/self.tileHeight)
- x1 = floor((x1-widthOffset)/self.tileWidth)
- y1 = floor(y1/self.tileHeight)
-
- -- Make sure that we stay within the boundry of the map
- x1 = x1 > 0 and x1 or 0
- y1 = y1 > 0 and y1 or 0
- x2 = x2 < self.width and x2 or self.width - 1
- y2 = y2 < self.height and y2 or self.height - 1
-
- else
- -- If the drawing range isn't defined then we draw all the tiles
- x1, y1, x2, y2 = 0, 0, self.width-1, self.height-1
- end
-
- -- Calculate the _tileRange for isometric tiles.
- else
- -- If the drawRange is set
- if x1 and y1 and x2 and y2 then
- x1, y1 = self:toIso(x1-self._widestTile,y1)
- x1, y1 = ceil(x1/self.tileHeight), ceil(y1/self.tileHeight)-1
- x2 = ceil((x2+self._widestTile)/self.tileWidth)
- y2 = ceil((y2+heightOffset)/self.tileHeight)
- -- else draw everything
- else
- x1 = 0
- y1 = 0
- x2 = self.width - 1
- y2 = self.height - 1
- end
- end
-
- -- Assign the new values to the tile range
- tr, ptr = layer._tileRange, layer._previousTileRange
- ptr[1], ptr[2], ptr[3], ptr[4] = tr[1], tr[2], tr[3], tr[4]
- tr[1], tr[2], tr[3], tr[4] = x1, y1, x2, y2
-
- -- If the tile range or useSpriteBatch is different than the last frame then we need to
- -- update its sprite batches.
- layer._redraw = self.useSpriteBatch ~= self._previousUseSpriteBatch or
- self._forceRedraw or
- tr[1] ~= ptr[1] or
- tr[2] ~= ptr[2] or
- tr[3] ~= ptr[3] or
- tr[4] ~= ptr[4]
- end
- end
-
- -- Set the previous useSpritebatch
- self._previousUseSpriteBatch = self.useSpriteBatch
-
- -- Reset the forced special redraw
- self._forceRedraw = false
- end
- ---------------------------------------------------------------------------------------------------
- -- Calling the map as a function will return the layer
- function Map:__call(layerName)
- return self.layers[layerName]
- end
- ---------------------------------------------------------------------------------------------------
- -- Returns the Map class
- return Map
|