前言

一个强盗以手枪对着我的胸口,要我倾囊给他,因而我自己从衣袋里掏出了钱包并亲手递给他,在这情况下,说我曾经给予承诺,这既不能改变案情,也不能意味着宽恕强力而转移权利。 – 约翰·洛克《政府论》

这个项目像个枪击游戏, 引用约翰·洛克的这句话来开篇。

我们在 MicroBlocks 分享会 0603 展示了 MicroBlocksRoblox 之间的互操作。 本文是这个演示的配套文档,帮助大家复现演示项目。

具体而言, 我们在 Roblox 中构建一个操场大小的像素屏(16x16), 当角色踩到这个巨型屏幕上的某个像素时, 像素随机变色, 同时,现实世界的对应像素也发生变化。

可以将其视为数字孪生的一个案例。

本文也展示了 AI(LLM) 作为编程助手,不仅可以让我们在不熟悉的领域快速起步, 还提升了整个学习体验。

我之前对游戏开发和 Roblox 都缺乏经验。借助 AI, 新手如我可以很快就构建出了核心功能,通过几句提示词,花上几分钟就构建了可运行的核心功能!

与 AI 一起编程,不只消除了起步的困难(对语法和 API 不熟悉), 还提供了实践项目式学习的途径。我要求 AI 帮助实施我的项目, 然后我阅读并试运行 AI 生成的项目代码,如有出错,就跟它反复沟通。在几分钟里我就进入到项目的核心工作中。

这个学习过程要比我之前的学习有效许多,之前一般先阅读并弄懂文档,然后开始编程。它之所以更有效,可能是因为我无需预先学习大量细节知识。 AI 帮我搭建项目骨架,帮我处理细节问题,我则在更高层面驾驶它。

之后,在反复沟通,解决问题过程中,我被迫关注具体细节,可能还需要通过查阅文档,才能进一步精细化地向 AI 表达我的想法。但此时,文档知识对我而言已经变得有趣(相比于之前学而不用的集邮式学习),因为它是构建我的想法大厦的砖块,知识一旦与学习者发生联系,就不再乏味了。

思路

Roblox 提供了出色的 3D 创作工具, 让我们可以天马行空地构建想象中的世界。在本文例子中,我们在 Roblox 里制造了一个操场大小的像素屏(16x16)。

我们为这个像素屏添加了行为, 使之能够响应用户的操作: 每当用户控制的角色碰到到像素时, 像素立刻随机变色, 并且现实的像素屏(由 MicroBlocks 驱动)也响应变色。

这里涉及到 Roblox 与现实设备的通信,我们使用了 HTTP 协议进行通信。Roblox 提供了 HTTP Client 与外部系统通信(需要的注意的是,它叫 HTTP Service 而不是 HTTP Server, Roblox 只能作为 HTTP client,而无法作为 HTTP Server)。

开始实验

由上边思路可知,演示项目涉及以下两个部分:

  • 在 MicroBlocks 中编程
  • 在 Roblox 中编程

在 MicroBlocks 中编程

NeoPanel 库

MicroBlocks 最近增加了一个新的库: NeoPanel。 由 Citilab 的 José 贡献给社区。

我在上周六(2023-05-27)的 MicroBlocks 分享会里分享了这个库。

我们使用这个库来驱动现实世界的像素屏。

编程板子使用了 ESP32, 我们需要在板子上运行 HTTP Server(支持 WiFi)。

MicroBlocks 程序

我使用 MicroBlocks 内置的 Browser Control Panel 作为脚手架。

这是修改后的程序 Roblox NeoPanel demo

这个程序比较简单, 它运行了一个 HTTP server。 要运行它,你需要做以下两件事:

  1. 设置像素屏信息: 像素排布、引脚
  2. 配置 wifi 信息

运行之后,你将在 MicroBlocks 里看到板子的 IP 地址。然后在浏览器打开: http://板子的IP地址, 诸如我的板子地址是: http://192.168.1.9

你可以通过这个界面设置屏幕上的像素,每当点击上图的提交, 你讲会把位置(7,7)的像素设置为红色, 实际会触发这个 HTTP 请求: http://192.168.1.9/?R=128&G=0&B=0&X=7&Y=7

由于设置像素只是打开一个 url 链接,所以只要我们在 Roblox 发送这个 HTTP 请求,同样能够设置像素颜色。

在 Roblox 中编程

我先将完整的项目分享在这里: 点击下载。 你可以通过以下方式直接将其加载到 Roblox 中:

运行之前,记的将像素屏的 url 设置为你的真实设备地址。

项目已经包含了以下所有的工作,所以将以下部分视为说明文档即可。

介绍 Roblox

Roblox 提供了世界上最易用的 3D 创作工具, 让任何人都能随时随地创造任何东西。

