这可能是世界上最简单的用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 字节码 。 如果你需要在他们之间分享任何东西 , 也没问题 , 因为它们可以共享原始内存 。
- 对手|一加9Pro全面曝光,或是小米11最大对手
- 行业|现在行业内客服托管费用是怎么算的
- 人民币|天猫国际新增“服务大类”,知舟集团提醒入驻这些类目的要注意
- 王兴称美团优选目前重点是建设核心能力;苏宁旗下云网万店融资60亿元;阿里小米拟增资居然之家|8点1氪 | 美团
- 长安|长安傍上华为这个大腿,市值暴涨500亿!可见华为影响力之大?
- 手机基带|为了5G降低4G网速?中国移动回应来了:罪魁祸首不是运营商
- 技术|做“视频”绿厂是专业的,这项技术获人民日报评论点赞
- 互联网|苏宁跳出“零售商”重组互联网平台业务 融资60亿只是第一步
- 峰会|这场峰会厉害了!政府企业专家媒体共议网络内容生态治理
- 手机|这个超强App,让手机快3倍,流畅到起飞