implemented character movement & initial selector

Co-authored-by: Ivan Yuriev <ivanyr44@gmail.com>
This commit is contained in:
Neckrat 2025-08-09 02:32:15 +03:00
parent e08fb7255f
commit d07d26680c
7 changed files with 100 additions and 23 deletions

View File

@ -6,6 +6,7 @@ local ANIMATION_SPEED = 0.1
local characterId = 1
--- @todo Композиция лучше наследования, но не до такой же степени! Надо отрефакторить и избавиться от сотни полей в таблице
--- @class Character
--- @field id integer
--- @field name string
@ -16,6 +17,8 @@ local characterId = 1
--- @field skills table
--- @field class "warrior"|"mage"
--- @field position Vec3
--- @field latestPosition Vec3 позиция, где character был один тик назад
--- @field runTarget Vec3 точка, в которую в данный момент бежит персонаж
--- @field size Vec3
local character = {}
character.__index = character
@ -66,10 +69,34 @@ local function spawn(name, spriteDir, level)
char = setmetatable(char, character)
Tree.level.characters[char.id] = char
Tree.level.positionGrid:add(char)
return char
end
--- @param target Vec3
function character:runTo(target)
self.state = "run"
self.runTarget = 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

View File

@ -14,6 +14,7 @@ local keymap = {
cameraMoveRight = control("key", "d"),
cameraMoveDown = control("key", "s"),
cameraMoveScroll = control("mouse", "3"),
select = control("mouse", "1")
}
function keymap:isDown(key)

View File

@ -7,12 +7,12 @@ grid.__index = grid
--- Adds a character id to the grid
--- @param character Character
function grid:add(character)
local centerX, centerY = character.position.x, character.position.y
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
grid[x][y] = character.id
self[x][y] = character.id
end
end
end
@ -20,12 +20,12 @@ end
--- Removes a character id from the grid
--- @param character Character
function grid:remove(character)
local centerX, centerY = character.position.x, character.position.y
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
grid[x][y] = nil
self[x][y] = nil
end
end
end
@ -37,7 +37,7 @@ end
local function generateGrid(width, height)
local g = utils.generateList(width, function(_)
return utils.generateList(height, function(_)
return nil
return {}
end)
end)

View File

@ -3,7 +3,7 @@ local utils = require "lib/utils"
--- @class Level
--- @field characters Character[]
--- @field positionGrid Grid
--- @field selected table
--- @field selector Selector
--- @field camera Camera
local level = {}
level.__index = level
@ -12,6 +12,7 @@ local function new()
return setmetatable({
characters = {},
positionGrid = (require "lib/grid").new(30, 30), -- magic numbers for testing purposes only
selector = (require "lib/selector").new(),
camera = (require "lib/camera").new()
}, level)
end

35
lib/selector.lua Normal file
View File

@ -0,0 +1,35 @@
--- @class Selector
--- @field id integer
local selector = {}
local function new()
return setmetatable({}, selector)
end
--- @param characterId integer
function selector:select(characterId)
self.id = characterId
end
function selector:deselect()
self.id = nil
end
--- TODO: сделать обработчик селектора
--- @param camera Camera
function selector:update(camera, dt)
if self.id then
return
end
local mousePosition = camera:toWorldPosition(Vec3 { love.mouse.getX(), love.mouse.getY() }):floor()
local characterPosition = Tree.level.positionGrid[mousePosition.x][mousePosition.y]
if Tree.controls:isDown("select") then
if characterPosition then
self:select(characterPosition)
end
end
end
return {
new = new
}

View File

@ -1,4 +1,4 @@
--- @class (exact) Vec3
--- @class Vec3
--- @field x number
--- @field y number
--- @field z number
@ -12,6 +12,8 @@ local function __tostring(self)
return "Vec3{" .. self.x .. ", " .. self.y .. ", " .. self.z .. "}"
end
--- @param other Vec3
--- @return Vec3
function __Vec3:add(other)
@ -51,31 +53,43 @@ function __Vec3:angle_to(other)
return (other - self):direction()
end
function __Vec3:floor()
return Vec3 { math.floor(self.x), math.floor(self.y), math.floor(self.z) }
end
function __Vec3:invert()
return self:scale(-1)
end
--- @param other Vec3
function __Vec3:subtract(other)
return self:add(other:invert())
end
--- @param other Vec3
function __Vec3:equalsTo(other)
return
self.x == other.x
and self.y == other.y
and self.z == other.z
end
---Vec3 constructor
---@param vec number[]
---@return Vec3
function Vec3(vec)
return setmetatable({
x = vec[1] or 0,
y = vec[2] or 0,
z = vec[3] or 0,
}, {
__index = __Vec3,
__tostring = __tostring,
__add = __Vec3.add,
__mul = __Vec3.scale,
__unm = function(self)
return __Vec3.scale(self, -1)
end,
__sub = function(self, other)
return self + -other
end,
__eq = function(self, other)
return
self.x == other.x
and self.y == other.y
and self.z == other.z
end,
__unm = __Vec3.invert,
__sub = __Vec3.subtract,
__eq = __Vec3.equalsTo
})
end

View File

@ -8,9 +8,8 @@ function love.conf(t)
end
function love.load()
character.spawn("Hero", Tree.assets.files.sprites.character)
local hero2 = character.spawn("Hero2", Tree.assets.files.sprites.character)
hero2.position = Vec3 { 1, 1 }; -- вообще-то мы вообще не хотим как-то взаимодействовать с персонажем за пределами update, но это удобно для тестов
local char = character.spawn("Hero", Tree.assets.files.sprites.character)
char:runTo(Vec3 { 5, 5 })
-- PlayerFaction.characters = { Hero1, Hero2 }
love.window.setMode(1080, 720, { resizable = true, msaa = 4, vsync = true })