跳转到内容
View in the app

A better way to browse. Learn more.

彼岸论坛

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.
欢迎抵达彼岸 彼岸花开 此处谁在 -彼岸论坛

[程序员] 凹语言开发案例分享: Pong 游戏

发表于

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

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

st0049-01.png

配置环境

安装凹语言 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 帧的频率被调用,其中分布出来游戏的输入、更新游戏状态并显示。

定义游戏对象

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

// 游戏的状态
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 个挡板的移动。

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 帧的速度更新状态:

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
	}
}

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

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 默认的配色表:

st0049-02.png

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

绘制场景的代码:

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

在线体验地址: https://wa-lang.org/wa/w4-pong/

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

Featured Replies

No posts to show

创建帐户或登录来提出意见

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.