refactor: integrate tweens into Task system and simplify camera animations
This commit is contained in:
parent
7f1c31f67e
commit
0017b6e104
@ -1,5 +1,6 @@
|
||||
local Vec3 = require "lib.utils.vec3"
|
||||
local utils = require "lib.utils.utils"
|
||||
local task = require "lib.utils.task"
|
||||
|
||||
local EPSILON = 0.001
|
||||
|
||||
@ -9,9 +10,6 @@ local EPSILON = 0.001
|
||||
--- @field speed number
|
||||
--- @field pixelsPerMeter integer
|
||||
--- @field scale number
|
||||
--- @field animationNode AnimationNode?
|
||||
--- @field animationEndPosition Vec3
|
||||
--- @field animationBeginPosition Vec3
|
||||
local camera = {
|
||||
position = Vec3 {},
|
||||
velocity = Vec3 {},
|
||||
@ -38,12 +36,6 @@ local controlMap = {
|
||||
}
|
||||
|
||||
function camera:update(dt)
|
||||
if self.animationNode and self.animationNode.state == "running" then
|
||||
self.animationNode:update(dt) -- тик анимации
|
||||
self.position = utils.lerp(self.animationBeginPosition, self.animationEndPosition, self.animationNode:getValue())
|
||||
return
|
||||
end
|
||||
|
||||
-------------------- зум на колесо ---------------------
|
||||
local y = Tree.controls.mouseWheelY
|
||||
if camera.scale > camera:getDefaultScale() * 5 and y > 0 then return end;
|
||||
@ -97,14 +89,14 @@ function camera:detach()
|
||||
love.graphics.pop()
|
||||
end
|
||||
|
||||
--- Плавно перемещает камеру к указанной точке.
|
||||
--- @param position Vec3
|
||||
--- @param animationNode AnimationNode
|
||||
function camera:animateTo(position, animationNode)
|
||||
if self.animationNode and self.animationNode.state ~= "finished" then self.animationNode:finish() end
|
||||
self.animationNode = animationNode
|
||||
self.animationEndPosition = position
|
||||
self.animationBeginPosition = self.position
|
||||
self.velocity = Vec3 {}
|
||||
--- @param duration number?
|
||||
--- @param easing function?
|
||||
--- @return Task
|
||||
function camera:animateTo(position, duration, easing)
|
||||
self.velocity = Vec3 {} -- Сбрасываем инерцию перед началом анимации
|
||||
return task.tween(self, { position = position }, duration or 1000, easing)
|
||||
end
|
||||
|
||||
--- @return Camera
|
||||
|
||||
@ -1,83 +1,125 @@
|
||||
--- Обобщенная асинхронная функция
|
||||
---
|
||||
--- Использование в общих чертах выглядит так:
|
||||
--- ```lua
|
||||
--- local multiplyByTwoCallback = nil
|
||||
--- local n = nil
|
||||
--- local function multiplyByTwoAsync(number)
|
||||
--- -- императивно сохраняем/обрабатываем параметр
|
||||
--- n = number
|
||||
--- return function(callback) -- это функция, которая запускает задачу
|
||||
--- multiplyByTwoCallback = callback
|
||||
--- end
|
||||
--- end
|
||||
---
|
||||
--- local function update(dt)
|
||||
--- --- ждем нужного момента времени...
|
||||
---
|
||||
--- if multiplyByTwoCallback then -- завершаем вычисление
|
||||
--- local result = n * 2
|
||||
--- multiplyByTwoCallback(result) -- результат асинхронного вычисления идет в параметр коллбека!
|
||||
--- multiplyByTwoCallback = nil
|
||||
--- end
|
||||
--- end
|
||||
---
|
||||
---
|
||||
--- --- потом это можно вызывать так:
|
||||
--- local task = multiplyByTwoAsync(21)
|
||||
--- -- это ленивое вычисление, так что в этот момент ничего не произойдет
|
||||
--- -- запускаем
|
||||
--- task(
|
||||
--- function(result) print(result) end -- выведет 42 после завершения вычисления, т.е. аналогично `task.then((res) => print(res))` на JS
|
||||
--- )
|
||||
---
|
||||
--- ```
|
||||
local easing_lib = require "lib.utils.easing"
|
||||
|
||||
--- Обобщенная асинхронная функция (Task).
|
||||
--- По сути это функция, принимающая коллбэк: `fun(callback: fun(value: T): nil): nil`
|
||||
--- @generic T
|
||||
--- @alias Task<T> fun(callback: fun(value: T): nil): nil
|
||||
--- @alias Task fun(callback: fun(value: T): nil): nil
|
||||
|
||||
--- Возвращает новый Task, который завершится после завершения всех переданных `tasks`.
|
||||
---
|
||||
--- Значение созданного Task будет содержать список значений `tasks` в том же порядке.
|
||||
---
|
||||
--- См. также https://api.dart.dev/dart-async/Future/wait.html
|
||||
--- @generic T
|
||||
--- @param tasks Task[]
|
||||
--- @return Task<T[]>
|
||||
local function wait(tasks)
|
||||
local count = #tasks
|
||||
local results = {}
|
||||
local task = {}
|
||||
local activeTweens = {}
|
||||
|
||||
return function(callback)
|
||||
for i, task in ipairs(tasks) do
|
||||
task(
|
||||
function(result)
|
||||
results[i] = result
|
||||
--- Внутренний хелпер для интерполяции (числа или Vec3)
|
||||
local function lerp(a, b, t)
|
||||
if type(a) == "number" then
|
||||
return a + (b - a) * t
|
||||
elseif type(a) == "table" and getmetatable(a) then
|
||||
-- Предполагаем, что это Vec3 или другой объект с операторами +, -, *
|
||||
return a + (b - a) * t
|
||||
end
|
||||
return b
|
||||
end
|
||||
|
||||
count = count - 1
|
||||
if count == 0 then callback(results) end
|
||||
end
|
||||
)
|
||||
--- Обновление всех активных анимаций (твинов).
|
||||
--- Нужно вызывать в love.update(dt)
|
||||
function task.update(dt)
|
||||
for i = #activeTweens, 1, -1 do
|
||||
local t = activeTweens[i]
|
||||
t.elapsed = t.elapsed + dt * 1000
|
||||
local progress = math.min(t.elapsed / t.duration, 1)
|
||||
local value = t.easing(progress)
|
||||
|
||||
for key, targetValue in pairs(t.properties) do
|
||||
t.target[key] = lerp(t.initial[key], targetValue, value)
|
||||
end
|
||||
|
||||
if progress >= 1 then
|
||||
table.remove(activeTweens, i)
|
||||
if t.resolve then t.resolve() end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Возвращает Completer — объект, который позволяет вручную завершить таску.
|
||||
--- @return table completer { complete: fun(val: T) }, Task<T> future
|
||||
function task.completer()
|
||||
local c = { completed = false, value = nil, cb = nil }
|
||||
function c:complete(val)
|
||||
if self.completed then return end
|
||||
self.completed = true
|
||||
self.value = val
|
||||
if self.cb then self.cb(val) end
|
||||
end
|
||||
|
||||
local future = function(callback)
|
||||
if c.completed then callback(c.value)
|
||||
else c.cb = callback end
|
||||
end
|
||||
return c, future
|
||||
end
|
||||
|
||||
--- Создает таску, которая плавно меняет свойства объекта.
|
||||
--- @param target table Объект, свойства которого меняем
|
||||
--- @param properties table Набор конечных значений { key = value }
|
||||
--- @param duration number Длительность в мс
|
||||
--- @param easing function? Функция смягчения (по умолчанию linear)
|
||||
--- @return Task<nil>
|
||||
function task.tween(target, properties, duration, easing)
|
||||
local initial = {}
|
||||
for k, _ in pairs(properties) do
|
||||
initial[k] = target[k]
|
||||
if type(initial[k]) == "table" and initial[k].copy then
|
||||
initial[k] = initial[k]:copy() -- Для Vec3
|
||||
end
|
||||
end
|
||||
|
||||
local comp, future = task.completer()
|
||||
table.insert(activeTweens, {
|
||||
target = target,
|
||||
initial = initial,
|
||||
properties = properties,
|
||||
duration = duration or 1000,
|
||||
easing = easing or easing_lib.linear,
|
||||
elapsed = 0,
|
||||
resolve = function() comp:complete() end
|
||||
})
|
||||
return future
|
||||
end
|
||||
|
||||
--- Возвращает таску, которая завершится сразу с переданным значением.
|
||||
function task.fromValue(val)
|
||||
return function(callback) callback(val) end
|
||||
end
|
||||
|
||||
--- Возвращает новый Task, который завершится после завершения всех переданных `tasks`.
|
||||
--- @param tasks Task[]
|
||||
--- @return Task<any[]>
|
||||
function task.wait(tasks)
|
||||
if #tasks == 0 then return task.fromValue({}) end
|
||||
local count = #tasks
|
||||
local results = {}
|
||||
|
||||
return function(callback)
|
||||
for i, t in ipairs(tasks) do
|
||||
t(function(result)
|
||||
results[i] = result
|
||||
count = count - 1
|
||||
if count == 0 then callback(results) end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Последовательно объединяет два `Task` в один.
|
||||
--- @generic T
|
||||
--- @generic R
|
||||
--- @param task Task<T> `Task`, который выполнится первым
|
||||
--- @param onCompleted fun(value: T): Task<R> Конструктор второго `Task`. Принимает результат выполнения первого `Task`
|
||||
--- @return Task<R>
|
||||
local function chain(task, onCompleted)
|
||||
--- @param t Task
|
||||
--- @param onCompleted fun(value: any): Task
|
||||
--- @return Task
|
||||
function task.chain(t, onCompleted)
|
||||
return function(callback)
|
||||
task(function(value)
|
||||
local task2 = onCompleted(value)
|
||||
task2(callback)
|
||||
t(function(value)
|
||||
local t2 = onCompleted(value)
|
||||
t2(callback)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
wait = wait,
|
||||
chain = chain
|
||||
}
|
||||
return task
|
||||
|
||||
1
main.lua
1
main.lua
@ -72,6 +72,7 @@ function love.update(dt)
|
||||
TestRunner:update(dt) -- закомментировать для отключения тестов
|
||||
|
||||
local t1 = love.timer.getTime()
|
||||
require('lib.utils.task').update(dt)
|
||||
Tree.controls:poll()
|
||||
Tree.level.camera:update(dt) -- сначала логика камеры, потому что на нее завязан UI
|
||||
testLayout:update(dt) -- потом UI, потому что нужно перехватить жесты и не пустить их дальше
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user