Compare commits

...

2 Commits

Author SHA1 Message Date
37eb712518 implement 'followPath' 2025-08-15 05:53:01 +03:00
0993b03088 Create deque.lua 2025-08-15 05:52:38 +03:00
8 changed files with 172 additions and 23 deletions

View File

@ -37,9 +37,9 @@ local function spawn(name, template, spriteDir, position, size, level)
return char
end
--- @param target Vec3
function character:runTo(target)
self.logic:runTo(target)
--- @param path Deque
function character:followPath(path)
self.logic:followPath(path)
end
function character:update(dt)

View File

@ -20,7 +20,7 @@ end
function graphics:draw()
local ppm = Tree.level.camera.pixelsPerMeter
local position = Tree.level.characters[self.id].logic.mapLogic.position
local position = Tree.level.characters[self.id].logic.mapLogic.displayedPosition
local state = Tree.level.characters[self.id].logic.state
if Tree.level.selector.id == self.id then love.graphics.setColor(0.5, 1, 0.5) end

View File

@ -13,30 +13,42 @@ logic.__index = logic
local function new(id, position, size)
return setmetatable({
id = id,
mapLogic = (require 'lib.character.map_logic').new(id, position, size)
mapLogic = (require 'lib.character.map_logic').new(id, position, size),
state = "idle"
}, logic)
end
--- @param path Vec3
--- @param path Deque
function logic:followPath(path)
if path:is_empty() then return end
self.mapLogic.path = path;
self:runTo(path:peek_front())
path:pop_front()
end
--- @param target Vec3
function logic:runTo(target)
self.mapLogic.t0 = love.timer.getTime()
self.state = "run"
self.mapLogic.runTarget = target
end
function logic:update(dt)
if self.state == "run" and self.mapLogic.runTarget then
if self.mapLogic.position:floor() == self.mapLogic.runTarget:floor() then -- мы добежали до цели и сейчас в целевой клетке
local vel = self.mapLogic.runTarget:subtract(self.mapLogic.position) --[[@as Vec3]]
local delta = love.timer.getTime() - self.mapLogic.t0 or love.timer.getTime()
local fraction = delta / 0.5
if fraction >= 1 then -- мы добежали до цели и сейчас в целевой клетке (возможно, промежуточной)
self.mapLogic.position = self.mapLogic.runTarget
if not self.mapLogic.path:is_empty() then -- еще есть, куда бежать
self:runTo(self.mapLogic.path:peek_front())
self.mapLogic.path:pop_front()
else -- мы добежали до финальной цели
self.state = "idle"
self.mapLogic.runTarget = nil
end
else -- мы не добежали до цели
local vel = (self.mapLogic.runTarget:subtract(self.mapLogic.position):normalize() --[[@as Vec3]]
):scale(1 * dt) -- бежим 2 условных метра в секунду
self.mapLogic.position = self.mapLogic.position:add(vel)
self.mapLogic.displayedPosition = self.mapLogic.position:add(vel:scale(fraction))
end
end

View File

@ -1,8 +1,10 @@
--- @class MapLogic
--- @field id Id
--- @field position Vec3
--- @field latestPosition Vec3 позиция, где character был один тик назад
--- @field runTarget Vec3 точка, в которую в данный момент бежит персонаж
--- @field displayedPosition Vec3 точка, в которой персонаж отображается
--- @field t0 number время начала движения для анимациии
--- @field path Deque путь, по которому сейчас бежит персонаж
--- @field size Vec3
local mapLogic = {}
@ -13,7 +15,9 @@ local function new(id, position, size)
return setmetatable({
id = id,
position = position or Vec3({}),
size = size or Vec3({ 1, 1 })
displayedPosition = position or Vec3({}),
size = size or Vec3({ 1, 1 }),
path = (require "lib.utils.deque").new()
}, mapLogic)
end

View File

