diff --git a/lib/character/behaviors/light.lua b/lib/character/behaviors/light.lua index 1f8b9d8..dd26adc 100644 --- a/lib/character/behaviors/light.lua +++ b/lib/character/behaviors/light.lua @@ -32,22 +32,19 @@ function behavior:draw() local positioned = self.owner:has(Tree.behaviors.positioned) if not positioned then return end - love.graphics.setBlendMode("add", "premultiplied") - Tree.level.camera:attach() - love.graphics.setCanvas(Tree.level.render.textures.lightLayer) - local shader = Tree.assets.files.shaders.light - shader:send("color", { self.color.x, self.color.y, self.color.z }) - shader:send("time", love.timer.getTime() + self.seed) - love.graphics.setShader(shader) - love.graphics.draw(Tree.assets.files.masks.circle128, positioned.position.x - self.intensity / 2, - positioned.position.y - self.intensity / 2, 0, self.intensity / 128, - self.intensity / 128) + Tree.level.render:enqueue(Tree.level.render.LAYERS.LIGHT, positioned.position.y, function() + love.graphics.setBlendMode("add", "premultiplied") + local shader = Tree.assets.files.shaders.light + shader:send("color", { self.color.x, self.color.y, self.color.z }) + shader:send("time", love.timer:getTime() + self.seed) + love.graphics.setShader(shader) + love.graphics.draw(Tree.assets.files.masks.circle128, positioned.position.x - self.intensity / 2, + positioned.position.y - self.intensity / 2, 0, self.intensity / 128, + self.intensity / 128) - love.graphics.setBlendMode("alpha") - - love.graphics.setShader() - love.graphics.setCanvas() - Tree.level.camera:detach() + love.graphics.setShader() + love.graphics.setBlendMode("alpha") + end) end return behavior diff --git a/lib/character/behaviors/shadowcaster.lua b/lib/character/behaviors/shadowcaster.lua index c343cf8..904322e 100644 --- a/lib/character/behaviors/shadowcaster.lua +++ b/lib/character/behaviors/shadowcaster.lua @@ -10,10 +10,7 @@ function behavior:draw() local sprite = self.owner:has(Tree.behaviors.sprite) local positioned = self.owner:has(Tree.behaviors.positioned) if not positioned then return end - if not sprite then - love.graphics.setCanvas() - return - end + if not sprite then return end local ppm = Tree.level.camera.pixelsPerMeter local position = positioned.position + Vec3 { 0.5, 0.5 } @@ -25,41 +22,40 @@ function behavior:draw() table.insert(lights, Tree.level.characters[id]) end + -- 1. Эллипс тени + Tree.level.render:enqueue(Tree.level.render.LAYERS.SHADOW, position.y, function() + love.graphics.push() + love.graphics.setColor(0, 0, 0, 1) + love.graphics.translate(position.x, position.y) + love.graphics.ellipse("fill", 0, 0, sprite.manifest.size / 2, sprite.manifest.size / 2 * math.cos(math.pi / 4)) + love.graphics.pop() + end) - Tree.level.camera:attach() - love.graphics.setCanvas(Tree.level.render.textures.shadowLayer) - love.graphics.push() - love.graphics.setColor(0, 0, 0, 1) - love.graphics.translate(position.x, position.y) - love.graphics.ellipse("fill", 0, 0, sprite.manifest.size / 2, sprite.manifest.size / 2 * math.cos(math.pi / 4)) - love.graphics.pop() + -- 2. Свет на спрайте + if #lights > 0 then + Tree.level.render:enqueue(Tree.level.render.LAYERS.SPRITE_LIGHT, position.y, function() + love.graphics.setBlendMode("add") + for _, light in ipairs(lights) do + local lightPos = light:has(Tree.behaviors.positioned).position + local lightVec = lightPos - position + local lightColor = light:has(Tree.behaviors.light).color + if lightPos.y > position.y then + love.graphics.setColor(lightColor.x, lightColor.y, lightColor.z, + 1 - 0.3 * lightVec:length()) + elseif position.y - lightPos.y < 3 then + love.graphics.setColor(lightColor.x, lightColor.y, lightColor.z, + (1 - easing.easeInSine((position.y - lightPos.y))) - 0.3 * lightVec:length()) + end - - love.graphics.setCanvas(Tree.level.render.textures.spriteLightLayer) - love.graphics.setBlendMode("add") - for _, light in ipairs(lights) do - local lightPos = light:has(Tree.behaviors.positioned).position - local lightVec = lightPos - position - - local lightColor = light:has(Tree.behaviors.light).color - if lightPos.y > position.y then - love.graphics.setColor(lightColor.x, lightColor.y, lightColor.z, - 1 - 0.3 * lightVec:length()) - elseif position.y - lightPos.y < 3 then - love.graphics.setColor(lightColor.x, lightColor.y, lightColor.z, - (1 - easing.easeInSine((position.y - lightPos.y))) - 0.3 * lightVec:length()) - end - - sprite.animationTable[sprite.state]:draw(sprite.sheets[sprite.state], - position.x, - position.y, nil, 1 / ppm * sprite.side, 1 / ppm, sprite.manifest.base.x, sprite.manifest.base.y) + sprite.animationTable[sprite.state]:draw(sprite.sheets[sprite.state], + position.x, + position.y, nil, 1 / ppm * sprite.side, 1 / ppm, sprite.manifest.base.x, sprite.manifest.base.y) + end + love.graphics.setBlendMode("alpha") + love.graphics.setColor(1, 1, 1) + end) end - love.graphics.setBlendMode("alpha") - - Tree.level.camera:detach() - love.graphics.setColor(1, 1, 1) - love.graphics.setCanvas() end return behavior diff --git a/lib/character/behaviors/sprite.lua b/lib/character/behaviors/sprite.lua index 0325a4b..6625b01 100644 --- a/lib/character/behaviors/sprite.lua +++ b/lib/character/behaviors/sprite.lua @@ -49,25 +49,22 @@ function sprite:draw() local ppm = Tree.level.camera.pixelsPerMeter local position = pos.position + Vec3 { 0.5, 0.5 } - love.graphics.setCanvas(Tree.level.render.textures.spriteLayer) - Tree.level.camera:attach() + Tree.level.render:enqueue(Tree.level.render.LAYERS.SPRITE, position.y, function() + love.graphics.setColor(1, 1, 1) + if Tree.level.selector.id == self.owner.id then + local texW, texH = self.sheets[self.state]:getWidth(), + self.sheets[self.state]:getHeight() + local shader = Tree.assets.files.shaders.outline + shader:send("texSize", { texW, texH }) + shader:send("time", love.timer:getTime()) + love.graphics.setShader(shader) + end + self.animationTable[self.state]:draw(self.sheets[self.state], + position.x, + position.y, nil, 1 / ppm * self.side, 1 / ppm, self.manifest.base.x, self.manifest.base.y) - love.graphics.setColor(1, 1, 1) - if Tree.level.selector.id == self.owner.id then - local texW, texH = self.sheets[self.state]:getWidth(), - self.sheets[self.state]:getHeight() - local shader = Tree.assets.files.shaders.outline - shader:send("texSize", { texW, texH }) - shader:send("time", love.timer:getTime()) - love.graphics.setShader(shader) - end - self.animationTable[self.state]:draw(self.sheets[self.state], - position.x, - position.y, nil, 1 / ppm * self.side, 1 / ppm, self.manifest.base.x, self.manifest.base.y) - - love.graphics.setShader() - Tree.level.camera:detach() - love.graphics.setCanvas() + love.graphics.setShader() + end) end ) end diff --git a/lib/level/grid/tile_grid.lua b/lib/level/grid/tile_grid.lua index 1a89a10..8982abb 100644 --- a/lib/level/grid/tile_grid.lua +++ b/lib/level/grid/tile_grid.lua @@ -9,17 +9,32 @@ map.__index = map --- @param size? Vec3 local function new(type, template, size) local tMap = require('lib.level.' .. type).new(template, size) - return setmetatable({ __grid = tMap }, map) + local grid = setmetatable({ __grid = tMap }, map) + grid:refreshBatch() + return grid +end + +function map:refreshBatch() + -- Находим атлас первого попавшегося тайла (предполагаем, что он один для всех) + local _, firstTile = next(self.__grid) + if not firstTile then return end + + local atlas = firstTile.atlasData.atlas + local count = 0 + for _ in pairs(self.__grid) do count = count + 1 end + + self.batch = love.graphics.newSpriteBatch(atlas, count) + for _, tile in pairs(self.__grid) do + -- 1/32 это масштаб, так как размер тайла в мире 1x1 метр, а в атласе 32x32 пикселя + self.batch:add(tile.atlasData.quad, tile.position.x, tile.position.y, 0, 1 / 32, 1 / 32) + end end function map:draw() - love.graphics.setCanvas(Tree.level.render.textures.floorLayer) - Tree.level.camera:attach() - utils.each(self.__grid, function(el) - el:draw() + if not self.batch then return end + Tree.level.render:enqueue(Tree.level.render.LAYERS.FLOOR, 0, function() + love.graphics.draw(self.batch) end) - Tree.level.camera:detach() - love.graphics.setCanvas() end return { new = new } diff --git a/lib/level/level.lua b/lib/level/level.lua index 67945eb..16238e7 100644 --- a/lib/level/level.lua +++ b/lib/level/level.lua @@ -56,9 +56,9 @@ end function level:draw() self.render:clear() self.tileGrid:draw() - while not self.characterGrid.yOrderQueue:is_empty() do -- по сути это сортировка кучей за n log n - self.characterGrid.yOrderQueue:pop():draw() - end + utils.each(self.characters, function(char) + char:draw() + end) self.render:draw() end diff --git a/lib/level/render.lua b/lib/level/render.lua index b0901d8..ae23047 100644 --- a/lib/level/render.lua +++ b/lib/level/render.lua @@ -1,12 +1,24 @@ --- @class Render --- @field textures table +--- @field queue table[] local render = { - textures = {} + textures = {}, + queue = {}, + LAYERS = { + FLOOR = 1, + SHADOW = 2, + LIGHT = 3, + SPRITE = 4, + SPRITE_LIGHT = 5, + OVERLAY = 6 + } } function render:clear() local weather = Tree.level.weather local txs = self.textures + self.queue = {} + love.graphics.setCanvas(txs.shadowLayer) love.graphics.clear() love.graphics.setCanvas(txs.spriteLayer) @@ -21,6 +33,10 @@ function render:clear() love.graphics.clear() end +function render:enqueue(layer, z, func) + table.insert(self.queue, { layer = layer, z = z, func = func }) +end + function render:free() for _, tx in pairs(self.textures) do tx:release() @@ -57,19 +73,98 @@ function render:applyBlur(input, radius) return self.textures.tmp2 end +---@param params {w: number?, h: number?} +---@return table|Render +local function new(params) + local w = params.w or love.graphics.getWidth() + local h = params.h or love.graphics.getHeight() + local lowResScale = 0.5 + + return setmetatable({ + lowResScale = lowResScale, + queue = {}, + textures = { + shadowLayer = love.graphics.newCanvas(w * lowResScale, h * lowResScale), + spriteLayer = love.graphics.newCanvas(w, h), + spriteLightLayer = love.graphics.newCanvas(w * lowResScale, h * lowResScale), + floorLayer = love.graphics.newCanvas(w, h), + overlayLayer = love.graphics.newCanvas(w, h), + lightLayer = love.graphics.newCanvas(w * lowResScale, h * lowResScale), + tmp1 = love.graphics.newCanvas(w * lowResScale, h * lowResScale), + tmp2 = love.graphics.newCanvas(w * lowResScale, h * lowResScale), + } + }, { __index = render }) +end + function render:draw() local weather = Tree.level.weather local txs = self.textures - love.graphics.setCanvas(txs.lightLayer) - love.graphics.draw(self:applyBlur(txs.shadowLayer, 4 * Tree.level.camera.scale)) + + -- 1. Сортировка очереди + table.sort(self.queue, function(a, b) + if a.layer ~= b.layer then + return a.layer < b.layer + end + return a.z < b.z + end) + + -- 2. Рендеринг очереди в соответствующие Canvas + local currentLayer = nil + for _, entry in ipairs(self.queue) do + if entry.layer ~= currentLayer then + if currentLayer then + Tree.level.camera:detach() + local wasLowRes = currentLayer == self.LAYERS.SHADOW or currentLayer == self.LAYERS.SPRITE_LIGHT or + currentLayer == self.LAYERS.LIGHT + if wasLowRes then + love.graphics.pop() + end + end + currentLayer = entry.layer + local isLowRes = currentLayer == self.LAYERS.SHADOW or currentLayer == self.LAYERS.SPRITE_LIGHT or + currentLayer == self.LAYERS.LIGHT + + if currentLayer == self.LAYERS.FLOOR then + love.graphics.setCanvas(txs.floorLayer) + elseif currentLayer == self.LAYERS.SHADOW then + love.graphics.setCanvas(txs.shadowLayer) + elseif currentLayer == self.LAYERS.LIGHT then + love.graphics.setCanvas(txs.lightLayer) + elseif currentLayer == self.LAYERS.SPRITE then + love.graphics.setCanvas(txs.spriteLayer) + elseif currentLayer == self.LAYERS.SPRITE_LIGHT then + love.graphics.setCanvas(txs.spriteLightLayer) + elseif currentLayer == self.LAYERS.OVERLAY then + love.graphics.setCanvas(txs.overlayLayer) + end + + if isLowRes then + love.graphics.push() + love.graphics.scale(self.lowResScale) + end + Tree.level.camera:attach() + end + entry.func() + end + if currentLayer then + Tree.level.camera:detach() + local wasLowRes = currentLayer == self.LAYERS.SHADOW or currentLayer == self.LAYERS.SPRITE_LIGHT or + currentLayer == self.LAYERS.LIGHT + if wasLowRes then + love.graphics.pop() + end + end love.graphics.setCanvas() - -- self.lightLayer:newImageData():encode("png", "lightLayer.png") - -- os.exit(0) + -- 3. Пост-процессинг и композиция + love.graphics.setCanvas(txs.lightLayer) + -- Радиус блюра тоже масштабируем, так как текстура меньше + love.graphics.draw(self:applyBlur(txs.shadowLayer, 4 * Tree.level.camera.scale * self.lowResScale)) + love.graphics.setCanvas() local lightShader = Tree.assets.files.shaders.light_postprocess lightShader:send("scene", txs.floorLayer) - lightShader:send("light", self:applyBlur(txs.lightLayer, 2)) + lightShader:send("light", self:applyBlur(txs.lightLayer, 2 * self.lowResScale)) lightShader:send("ambient", { weather.ambientLight.x, weather.ambientLight.y, weather.ambientLight.z }) love.graphics.setShader(lightShader) love.graphics.draw(txs.floorLayer) @@ -84,23 +179,4 @@ function render:draw() love.graphics.setShader() end ----@param params {w: number?, h: number?} ----@return table|Render -local function new(params) - local w = params.w or love.graphics.getWidth() - local h = params.h or love.graphics.getHeight() - return setmetatable({ - textures = { - shadowLayer = love.graphics.newCanvas(w, h), - spriteLayer = love.graphics.newCanvas(w, h), - spriteLightLayer = love.graphics.newCanvas(w, h), - floorLayer = love.graphics.newCanvas(w, h), - overlayLayer = love.graphics.newCanvas(w, h), - lightLayer = love.graphics.newCanvas(w, h), - tmp1 = love.graphics.newCanvas(w, h), - tmp2 = love.graphics.newCanvas(w, h), - } - }, { __index = render }) -end - return { new = new } diff --git a/lib/spell/spell.lua b/lib/spell/spell.lua index 5ef80f0..e57ffba 100644 --- a/lib/spell/spell.lua +++ b/lib/spell/spell.lua @@ -54,33 +54,28 @@ function spell:draw() local path = self.path --[[@as Deque?]] if not path then return end --- Это отрисовка пути персонажа к мышке - Tree.level.camera:attach() - love.graphics.setCanvas(Tree.level.render.textures.overlayLayer) - local i = 0 - path:pop_front() - for p in path:values() do - i = i + 1 - local s = 1 / Tree.level.camera.pixelsPerMeter - local quad = i > self.distance and icons:pickQuad('dev_path_closed') or icons:pickQuad('dev_path') - love.graphics.draw(icons.atlas, quad, p.x, p.y, 0, s, s) - end - love.graphics.setCanvas() - Tree.level.camera:detach() - love.graphics.setColor(1, 1, 1) + Tree.level.render:enqueue(Tree.level.render.LAYERS.OVERLAY, 0, function() + local i = 0 + path:pop_front() + for p in path:values() do + i = i + 1 + local s = 1 / Tree.level.camera.pixelsPerMeter + local quad = i > self.distance and icons:pickQuad('dev_path_closed') or icons:pickQuad('dev_path') + love.graphics.draw(icons.atlas, quad, p.x, p.y, 0, s, s) + end + love.graphics.setColor(1, 1, 1) + end) else - Tree.level.camera:attach() - love.graphics.setCanvas(Tree.level.render.textures.overlayLayer) - love.graphics.setColor(1, 1, 1, 0.5) - for _, p in pairs(self.targets) do - local s = self.tSize / Tree.level.camera.pixelsPerMeter - local quad = icons:pickQuad('dev_target') - love.graphics.draw(icons.atlas, quad, p.x + 0.5 - self.tSize / 2, p.y + 0.5 - self.tSize / 2, 0, s, s) - end - love.graphics.setShader() - - love.graphics.setCanvas() - Tree.level.camera:detach() - love.graphics.setColor(1, 1, 1) + Tree.level.render:enqueue(Tree.level.render.LAYERS.OVERLAY, 0, function() + love.graphics.setColor(1, 1, 1, 0.5) + for _, p in pairs(self.targets) do + local s = self.tSize / Tree.level.camera.pixelsPerMeter + local quad = icons:pickQuad('dev_target') + love.graphics.draw(icons.atlas, quad, p.x + 0.5 - self.tSize / 2, p.y + 0.5 - self.tSize / 2, 0, s, s) + end + love.graphics.setShader() + love.graphics.setColor(1, 1, 1) + end) end end diff --git a/main.lua b/main.lua index f2e56ad..4369671 100644 --- a/main.lua +++ b/main.lua @@ -130,5 +130,5 @@ function love.resize(w, h) local render = Tree.level.render if not render then return end render:free() - Tree.level.render = (require "lib.level.render").new { w, h } + Tree.level.render = (require "lib.level.render").new { w = w, h = h } end