128 lines
5.0 KiB
Lua
128 lines
5.0 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 lightIds = Tree.level.lightGrid:query(position, 5)
|
||
local lightsData = {}
|
||
local numLights = 0
|
||
for _, id in ipairs(lightIds) do
|
||
local light = Tree.level.characters[id]
|
||
local lPos = light:has(Tree.behaviors.positioned).position
|
||
local lColor = light:has(Tree.behaviors.light).color
|
||
table.insert(lightsData, { lPos.x, lPos.y, lColor.x, lColor.y, lColor.z })
|
||
numLights = numLights + 1
|
||
if numLights >= 8 then break end
|
||
end
|
||
|
||
local lightShader = Tree.assets.files.shaders.sprite_light
|
||
local weather = Tree.level.weather
|
||
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)
|
||
if numLights > 0 then
|
||
-- Формируем массив для шейдера: lights[i].position и lights[i].color
|
||
for i, data in ipairs(lightsData) do
|
||
lightShader:send(string.format("lights[%d].position", i - 1), { data[1], data[2] })
|
||
lightShader:send(string.format("lights[%d].color", i - 1), { data[3], data[4], data[5] })
|
||
end
|
||
end
|
||
|
||
love.graphics.setShader(lightShader)
|
||
|
||
if Tree.level.selector.id == self.owner.id then
|
||
-- Если выбран, то рисуем еще и обводку?
|
||
-- В LÖVE нельзя поставить два шейдера сразу через setShader.
|
||
-- Для простоты пока оставим только свет, либо нужно комбинировать шейдеры.
|
||
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()
|
||
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
|