以正确的方式实现模型 - 视图 - 控制器

Sne*_*ess 21 oop model-view-controller cocoa objective-c

在Objective-C/Cocoa中为OS X开发游戏,我完成了原型,尽管它值得完成.这是一堆乱七八糟的代码,是我的第一款游戏,但一切正常.我一直在阅读将事情放在一起的最佳方式,而MVC似乎最有意义,但我有点困惑.

它从哪里开始?用控制器?这似乎对我来说最有意义,但它是如何开始的?在我的原型混乱中,我从init视图开始并从那里开始.我会为控制器做同样的事情,并把它需要的东西init?或者还有什么我可以用来做这个?如果从它开始init,我init该如何控制器?

我如何建立游戏世界?我目前使用两个阵列,一个用于世界(墙壁,地板,门,水,熔岩等),一个用于项目(我将为角色添加第三个).加载地图(.plist),然后创建对象并将其添加到它所属的数组中.阵列在哪里?在原型中,它们也是视图的一部分,所以我想你可以说我将两者(视图和控制器)结合在一起.是否会为每个地图创建一个Map对象?是否会有包含所有地图的Maps对象?

它们如何一起工作?玩家按下一个键,在游戏中移动角色.视图将处理输入,对吗?你会把它发送到控制器,它会检查地图/其他数组中的所有东西(墙壁,怪物等),然后返回结果吗?或者你会把它发送到播放器,它会转到控制器,它会做所有的检查,然后返回结果?

我以为我把它放在脑子里很漂亮,但我想的越多,我的想法越不固定,我就越困惑.如果您认为可以更有效地获得重点,请务必毫不犹豫地画出一些东西.

如果您花时间阅读所有这些内容,感谢您的耐心等待.从我收集到的,大多数编写代码的人都不使用任何设计.在阅读完这篇文章后,我可以看到为什么有些人会避免它,这让人感到困惑,人们似乎认为这不值得花时间.我个人认为这些优点完全超出了缺点(有没有?)而且只有在每次想要实现新功能时都不需要进行全部重写的情况下才能保持整齐有序.你不会在没有设计的情况下建造房屋,汽车或设备,为什么你会在没有设计的情况下编写复杂的程序?

我问这个问题是因为我想以正确的方式做到这一点,而不是通过黑客攻击和半途径来实现"胜利".

小智 17

您可能对我给ACCU '09 - " 采用Cocoa和Objective-C中的模型 - 视图 - 控制器 "的演示感兴趣.

它从哪里开始?用控制器?这似乎对我来说最有意义,但它是如何开始的?

创建一个新的Cocoa应用程序项目,您将看到模板已经提供了一个控制器类 - 它是应用程序委托类.现在看看MainMenu.xib.有一个app delegate实例,它连接到"File's Owner"对象的delegate插座.在这种情况下,NSApplication文件的所有者; 这是想要MainMenu解压缩的东西.所以这真的是应用程序的委托.

这意味着我们有一些控制器对象,可以与NSApplication实例通信,并且可以在XIB中拥有所有其他对象的出口.这使它成为设置应用程序初始状态的好地方 - 即成为应用程序的"入口点".实际上,切入点应该是-applicationDidFinishLaunching:方法.一旦应用程序完成了使您的应用程序进入稳定运行状态所需的所有内容,就会调用它 - 换句话说,Cocoa很高兴它完成了所需的工作,其他一切都取决于您.

-applicationDidFinishLaunching:是您想要创建或恢复初始模型的地方,它是应用程序状态的表示(您也可以将其视为代表用户的文档,如果该类比适合您的应用程序 - 基于文档的应用程序仅比默认值复杂一点)并告诉View如何向用户表示事物.在许多应用程序中,您不需要在应用程序启动时加载整个模型; 一开始它可能很慢并且使用的内存比你需要的多,其次第一个View可能不会显示关于模型的每一个位.因此,您只需加载您需要的位,以便向用户显示内容是什么; 您正在控制视图和模型之间的交互.

如果您需要在不同的视图中显示其他信息 - 例如,如果您的主视图是主视图并且您需要显示详细信息编辑器 - 那么当用户告诉您他们想要做什么时,您需要进行设置.他们通过执行某些操作来告诉您,您可以在应用程序委托中处理这些操作.然后创建一个新的Controller来支持新的View,并告诉它在哪里获得它需要的Model信息.您可以将其他View对象保存在单独的XIB中,因此只有在需要时才会加载它们.

