level-complete.md 9.8 KB


title: 关卡完成代码示例

brief: 在本示例项目中,您学习显示关卡完成时可能出现的分数计数效果。

关卡完成 - 示例项目

在本示例项目中,您可以从编辑器打开从GitHub下载,我们演示了显示关卡完成时可能出现的分数计数效果。总分数会逐渐累加,当达到不同分数等级时会出现三颗星星。该示例还使用了重新加载功能,以便在调整值时快速周转。

场景由游戏中的消息触发。 该消息包含获得的总分数以及三颗星星应该在哪个分数等级出现。 当这种情况发生时,标题文本("关卡完成!")会淡入,同时缩小到常规大小(100%)。这是在下面的on_message()中完成的。

标题文本动画完成后,总分数开始计数。每次发生这种情况时,当前分数都会增加一小步。然后我们检查是否已经跨越了某个星星等级,如果是,则开始星星的动画(见下文)。只要我们还没有达到目标分数,总分数就会以弹跳效果进行动画。 随着接近总分数,它也会增长到最大比例。同样,它的颜色会逐渐从白色变为绿色。这是在inc_score()中完成的。

每次星星出现时,它都会淡入并缩小到常规大小。这是在animate_star()中完成的。

当星星完成动画后,较小的星星会在大星星周围以圆形生成。这是在spawn_small_stars()中完成的。

然后它们被动画化为从星星中随机射出。它们在向外扩展时,速度和比例都是随机的。然后它们淡出并最终被删除。这是在animate_small_star()delete_small_star()中完成的。

当分数达到总分数时,高分印记会淡入并缩小回原位。这是在inc_score()的末尾启动的,并在animate_imprint()中执行。

setup()函数确保节点具有正确的初始值。通过从on_reload()调用setup(),我们确保每次从Defold Editor重新加载脚本时都正确设置了所有内容。

-- 文件: level_complete.gui_script

-- 分数每秒递增的速度
local score_inc_speed = 51100
-- 每次更新分数之间的时间间隔
local dt = 0.03
-- 计数开始时分数的比例
local score_start_scale = 0.7
-- 达到目标分数时分数的比例
local score_end_scale = 1.0
-- 每次递增时分数"弹跳"的程度
local score_bounce_factor = 1.1
-- 每颗大星星生成的小星星数量
local small_star_count = 16

local function setup(self)
    -- 使标题颜色透明
    local c = gui.get_color(self.heading)
    c.w = 0
    gui.set_color(self.heading, c)
    -- 使标题阴影透明
    c = gui.get_shadow(self.heading)
    c.w = 0
    gui.set_shadow(self.heading, c)
    -- 初始设置标题为两倍比例
    local s = 2
    gui.set_scale(self.heading, vmath.vector3(s, s, s))
    -- 设置初始分数(0)
    gui.set_text(self.score, "0")
    -- 设置分数颜色为不透明白色
    gui.set_color(self.score, vmath.vector4(1, 1, 1, 1))
    -- 设置比例以便分数在计数时可以增长
    gui.set_scale(self.score, vmath.vector4(score_start_scale, score_start_scale, 1, 0))

    -- 使所有大星星透明
    for i=1,#self.stars do
        gui.set_color(self.stars[i], vmath.vector4(1, 1, 1, 0))
    end
    -- 使印记透明
    gui.set_color(self.imprint, vmath.vector4(1, 1, 1, 0))
    -- 当前显示的分数
    self.current_score = 0
    -- 计数时的目标分数
    self.target_score = 0
end

function init(self)
    -- 检索节点以便更容易访问
    self.heading = gui.get_node("heading")
    self.stars = {gui.get_node("star_left"), gui.get_node("star_mid"), gui.get_node("star_right")}
    self.score = gui.get_node("score")
    self.imprint = gui.get_node("imprint")
    -- 分数的起始颜色
    self.score_start_color = vmath.vector4(1, 1, 1, 1)
    -- 保存分数颜色并在稍后计数时向其动画
    self.score_end_color = gui.get_color(self.score)
    setup(self)
end

-- 删除小星星,当星星完成动画时调用
local function delete_small_star(self, small_star)
    gui.delete_node(small_star)
end

-- 根据给定的初始位置和角度为小星星设置动画
local function animate_small_star(self, pos, angle)
    -- 小星星的移动方向
    local dir = vmath.vector3(math.cos(angle), math.sin(angle), 0, 0)
    -- 创建小星星
    local small_star = gui.new_box_node(pos + dir * 20, vmath.vector3(64, 64, 0))
    -- 设置其纹理
    gui.set_texture(small_star, "small_star")
    -- 设置其颜色为全白
    gui.set_color(small_star, vmath.vector4(1, 1, 1, 1))
    -- 设置起始比例为低
    local start_s = 0.3
    gui.set_scale(small_star, vmath.vector3(start_s, start_s, 1))
    -- 每个小星星的比例变化
    local end_s_var = 1
    -- 这颗星星的实际结束比例
    local end_s = 0.5 + math.random() * end_s_var
    gui.animate(small_star, gui.PROP_SCALE, vmath.vector4(end_s, end_s, 1, 0), gui.EASING_NONE, 0.5)
    -- 行进距离的变化(本质上是星星的速度)
    local dist_var = 300
    -- 星星将行进的实际距离
    local dist = 400 + math.random() * dist_var
    gui.animate(small_star, gui.PROP_POSITION, pos + dir * dist, gui.EASING_NONE, 0.5)
    gui.animate(small_star, gui.PROP_COLOR, vmath.vector4(1, 1, 1, 0), gui.EASING_OUT, 0.3, 0.2, delete_small_star)
