local anim8 = require "lib.utils.anim8" --- @class SpriteBehavior : Behavior --- @field animationTable 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) 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) 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) 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 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