Compare commits

..

No commits in common. "46a52c31df7ca719238e059f124019a5c71f31ec" and "eecf24c4717dc1604d43a65d729bb4af705f92cc" have entirely different histories.

41 changed files with 179 additions and 479 deletions

3
.gitattributes vendored
View File

@ -1,3 +0,0 @@
*.ogg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.ttf filter=lfs diff=lfs merge=lfs -text

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 B

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 B

After

Width:  |  Height:  |  Size: 368 B

View File

@ -1,85 +0,0 @@
#define MAX_LIGHTS 8
struct Light {
vec2 position;
vec3 color;
float radius;
};
extern Light lights[MAX_LIGHTS];
extern int num_lights;
extern vec2 sprite_pos; // Мировая позиция спрайта (в метрах)
extern vec3 ambient; // Эмбиентное освещение
extern vec3 sky; // Цвет неба
// Параметры выделения
extern bool is_selected;
extern vec2 tex_size;
extern float time;
// Функция для имитации easing.easeInSine
float easeInSine(float x) {
return 1.0 - cos((x * 3.14159) / 2.0);
}
vec4 effect(vec4 vcolor, Image tex, vec2 texture_coords, vec2 screen_coords)
{
vec4 texColor = Texel(tex, texture_coords);
// ЛОГИКА ОБВОДКИ (Outline)
// Мы выполняем ее до расчетов освещения. Если пиксель прозрачный и объект выбран,
// проверяем соседей, чтобы понять, не граница ли это.
// Обводка рисуется "самосветящейся", чтобы выделение было видно даже в полной темноте.
if (is_selected && texColor.a <= 0.0) {
float maxAlpha = 0.0;
// Проверяем соседние пиксели (квадратом 3x3)
for (float x = -1.0; x <= 1.0; x++) {
for (float y = -1.0; y <= 1.0; y++) {
if (x == 0.0 && y == 0.0) continue;
vec2 offset = vec2(x, y) / tex_size;
maxAlpha = max(maxAlpha, Texel(tex, texture_coords + offset).a);
}
}
if (maxAlpha > 0.0) {
// Эффект пульсации и "бегущей волны" из оригинального шейдера outline.glsl
float modY = (0.75 + sin(time) * 0.25) * (0.5 + cos(texture_coords.y * 10.0 + time * 2.0) * 0.5);
return vec4(vec3(modY, 0.2 * sin(time) + 0.5, 0.5), 1.0);
}
}
if (texColor.a == 0.0) {
return vec4(0.0);
}
// Расчет базового освещения мира: персонаж освещается смесью неба (Sky) и отраженного света (Ambient).
vec3 baseLight = ambient + (vec3(1.0) - ambient) * sky;
// Десатурация базового света на 30%.
float luma = dot(baseLight, vec3(0.2126, 0.7152, 0.0722));
vec3 characterBaseLight = mix(baseLight, vec3(luma), 0.3);
vec3 pointLight = vec3(0.0);
for (int i = 0; i < num_lights; i++) {
vec2 lightPos = lights[i].position;
float radius = lights[i].radius;
vec2 lightVec = lightPos - sprite_pos;
float dist = length(lightVec);
// Плавное затухание света по радиусу.
float radiusFalloff = smoothstep(radius, radius * 0.2, dist);
// Реализация псевдо-проекции (3D-эффект):
float frontWeight = smoothstep(-2.0, 0.5, lightVec.y);
float attenuation = radiusFalloff * frontWeight;
pointLight += lights[i].color * attenuation;
}
pointLight = clamp(pointLight, 0.0, 1.0);
// Финальный расчет цвета:
return vec4(texColor.rgb * (characterBaseLight + pointLight), texColor.a);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,10 +1,5 @@
local easing = require "lib.utils.easing"
local pf = require "lib.pathfinder"
local utils = require "lib.utils.utils"
--- @alias AIAction fun(self: AIBehavior): Task<nil>
--- @return Character
local function closestCharacter(char)
local caster = Vec3 {}
char:try(Tree.behaviors.positioned, function(b)
@ -25,142 +20,45 @@ local function closestCharacter(char)
return charTarget
end
-- --- Возвращает все точки в радиусе в виде векторов (должен по крайней мере)
-- --- @param radius integer
-- --- @param center Vec3
-- --- @return Vec3[]
-- local function circleVectors(center, radius)
-- local vecs = {}
-- local res = {}
-- for t = 0, 2 * math.pi, EPSILON do
-- local x = math.cos(t) * radius + center.x
-- local y = math.sin(t) * radius + center.y
-- table.insert(vecs, Vec3 { math.floor(x), math.floor(y) })
-- end
-- for _, v in pairs(vecs) do
-- local i = 1
-- while i <= #res and (res[i].x ~= v.x or res[i].y ~= v.y) do
-- i = i + 1
-- end
-- if i == #res + 1 or #res == 0 then
-- table.insert(res, v)
-- print('[AI]: circle vecs:', v)
-- end
-- end
-- return res
-- end
--- Возвращает все точки в радиусе в виде векторов (должен по крайней мере)
--- @param radius integer
--- @param center Vec3
--- @return Vec3[]
local function circleVectors(center, radius)
local dx, dy, err = radius, 0, 1 - radius
local vecs, res = {}, {}
while dx >= dy do
table.insert(vecs, Vec3 { center.x + dx, center.y + dy })
table.insert(vecs, Vec3 { center.x - dx, center.y + dy })
table.insert(vecs, Vec3 { center.x + dx, center.y - dy })
table.insert(vecs, Vec3 { center.x - dx, center.y - dy })
table.insert(vecs, Vec3 { center.x + dy, center.y + dx })
table.insert(vecs, Vec3 { center.x - dy, center.y + dx })
table.insert(vecs, Vec3 { center.x + dy, center.y - dx })
table.insert(vecs, Vec3 { center.x - dy, center.y - dx })
dy = dy + 1
if err < 0 then
err = err + 2 * dy + 1
else
dx, err = dx - 1, err + 2 * (dy - dx) + 1
end
end
for _, v in pairs(vecs) do
local i = 1
while i <= #res and (res[i].x ~= v.x or res[i].y ~= v.y) and v.x >= 0 and v.y >= 0 do
i = i + 1
end
if i == #res + 1 or #res == 0 then
table.insert(res, v)
print('[AI]: circle vecs:', v)
end
end
return vecs
end
--- ищет пути к ближайшему персу в определённом радиусе
--- @param owner Character
--- @param radius integer здесь мы должны сами определять, сколько должны не доходить до персонажа (1 <= n)
--- @return Vec3|nil
local function pathToClosestCharacter(owner, radius)
local charTarget = closestCharacter(owner)
local targetPosition, ownerPosition = charTarget:has(Tree.behaviors.positioned), owner:has(Tree.behaviors.positioned)
if not targetPosition or not ownerPosition then return end
local circleVecs = circleVectors(targetPosition.position, radius)
local target = circleVecs[#circleVecs]
local path = pf(ownerPosition.position, target)
for i, c in ipairs(circleVecs) do
local newPath = pf(ownerPosition.position, c)
if newPath:size() < path:size() then
path = newPath
target = c
end
end
return target
end
--- @type table<Class, AIAction>
local aiNature = {
dev_warrior = function(self)
return function(callback) -- почему так, описано в Task
self.owner:try(Tree.behaviors.spellcaster, function(spellB)
self.target = pathToClosestCharacter(self.owner, 1)
local attackTarget = closestCharacter(self.owner):has(Tree.behaviors.positioned)
if not attackTarget then return end
local task1 = spellB.spellbook[1]:cast(self.owner, self.target)
if task1 then
task1(
function()
-- здесь мы оказываемся после того, как сходили в первый раз
print('[AI]: я походил')
local task2 = spellB.spellbook[3]:cast(self.owner, attackTarget.position)
if task2 then
-- дергаем функцию после завершения хода
print('[AI]: и ударил')
task2(callback)
else
print('[AI]: чёт не бьётся')
callback()
end
end
)
else
print('рот этого казино')
callback()
end
end)
end
end,
dev_mage = function(self)
return function(callback)
print("etoh... bleh")
callback()
end
end
}
--- @class AIBehavior : Behavior
--- @field target Vec3?
local behavior = {}
behavior.__index = behavior
behavior.id = "ai"
--- @param class Class
function behavior.new(class)
return setmetatable({
makeTurn = aiNature[class]
}, behavior)
function behavior.new()
return setmetatable({}, behavior)
end
--- @return Task<nil>
function behavior:makeTurn()
return function(callback) -- почему так, описано в Task
self.owner:try(Tree.behaviors.spellcaster, function(spellB)
local charTarget = closestCharacter(self.owner)
charTarget:try(Tree.behaviors.positioned, function(b)
self.target = Vec3 { b.position.x, b.position.y + 1 } --- @todo тут захардкожено + 1, но мы должны как-то хитро определять с какой стороны обойти
end)
local task1 = spellB.spellbook[1]:cast(self.owner, self.target)
if task1 then
task1(
function()
-- здесь мы оказываемся после того, как сходили в первый раз
local newTarget = Vec3 { 1, 1 }
local task2 = spellB.spellbook[1]:cast(self.owner, newTarget)
if task2 then
-- дергаем функцию после завершения хода
task2(callback)
else
callback()
end
end
)
else
callback()
end
end)
end
end
return behavior

View File

@ -32,19 +32,22 @@ function behavior:draw()
local positioned = self.owner:has(Tree.behaviors.positioned)
if not positioned then return end
Tree.level.render:enqueue(Tree.level.render.LAYERS.LIGHT, positioned.position.y, function()
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)
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.setShader()
love.graphics.setBlendMode("alpha")
end)
love.graphics.setShader()
love.graphics.setCanvas()
Tree.level.camera:detach()
end
return behavior

View File

@ -10,7 +10,10 @@ 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 return end
if not sprite then
love.graphics.setCanvas()
return
end
local ppm = Tree.level.camera.pixelsPerMeter
local position = positioned.position + Vec3 { 0.5, 0.5 }
@ -22,14 +25,41 @@ function behavior:draw()
table.insert(lights, Tree.level.characters[id])
end
-- 1. Эллипс тени
Tree.level.render:enqueue(Tree.level.render.LAYERS.SHADOW, position.y, function()
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()
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)
end
love.graphics.setBlendMode("alpha")
Tree.level.camera:detach()
love.graphics.setColor(1, 1, 1)
love.graphics.setCanvas()
end
return behavior

View File

@ -49,71 +49,25 @@ function sprite:draw()
local ppm = Tree.level.camera.pixelsPerMeter
local position = pos.position + Vec3 { 0.5, 0.5 }
Tree.level.render:enqueue(Tree.level.render.LAYERS.SPRITE, position.y, function()
love.graphics.setCanvas(Tree.level.render.textures.spriteLayer)
Tree.level.camera:attach()
love.graphics.setColor(1, 1, 1)
-- Собираем источники света для шейдера
local queryRadius = 12 -- Увеличенный радиус для плавности
local lightIds = Tree.level.lightGrid:query(position, queryRadius)
local lightsData = {}
for _, id in ipairs(lightIds) do
local lightChar = Tree.level.characters[id]
local b = lightChar:has(Tree.behaviors.light) --[[@as LightBehavior]]
local lPos = lightChar:has(Tree.behaviors.positioned).position
local dist = (lPos - position):length()
-- Берем только те, что могут дотянуться до нас своим радиусом
if dist < b.intensity + 2 then
table.insert(lightsData, {
x = lPos.x,
y = lPos.y,
r = b.color.x,
g = b.color.y,
b = b.color.z,
radius = b.intensity,
dist = dist
})
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
end
-- Сортируем по дистанции, чтобы выбрать 8 самых влиятельных
table.sort(lightsData, function(a, b) return a.dist < b.dist end)
local lightShader = Tree.assets.files.shaders.sprite_light
local weather = Tree.level.weather
local numLights = math.min(#lightsData, 8)
local isSelected = (Tree.level.selector.id == self.owner.id)
lightShader:send("sprite_pos", { position.x, position.y })
lightShader:send("sky", { weather.skyLight.x, weather.skyLight.y, weather.skyLight.z })
lightShader:send("ambient", { weather.ambientLight.x, weather.ambientLight.y, weather.ambientLight.z })
lightShader:send("num_lights", numLights)
lightShader:send("is_selected", isSelected)
if isSelected then
local sheet = self.sheets[self.state]
lightShader:send("tex_size", { sheet:getWidth(), sheet:getHeight() })
lightShader:send("time", love.timer:getTime())
end
for i = 1, numLights do
local l = lightsData[i]
local idx = i - 1
lightShader:send(string.format("lights[%d].position", idx), { l.x, l.y })
lightShader:send(string.format("lights[%d].color", idx), { l.r, l.g, l.b })
lightShader:send(string.format("lights[%d].radius", idx), l.radius)
end
love.graphics.setShader(lightShader)
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()
end)
Tree.level.camera:detach()
love.graphics.setCanvas()
end
)
end

View File

@ -1,10 +1,7 @@
--- @alias Class "dev_warrior"|"dev_mage"
--- @class StatsBehavior : Behavior
--- @field hp integer
--- @field mana integer
--- @field initiative integer
--- @field class Class
--- @field isInTurnOrder boolean
--- @field amIAlive boolean
local behavior = {}
@ -28,9 +25,8 @@ end
--- @param hp? integer
--- @param mana? integer
--- @param initiative? integer
--- @param class? Class
--- @param isInTurnOrder? boolean
function behavior.new(hp, mana, initiative, class, isInTurnOrder)
function behavior.new(hp, mana, initiative, isInTurnOrder)
return setmetatable({
hp = hp or 20,
mana = mana or 10,

View File

@ -9,32 +9,17 @@ map.__index = map
--- @param size? Vec3
local function new(type, template, size)
local tMap = require('lib.level.' .. type).new(template, size)
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
return setmetatable({ __grid = tMap }, map)
end
function map:draw()
if not self.batch then return end
Tree.level.render:enqueue(Tree.level.render.LAYERS.FLOOR, 0, function()
love.graphics.draw(self.batch)
love.graphics.setCanvas(Tree.level.render.textures.floorLayer)
Tree.level.camera:attach()
utils.each(self.__grid, function(el)
el:draw()
end)
Tree.level.camera:detach()
love.graphics.setCanvas()
end
return { new = new }

View File

@ -20,7 +20,7 @@ local function new(type, template)
local size = Vec3 { 30, 30 } -- magic numbers for testing purposes only
print(type, template, size)
Tree.audio:play(Tree.assets.files.audio.music.level1.progressive_plains)
Tree.audio:play(Tree.assets.files.audio.music.level1.battle)
return setmetatable({
size = size,
@ -56,9 +56,9 @@ end
function level:draw()
self.render:clear()
self.tileGrid:draw()
utils.each(self.characters, function(char)
char:draw()
end)
while not self.characterGrid.yOrderQueue:is_empty() do -- по сути это сортировка кучей за n log n
self.characterGrid.yOrderQueue:pop():draw()
end
self.render:draw()
end

View File

@ -1,30 +1,18 @@
--- @class Render
--- @field textures table<string, love.Canvas>
--- @field queue table[]
--- @field lowResScale number
--- @field LAYERS table<string, integer>
local render = {
textures = {},
queue = {},
lowResScale = 1.0,
LAYERS = {
FLOOR = 1,
SHADOW = 2,
LIGHT = 3,
SPRITE = 4,
OVERLAY = 5
}
textures = {}
}
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)
love.graphics.clear()
love.graphics.setCanvas(txs.spriteLightLayer)
love.graphics.clear(weather.skyLight.x, weather.skyLight.y, weather.skyLight.z)
love.graphics.setCanvas(txs.floorLayer)
love.graphics.clear()
love.graphics.setCanvas(txs.lightLayer)
@ -33,10 +21,6 @@ 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()
@ -73,102 +57,50 @@ 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),
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
-- 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.LIGHT
if wasLowRes then
love.graphics.pop()
end
end
currentLayer = entry.layer
local isLowRes = currentLayer == self.LAYERS.SHADOW 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.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.LIGHT
if wasLowRes then
love.graphics.pop()
end
end
love.graphics.draw(self:applyBlur(txs.shadowLayer, 4 * Tree.level.camera.scale))
love.graphics.setCanvas()
-- 3. Пост-процессинг и композиция
love.graphics.setCanvas(txs.lightLayer)
-- Радиус блюра тоже масштабируем, так как текстура меньше
love.graphics.draw(self:applyBlur(txs.shadowLayer, 4 * Tree.level.camera.scale * self.lowResScale))
love.graphics.setCanvas()
-- self.lightLayer:newImageData():encode("png", "lightLayer.png")
-- os.exit(0)
local lightShader = Tree.assets.files.shaders.light_postprocess
lightShader:send("scene", txs.floorLayer)
lightShader:send("light", self:applyBlur(txs.lightLayer, 2 * self.lowResScale))
lightShader:send("light", self:applyBlur(txs.lightLayer, 2))
lightShader:send("ambient", { weather.ambientLight.x, weather.ambientLight.y, weather.ambientLight.z })
love.graphics.setShader(lightShader)
love.graphics.draw(txs.floorLayer)
love.graphics.setShader()
love.graphics.draw(txs.overlayLayer)
love.graphics.setShader(lightShader)
-- Спрайты уже полностью освещены в SpriteBehavior (с учетом ambient и point lights)
-- Поэтому рисуем их "как есть"
lightShader:send("scene", txs.spriteLayer)
lightShader:send("light", txs.spriteLightLayer)
love.graphics.draw(txs.spriteLayer)
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 }

View File

@ -54,7 +54,8 @@ function spell:draw()
local path = self.path --[[@as Deque?]]
if not path then return end
--- Это отрисовка пути персонажа к мышке
Tree.level.render:enqueue(Tree.level.render.LAYERS.OVERLAY, 0, function()
Tree.level.camera:attach()
love.graphics.setCanvas(Tree.level.render.textures.overlayLayer)
local i = 0
path:pop_front()
for p in path:values() do
@ -63,10 +64,12 @@ function spell:draw()
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)
end)
else
Tree.level.render:enqueue(Tree.level.render.LAYERS.OVERLAY, 0, function()
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
@ -74,8 +77,10 @@ function spell:draw()
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)
end)
end
end

View File

@ -104,7 +104,6 @@ local attack = spell.new {
Tree.behaviors.light.new { color = Vec3 { 0.6, 0.3, 0.3 }, intensity = 4 },
Tree.behaviors.positioned.new(targetCharacter:has(Tree.behaviors.positioned).position + Vec3 { 0.5, 0.5 }),
}
Tree.audio:play(Tree.assets.files.audio.sounds.hurt)
return
task.wait {
task.chain(task.tween(light:has(Tree.behaviors.light) --[[@as LightBehavior]],
@ -117,6 +116,8 @@ local attack = spell.new {
}
end
),
Tree.audio:play(Tree.assets.files.audio.sounds.hurt)
}
}
end

View File

@ -30,7 +30,7 @@ function love.load()
:addBehavior {
Tree.behaviors.residentsleeper.new(),
Tree.behaviors.stats.new(nil, nil, 1),
Tree.behaviors.positioned.new(Vec3 { 3, 1 }),
Tree.behaviors.positioned.new(Vec3 { 4, 3 }),
Tree.behaviors.tiled.new(),
Tree.behaviors.sprite.new(Tree.assets.files.sprites.character),
Tree.behaviors.shadowcaster.new(),
@ -41,7 +41,7 @@ function love.load()
:addBehavior {
Tree.behaviors.residentsleeper.new(),
Tree.behaviors.stats.new(nil, nil, 3),
Tree.behaviors.positioned.new(Vec3 { 7, 2 }),
Tree.behaviors.positioned.new(Vec3 { 5, 3 }),
Tree.behaviors.tiled.new(),
Tree.behaviors.sprite.new(Tree.assets.files.sprites.character),
Tree.behaviors.shadowcaster.new(),
@ -78,14 +78,6 @@ function love.load()
Tree.level.turnOrder:add(id)
end
-- --- Это тестовый источник света, привязанный к мышке, и я очень прошу его не трогать
-- character.spawn("light")
-- :addBehavior {
-- Tree.behaviors.positioned.new(),
-- Tree.behaviors.cursor.new(),
-- Tree.behaviors.light.new { color = Vec3 { 0.5, 0.5, 0.5 }, intensity = 5 }
-- }
Tree.level.turnOrder:endRound()
print("Now playing:", Tree.level.turnOrder.current)
@ -136,14 +128,9 @@ function love.draw()
love.graphics.setColor(1, 1, 1)
love.graphics.setFont(Tree.fonts:getTheme("Roboto_Mono"):getVariant("small"))
local mousePosX, mousePosY = love.mouse.getPosition()
local mousePos = Tree.level.camera:toWorldPosition(Vec3 { mousePosX, mousePosY }):floor()
local stats = "fps: " ..
love.timer.getFPS() ..
" lt: " .. lt ..
" dt: " .. dt ..
" mem: " .. string.format("%.2f MB", collectgarbage("count") / 1000) ..
" mouse pos: " .. tostring(mousePos)
" lt: " .. lt .. " dt: " .. dt .. " mem: " .. string.format("%.2f MB", collectgarbage("count") / 1000)
love.graphics.print(stats, 10, 10)
local t2 = love.timer.getTime()
@ -154,5 +141,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 = w, h = h }
Tree.level.render = (require "lib.level.render").new { w, h }
end