Skip to content
On this page

凹语言开发案例分享: Pong游戏


WASM-4 是一款使用 WebAssembly 实现的复古风格游戏机。凹语言作为国内首个面向 WebAssembly 设计的通用编程语言在 syscall/wasm4 内置标准库对 WASM4 平台提供了支持,从而为使用凹语言开发小游戏的用户提供最佳体验。

我们以一个简单的乒乓球游戏作为例子,看看如何开发WASM4游戏。

配置环境

安装凹语言 v0.15 以上的版本,或者通过以下Go命令安装最新的wa命令行:

$ go install wa-lang.org/wa@master

然后通过以下命令创建一个hello新目录工程:

$ wa init -wasm4
$ tree hello/
hello/
├── README.md
├── src
│   └── main.wa
└── wa.mod

2 directories, 3 files

命令行环境进入hello目录后,输入wa run可以在浏览器打开查看效果。

程序整体骨架

直接修改src/main.wa文件:

import (
	"math/rand"
	"strconv"
	"syscall/wasm4"
)

const (
	width  = 5
	height = 15

	ballSize   = 5
	screenSize = int(wasm4.SCREEN_SIZE)
)

// 玩家1(右边): 上下方向键
// 玩家2(左边): ED键对应上下键, 左右方向键盘控制

global game = NewPongGame(true) // 双人游戏

#wa:export update
func Update {
	game.Input()
	game.Update()
	game.Draw()
}

Update函数会以每秒60帧的频率被调用,其中分布出来游戏的输入、更新游戏状态并显示。

定义游戏对象

在对象中保存的游戏状态:

wa
// 游戏的状态
type PongGame :struct {
	isMultiplayer: bool // 多人游戏
	ballX:         int  // 球的水平位置
	ballY:         int  // 球的竖直位置
	dirX:          int  // 球的方向
	dirY:          int  // 球的方向
	y1:            int  // 左边挡板位置
	y2:            int  // 右边挡板位置
	score1:        int  // 玩家分数
	score2:        int  // 玩家分数
}

// 构建一个新游戏对象
func NewPongGame(enableMultiplayer: bool) => *PongGame {
	return &PongGame{
		isMultiplayer: enableMultiplayer,
		ballX:         screenSize / 2,
		ballY:         screenSize / 2,
		dirX:          1,
		dirY:          1,
		y1:            screenSize / 2,
		y2:            screenSize / 2,
		score1:        0,
		score2:        0,
	}
}

主要是乒乓球、挡板等位置和方向信息。

处理输入键

通过不同方向键盘分别控制2个挡板的移动。

wa
func PongGame.Input {
	// 第1个玩家
	if pad := wasm4.GetGamePad1(); pad&wasm4.BUTTON_UP != 0 && this.y1 > 0 {
		this.y1 -= 2
	} else if pad&wasm4.BUTTON_DOWN != 0 && this.y1+height < screenSize {
		this.y1 += 2
	}

	// 第2个玩家或机器人
	if this.isMultiplayer {
		// 左右方向键盘控制
		if pad := wasm4.GetGamePad1(); pad&wasm4.BUTTON_LEFT != 0 && this.y2 > 0 {
			this.y2 -= 2
		} else if pad&wasm4.BUTTON_RIGHT != 0 && this.y2+height < screenSize {
			this.y2 += 2
		}

		if pad := wasm4.GetGamePad2(); pad&wasm4.BUTTON_UP != 0 && this.y2 > 0 {
			this.y2 -= 2
		} else if pad&wasm4.BUTTON_DOWN != 0 && this.y2+height < screenSize {
			this.y2 += 2
		}
	} else {
		this.y2 = this.ballY // 自动对齐到接球位置(TODO: 失误机制)
	}
}

根据键盘更新挡板的位置信息。

更新游戏的状态

每秒钟60帧的速度更新状态:

wa
func PongGame.Update {
	// 更新球的方向
	if dirNow := this.paddleCollision(); dirNow != 0 {
		wasm4.Tone(2000, 5, 100, wasm4.TONE_PULSE2|wasm4.TONE_MODE2)
		if rand.Int()%2 != 0 {
			this.dirX = dirNow
			this.dirY = -1
		} else {
			this.dirX = dirNow
			this.dirY = 1
		}
	}

	// 更新球的位置
	this.ballX += this.dirX
	this.ballY += this.dirY

	// 检查球是否反弹
	if this.ballY > screenSize || this.ballY < 0 {
		wasm4.Tone(2000, 5, 100, wasm4.TONE_PULSE2|wasm4.TONE_MODE2)
		this.dirY = -this.dirY
	}

	// 判断得分
	if this.ballX <= 0 || this.ballX > screenSize {
		wasm4.Tone(1000, 5, 100, wasm4.TONE_PULSE2|wasm4.TONE_MODE2)

		if this.ballX <= 0 { // 左边玩家失球
			this.score2 += 1
		} else if this.ballX > screenSize {
			this.score1 += 1 // 右边玩家失球
		}

		// 重置球位置
		this.ballX = screenSize / 2
		this.ballY = screenSize / 2
		this.dirX = -this.dirX
	}
}

同时判断失球和得分情况。以下是碰撞判断:

wa
func PongGame.paddleCollision => int {
	if this.ballX < width &&
		this.ballY < this.y2+height &&
		this.ballY+ballSize > this.y2 {
		return 1
	}
	if this.ballX+ballSize > screenSize-width &&
		this.ballY < this.y1+height &&
		this.ballY+ballSize > this.y1 {
		return -1
	}
	return 0
}

球碰到和超出边界表示失球得分。

如何画乒乓球和挡板

WASM4 的调色板寄存器一次只能存储 4 种颜色,可以通过更改这一寄存器来引入新的颜色。以下是WASM4默认的配色表:

WASM4内置的绘图函数不直接访问这个颜色表寄存器,而是访问同样能够存储4个颜色的 DRAW_COLORS寄存器来指定对应的颜色表索引。可以通过wasm4.SetDrawColors函数完成。

绘制场景的代码:

wa
func PongGame.Draw {
	wasm4.SetDrawColors(0, 4)
	wasm4.SetDrawColors(1, 0)
	wasm4.Text(strconv.Itoa(this.score1), 85, 0)
	wasm4.Text(strconv.Itoa(this.score2), 70, 0)
	wasm4.Rect(screenSize/2, 0, 2, screenSize)

	wasm4.SetDrawColors(0, 2)
	wasm4.SetDrawColors(1, 3)
	wasm4.Oval(this.ballX, this.ballY, ballSize, ballSize)
	wasm4.Rect(0, this.y2, width, height)
	wasm4.Rect(screenSize-width, this.y1, width, height)
}

有此乒乓球游戏就完成了。

完整代码

完整代码大约150行:https://github.com/wa-lang/wa/tree/master/waroot/examples/w4-pong

如果你也是游戏爱好者,也可以试试用凹语言开发自己的游戏了。