如果你曾经试图在创造 3D 世界,但备受挫败,Roblox 可能是你的救星。

近期我们(DynaLab)在 Roblox 里做一些与 AI (LLM)相关的探索, 被这个环境的灵活度与易用性所折服, 它在某些方面达到了个人计算期待的软件品质, 具有相当不错的 Liveness 特质。

安装配置

为了在 Roblox 中编程程序, 你首先需要 安装配置 Roblox Studio,可以将 Roblox Studio 看作在 3D 世界编程的 IDE。

提醒: 在国内运行你需要科学上网, Roblox 有一些额外要求,可能需要使用 Clash Pro, 并启用增强模式。

在 Roblox 脚本中发起 HTTP 请求

为了让我们的 Roblox 项目与 MicroBlocks 项目交互,我们决定采用 HTTP 通信,具体而言,我们想让 Roblox 脚本发起 HTTP 请求,把信息传到硬件设备里。

首先需要对 Roblox 项目进行配置, 使之可以发起 HTTP 请求(可参考 HttpService)。 具体而言,打开: FIlE -> Game Settings -> Security , 开启 Allow HTTP Requests:

完成设置之后,你就可以在 Script 里发起 HTTP 请求, 诸如:

1
2
3
local HttpService = game:GetService("HttpService")

HttpService:GetAsync("http://192.168.1.9/?R=128&G=0&B=0&X=7&Y=7")  -- 前边的例子

以上代码完成了最核心的工作: 让 Roblox 与 硬件设备通信。

剩下的工作,都属于 在Rolbox构建 3D 体验 范畴, 如果你之前有 Roblox 经验,不必看剩下的文章,应该都能完成我们的演示项目。

但我之前没什么经验,所以我打算让 ChatGPT 帮我写 Roblox 脚本。

我打算让 ChatGPT 专注于构建"操场大小的像素屏"。网络通信很简单(只有以上两行),我自己添加进入即可。

提示词

我给 ChatGPT 的提示词:

1
2
3
4
5
在 Roblox 创建脚本做以下事情:

1. 创建一个方形面板,名叫 NeoPanelBasePlate。方形面板的尺寸正好容纳其上的所有长方体
2. 方形面板上边均匀排布 16 行 16 列的 光滑黑色长方体, 这些长方体的尺寸如下, 长: 4, 宽: 4, 高:1。 这些长方体之间间隔为 4
3. 这些长方体有如下行为: 当角色碰到某个长方体时,它就随机变色。对每个长方体做防抖处理。

我将以上提示词输入到 GPT-4, 立刻得到了相当不错的脚手架代码。第一次运行就基本完成了 90% 的功能! 一些细微瑕疵在我跟它反馈之后, GPT-4 很快就修复了。

