用C++编写的游戏通常具有类层次结构,例如
现在我已经读过一些人认为即使使用C++,类层次结构也是错误的架构.但至少它会尝试代码重用.并且显而易见的是能够将所有东西都推到一个管理容器中,因为所有东西都很容易归入CEntity列表.
但无论如何,对于那些试图从C++切换到Haskell来制作游戏的人来说,如何改变体系结构以适应Haskell的功能范例?
not*_*job 16
我认为将OO代码转换为Haskell是错误的,而是在Haskell中从头开始编写游戏.
在我看来,最适合用于游戏编程的工具是功能反应编程.这使您可以根据行为和事件进行思考 - 您的游戏元素会随着时间的推移而发生变化,您可以将它们组合起来并定义它们之间的关
(你并不需要的一切推到一个容器中,除非你错过了世界管理更新的高级方式,并被迫沿着一些收集应用迭代.update()
方法.官能团反应性编程是管理更新的高级方式.)
学习思考玻璃钢需要时间,但投资是值得的.
代码重用(通常在Haskell中)通过
Functor
,Applicative
,Foldable
,Traversable
,Monad
这些往往比亚型多态性应用得更广泛.
我先说我对游戏开发一无所知,所以我的回答可能不适用于你的问题.
话虽如此,我认为关键是要问一个问题:为什么你在C++这样的语言中使用类层次?我认为答案是双重的:子类型多态性和代码重用.
正如您所指出的,使用继承来实现代码重用通常会受到批评,我认为是正确的.首选组合继承通常是很好的建议,它会减少耦合并使事情更加明确.Haskell中的等价物只是重用函数,这非常简单.
这给我们带来了第二个好处:亚型多态性.Haskell不支持子类型或子类型polymorphsim,但是对于类型类,它有另一种特殊的多态性,甚至更普遍(因为事物不需要处于子类型关系来实现相同的函数).
所以我的答案是:想想为什么你想要一个班级层次.如果您想要代码重用,那么只需通过将代码分解为足够通用的函数并重用这些代码来重用代码,如果您希望多态使用类型类.
在某些情况下,子类型实际上是有用的,因此有时Haskell不支持这一点,但根据我的经验,这是非常罕见的.另一方面,继承往往在C++或Java等语言中被过度使用,因为这是他们提供的一刀切的工具.
一般来说,我同意@ enoughreptocomment的答案,即在Haskell中重现OO设计是错误的 - 你通常可以做得更好!我只是想指出类层次给你的东西,以及在Haskell中可以实现类似的东西.
编辑(回应Zeta的评论):
确实,类型类不允许在列表等数据类型中使用异构类型,但是如果有额外的辅助数据类型,这也可以实现(从Haskell wikibook中窃取):
{-# LANGUAGE ExistentialQuantification #-}
data ShowBox = forall s. Show s => SB s
heteroList :: [ShowBox]
heteroList = [SB (), SB 5, SB True]
instance Show ShowBox where
show (SB s) = show s
f :: [ShowBox] -> IO ()
f xs = mapM_ print xs
main = f heteroList
Run Code Online (Sandbox Code Playgroud)
Haskell没有子类型,因此很难直接转换层次结构.你可以试着用类型类做一些疯狂的黑客但我不推荐它,因为它很快变得非常复杂.
您链接到的基于组件的体系结构与代码重用一样好,并且更容易转换为Haskell,因为没有类层次结构.
例如,在C++中,您将拥有一个渲染组件.在C++中,您可以将其表示为抽象渲染界面和一些具体的Render类.
class Renderer {
virtual void draw(double x, double y) = 0;
virtual void frobnicate(int n) = 0;
};
class HumanRenderer: public Renderer {
//render Players and Pedestrians...
//(code reuse!)
//constructor:
HumanRenderer(int age);
};
class MedkitRenderer: public Renderer{
//render the medkit
//constructor:
HumanRenderer(Color color);
};
Run Code Online (Sandbox Code Playgroud)
在Haskell中,如果没有子类型,你会做类似的事情.父接口的类型只是函数的记录:
data Renderer = Renderer {
rendererDraw :: Double -> Double -> IO (),
rendererFrobnicate :: Int -> IO ()
}
-- I'm putting everything in the IO monad so the code is side effecting like
--in the C++ version. If you want to avoid this mutation then this is where that
--functional reactive programming stuff would come in.
Run Code Online (Sandbox Code Playgroud)
并且具体类的构造函数只是返回其中一个记录的函数.
humanRenderer :: Int -> Renderer
humanRenderer age = -- ...
medkitRenderer :: Color -> Renderer
medkitRenderer rgb = -- ...
Run Code Online (Sandbox Code Playgroud)
请注意,由于存在单个"渲染器"类型,因此您可以将不同类型的渲染器放在同源列表中,就像在cpp中一样(这在类型类方法中会更加棘手):
renderers :: [Renderer]
renderers = [ humanRenderer 10, humanRenderer 20, medKitRenderer Red ]
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1379 次 |
最近记录: |