2021SC@SDUSC
目录
一.概述
本文将重点介绍ebiten的时钟系统以及图像绘制选项中的Filter纹理过滤器。首先时钟系统是用来在游戏的进行过程中对时间线进行一个记录,有获取当前时间,初始化参数,获取当前FPS(每秒传输帧数),TPS(每秒处理事务数),获取TPS的计算计数,更新当前FPS和TPS,更新系统时间等方法,然后Filter是图像绘制选项中的参数,设置绘制图像中的纹理滤镜,下面将逐一分析。
二.代码分析
1.时钟系统
var (
lastNow int64
//lastSystemTime是上次更新中的最后一个系统时间。
//lastSystemTime表示游戏中的逻辑时间,可以大于Curren时间
lastSystemTime int64
currentFPS float64
currentTPS float64
lastUpdated int64
fpsCount = 0
tpsCount = 0
m sync.Mutex
)
func init() {
n := now()
lastNow = n
lastSystemTime = n
lastUpdated = n
}
首先定义全局变量上次更新的时间lastNow,上次系统时间,当前FPS,TPS,最后一次更新,帧数和事务数,以及一个互斥锁。
上述部分变量在init()方法中完成初始化。
其中now函数的定义在同级文件夹下的now.go文件内:
var initTime = time.Now()
func now() int64 {
// Time.Since()返回单调计时器差(#875):
// https://golang.org/pkg/time/#hdr-Monotonic_Clocks
return int64(time.Since(initTime))
}
作用是获取当前的系统时间。
然后是在有互斥锁保护的前提下获取当前FPS和TPS:
func CurrentFPS() float64 {
m.Lock()
v := currentFPS
m.Unlock()
return v
}
func CurrentTPS() float64 {
m.Lock()
v := currentTPS
m.Unlock()
return v
}
下面是根据TPS计数:
func calcCountFromTPS(tps int64, now int64) int {
if tps == 0 {
return 0
}
if tps < 0 {
panic("clock: tps must >= 0")
}
diff := now - lastSystemTime
if diff < 0 {
return 0
}
count := 0
syncWithSystemClock := false
//检测上一次时间是否太旧。
//如果TPS太大,如300(#1444),则使用5刻度或5/60秒
if diff > max(int64(time.Second)*5/tps, int64(time.Second)*5/60) {
// The previous time is too old.
// Let's force to sync the game time with the system clock.
syncWithSystemClock = true
} else {
count = int(diff * tps / int64(time.Second))
}
//稳定计数。
//如果不进行此调整,计数可能会不稳定,如0、2、0、2、...。
//TODO:刷新此逻辑,以使其适用于任何FPS。现在,这仅在FPS=TPS时有效。
if count == 0 && (int64(time.Second)/tps/2) < diff {
count = 1
}
if count == 2 && (int64(time.Second)/tps*3/2) > diff {
count = 1
}
if syncWithSystemClock {
lastSystemTime = now
} else {
lastSystemTime += int64(count) * int64(time.Second) / tps
}
return count
}
防止出现长时间不更新时间导致此时的每秒处理事务数目过大,所以需要不断的计算并更新这些数值,实现方法是传入当前时间和TPS数,计算当前时间和上次更新时间的差值,如果这个值过大,那么就将TPS进行一个稳定计数。
然后是更新FPS和TPS方法:
func updateFPSAndTPS(now int64, count int) {
fpsCount++
tpsCount += count
if now < lastUpdated {
panic("clock: lastUpdated must be older than now")
}
if time.Second > time.Duration(now-lastUpdated) {
return
}
currentFPS = float64(fpsCount) * float64(time.Second) / float64(now-lastUpdated)
currentTPS = float64(tpsCount) * float64(time.Second) / float64(now-lastUpdated)
lastUpdated = now
fpsCount = 0
tpsCount = 0
}
传入当前时间和刚刚得到的TPS稳定计数,将帧数加一,事务数加上稳定计数,然后进行异常判断,当前时间不能比上一次更新时间还前且time.Second的值要比now-lastUpdate要小,接着计算FPS的值就是这一段时间内增加的帧数乘以Second除以距离上次更新的时间,然后更新上次更新时间的值,并将帧数和事务数归零。
下面是更新方法:
const SyncWithFPS = -1
//Update更新内部时钟状态,返回整数值。
//表示游戏根据给定的TPS需要更新多少次。
//TPS表示TPS(每秒刻度)。
//如果TPS为SyncWithFPS,则Update始终返回1。
//如果TPS<=0且不是SyncWithFPS,则Update始终返回0。
//。
//预计每帧都会调用Update
func Update(tps int) int {
m.Lock()
defer m.Unlock()
n := now()
if lastNow > n {
//这确保now()必须是单调的(#875)。
panic("clock: lastNow must be older than n")
}
lastNow = n
c := 0
if tps == SyncWithFPS {
c = 1
} else if tps > 0 {
c = calcCountFromTPS(int64(tps), n)
}
updateFPSAndTPS(n, c)
return c
}
每帧都调用,调用now()方法获取当前时间,计算当前TPS数并返回且将FPS数加一。
2.Filter纹理过滤器
ebiten的过滤器定义在internal/driver/filter.go文件内,代码如下:
type Filter int
const (
FilterNearest Filter = iota
FilterLinear
FilterScreen
)
其中FilterNearest表示最近(边缘清晰)的过滤器,FilterLine表示线性滤镜,filterScreen代表一种特殊的Screen筛选器。仅限内部使用且当使用filterScreen时,可以忽略颜色矩阵或颜色顶点值等参数。
纹理过滤器的其中一个很重要的目的就是图像的缩放,以图像缩放为例,采用最近的过滤器时,用最靠近像素中心的那个纹理单元进行放大和缩小,效率更高,但效果不好,锯齿严重。
采用线性过滤器时,是对靠近像素中心的纹理单元,取加权平均值,用于放大和缩小。效果更好,效率稍低。
但是线性方式存在问题,那就是边缘处理,通常情况下有两种方法,一种是取边缘外元素作为普通点进行加权计算,一种是不取。
前者表示“使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色”,只经过简单比较,需要运算较少,可能速度较快
后者表示“使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色”,需要经过加权平均计算,其中涉及除法运算,可能速度较慢。
具体的调用在根目录下的graphics.go文件中
//Filter表示放大或缩小图像时使用的纹理滤镜类型
type Filter int
const (
//FilterNeest表示最近(边缘清晰)的过滤器
FilterNearest Filter = Filter(driver.FilterNearest)
//FilterLine表示线性滤镜
FilterLinear Filter = Filter(driver.FilterLinear)
//filterScreen代表一种特殊的Screen筛选器。仅限内部使用。
//。
//当使用filterScreen时,可以忽略颜色矩阵或颜色顶点值等参数
filterScreen Filter = Filter(driver.FilterScreen)
)