我正在尝试创建一个由几种不同类型组成的游戏引擎:
data Camera = Camera ...
data Light = SpotLight ... | DirectionalLight ...
data Object = Monster ... | Player ... | NPC ...
Run Code Online (Sandbox Code Playgroud)
但是,我现在正在尝试为所有这些实体实现基本物理.这要求它们各自包含a pos :: (Double, Double, Double)和a velocity :: (Double, Double, Double).
在面向对象的语言中,我将它实现为:
Camera implements PhysicalObject
Run Code Online (Sandbox Code Playgroud)
其中PhysicalObject包含两个属性pos和velocity.
我的直接反应是将它们全部放在同一类型中:
data Object = Monster ... | Player ... | NPC ... | Camera ...
Run Code Online (Sandbox Code Playgroud)
然而,我担心这可能会使得实现特定于摄像机的功能,特定于灯光的功能等变得困难.实际上,除了它们在世界上都具有物理位置和速度这一事实之外,它们几乎没有其他共同点. .
有没有比在每个类型构造函数中定义两个属性更简单的方法?
Chr*_*lor 15
我可以想到两种方法 - 类型和镜头.
class PhysicalObject m where
position :: m -> (Double, Double, Double)
velocity :: m -> (Double, Double, Double)
Run Code Online (Sandbox Code Playgroud)
然后,您将沿着以下行为对象创建实例
data Camera = Camera
{ cameraPosition :: (Double,Double,Double)
, cameraVelocity :: (Double,Double,Double)
}
instance PhysicalObject Camera where
position = cameraPosition
cameraVelocity = cameraVelocity
Run Code Online (Sandbox Code Playgroud)
和你的其他类型相似.然后,任何不需要知道对象细节的函数都可以只需要它的参数是实例PhysicalObject,例如:
type TimeInterval = Double
newPosition :: PhysicalObject m => TimeInterval -> m -> (Double,Double,Double)
newPosition dt obj = (x + du * dt, y + dv * dt, z + dw * dt)
where
(x,y,z) = position obj
(u,v,w) = velocity obj
Run Code Online (Sandbox Code Playgroud)
但是,您将很难编写使用此代码修改对象的函数- 该类告诉Haskell它如何访问对象的位置和速度,而不是如何修改它们.
另一种选择是转向镜头库.这是一个有点野兽,但它允许你写一些非常自然的代码.首先,有一些样板
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
Run Code Online (Sandbox Code Playgroud)
现在定义一些位置和速度数据类型.不要担心以下划线为前缀的奇怪字段名称 - 我们不会使用它们.
data Pos = Pos { _posX, _posY, _posZ :: Double }
data Vel = Vel { _velX, _velY, _velZ :: Double }
instance Show Pos where show (Pos x y z) = show (x,y,z)
instance Show Vel where show (Vel x y z) = show (x,y,z)
Run Code Online (Sandbox Code Playgroud)
现在,您使用一些Template Haskell来为您的数据类型派生镜头.这将生成类型类HasPos,HasVel其方法允许您访问和修改作为这些类的实例的任何值.
makeClassy ''Pos
makeClassy ''Vel
Run Code Online (Sandbox Code Playgroud)
现在定义您的相机类,其中包括位置和速度.
data Camera = Camera
{ _cameraPos :: Pos
, _cameraVel :: Vel } deriving (Show)
Run Code Online (Sandbox Code Playgroud)
Template Haskell的另一部分将自动创建功能,cameraPos并cameraVel允许您访问和修改相机的位置和速度.
makeLenses ''Camera
Run Code Online (Sandbox Code Playgroud)
最后,声明您的相机是HasPos和HasVel类的实例,并使用其方法的默认实现.
instance HasPos Camera where pos = cameraPos
instance HasVel Camera where vel = cameraVel
Run Code Online (Sandbox Code Playgroud)
现在我们已经准备好做一些真正的工作了.我们来定义一个示例相机
camera = Camera (Pos 0 0 0) (Vel 10 5 0)
Run Code Online (Sandbox Code Playgroud)
修改相机,返回具有更新位置的新相机的功能是
move :: (HasPos a, HasVel a) => TimeInterval -> a -> a
move dt obj = obj
& posX +~ dt * obj^.velX
& posY +~ dt * obj^.velY
& posZ +~ dt * obj^.velZ
Run Code Online (Sandbox Code Playgroud)
请注意,这是一个完全通用的函数,用于移动任何具有位置和速度的对象 - 它根本不是特定于该Camera类型的.它还具有看起来很像命令式代码的优势!
如果您现在将所有这些加载到GHCI中,您可以看到它的实际效果
>> camera
Camera {_cameraPos = (0.0,0.0,0.0), _cameraVel = (10.0,5.0,0.0)}
>> move 0.1 camera
Camera {_cameraPos = (1.0,0.5,0.0), _cameraVel = (10.0,5.0,0.0)}
Run Code Online (Sandbox Code Playgroud)
我会实现它类似于:
type Position = (Double, Double, Double)
type Velocity = (Double, Double, Double)
class PhysicalObject a where
pos :: a -> Position
velocity :: a -> Velocity
data Camera = Camera
{ camPos :: Position
, camVel :: Velocity
} deriving (Eq, Show)
instance PhysicalObject Camera where
pos = camPos
velocity = camVel
Run Code Online (Sandbox Code Playgroud)
然后,您可以针对您定义的每种类型执行类似操作PhysicalObject.