Refactor level loading into a separate module for easy extraction

This commit is contained in:
Hugues Ross 2021-02-24 06:34:51 -05:00
parent f7e2971ccd
commit e0bc23f95c
2 changed files with 219 additions and 99 deletions

214
level.lua
View File

@ -1,27 +1,24 @@
local api = require("level_api")
local level = {
width = 0,
height = 0,
api = api,
player_x = 0,
player_y = 0,
tile_width = 16,
tile_height = 16,
tiles = {},
quads = {},
tilesets = {},
connections = {
},
}
level.autotile = require("autotile")
function level.init(self, filename, enemies)
enemies:clear()
tiles = {}
connections = {}
api:register_pre_load(function(self, image, data)
self.tile_width = 16
self.tile_height = 16
self.tiles = {}
self.tilesets = {}
self.connectionOffsets = {}
self.connections = {}
self.autotile = require("autotile")
self.levelData = require("levels/" .. filename)
self.width, self.height = image:getDimensions()
self.levelData = data
for i,f in ipairs(self.levelData.walls) do
for i,f in ipairs(data.walls) do
local tex = love.graphics.newImage("tiles/"..f[1])
self.tilesets[i] = {
quad = love.graphics.newQuad(0, 0, self.tile_width, self.tile_height, tex),
@ -32,7 +29,7 @@ function level.init(self, filename, enemies)
args = f[3] or {},
}
end
for i,f in ipairs(self.levelData.floors) do
for i,f in ipairs(data.floors) do
local tex = love.graphics.newImage("tiles/"..f[1])
self.tilesets[i + 5] = {
quad = love.graphics.newQuad(0, 0, self.tile_width, self.tile_height, tex),
@ -43,8 +40,8 @@ function level.init(self, filename, enemies)
args = f[3] or {},
}
end
if self.levelData.doors then
for i,f in ipairs(self.levelData.doors) do
if data.doors then
for i,f in ipairs(data.doors) do
local tex = love.graphics.newImage("tiles/"..f[1])
self.tilesets[i + 9] = {
quad = love.graphics.newQuad(0, 0, self.tile_width, self.tile_height, tex),
@ -56,100 +53,119 @@ function level.init(self, filename, enemies)
}
end
end
end)
local tileData = love.image.newImageData("levels/" .. self.levelData.map)
local connectionOffsets = {}
-- Walls and floors
api:register_color({1, 1, 1}, function(self, x, y, value, data)
local tileIndex = (y - 1) * self.width + x
self.width, self.height = tileData:getDimensions()
for i=1,self.height do
for j=1,self.width do
local tileIndex = (i - 1) * self.width + j
local index = math.ceil(value * 8)
if index >= 5 then
self.tiles[tileIndex] = (9 - index) + 14
else
self.tiles[tileIndex] = index + 10
end
end)
local r, g, b = tileData:getPixel(j - 1, i - 1)
if r == g and r == b then
local index = math.ceil(r * 8)
if index >= 5 then
self.tiles[tileIndex] = (9 - index) + 14
else
self.tiles[tileIndex] = index + 10
end
else
self.tiles[tileIndex] = 0
if g == 0 and b == 0 and r > 0 then
local index = 9 - math.ceil(r * 8)
-- Enemies
api:register_color({1, 0, 0}, function(self, x, y, value, data)
local tileIndex = (y - 1) * self.width + x
local index = 9 - math.ceil(value * 8)
if self.levelData.enemies[index] then
local enemy = self.levelData.enemies[index].data.new((j - 1) * self.tile_width, (i - 1) * self.tile_height)
if self.levelData.enemies[index].props then
for k,v in pairs(self.levelData.enemies[index].props) do
print(k.." = "..v)
enemy[k] = v
end
end
enemies:add(enemy)
if self.levelData.enemies[index].wall then
self.tiles[tileIndex] = self.levelData.enemies[index].wall + 9
else
self.tiles[tileIndex] = (self.levelData.enemies[index].floor or 1) + 14
end
else
self.tiles[tileIndex] = 15
end
elseif r == 0 and g == 0 and b > 0 then
local index = 9 - math.ceil(b * 8)
if index == 8 then
self.player_x = j - 1
self.player_y = i - 1
if self.levelData.transitions[index] then
if self.levelData.transitions[index].door then
self.tiles[tileIndex] = self.levelData.transitions[index].door + 18
elseif self.levelData.transitions[index].tile then
self.tiles[tileIndex] = self.levelData.transitions[index].tile + 14
end
end
elseif self.levelData.transitions[index] then
if self.levelData.transitions[index].door then
self.tiles[tileIndex] = self.levelData.transitions[index].door + 18
elseif self.levelData.transitions[index].tile then
self.tiles[tileIndex] = self.levelData.transitions[index].tile + 14
end
if not connectionOffsets[index] then
connectionOffsets[index] = { j - 1, i - 1 }
end
local transition = self.levelData.transitions[index]
if self.levelData.transitions[index].absolute then
self.connections[tileIndex] = {
x = transition.x,
y = transition.y,
map = transition.map,
}
else
self.connections[tileIndex] = {
x = transition.x + (j - connectionOffsets[index][1]) - 1,
y = transition.y + (i - connectionOffsets[index][2]) - 1,
map = transition.map,
}
end
end
end
if self.levelData.enemies[index] then
local enemy = self.levelData.enemies[index].data.new((x - 1) * self.tile_width, (y - 1) * self.tile_height)
if self.levelData.enemies[index].props then
for k,v in pairs(self.levelData.enemies[index].props) do
print(k.." = "..v)
enemy[k] = v
end
end
-- TODO
self.levelData.enemy_system:add(enemy)
if self.levelData.enemies[index].wall then
self.tiles[tileIndex] = self.levelData.enemies[index].wall + 9
else
self.tiles[tileIndex] = (self.levelData.enemies[index].floor or 1) + 14
end
else
self.tiles[tileIndex] = 15
end
end)
-- Connections
api:register_color({0, 0, 1}, function(self, x, y, value, data)
local tileIndex = (y - 1) * self.width + x
self.tiles[tileIndex] = 0
local index = 9 - math.ceil(value * 8)
if index == 8 then
self.player_x = x - 1
self.player_y = y - 1
if self.levelData.transitions[index] then
if self.levelData.transitions[index].door then
self.tiles[tileIndex] = self.levelData.transitions[index].door + 18
elseif self.levelData.transitions[index].tile then
self.tiles[tileIndex] = self.levelData.transitions[index].tile + 14
end
end
elseif self.levelData.transitions[index] then
if self.levelData.transitions[index].door then
self.tiles[tileIndex] = self.levelData.transitions[index].door + 18
elseif self.levelData.transitions[index].tile then
self.tiles[tileIndex] = self.levelData.transitions[index].tile + 14
end
if not self.connectionOffsets[index] then
self.connectionOffsets[index] = { x - 1, y - 1 }
end
local transition = self.levelData.transitions[index]
if self.levelData.transitions[index].absolute then
self.connections[tileIndex] = {
x = transition.x,
y = transition.y,
map = transition.map,
}
else
self.connections[tileIndex] = {
x = transition.x + (x - self.connectionOffsets[index][1]) - 1,
y = transition.y + (y - self.connectionOffsets[index][2]) - 1,
map = transition.map,
}
end
end
end)
-- Autotiles
api:register_post_load(function(self, image, data)
self.quads = {}
for i,t in ipairs(self.tiles) do
local x = (i - 1) % self.width
local y = math.floor((i - 1) / self.width)
if t >= 10 then
self.quads[i] = self.tilesets[t - 9].func(self.tilesets[t - 9], self:getAdjacentTiles(x, y), self.tilesets[t - 9].args)
self.quads[i] = self.tilesets[t - 9].func(self.tilesets[t - 9], level.getAdjacentTiles(self, x, y), self.tilesets[t - 9].args)
end
end
end)
function level.init(self, filename, enemies)
enemies:clear()
local level_data = require("levels/" .. filename)
level_data.enemy_system = enemies
local loaded_level = self.api:load(love.image.newImageData("levels/" .. level_data.map), level_data)
for k,v in pairs(loaded_level) do
self[k] = v
end
end
-- OLD CODE
function level.draw(self)
for i,t in ipairs(self.tiles) do
local x = (i - 1) % self.width
@ -172,9 +188,9 @@ end
function level.getAdjacentTiles(self, x, y)
return {
self:getTile(x - 1, y - 1), self:getTile(x, y - 1), self:getTile(x + 1, y - 1),
self:getTile(x - 1, y), self:getTile(x, y), self:getTile(x + 1, y),
self:getTile(x - 1, y + 1), self:getTile(x, y + 1), self:getTile(x + 1, y + 1),
level.getTile(self, x - 1, y - 1), level.getTile(self, x, y - 1), level.getTile(self, x + 1, y - 1),
level.getTile(self, x - 1, y), level.getTile(self, x, y), level.getTile(self, x + 1, y),
level.getTile(self, x - 1, y + 1), level.getTile(self, x, y + 1), level.getTile(self, x + 1, y + 1),
}
end
function level.getTileCollision(self, x, y)

