2021SC@SDUSC
目录
一、前言
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 包管理
编译器会在 GOPATH 和 GOROOT 中寻找包
最后一行是重命名为 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 双通道 ··> 2 字节 * 2 通道
// 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 实现一下。