refactor character & grid again
Co-authored-by: Ivan Yuriev <ivanyr44@gmail.com>
This commit is contained in:
parent
1a2a7ab60f
commit
af792bd2d5
46
lib/character/animation.lua
Normal file
46
lib/character/animation.lua
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
local anim8 = require "lib/anim8"
|
||||||
|
|
||||||
|
--- Скорость между кадрами в анимации
|
||||||
|
local ANIMATION_SPEED = 0.1
|
||||||
|
|
||||||
|
--- @class Animation
|
||||||
|
--- @field animationTable table<string, table>
|
||||||
|
local animation = {}
|
||||||
|
|
||||||
|
local function new(id, spriteDir)
|
||||||
|
local anim = {
|
||||||
|
animationTable = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
local 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)
|
||||||
|
animationGrid[n] = aGrid(tiles, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
anim.state = "idle"
|
||||||
|
|
||||||
|
anim.animationTable.idle = anim8.newAnimation(animationGrid["idle"], ANIMATION_SPEED)
|
||||||
|
anim.animationTable.run = anim8.newAnimation(animationGrid["run"], ANIMATION_SPEED)
|
||||||
|
anim.animationTable.attack = anim8.newAnimation(animationGrid["attack"], ANIMATION_SPEED, function()
|
||||||
|
anim.state = "idle"
|
||||||
|
end)
|
||||||
|
anim.animationTable.hurt = anim8.newAnimation(animationGrid["hurt"], ANIMATION_SPEED, function()
|
||||||
|
anim.state = "idle"
|
||||||
|
end)
|
||||||
|
|
||||||
|
return setmetatable(anim, animation)
|
||||||
|
end
|
||||||
|
|
||||||
|
function animation:getState()
|
||||||
|
return self.state
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param state State
|
||||||
|
function animation:setState(state)
|
||||||
|
self.state = state
|
||||||
|
end
|
||||||
|
|
||||||
|
return { new = new }
|
||||||
@ -1,22 +1,17 @@
|
|||||||
local anim8 = require "lib/anim8"
|
local anim8 = require "lib/anim8"
|
||||||
require 'lib/vec3'
|
require 'lib/vec3'
|
||||||
|
|
||||||
--- Скорость между кадрами в анимации
|
|
||||||
local ANIMATION_SPEED = 0.1
|
|
||||||
|
|
||||||
|
--- @alias Id integer
|
||||||
|
--- @type Id
|
||||||
local characterId = 1
|
local characterId = 1
|
||||||
|
|
||||||
--- @todo Композиция лучше наследования, но не до такой же степени! Надо отрефакторить и избавиться от сотни полей в таблице
|
--- @todo Композиция лучше наследования, но не до такой же степени! Надо отрефакторить и избавиться от сотни полей в таблице
|
||||||
--- @class Character
|
--- @class Character
|
||||||
--- @field id integer
|
--- @field id Id
|
||||||
--- @field animationTable table<string, table>
|
|
||||||
--- @field state "idle"|"run"|"attack"|"hurt"
|
|
||||||
--- @field info Info
|
--- @field info Info
|
||||||
--- @field player table
|
--- @field graphics Graphics
|
||||||
--- @field position Vec3
|
--- @field logic Logic
|
||||||
--- @field latestPosition Vec3 позиция, где character был один тик назад
|
|
||||||
--- @field runTarget Vec3 точка, в которую в данный момент бежит персонаж
|
|
||||||
--- @field size Vec3
|
|
||||||
local character = {}
|
local character = {}
|
||||||
character.__index = character
|
character.__index = character
|
||||||
|
|
||||||
@ -24,88 +19,37 @@ character.__index = character
|
|||||||
--- @param name string
|
--- @param name string
|
||||||
--- @param template ClassTemplate
|
--- @param template ClassTemplate
|
||||||
--- @param spriteDir table
|
--- @param spriteDir table
|
||||||
|
--- @param position? Vec3
|
||||||
|
--- @param size? Vec3
|
||||||
--- @param level? integer
|
--- @param level? integer
|
||||||
local function spawn(name, template, spriteDir, level)
|
local function spawn(name, template, spriteDir, position, size, level)
|
||||||
local animationGrid = {}
|
local char = {}
|
||||||
-- 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)
|
|
||||||
animationGrid[n] = aGrid(tiles, 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
local char = {
|
|
||||||
animationTable = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
char.id = characterId
|
char.id = characterId
|
||||||
characterId = characterId + 1
|
characterId = characterId + 1
|
||||||
|
|
||||||
char.position = Vec3({})
|
char.logic = (require 'lib.character.logic').new(char.id, position, size)
|
||||||
char.size = Vec3({ 1, 1 })
|
char.graphics = (require 'lib.character.graphics').new(char.id, spriteDir)
|
||||||
|
char.info = (require "lib/character/info").new(name, template, level)
|
||||||
char.state = "idle"
|
|
||||||
|
|
||||||
char.animationTable.idle = anim8.newAnimation(animationGrid["idle"], ANIMATION_SPEED)
|
|
||||||
char.animationTable.run = anim8.newAnimation(animationGrid["run"], ANIMATION_SPEED)
|
|
||||||
char.animationTable.attack = anim8.newAnimation(animationGrid["attack"], ANIMATION_SPEED, function()
|
|
||||||
char.state = "idle"
|
|
||||||
end)
|
|
||||||
char.animationTable.hurt = anim8.newAnimation(animationGrid["hurt"], ANIMATION_SPEED, function()
|
|
||||||
char.state = "idle"
|
|
||||||
end)
|
|
||||||
|
|
||||||
char.info = (require "lib/character/info").new(name, template)
|
|
||||||
|
|
||||||
char = setmetatable(char, character)
|
char = setmetatable(char, character)
|
||||||
Tree.level.characters[char.id] = char
|
Tree.level.characters[char.id] = char
|
||||||
Tree.level.positionGrid:add(char)
|
Tree.level.characterGrid:add(char)
|
||||||
return char
|
return char
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @param target Vec3
|
--- @param target Vec3
|
||||||
function character:runTo(target)
|
function character:runTo(target)
|
||||||
self.state = "run"
|
self.logic:runTo(target)
|
||||||
self.runTarget = target
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function character:update(dt)
|
function character:update(dt)
|
||||||
if self.state == "run" and self.runTarget then
|
self.logic:update(dt)
|
||||||
if self.position:floor() == self.runTarget:floor() then -- мы добежали до цели и сейчас в целевой клетке
|
self.graphics:update(dt)
|
||||||
self.state = "idle"
|
|
||||||
self.runTarget = nil
|
|
||||||
else -- мы не добежали до цели
|
|
||||||
local vel = (self.runTarget:subtract(self.position):normalize() --[[@as Vec3]]
|
|
||||||
):scale(2 * dt) -- бежим 2 условных метра в секунду
|
|
||||||
self.position = self.position:add(vel)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.position ~= self.latestPosition then
|
|
||||||
-- типа уведомление о том, что положение (на уровне клеток) изменилось
|
|
||||||
Tree.level.positionGrid:remove(self)
|
|
||||||
Tree.level.positionGrid:add(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
if love.keyboard.isDown("r") then
|
|
||||||
self.state = "run"
|
|
||||||
end
|
|
||||||
if love.keyboard.isDown("i") then
|
|
||||||
self.state = "idle"
|
|
||||||
end
|
|
||||||
if love.keyboard.isDown("u") then
|
|
||||||
self.state = "attack"
|
|
||||||
end
|
|
||||||
if love.keyboard.isDown("h") then
|
|
||||||
self.state = "hurt"
|
|
||||||
end
|
|
||||||
self.animationTable[self.state]:update(dt)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function character:draw()
|
function character:draw()
|
||||||
local ppm = Tree.level.camera.pixelsPerMeter
|
self.graphics:draw()
|
||||||
self.animationTable[self.state]:draw(Tree.assets.files.sprites.character[self.state], self.position.x,
|
|
||||||
self.position.y, nil, 1 / ppm, 1 / ppm, 38, 47)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return { spawn = spawn }
|
return { spawn = spawn }
|
||||||
|
|||||||
31
lib/character/graphics.lua
Normal file
31
lib/character/graphics.lua
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
--- @class Graphics
|
||||||
|
--- @field id Id
|
||||||
|
--- @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)
|
||||||
|
local state = Tree.level.characters[self.id].logic.state
|
||||||
|
self.animation.animationTable[state]:update(dt)
|
||||||
|
end
|
||||||
|
|
||||||
|
function graphics:draw()
|
||||||
|
local ppm = Tree.level.camera.pixelsPerMeter
|
||||||
|
local position = Tree.level.characters[self.id].logic.mapLogic.position
|
||||||
|
local state = Tree.level.characters[self.id].logic.state
|
||||||
|
|
||||||
|
self.animation.animationTable[state]:draw(Tree.assets.files.sprites.character[state],
|
||||||
|
position.x,
|
||||||
|
position.y, nil, 1 / ppm, 1 / ppm, 38, 47)
|
||||||
|
end
|
||||||
|
|
||||||
|
return { new = new }
|
||||||
45
lib/character/logic.lua
Normal file
45
lib/character/logic.lua
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
--- @alias State "idle"|"run"|"attack"|"hurt"
|
||||||
|
|
||||||
|
--- @class Logic
|
||||||
|
--- @field id Id
|
||||||
|
--- @field mapLogic MapLogic
|
||||||
|
--- @field state State
|
||||||
|
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)
|
||||||
|
}, logic)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param target Vec3
|
||||||
|
function logic:runTo(target)
|
||||||
|
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 -- мы добежали до цели и сейчас в целевой клетке
|
||||||
|
self.state = "idle"
|
||||||
|
self.mapLogic.runTarget = nil
|
||||||
|
else -- мы не добежали до цели
|
||||||
|
local vel = (self.mapLogic.runTarget:subtract(self.mapLogic.position):normalize() --[[@as Vec3]]
|
||||||
|
):scale(2 * dt) -- бежим 2 условных метра в секунду
|
||||||
|
self.mapLogic.position = self.mapLogic.position:add(vel)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.mapLogic.position ~= self.mapLogic.latestPosition then
|
||||||
|
-- типа уведомление о том, что положение (на уровне клеток) изменилось
|
||||||
|
Tree.level.characterGrid:remove(Tree.level.characters[self.id])
|
||||||
|
Tree.level.characterGrid:add(Tree.level.characters[self.id])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return { new = new }
|
||||||
20
lib/character/map_logic.lua
Normal file
20
lib/character/map_logic.lua
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
--- @class MapLogic
|
||||||
|
--- @field id Id
|
||||||
|
--- @field position Vec3
|
||||||
|
--- @field latestPosition Vec3 позиция, где character был один тик назад
|
||||||
|
--- @field runTarget Vec3 точка, в которую в данный момент бежит персонаж
|
||||||
|
--- @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({}),
|
||||||
|
size = size or Vec3({ 1, 1 })
|
||||||
|
}, mapLogic)
|
||||||
|
end
|
||||||
|
|
||||||
|
return { new = new }
|
||||||
47
lib/grid.lua
47
lib/grid.lua
@ -1,47 +0,0 @@
|
|||||||
local utils = require "lib/utils"
|
|
||||||
|
|
||||||
--- @class Grid
|
|
||||||
local grid = {}
|
|
||||||
grid.__index = grid
|
|
||||||
|
|
||||||
--- Adds a character id to the grid
|
|
||||||
--- @param character Character
|
|
||||||
function grid:add(character)
|
|
||||||
local centerX, centerY = math.floor(character.position.x), math.floor(character.position.y)
|
|
||||||
local sizeX, sizeY = character.size.x, character.size.y
|
|
||||||
|
|
||||||
for y = centerY, centerY + sizeY - 1 do
|
|
||||||
for x = centerX, centerX + sizeX - 1 do
|
|
||||||
self[x][y] = character.id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Removes a character id from the grid
|
|
||||||
--- @param character Character
|
|
||||||
function grid:remove(character)
|
|
||||||
local centerX, centerY = math.floor(character.position.x), math.floor(character.position.y)
|
|
||||||
local sizeX, sizeY = character.size.x, character.size.y
|
|
||||||
|
|
||||||
for y = centerY, centerY + sizeY - 1 do
|
|
||||||
for x = centerX, centerX + sizeX - 1 do
|
|
||||||
self[x][y] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Generates an empty grid
|
|
||||||
--- @param width number
|
|
||||||
--- @param height number
|
|
||||||
--- @return Grid
|
|
||||||
local function generateGrid(width, height)
|
|
||||||
local g = utils.generateList(width, function(_)
|
|
||||||
return utils.generateList(height, function(_)
|
|
||||||
return {}
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
return setmetatable(g, grid)
|
|
||||||
end
|
|
||||||
|
|
||||||
return { new = generateGrid }
|
|
||||||
27
lib/grid/character.lua
Normal file
27
lib/grid/character.lua
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
local Grid = require "lib.grid.grid"
|
||||||
|
|
||||||
|
--- @class CharacterGrid: Grid
|
||||||
|
local CharacterGrid = setmetatable({}, { __index = Grid })
|
||||||
|
CharacterGrid.__index = CharacterGrid
|
||||||
|
|
||||||
|
function CharacterGrid.new(width, height)
|
||||||
|
return setmetatable(Grid.new(width, height, nil), CharacterGrid)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Adds a character id to the grid
|
||||||
|
--- @param character Character
|
||||||
|
function CharacterGrid:add(character)
|
||||||
|
local cx, cy = math.floor(character.logic.mapLogic.position.x), math.floor(character.logic.mapLogic.position.y)
|
||||||
|
local sx, sy = character.logic.mapLogic.size.x, character.logic.mapLogic.size.y
|
||||||
|
self:fillRect(cx, cy, sx, sy, character.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Removes a character id from the grid
|
||||||
|
--- @param character Character
|
||||||
|
function CharacterGrid:remove(character)
|
||||||
|
local cx, cy = math.floor(character.logic.mapLogic.position.x), math.floor(character.logic.mapLogic.position.y)
|
||||||
|
local sx, sy = character.logic.mapLogic.size.x, character.logic.mapLogic.size.y
|
||||||
|
self:fillRect(cx, cy, sx, sy, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
return { new = CharacterGrid.new }
|
||||||
58
lib/grid/grid.lua
Normal file
58
lib/grid/grid.lua
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
local utils = require "lib/utils"
|
||||||
|
|
||||||
|
--- @class Grid
|
||||||
|
local Grid = {}
|
||||||
|
Grid.__index = Grid
|
||||||
|
|
||||||
|
--- Создать пустую сетку width x height, заполненную initial (по умолчанию nil)
|
||||||
|
function Grid.new(width, height, initial)
|
||||||
|
local g = utils.generateList(width, function()
|
||||||
|
return utils.generateList(height, function()
|
||||||
|
return initial
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
return setmetatable(g, Grid)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param x integer
|
||||||
|
--- @param y integer
|
||||||
|
function Grid:get(x, y)
|
||||||
|
local col = self[x]
|
||||||
|
return col and col[y] or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param x integer
|
||||||
|
--- @param y integer
|
||||||
|
function Grid:set(x, y, value)
|
||||||
|
self[x][y] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param x integer
|
||||||
|
--- @param y integer
|
||||||
|
function Grid:clear(x, y)
|
||||||
|
self[x][y] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- нормализуем прямоугольник (поддержка отрицательных размеров)
|
||||||
|
local function normalizeRect(x, y, w, h)
|
||||||
|
if w < 0 then
|
||||||
|
x = x + w + 1; w = -w
|
||||||
|
end
|
||||||
|
if h < 0 then
|
||||||
|
y = y + h + 1; h = -h
|
||||||
|
end
|
||||||
|
return x, y, w, h
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Заполнить прямоугольник значением value
|
||||||
|
function Grid:fillRect(x, y, w, h, value)
|
||||||
|
x, y, w, h = normalizeRect(x, y, w, h)
|
||||||
|
local x2, y2 = x + w - 1, y + h - 1
|
||||||
|
for yy = y, y2 do
|
||||||
|
for xx = x, x2 do
|
||||||
|
self[xx][yy] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return Grid
|
||||||
18
lib/grid/tile.lua
Normal file
18
lib/grid/tile.lua
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
local Grid = require "lib.grid.grid"
|
||||||
|
|
||||||
|
--- @class TileGrid: Grid
|
||||||
|
local TileGrid = setmetatable({}, { __index = Grid })
|
||||||
|
TileGrid.__index = TileGrid
|
||||||
|
|
||||||
|
function TileGrid.new(width, height)
|
||||||
|
return setmetatable(Grid.new(width, height, nil), TileGrid)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param x integer
|
||||||
|
--- @param y integer
|
||||||
|
--- @param tile Tile | nil
|
||||||
|
function TileGrid:set(x, y, tile)
|
||||||
|
Grid.set(self, x, y, tile)
|
||||||
|
end
|
||||||
|
|
||||||
|
return { new = TileGrid.new }
|
||||||
@ -2,7 +2,7 @@ local utils = require "lib/utils"
|
|||||||
|
|
||||||
--- @class Level
|
--- @class Level
|
||||||
--- @field characters Character[]
|
--- @field characters Character[]
|
||||||
--- @field positionGrid Grid
|
--- @field characterGrid CharacterGrid
|
||||||
--- @field selector Selector
|
--- @field selector Selector
|
||||||
--- @field camera Camera
|
--- @field camera Camera
|
||||||
local level = {}
|
local level = {}
|
||||||
@ -11,7 +11,8 @@ level.__index = level
|
|||||||
local function new()
|
local function new()
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
characters = {},
|
characters = {},
|
||||||
positionGrid = (require "lib/grid").new(30, 30), -- magic numbers for testing purposes only
|
characterGrid = (require "lib/grid/character").new(30, 30), -- magic numbers for testing purposes only
|
||||||
|
tileGrid = (require "lib/grid/tile").new(30, 30), -- magic numbers for testing purposes only
|
||||||
selector = (require "lib/selector").new(),
|
selector = (require "lib/selector").new(),
|
||||||
camera = (require "lib/camera").new()
|
camera = (require "lib/camera").new()
|
||||||
}, level)
|
}, level)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user