Defold 内置支持多种动画:
逐帧动画就是由一些列静态图片轮流显示生成的动画. 这种技术类似于老式翻页动画 (详见 http://en.wikipedia.org/wiki/Traditional_animation). 由于每帧的独立性使得这种技术很自由. 但是每帧一张图片会很耗费内存. 相似图片越多动画过渡越平滑同时也带来了巨大的工作量. Defold 逐帧动画使用来自于 图集, 或者 瓷砖图源 里水平排列的图片.
Spine 动画提供 2D 骨骼动画 支持 (详见 http://en.wikipedia.org/wiki/Skeletal_animation). 这是一种类似于剪裁动画的技术. 剪裁动画把对象分成各部分 (比如身体, 眼睛, 嘴巴之类的) 在每帧上独立运动. Spine 动画可以建立隐藏的, 树形关联的虚拟 骨骼. 骨架, 或称 绑定, 来为骨骼上添加的图片单独做动画. Defold 支持以 Spine JSON 格式 输出的动画. Skeletal 动画都很平滑因为骨骼动画关键帧之间可以自动进行插值.
关于导入 Spine 数据作为 Spine 模型和动画, 详见 Spine 教程.
3D 模型的骨骼动画和 Spine 动画类似但是是针对于 3D 空间的. 3D 模型不是像剪裁动画那样先分成各个部分然后用骨骼连起来做动画. 而是使用骨骼精细控制模型上各个三角形如何移动.
关于如何导入 3D 模型动画, 详情请见 模型教程.
{.inline srcset=”images/animation/blender_animation@2x.png 2x”}
数值类的属性 (numbers, vector3, vector4 和 quaterions) 以及着色器常量都可以由内置的属性动画系统制作属性动画, 即使用 go.animate()
函数. 引擎会在属性值之间进行 “补间” 依照指定的播放和缓动模式进行播放. 你也可以自定义缓动函数.
{.inline srcset=”images/animation/property_animation@2x.png 2x”}
Sprite 和 GUI 方块节点可以用来播放逐帧动画而且可以在运行时进行控制.
sprite.play_flipbook()
函数播放逐帧动画. 示例见下文.gui.play_flipbook()
函数播放逐帧动画. 示例见下文.::: 注意 ping-pong 播放模式把动画从第一帧播放到最后一帧再反向播放到 第二帧 , 而不是第一帧. 这样便于连续播放的衔接. :::
假设你的游戏有个 “dodge” 功能, 按下指定的键主角就进行闪避动作. 为此你建立了四组动画:
逻辑代码如下:
local function play_idle_animation(self)
if self.dodge then
sprite.play_flipbook("#sprite", hash("dodge_idle"))
else
sprite.play_flipbook("#sprite", hash("idle"))
end
end
function on_input(self, action_id, action)
-- "dodge" 就是输入动作
if action_id == hash("dodge") then
if action.pressed then
sprite.play_flipbook("#sprite", hash("start_dodge"), play_idle_animation)
-- 记录闪避动作已开始
self.dodge = true
elseif action.released then
sprite.play_flipbook("#sprite", hash("stop_dodge"), play_idle_animation)
-- 记录闪避动作完成
self.dodge = false
end
end
end
给节点选择图片或者动画时, 实际上也同时指定了图片来源 (图集或者瓷砖图源) 以及默认动画. 节点图源是静态的, 但是当前播放的动画是可以在运行时指定的. 静态图片被视作单帧动画, 所以运行时切换图片相当于播放另一个动画:
local function flipbook_done(self)
msg.post("#", "jump_completed")
end
function init(self)
local character_node = gui.get_node("character")
-- 新动画/图片播放时
-- 节点图源要存在默认动画.
gui.play_flipbook(character_node, "jump_left", flipbook_done)
end
动画播放完成时可以提供一个回调函数. 如果动画是以 ONCE_*
模式播放, 播放完成后会调用这个回调函数.
在 Spine 模型上播放动画, 只需调用 spine.play_anim()
函数:
local function anim_done(self)
-- 动画播放完成, 做其他事情...
end
function init(self)
-- 在 "spinemodel" 组件上播放 "walk" 动画同时与上一个动画
-- 在前 0.1 内混合, 然后进行回调.
local anim_props = { blend_duration = 0.1 }
spine.play_anim("#spinemodel", "run", go.PLAYBACK_LOOP_FORWARD, anim_props, anim_done)
end
如果动画是以 go.PLAYBACK_ONCE_*
模式播放, 然后在 spine.play_anim()
里指定回调函数, 则动画播放完成后会调用回调函数. 关于回调函数详见下文.
除了 spine.play_anim()
还有更高级的方法, Spine Model 组件暴露了一个 “cursor” 属性可以通过 go.animate()
进行控制:
-- 设置 spine model 动画但是不播放.
spine.play_anim("#spinemodel", "run_right", go.PLAYBACK_NONE)
-- 设置播放头为 0
go.set("#spinemodel", "cursor", 0)
-- 基于 in-out quad 缓动慢慢对播放头进行从 0 到 1 的 pingpong 补间.
go.animate("#spinemodel", "cursor", go.PLAYBACK_LOOP_PINGPONG, 1, go.EASING_INOUTQUAD, 6)
::: 注意 补间和设置播放头时, 时间轴事件不会被触发. :::
Spine 骨架的各个骨骼实例在游戏对象内展示出来. 在 Spine model 组件的 Outline 视图内, 可以看到完整的嵌套关系. 在此层级嵌套关系中你可以看到骨骼的名称和其所在的位置.
通过骨骼名称, 就可以在运行时得到骨骼实例. 函数 spine.get_go()
返回指定骨骼的 id, 然后就可以用来进行设置父级之类的操作:
-- 把手枪绑定到英雄手上
local hand = spine.get_go("heroine#spinemodel", "front_hand")
msg.post("pistol", "set_parent", { parent_id = hand })
Spine 动画可以基于精确的时间触发事件. 对于需要做同步行为的功能非常有帮助, 例如播放走路声音, 场景粒子效果, 在骨骼层级上进行绑定和解绑或者实现你需要的其他功能.
在 Spine 软件里可以使用时间轴设置事件:
各种事件由事件 id 表示 (上例中是 “bump”) 而且时间轴上的事件可以包含一些数据:
动画播放遇到事件时, spine_event
消息会被发回到调用 spine.play()
函数的脚本上. 消息数据参数就是事件附带的数据, 连同其他一些有用的数据:
t
animation_id
string
float
integer
event_id
blend_weight
-- Spine 动画包含与动画同步的音效.
-- 作为消息传到这里.
function on_message(self, message_id, message, sender)
if message_id == hash("spine_event") and message.event_id == hash("play_sound") then
-- 播放动画音效. 事件数据包括声音组件和声音增益.
local url = msg.url("sounds")
url.fragment = message.string
sound.play(url, { gain = message.float })
end
end
通过调用 model.play_anim()
函数播放模型动画:
function init(self)
-- 在 #model 上来回播放 "wiggle" 动画
model.play_anim("#model", "wiggle", go.PLAYBACK_LOOP_PINGPONG)
end
::: 注意 Defold 目前只支持烘焙动画. 动画每个骨骼每一帧都要有矩阵数据, 而不是单独的位置, 旋转和缩放数据.
动画是线性插值的. 如果需要曲线插值动画要在输出时烘焙.
不支持 Collada 中的动画剪辑. 想要一个模型多个动画, 就要分别导出为 .dae 文件然后在 Defold 里组成 .animationset 文件. :::
模型骨骼作为游戏对象展示出来.
通过骨骼名称, 就可以在运行时得到骨骼实例. 函数 model.get_go()
返回指定骨骼的 id.
-- 得到 wiggler 模型的中央骨骼
local bone_go = model.get_go("#wiggler", "Bone_002")
-- 然后可以任意操作该游戏对象...
像 Spine 模型一样, 3D 模型也可以通过控制 cursor
属性播放动画:
-- 设置 #model 上的动画但不播放
model.play_anim("#model", "wiggle", go.PLAYBACK_NONE)
-- 把播放头设置为动画起始位置
go.set("#model", "cursor", 0)
-- 基于 in-out quad 缓动对播放头进行从 0 到 1 的 pingpong 补间.
go.animate("#model", "cursor", go.PLAYBACK_LOOP_PINGPONG, 1, go.EASING_INOUTQUAD, 3)
制作游戏对象或者组件的属性动画, 可以使用函数 go.animate()
. 对于 GUI 节点属性, 可以使用函数 gui.animate()
.
-- 设置 y 轴位置为 200
go.set(".", "position.y", 200)
-- 制作动画
go.animate(".", "position.y", go.PLAYBACK_LOOP_PINGPONG, 100, go.EASING_OUTBOUNCE, 2)
停止某个属性的所有动画, 调用 go.cancel_animations()
, 对于 GUI 节点, 调用 gui.cancel_animation()
:
-- 停止当前游戏对象欧拉 z 轴旋转动画
go.cancel_animation(".", "euler.z")
如果取消组合属性的动画, 例如 position
, 其所有子属性 (position.x
, position.y
和 position.z
) 动画也会一同取消.
属性教程 涵盖游戏对象, 组件和 GUI 节点的所有属性.
几乎所有 GUI 节点属性都可以制作动画. 比如说, 把一个节点的 color
设置成透明看不见然后制作属性动画到全白使其可见 (也就是没有染色).
local node = gui.get_node("button")
local color = gui.get_color(node)
-- 节点白色动画
gui.animate(node, gui.PROP_COLOR, vmath.vector4(1, 1, 1, 1), gui.EASING_INOUTQUAD, 0.5)
-- 边框红色动画
gui.animate(node, "outline.x", 1, gui.EASING_INOUTQUAD, 0.5)
-- 位置延 x 轴移动 100 像素动画
gui.animate(node, hash("position.x"), 100, gui.EASING_INOUTQUAD, 0.5)
动画可以单次播放也可以循环播放. 取决于播放模式:
pingpong 模式先正向播放, 再反向播放. GUI 属性动画也有这些播放模式:
缓动决定动画基于时间的变化. 下面列出了内置的缓动函数.
以下可用于 go.animate()
函数:
go.EASING_LINEAR | |
go.EASING_INBACK | go.EASING_OUTBACK |
go.EASING_INOUTBACK | go.EASING_OUTINBACK |
go.EASING_INBOUNCE | go.EASING_OUTBOUNCE |
go.EASING_INOUTBOUNCE | go.EASING_OUTINBOUNCE |
go.EASING_INELASTIC | go.EASING_OUTELASTIC |
go.EASING_INOUTELASTIC | go.EASING_OUTINELASTIC |
go.EASING_INSINE | go.EASING_OUTSINE |
go.EASING_INOUTSINE | go.EASING_OUTINSINE |
go.EASING_INEXPO | go.EASING_OUTEXPO |
go.EASING_INOUTEXPO | go.EASING_OUTINEXPO |
go.EASING_INCIRC | go.EASING_OUTCIRC |
go.EASING_INOUTCIRC | go.EASING_OUTINCIRC |
go.EASING_INQUAD | go.EASING_OUTQUAD |
go.EASING_INOUTQUAD | go.EASING_OUTINQUAD |
go.EASING_INCUBIC | go.EASING_OUTCUBIC |
go.EASING_INOUTCUBIC | go.EASING_OUTINCUBIC |
go.EASING_INQUART | go.EASING_OUTQUART |
go.EASING_INOUTQUART | go.EASING_OUTINQUART |
go.EASING_INQUINT | go.EASING_OUTQUINT |
go.EASING_INOUTQUINT | go.EASING_OUTINQUINT |
以下可用于 gui.animate()
函数:
gui.EASING_LINEAR | |
gui.EASING_INBACK | gui.EASING_OUTBACK |
gui.EASING_INOUTBACK | gui.EASING_OUTINBACK |
gui.EASING_INBOUNCE | gui.EASING_OUTBOUNCE |
gui.EASING_INOUTBOUNCE | gui.EASING_OUTINBOUNCE |
gui.EASING_INELASTIC | gui.EASING_OUTELASTIC |
gui.EASING_INOUTELASTIC | gui.EASING_OUTINELASTIC |
gui.EASING_INSINE | gui.EASING_OUTSINE |
gui.EASING_INOUTSINE | gui.EASING_OUTINSINE |
gui.EASING_INEXPO | gui.EASING_OUTEXPO |
gui.EASING_INOUTEXPO | gui.EASING_OUTINEXPO |
gui.EASING_INCIRC | gui.EASING_OUTCIRC |
gui.EASING_INOUTCIRC | gui.EASING_OUTINCIRC |
gui.EASING_INQUAD | gui.EASING_OUTQUAD |
gui.EASING_INOUTQUAD | gui.EASING_OUTINQUAD |
gui.EASING_INCUBIC | gui.EASING_OUTCUBIC |
gui.EASING_INOUTCUBIC | gui.EASING_OUTINCUBIC |
gui.EASING_INQUART | gui.EASING_OUTQUART |
gui.EASING_INOUTQUART | gui.EASING_OUTINQUART |
gui.EASING_INQUINT | gui.EASING_OUTQUINT |
gui.EASING_INOUTQUINT | gui.EASING_OUTINQUINT |
可以使用 vector
和其中的一系列值代替预置缓动函数. 矢量值从 (0
) 过渡到 (1
). 引擎会从矢量中取样并自动线性插值生成缓动曲线.
示例如下:
local values = { 0, 0.4, 0.2, 0.2, 0.5. 1 }
local my_easing = vmath.vector(values)
生成的缓动曲线如下:
下面的例子是让游戏对象的 y 轴位置依照自定义曲线从当前位置到 200 来回跳跃:
local values = { 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1 }
local square_easing = vmath.vector(values)
go.animate("go", "position.y", go.PLAYBACK_LOOP_PINGPONG, 200, square_easing, 2.0)
所有动画函数 (go.animate()
, gui.animate()
, gui.play_flipbook()
, gui.play_spine_anim()
, sprite.play_flipbook()
, spine.play_anim()
和 model.play_anim()
) 可以在最后一个参数上传入Lua回调函数. 当动画播放完成时会调用这个函数. 对于循环动画, 和使用 go.cancel_animations()
手动取消播放的动画, 不会调用回调函数. 动画播放完成的回调函数里可以发送消息或者继续播放其他动画.
不同动画函数的回调函数参数有些许区别. 具体请参照动画函数的 API 文档.
local function done_bouncing(self, url, property)
-- 动画播放完成. 进行各种处理...
end
function init(self)
go.animate(".", "position.y", go.PLAYBACK_ONCE_FORWARD, 100, go.EASING_OUTBOUNCE, 2, 0, done_bouncing)
end
Did you spot an error or do you have a suggestion? Please let us know on GitHub!
GITHUB