You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免ClojureScript游戏引擎运行时阻塞浏览器事件?

问题:游戏循环阻塞Three.js渲染与按键事件处理

背景

我使用以下技术构建小型ECS游戏引擎:

  • Clojure
  • Three.js作为图形渲染目标之一
  • shadow-cljs用于项目构建

问题描述

浏览器加载应用后,游戏引擎的主游戏循环会阻塞Three.js通过requestAnimationFrame进行的屏幕渲染,同时阻止按键事件处理,直到循环结束后两者才恢复正常。

典型运行流程:

  1. 刷新浏览器窗口
  2. 控制台可见主游戏循环执行
  3. 循环期间,屏幕无内容显示,按键操作无日志输出
  4. 游戏结束后,所有按键事件批量触发并一次性记录
  5. 最后Three.js渲染器完成渲染

已尝试的无效方案

  • 在go表单中运行游戏
  • 先执行:start-up阶段,再用setTimeout运行剩余游戏循环
  • 不在init中调用run函数,尝试从配置文件指定加载后运行

预期效果

游戏循环运行时,按键事件能被实时处理,且Three.js渲染器每次在游戏循环中调用draw-scene!系统时都能完成渲染。


代码示例

src/app.cljs

(ns app
  (:require ["three" :as three]
            [snake :refer [add-snake-plugin]]
            [chaos.plugins.core :refer [add-core-plugins]]
            [chaos.plugins.timer :as timer]
            [chaos.engine.world :as chaos :refer [create-world
                                                  defsys
                                                  add-system
                                                  add-system-dependency
                                                  add-stage-dependency]]))

defsys setup-threejs {}
  (let [w (.-innerWidth js/window)
        h (.-innerHeight js/window)
        aspect (/ w h)
        camera (three/PerspectiveCamera. 75 aspect 0.1 1000)
        renderer (three/WebGLRenderer.)]

    ;; Setup renderer and dom elements.
    (.setSize renderer w h)
    (.. js/document -body (appendChild (.-domElement renderer)))
    ;; Move camera back
    (set! (.. camera -position -z) 5)
    (println "Cam Z:" (.. camera -position -z))
    (println renderer)

    [[:add [:resources :camera] camera]
     [:add [:resources :scene] (three/Scene.)]
     [:add [:resources :renderer] renderer]])

defsys add-cube {:resources [:scene]}
  (let [scene (:scene resources)
        geometry (three/BoxGeometry. 1 1 1)
        material (three/MeshBasicMaterial. #js {:color 0x00ff00})
        cube (three/Mesh. geometry material)]
    (.add scene cube)
    [])

defsys draw-scene! {:resources [:renderer :scene :camera]
                     :events :tick}
  (println "Drawing...")
  (let [{:keys [:renderer :scene :camera]} resources
        render-scene #(.render renderer scene camera)]
    (.. js/window (requestAnimationFrame render-scene))
    [])

defsys capture-key-down {}
  (let [raw (atom [])
        add-event (fn [event]
                    (println "Keydown event!")
                    (swap! raw conj event))]
    (.addEventListener js/window "keydown" add-event)
    [[:add [:resources :key-down-events] raw]])

defsys handle-key-down {:resources [:key-down-events]}
  (println "KEYS" (:key-down-events resources))

;; ... Pruned some irrelevant systems ...

defn ^:dev/after-load run []
  (-> (create-world)
      add-core-plugins ;; Main engine plugins (removing has no effect)
      add-snake-plugin ;; The snake game library from another example (as above)
      (add-system :start-up setup-threejs) 
      (add-system :start-up add-cube)
      (add-system-dependency add-cube setup-threejs)
      (add-system :render draw-scene!)

      ;; Set of irrelevant systems which essentially just exit the game after 5 seconds.
      (add-system :start-up add-exit-timer)
      (add-system :pre-step pass-time)
      (add-system exit-after-5)

      ;; Gets key events from window
      (add-system :start-up capture-key-down)

      (add-stage-dependency :render :update)
      chaos/play)

;; shadow-cljs entry point
defn init []
  (println "Refresh.")
  (run))

shadow-cljs.edn

;; shadow-cljs configuration
{:deps true
 :dev-http {8080 "public"}
 :builds
 {:app {:target :browser
        :modules {:main {:init-fn app/init}}}}}

内容的提问来源于stack exchange,提问作者hjk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 20:18:27