为了账号安全,请及时绑定邮箱和手机立即绑定

用Go和Korok写一个Flappybird游戏-4

标签:
Go

前一节 我们实现了游戏的核心逻辑,比如鸟的飞行效果,管道和地面的滚动,碰撞检测等等。本节会给各种场景和状态的转换添加过渡效果,让整个游戏进程更加自然。本节你将会学会:

  1. 如何添加场景转换

  2. 如何使用补间动画

注:如果你没有完成上一节的教程,也可以直接从 这里 下载到上节结束时的代码,以便开始本节的内容。

添加死亡菜单

上节结束的时候,我们游戏已经可以正常游戏了,但是死亡的时候仅仅是暂停了动效。此时需要弹出一个“死亡”菜单以更好的提示用户。我们依然使用 GUI 系统来实现这个菜单,有了之前的基础,实现这个菜单应该不是太困难的事情,按部就班:

    // 添加新的菜单参数
    type GameScene struct {
        ...
        gameover struct{
            gfx.Tex2D
            gui.Rect
        }
        score struct{
            gfx.Tex2D
            gui.Rect
        }
        restart struct{
            gfx.Tex2D
            gui.Rect
        }
        ...
    }    // 初始化菜单参数
    sn.gameover.Tex2D, _ = at.GetByName("gameover.png")
    sn.gameover.Rect = gui.Rect{
        X: (320-233)/2,
        Y: 70,
        W: 233,
        H: 70,
    }
    sn.score.Tex2D, _ = at.GetByName("result_board.png")
    sn.score.Rect = gui.Rect{
        X: (320 - 240)/2,
        Y: 200,
        W: 240,
        H: 120,
    }
    sn.restart.Tex2D, _ = at.GetByName("start.png")
    sn.restart.Rect = gui.Rect{
        X: (320 - 120)/2,
        Y: 360,
        W: 120,
        H: 60,
    }    // 实现 showOver 方法,绘制死亡菜单
    func (sn *GameScene) showOver(dt float32) {        // show game over
        gui.Image(1, sn.gameover.Rect, sn.gameover.Tex2D, nil)        // show score
        gui.Image(2, sn.score.Rect, sn.score.Tex2D, nil)        // show restart button
        e := gui.ImageButton(3, sn.restart.Rect, sn.restart.Tex2D, sn.restart.Tex2D, nil)        if e.JustPressed() {            // do something...
        }
    }

这部分代码我们就不做过多的解释了,它和实现 Ready 状态的菜单,并没有任何区别。此时点击 "重新开始" 按钮,并没有任何反应,这是因为我们还没有处理点击事件, 运行这部分代码大概会得到这样的“死亡”菜单:

game over


重置游戏状态非常简单,在 GameScene 中添加新的方法 reStart (),在这里重置了鸟的当前状态,管道管理系统的状态:


func (sn *GameScene) reStart() {
    sn.state = Ready    // bird
    sn.bird.state = Flying
    sn.bird.Vec2 = f32.Vec2{80, 240}
    sn.bird.vy = 0
    sn.bird.rotate = 0
    korok.Transform.Comp(sn.bird.Entity).SetRotation(0)
    korok.Flipbook.Comp(sn.bird.Entity).Play("flying")    // pipes
    sn.PipeSystem.Reset()
    sn.PipeSystem.StartScroll()
}

然后在之前的事情处理方法中调用之,现在运行:


reset game state

游戏状态已经正确的重置了。但是两种状态之间的转换略显僵硬。界面直接切换没有任何的过渡效果,如果能让场景渐变过去那就再好不过了。

场景转化动画

在我们当前的实现中,有好几个地方的场景过渡都是有问题的,最早的是开始游戏的场景过渡,其次是重新开始的场景过渡。实现场景过渡有很多种办法,此处的算法是:在场景上蒙上一层白色的遮罩,遮罩逐渐从透明过渡到白色,然后再从白色过渡到透明,在过渡的中间时刻切换场景。

从新回到 StartScene 这个场景,这次我们将会在它上面添加一层遮罩。这层遮罩是用 GUI 系统添加的一个白色图片,然后用补间动画系统来对遮罩的颜色做插值动画。在 StartScene 中添加一个属性:

type StartScene struct {
    ...
    mask gfx.Color
}

它的默认值为透明色,这样默认情况下它是不会遮住场景的,然后在 Update 方法中添加方法绘制遮罩:

    // fade color
    if sn.mask.A > 0 {
        gui.ColorRect(gui.Rect{W:320,H:480}, sn.mask,0)
    }

这段代码绘制了一个白色的矩形框,它的大小完全遮住整个屏幕。接下来是最重要的逻辑了 —— 启动动画:

