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"
|
||||
require 'lib/vec3'
|
||||
|
||||
--- Скорость между кадрами в анимации
|
||||
local ANIMATION_SPEED = 0.1
|
||||
|
||||
--- @alias Id integer
|
||||
--- @type Id
|
||||
local characterId = 1
|
||||
|
||||
--- @todo Композиция лучше наследования, но не до такой же степени! Надо отрефакторить и избавиться от сотни полей в таблице
|
||||
--- @class Character
|
||||
--- @field id integer
|
||||
--- @field animationTable table<string, table>
|
||||
--- @field state "idle"|"run"|"attack"|"hurt"
|
||||
--- @field id Id
|
||||
--- @field info Info
|
||||
--- @field player table
|
||||
--- @field position Vec3
|
||||
--- @field latestPosition Vec3 позиция, где character был один тик назад
|
||||
--- @field runTarget Vec3 точка, в которую в данный момент бежит персонаж
|
||||
--- @field size Vec3
|
||||
--- @field graphics Graphics
|
||||
--- @field logic Logic
|
||||
local character = {}
|
||||
character.__index = character
|
||||
|
||||
@ -24,88 +19,37 @@ character.__index = character
|
||||
--- @param name string
|
||||
--- @param template ClassTemplate
|
||||
--- @param spriteDir table
|
||||
--- @param position? Vec3
|
||||
--- @param size? Vec3
|
||||
--- @param level? integer
|
||||
local function spawn(name, template, spriteDir, level)
|
||||
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
|
||||
|
||||
local char = {
|
||||
animationTable = {}
|
||||
}
|
||||
local function spawn(name, template, spriteDir, position, size, level)
|
||||
local char = {}
|
||||
|
||||
char.id = characterId
|
||||
characterId = characterId + 1
|
||||
|
||||
char.position = Vec3({})
|
||||
char.size = Vec3({ 1, 1 })
|
||||
|
||||
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.logic = (require 'lib.character.logic').new(char.id, position, size)
|
||||
char.graphics = (require 'lib.character.graphics').new(char.id, spriteDir)
|
||||
char.info = (require "lib/character/info").new(name, template, level)
|
||||
|
||||
char = setmetatable(char, character)
|
||||
Tree.level.characters[char.id] = char
|
||||
Tree.level.positionGrid:add(char)
|
||||
Tree.level.characterGrid:add(char)
|
||||
return char
|
||||
end
|
||||
|
||||
--- @param target Vec3
|
||||
function character:runTo(target)
|
||||
self.state = "run"
|
||||
self.runTarget = target
|
||||
self.logic:runTo(target)
|
||||
end
|
||||
|
||||
function character:update(dt)
|
||||
if self.state == "run" and self.runTarget then
|
||||
if self.position:floor() == self.runTarget:floor() then -- мы добежали до цели и сейчас в целевой клетке
|
||||
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)
|
||||
self.logic:update(dt)
|
||||
self.graphics:update(dt)
|
||||
end
|
||||
|
||||
function character:draw()
|
||||
local ppm = Tree.level.camera.pixelsPerMeter
|
||||
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)
|
||||
self.graphics:draw()
|
||||
end
|
||||
|
||||
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
|
||||
--- @field characters Character[]
|
||||
--- @field positionGrid Grid
|
||||
--- @field characterGrid CharacterGrid
|
||||
--- @field selector Selector
|
||||
--- @field camera Camera
|
||||
local level = {}
|
||||
@ -11,7 +11,8 @@ level.__index = level
|
||||
local function new()
|
||||
return setmetatable({
|
||||
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(),
|
||||
camera = (require "lib/camera").new()
|
||||
}, level)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user