Merge pull request 'character-rework' (#7) from character-rework into main
Reviewed-on: #7
This commit is contained in:
commit
177e1ef347
@ -1,46 +0,0 @@
|
|||||||
local anim8 = require "lib.utils.anim8"
|
|
||||||
|
|
||||||
--- Скорость между кадрами в анимации
|
|
||||||
local ANIMATION_SPEED = 0.1
|
|
||||||
|
|
||||||
LEFT = -1
|
|
||||||
RIGHT = 1
|
|
||||||
|
|
||||||
--- @class Animation
|
|
||||||
--- @field animationTable table<string, table>
|
|
||||||
--- @field animationGrid table
|
|
||||||
--- @field state "idle"|"run"|"hurt"|"attack"
|
|
||||||
--- @field side 1|-1
|
|
||||||
local animation = {}
|
|
||||||
animation.__index = animation
|
|
||||||
|
|
||||||
local function new(id, spriteDir)
|
|
||||||
local anim = {
|
|
||||||
animationTable = {},
|
|
||||||
animationGrid = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
-- n: name; i: image
|
|
||||||
for n, i in pairs(spriteDir) do
|
|
||||||
local aGrid = anim8.newGrid(96, 64, i:getWidth(), i:getHeight())
|
|
||||||
local tiles = '1-' .. math.ceil(i:getWidth() / 96)
|
|
||||||
anim.animationGrid[n] = aGrid(tiles, 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
anim.state = "idle"
|
|
||||||
anim.side = RIGHT
|
|
||||||
|
|
||||||
return setmetatable(anim, animation)
|
|
||||||
end
|
|
||||||
|
|
||||||
function animation:getState()
|
|
||||||
return self.state
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param state CharacterState
|
|
||||||
function animation:setState(state, onLoop)
|
|
||||||
self.animationTable[state] = anim8.newAnimation(self.animationGrid[state], ANIMATION_SPEED, onLoop)
|
|
||||||
self.state = state
|
|
||||||
end
|
|
||||||
|
|
||||||
return { new = new }
|
|
||||||
20
lib/character/behaviors/behavior.lua
Normal file
20
lib/character/behaviors/behavior.lua
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
--- Поведение персонажа. Их можно комбинировать как угодно, добавлять и заменять на лету...
|
||||||
|
--- @class Behavior
|
||||||
|
--- @field id string
|
||||||
|
--- @field owner Character
|
||||||
|
--- @field dependencies Behavior[]
|
||||||
|
--- @field new fun(...) : self
|
||||||
|
--- @field update fun(self, dt): nil
|
||||||
|
--- @field draw fun(self): nil
|
||||||
|
local behavior = {}
|
||||||
|
behavior.__index = behavior
|
||||||
|
behavior.id = "behavior"
|
||||||
|
behavior.dependencies = {}
|
||||||
|
|
||||||
|
function behavior.new() return setmetatable({}, behavior) end
|
||||||
|
|
||||||
|
function behavior:update(dt) end
|
||||||
|
|
||||||
|
function behavior:draw() end
|
||||||
|
|
||||||
|
return behavior
|
||||||
77
lib/character/behaviors/map.lua
Normal file
77
lib/character/behaviors/map.lua
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
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 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 path Deque
|
||||||
|
function mapBehavior:followPath(path)
|
||||||
|
if path:is_empty() then return end
|
||||||
|
self.position = self.displayedPosition
|
||||||
|
self.owner:try(Tree.behaviors.sprite, function(sprite)
|
||||||
|
sprite:play("run", true)
|
||||||
|
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:peek_front())
|
||||||
|
self.path:pop_front()
|
||||||
|
else -- мы добежали до финальной цели
|
||||||
|
self.owner:try(Tree.behaviors.sprite, function(sprite)
|
||||||
|
sprite:play("idle", true)
|
||||||
|
end)
|
||||||
|
self.runTarget = nil
|
||||||
|
end
|
||||||
|
else -- анимация перемещения не завершена
|
||||||
|
self.displayedPosition = utils.lerp(self.position, self.runTarget, fraction) -- линейный интерполятор
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return mapBehavior
|
||||||
25
lib/character/behaviors/spellcaster.lua
Normal file
25
lib/character/behaviors/spellcaster.lua
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
--- @class SpellcasterBehavior : Behavior
|
||||||
|
--- @field spellbook Spell[] собственный набор спеллов персонажа
|
||||||
|
--- @field cast Spell | nil ссылка на активный спелл из спеллбука
|
||||||
|
local behavior = {}
|
||||||
|
behavior.__index = behavior
|
||||||
|
behavior.id = "spellcaster"
|
||||||
|
|
||||||
|
---@param spellbook Spell[] | nil
|
||||||
|
---@return SpellcasterBehavior
|
||||||
|
function behavior.new(spellbook)
|
||||||
|
local spb = require "lib.spellbook" --- @todo временное добавление ходьбы всем персонажам
|
||||||
|
local t = {}
|
||||||
|
t.spellbook = spellbook or spb.of { spb.walk }
|
||||||
|
return setmetatable(t, behavior)
|
||||||
|
end
|
||||||
|
|
||||||
|
function behavior:update(dt)
|
||||||
|
if self.cast then self.cast:update(self.owner, dt) end
|
||||||
|
end
|
||||||
|
|
||||||
|
function behavior:draw()
|
||||||
|
if self.cast then self.cast:draw() end
|
||||||
|
end
|
||||||
|
|
||||||
|
return behavior
|
||||||
70
lib/character/behaviors/sprite.lua
Normal file
70
lib/character/behaviors/sprite.lua
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
local anim8 = require "lib.utils.anim8"
|
||||||
|
|
||||||
|
--- @class SpriteBehavior : Behavior
|
||||||
|
--- @field animationTable table<string, table>
|
||||||
|
--- @field animationGrid table
|
||||||
|
--- @field state "idle"|"run"|"hurt"|"attack"
|
||||||
|
--- @field side 1|-1
|
||||||
|
local sprite = {}
|
||||||
|
sprite.__index = sprite
|
||||||
|
sprite.id = "sprite"
|
||||||
|
sprite.dependencies = { Tree.behaviors.map }
|
||||||
|
sprite.LEFT = -1
|
||||||
|
sprite.RIGHT = 1
|
||||||
|
--- Скорость между кадрами в анимации
|
||||||
|
sprite.ANIMATION_SPEED = 0.1
|
||||||
|
|
||||||
|
function sprite.new(spriteDir)
|
||||||
|
local anim = setmetatable({}, sprite)
|
||||||
|
anim.animationTable = {}
|
||||||
|
anim.animationGrid = {}
|
||||||
|
|
||||||
|
-- n: name; i: image
|
||||||
|
for n, i in pairs(spriteDir) do
|
||||||
|
local aGrid = anim8.newGrid(96, 64, i:getWidth(), i:getHeight())
|
||||||
|
local tiles = '1-' .. math.ceil(i:getWidth() / 96)
|
||||||
|
anim.animationGrid[n] = aGrid(tiles, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
anim.state = "idle"
|
||||||
|
anim.side = sprite.RIGHT
|
||||||
|
anim:play("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 Tree.assets.files.sprites.character[self.state] then return end
|
||||||
|
|
||||||
|
self.owner:try(Tree.behaviors.map,
|
||||||
|
function(map)
|
||||||
|
local ppm = Tree.level.camera.pixelsPerMeter
|
||||||
|
local position = map.displayedPosition
|
||||||
|
if Tree.level.selector.id == self.owner.id then love.graphics.setColor(0.5, 1, 0.5) end
|
||||||
|
self.animationTable[self.state]:draw(Tree.assets.files.sprites.character[self.state],
|
||||||
|
position.x + 0.5,
|
||||||
|
position.y + 0.5, nil, 1 / ppm * self.side, 1 / ppm, 38, 47)
|
||||||
|
love.graphics.setColor(1, 1, 1)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param state string
|
||||||
|
--- @param loop boolean | nil
|
||||||
|
function sprite:play(state, loop)
|
||||||
|
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, function()
|
||||||
|
if not loop then self:play("idle", true) end
|
||||||
|
end)
|
||||||
|
self.state = state
|
||||||
|
end
|
||||||
|
|
||||||
|
return sprite
|
||||||
@ -5,14 +5,10 @@ require 'lib.utils.vec3'
|
|||||||
--- @type Id
|
--- @type Id
|
||||||
local characterId = 1
|
local characterId = 1
|
||||||
|
|
||||||
--- @todo Композиция лучше наследования, но не до такой же степени! Надо отрефакторить и избавиться от сотни полей в таблице
|
|
||||||
--- @class Character
|
--- @class Character
|
||||||
--- @field id Id
|
--- @field id Id
|
||||||
--- @field info Info
|
--- @field behaviors Behavior[]
|
||||||
--- @field graphics Graphics
|
--- @field _behaviorsIdx {string: integer}
|
||||||
--- @field logic Logic
|
|
||||||
--- @field spellbook Spell[] собственный набор спеллов персонажа
|
|
||||||
--- @field cast Spell | nil ссылка на активный спелл из спеллбука
|
|
||||||
local character = {}
|
local character = {}
|
||||||
character.__index = character
|
character.__index = character
|
||||||
|
|
||||||
@ -26,50 +22,86 @@ character.__index = character
|
|||||||
local function spawn(name, template, spriteDir, position, size, level)
|
local function spawn(name, template, spriteDir, position, size, level)
|
||||||
local char = {}
|
local char = {}
|
||||||
|
|
||||||
|
char = setmetatable(char, character)
|
||||||
char.id = characterId
|
char.id = characterId
|
||||||
characterId = characterId + 1
|
characterId = characterId + 1
|
||||||
char = setmetatable(char, character)
|
char.behaviors = {}
|
||||||
|
char._behaviorsIdx = {}
|
||||||
|
|
||||||
|
char:addBehavior {
|
||||||
|
Tree.behaviors.map.new(position, size),
|
||||||
|
Tree.behaviors.sprite.new(spriteDir),
|
||||||
|
Tree.behaviors.spellcaster.new()
|
||||||
|
}
|
||||||
|
|
||||||
char:addModules(
|
|
||||||
{
|
|
||||||
logic = (require 'lib.character.logic').new(char.id, position, size),
|
|
||||||
graphics = (require 'lib.character.graphics').new(char.id, spriteDir),
|
|
||||||
info = (require "lib/character/info").new(name, template, level)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Tree.level.characters[char.id] = char
|
Tree.level.characters[char.id] = char
|
||||||
return char
|
return char
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Проверяет, есть ли у персонажа поведение [behavior].
|
||||||
|
--- @generic T : Behavior
|
||||||
|
--- @param behavior T
|
||||||
|
--- @return T | nil
|
||||||
|
function character:has(behavior)
|
||||||
|
--- @cast behavior Behavior
|
||||||
|
local idx = self._behaviorsIdx[behavior.id]
|
||||||
|
if not idx then return nil end
|
||||||
|
return self.behaviors[idx] or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Если у персонажа есть поведение [behavior], применяет к нему [fn]
|
||||||
|
---
|
||||||
|
--- Дальше meme для интеллектуалов
|
||||||
|
---
|
||||||
|
--- *Я: мам купи мне >>=*
|
||||||
|
---
|
||||||
|
--- *Мама: у нас дома есть >>=*
|
||||||
|
---
|
||||||
|
--- *Дома:*
|
||||||
|
--- @generic T : Behavior
|
||||||
|
--- @generic V
|
||||||
|
--- @param behavior T
|
||||||
|
--- @param fn fun(behavior: T) : V | nil
|
||||||
|
--- @return V | nil
|
||||||
|
function character:try(behavior, fn)
|
||||||
|
local b = self:has(behavior)
|
||||||
|
if not b then return end
|
||||||
|
return fn(b)
|
||||||
|
end
|
||||||
|
|
||||||
--- usage:
|
--- usage:
|
||||||
--- addModules( {logic = logic.new(), graphics = graphics.new(), ...} )
|
--- addBehavior( {logic.new(), graphics.new(), ...} )
|
||||||
function character:addModules(modules)
|
---
|
||||||
for key, module in pairs(modules) do
|
--- or you may chain this if you are a wannabe haskell kiddo
|
||||||
module.owner = self
|
--- @param behaviors Behavior[]
|
||||||
self[key] = module
|
--- @return Character | nil
|
||||||
|
function character:addBehavior(behaviors)
|
||||||
|
for _, b in ipairs(behaviors) do
|
||||||
|
if b.dependencies then
|
||||||
|
for _, dep in ipairs(b.dependencies) do
|
||||||
|
if not self:has(dep) then
|
||||||
|
return print("[Character]: cannot add \"" .. b.id ..
|
||||||
|
"\" for a character (Id = " .. self.id .. "): needs \"" .. dep.id .. "\"!")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
b.owner = self
|
||||||
|
table.insert(self.behaviors, b)
|
||||||
|
self._behaviorsIdx[b.id] = #self.behaviors
|
||||||
end
|
end
|
||||||
end
|
return self
|
||||||
|
|
||||||
--- геттеры и сеттеры для "внешних" данных
|
|
||||||
--- @return CharacterState
|
|
||||||
function character:getState()
|
|
||||||
return self.logic.state or "idle"
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param path Deque
|
|
||||||
function character:followPath(path)
|
|
||||||
self.logic:followPath(path)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function character:update(dt)
|
function character:update(dt)
|
||||||
self.logic:update(dt)
|
for _, b in ipairs(self.behaviors) do
|
||||||
if self.cast then self.cast:update(self, dt) end
|
if b.update then b:update(dt) end
|
||||||
self.graphics:update(dt)
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function character:draw()
|
function character:draw()
|
||||||
self.graphics:draw()
|
for _, b in ipairs(self.behaviors) do
|
||||||
if self.cast then self.cast:draw() end
|
if b.draw then b:draw() end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return { spawn = spawn }
|
return { spawn = spawn }
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
--- @class Graphics
|
|
||||||
--- @field owner Character
|
|
||||||
--- @field animation Animation
|
|
||||||
local graphics = {}
|
|
||||||
graphics.__index = graphics
|
|
||||||
|
|
||||||
--- @param id Id
|
|
||||||
--- @param spriteDir table
|
|
||||||
local function new(id, spriteDir)
|
|
||||||
return setmetatable({
|
|
||||||
id = id,
|
|
||||||
animation = (require 'lib.character.animation').new(id, spriteDir)
|
|
||||||
}, graphics)
|
|
||||||
end
|
|
||||||
|
|
||||||
function graphics:update(dt)
|
|
||||||
self.animation.animationTable[self.owner:getState()]:update(dt)
|
|
||||||
end
|
|
||||||
|
|
||||||
function graphics:draw()
|
|
||||||
local ppm = Tree.level.camera.pixelsPerMeter
|
|
||||||
local position = self.owner.logic.mapLogic.displayedPosition
|
|
||||||
local state = self.owner:getState()
|
|
||||||
|
|
||||||
if Tree.level.selector.id == self.owner.id then love.graphics.setColor(0.5, 1, 0.5) end
|
|
||||||
|
|
||||||
self.animation.animationTable[state]:draw(Tree.assets.files.sprites.character[state],
|
|
||||||
position.x + 0.5,
|
|
||||||
position.y + 0.5, nil, 1 / ppm * self.animation.side, 1 / ppm, 38, 47)
|
|
||||||
love.graphics.setColor(1, 1, 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
return { new = new }
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
local utils = require "lib.utils.utils"
|
|
||||||
|
|
||||||
--- @alias CharacterState "idle"|"run"|"attack"|"hurt"
|
|
||||||
|
|
||||||
--- @class Logic
|
|
||||||
--- @field owner Character
|
|
||||||
--- @field mapLogic MapLogic
|
|
||||||
--- @field state CharacterState
|
|
||||||
local logic = {}
|
|
||||||
logic.__index = logic
|
|
||||||
|
|
||||||
--- @param id Id
|
|
||||||
--- @param position? Vec3
|
|
||||||
--- @param size? Vec3
|
|
||||||
local function new(id, position, size)
|
|
||||||
return setmetatable({
|
|
||||||
id = id,
|
|
||||||
mapLogic = (require 'lib.character.map_logic').new(id, position, size),
|
|
||||||
state = "idle"
|
|
||||||
}, logic)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param state CharacterState
|
|
||||||
function logic:setState(state)
|
|
||||||
self.state = state
|
|
||||||
self.owner.graphics.animation:setState(state, (state ~= "idle" and state ~= "run") and function()
|
|
||||||
self:setState("idle")
|
|
||||||
end or nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param path Deque
|
|
||||||
function logic:followPath(path)
|
|
||||||
if path:is_empty() then return end
|
|
||||||
self:setState("run")
|
|
||||||
self.mapLogic.path = path;
|
|
||||||
---@type Vec3
|
|
||||||
local nextCell = path:peek_front()
|
|
||||||
self:runTo(nextCell)
|
|
||||||
path:pop_front()
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param target Vec3
|
|
||||||
function logic:runTo(target)
|
|
||||||
self.mapLogic.t0 = love.timer.getTime()
|
|
||||||
self.mapLogic.runTarget = target
|
|
||||||
local charPos = self.mapLogic.position
|
|
||||||
if target.x < charPos.x then
|
|
||||||
self.owner.graphics.animation.side = LEFT
|
|
||||||
elseif target.x > charPos.x then
|
|
||||||
self.owner.graphics.animation.side = RIGHT
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function logic:update(dt)
|
|
||||||
if self.state == "run" and self.mapLogic.runTarget then
|
|
||||||
local delta = love.timer.getTime() - self.mapLogic.t0 or love.timer.getTime()
|
|
||||||
local fraction = delta /
|
|
||||||
(0.5 * self.mapLogic.runTarget:subtract(self.mapLogic.position):length()) -- бежим одну клетку за 500 мс, по диагонали больше
|
|
||||||
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:setState("idle")
|
|
||||||
self.mapLogic.runTarget = nil
|
|
||||||
end
|
|
||||||
else -- анимация перемещения не завершена
|
|
||||||
self.mapLogic.displayedPosition = utils.lerp(self.mapLogic.position, self.mapLogic.runTarget, fraction) -- линейный интерполятор
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return { new = new }
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
--- @class MapLogic
|
|
||||||
--- @field id Id
|
|
||||||
--- @field position Vec3
|
|
||||||
--- @field runTarget Vec3 точка, в которую в данный момент бежит персонаж
|
|
||||||
--- @field displayedPosition Vec3 точка, в которой персонаж отображается
|
|
||||||
--- @field t0 number время начала движения для анимациии
|
|
||||||
--- @field path Deque путь, по которому сейчас бежит персонаж
|
|
||||||
--- @field size Vec3
|
|
||||||
local mapLogic = {}
|
|
||||||
|
|
||||||
--- @param id Id
|
|
||||||
--- @param position? Vec3
|
|
||||||
--- @param size? Vec3
|
|
||||||
local function new(id, position, size)
|
|
||||||
return setmetatable({
|
|
||||||
id = id,
|
|
||||||
position = position or Vec3({}),
|
|
||||||
displayedPosition = position or Vec3({}),
|
|
||||||
size = size or Vec3({ 1, 1 }),
|
|
||||||
path = (require "lib.utils.deque").new()
|
|
||||||
}, mapLogic)
|
|
||||||
end
|
|
||||||
|
|
||||||
return { new = new }
|
|
||||||
@ -12,9 +12,12 @@ function grid:add(id)
|
|||||||
local character = Tree.level.characters[id]
|
local character = Tree.level.characters[id]
|
||||||
if not character then return end
|
if not character then return end
|
||||||
|
|
||||||
local centerX, centerY = math.floor(character.logic.mapLogic.position.x + 0.5),
|
local mapB = character:has(Tree.behaviors.map)
|
||||||
math.floor(character.logic.mapLogic.position.y + 0.5)
|
if not mapB then return end
|
||||||
local sizeX, sizeY = character.logic.mapLogic.size.x, character.logic.mapLogic.size.y
|
|
||||||
|
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
|
||||||
|
|
||||||
for y = centerY, centerY + sizeY - 1 do
|
for y = centerY, centerY + sizeY - 1 do
|
||||||
for x = centerX, centerX + sizeX - 1 do
|
for x = centerX, centerX + sizeX - 1 do
|
||||||
@ -26,7 +29,10 @@ end
|
|||||||
--- @param a Character
|
--- @param a Character
|
||||||
--- @param b Character
|
--- @param b Character
|
||||||
local function drawCmp(a, b)
|
local function drawCmp(a, b)
|
||||||
return a.logic.mapLogic.displayedPosition.y < b.logic.mapLogic.displayedPosition.y
|
-- здесь персонажи гарантированно имеют нужное поведение
|
||||||
|
return a:has(Tree.behaviors.map).displayedPosition.y
|
||||||
|
<
|
||||||
|
b:has(Tree.behaviors.map).displayedPosition.y
|
||||||
end
|
end
|
||||||
|
|
||||||
--- fills the grid with the actual data
|
--- fills the grid with the actual data
|
||||||
|
|||||||
@ -39,7 +39,7 @@ end
|
|||||||
|
|
||||||
function level:draw()
|
function level:draw()
|
||||||
self.tileGrid:draw()
|
self.tileGrid:draw()
|
||||||
while not self.characterGrid.yOrderQueue:is_empty() do -- по сути это сортировка кучей за n log n, но линейное
|
while not self.characterGrid.yOrderQueue:is_empty() do -- по сути это сортировка кучей за n log n
|
||||||
self.characterGrid.yOrderQueue:pop():draw()
|
self.characterGrid.yOrderQueue:pop():draw()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -25,10 +25,11 @@ function selector:update(dt)
|
|||||||
|
|
||||||
if not characterId and self.id then -- Когда кликаем по тайлу за персонажа в режиме каста, кастуем спелл
|
if not characterId and self.id then -- Когда кликаем по тайлу за персонажа в режиме каста, кастуем спелл
|
||||||
local char = Tree.level.characters[self.id]
|
local char = Tree.level.characters[self.id]
|
||||||
if char.cast then
|
char:try(Tree.behaviors.spellcaster, function(b)
|
||||||
char.cast:cast(char, mousePosition)
|
if not b.cast then return end
|
||||||
char.cast = nil
|
b.cast:cast(char, mousePosition)
|
||||||
end
|
b.cast = nil
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
self:select(characterId)
|
self:select(characterId)
|
||||||
|
|
||||||
|
|||||||
@ -17,16 +17,15 @@ local walk = setmetatable({
|
|||||||
}, spell)
|
}, spell)
|
||||||
|
|
||||||
function walk:cast(caster, target)
|
function walk:cast(caster, target)
|
||||||
caster.cast = nil
|
|
||||||
local path = self.path
|
local path = self.path
|
||||||
path:pop_front()
|
path:pop_front()
|
||||||
print("Following path: ")
|
print("Following path: ")
|
||||||
for p in path:values() do print(p) end
|
for p in path:values() do print(p) end
|
||||||
caster:followPath(path)
|
caster:has(Tree.behaviors.map):followPath(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
function walk:update(caster, dt)
|
function walk:update(caster, dt)
|
||||||
local charPos = caster.logic.mapLogic.position:floor()
|
local charPos = caster:has(Tree.behaviors.map).position:floor()
|
||||||
--- @type Vec3
|
--- @type Vec3
|
||||||
local mpos = Tree.level.camera:toWorldPosition(Vec3 { love.mouse.getX(), love.mouse.getY() }):floor()
|
local mpos = Tree.level.camera:toWorldPosition(Vec3 { love.mouse.getX(), love.mouse.getY() }):floor()
|
||||||
self.path = require "lib.pathfinder" (charPos, mpos)
|
self.path = require "lib.pathfinder" (charPos, mpos)
|
||||||
|
|||||||
13
lib/tree.lua
13
lib/tree.lua
@ -3,9 +3,14 @@
|
|||||||
--- В love.update обновлять, в love.draw читать
|
--- В love.update обновлять, в love.draw читать
|
||||||
|
|
||||||
|
|
||||||
Tree = {
|
Tree = {
|
||||||
assets = (require "lib.utils.asset_bundle"):load()
|
assets = (require "lib.utils.asset_bundle"):load()
|
||||||
}
|
}
|
||||||
Tree.panning = require "lib/panning"
|
Tree.panning = require "lib/panning"
|
||||||
Tree.controls = require "lib.controls"
|
Tree.controls = require "lib.controls"
|
||||||
Tree.level = (require "lib.level.level").new("procedural", "flower_plains") -- для теста у нас только один уровень, который сразу же загружен
|
Tree.level = (require "lib.level.level").new("procedural", "flower_plains") -- для теста у нас только один уровень, который сразу же загружен
|
||||||
|
|
||||||
|
Tree.behaviors = {} --- @todo написать нормальную загрузку поведений
|
||||||
|
Tree.behaviors.map = require "lib.character.behaviors.map"
|
||||||
|
Tree.behaviors.spellcaster = require "lib.character.behaviors.spellcaster"
|
||||||
|
Tree.behaviors.sprite = require "lib.character.behaviors.sprite"
|
||||||
|
|||||||
@ -11,9 +11,11 @@ local SkillButton = ui.Rectangle {
|
|||||||
}
|
}
|
||||||
function SkillButton:update(dt)
|
function SkillButton:update(dt)
|
||||||
ui.Rectangle.update(self, dt)
|
ui.Rectangle.update(self, dt)
|
||||||
self.color = self.owner.cast and { 0, 1, 0 } or { 1, 0, 0 }
|
self.owner:try(Tree.behaviors.spellcaster, function(spellcaster)
|
||||||
self:onTap(function()
|
self.color = spellcaster.cast and { 0, 1, 0 } or { 1, 0, 0 }
|
||||||
self.owner.cast = self.owner.cast and nil or self.owner.spellbook[self.spellId]
|
self:onTap(function()
|
||||||
|
spellcaster.cast = spellcaster.cast and nil or spellcaster.spellbook[self.spellId]
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
16
main.lua
16
main.lua
@ -3,25 +3,15 @@
|
|||||||
local character = require "lib/character/character"
|
local character = require "lib/character/character"
|
||||||
require "lib/tree"
|
require "lib/tree"
|
||||||
local layout = require "lib.ui.layout"
|
local layout = require "lib.ui.layout"
|
||||||
local spellbook = require "lib.spellbook"
|
|
||||||
|
|
||||||
function love.conf(t)
|
function love.conf(t)
|
||||||
t.console = true
|
t.console = true
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.load()
|
function love.load()
|
||||||
for x = 0, 29, 1 do
|
character.spawn("Hero", "warrior", Tree.assets.files.sprites.character)
|
||||||
for y = 0, 29, 1 do
|
character.spawn("Hero", "warrior", Tree.assets.files.sprites.character, Vec3 { 3, 3 })
|
||||||
if math.random() > 0.8 then
|
|
||||||
local c = character.spawn("Hero", "warrior", Tree.assets.files.sprites.character)
|
|
||||||
c.logic.mapLogic.position = Vec3 { x, y }
|
|
||||||
c.logic.mapLogic.displayedPosition = Vec3 { x, y }
|
|
||||||
c.spellbook = spellbook.of { spellbook.walk }
|
|
||||||
c.logic:setState("attack")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
love.window.setMode(1080, 720, { resizable = true, msaa = 4, vsync = true })
|
love.window.setMode(1080, 720, { resizable = true, msaa = 4, vsync = true })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user