2021SC@SDUSC山东大学软件学院软件工程应用与实践--Ebiten代码分析 源码分析(九)

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)
)
上一篇:设计模式-责任链模式~晚霞


下一篇:Spring Security(1-6) Spring Security 底层原理