From 7ff7e47a907c943f55a3679e18d52aa8e3897cb7 Mon Sep 17 00:00:00 2001 From: PeaAshMeter Date: Sat, 17 Jan 2026 14:49:08 +0300 Subject: [PATCH] add PositionedBehavior --- lib/annotations.lua | 4 +- lib/character/behaviors/cursor.lua | 19 +++++ lib/character/behaviors/light.lua | 14 ++-- lib/character/behaviors/map.lua | 91 ------------------------ lib/character/behaviors/positioned.lua | 25 +++++++ lib/character/behaviors/shadowcaster.lua | 8 +-- lib/character/behaviors/sprite.lua | 6 +- lib/character/behaviors/tiled.lua | 83 +++++++++++++++++++++ lib/level/grid/character_grid.lua | 19 +++-- lib/level/grid/light_grid.lua | 5 +- lib/simple_ui/level/end_turn.lua | 4 +- lib/spellbook.lua | 10 +-- main.lua | 41 +++-------- 13 files changed, 173 insertions(+), 156 deletions(-) create mode 100644 lib/character/behaviors/cursor.lua delete mode 100644 lib/character/behaviors/map.lua create mode 100644 lib/character/behaviors/positioned.lua create mode 100644 lib/character/behaviors/tiled.lua diff --git a/lib/annotations.lua b/lib/annotations.lua index 63bfea7..6a0d688 100644 --- a/lib/annotations.lua +++ b/lib/annotations.lua @@ -1,8 +1,10 @@ --- @meta _ -Tree.behaviors.map = require "lib.character.behaviors.map" Tree.behaviors.spellcaster = require "lib.character.behaviors.spellcaster" Tree.behaviors.sprite = require "lib.character.behaviors.sprite" Tree.behaviors.stats = require "lib.character.behaviors.stats" Tree.behaviors.residentsleeper = require "lib.character.behaviors.residentsleeper" Tree.behaviors.shadowcaster = require "lib.character.behaviors.shadowcaster" Tree.behaviors.light = require "character.behaviors.light" +Tree.behaviors.positioned = require "character.behaviors.positioned" +Tree.behaviors.tiled = require "character.behaviors.tiled" +Tree.behaviors.cursor = require "character.behaviors.cursor" diff --git a/lib/character/behaviors/cursor.lua b/lib/character/behaviors/cursor.lua new file mode 100644 index 0000000..873115e --- /dev/null +++ b/lib/character/behaviors/cursor.lua @@ -0,0 +1,19 @@ +--- Добавляет следование за курсором мыши +--- @class CursorBehavior : Behavior +local behavior = {} +behavior.__index = behavior +behavior.id = "cursor" + +---@return CursorBehavior +function behavior.new() + return setmetatable({}, behavior) +end + +function behavior:update() + self.owner:try(Tree.behaviors.positioned, function(b) + local mx, my = love.mouse.getX(), love.mouse.getY() + b.position = Tree.level.camera:toWorldPosition(Vec3 { mx, my }) + end) +end + +return behavior diff --git a/lib/character/behaviors/light.lua b/lib/character/behaviors/light.lua index 9e08c35..8d6b93c 100644 --- a/lib/character/behaviors/light.lua +++ b/lib/character/behaviors/light.lua @@ -1,38 +1,36 @@ --- @class LightBehavior : Behavior --- @field intensity number --- @field color Vec3 ---- @field position Vec3 --- @field seed integer local behavior = {} behavior.__index = behavior behavior.id = "light" ----@param values {intensity: number?, color: Vec3?, position: Vec3?, seed: integer?} +---@param values {intensity: number?, color: Vec3?, seed: integer?} ---@return LightBehavior function behavior.new(values) return setmetatable({ intensity = values.intensity or 1, color = values.color or Vec3 { 1, 1, 1 }, - position = values.position or Vec3 {}, seed = values.seed or math.random(math.pow(2, 16)) }, behavior) end function behavior:update() - local mx, my = love.mouse.getX(), love.mouse.getY() - self.position = Tree.level.camera:toWorldPosition(Vec3 { mx, my }) end function behavior:draw() + local positioned = self.owner:has(Tree.behaviors.positioned) + if not positioned then return end + 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) love.graphics.setShader(shader) - -- love.graphics.setBlendMode("add") - love.graphics.draw(Tree.assets.files.masks.circle128, self.position.x - self.intensity / 2, - self.position.y - self.intensity / 2, 0, self.intensity / 128, + 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.setBlendMode("alpha") diff --git a/lib/character/behaviors/map.lua b/lib/character/behaviors/map.lua deleted file mode 100644 index d22804d..0000000 --- a/lib/character/behaviors/map.lua +++ /dev/null @@ -1,91 +0,0 @@ -local utils = require "lib.utils.utils" - ---- Отвечает за размещение и перемещение по локации ---- @class MapBehavior : Behavior ---- @field position Vec3 ---- @field runTarget Vec3 точка, в которую в данный момент бежит персонаж ---- @field displayedPosition Vec3 точка, в которой персонаж отображается ---- @field t0 number время начала движения для анимациии ---- @field path Deque путь, по которому сейчас бежит персонаж ---- @field animationNode? AnimationNode AnimationNode, с которым связана анимация перемещения ---- @field size Vec3 -local mapBehavior = {} -mapBehavior.__index = mapBehavior -mapBehavior.id = "map" - - ---- @param position? Vec3 ---- @param size? Vec3 -function mapBehavior.new(position, size) - return setmetatable({ - position = position or Vec3({}), - displayedPosition = position or Vec3({}), - size = size or Vec3({ 1, 1 }), - }, mapBehavior) -end - ---- @param position Vec3 -function mapBehavior:lookAt(position) - self.owner:try(Tree.behaviors.sprite, - function(sprite) - if position.x > self.displayedPosition.x then sprite.side = sprite.RIGHT end - -- (sic!) - if position.x < self.displayedPosition.x then sprite.side = sprite.LEFT end - end - ) -end - ---- @param path Deque ---- @param animationNode AnimationNode -function mapBehavior:followPath(path, animationNode) - if path:is_empty() then return animationNode:finish() end - self.animationNode = animationNode - self.position = self.displayedPosition - self.owner:try(Tree.behaviors.sprite, function(sprite) - sprite:loop("run") - end) - self.path = path; - ---@type Vec3 - local nextCell = path:peek_front() - self:runTo(nextCell) - path:pop_front() -end - ---- @param target Vec3 -function mapBehavior:runTo(target) - self.t0 = love.timer.getTime() - self.runTarget = target - self.owner:try(Tree.behaviors.sprite, - function(sprite) - if target.x < self.position.x then - sprite.side = Tree.behaviors.sprite.LEFT - elseif target.x > self.position.x then - sprite.side = Tree.behaviors.sprite.RIGHT - end - end - ) -end - -function mapBehavior:update(dt) - if self.runTarget then - local delta = love.timer.getTime() - self.t0 or love.timer.getTime() - local fraction = delta / - (0.5 * self.runTarget:subtract(self.position):length()) -- бежим одну клетку за 500 мс, по диагонали больше - if fraction >= 1 then -- анимация перемещена завершена - self.position = self.runTarget - if not self.path:is_empty() then -- еще есть, куда бежать - self:runTo(self.path:pop_front()) - else -- мы добежали до финальной цели - self.owner:try(Tree.behaviors.sprite, function(sprite) - sprite:loop("idle") - end) - self.runTarget = nil - if self.animationNode then self.animationNode:finish() end - end - else -- анимация перемещения не завершена - self.displayedPosition = utils.lerp(self.position, self.runTarget, fraction) -- линейный интерполятор - end - end -end - -return mapBehavior diff --git a/lib/character/behaviors/positioned.lua b/lib/character/behaviors/positioned.lua new file mode 100644 index 0000000..a48cc01 --- /dev/null +++ b/lib/character/behaviors/positioned.lua @@ -0,0 +1,25 @@ +--- Отвечает за размещение на уровне +--- @class PositionedBehavior : Behavior +--- @field position Vec3 +local behavior = {} +behavior.__index = behavior +behavior.id = "positioned" + +--- @param position? Vec3 +function behavior.new(position) + return setmetatable({ + position = position or Vec3({}), + }, behavior) +end + +--- @param position Vec3 +function behavior:lookAt(position) + self.owner:try(Tree.behaviors.sprite, + function(sprite) + if position.x > self.position.x then sprite.side = sprite.RIGHT end + if position.x < self.position.x then sprite.side = sprite.LEFT end + end + ) +end + +return behavior diff --git a/lib/character/behaviors/shadowcaster.lua b/lib/character/behaviors/shadowcaster.lua index 7e3f0eb..3407143 100644 --- a/lib/character/behaviors/shadowcaster.lua +++ b/lib/character/behaviors/shadowcaster.lua @@ -8,11 +8,11 @@ function behavior.new() return setmetatable({}, behavior) end function behavior:draw() local sprite = self.owner:has(Tree.behaviors.sprite) - local map = self.owner:has(Tree.behaviors.map) - if not map then return end + local positioned = self.owner:has(Tree.behaviors.positioned) + if not positioned then return end local ppm = Tree.level.camera.pixelsPerMeter - local position = map.displayedPosition + Vec3 { 0.5, 0.5 } + local position = positioned.position + Vec3 { 0.5, 0.5 } local lightIds = Tree.level.lightGrid:query(position, 5) --- @type Character[] @@ -37,7 +37,7 @@ function behavior:draw() love.graphics.setCanvas(Tree.level.render.textures.spriteLightLayer) love.graphics.setBlendMode("add") for _, light in ipairs(lights) do - local lightPos = light:has(Tree.behaviors.light).position + local lightPos = light:has(Tree.behaviors.positioned).position local lightVec = lightPos - position local lightColor = light:has(Tree.behaviors.light).color diff --git a/lib/character/behaviors/sprite.lua b/lib/character/behaviors/sprite.lua index bc8fe9a..33f076b 100644 --- a/lib/character/behaviors/sprite.lua +++ b/lib/character/behaviors/sprite.lua @@ -41,10 +41,10 @@ end function sprite:draw() if not self.animationTable[self.state] or not Tree.assets.files.sprites.character[self.state] then return end - self.owner:try(Tree.behaviors.map, - function(map) + self.owner:try(Tree.behaviors.positioned, + function(pos) local ppm = Tree.level.camera.pixelsPerMeter - local position = map.displayedPosition + Vec3 { 0.5, 0.5 } + local position = pos.position + Vec3 { 0.5, 0.5 } love.graphics.setCanvas(Tree.level.render.textures.spriteLayer) Tree.level.camera:attach() diff --git a/lib/character/behaviors/tiled.lua b/lib/character/behaviors/tiled.lua new file mode 100644 index 0000000..3c8fbc5 --- /dev/null +++ b/lib/character/behaviors/tiled.lua @@ -0,0 +1,83 @@ +local utils = require "lib.utils.utils" + +--- Отвечает за перемещение по тайлам +--- @class TiledBehavior : Behavior +--- @field private runSource? Vec3 точка, из которой бежит персонаж +--- @field private runTarget? Vec3 точка, в которую в данный момент бежит персонаж +--- @field private path? Deque путь, по которому сейчас бежит персонаж +--- @field private animationNode? AnimationNode AnimationNode, с которым связана анимация перемещения +--- @field private t0 number время начала движения +--- @field size Vec3 +local behavior = {} +behavior.__index = behavior +behavior.id = "tiled" + +--- @param size? Vec3 +function behavior.new(size) + return setmetatable({ + size = size or Vec3({ 1, 1 }), + }, behavior) +end + +--- @param path Deque +--- @param animationNode AnimationNode +function behavior:followPath(path, animationNode) + if path:is_empty() then return animationNode:finish() end + self.animationNode = animationNode + self.owner:try(Tree.behaviors.sprite, function(sprite) + sprite:loop("run") + end) + self.path = path; + ---@type Vec3 + local nextCell = path:peek_front() + self:runTo(nextCell) + path:pop_front() +end + +--- @param target Vec3 +function behavior:runTo(target) + local positioned = self.owner:has(Tree.behaviors.positioned) + if not positioned then return end + + self.t0 = love.timer.getTime() + self.runTarget = target + + self.runSource = positioned.position + + self.owner:try(Tree.behaviors.sprite, + function(sprite) + if target.x < positioned.position.x then + sprite.side = Tree.behaviors.sprite.LEFT + elseif target.x > positioned.position.x then + sprite.side = Tree.behaviors.sprite.RIGHT + end + end + ) +end + +function behavior:update(dt) + if self.runTarget then + local positioned = self.owner:has(Tree.behaviors.positioned) + if not positioned then return end + + local delta = love.timer.getTime() - self.t0 or love.timer.getTime() + local fraction = delta / + (0.5 * self.runTarget:subtract(self.runSource):length()) -- бежим одну клетку за 500 мс, по диагонали больше + if fraction >= 1 then -- анимация перемещена завершена + positioned.position = self.runTarget + if not self.path:is_empty() then -- еще есть, куда бежать + self:runTo(self.path:pop_front()) + else -- мы добежали до финальной цели + self.owner:try(Tree.behaviors.sprite, function(sprite) + sprite:loop("idle") + end) + self.runTarget = nil + if self.animationNode then self.animationNode:finish() end + end + else -- анимация перемещения не завершена + positioned.position = utils.lerp(self.runSource, self.runTarget, fraction) -- линейный интерполятор + end + end +end + +return behavior diff --git a/lib/level/grid/character_grid.lua b/lib/level/grid/character_grid.lua index e04c357..292f475 100644 --- a/lib/level/grid/character_grid.lua +++ b/lib/level/grid/character_grid.lua @@ -12,12 +12,15 @@ function grid:add(id) local character = Tree.level.characters[id] if not character then return end - local mapB = character:has(Tree.behaviors.map) - if not mapB then return end + local positioned = character:has(Tree.behaviors.positioned) + if not positioned then return end - local centerX, centerY = math.floor(mapB.displayedPosition.x + 0.5), - math.floor(mapB.displayedPosition.y + 0.5) - local sizeX, sizeY = mapB.size.x, mapB.size.y + local tiled = character:has(Tree.behaviors.tiled) + if not tiled then return end + + local centerX, centerY = math.floor(positioned.position.x + 0.5), + math.floor(positioned.position.y + 0.5) + local sizeX, sizeY = tiled.size.x, tiled.size.y for y = centerY, centerY + sizeY - 1 do for x = centerX, centerX + sizeX - 1 do @@ -30,11 +33,7 @@ end --- @param b Character local function drawCmp(a, b) --- @TODO: это захардкожено, надо разделить поведения - return (a:has(Tree.behaviors.map) and a:has(Tree.behaviors.map).displayedPosition.y or - a:has(Tree.behaviors.light).position.y) - < - (b:has(Tree.behaviors.map) and b:has(Tree.behaviors.map).displayedPosition.y or - b:has(Tree.behaviors.light).position.y) + return a:has(Tree.behaviors.positioned).position.y < b:has(Tree.behaviors.positioned).position.y end --- fills the grid with the actual data diff --git a/lib/level/grid/light_grid.lua b/lib/level/grid/light_grid.lua index ace03d7..9b7042b 100644 --- a/lib/level/grid/light_grid.lua +++ b/lib/level/grid/light_grid.lua @@ -15,9 +15,10 @@ function grid:add(id) local lightB = character:has(Tree.behaviors.light) if not lightB then return end + local positioned = character:has(Tree.behaviors.positioned) + if not positioned then return end - - local key = tostring(Vec3 { lightB.position.x, lightB.position.y }:floor()) + local key = tostring(Vec3 { positioned.position.x, positioned.position.y }:floor()) if not self.__grid[key] then self.__grid[key] = {} end table.insert(self.__grid[key], character.id) end diff --git a/lib/simple_ui/level/end_turn.lua b/lib/simple_ui/level/end_turn.lua index e1cb679..8e36e29 100644 --- a/lib/simple_ui/level/end_turn.lua +++ b/lib/simple_ui/level/end_turn.lua @@ -49,11 +49,11 @@ function endTurnButton:onClick() Tree.level.selector:select(nil) local cid = Tree.level.turnOrder.current local playing = Tree.level.characters[cid] - if not playing:has(Tree.behaviors.map) then return end + if not playing:has(Tree.behaviors.positioned) then return end AnimationNode { function(node) - Tree.level.camera:animateTo(playing:has(Tree.behaviors.map).displayedPosition, node) + Tree.level.camera:animateTo(playing:has(Tree.behaviors.positioned).position, node) end, duration = 1500, easing = easing.easeInOutCubic, diff --git a/lib/spellbook.lua b/lib/spellbook.lua index 311e5a7..2149541 100644 --- a/lib/spellbook.lua +++ b/lib/spellbook.lua @@ -51,7 +51,7 @@ function walk:cast(caster, target) local sprite = caster:has(Tree.behaviors.sprite) if not sprite then return true end AnimationNode { - function(node) caster:has(Tree.behaviors.map):followPath(path, node) end, + function(node) caster:has(Tree.behaviors.tiled):followPath(path, node) end, onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end, }:run() @@ -59,7 +59,7 @@ function walk:cast(caster, target) end function walk:update(caster, dt) - local charPos = caster:has(Tree.behaviors.map).position:floor() + local charPos = caster:has(Tree.behaviors.positioned).position:floor() --- @type Vec3 local mpos = Tree.level.camera:toWorldPosition(Vec3 { love.mouse.getX(), love.mouse.getY() }):floor() self.path = require "lib.pathfinder" (charPos, mpos) @@ -104,8 +104,8 @@ local attack = setmetatable({}, spell) attack.tag = "dev_attack" function attack:cast(caster, target) - if caster:try(Tree.behaviors.map, function(map) - local dist = math.max(math.abs(map.position.x - target.x), math.abs(map.position.y - target.y)) + if caster:try(Tree.behaviors.positioned, function(p) + local dist = math.max(math.abs(p.position.x - target.x), math.abs(p.position.y - target.y)) print("dist:", dist) return dist > 2 end) then @@ -128,7 +128,7 @@ function attack:cast(caster, target) local targetSprite = targetCharacter:has(Tree.behaviors.sprite) if not sprite or not targetSprite then return true end - caster:try(Tree.behaviors.map, function(map) map:lookAt(target) end) + caster:try(Tree.behaviors.positioned, function(b) b:lookAt(target) end) AnimationNode { onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end, diff --git a/main.lua b/main.lua index 681f988..b291035 100644 --- a/main.lua +++ b/main.lua @@ -17,7 +17,8 @@ function love.load() :addBehavior { Tree.behaviors.residentsleeper.new(), Tree.behaviors.stats.new(nil, nil, 1), - Tree.behaviors.map.new(), + Tree.behaviors.positioned.new(Vec3 { 3, 3 }), + Tree.behaviors.tiled.new(), Tree.behaviors.sprite.new(Tree.assets.files.sprites.character), Tree.behaviors.shadowcaster.new(), Tree.behaviors.spellcaster.new() @@ -25,26 +26,9 @@ function love.load() character.spawn("Baris") :addBehavior { Tree.behaviors.residentsleeper.new(), - Tree.behaviors.stats.new(nil, nil, 2), - Tree.behaviors.map.new(Vec3 { 3, 3 }), - Tree.behaviors.sprite.new(Tree.assets.files.sprites.character), - Tree.behaviors.shadowcaster.new(), - Tree.behaviors.spellcaster.new() - }, - character.spawn("Foodor Jr") - :addBehavior { - Tree.behaviors.residentsleeper.new(), - Tree.behaviors.stats.new(nil, nil, 3), - Tree.behaviors.map.new(Vec3 { 0, 3 }), - Tree.behaviors.sprite.new(Tree.assets.files.sprites.character), - Tree.behaviors.shadowcaster.new(), - Tree.behaviors.spellcaster.new() - }, - character.spawn("Baris Jr") - :addBehavior { - Tree.behaviors.residentsleeper.new(), - Tree.behaviors.stats.new(nil, nil, 4), - Tree.behaviors.map.new(Vec3 { 0, 6 }), + Tree.behaviors.stats.new(nil, nil, 1), + Tree.behaviors.positioned.new(Vec3 { 5, 5 }), + Tree.behaviors.tiled.new(), Tree.behaviors.sprite.new(Tree.assets.files.sprites.character), Tree.behaviors.shadowcaster.new(), Tree.behaviors.spellcaster.new() @@ -55,15 +39,12 @@ function love.load() Tree.level.turnOrder:add(id) end - for i = 1, 1, 1 do - for j = 1, 1, 1 do - character.spawn("My Light") - :addBehavior { - Tree.behaviors.light.new { color = Vec3 { 88, 34, 13 }, position = Vec3 { i, j } * 3, intensity = 10 } - } - end - end - + character.spawn("My Light") + :addBehavior { + Tree.behaviors.light.new { color = Vec3 { 88, 34, 13 }, intensity = 10 }, + Tree.behaviors.positioned.new(), + Tree.behaviors.cursor.new() + } Tree.level.turnOrder:endRound() print("Now playing:", Tree.level.turnOrder.current)