以上提示词是我凭记忆重写的,可能和真实的提示词有出入,最初的对话记录找不到了,因为 OpenAI 账号被封 :-( OpenAI 最近 GPU 不足, Twitter 上不少 Plus 用户抱怨被退钱封号。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
-- 计算需要的板子大小和位置
local blockSize = Vector3.new(4, 1, 4)
local blockGap = 4
local gridLength = 16
local basePlateSize = Vector3.new((blockSize.X + blockGap) * gridLength, 1, (blockSize.Z + blockGap) * gridLength)

-- 创建方形板子
local basePlate = Instance.new("Part")
basePlate.Name = "NeoPanelBasePlate"
basePlate.Size = basePlateSize
basePlate.Position = Vector3.new(basePlateSize.X / 2, 0, basePlateSize.Z / 2)
basePlate.Anchored = true
basePlate.Parent = game.Workspace

-- 创建16行16列的光滑黑色长方体
for i = 1, 16 do
	for j = 1, 16 do
		local block = Instance.new("Part")
		block.Size = blockSize
		block.Position = Vector3.new(i * (blockSize.X + blockGap) - blockSize.X / 2, blockSize.Y / 2, j * (blockSize.Z + blockGap) - blockSize.Z / 2)
		block.BrickColor = BrickColor.new("Black")
		block.Material = Enum.Material.SmoothPlastic
		block.Anchored = true
		block.Parent = basePlate

		-- 创建防抖动机制
		local debounce = false

		-- 添加触摸事件
		local function changeColor(otherPart)
			local humanoid = otherPart.Parent:FindFirstChildWhichIsA("Humanoid")
			-- print(humanoid.PlatformStand, humanoid:GetState())
			if not debounce and humanoid and humanoid.PlatformStand == false and humanoid:GetState() == Enum.HumanoidStateType.Running then
				debounce = true
				-- 更改颜色
				block.BrickColor = BrickColor.Random()
				wait(1) -- 防抖动
				debounce = false
			end
		end

		block.Touched:Connect(changeColor)
	end
end

ServerScriptService 创建 Script, 放入以上脚本。

我们就得到一个相当令人满意的 “操场大小的像素屏” 了, 每个像素都会响应角色的行为。

命名是计算机世界最难的两件事之一(另一件是缓存), 为何不叫 ChatGPT 来做呢? 我让它给这个脚本起一个合适的名字, 它的答复是: ColorChangingBlocksOnJump

加上网络请求

加上网络请求之后, 代码也很简单:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
local HttpService = game:GetService("HttpService")

-- 计算需要的板子大小和位置
local blockSize = Vector3.new(4, 1, 4)
local blockGap = 4
local gridLength = 16
local basePlateSize = Vector3.new((blockSize.X + blockGap) * gridLength, 1, (blockSize.Z + blockGap) * gridLength)

-- 创建方形板子
local basePlate = Instance.new("Part")
basePlate.Name = "NeoPanelBasePlate"
basePlate.Size = basePlateSize
basePlate.Position = Vector3.new(basePlateSize.X / 2, 0, basePlateSize.Z / 2)
basePlate.Anchored = true
-- basePlate.BrickColor = BrickColor.new("Black")
basePlate.Parent = game.Workspace
local NeoPanel_url = "http://192.168.1.9"

-- 创建16行16列的光滑黑色长方体
for i = 1, 16 do
	for j = 1, 16 do
		local block = Instance.new("Part")
		block.Size = blockSize
		block.Position = Vector3.new(i * (blockSize.X + blockGap) - blockSize.X / 2, blockSize.Y / 2, j * (blockSize.Z + blockGap) - blockSize.Z / 2)
		block.BrickColor = BrickColor.new("Black")
		block.Material = Enum.Material.SmoothPlastic
		block.Anchored = true
		block.Parent = basePlate

		-- 创建防抖动机制
		local debounce = false

		-- 添加触摸事件
		local function changeColor(otherPart)
			local humanoid = otherPart.Parent:FindFirstChildWhichIsA("Humanoid")
			-- print(humanoid.PlatformStand, humanoid:GetState())
			if not debounce and humanoid and humanoid.PlatformStand == false and humanoid:GetState() == Enum.HumanoidStateType.Running then
				debounce = true
				-- 更改颜色
				block.BrickColor = BrickColor.Random()

				local r,g,b = math.floor(block.BrickColor.r*255), math.floor(block.BrickColor.g*255), math.floor(block.BrickColor.b*255)
				local url = `{NeoPanel_url}/?R={r}&G={g}&B={b}&X={i}&Y={j}`
				print('url:', url)
				local response = HttpService:GetAsync(url)

				wait(1) -- 防抖动
				debounce = false
			end
		end

		block.Touched:Connect(changeColor)
	end
end

发布到 Roblox 社区?

如果你想将项目发布到 Roblox 社区,被更多人使用,可能需要处理一下网络问题。

Roblox 开发环境(Roblox Studio) 运行在本地,所以可以与局域网的设备通信。 如果你讲项目发布到 Roblox 社区, Roblox 中的 HTTP 请求将无法发到你的本地设备上,

如果你确实需要这样做,需要把硬件设备的 IP 地址也映射到公网, 可以通过 ngrok 来做到这点, 命令类似: ~/ngrok http 192.168.1.9:80 (192.168.1.9 替换为你的设备 IP)

进阶

以上代码没有包含枪击清屏的功能。这是为了降低复杂性, 以上代码最小化了在 IDE 里进行的操作。创建物体都在脚本里实现了, 无需在 IDE 里摆弄 3D 模型。

但不少用户觉得枪击效果非常酷。 这部分分享如何实现。

但我们的演示里,包含了一个靶子: 当我们用枪击中这个靶子时, 虚拟世界和现实世界的像素屏都会清屏。

添加靶子

在项目中添加一个 Part, 它的名字是 Clear。我还给了它一个贴花(Decal)

添加枪支

Roblox 官方为社区制作了一些精致的武器系统

我使用的枪是 submachine gun ,通过搜索找到它,然后将其添加到游戏里。

修改枪支对象(SMG) WeaponsSystem 模块里的 doDamage 函数:

1
2
3
4
5
6
7
8
9
function WeaponsSystem.doDamage(target, amount, damageType, dealer, hitInfo, damageData)
	if target.name == 'Clear' then -- Clear 是靶子对象,它的名字是 Clear
		for i, v in pairs(game.Workspace.NeoPanelBasePlate:GetChildren()) do
			v.BrickColor = BrickColor.new("Black")
		end
		local url = "http://192.168.1.9/clear"  -- 在 MicroBlocks 里编写了 clear 服务
		HttpService:GetAsync(url)
	end
    ...

完成。

参考