我想编写一个游戏,并希望将组件模式用于多个实体。
在具有接口/类型类/多重继承的语言中,没有问题。
我希望某些实体可以更新,但不能渲染,有些实体应该是。
Haskell中:
class Updateable a where
update :: Float -> a -> a
class Renderable a where
render :: a -> Picture
class InputHandler a where
handleInput :: Event -> a -> a
我可以创建一个可以更新的内容列表。
updateAll :: Updateable a => Float -> [a] -> [a]
updateAll delta objs = map (update delta) objs
在Java / D / ...中,这可以通过接口
实现interface Updateable {
void update(float delta);
}
// somewhere in a method
List<Updateable> objs = ...;
for (Updateable o : objs) {
o.update(delta);
}
现在我想知道如何使用multimethods在nim中实现这一点。
是否可以用类型表示拟合多方法的存在?
var objs: seq[???] = @[]
答案 0 :(得分:4)
我不确定这是否能回答你的问题,但值得一提。
如果您要根据类型将游戏对象存储在单独的列表中,您仍然可以编写许多通用逻辑。由于预读和分支预测,按类型存储对象具有更好的性能。从一个应该知道他在说什么的人那里看这个讲座:Multiprocessor Game Loops: Lessons from Uncharted 2: Among Thieves。
例如,如果您为某些对象类型定义了texture
proc,那么您可以编写一个适用于所有对象类型的通用draw(t: T) = magicRenderToScreen(texture(t))
proc。如果您正在实施资源池或任何类型的一般行为,这也很有用。
你必须以某种方式在渲染和更新循环中包含每个受影响的对象类型,但这在实践中通常不是什么大问题。你甚至可以使用一个简单的宏来减少冗长,所以你的渲染循环只包含renderAll(players, enemies, sprites, tiles)
通用列表在编译语言中并不简单,并且nim强迫您查看它,这在您处理游戏时很有用。要拥有通用列表,您通常必须使用指针和动态调度,或某种类型的联合类型。我似乎记得nim过去能够从父对象ref调用正确的多方法(这将使列表包含多个类型并在运行时动态调度)但我老实说不确定是否仍然可以做...?
更有知识的人请告诉我们!
答案 1 :(得分:3)
缺少明确的interface
关键字是common question in the Nim community。考虑到Araq的答案并将其应用于基于Java / D片段的假设案例,我们可以这样写:
import strutils # For formatFloat
type
IUpdateable =
tuple[
update: proc(v: float) {.closure.},
show: proc(): string {.closure.}
]
Rounded = ref object
internalValue: float
Real = ref object
a_real_value: float
# Here goes our rounded type.
proc `$`(x: Rounded): string =
result = "Rounded{" & $int(x.internalValue) & "}"
proc updateRounded(x: Rounded, delta: float) =
x.internalValue += delta
proc getUpdateable(x: Rounded): IUpdateable =
result = (
update: proc(v: float) = x.updateRounded(v),
show: proc(): string = `$`(x)
)
converter toIUpdateable(x: Rounded): IUpdateable =
result = x.getUpdateable
# Here goes our Real type.
proc `$`(x: Real): string =
result = "Real{" &
x.a_real_value.format_float(precision = 3) & "}"
proc update_real(x: Real, delta: float) =
x.a_real_value += delta
proc getUpdateable(x: Real): IUpdateable =
result = (
update: proc(v: float) = x.update_real(v),
show: proc(): string = `$`(x)
)
# Here goes the usage
proc main() =
var objs: seq[IUpdateable] = @[]
var a = Rounded()
var b = Real()
a.internalValue = 3.5
b.a_real_value = 3.5
objs.add(a) # works because of toIUpdateable()
objs.add(b.getUpdateable)
for obj in objs:
echo "Going through one loop iteration"
echo "\t", obj.show()
obj.update(0.4)
echo "\t", obj.show()
obj.update(0.4)
echo "\t", obj.show()
main()
# -> Going through one loop iteration
# -> Rounded{3}
# -> Rounded{3}
# -> Rounded{4}
# -> Going through one loop iteration
# -> Real{3.50}
# -> Real{3.90}
# -> Real{4.30}
然而,正如你可以read in that forum thread,根据你需要什么接口,其他方法可能会更好。此外,可能未来的方式是concepts,但是像往常一样,手册是干的the related unit tests are cryptic所以我无法将上一个元组示例翻译成概念。
如果你想参加概念,你应该直接在论坛中提问,但要注意,正如手册所说,concepts are still in development。
答案 2 :(得分:0)
Swift存在相同的问题,并且在那里使用了Type Erasure,它与前面的注释中所建议的相同,但结构更为紧凑。 Nim中的一般模式如下:
#-------------------------------------------------------------
# types
#-------------------------------------------------------------
type C = concept type C
proc name(x: C, msg: string): string
type AnyC = object
name: proc(msg: string): string # doesn't contain C
type A = object
type B = object
#-------------------------------------------------------------
# procs
#-------------------------------------------------------------
proc name(x: A, msg: string): string = "A" & msg
proc name(x: B, msg: string): string = "B" & msg
proc name(x: AnyC, msg: string): string = x.name(msg) # AnyC implements C
proc to_any(x: A): AnyC = AnyC(
name: proc (msg: string): string = name(x, msg) # x captured by proc
)
proc to_any(x: B): AnyC = AnyC(
name: proc (msg: string): string = name(x, msg) # x captured by proc
)
# actually use C
proc print_name(x: C, msg: string) = echo x.name(msg)
#-------------------------------------------------------------
# main
#-------------------------------------------------------------
let a = A()
let b = B()
let cs = [a.to_any(), b.to_any()] # the main goal of most erasure cases
for c in cs:
c.print_name(" erased") # e.g. "A erased"
在此示例中,AnyC
实现了C
,A
和B
也实现了C
,但更重要的是可以转换为AnyC
。 Any*
类型通常包含闭包,以有效地擦除该类型,并通过简单转发参数来实现concept
本身。
我希望有一个宏或能够自动实现Any*
和to_any
的东西。