Skip to content

Commit ea9e387

Browse files
committed
zz
1 parent 4ad974b commit ea9e387

File tree

4 files changed

+244
-0
lines changed

4 files changed

+244
-0
lines changed

docs/public/st0049-01.png

1.97 KB
Loading

docs/public/st0049-02.png

4.88 KB
Loading

docs/smalltalk/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ title: 碎碎念
66

77
“碎碎念”是本站发布日常随笔的栏目,内容包括项目进展、小点子、有趣的讨论等。
88

9+
- 2024-09-06: [# 凹语言游戏开发案例分享: Pong](st0049.md)
910
- 2024-09-05: [凹语言图书: µGo语言实现](st0048.md)
1011
- 2024-08-28: [凹语言从G-Star毕业](st0047.md)
1112
- 2024-08-25: [凹语言支持Wasm4游戏平台](st0046.md)

docs/smalltalk/st0049.md

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
# 凹语言游戏开发案例分享: Pong
2+
3+
- 时间:2024-09-06
4+
- 撰稿:凹语言 开发组
5+
- 转载请注明原文链接:[https://wa-lang.org/smalltalk/st0049.html](https://wa-lang.org/smalltalk/st0049.html)
6+
7+
---
8+
9+
WASM-4 是一款使用 WebAssembly 实现的复古风格游戏机。凹语言作为国内首个面向 WebAssembly 设计的通用编程语言在 `syscall/wasm4` 内置标准库对 WASM4 平台提供了支持,从而为使用凹语言开发小游戏的用户提供最佳体验。
10+
11+
我们以一个简单的乒乓球游戏作为例子,看看如何开发WASM4游戏。
12+
13+
![](/st0049-01.png)
14+
15+
## 配置环境
16+
17+
安装凹语言 v0.15 以上的版本,或者通过以下Go命令安装最新的wa命令行:
18+
19+
```
20+
$ go install wa-lang.org/wa@master
21+
```
22+
23+
然后通过以下命令创建一个hello新目录工程:
24+
25+
```
26+
$ wa init -wasm4
27+
$ tree hello/
28+
hello/
29+
├── README.md
30+
├── src
31+
│ └── main.wa
32+
└── wa.mod
33+
34+
2 directories, 3 files
35+
```
36+
37+
命令行环境进入hello目录后,输入`wa run`可以在浏览器打开查看效果。
38+
39+
## 程序整体骨架
40+
41+
直接修改`src/main.wa`文件:
42+
43+
```
44+
import (
45+
"math/rand"
46+
"strconv"
47+
"syscall/wasm4"
48+
)
49+
50+
const (
51+
width = 5
52+
height = 15
53+
54+
ballSize = 5
55+
screenSize = int(wasm4.SCREEN_SIZE)
56+
)
57+
58+
// 玩家1(右边): 上下方向键
59+
// 玩家2(左边): ED键对应上下键, 左右方向键盘控制
60+
61+
global game = NewPongGame(true) // 双人游戏
62+
63+
#wa:export update
64+
func Update {
65+
game.Input()
66+
game.Update()
67+
game.Draw()
68+
}
69+
```
70+
71+
Update函数会以每秒60帧的频率被调用,其中分布出来游戏的输入、更新游戏状态并显示。
72+
73+
## 定义游戏对象
74+
75+
在对象中保存的游戏状态:
76+
77+
```wa
78+
// 游戏的状态
79+
type PongGame :struct {
80+
isMultiplayer: bool // 多人游戏
81+
ballX: int // 球的水平位置
82+
ballY: int // 球的竖直位置
83+
dirX: int // 球的方向
84+
dirY: int // 球的方向
85+
y1: int // 左边挡板位置
86+
y2: int // 右边挡板位置
87+
score1: int // 玩家分数
88+
score2: int // 玩家分数
89+
}
90+
91+
// 构建一个新游戏对象
92+
func NewPongGame(enableMultiplayer: bool) => *PongGame {
93+
return &PongGame{
94+
isMultiplayer: enableMultiplayer,
95+
ballX: screenSize / 2,
96+
ballY: screenSize / 2,
97+
dirX: 1,
98+
dirY: 1,
99+
y1: screenSize / 2,
100+
y2: screenSize / 2,
101+
score1: 0,
102+
score2: 0,
103+
}
104+
}
105+
```
106+
107+
主要是乒乓球、挡板等位置和方向信息。
108+
109+
## 处理输入键
110+
111+
通过不同方向键盘分别控制2个挡板的移动。
112+
113+
```wa
114+
func PongGame.Input {
115+
// 第1个玩家
116+
if pad := wasm4.GetGamePad1(); pad&wasm4.BUTTON_UP != 0 && this.y1 > 0 {
117+
this.y1 -= 2
118+
} else if pad&wasm4.BUTTON_DOWN != 0 && this.y1+height < screenSize {
119+
this.y1 += 2
120+
}
121+
122+
// 第2个玩家或机器人
123+
if this.isMultiplayer {
124+
// 左右方向键盘控制
125+
if pad := wasm4.GetGamePad1(); pad&wasm4.BUTTON_LEFT != 0 && this.y2 > 0 {
126+
this.y2 -= 2
127+
} else if pad&wasm4.BUTTON_RIGHT != 0 && this.y2+height < screenSize {
128+
this.y2 += 2
129+
}
130+
131+
if pad := wasm4.GetGamePad2(); pad&wasm4.BUTTON_UP != 0 && this.y2 > 0 {
132+
this.y2 -= 2
133+
} else if pad&wasm4.BUTTON_DOWN != 0 && this.y2+height < screenSize {
134+
this.y2 += 2
135+
}
136+
} else {
137+
this.y2 = this.ballY // 自动对齐到接球位置(TODO: 失误机制)
138+
}
139+
}
140+
```
141+
142+
根据键盘更新挡板的位置信息。
143+
144+
## 更新游戏的状态
145+
146+
每秒钟60帧的速度更新状态:
147+
148+
```wa
149+
func PongGame.Update {
150+
// 更新球的方向
151+
if dirNow := this.paddleCollision(); dirNow != 0 {
152+
wasm4.Tone(2000, 5, 100, wasm4.TONE_PULSE2|wasm4.TONE_MODE2)
153+
if rand.Int()%2 != 0 {
154+
this.dirX = dirNow
155+
this.dirY = -1
156+
} else {
157+
this.dirX = dirNow
158+
this.dirY = 1
159+
}
160+
}
161+
162+
// 更新球的位置
163+
this.ballX += this.dirX
164+
this.ballY += this.dirY
165+
166+
// 检查球是否反弹
167+
if this.ballY > screenSize || this.ballY < 0 {
168+
wasm4.Tone(2000, 5, 100, wasm4.TONE_PULSE2|wasm4.TONE_MODE2)
169+
this.dirY = -this.dirY
170+
}
171+
172+
// 判断得分
173+
if this.ballX <= 0 || this.ballX > screenSize {
174+
wasm4.Tone(1000, 5, 100, wasm4.TONE_PULSE2|wasm4.TONE_MODE2)
175+
176+
if this.ballX <= 0 { // 左边玩家失球
177+
this.score2 += 1
178+
} else if this.ballX > screenSize {
179+
this.score1 += 1 // 右边玩家失球
180+
}
181+
182+
// 重置球位置
183+
this.ballX = screenSize / 2
184+
this.ballY = screenSize / 2
185+
this.dirX = -this.dirX
186+
}
187+
}
188+
```
189+
190+
同时判断失球和得分情况。以下是碰撞判断:
191+
192+
```wa
193+
func PongGame.paddleCollision => int {
194+
if this.ballX < width &&
195+
this.ballY < this.y2+height &&
196+
this.ballY+ballSize > this.y2 {
197+
return 1
198+
}
199+
if this.ballX+ballSize > screenSize-width &&
200+
this.ballY < this.y1+height &&
201+
this.ballY+ballSize > this.y1 {
202+
return -1
203+
}
204+
return 0
205+
}
206+
```
207+
208+
球碰到和超出边界表示失球得分。
209+
210+
## 如何画乒乓球和挡板
211+
212+
WASM4 的调色板寄存器一次只能存储 4 种颜色,可以通过更改这一寄存器来引入新的颜色。以下是WASM4默认的配色表:
213+
214+
![](/st0049-02.png)
215+
216+
WASM4内置的绘图函数不直接访问这个颜色表寄存器,而是访问同样能够存储4个颜色的 DRAW_COLORS寄存器来指定对应的颜色表索引。可以通过`wasm4.SetDrawColors`函数完成。
217+
218+
绘制场景的代码:
219+
220+
```wa
221+
func PongGame.Draw {
222+
wasm4.SetDrawColors(0, 4)
223+
wasm4.SetDrawColors(1, 0)
224+
wasm4.Text(strconv.Itoa(this.score1), 85, 0)
225+
wasm4.Text(strconv.Itoa(this.score2), 70, 0)
226+
wasm4.Rect(screenSize/2, 0, 2, screenSize)
227+
228+
wasm4.SetDrawColors(0, 2)
229+
wasm4.SetDrawColors(1, 3)
230+
wasm4.Oval(this.ballX, this.ballY, ballSize, ballSize)
231+
wasm4.Rect(0, this.y2, width, height)
232+
wasm4.Rect(screenSize-width, this.y1, width, height)
233+
}
234+
```
235+
236+
有此乒乓球游戏就完成了。
237+
238+
## 完整代码
239+
240+
完整代码大约150行:https://github.com/wa-lang/wa/tree/master/waroot/examples/w4-pong
241+
242+
如果你也是游戏爱好者,也可以试试用凹语言开发自己的游戏了。
243+

0 commit comments

Comments
 (0)