104
level_api.lua Normal file
View File

@ -0,0 +1,104 @@
-------
-- Simple API for loading levels from image files
-- @module level_api
-- @alias level
-- @author Hugues Russ
local level = {
colors = {
[1] = {}, -- Red
[2] = {}, -- Green
[3] = {}, -- Yellow
[4] = {}, -- Blue
[5] = {}, -- Magenta
[6] = {}, -- Cyan
[7] = {}, -- White
},
pre_load = {},
post_load = {},
}
--- Register a callback to perform on pixels of a certain color channel.
-- The function will receive the average value of the color in a range from 0-1
-- @param self
-- @param channels A table of three numbers of 1 or 0. (eg. `{ 1, 1, 1 }` = white, `{ 0, 1, 0 }` = green)
-- @param handle_color The color callback (eg. `function on_color(self, x, y, value, level_data)`)
function level.register_color(self, channels, handle_color)
local channel = (channels[1] * 1) + (channels[2] * 2) + (channels[3] * 4)
table.insert(self.colors[channel], handle_color)
end
--- Register a callback to perform before loading a level.
-- This is useful for initialization
-- @param self
-- @param pre_load The pre-load callback (eg. function `on_pre_load(self, level_image, level_data)`)
function level.register_pre_load(self, pre_load)
table.insert(self.pre_load, pre_load)
end
--- Register a callback to perform before after a level.
-- This is useful for cleanup and registering loaded data
-- @param self
-- @param post_load The post-load callback (eg. `function on_post_load(self, level_image, level_data)`)
function level.register_post_load(self, post_load)
table.insert(self.post_load, post_load)
end
--- Load a level from an image file and data table
-- @param self
-- @param image An image representing the level
-- @param data A table containing additional data to pass to callbacks
-- @return A table representing the loaded level
function level.load(self, image, data)
local level_table = {}
for _,fn in ipairs(self.pre_load) do
fn(level_table, image, data)
end
local width, height = image:getDimensions()
for i=1,height do
for j=1,width do
-- Determine the channel from rgb
local r, g, b = image:getPixel(j - 1, i - 1)
local channel = 0
local count = 0
local value = 0
if r ~= 0 then
channel = channel + 1
count = count + 1
value = value + r
end
if g ~= 0 then
channel = channel + 2
count = count + 1
value = value + g
end
if b ~= 0 then
channel = channel + 4
count = count + 1
value = value + b
end
if channel == 0 then
channel = 7
count = 3
value = r + g + b
end -- Black = White channel
-- Handle each callback for the selected channel
for _,fn in ipairs(self.colors[channel]) do
fn(level_table, j, i, value / count, data)
end
end
end
for _,fn in ipairs(self.post_load) do
fn(level_table, image, data)
end
return level_table
end
return level