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 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 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