end

-- 生成多个小星星
local function spawn_small_stars(self, star)
    -- 小星星将围绕生成的大星星的位置
    local p = gui.get_position(star)
    for i = 1,small_star_count do
        -- 计算特定小星星的角度
        local angle = 2 * math.pi * i/small_star_count
        -- 以及位置
        local pos = vmath.vector3(p.x, p.y, 0)
        -- 生成并为小星星设置动画
        animate_small_star(self, pos, angle)
    end
end

-- 开始大星星淡入的动画
local function animate_star(self, star)
    -- 淡入持续时间
    local fade_in = 0.2
    -- 使其透明
    gui.set_color(star, vmath.vector4(1, 1, 1, 0))
    -- 淡入
    gui.animate(star, gui.PROP_COLOR, vmath.vector4(1, 1, 1, 1), gui.EASING_IN, fade_in)
    -- 初始比例
    local scale = 5
    gui.set_scale(star, vmath.vector3(scale, scale, 1))
    -- 缩小回原位
    gui.animate(star, gui.PROP_SCALE, vmath.vector4(1, 1, 1, 0), gui.EASING_IN, fade_in, 0, spawn_small_stars)
end

-- 开始印记淡入的动画
local function animate_imprint(self)
    -- 等待一段时间后印记出现
    local delay = 0.8
    -- 淡入持续时间
    local fade_in = 0.2
    -- 初始比例
    local scale = 4
    gui.set_scale(self.imprint, vmath.vector4(scale, scale, 1, 0))
    -- 缩小回原位
    gui.animate(self.imprint, gui.PROP_SCALE, vmath.vector4(1, 1, 1, 0), gui.EASING_IN, fade_in, delay)
    -- 同时淡入
    gui.animate(self.imprint, gui.PROP_COLOR, vmath.vector4(1, 1, 1, 1), gui.EASING_IN, fade_in, delay)
end

-- 将分数向目标递增一步
local function inc_score(self, node)
    -- 这一步骤分数递增的量
    local score_inc = score_inc_speed * dt
    -- 递增后的新分数
    local new_score = self.current_score + score_inc
    for i = 1,#self.stars do
        -- 如果我们跨越了星星出现的分数等级,则开始为大星星设置动画
        if self.current_score < self.star_levels[i] and new_score >= self.star_levels[i] then
            animate_star(self, self.stars[i])
        end
    end
    -- 更新分数,但限制在目标分数
    self.current_score = math.min(new_score, self.target_score)
    -- 更新屏幕上的分数
    gui.set_text(self.score, tostring(self.current_score))
    -- 如果我们尚未完成,继续动画和递增
    if self.current_score < self.target_score then
        -- 我们离目标有多近
        local f = self.current_score / self.target_score
        -- 混合颜色以获得缓慢的淡入效果
        local c = vmath.lerp(f, self.score_start_color, self.score_end_color)
        gui.animate(self.score, gui.PROP_COLOR, c, gui.EASING_NONE, dt, 0, inc_score)
        -- 这一步的新比例
        local s = vmath.lerp(f, score_start_scale, score_end_scale)
        -- 通过弹跳因子增加比例
        local sp = s * score_bounce_factor
        -- 从弹跳比例动画回适当比例
        gui.set_scale(self.score, vmath.vector4(sp, sp, 1, 0))
        gui.animate(self.score, gui.PROP_SCALE, vmath.vector4(s, s, 1, 0), gui.EASING_NONE, dt)
    else
        -- 我们完成了,淡入印记
        -- 注意!在实际情况下,这应该与实际存储的高分进行检查
        animate_imprint(self)
    end
end

function on_message(self, message_id, message, sender)
    -- 有人告诉我们应该显示关卡完成场景
    if message_id == hash("level_completed") then
        -- 检索获得的分数以及星星应该在哪个分数等级显示
        self.target_score = message.score
        self.star_levels = message.star_levels
        -- 淡入标题("关卡完成")
        local c = gui.get_color(self.heading)
        c.w = 1
        gui.animate(self.heading, gui.PROP_COLOR, c, gui.EASING_IN, dt, 0.0, inc_score)
        c = gui.get_shadow(self.heading)
        c.w = 1
        gui.animate(self.heading, gui.PROP_SHADOW, c, gui.EASING_IN, dt, 0.0)
        -- 将其缩小回原位
        gui.animate(self.heading, gui.PROP_SCALE, vmath.vector4(1, 1, 1, 0), gui.EASING_IN, 0.2, 0.0)
    end
end

-- 当脚本重新加载时调用此函数
-- 通过设置场景并模拟关卡完成,我们获得了一个非常快速的工作流程来进行调整
function on_reload(self)
    -- 确保任何设置更改都被考虑在内
    setup(self)
    -- 模拟关卡已完成
    msg.post("#gui", "level_completed", {score = 102000, star_levels = {40000, 70000, 100000}})
end