func (sn *StartScene) fadeOut() {
    anim.OfColor(&sn.mask, gfx.Transparent, gfx.White).SetFunction(ease.InOutSine).SetDuration(1).OnComplete(func(reverse bool) {
        sn.LoadGame()
    }).Forward()
}if e.JustPressed() {
    sn.fadeOut()
}

在之前的代码中,点击按钮之后就直接开始 LoadGame 了。现在点击之后,先执行一段动画,动画把遮罩颜色从透明色渐变到白色,在动画结束之后再加载游戏。anim.OfColor 的方法签名如下:

func OfColor(target *gfx.Color, from, to gfx.Color) *proxyAnimator

它的可以把一个 gfx.Color 在指定的时间内从初始值过渡到目标值。在 Korok 的当前实现中还绘制纯色的图形还有些缺陷,需要设置字体系统之后才可以绘制图形,所以还需要在设置一下字体:

func (sn *StartScene) Load() {
    asset.Texture.LoadAtlas("images/bird.png", "images/bird.json")
    asset.Font.LoadTrueType("font1", "fonts/Marker Felt.ttf")
}

func (sn *StartScene) OnEnter(g *game.Game) {
    font, _ := asset.Font.Get("font1")
    gui.SetFont(font)
    ...
}

运行这段代码,下面就是渐变效果:


fade out

现在当前场景已经可以渐出了,但是结束的时候好像闪烁了一下。这是因为我么只给当前场景加入了渐出的动画,却没有给下一个场景添加渐入的动画。接下来会 GameScene 加入渐入的动画,但是在实现上会略有不同。在 StartScene 中,我们使用 anim.OfColor 来执行动画,这个方法适合快速实现的场景但是效率不高,在 GameScene 中,我们尝试一种更快的动画实现方式——Tween系统。

Tween 系统

这是 Korok 的底层动画系统实现,核心是一个归一化的 Animator 动画驱动器。此处会使用 ColorTween 来执行颜色的补间动画,首先在 GameScene 中添加属性:

type GameScene struct {
    ...
    
    alphaTween ween.ColorTween
}

OnEnter 方法中,给它设置一个动画驱动器 Animator:

sn.alphaTween.Range(gfx.White, gfx.Transparent).Animate(g.TweenEngine.NewAnimator())sn.alphaTween.Animator().SetFunction(ease.InOutSine).SetDuration(.5).Forward()

这段代码给 ColorTween 设置了起始颜色 [White, Transparent],并配置了 Animator,然后配置 Animatorease 方程为 InOutSine,设置动画时长为 0.5秒,然后直接启动动画(因为我们希望场景加载之后立刻执行一个渐入的动画)。同时,和前一个场景类似,在 Update 方法中,需要绘制一个遮罩:

    if sn.alphaTween.Value().A > 0 {        z := gui.SetZOrder(gui.DefaultZOrder+1)
        gui.ColorRect(gui.Rect{0,0, 320, 480}, sn.alphaTween.Value(), 0)
        gui.SetZOrder(z)
    }

此处绘制遮罩的时候,调整了遮罩的z-order,这样可以使它绘制在其它GUI元素的上层,之所以要改变 z-order 是因为GUI的绘制使用的是画家算法,先绘制的UI元素实际会显示在下层,而我们在 Update 方法的入口绘制的遮罩,如果不改变z-order他会显示在其它UI的下方。现在运行一下:

perfect!!


现在我们已经实现了一次完美的过渡动画!!


接下来还有一个地方,在点击重新开始的时候也需要过渡动画。前面已经写了很多代码了,实现这里的动画,只要调用 ColorTween 直接执行新的即可:

    if e.JustPressed() {
        sn.alphaTween.Range(gfx.Transparent, gfx.White)
        sn.alphaTween.Animator().SetFunction(ease.InOutSine).SetDuration(.5).OnComplete(func (r bool) {
            sn.reStart()
        }).Forward()
    }

    func (sn *GameScene) reStart() {
        ....        // reverse
        sn.alphaTween.Animator().OnComplete(nil).Reverse()
    }

检测到点击事件后,不再直接调用 reStart() 方法,而是先执行 ColorTween 启动动画,在动画结束后在调用 reStart() 方法,同时在 reStart() 结束后执行 ColorTweenReverse 方法,这是一个便捷的方法可以把刚刚的动画过程反向执行。这样我们就实现了一个: 透明 -> 白色 (restart) -> 透明 的过程。

final tween


总结

以上我们实现了场景转化动画,学会使用了补间动画系统。"死亡" 菜单在原游戏中是有一个跳出的动画的,此处我们并没有实现,这部分交给你来实现了。下一节,将会给游戏添加音效,没有音效怎么好意思叫视频游戏呢!

代码我已经传到 GitHub - ntop001/flappybird,请关注 ch4 分支。



作者:ntop
链接:https://www.jianshu.com/p/a2c841136e20

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消