我有一个实体(映射)的向量,我已将此向量过滤到具有特定键的那些实体。然后,我应用一个函数来更新这些实体中的某些值,现在有一个我想与原始超集合并的子集。
world [{a} {b} {c} {d} {e}]
a [{b} {d} {e}] ; (filter matches? world)
b [{b'} {d'} {e'}] ; (map func a)
new-world [{a} {b'} {c} {d'} {e'}] ; (merge world b)
如何将更新b
与原始合并world
?
我的地图结构是:
{:id identity
:attrs {:property1 {:param1 value
:param2 value}
:property2 {}}
可以有可变数量的属性。属性可以具有0({}
)或更多参数。映射中唯一更改的部分是值,并且其中只有一部分。
我必须查看每张地图以识别它,然后进行相应合并?惯用的方法是什么?
Clojure的map函数可以采用多个序列,但是不会将我的子集b
与整个超集进行比较world
,只能b
将世界实体的数量进行比较,并且一旦b
耗尽就停止。
> (map #(str %1 " " %2) [1 2 3 4 5] [6 7 8])
("1 6" "2 7" "3 8")
首先我是否为数据选择了错误的结构?如果没有矢量和仅一张地图,我会更好吗?
{:entid1
{:property1 {:param1 value :param2 value}
:property2 {}}
:entid2
{:property1 {:param1 value :param2 value}
:property2 {:param1 value}
:property3 {:param1 value :param2 value :param3 value}}}
这个问题看起来很相似,但是不能正确合并我的向量。
现实世界中的实现
我的实际代码是我正在写的游戏的一部分,以使自己熟悉Clojure(在这种情况下为ClojureScript)。
游戏状态(世界)如下:
[{:id :player,
:attrs
{:gravity {:weight 10},
:jump {:height 60, :falling false, :ground true},
:renderable {:width 37, :height 35},
:position {:x 60, :y 565},
:walk {:step 4, :facing :right},
:input {},
:solid {:blocked false}}}
{:id wall1,
:attrs
{:solid {},
:position {:x 0, :y 0},
:renderable {:width 20, :height 600}}}
{:id wall2,
:attrs
{:solid {},
:position {:x 780, :y 0},
:renderable {:width 20, :height 600}}}
{:id platform3,
:attrs
{:solid {},
:position {:x 20, :y 430},
:renderable {:width 600, :height 20}}}]
我在每个动画帧上更新世界,然后重新渲染画布。
(defn game-loop []
(ui/request-frame game-loop)
(-> world
system/update!
ui/render))
system/update!
是:
(defn update! [world]
(let [new-world (->> @world
(phys/move @player/input-cmd))]
(player/clear-cmd)
(reset! world new-world)
@world))
我的计划是让一系列系统在线程最后一个宏中更新世界。我正在写作中phys/move
。
这意味着系统必须更新世界,而不仅仅是返回它们对世界的影响(变化的向量)。
我正在考虑让该system/update!
功能管理世界上的效果(应用更新)是否更易于管理。因此,系统仅返回其更改列表。就像是:
(defn update! [world]
(let [updates []
game @world]
(conj updates (phys/move @player/input-cmd game))
(conj updates (camera/track :player game))
; etc.
(reset! world
(map #(update-world updates %) world))
@world))
重复的(conj updates (func world))
感觉虽然笨拙。这可能不仅仅是在系统功能中合并更新(或返回修改后的实体)的工作。
如何优雅地通过我的系统功能之间的状态变化(phys/move
,camera/track
),和子系统的功能(walk
,jump
)?
我在将Joaquin的仅地图方法应用于我的move
系统上的尝试是:
(ns game.phys)
(def step 4)
(def height 50)
(defn walk?
"is this a walk command?"
[cmd]
(#{:left :right} cmd))
(defn walks?
"update? equivalent"
[entity]
(contains? (:attrs entity) :position))
(defn walk
[cmd entity]
(if (walks? entity)
(let [x (get-in entity [:attrs :position :x]
op (if (= cmd :left) - +)
update {:attrs {:position {:x (op x step)}
:walk {:facing cmd}}}]
(merge entity update))
entity))
; cmd is a deref'ed atom that holds player input commands
; e.g. :left :right: :jump
(defn move
[cmd world]
(cond
(walk? cmd) (map #(walk input-cmd %) world)
(jump? cmd) (map #(jump height %) world)
:else world))
它比所有方法都要简单(拥有地图矢量是很常见的事情,它可能会适当地解决您的问题)。不用先做过滤器再做地图,只需做一个地图即可:
(defn update? [entity] ...)
(defn update-entity [entity] ...)
(defn update-world [world]
(let [update #(if (matches? %) (update-entity %) %)]
(map update world)))
这种更新或仅保留某些内容的模式非常普遍,使更新某些内容的函数返回更新的内容或旧的内容(如果它什么都不做)是惯用的,因此最后它就像:
(defn update-entity?
"Predicate that returns if an entity needs updating"
[entity] ...)
(defn update-entity
"Updates an entity if it is needed.
Otherwise returns the unchanged entity"
[entity]
(if (update-entity? entity)
(assoc entity :something :changed)
entity))
(defn update-world [world]
(map update-entity world))
您可以在此蛇游戏的游戏逻辑中看到有关此行为的一些示例:
https://github.com/joakin/cnake/blob/master/src/cnake/game.cljs#L54-L66
https://github.com/joakin/cnake/blob/master/src/cnake/game.cljs #L102-L114
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句