149 lines
5.7 KiB
Lua

local anim8 = require "lib.utils.anim8"
--- @class SpriteBehavior : Behavior
--- @field animationTable table<string, table>
--- @field animationGrid table
--- @field manifest table
--- @field sheets table
--- @field state "idle"|"run"|"hurt"|"attack"
--- @field side 1|-1
local sprite = {}
sprite.__index = sprite
sprite.id = "sprite"
sprite.LEFT = -1
sprite.RIGHT = 1
--- Скорость между кадрами в анимации
sprite.ANIMATION_SPEED = 0.1
function sprite.new(spriteDir)
local anim = setmetatable({}, sprite)
anim.animationTable = {}
anim.animationGrid = {}
anim.manifest = spriteDir.manifest
anim.sheets = spriteDir.sheets
-- n: name; i: image
for n, i in pairs(spriteDir.sheets) do
local aGrid = anim8.newGrid(anim.manifest.width, anim.manifest.height, i:getWidth(), i:getHeight())
local tiles = '1-' .. math.ceil(i:getWidth() / anim.manifest.width)
anim.animationGrid[n] = aGrid(tiles, 1)
end
anim.state = "idle"
anim.side = sprite.RIGHT
anim:loop("idle")
return anim
end
function sprite:update(dt)
local anim = self.animationTable[self.state] or self.animationTable["idle"] or nil
if not anim then return end
anim:update(dt)
end
function sprite:draw()
if not self.animationTable[self.state] or not self.sheets[self.state] then return end
self.owner:try(Tree.behaviors.positioned,
function(pos)
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.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
})
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)
end
)
end
--- @return Task<nil>
function sprite:animate(state)
return function(callback)
if not self.animationGrid[state] then
print("[SpriteBehavior]: no animation for '" .. state .. "'")
return callback()
end
self.animationTable[state] = anim8.newAnimation(self.animationGrid[state], self.ANIMATION_SPEED,
function()
self:loop("idle")
callback()
end)
self.state = state
end
end
function sprite:loop(state)
if not self.animationGrid[state] then
return print("[SpriteBehavior]: no animation for '" .. state .. "'")
end
self.animationTable[state] = anim8.newAnimation(self.animationGrid[state], self.ANIMATION_SPEED)
if state == 'idle' then
self.animationTable[state]:gotoFrame(love.math.random(#self.animationTable[state].frames))
end
self.state = state
end
return sprite