这可能是世界上最简单的用Go来写WebAssembly的教程( 二 )

看起来是不是很像 JS 代码?
是的 , 这就是与 DOM 交互所需的全部内容!现在只需要几个 get 方法还有调用函数即可 。
在这一点上 , 我问自己:在某种程度上 , 我仍然在写 JS … 这怎么算是升级?因为我们还不能直接访问 DOM , 所以我们必须(通过 JS)调用 DOM 来做任何事情 。 想象一下如何用 JSX / React 来抽象化它 。
实际上 , 已经可以做到了 , 请期待我的下篇文章。
「渲染」还有事件处理直接使用 syscall / js 库 , 这个写法看起来有点像 ES5 的回调 。 但我们能够监听 DOM 事件 , 而且那些静态类型看起来很干净!
func main() { setup()// 在编译时声明渲染器 var renderer js.Func // 没有错 , 看起来很像 JS 的回调renderer = js.FuncOf(func(this js.Value, args []js.Value) interface{} {updateGame()// 实现 60FPS 的动画window.Call("requestAnimationFrame", renderer)return nil }) window.Call("requestAnimationFrame", renderer) // 让我们处理下 鼠标/手势 点击事件 var mouseEventHandler js.Func = js.FuncOf(func(this js.Value, args []js.Value) interface{} {updatePlayer(args[0])return nil }) window.Call("addEventListener", "pointerdown", mouseEventHandler)}func updatePlayer(event js.Value) {}func updateGame() {}日志记录、音频播放以及「异步」执行在 Go 中 , 有一个惯例是把所有的函数都写成同步的方式 , 由调用者决定函数的执行是否是异步的 。 异步运行函数非常简单 , 只要在前面加上 go 就行了!它使用自己的上下文创建一个线程 , 你仍然可以将父级上下文绑定给它 , 不要担心哈 。
func updatePlayer(event js.Value) { mouseX := event.Get("clientX").Float() mouseY := event.Get("clientY").Float()// `go` 关键字是主要用来实现线程、异步、并行的功能 // TODO 与 Web Workers 的区别 // TODO 与 Service Workers 的区别 //go log("mouseEvent", "x", mouseX, "y", mouseY) // 下一个关键点 if isLaserCaught(mouseX, mouseY, gs.laserX, gs.laserY) {go playSound() }}// 不要以为我用了什么黑魔法 , 这里直接使用了 HTML5 的 API// #Basic_usagefunc playSound() { beep.Call("play") window.Get("navigator").Call("vibrate", 300)}// 这里主要用了 JS 的解构赋值语法// 这里的 `...interface{}` 有点像 TS 的 `any` 语法// #Descriptionfunc log(args ...interface{}) { window.Get("console").Call("log", args...)}让游戏一直跑下去!该代码创建一个非缓冲通道 , 并尝试从该通道接收数据 。 因为没有人向它发送任何东西 , 它本质上是一个永久的阻塞操作 , 允许我们永远运行我们的程序 。
func main() { //// 创建空通道 runGameForever := make(chan bool) setup() // 尝试从空通道接收 // 由于没有人向它发送任何数据 , 它本质上是一个永久阻塞操作// 我们有一个 daeomon / service / background 程序// 在 WASM 里 , 我们的游戏会一直运行<-runGameForever}更新游戏状态并移动小红点这里没有状态管理 , 只有一个简单的声明类型的结构体 , 它不允许在内部传递任何不正确的值 。
import ( "math")type gameState struct{ laserX, laserY, directionX, directionY, laserSize float64 }var ( // gs 处于最高范围 , 小于这个范围小红点都能都能访问 gs = gameState{laserSize: 35, directionX: 3.7, directionY: -3.7, laserX: 40, laserY: 40})func updateGame() { // 边界判断 if gs.laserX+gs.directionX > windowSize.w-gs.laserSize || gs.laserX+gs.directionX < gs.laserSize {gs.directionX = -gs.directionX } if gs.laserY+gs.directionY > windowSize.h-gs.laserSize || gs.laserY+gs.directionY < gs.laserSize {gs.directionY = -gs.directionY } // 移动小红点gs.laserX += gs.directionX gs.laserY += gs.directionYr/> // 清除画布 laserCtx.Call("clearRect", 0, 0, windowSize.w, windowSize.h)r/> //画一个小红点laserCtx.Call("beginPath") laserCtx.Call("arc", gs.laserX, gs.laserY, gs.laserSize, 0, math.Pi*2, false) laserCtx.Call("fill") laserCtx.Call("closePath")}r/>// 判断点击的点是不是在小红点内部func isLaserCaught(mouseX, mouseY, laserX, laserY float64) bool {r/> // 直接这样返回是不行的r/> // return laserCtx.Call("isPointInPath", mouseX, mouseY).Bool()>// 所以这里我通过勾股定理来实现r/> // 同时我给 laserSize 属性的值加上 15 , 让猫爪更容易点击return (math.Pow(mouseX-laserX, 2) + math.Pow(mouseY-laserY, 2)) < math.Pow(gs.laserSize+15, 2)}总结事实上 , WASM 仍然被认为是一个 [MVP]( -post- MVP -future/) (MAP) , 你可以不用编写一行 JS , 就能创建一个像这样的游戏 。 惊不惊讶!CanIUse 上 WASM 的支持已经是一片绿色了 , 没有人可以阻止你去创建基于 WASM 的网站和应用 。
你可以组合所有你想要的语言 , 像是把 JS 转成 WASM 。 最后 , 它们都将编译成 WASM 字节码 。 如果你需要在他们之间分享任何东西 , 也没问题 , 因为它们可以共享原始内存 。