feature/animation_tree #11
							
								
								
									
										51
									
								
								lib/animation_node.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								lib/animation_node.lua
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | ||||
| --- @alias voidCallback fun(): nil | ||||
| --- @alias animationRunner fun(node: AnimationNode) | ||||
| 
 | ||||
| --- @class AnimationNode | ||||
| --- @field count integer | ||||
| --- @field run animationRunner | ||||
| --- @field parent AnimationNode? | ||||
| --- @field children AnimationNode[] | ||||
| --- @field finish voidCallback | ||||
| --- @field onEnd voidCallback? | ||||
| local animation = {} | ||||
| animation.__index = animation | ||||
| 
 | ||||
| --- Регистрация завершения дочерней анимации | ||||
| function animation:bubbleUp() | ||||
|     self.count = self.count - 1 | ||||
|     if self.count > 0 then return end | ||||
|     if self.onEnd then self.onEnd() end | ||||
|     if self.parent then self.parent:bubbleUp() end | ||||
| end | ||||
| 
 | ||||
| --- @param children AnimationNode[] | ||||
| --- Запланировать анимации после текущей, которые запустятся одновременно друг с другом | ||||
| function animation:chain(children) | ||||
|     for _, child in ipairs(children) do | ||||
|         child.parent = self | ||||
|         table.insert(self.children, child) | ||||
|         self.count = self.count + 1 | ||||
|     end | ||||
|     return self | ||||
| end | ||||
| 
 | ||||
| --- @param data {[1]: animationRunner, onEnd?: voidCallback, children?: AnimationNode[]} | ||||
| --- @return AnimationNode | ||||
| local function new(data) | ||||
|     local t = setmetatable({}, animation) | ||||
|     t.run = data[1] | ||||
|     t.onEnd = data.onEnd | ||||
|     t.count = 1 -- своя анимация | ||||
|     t.children = {} | ||||
|     t:chain(data.children or {}) | ||||
|     t.finish = function() | ||||
|         t:bubbleUp() | ||||
|         for _, anim in ipairs(t.children) do | ||||
|             anim:run() | ||||
|         end | ||||
|     end | ||||
|     return t | ||||
| end | ||||
| 
 | ||||
| return new | ||||
| @ -7,7 +7,7 @@ local utils = require "lib.utils.utils" | ||||
| --- @field displayedPosition Vec3 точка, в которой персонаж отображается | ||||
| --- @field t0 number время начала движения для анимациии | ||||
| --- @field path Deque путь, по которому сейчас бежит персонаж | ||||
| --- @field onWalkEnd nil | fun() : nil Функция, которая будет вызвана по завершению [followPath] | ||||
| --- @field animationNode? AnimationNode AnimationNode, с которым связана анимация перемещения | ||||
| --- @field size Vec3 | ||||
| local mapBehavior = {} | ||||
| mapBehavior.__index = mapBehavior | ||||
| @ -25,12 +25,13 @@ function mapBehavior.new(position, size) | ||||
| end | ||||
| 
 | ||||
| --- @param path Deque | ||||
| function mapBehavior:followPath(path, onEnd) | ||||
|     if path:is_empty() then return onEnd() end | ||||
|     self.onWalkEnd = onEnd | ||||
| --- @param animationNode AnimationNode | ||||
| function mapBehavior:followPath(path, animationNode) | ||||
|     if path:is_empty() then return animationNode:finish() end | ||||
|     self.animationNode = animationNode | ||||
|     self.position = self.displayedPosition | ||||
|     self.owner:try(Tree.behaviors.sprite, function(sprite) | ||||
|         sprite:play("run", true) | ||||
|         sprite:loop("run") | ||||
|     end) | ||||
|     self.path = path; | ||||
|     ---@type Vec3 | ||||
| @ -66,11 +67,11 @@ function mapBehavior:update(dt) | ||||
|                 self.path:pop_front() | ||||
|             else -- мы добежали до финальной цели | ||||
|                 self.owner:try(Tree.behaviors.sprite, function(sprite) | ||||
|                     sprite:play("idle", true) | ||||
|                     sprite:loop("idle") | ||||
|                 end) | ||||
|                 self.runTarget = nil | ||||
|                 if self.onWalkEnd then | ||||
|                     self.onWalkEnd() | ||||
|                 if self.animationNode then | ||||
|                     self.animationNode:finish() | ||||
|                     self.onWalkEnd = nil | ||||
|                 end | ||||
|             end | ||||
|  | ||||
| @ -68,4 +68,25 @@ function sprite:play(state, loop) | ||||
|     self.state = state | ||||
| end | ||||
| 
 | ||||
| --- @param node AnimationNode | ||||
| function sprite:animate(state, node) | ||||
|     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() | ||||
|             self:loop("idle") | ||||
|             node:finish() | ||||
|         end) | ||||
|     self.state = state | ||||
| end | ||||
| 
 | ||||
| function sprite:loop(state) | ||||
|     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) | ||||
|     self.state = state | ||||
| end | ||||
| 
 | ||||
| return sprite | ||||
|  | ||||
| @ -7,6 +7,8 @@ | ||||
| --- --TODO: каждый каст должен возвращать объект, который позволит отследить момент завершения анимации спелла | ||||
| --- Да, это Future/Promise/await/async | ||||
| 
 | ||||
| local Animation = require "lib.animation_node" | ||||
| 
 | ||||
| --- @class Spell Здесь будет много бойлерплейта, поэтому тоже понадобится спеллмейкерский фреймворк, который просто вернет готовый Spell | ||||
| --- @field update fun(self: Spell, caster: Character, dt: number): nil Изменяет состояние спелла | ||||
| --- @field draw fun(self: Spell): nil Рисует превью каста, ничего не должна изменять в идеальном мире | ||||
| @ -37,11 +39,33 @@ function walk:cast(caster, target) | ||||
|     if path:is_empty() then return false end | ||||
| 
 | ||||
|     for p in path:values() do print(p) end | ||||
|     caster:has(Tree.behaviors.map):followPath(path, function() | ||||
|         caster:has(Tree.behaviors.spellcaster):endCast() | ||||
|     end) | ||||
|     -- TODO: списать деньги за каст (антиутопия какая-то) | ||||
|     -- TODO: привязка тинькоффа | ||||
| 
 | ||||
|     Animation { | ||||
|         function(node) caster:has(Tree.behaviors.sprite):animate("hurt", node) end, | ||||
|         onEnd = function() caster:has(Tree.behaviors.spellcaster):endCast() end, | ||||
|         children = { | ||||
|             Animation { | ||||
|                 function(node) | ||||
|                     caster:has(Tree.behaviors.map):followPath(path, node) | ||||
|                 end, | ||||
|             }, | ||||
|             Animation { | ||||
|                 function(node) | ||||
|                     Tree.level.characters[2]:has(Tree.behaviors.sprite):animate("hurt", node) | ||||
|                 end, | ||||
|                 children = { | ||||
|                     Animation { | ||||
|                         function(node) | ||||
|                             local from = Tree.level.characters[2]:has(Tree.behaviors.map).position | ||||
|                             local p = (require "lib.pathfinder")(from, Vec3 { 10, 10 }) | ||||
|                             Tree.level.characters[2]:has(Tree.behaviors.map):followPath(p, node) | ||||
|                         end | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     }:run() | ||||
| 
 | ||||
|     caster:try(Tree.behaviors.stats, function(stats) | ||||
|         stats.mana = stats.mana - 2 | ||||
|         print(stats.mana) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user