我如何建立游戏世界?我目前使用两个阵列,一个用于世界(墙壁,地板,门,水,熔岩等),一个用于项目(我将为角色添加第三个).加载地图(.plist),然后创建对象并将其添加到它所属的数组中.阵列在哪里?在原型中,它们也是视图的一部分,所以我想你可以说我将两者(视图和控制器)结合在一起.是否会为每个地图创建一个Map对象?是否会有包含所有地图的Maps对象?

我们可以通过分析上面的陈述来确定我们正在建模的对象 - 你可能没有意识到,但你已经勾勒出一个规范:-).有一个包含墙壁,门等的世界,所以我们知道我们需要那些物体,它们应该属于一个世界物体.但我们也有物品和人物 - 他们如何与世界互动?一个地方可以包含水和角色吗?如果是这样,也许世界由地点组成,每个地点都可以有墙或门或其他任何东西,它也可以有物品和角色.请注意,如果我这样写,它似乎属于该位置,而不是该项的位置.我会说"垫子上有一只猫",而不是"猫下面有垫子".

因此,只需考虑您希望游戏世界代表什么,以及游戏中的事物之间的关系.这称为域建模,因为您在描述游戏世界中的事物而不是试图描述软件世界中的事物.如果它有帮助,写下描述游戏世界的几句话,并像我在上一段中所做的那样寻找动词和名词.

现在你的一些名词将成为软件中的对象,有些将成为其他对象的属性.动词将是动作(即方法).但无论哪种方式,如果你考虑首先要考虑的是什么,而不是直接跳到软件,就会更容易思考.

它们如何一起工作?玩家按下一个键,在游戏中移动角色.视图将处理输入,对吗?你会把它发送到控制器,它会检查地图/其他数组中的所有东西(墙壁,怪物等),然后返回结果吗?或者你会把它发送到播放器,它会转到控制器,它会做所有的检查,然后返回结果?

我喜欢遵循"告诉,不要问"政策,该政策说你命令某个对象做某事而不是让它给你提供做出决定的信息.这样,如果行为发生变化,您只需要修改被告知的对象.这对你的例子来说意味着View处理一个按键事件(因为它们被处理NSControl),它告诉Controller这个事件发生了.假设View接收到一个"左箭头"按键,Controller确定这意味着玩家应向左移动.我只是告诉玩家"向左移动",并让玩家理清当向左移动时发生的事情意味着碰到墙壁或怪物.

为了解释我为什么要这样做,想象你在游戏1.1中添加了玩家游泳的能力.现在玩家有一些ableToSwim属性,所以你需要改变玩家.如果你告诉玩家向左移动,那么你更新玩家就知道在水上移动意味着什么,这取决于他们是否可以游泳.如果控制器要求玩家向左移动并做出决定,那么控制器需要知道询问是否能够游泳,并且需要知道它在水附近意味着什么.正如游戏中可能与玩家互动的任何其他控制器对象一样,iPhone游戏中的控制器也是如此;-).


Lou*_*arg 7

看起来你的主要困惑是应用程序启动时如何构建事物.很多人都对此感到困惑,因为感觉就像是魔术正在发生,但让我看看我是否可以将其分解.

  1. 应用程序由用户启动
  2. 调用C main()函数
  3. main()调用NSApplicationMain()
  4. NSApplicationMain()加载MainMenu.nib(或info.plist中指定的另一个nib)
  5. Nib加载初始化nib中定义的所有对象(包括应用程序委托)
  6. Nib加载使得在nib中定义所有连接
  7. Nib加载在刚刚创建的所有对象上调用awakeFromNib
  8. -applicationWillFinishLaunching:在应用程序委托中调用.
  9. NSApplication(或Info.plist中指定的子类)已初始化
  10. -applicationDidFinishLaunching:在应用程序委托中调用.

请注意,应用程序委托在NSApplication(子)类之前初始化.这就是为什么应用程序(Will | Did)FinishLaunching:接受通知而不是NSApplication它是委托.

这样做的一般结果是您的视图是通过nibs为您创建的,并且您的控制器是作为nib启动的副作用而创建的,因为它们往往是nib中的根级对象,或者是nib的File's Owners.您的模型通常是在应用程序委托中创建的,或者是在首次访问它时懒惰地初始化的单例.