正文
()
{
...
for
!
window
.
ShouldClose
()
{
for
x
:=
range cells
{
for
_
,
c
:=
range cells
[
x
]
{
c
.
checkState
(
cells
)
}
}
draw
(
cells
,
window
,
program
)
}
}
现在我们的游戏逻辑全都设置好了,我们需要修改细胞绘制函数来跳过绘制不存活的细胞:
func (c *cell) draw() {
if !c.alive {
return
}
gl.BindVertexArray(c.drawable)
gl.DrawArrays(gl.TRIANGLES, 0, int32(len(square)/3))
}
如果我们现在运行这个游戏,你将看到一个纯黑的屏幕,而不是我们辛苦工作后应该看到生命模拟。为什么呢?其实这正是模拟在工作。因为我们没有活着的细胞,所以就一个都不会绘制出来。
现在完善这个函数。回到
makeCells
函数,我们用
0.0
到
1.0
之间的一个随机数来设置游戏的初始状态。我们会定义一个大小为
0.15
的常量阈值,也就是说每个细胞都有 15% 的几率处于存活状态。
import (
"math/rand"
"time"
...
)
const (
...
threshold = 0.15
)
func makeCells() [][]*cell {
rand.Seed(time.Now().UnixNano())
cells := make([][]*cell, rows, rows)
for x := 0; x < rows; x++ {
for y := 0; y < columns; y++ {
c := newCell(x, y)
c.alive = rand.Float64() < threshold
c.aliveNext = c.alive
cells[x] = append(cells[x], c)
}
}
return cells
}
我们首先增加两个引入:随机(
math/rand
)和时间(
time
),并定义我们的常量阈值。然后在
makeCells
中我们使用当前时间作为随机种子,给每个游戏一个独特的起始状态。你也可也指定一个特定的种子值,来始终得到一个相同的游戏,这在你想重放某个有趣的模拟时很有用。
接下来在循环中,在用
newCell
函数创造一个新的细胞时,我们根据随机浮点数的大小设置它的存活状态,随机数在
0.0
到
1.0
之间,如果比阈值(
0.15
)小,就是存活状态。再次强调,这意味着每个细胞在开始时都有 15% 的几率是存活的。你可以修改数值大小,增加或者减少当前游戏中存活的细胞。我们还把
aliveNext
设成
alive
状态,否则在第一次迭代之后我们会发现一大片细胞消亡了,这是因为
aliveNext
将永远是
false
。
现在继续运行它,你很有可能看到细胞们一闪而过,但你却无法理解这是为什么。原因可能在于你的电脑太快了,在你能够看清楚之前就运行了(甚至完成了)模拟过程。
让我们降低游戏速度,在主循环中引入一个帧率(FPS)限制:
const (
...
fps = 2
)
func main() {
...
for !window.ShouldClose() {
t := time.Now()
for x := range cells {
for _, c := range cells[x] {
c.checkState(cells)
}
}
if err := draw(prog, window, cells); err != nil {
panic(err)
}
time.Sleep(time.Second/time.Duration(fps) - time.Since(t))
}
}
现在你能给看出一些图案了,尽管它变换的很慢。把 FPS 加到 10,把方格的尺寸加到 100x100,你就能看到更真实的模拟:
const (
...
rows = 100
columns = 100
fps = 10
...
)
试着修改常量,看看它们是怎么影响模拟过程的 —— 这是你用 Go 语言写的第一个 OpenGL 程序,很酷吧?
进阶内容?
这是《OpenGL 与 Go 教程》的最后一节,但是这不意味着到此而止。这里有些新的挑战,能够增进你对 OpenGL (以及 Go)的理解。
☉ 让用户能够通过命令行参数指定格子尺寸、帧率、种子和阈值。在 GitHub 上的
github.com/KyleBanks/conways-gol
[4]
里你可以看到一个已经实现的程序。
☉ 用颜色表示细胞的状态 —— 比如,在第一帧把存活状态的格子设成绿色,如果它们存活了超过三帧的时间,就变成黄色。
☉ 如果模拟过程结束了,就自动关闭窗口,也就是说所有细胞都消亡了,或者是最后两帧里没有格子的状态有改变。
☉ 将着色器源代码放到单独的文件中,而不是把它们用字符串的形式放在 Go 的源代码中。
总结
希望这篇教程对想要入门 OpenGL (或者是 Go)的人有所帮助!这很有趣,因此我也希望理解学习它也很有趣。
正如我所说的,OpenGL 可能是非常恐怖的,但只要你开始着手了就不会太差。你只用制定一个个可达成的小目标,然后享受每一次成功,因为尽管 OpenGL 不会总像它看上去的那么难,但也肯定有些难懂的东西。我发现,当遇到一个难于理解用 go-gl 生成的代码的 OpenGL 问题时,你总是可以参考一下在网上更流行的当作教程的 C 语言代码,这很有用。通常 C 语言和 Go 语言的唯一区别是在 Go 中,gl 函数的前缀是
gl.
而不是
gl
,常量的前缀是
gl
而不是
GL_
。这可以极大地增加了你的绘制知识!
该教程的完整源代码可从
GitHub
[4]
上获得。
回顾
这是 main.go 文件最终的内容:
package main
import (
"fmt"
"log"
"math/rand"
"runtime"
"strings"
"time"
"github.com/go-gl/gl/v4.1-core/gl" // OR: github.com/go-gl/gl/v2.1/gl
"github.com/go-gl/glfw/v3.2/glfw"
)
const (
width = 500
height