@ -1,6 +1,8 @@
local deque = require "lib.utils.deque"
--- @param cur Vec3
--- @param to Vec3
--- @param acc Vec3[]
--- @param acc Deque
local function greedy_trace_step(cur, to, acc)
local lengthTable = {}
for x = -1, 1 do
@ -15,7 +17,7 @@ local function greedy_trace_step(cur, to, acc)
end
local next = min[1]
table.insert(acc, cur)
acc = acc:push_back(cur)
if cur == to then
return acc
end
@ -24,8 +26,9 @@ end
--- @param from Vec3
--- @param to Vec3
--- @return Deque
local function trace(from, to)
return greedy_trace_step(from, to, {})
return greedy_trace_step(from, to, deque.new())
end
return trace

View File

@ -27,7 +27,17 @@ function selector:update(dt)
end
local characterId = Tree.level.characterGrid:get(mousePosition.x, mousePosition.y)
if not characterId and self.id then -- временная обработка события "побежать к точке"
local char = Tree.level.characters[self.id]
local charPos = char.logic.mapLogic.position
local path = (require "lib.pathfinder")(charPos, mousePosition)
path:pop_front()
print("Following path: ")
for p in path:values() do print(p) end
char:followPath(path)
end
self:select(characterId)
print("[Selector]:", mousePosition, characterId and "selected " .. characterId or "deselected")
end

121
lib/utils/deque.lua Normal file
View File

@ -0,0 +1,121 @@
---@class Deque
---@field private first integer
---@field private last integer
---@field private _data table<integer, any>
local Deque = {}
Deque.__index = Deque
---Создать пустую очередь
---@return Deque
local function new()
---@type Deque
local self = setmetatable({
first = 1,
last = 0,
_data = {},
}, Deque)
return self
end
---Количество элементов
---@return integer
function Deque:size()
return self.last - self.first + 1
end
---Пуста ли очередь
---@return boolean
function Deque:is_empty()
return self.first > self.last
end
---Добавить в начало
---@param value any
---@return Deque self
function Deque:push_front(value)
self.first = self.first - 1
self._data[self.first] = value
return self
end
---Добавить в конец
---@param value any
---@return Deque self
function Deque:push_back(value)
self.last = self.last + 1
self._data[self.last] = value
return self
end
---Забрать из начала
---@return any
function Deque:pop_front()
if self.first > self.last then return nil end
local value = self._data[self.first]
self._data[self.first] = nil
self.first = self.first + 1
return value
end
---Забрать из конца
---@return any
function Deque:pop_back()
if self.first > self.last then return nil end
local value = self._data[self.last]
self._data[self.last] = nil
self.last = self.last - 1
return value
end
---Подсмотреть начало
---@return any
function Deque:peek_front()
if self.first > self.last then return nil end
return self._data[self.first]
end
---Подсмотреть конец
---@generic T
---@return T|nil
function Deque:peek_back()
if self.first > self.last then return nil end
return self._data[self.last]
end
---Очистить очередь
function Deque:clear()
-- Полная очистка таблицы для GC
for i = self.first, self.last do
self._data[i] = nil
end
self.first, self.last = 1, 0
end
---Итератор по значениям слева направо
---@return fun():any
function Deque:values()
local i = self.first
return function()
if i <= self.last then
local v = self._data[i]
i = i + 1
return v
end
end
end
---Преобразовать в массив
---@return any[]
function Deque:to_array()
local n = self:size()
local arr = {}
if n <= 0 then return arr end
local j = 1
for i = self.first, self.last do
arr[j] = self._data[i]
j = j + 1
end
return arr
end
return { new = new }

View File

@ -12,8 +12,7 @@ end
local width = 30
local height = 30
function love.load()
local char = character.spawn("Hero", "warrior", Tree.assets.files.sprites.character)
char:runTo(Vec3 { 5, 5 })
character.spawn("Hero", "warrior", Tree.assets.files.sprites.character)
Grass = Tree.assets.files.tiles.grass.atlas
Gr1 = love.graphics.newQuad(0, 32, 32, 32, Grass)
@ -76,8 +75,8 @@ function love.draw()
local path = (require "lib.pathfinder")(charPos, mpos)
love.graphics.setColor(1, 0, 0)
for _, p in ipairs(path) do
love.graphics.rectangle("fill", p.x + 0.5, p.y + 0.5, 0.1, 0.1)
for p in path:values() do
love.graphics.rectangle("fill", p.x + 0.45, p.y + 0.45, 0.1, 0.1)
end
love.graphics.setColor(1, 1, 1)
end