ebiten 学习(2)-无限循环播放器

2021SC@SDUSC

目录

一、前言

1.说明

2.ebiten 包目录

3.梗概

二、无限循环音频

 1.包

 2.引入包

 3.从 main() 开始

 4.Layout 方法

 5.Update 逻辑

 6.Draw 绘制界面

 7.运行结果

 8.总结

三、尾声

 1.困难

 2.下一次


一、前言

1.说明

audio 说明文档地址

audio package - github.com/hajimehoshi/ebiten/v2/audio - pkg.go.dev

本文中所有截图和代码均来自说明文档或 ebiten 源文件

笔者负责 audio 音频相关的代码分析

2.ebiten 包目录

3.梗概

分析 audioinfiniteloop 音频无限循环的一个实现(ebiten 源码中的例子)

文件位置

 

代码外观

这个“游戏”继承run.go 中的 Game 结构体以及方法

// game's Update function is called every tick to update the game logic.

每一帧都会调用Update方法,默认60帧即TPS = 60

// game's Draw function is called every frame to draw the screen.

Draw方法用来绘制界面元素,ebiten把一切都看作图片

// game's Layout function is called when necessary, and you can specify the logical screen size by the function.

用来调整程序框大小和内部屏幕大小的比例关系

                                                                                              ---英文摘自 run.go 文件方法注释


二、无限循环音频

 1.包

package main

代表此文件属于 main 包,main 方法只能在 main 包中

 2.引入包

后几行使用包的相对路径,因为使用了 go mod 包管理

编译器会在 GOPATHGOROOT 中寻找包

最后一行是重命名 raudio 

类似的还有

import (
     _ "      "
)

只引入不使用方法(go 默认引入必须使用)

import (
     . "      "
)

方法全部拷贝引入(不常用,会有重名风险)

 3.从 main() 开始

func main() {
    ebiten.SetWindowSize(screenWidth, screenHeight)
    ebiten.SetWindowTitle("Audio Infinite Loop (Ebiten Demo)")
    if err := ebiten.RunGame(&Game{}); err != nil {
        log.Fatal(err)
    }
}

首先是调用了 ebiten 中的 SetWindowSize( , ) 方法设置窗口大小

传入的参数是全局常量

 

然后 SetWindowTitle() 方法设置窗口的标题

效果如图

最后 run 这个”游戏”,RunGame() 方法一个进程只有一个

如果出现错误就会终止并且日志记录

(RunGame() 等方法以后会专门分析)

 4.Layout 方法

func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
    return screenWidth, screenHeight
}

Layout 方法设置窗口大小,具体操作没有找到,下周和分析 ui 的同学交流一下

 5.Update 逻辑

func (g *Game) Update() error {

//本程序只需要一个播放器,这是限制语句,只在最开始通过一次

    if g.player != nil {
        return nil
    }

//按照取样频率(常量 sampleRate = 22050)创建音频环境(audioContext)

//音频环境可以创建播放器 (Player)

    if g.audioContext == nil {
        g.audioContext = audio.NewContext(sampleRate)
    }

//解码ogg文件(raudio.Ragtime_ogg),按照音频环境的取样频率

//oggs 既是解码后的流阅读器也是流关闭器也是流定位器

    // Decode an Ogg file.
    // oggS is a decoded io.ReadCloser and io.Seeker.
    oggS, err := vorbis.Decode(g.audioContext, bytes.NewReader(raudio.Ragtime_ogg))
    if err != nil {
        return err
    }

//以解码的 oggs 创建无限循环流

//传入参数导言长度循环长度,秒数是常量,*4的原因:16 bits 双通道 ··> 字节 通道

    // Create an infinite loop stream from the decoded bytes.
    // s is still an io.ReadCloser and io.Seeker.
    s := audio.NewInfiniteLoopWithIntro(oggS, introLengthInSecond*4*sampleRate, loopLengthInSecond*4*sampleRate)

//新建播放器

//启动播放器

    g.player, err = g.audioContext.NewPlayer(s)
    if err != nil {
        return err
    }
​
    // Play the infinite-length stream. This never ends.
    g.player.Play()
    return nil
}

 6.Draw 绘制界面

如图

//获得上图的 Current 小数点之后的效果

func (g *Game) Draw(screen *ebiten.Image) {
    pos := g.player.Current()
    if pos > 5*time.Second {
        pos = (g.player.Current()-5*time.Second)%(4*time.Second) + 5*time.Second
    }

//格式化字符串

    msg := fmt.Sprintf(`TPS: %0.2f
This is an example using
audio.NewInfiniteLoopWithIntro.
​
Intro:   0[s] - %[2]d[s]
Loop:    %[2]d[s] - %[3]d[s]
Current: %0.2[4]f[s]`, ebiten.CurrentTPS(), introLengthInSecond, introLengthInSecond+loopLengthInSecond, float64(pos)/float64(time.Second))
// DebugPrint draws the string str on the image on left top corner.
    ebitenutil.DebugPrint(screen, msg)
}

 7.运行结果

音频一共 9 秒

先播放前 5 秒,之后无限循环播放后 4 秒

设置如下图倒数2、3行

Current 从 0.00 ····> 8.99

Current 从 5.00 ····> 8.99 无限循环

 8.总结

  • 本样例中最为核心的方法是重写的 Update() 方法
  • 播放第一步:生成音频环境,根据采样频率
g.audioContext = audio.NewContext(sampleRate)
  • 第二步:在上述音频环境下对音频文件解码,并起名
oggS, err := vorbis.Decode(g.audioContext, bytes.NewReader(raudio.Ragtime_ogg))
if err != nil {
	return err
}
  • 第三步:用上述解码后的变量、导言长度、循环长度创建无限循环 io.Reader
s := audio.NewInfiniteLoopWithIntro(oggS, introLengthInSecond*4*sampleRate, loopLengthInSecond*4*sampleRate)
  • 第四步:在上述音频环境下用第三步 io.Reader 创建播放器
g.player, err = g.audioContext.NewPlayer(s)
if err != nil {
	return err
}
  • 第五步:播放器调用 Play() 方法 
g.player.Play()

三、尾声

 1.困难

本次算是对于 audio 的初步了解,眼熟 ebiten 的简单流程。

分析有些不太容易,例子中使用的方法一层层深挖进去,涉及到了 go 语言中的结构体、继承、重载等,还有些地方不太明了,比如

&Game{} 是什么?

Layout() 方法什么时候调用?

bitDepthInBytes = 2 是什么意思?

音频环境,播放器背后的逻辑是什么?

 2.下一次

下周深入学习背后的的逻辑,学习 go 的继承方法,写几个 demo 实现一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值