(译)cocos2d-x跨android&ios平台开发入门教程

免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播。同时,转载时不要移除本申明。如产生任何纠纷,均与本博客所有人、发表该翻译稿之人无任何关系。谢谢合作!

原文链接地址:http://www.raywenderlich.com/11283/cocos2d-x-for-ios-and-android-getting-started

教程截图:

    Cocos2D是一个非常棒而且非常容易使用的游戏框架,但是,由于它是采用objc写的,所以你只能使用它来开发ios和mac下面的游戏。

    假如你可以使用和cocos2d相似的api来开发android上面的游戏,那岂不是更爽?这样,你就可以毫不费劲地扩大你的游戏的市场份额(android的用户数量巨大啊!)

    好吧,确实存在这么一个游戏框架,她就是cocos2d-x!cocos2d-x采用c++把cocos2d的api重新移植了一遍,,除了一些语法细节上面的差异之外,你可以使用几乎一样的api来编写游戏。正是由于它是c++写的,所以可以跨多个平台,从ios到android、windows、再到linux、bada等!

    在这篇教程里面,你将学习到,如何使用cocos2d-x和c++来编写一个能够同时在ios和android设备上面跑的“Hello World”程序。

    而且,过两天,我会再出一篇教程,向大家展示一个具体的游戏例子---一个跨平台的太空射击游戏!

    好了,带上你的iPhone和Android设备,让我们开始吧!

Hello, Cocos2D-X for iOS!

    在继续之前,确保你先下载最新版本的cocos2d-x

    随便在你的硬盘的某个位置上面解压就可以了。注意,这个解压出来的文件夹的完整路径名,我们之后会用一个统一的别名 $COCOS2DX_HOME来指代。

    现在,你已经下载到源码了,让我们来安装项目模板吧!你可以先cd到$COCOS2DX_HOME这个目录(译者:指你刚刚解压缩代码的存放路径)下面去。在安装模板之前,先退出Xcode,然后打开一个终端,并输入下面的命令:

cd $COCOS2DX_HOME
sudo ./install-templates-xcode.sh -u
注意: 在上面所示的代码中,你不能直接输入$COCOS2DX_HOME,而要输入实际的路径。如果你直接输入了$COCOS2DX_HOME是不会有任何反应的。当然,你可以参考我写的《如何在macos下面配置集成ios和android游戏教程》,里面有讲到环境变量的配置。大概方法就是打开~/.bash_profile文件,然后用export来定义环境变量就ok了。

    你可能会被要求输入管理员密码(加了-u应该是不需要输入密码的,不加-u就需要输入),之后的过程就非常简单了。在模板安装好之后,启动xcode,然后创建一个新的项目。这时,你可以在项目模板对话框中看到有cocos2d-x的模板了。不用担心,cocos2d-x的模板与cocos2d的模板并不会有冲突,因此,你还是可以创建cocos2d的项目。

    让我们来试一下新模板吧!打开Xcode,然后创建一个新的工程,选择 iOS\cocos2d-x\cocos2dx模板,如下图所示:

    把工程例句为Cocos2DxFirstIosSample,然后保存。

   现在,编译并运行,你将会看到cocos2d-x的hello wolrd程序跑起来了,如下图所示:

    非常简单,不是吗?这时,你可以看看xcode里面的文件,特别是Classes\HelloWorldScene.H和Classes\HelloWorldScene.cpp。如果你也熟悉cocos2d的话,那么里面的代码看起来会觉得非常熟悉---几乎完全一样的cocos2d API,只是语言是c++而已!

配置cocos2d-x的eclipse开发环境

    现在我们已经完成了Xcode4的hello world程序了,是时候看看如何使用eclipse来配置android 的cocos2d-x开发环境了。

    这篇教程假设你有一个可以进行Android开发的标准开发环境。如果你还没有的话,可以参考这篇教程来配置,它里面提供了详细的过程,教你如何一步步配置好一个标准的eclipse android开发环境。

   但是,请等一下。一个标准的eclipse android开发环境是专为java开发设计的,而cocos2d-x却是基于c++来做开发的!

    不用担心,eclipse已经有10年的历史了,它有许多插件可以支持其它语言的开发,这当然就包括c/C++语言的插件啦。先让我们来安装这个插件吧。打开eclipse,然后执行以下步骤:

  • 从Eclipse IDE的主工具栏上面,选择Help/Install New Software。
  • 打开Work With复选框,并从中选择一个包含你的eclipse版本名称的项(如果是最新版的eclipse的话,就选择indigo)
  • 在插件树里面找到Programming Languages (只有当你复选中“Group items by category”时,你才可以看到插件树。)并打开它.
  • 选择CDT插件,然后安装下列组件。(但是,请注意,你如果直接就点击C/C++ Development Tools的话,你是得不到下图所示的样子的,你还需要在“Mobile Development”里面去查找相关的项)(译者:我没找了,直接就选择了C/C++ Development Tools就Next安装了)

 点击next来完成向导,然后等待组件下载并安装。现在,你可以使用eclipse来开发C/C++项目了!

Setup the Android NDK (Native Development Toolkit)

 本来,android开发只能采用java,而且现在市场上面大部分的app也是采用java写的。

    然而,现在你还可以通过Native Development Toolkit (NDK),使用C/C++来编写android程序。这种编程方式是由Google在2009年6月份引入的,它允许一些组件采用C/C++来编写,然后通过标准的Java Native Interface (JNI)来调用。

安装NDK的过程非常简单:

  • 下载最新版本的NDK。 here (注意选择 MacOSX 平台).
  • 解压tar.bz2 到任意目录.之后,我会使用$NDKROOT来指定这个解压缩后的目录。

    NDK给Android开发带来了全套的C/C++编译工具集,可以使用GCC4.4.3来编译来链接并且可以构建即时安装的APK包。

   有了这套编译工具集,我们就可以在eclipse里面集成一些外部的C/C++库(比如cocos2d-x)。这些库被编译成动态库,然后通过JNI(Java Native Interface)与Java Android体系程序进行交互。

这些编译工具集可以采用下面两种方式使用 :

  • 独立模式: 直接在你的makefile中使用 arm-linux-androideabi-g++. 这种方式会增加你的项目的复杂性和可维护性。我推荐你不要使用这种方式。
  • 集成模式: 使用$NDKROOT/ndk-build shell 工具,它是一种高度可定制的makefile,专门为NDK库所设计的。这也是我们这篇教程所采用的方式。

 

解释JNI和NDK可能会花费大量时间,而且也超出了本教程的讨论范围。现在网络上有许多关于JNI的资源。这里还推荐一本书,它也讨论了JNI这个主题。

    如果你需要更多关于NDK的信息,这里推荐一本非常好的书,它里面涵盖了使用C/C++进行NDK开发,书名是: Android Native Development Kit Beginner’s Guide。这本书覆盖了NDK编程的方方面面,可谓是“从入门到精通”,而且NDK本身也带有非常详细的文档,在$NDKROOT/docs路径下面。

Hello, Cocos2D-X for Android!

    现在,让我们在Andriod平台上面来开发一个“Hello,World”程序吧,就像我们之前在ios平台上的程序一样。

    我们需要通过命令行来完成工作,因为目前还没有在Eclipse IDE里面集成cocos2d-x的模板。

    在$COCOS2DX_HOME目录下面包含一个shell脚本,叫做create-android-project.sh,我们可能通过它来创建android项目。但是,在运行脚本之前,我们需要在脚本文件的顶部做一些修改: 

# set environment paramters
NDK_ROOT_LOCAL="/home/laschweinski/android/android-ndk-r5"
ANDROID_SDK_ROOT_LOCAL="/home/laschweinski/android/android-sdk-linux_86"

 

修改上面这些行,把NDK_ROOT_LOCAL指向你安装Android NDK ($NDKROOT)的位置,同时把ANDROID_SDK_ROOT_LOCAL指向你安装Android SDK位置。(译者:这里其实可以不用修改,读者可以打开这个sh脚本去看看为什么。如果之前看过我的配置教程,里面有配置一个全局的NDK_ROOT和ANDROID_SDK_ROOT。这个脚本在判断有全局的环境变量存在的时候,会直接替代NDK_ROOT_LOCAL和ANDROID_SDK_ROOT_LOCAL。)

    现在,可以运行create-android-project.sh脚本了,然后你会接收到一系列的提示输入。我们将一个个向您解释一下:

  1. 第一个提示要求你“Input package path”。这个包名会给后面的java代码使用。你可以使用你的域名反过来写,类似写ios的bundle ID。比如com.yourdomain.samplecocos2dxandroid ,记住实际输入的时候要替换掉“com.yourdomain”。
  2. 接下来,你会得到一系列可用的Android API和它们的id号。这个具体取决于你的机器上面安装的Andriod API的情况。
  3. 最后,你需要提供项目的名称,在这里取名为samplecocos2dxandroid

命令行的输出大致如下:

 

bash-$ ./create-android-project.sh
Input package path. For example: org.cocos2dx.example
org.jymc.samplecocos2dxandroid
. . .
Available Android targets:
----------
. . .
----------
id: 9 or "Google Inc.:Google APIs:15"
     Name: Google APIs
     Type: Add-On
     Vendor: Google Inc.
     Revision: 1
     Description: Android + Google APIs
     Based on Android 4.0.3 (API level 15)
. . .
input target id:
9
input your project name:
samplecocos2dxandroid
Created project directory:
/Users/jymen/development/cocos2dx/samplecocos2dxandroid . . . Added file /Users/jymen/development/cocos2dx/samplecocos2dxandroid/AndroidManifest.xml Added file /Users/jymen/development/cocos2dx/samplecocos2dxandroid/build.xml Added file /Users/jymen/development/cocos2dx/samplecocos2dxandroid/proguard.cfg bash-$

 注意“Created project directory:”这一行是脚本文件最后输出来的,这个输出的路径也就是你的Andriod项目被创建好的路径。我这里指的是/Users/jymen/development/cocos2dx/samplecocos2dxandroid,如上图所示:

Note: Do not try to move the project from that location to a different location. At least one of the scripts we’ll work with in the next section will not work if you do.

建工程

    这里有两个步骤来构建项目----首先通过命令行脚本编译c++代码,然后通过ecipse来编译java代码。

    为了编译c++代码,我们需要切换到$PROJECT_HOME/android文件夹下面去,然后在终端里面输入下列命令:

./build_native.sh

你应该会看到下面类似的输出:

Gdbserver      : [arm-linux-androideabi-4.4.3] libs/armeabi/gdbserver
Gdbsetup       : libs/armeabi/gdb.setup
Compile++ thumb  : cocos2d <= CCConfiguration.cpp
Compile++ thumb  : cocos2d <= CCDrawingPrimitives.cpp
:
:
:
Compile++ thumb  : cocos2d <= CCTileMapAtlas.cpp
Compile++ thumb  : cocos2d <= CCTouchDispatcher.cpp
Compile++ thumb  : cocos2d <= CCTouchHandler.cpp
Prebuilt       : libstlport_static.a <= /sources/cxx-stl/stlport/libs/armeabi/
SharedLibrary  : libcocos2d.so
Install        : libcocos2d.so => libs/armeabi/libcocos2d.so
Compile++ thumb  : cocosdenshion <= SimpleAudioEngine.cpp
Compile++ thumb  : cocosdenshion <= SimpleAudioEngineJni.cpp
SharedLibrary  : libcocosdenshion.so
Install        : libcocosdenshion.so => libs/armeabi/libcocosdenshion.so
Compile++ thumb  : game_logic <= AppDelegate.cpp
Compile++ thumb  : game_logic <= HelloWorldScene.cpp
SharedLibrary  : libgame_logic.so
Install        : libgame_logic.so => libs/armeabi/libgame_logic.so
Compile++ thumb  : game <= main.cpp
SharedLibrary  : libgame.so
Install        : libgame.so => libs/armeabi/libgame.so

 这里就是在编译cocos2d-x库和你的项目里面的c++文件。

    为了编译java代码,我们需要创建一个eclipse工程---这可比命令行要方便多了。:)

    打开Eclipse,然后他吃软饭File\New\Other,选择Android\Android Project,然后点击Next,在Project Name里面输入samplecocos2dxandroid ,然后选择 Create project from existing source,然后浏览到$PROJECT_HOME/android文件夹,如下图所示:

 

  点击Next,选择一个 Android version to target(这里选择的是3.2,你可以选择任何一个)

点击Finish,好了,现在你有一个工程了!

在左边的树上右击项目,选择Run As\Android Application,然后AVD (Android Virtual Device)就会启动,然后就会跑出来Hello, Cocos2D-X项目了!

 

 注意:如果程序没有启动的话,你需要按照提示去创建一个AVD来测试。更多的细节,请参考Getting Started with Android Development 这篇教程。

    恭喜你,你现在有一个“HelloWorld”项目可以同时跑在iosandroid上面了!

 

Eclipse里面定一个java/c++的工程

 

    通过命令脚本来编译c++代码,完了之后用eclipse来编译java代码,这种来回切换的做法非常笨重。如果全部可以用eclipse来完成的话,那么生活会更简单一些。

    幸运的是,我们可以告诉eclipse,我们的android项目是一个集成java/c++的跨语言项目!

   为此,我们在左边的项目树上选择samplecocos2dxandroid,然后从菜单里面选择File\New\Other,然后从对话框里面选择C/C++\Convert to a C/C++ Project,如下所示:

 

点击Next。在下一个屏幕里面,选中你的项目,然后选择c++单选按钮,然后为Project type指定Makefile project\Other Toolchain,如下所示:

  点击完成。这时eclipse会询问你是否打开C/C++视图,当对话框出现的时候,选择NO。

   接下来的步骤就是修改项目设置,指定如何运行我们的make命令。在左边的项目树上,右击samplecocos2dxandroid工程,选择Properties,然后选择C/C++ build

取消“Use default build command”复选框,然后在build文本框中输入下面的命令:

bash ${workspace_loc:/samplecocos2dxandroid}/build_native.sh NDK_DEBUG=1 V=1

点击Apply,然后点OK。

   回到ecipse,从主菜单中选择Project\Build All,这时你可以在ecipse的控制台输出中看到c++ make在运行。

我们仍然有一些警告需要解决。想知道我是什么意思吗,你打开jni/helloworld/main.cpp,这里你会看到一系列的警告,如下所示:

这些警告之所以会出现,是因为我们没有配置正确的c++包含路径。为了解决它,我们右击samplecocos2dxandroid工程,然后选择Properties\C/C++ General\Path and Symbols\GNU C++。

   选择Add…按钮来选择下面的目录,之后点击Apply和OK。

$(NDKROOT)/platforms/android-9/arch-arm/usr/include
$(COCOS2DX_HOME)/cocos2dx/include

注意: 不用忘了使用实际的路径来替换掉$(NDKROOT) 和$(COCOS2DX_HOME)。

 

 点击Apply,这时会提示你是否重建索引。直接点击yes继续就可以了。

   现在,你再看看main.cpp,你会看到大部分警告已经消失了。

 但是,AppDelegate.h文件还是找不到。这是因为AppDelegate.h是在$PROJECT_HOME\Classes文件夹下面。这个文件夹对我们来说非常重要,因为它包含了我们工程里面的可移植的c++类,比如HelloWorldScene.cpp等。

    当我们创建eclipse项目的时候,我们必须选择$PROJECT_HOME\android文件夹,因为eclipse需要一个AndroidManifest.xml文件。但是,这样的话,我们的工程就不包括关键的“Classes”文件夹了,这也是为什么我们会得到这么多警告的原因。

   让我们来修正它吧。右击samplecocos2dxandroid项目,选择Properties\C/C++ General\Paths and Symbols\Source location。点击Link Folder,然后复选中Link to a folder in the file system,接着浏览到$PROJECT_HOME文件夹,并指向Classes目录,最后点Apply和OK。

你现在可以在文件树里面看到Classes目录了,而且在main.cpp里面的#include “AppDelegate.h”警告也应该消失了。

   Eclipse工程里面还会有许多警告,但是,这是因为eclipse对于解析c++头文件的能力并不强大。为了消除这些警告,我们又需要设置一下项目设置。(右键点工程,然后选择Properties),把Code Analysis部分的warnings关闭,如下图所示:

 然后点击Apply和OK,这时,你就得到一个工程可以在eclipse里面进行编辑了。

 

What About the Java Code?

    这时你在eclipse里面再找找看,你会在src和gen目录下面找到一些java代码,你肯定很好奇这些代码是干吗用的。

   Andriod开发采用的主要语言是java,这个项目模板已经为我们创建了一些java框架代码,用来加载c++编写好的动态库。

   一般情况下,你是不需要修改这些java代码的,但是,还是让我们来看看src\com\xxx\samplecosos2dxandroid\samplecocos2dandroid.java这个类。

   这个类包含了主Android Activity类,当应用程序启动的时候会加载。它首先通过NDK来请求编译好的c++动态库。(如果采用的是最新版的coocs2dx的话,下面只会加载一个game库)

 

 由于类是从Cocos2dxActivity所派生,在后台cocos2d-x会把控制权移交给c++的AppDelegate 的initInstance方法来处理,最后applicationDidFinishLAunching方法也就被执行起来了。

    因此,再重申一遍,java代码只是一些包装代码,大部分情况下我们都不要去修改它,因为我们的游戏逻辑是由c++写的。

So to repeat, the Java code just contains wrapper code that you will rarely need to change, since all the game’s logic will be inside the C++ code.

连接andriodios

    现在,我们有两个cocos2d-x的项目了,一个是ios下面的,一个是andriod下面的。我们的目标是让两个工程共享同样的目录,这样就可以使用同样的c++文件了。

    我们两个项目里面,都有一个文件夹叫做“Classes”,它包含了我们的可移植的游戏代码(如下图:android的在左边,ios的在右边)

  如果你仔细看看eclipse和xcode里面的Classes目录,你会发现这些c++文件完全相等。因此,我们需要做的就是让两个项目指向同一个文件夹就ok了!

 

    为了实现这个目标,在xcode里面打开Cocos2DxFirstIosSample项目。选择Classes分组,然后点击Delete,选择“Move to Trash”;

    接下来,右键Project Navigator,选择“New Group”,然后重命名为Classes。点键Classes,点击Add Files。然后选择$PROJECT_HOME\Classes目录,同时确保“Copy items into destination group’s folder”没有被选中!

 然后clean并rebuild xcode项目,并运行一下,确保一切ok。

   恭喜,现在基础配置全部弄好了!你可以试着在eclipse里面修改,xcode这边运行,或者xcode里面修改,eclipse里面运行了。

什么候使用 Xcode, 什么候使用Eclipse?

    现在,你有两个项目了,那么问题马上就出来了,我们到底什么时候该使用哪一个呢?

    使用cocos2d-x通常的开发策略是,首先在ios上面通过xcode来测试,确认没问题后,再通过eclipse来测试。只需要在心里记住下面几点就行:

  1. 经常测试.完一个小功能之后,就应该andriod上面测试看行不行。这样,就不至于遇到问题找不到了。 
  2. 在多个设备上面测试: 现在市场上面有大量的android设备,而且它们的差别很细微,所以,你要尽可能多地测试多种不同型号的设备。

当然,你也可以先在andriod上面测试,然后再在ios上面测试。但是这样肯定会降低你的生产率。因为xcode比eclipse反应速度更快,而ios模拟器比android的模拟器的启动速度也要快一些。

调试技巧

在ios上面调试cocos2d-x和cocos2d的方法差不多,这里也没什么多讲的。

然而,对于Android,这里有几点需要注意下。

   当使用NDK来调试的时候,一个“服务端的”gdb和gdbcontext被ndk-build shell脚本塞到APk里面去了。这个远程的gdb调试器可以与任何gdb兼容的客户端进行通信。

   Eclipse里面最好的gdb agent就是 NVidia debug manager plugin,但是安装这个插件不在本教程的讨论范围之内。

注意: 如果你想安装NVidia Debug Manager plugin,你首先到这个页面下载完整的Tegra Android开发包,即便你可能现在并不需要它。然后,安装Tegra Developer,它会提示你安装哪些项,然后你可以选择NVidia Debug Manager plugin这个插件了。当你安装完之后,它并没有包插件安装到eclipse里面去,而是把zip文件放在你的硬盘的某个位置,你可以参考这个pdf文档来进行安装。

    我发现在一些物理设备(比如Samsumg Galaxy S),remote debugger老是启动不了,但是使用AVD就可以正常启动。

   为了启动一个debug会话,你可以选择你的项目,然后右击Debug As Android NDK application,如下图所示: 

 

请注意,只有你安装了NVidia debug manager plugin,你才会在右击的时候看到Android NDK Application这个选项。

   你可以在一行代码上面双击来设置一个断点,如下所示:

 

 当断点到达的时候,它将会停留在断点所在行:

 悲催的是,使用gdb来调试android有时候并不管用,所以,你可能需要添加一些额外的tracing系统。想知道更多的信息,请联系我,我将与大家分享这些tips。

何去何从?

    恭喜你,你现在可以使用cocos2d-x来开发iphone和android下面的游戏了。

   请耐心等待我的第二篇教程吧,它将带你一起开发一个跨平台的太空射击游戏,当然是使用cocos2d-x啦!

   如果你遇到什么问题,或者有什么意见或建议,欢迎留言!

   更多精彩教程,请关注泰然首页:)

 

posted @ 2012-04-28 10:53 子龙山人 阅读(2263) 评论(5) 编辑

cocos2d里面如何实现MVC(完)

    今天我们讨论的主题是Model-View-Controller (MVC)设计模式,以及如何在cocos2d里面实现它。来自波兰的Bartek Wilczyński写了一系列的文章来介绍这个模式,同时说明了为什么要使用mvc,以及如何在cocos2d里面使用mvc。

    这个波兰人写的文章已经被我全部翻译过来了,请点击传送门查看。

    当我在读他写的这些文章的时候,我记得Jeremy Flores在github上面有一个cocos2d里面实现mvc的版本库。他把它取名为Cocos2D-MNC,全名是Model-Node-Controller。并且代码是开源的,MIT许可。

    这个MVC模式和游戏实体组件系统差不多,我在这篇文章里面就有介绍过了。对于这两个系统来说,它的思想都是统一的,那就是不要继承CCSprite并把游戏logic全部塞到sprite里面去。CCSprite应该只负责渲染显示。而且有时候,你可能需要创建很多sprite,我们最好是创建一个CCNode类,然后里面聚合许多sprites。这样CCNode成为了Controller,控制view。当view(比如sprite,effect,gL drawings等等)在屏幕上面移动的时候,controller结点会轮询所有它包含的结点来查询一些游戏相关的状态信息,并且做一些游戏逻辑,然后反过来再更新view。

    对于小游戏来说,mvc模式确实可以运行地很好。它比起直接继承CCSprite,并把一大堆处理逻辑放到CCSprite里面要强多了。如果你发现,你还是不停地继承ccsprite,然后把一大堆处理逻辑塞到一个ccsprite的子类里面,那么你就应该考虑一下mvc设计模式了。

     当我们在cocos2d论坛里面提到“是否继承CCSprite还是使用一些model类来构建你的游戏对象结构?”这样的问题的时候,我还是要再强调一点,多用组合,少用继承!如果一味地使用继承,那么当游戏世界里面的对象种类变多,功能变复杂以后,会导致整个继承树“头重脚轻”,严重破坏了良好的面向对象设计原则---我们设计的类层次结构应该是扁平结构的,而不是一个头很大的树。

    那么,我们需要使用怎样的架构来处理游戏里面的对象呢?答案就是使用组合。现在已经有一些非常好的引擎,比如TorqueXPushButton Engine、Unity3D等,它们都是基于组合的实体组件系统。

    你可以从PushButton的文档里面得到有关实体组件系统的介绍。同时,可以读一读《Components in TorqueX and what the differences are to XNA Game Components》这篇文章来加深对实体组件系统的理解。

     你还可以从维基百科上获得更多的信息。实际上,objc语言本身就是被设计为一种可重用的软件组件。

    Scott Bilas在2002的GDC大会上提出了一种基于数据驱动的游戏对象系统,同时Dungeon Siege使用了这个新理念,它指出了为什么继承对于游戏开发者来说非常不好,还说明了基于对象组合的组件系统的优点。事实上,在2002年,我开始与SpellForce一起工作的时候,我们已经有一个组件系统了,我们把它叫做Aspects、Abilities和Spells。它可以帮助我们把所有的游戏数据都存储到数据库里面,程序员只需要写一些泛型代码来统一处理这些数据就行了。

    在2009年的GDC大会上面,Radical Entertainment’s Marcin Chady也做了一个类似的ppt,大家可以点此查看

    Mick West还写了一篇文章,《重构游戏实体为游戏组件》,在这篇文章里面,它很好地描述了,为什么要更改以前的继承模型,转而投向组件系统的怀抱。

    还有一些更高级的读物,比如一些paper《Dynamic Game Object Component System for Mutable Behavior Characters》 ,它基于finite state machines讨论了组件系统,而且引入了基于规则的NPC行为系统。

   在Game Architect 博客里面把它称之为Anatomy of Despair,并且指出了基于继承的类设计的一些缺点,同时展示了使用组合如何来解决这些问题。

 

  后记:《如何在cocos2d里面实现mvc》这一系统的文章到此就全部结束了,非常感谢大家的耐心阅读。mvc在cocoa开发中被广泛使用,几乎没有哪一个ios开发者不知道mvc。但是,mvc不是银弹,没有银弹!我个人觉得基于组件的实体系统和fsm更适合现在游戏架构。由于本人水平和经验有限,故只能翻译这么多。这篇博文提到了其它许多没有翻译过的文章,建议大家都读一读。我也不打算翻译了。有兴趣的同学可以翻译出来,为开发者社区贡献一点力量。

 

   全剧终!

 

参考文献:

Game Programming Patterns / Behaving Patterns / Component

How to implement MVC pattern in cocos2d game | XPerienced Blog

T=Machine » Entity System 1: Java/Android

T=Machine » Entity Systems are the future of MMOG development – Part 1

How to implement MVC pattern in cocos2d game–part 2 | XPerienced Blog

Games from Within| Indie iPhone game development

Game Programming Patterns

Game development |Mobile development |PODD

Cowboy Programming » Evolve Your Hierarchy

Object-Oriented Game Design| GBGames - Thoughts on Indie Game Development

Why Use MVC for Games? A Q&A Session | DeadPanic

C Coroutines for Game Entity State Management | Will's Blog

10 Reasons the Age of Finite State Machines is Over — AiGameDev.com

Subclass CCSprite vs Model Class. Best Practice? « cocos2d for iPhone

Animating a CCSprite using MVC design « cocos2d for iPhone

Animating a CCSprite using MVC design « cocos2d for iPhone

Subclassing CCNode and using CCSpriteBatchNode properly « cocos2d for iPhone

Component based entity systems « cocos2d for iPhone

How do you organize your code? « cocos2d for iPhone

Game Object Structure: Inheritance vs. Aggregation

Game Architecture Best Patterns and Practices - App Hub Forums

Finite State Machine Part 1

c++ - What are some programming design patterns that are useful in game development? - Game Development - Stack Exchange

How to write solid Pure Aggregation (composition) Game Objects in Java? - Stack Overflow

Prefer Composition over Inheritance | Learn & Master Cocos2D Game Development

Components, Draw Calls and performance…. « Big Bad Robots Indie Game Studio

To Components with Cocos2D with love… « Big Bad Robots Indie Game Studio

PushButton Engine

PBEDemos.asArtemis 

Entity System FrameworkT=Machine » Entity System 1: Objective-C

architecture - Component based game engine design - Stack Overflow

Component Based Entity System Design Part 1 | Purple Pwny Games

Component-based game object systems in practice - Game Development - Stack Exchange

What is (functional) reactive programming? - Stack Overflow

Why I switched from component-based game engine architecture to functional reactive programming – Lambdor Devblog by Gerold Meisinger

Quickstarting game development in Haskell and Ubuntu – Lambdor Devblog by Gerold Meisinger

Game Coding Complete - Component System Example Code (from GPG6)

objective c - Obj-C component-based game architecture and message forwarding - Stack Overflow

mikeash.com: Friday Q&A 2009-03-27: Objective-C Message Forwarding

Articles - Component Based Objects

Unseen-Academy - Component System

Componentbased entity systems - Game Development Lab Wiki

divotkey/cogaen3-java - GitHub

Open source component-based game or engines? - Game Development - Stack Exchange

Component Based Entity System Design Part 2 | Purple Pwny Games

一个基于组件的动态对象系统 - 游戏创意和设计 - TechWeb-游戏社区

游戏对象的实现 (补)_thunder54007-ChinaUnix博客

游戏对象的实现 - 组件工厂 - C++博客

thelinuxlich/artemis_CSharp - GitHub

thelinuxlich/starwarrior_CSharp - GitHub

Recommended Eclipse plugins to generate UML from Java code - Stack Overflow

Game Object Structure: Inheritance vs. Aggregation

Linkvent Calendar, Day 3: MVC with Cocos2D | Learn & Master Cocos2D Game Development

Artemis Entity Framework Ported to C# | Ploobs

Downloads - cistron - A component-based programming framework targeted at game development. - Google Project Hosting

C++ Port of Artemis Entity Component System Framework (In Progress) - GameDev.net

posted @ 2012-03-18 21:29 子龙山人 阅读(2370) 评论(10) 编辑

cocos2d里面如何实现MVC(六)

    本文将会比较简短。如果你对于前面几篇介绍cocos2d里面如何实现mvc有什么不清楚的地方,请跟我讲,这样我就可以补充地更详细一点。我(原作者)最近接了一些非常大的外包项目,所以,用来写博客的时间不是很多。在此,我简单地总结一下前面写的这些文章:

  1. 我们实现了Model,它可以帮助我们封装游戏的逻辑,这样可以使我们的关注点集中于游戏逻辑,而不是其它的(比如渲染)
  2. 我们实现了Controller来处理用户交互,同时相应地更新model。
  3. 我们实现了View,而它的职责仅仅是负责显示model的状态。
它们三者之间的交互关系如下:
  1. Controller负责初始化Model和View
  2. View负责显示Model 
  3. View负责接收用户touch事件,然后传递给controller来处理。
  4. Controller实现view的代理,同时可以操作Model。
  5. model则执行一些游戏逻辑处理并通知view它的状态改变。
  6. View根据model的当前状态来更新它里面的所有对象,比如精灵。
留几个开放性的问题:
  1. 如果你想使用NSUodoManager来实现“撤销”功能,你会把该功能放在哪里呢?Model?Controller?View?
  2. 你会怎么保存当前场景的状态信息呢?
  3. 或者你还可以补充其它问题?

 

后记:本文已同步更新到cocos2d mvc这个系列里面去了。

    如果你觉得本文章对你有所帮助,请您点一下旁边的“推荐”按钮,这样可以让更多的人看到,同时也会给我写作的动力,谢谢大家。

posted @ 2012-03-18 20:44 子龙山人 阅读(840) 评论(1) 编辑

cocos2d里面如何实现MVC(五)

    本文基于前面两篇文章,如果您还没有看过,建议先阅读下面两篇文章:

更新Model

    当用户从工具箱中选一个小工具,然后把它放置到game board上面去时,我们需要编码响应这些事件。在上一篇文章中,我们已经实现了GameBoardViewDelegate的touchedAtRow方法。我们还需要给这个协议再添加一个接口方法。如下所示:

@protocol GameBoardViewDelegate

- (void)gameBoard:(GameBoard *)gameBoard touchedAtRow:(int)row column:(int)column;
- (void)gameBoard:(GameBoard *)gameBoard toolboxItemTouchedAtIndex:(int)index;

@end

    我们需要修改touch事件处理器,这样就可以判断我们到底是触摸了工具箱还是game board。

- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint point = [self convertTouchToNodeSpace:touch];

// touched on a game board
if (CGRectContainsPoint(gameBoardRectangle, point)) {
int row, column;
// calculate row and column based on a touch coordinate
// ...
// call controller
[self.delegate gameBoard:self.gameBoard touchedAtRow:row column:column];
}
// touched on a toolbox
else if (CGRectContainsPoint(toolboxRectangle, point)) {
int index;
// calculate toolbox item index based on a touch coordinate
[self.delegate gameBoard:self.gameBoard toolboxItemTouchedAtIndex:index];
}
}

    在controller类里面处理touch事件是非常简单的,我们只需要持有一个model的引用,然后基于touch事件来调用model的方法就行了。我们的接口看起来和下面差不多,只是省略掉了一些实现细节:

@interface GameBoard : NSObject {
// ...
}

// ...
- (void)putGamePiece:(GamePiece *)gamePiece row:(int)row column:(int)column;
- (GamePiece *)getGamePieceFromToolboxItemAtIndex:(int)index;
@end

    然后,我们在GameBoardController里面完全实现GameBoardViewDelegate的两个方法。

- (void)gameBoard:(GameBoard *)aGameBoard toolboxItemTouchedAtIndex:(int)index {
// keep the toolbox selection state in the Model
gameBoard.selectedToolboxItemIndex = index;
}

- (void)gameBoard:(GameBoard *)aGameBoard touchedAtRow:(int)row column:(int)column {
// if the toolbox item is selected move item from toolbox to game board
if (gameBoard.selectedToolboxItemIndex != -1) {
GamePiece *gamePiece = [gameBoard getGamePieceFromToolboxItemAtIndex:gameBoard.selectedToolboxItemIndex];
[gameBoard putGamePiece:gamePiece row:row column:column];
}
}

    到目前为止,我们实现了,用户可以点击工具箱中的小工具,然后把它们放置到game board中的一个小方块上面,同时model类在中间起了桥梁作用。

通知view关于model的改变

    为了在view里面反映出model的状态更改,我们可以在model有变化的时候给view发送通知消息,然后view就可以根据不同的消息来作出不同的响应了。和我们在实现view通过controller一样,这里我们也定义了一个GameBoardDelegate,用来通知view model的变化。

@protocol GameBoardDelegate;
@interface GameBoard : NSObject
// ...
@property (nonatomic, assign)
id&lt;GameBoardDelegate&gt; delegate;
// ...
@end

@protocol GameBoardDelegate
- (void)gameBoard:(GameBoard *)gameBoard didPutGamePiece:(GamePiece *)gamePiece row:(int)row column:(int)column;
@end

@implementation GameBoard

- (void)putGamePiece:(GamePiece *)gamePiece row:(int)row column:(int)column {
// ...
// store game piece
// notify that the game piece was put on a gameboard
[delegate gameBoard:self didPutGamePiece:gamePiece row:row column:column];
}

@end

    在GameBoardView里面实现GameBoardDelegate的时候,当我们需要在game board上面放置一个小工具的时候,我们定义了一个CCSprite。

@interface GameBoardView : CCLayer
// ...
@end

@implementation GameBoardView

- (id)initWithGameBoard:(GameBoard *)aGameBoard delegate:(id)aDelegate {
if ((self = [super init])) {
// retain gameboard
self.gameBoard = aGameBoard;
self.gameBoard.delegate = self;

// assign delegate
self.delegate = aDelegate;
}
}

- (void)gameBoard:(GameBoard *)gameBoard didPutGamePiece:(GamePiece *)gamePiece row:(int)row column:(int)column {
// create CCSprite and put it on a game board at corresponding position
CCSprite *gamePieceSprite = [CCSprite spriteWithFile:fileName];
// ...
[self addChild:gamePieceSprite];
}

@end

总结

    现在框架中所有的部分都联系起来了,model、view和controller三者组成了著名的MVC模式

  • View接收touch事件,然后把事件传递给controller,
  • Controller 响应用户的touch事件,然后更新model
  • model 更新它自身的状态, 处理游戏逻辑,然后告诉view它改变了哪些东西。
  • View则基于Model当前的状态来更新自己的显示 

 

后记:本文已同步更新到cocos2d mvc这个系列里面去了。

    如果你觉得本文章对你有所帮助,请您点一下旁边的“推荐”按钮,这样可以让更多的人看到,同时也会给我写作的动力,谢谢大家。






posted @ 2012-03-18 20:30 子龙山人 阅读(1043) 评论(2) 编辑

cocos2d里面如何实现MVC(四)

    在上一篇文章中,我们使用cocos2d基于mvc做了一个简单了游戏架子,这个架子还非常简单,还有许多东西有待实现。

介绍模型

    在上一篇博文中,我们介绍了view和controller。为了实现mvc模式,我们还需要添加一个model类来维护游戏的状态。我们的实现应该要包含下列这些类:

  1. GameBoardView - 也就是View,
  2. GameBoardController - 也就是Controller.
  3. GameBoard – 也就是Model.

Model 实现

GameBoard 实现

    我们在第一部分所描述的需求是这样子的:

    。。。一个game board是通过n行n列组成的,它会随着游戏难度有所变化。

    因此,我们按照下面的编码方式来实现之:

@interface GameBoard : NSObject {
NSInteger numberOfRows;
NSInteger numberOfColumns;
}

- (id)initWithRows:(NSInteger)aNumberOfRows columns:(NSInteger)aNumberOfColumns;

@property (nonatomic) NSInteger numberOfRows;
@property (nonatomic) NSInteger numberOfColumns;

@end

    请注意,model是从NSObject继承过来的---因为model只需要关注game board的状态就行了(当然,还有相应的更新状态的方法)---我们不应该把其它东西也放进来,比如继承到CCNode就不行,我们并不需要CCNode的东西,所以,为了纯粹性,我们这里继承到game board。

GameBoardView 的实现

    我们现在需要修改View,同时它包含一个model的引用,我们可以通过initWithGameBoard方法来初始化这个成员变量:

@interface GameBoardView : CCNode {
GameBoard *gameBoard;
}

@property (nonatomic, retain) GameBoard *gameBoard;

- (id)initWithGameBoard:(GameBoard *)aGameBoard;

@end

   具体GameBoardView的实现细节如下:(为了演示方便,我们忽略了实际渲染每一个小方块的代码)

- (id)initWithGameBoard:(GameBoard *)aGameBoard {
if ((self = [super init])) {
// retain gameboard
self.gameBoard = aGameBoard;

// render gameboard background
CCSprite *gameboardSprite = [CCSprite spriteWithFile:@"gameboard.png"];
gameboardSprite.anchorPoint = CGPointMake(0, 0);

[self addChild:gameboardSprite];

// render spaces
for (int i = 0; i &lt; gameBoard.numberOfRows; i++) {
for (int j = 0; j &lt; gameBoard.numberOfColumns; j++) {
// position and render game board spaces
}
}
}

return self;
}

GameBoardController

    最后,我们要更新GameBoardController的init方法,因为view需要把GameBoard对象通过init方法注入进去,所以,我们在controller的init方法里面,就应该定义好model对象,然后传递给view。

- (id)init {
if ((self = [super init])) {
// initialize model
gameBoard = [[GameBoard alloc] initWithRows:7 columns:9];

// initialize view
view = [[GameBoardView alloc] initWithGameBoard:gameBoard];

[self addChild:view];
}

return self;
}

处理touch事件

GameBoardView updates

    为了能够处理touch事件,我们需要再稍微修改一下View。我们让它继承至CCLayer,而不是CCNode。因为CCLayer内置了处理touch事件的方法:

@interface GameBoardView : CCLayer {
...
}

    而view本身是不应该处理用户的交互(touch事件)的,所以,我们需要定义一个代理(GameBoardViewDelegate)。(译者:为什么这里要定义代理呢?所谓代理代理,当然就是你不想做的事,找别人去做,这就是代理。所以,当你写代码的时候,你想保持类的简单性、重用性,你就可以把事件尽量都交给其它类去做,自己只管做好自己的事。也就是SRP,单一职责原则。如果一个类关注的点过多,做的事情太多。这些事情不管是你直接做的,还是调用别的对象去完成的。这都不行,自己做这些事,那就会使类的功能复杂化,维护不方便。而过多地调用其它对象来完成一些事情,表面上看起来好像不错,实际上是过度耦合了。我们编写类的原则应该是追求高内聚,低耦合的。可能你会说,用代理不也是交给别人做吗?没错,问的好。但是,代理是接口,我们是针对接口编程,所以它的重用性会非常好。因此,下次当你想写可扩展和可重用的代码的时候,不妨先想想代理这个东西吧。objc里面delegate是用protocol实现的,而java和c++则是用接口实现的,具体他们之间怎么转换的,比较一下应该就可以了。)

@protocol GameBoardViewDelegate
- (void)gameBoard:(GameBoard *)gameBoard touchedAtRow:(int)row column:(int)column;
@end

    我们还需要再修改一下GameBoardView的init方法,通过传送一个delegate进来处理touch事件。

- (id)initWithGameBoard:(GameBoard *)aGameBoard delegate:(id)aDelegate;

下面是touch事件的具体实现:

- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint point = [self convertTouchToNodeSpace:touch];

// calculate row and column touched by the user and call a delegate method
// ...
[self.delegate gameBoard:self.gameBoard touchedAtRow:row column:column];
}

GameBoardController 更新

    GameBoardController将会负责处理用户touch事件,所以,我们需要让GameBoardController实现GameBoardViewDelegate接口:

@interface GameBoardController : CCNode<GameBoardViewDelegate>
- (void)gameBoard:(GameBoard *)gameBoard touchedAtRow:(int)row column:(int)column {
// do the game logic here and update view accordingly
}

   还有最后一步,那就是修改view的init方法,把controller传递进去。

// initialize view
view = [[GameBoardView alloc] initWithGameBoard:gameBoard delegate:self];

总结

    在这篇文章中,我们实现了Model,同时还通过一些代码把view和controller联系起来了。同时,我们还在view和controller,以及用户touch事件之间建立了联系,这样controller类就可以来响应游戏里面的用户输入了。(译者:为什么费这么多劲,无非就是职责分离,这个非常重要!)。在接下来的文章里面,我们会谈到下面两个问题:

  • 在Controller里面更新Model,
  • 通知View关于Model的改变.

 

 

后记:本文已同步更新到cocos2d mvc这个系列里面去了。

    如果你觉得本文章对你有所帮助,请您点一下旁边的“推荐”按钮,这样可以让更多的人看到,同时也会给我写作的动力,谢谢大家。








posted @ 2012-03-18 19:59 子龙山人 阅读(1060) 评论(0) 编辑

cocos2d里面如何实现MVC(三)

    引子:前面两篇文章介绍了一些关于在cocos2d里面如何实现mvc的理论知识,接下来的这三篇教程,我将用一个简单的教程示例,给大家演示一下具体代码实现细节。

 

    这篇文章的写作目的就是让大家更好地理解如何在cocos2d里面实践mvc模式(当然,这里演示的不一定是标准的mvc,因为cocos2d特殊的编程方式。但是,这并不妨碍我们编写更好的代码,你们说对吧?),本文是基于前两篇文章的,所以,在继续阅读之前,我强烈建议你先读一下第一篇的理论介绍。

情景

    我们将制作一个简单的面板解谜游戏(board puzzle game),当然,我们不是简单地开发一个游戏,而是要利用mvc开发出一个简单的“游戏框架”,而且这个框架将会在我的新游戏里面使用到,它具有如下一些特性:

  1. 一个n行n列的游戏面板(game board),n可以随着游戏难度进行变化。 
  2. 这个游戏面板里面会包含一些“小方块(game pieces)”,而且每一个game board上都只能放一个game piece。
  3. 这个游戏面板可以初始化一些固定的小方块,玩家在游戏过程中,是不能移动这些小方块的。
  4. 这里还定义了一个“工具箱(toolbox)”,它上面可以放置许多小工具(toolbox item),它们可以看作是“可放置可移动小方块的槽子”。
  5. 小工具(或者叫槽子)上面可以放置许多同一类型的小工具。
  6. 这些小工具可以从工具箱上面移动,并且可以放置到game board 上面。

 

基本概念

来自 wikipedia:

    model负责管理应用领域的数据和行为逻辑,同时负责响应对自己的状态数据请求(这些请求通常是从view过来的),然后响应一些指令来更改自身的状态(这些请求通常是来自controller的)。在一个事件驱动的系统中,model会通知订阅者(observers)(通常是views)它的状态改变,这样view就可以做相应的显示更新。

    view则根据model的状态来合理地显示,通常是一些UI元素。一个model可以对应多个view,比如,同一数据的柱状图、饼状条、曲线图等。

   controller负责接收多用户输入和调用model的一些方法。一个controller通过从用户那里获得输入,然后操作model对象,最后,model通知view来更新显示。

   从维基百科的定义中,我们可以识别出以下几个主要的类(我们会在后面把model给加上去):

  1. GameBoardView 代表应用程序的主视图
  2. GameBoardController 是GameBoardView的一个控制器。

    请注意,这里的实线代表一种直接的关联关系(controller里面包含一个view的引用),而虚线则代表了一种间接的关联(通过观察者模式)。这里的直接关联后面会用来实现touch事件处理。

实现

项目组织结构

    在XCode4里面基于cocos2d的默认模板创建一个新的项目之后,我们又创建了下面这些Groups:

  1. View – views & controller 组 (我们也可以把view和controller放在那个不同的group里面,但是,由于我们两个有直接的关联关系,为了方便,我就把它们放在一些了)
  2. Model – 之后,我们会把model类放在这个group下面。

GameBoardView 的实现

    接下来,我们开始实现GameBoardView。首先,我们把GameBoardView继承至CCNode。

@interface GameBoardView : CCNode {
}

    然后,实现它的init方法,然后简单地显示一串字符来验证程序的正确性。(译者:这就和我们有时候会在方法的第一句加一个CCLOG一样,只是为了验证函数是否被调用了,确保每一步都是按照你的想法去走的,这样比那种埋头编写2个小时代码不编译,而后花一晚上修改编译错误和bug要好很多。有时候只是输出还不够,还必须要做单元测试,这样才能提高效率)

- (id)init {
if ((self = [super init])) {
// create and initialize a Label
CCLabelTTF *label = [CCLabelTTF labelWithString:@"Hello World from view" fontName:@"Marker Felt" fontSize:48];

// ask director the the window size
CGSize size = [[CCDirector sharedDirector] winSize];

// position the label on the center of the screen
label.position = ccp( size.width /2 , size.height/2 );

// add the label as a child to this Layer
[self addChild: label];
}

return self;
}

GameBoardController 实现

GameBoardController负责初始化view,所以它里面包含了一个GameBoardView的引用,将来就可以非常方便地直接使用了。

@interface GameBoardController : CCNode {
GameBoardView *view;
}

因为我们的GameBoardController继承到CCNode,所以,我们可以把GameBoardView当作GameBoardController的孩子给添加进去。

- (id)init {
if ((self = [super init])) {
view = [GameBoardView node];

[self addChild:view];
}

return self;
}

最后的修改

    我们然后修改AppDelegate类,然后运行我们新创建的contorller:

[[CCDirector sharedDirector] runWithScene: [GameBoardController node]];

   好了,现在编译并运行。当程序跑起来的时候,这个结果和cocos2d自带的模板运行效果差不多。但是,有个很重要的区别,那就是我们创建了一个mvc的骨架,在接下来的游戏逻辑中,我们可以在上面做很多文章。

接下来做什么

    该项目进行到现在,已经为我们引入一些更高级的概念打下了良好的基础,所以,在下一篇教程里,我们将涉及下面两个东西:

  1. 处理touch事件.
  2. 引用model的概念.

   

后记:本文已同步更新到cocos2d mvc这个系列里面去了。

    如果你觉得本文章对你有所帮助,请您点一下旁边的“推荐”按钮,这样可以让更多的人看到,同时也会给我写作的动力,谢谢大家。





posted @ 2012-03-18 17:07 子龙山人 阅读(1197) 评论(0) 编辑

(译)Iphone开发之音频101 (第一部分): 文件和数据类型

    免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播。同时,转载时不要移除本申明。如产生任何纠纷,均与本博客所有人、发表该翻译稿之人无任何关系。谢谢合作!

 

原文链接地址:http://www.raywenderlich.com/204/audio-101-for-iphone-developers-file-and-data-formats

 

本教程由大侠自来翻译,泰然论坛翻译组校对,感谢大侠自来也

 

  在做iphone开发之前,我(ray)对声音格式了解的相当少。我知道一些“wav”“mp3”声音格式的差异,但是我肯定不能准确地告诉你“AAC”“CAF”是什么格式的声音文件,同时也不知道在MAC上最好的转换音频文件的方法是什么。

  我深知,假如你想要成为一名合格的iphone开发者,你必须对声音文件数据格式,转换,录音和使用什么音频操作API有个基本的了解。

  这篇文章是三篇涵盖音频开发教程中的第一篇。在这篇文章中,我们将会从文件和数据格式开始。

文件格式和数据格式

   首先我们要知道的是,对每个音频文件有两部分:1是文件格式(也叫音频容器),2是数据格式(也叫音频编码)。

  文件格式(或者是音频容器)描述了这个文件它自己的格式。它里面的实际音频数据能使用很多不同的方式编码。例如,一个后缀为caf的文件是一种文件格式,它能够包含用MP3、线性pcmlpcm和其他许多格式编码的音频数据。

    接下来让我们更深入些。

数据格式(音频编码)

  我们接下来将要开始了解音频编码,而不是文件格式,因为音频编码才是最重要的。

  以下是iphone支持的的音频数据编码格式和针对每个的描述:

  •  AACaac其实是高级音频编码(advanced audio coding)”的缩写,它是被设计用来取代MP3格式的。你可能会想,它压缩了原始的声音,导致容量占用少但是质量肯定会有所下降。不过这些质量的损失取决于声音比特率的大小,当比特率合适的时候,这些损失人耳是很难听出来的。事实上,aacmp3有更好的压缩率,特别是在比特率低于128bit/s的时候 
  •  HE-AACHE-AACAAC的一个超集,这个“HE”代表的是“High efficiency”。 HE-AAC是专门为低比特率所优化的一种音频编码格式,比如streaming audio就特别适合使用这种编码格式
  • AMRAMR全称是“Adaptive Multi-Rate”它也是另一个专门为“说话(speech)”所优化的编码格式,也是适合低比特率环境下采用。
  • ALAC:它全称是“Apple Lossless”,这是一种没有任何质量损失的音频编码方式,也就是我们说的无损压缩。在实际使用过程中,它能够压缩40%-60%的原始数据。这种编码格式的解码速度非常快,这对iphone或者ipod这种小型设备来说非常适合。
  • iLBC: 这是另一种专门为说话所设计的音频编码格式,它非常适合于IP电话等其它需要流式音频的场合。
  • IMA4: 这是一个在16-bit音频文件下按照41的压缩比来进行压缩的格式。这是iphone上面一种非常重要的编码格式,我们将在以后讨论原因。
  •  Linear PCM它的中文意思是基于线性脉冲编码调制,用于将模拟声音数据转换成数字声音数据。简而言之,就是意味着无压缩数据。由于数据是非压缩的,它可以非常快的播放,并且当空间不是问题时,这是在iphone上面首选的音频编码方式。
  •  μ-law and a-law: 就我所知道的,这种编码是交替的编码模拟数据为数字格式数据,但是在speech优化方面比linear PCM更好。
  • ·MP3: 这种格式是我们都知道也喜欢的,虽然很多年过去了,但MP3到目前为止仍然是一种非常流行的编码格式,它也能被iphone很好地支持。

我们到底选择什么编码格式?

  上面的看起来是张很大的表,但是实际上只有一些是我们做开发时首选的。在具体做选择的时候,你必须记住以下几点:

  • 你可以播放linear PCM, IMA4和一些其它没有压缩的或者简单压缩的音频格式,这些格式可以很好地被iphone的硬件解码。
  • 对于更多高级的压缩格式,例如AACMP3,和ALACiphone并没有提供硬件编解码器的支持来很快解压缩这些数据,并且在一个时候只能处理一个文件。因此,假如你播放超过一个使用这些编码的音频文件,系统将选择使用软件来解码,那样会比较慢,还会占用CPU

  因此选择什么样的数据格式,这里有两条建议:

  • 假如空间不是问题,那么使用linear PCM来编码每个音频。这样不仅使你的音频最快地播放,而且你能够在不占用CPU资源的情况下同时播放多个声音。
  • 假如空间是问题,你大多数时候要使用AAC来编码你的背景音乐和IMA4来编码你的音效。

Linear PCM的许多变种

  还有一个很重要的关于linear PCM问题需要强调,那就是这种无压缩的数据格式是iphone上面编程首选的。针对不同的数据存储,这里有一些linear PCM变种。这些数据可以使用高尾数或者低尾数格式来储存,它们之间的差别,就像浮点型和整型它们占用的位宽不同。

  这里最重要的一件事就是,在iphone上面首选的linear PCM是低尾数(little-endian)格式的16位整型,或者是"LEI16"(好像是一种编码格式,apple caf audio format code: LEI16, ios设备的音频格式是16位低尾数编码)。这个是和Mac os x不一样的,它使用的是本地32位浮点型尾数编码。因为音频文件经常要在Mac上面创建,所以检查文件并把他们转换为iphone首选的音频编码格式是个很好的主意。

文件格式(音频容器)

  iphone支持很多文件格式,包括MPEG-1 (.mp3), MPEG-2 ADTS (.aac), AIFF, CAF, and WAVE。但是最重要的事是你可以只使用CAF,因为它能包含任何iphone支持的编码格式的数据,在iPhone上面它是推荐的文件格式。

  (译者:在这里我在啰嗦点,其实文件格式就像是桶一样,里面可以装很多水,那些水就是那些音频数据。桶有很多种,也就是有很多种文件格式,而且不一样的桶,也需要装不同的水。CAF这种桶就可以装各种各样的水,不过有些就只能装几种类型的水。希望我这样的比喻你可以很好的理解。

比特率

  这有一个有关音频编码很重要的术语,我们接下来会提及:比特率。

  比特率是音频文件每秒占据的字节数(比特数)。一些像AAC或者MP3编码会指定音频文件压缩的比特数。当你在使用比较低的比特率时,你将会丢失声音质量。

  你应该根据特定的声音文件的不同来选择不同的比特率,试着使用不同的比特率,来比较哪个是最合适的,在声音文件大小和声音质量之间做一些权衡。假如你的文件大多数是语音说话数据,你可以使用比较低的比特率。

  这里有一个表,给你一个最常见的比特率的概述:

  • 32kbit/s: 调幅(AM)广播的质量
  • 48kbit/s: 一般比较长时间的语音播客的比特率
  • 64kbit/s: 一般正常长度的语音播客的比特率
  • 96kbit/s: 调频(FM)广播的质量
  • 128kbit/s: 大多数MP3音乐的比特率
  • 160kbit/s: 那些很喜欢音乐的,想要听觉感受的人更喜欢的在128kbit/s之上的一个比特率
  • 192kbit/s: 数字电台的质量
  • 320kbit/s: 在这个比特率下人们几乎和CD的播放效果一样,不能区别
  • 500kbit/s-1,411kbit/s: 无损的音频编码,就像linear PCM

采样率

  在我们继续介绍音频之前,这里还有一个术语我们需要了解一下,就是:采样率。

  当转换一个模拟信号到数字格式,采样率表示多久抽取一次声音波形试样来转换成一个数字信号。

  大多情况下,44100Hz是被经常使用的,因为这和CD音频一样的采样率。

 

     更多关于iphone音频格式及音频操作的文章,可以去大侠自来也的主页看看。 

 

    要论坛交流,请点击传送门

posted @ 2012-03-18 14:41 子龙山人 阅读(745) 评论(0) 编辑

cocos2d里面如何实现mvc

cocos2d里面如何实现MVC(一)

 

cocos2d里面如何实现MVC(二)

 

cocos2d里面如何实现mvc(三)

 

cocos2d里面如何实现mvc(四)

 

cocos2d里面如何实现mvc(五)

 

cocos2d里面如何实现mvc(六)

 

cocos2d里面如何实现mvc(完)

posted @ 2012-03-11 23:07 子龙山人 阅读(2171) 评论(0) 编辑

cocos2d里面如何实现MVC(二)

   上一篇博文中,我提到了《如何在cocos2d里面实现mvc》,但是,都是一些纯理论的东西,我们需要看一些代码才能理解地更清楚。这篇博文是基于上一篇来写的,所以我建议你先阅读完上一篇再接着往下看。

模型类

    就像之前所讨论的,GameModel类存储了游戏世界里面的一些属性,比如当前的重力。但是,它同时也负责创建和连接游戏里面的对象,比如Player和Platforms。它们之间的关系如下图所示:(译者:这里采用了针对接口编程的方法,所有的游戏对象都继承至updateable接口,这样就可以在game loop里面更新自己了。同时GameModel类提供了一个工厂方法createGameObjects,用来创建游戏里面的对象。)

 

    你可能已经注意到了,所有的model类都实现了updateable protocol,并实现了update方法。这样它们就可以在game loop里面更新自己的状态了。比如,在Player类里面,我们需要根据当前x轴和y轴的速度来更新player的位置信息。在我的游戏里面,我把它委托给Physics组件,它是我实现的一个简单的物理引擎。但是,假如你的游戏很简单的话,你可以不用分开你的物理代码,然后可以直接在update方法里面来做碰撞检测等物理操作。

 

@implementation Player
- (void)update:(ccTime)dt
{
[_physics updateModel:self dt:dt];
// detect collisions with game objects, etc.
}

GameModel实现的update方法,不仅仅用来更新自己的状态,同时,它还调用player的update方法和所有platform的update方法。这个update方法,之后会被game loop所调用。

@implementation GameModel
- (void)update:(ccTime)dt
{
// modify game model properties here
// update player
[self.player update:dt];
// update platforms
for (Platform *platform in _platforms) {
[platform update:dt];
}
// ...
}

视图和控制器类

    对于我的游戏里面的每一个场景(CCScene),都关联了一个Controller类,它负责处理用户交互、创建视图和管理场景的跳转。控制器会schedule一个游戏主循环,在这个loop里面,所有的model和view的update方法都会被调用。

@implementation GameplayController
- (id)init
{
if((self=[super init])) {
GameplayView *view = [[GameplayView alloc] initWithDelegate:self];
// retain view in controller
self.view = view;
// release view
[view release];

// init model
GameModel *model = [GameModel sharedModel];
[model createGameObjects];
[model.player run];

[self scheduleUpdate];
}
}

- (void)update:(ccTime) dt
{
GameModel *model = [GameModel sharedModel];

if (model.isGameOver) {
[[CCDirector sharedDirector] replaceScene:[GameOverController node]];
}

// process model
[model update:dt];

// update view
[self.view update:dt];
}

View主要负责根据model的状态来渲染游戏画面。但是,同时,因为cococs2d的实现方式,我们还需要把touch事件传递给controller类。你应该注意到了,view不并直接依赖controller。view类调用controller的方法是通过GameViewDelegate协议来实现的。这也是为什么我们要在init方法里面传递一个delegate的原因。 

@implementation GameplayView
- (id)initWithDelegate:(id)theDelegate
{
if ((self = [super init])) {
self.delegate = theDelegate;

// initialize layers
_backgroundLayer = [GameplayBackgroundLayer node];
[self.delegate addChild: _backgroundLayer];

_platformLayer = [GameplayPlatformLayer node];
[self.delegate addChild:_platformLayer];

_playerLayer = [GameplayPlayerLayer node];
_playerLayer.delegate = theDelegate;
[self.delegate addChild: _playerLayer];

_hudLayer = [GameplayHudLayer node];
_hudLayer.delegate = theDelegate;
[self.delegate addChild:_hudLayer];
}

return self;
}

// 更新:我忘了告诉大家layer本身是怎么实现的了。其实很简单,就是创建一些sprite、action和animation等。 

@implementation GameplayPlayerLayer
- (id)init
{
if ((self = [super init])) {
self.isTouchEnabled = YES;
self.isAccelerometerEnabled = YES;
ResourceManager *resources = [ResourceManager sharedResourceManager];

[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:PLAYER_SPRITE_SHEET_PLIST];

CCSpriteBatchNode *spriteSheet = [resources playerSpriteSheet];
[self addChild:spriteSheet];
// ...
// initialize sprites
// initialize animations
}

    层里面的精灵都会在layer的update方法里面被更新,如下所示:

 

- (void)update:(ccTime)dt 
{
// update player sprite based on model
GameModel *model = [GameModel sharedModel];

_playerSprite.position = ccp((model.player.position.x - model.viewPort.rect.origin.x) * PPM_RATIO, (model.player.position.y - model.viewPort.rect.origin.y) * PPM_RATIO);
}

    注意,在渲染player的位置的时候,我们使用了PPM_RATIO,用来把米转换成point。(为什么是point而不是pixel,因为cocos2d使用的是point而不是pixel,不明白的可以看看源代码和官方文档)

    touch事件被传递给了controller类,如下所示:

@implementation GameplayPlayerLayer
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.delegate playerBeginJump];
}

  然后下图就是view和controller交互的完整的UML图:

处理模型事件

    上一篇博文中,我留下了一个问题,就是怎么处理model和controller之间的交互。其它很简单,就是使用观察者模式,controller只要订阅model的事件,然后定义相应的处理方法即可。当model更新的时候,会触发事件,然后所有侦听了该事件的controller都能被通知到。下面给出实现:(译者:很多童靯不知道对象之间该怎么交互,其实使用NSNotification可以大大地解耦对象的交互,使代码更容易维护。)

@implementation Player
- (void)beginJump
{
if ([_gameModel isOnGround:self]) {
[[NSNotificationCenter defaultCenter] postNotificationName:EVENT_PLAYER_BEGIN_JUMP object:nil];
...
}

   controller订阅事件,当事件发生的时候会得到通知,同时相应的事件处理函数将会被调用。

@implementation GameplayController
- (id)init
{
if ((self = [super init])) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onPlayerBeginJumpNotification:) name:EVENT_PLAYER_BEGIN_JUMP object:nil];
...
}
}

- (void)onPlayerBeginJumpNotification:(NSNotification *)notification
{
[[SimpleAudioEngine sharedEngine] playEffect:PLAYER_JUMP_SOUND];
}

就这么多!

    乍一看,可能会觉得有点复杂。而且,要创建这么多类,却只是为了实现一个简单的功能,确实有点划不来。而且,你还记得吗?如果在系统里面添加太多的类,其实是一种反模式(anti-pattern),叫做Fear of Adding Classes。但是,从长远的角度来看,从可维护性的角度来看,加这么多类是值得的。后面的教程我将向大家展示出来。如果大家对于如何在cocos2d里面使用mvc有更好的看法,欢迎补充。




posted @ 2012-03-11 23:05 子龙山人 阅读(1805) 评论(2) 编辑

cocos2d里面如何实现MVC(一)

 

 

前言:

    众所周知,现在MVC非常流行。现在只要随便搜索一下,哪里都是MVC的影子。刚开始在j2ee里面,然后是rails,后面居然.net也出来了,ios更不用说,哪里都是mvc,而且强制你必须使用mvc。但是,我们写的那些程序,真正完全符合mvc吗?呵呵,这个不好说,看个人理解程度而异。mvc实在是太火了,马上就有人在cocos2d社区里面讨论,cocos2d该怎么实现mvc呢?大家你一言,我一语,讨论的是热火朝天。有人支持,也有人反对。不管咋样,今天让我们也来见识一下cocos2d里面的mvc,看看到底这玩意儿好使不。

 

    Model-View-Controller (MVC) 在web应用开发中非常流行,它是一种组合设计模式,目前被广泛应用于带有图形交互用户界面程序开发中。一些web开发框架,比如Ruby On Rails,Django 和 ASP.NET MVC, 它们是不同语言平台上面的web开发框架,但是,它们都共用同样的原则--那就是把用户表示层和逻辑层分离开来。关注点分离(SoC),这个原则在现代软件工程方法中是一个非常重要的设计理念--不要迷失于实现细节,遇到一个实际问题的时候,要划分不同的关注点,且这些关注点必须隔离开来,这样才能达到更好的代码重用度,以获得鲁棒性、可适配性和可维护性。所有这些软件属性对于软件质量来说都是至关重要的。

    Cocos2d本身并不是基于mvc的理念来设计的,但是,这并不防碍你在自己的游戏开发中使用mvc。实现方式肯定是多种多样的,在这篇博文中, 我只是向大家分享一下我是怎么在cocos2d里面实现mvc的,同时,在最后,我会写一个简单的游戏demo,当然,里面使用的是cocos2d+mvc。

 

现有问题

    cocos2d里面有这样一些类,CCSprite,CCLayer,CCScene,所有这些,都是CCNode的子类。基本上,大家在使用cocos2d开发游戏的时候,都会采用下面的步骤来实现游戏逻辑:

  1. 通过应用程序代理类来初始化第一个CCScene(即AppDelegate里面的第一个CCScene),
  2. CCScene里面实例化一个或者多个CCLayer,并把它们当作孩子添加进去。
  3. CCLayer 里面实例化一个或者多个CCSprite,也调用addChild添加进去
  4. CCScene 处理用户输入(比如touch事件和加速计的改变),同时更新CCLayer和CCSpirte的属性,比如更改CCSprite的position,让sprite运行一个或多个actioin等。
  5. CCScene里在运行一个游戏循环(game loop,一般是1/60更新一次),然后CCLayer和CCSprite就在这个game loop里面做一些更新和游戏逻辑。

    这个过程看起来非常简单,而且也可以很快地做出游戏来。这也是为什么cocos2d这么流行的原因,它实在是太简单了。但是,当你的游戏逻辑越来越复杂的时候,你的代码会变得越来越难以维护。这里面最突出的问题就是,CCScene这个类负责的事情太多了---同时要处理用户交互,还有负责游戏逻辑(逻辑层)和画面显示(表示层)。(译者:根据SoC的原则,这显然是不合理的,我们应该把职责分离开来,这样代码才更容易维护。同时SRP(单一职责原则)也是这么要求的,一个类只负责一件事情)

 

模型(Model)

    MVC它会把一个系统划分为以下几个组件:

  • Model ,它负责与领域相关的逻辑处理代码,也可以说是逻辑层,或者领域层。
  • View ,只负责界面显示。
  • Controller ,它负责处理用户交互。

    让我们先从model开始。Model代表了游戏逻辑。因为我现在正在制作一个platform游戏,所以,我讲的一些东西也是与platform游戏相关联的。我的游戏里面的model包含下面一些类(当然,仅仅是一部分类)

  • Player,
    • 包含一些属性,比如:player的位置、当前速度(x轴速度、y轴速度)等。
    • 包含一些与player有关的处理逻辑,比如:run,walk,jmup等。
    • 包含一个update方法,该方法会被游戏主循环每一帧刷新时所调用,它主要负责更新player model。
  • Platform,
    • 包含一些属性,比如:platform位置、宽度、高度等。
    • 包含一些与platform有关的处理逻辑,比如:倾塌等
    • 包含一个update方法,该方法会被游戏主循环每一帧刷新时所调用,它主要负责更新patform的model。
  • GameModel,
    • 包含一些游戏世界的属性,比如重力等。
    • 包含一些方法来执行游戏逻辑。
    • 包含一个update方法,该方法会在每一帧刷新的时候被game loop所调用,然后它就可以更新自己的状态,同时还会触发游戏世界里面的其它对象也相应地更新自己的状态。

    你可能会问:有些属性你完全没有必要重复定义,你可以直接从CCSprite里面得到,比如position、width、height等。我想说:有对有错。说对呢,是因为它们确实差不多,可以拿来就用。说不对呢,那是因为,model有可能使用一些不同的计量单位,比如米,而不是像素。(比如box2d这样的,就不是使用像素作为单位)。在我的model里面,我使用的是米,当然,你也可以使用英尺,或者其它单位。渲染引擎对于model来说是透明的,model完全不用关心。

视图(View)

    根据mvc的原则,view应该只负责界面显示。它实际上也是在cocos2d里面实现mvc时,最简单的一个。如果你有一个model,你可以使用CCLayer,然后添加一些CCSprite或者其它coocs2d类来处理显示问题。把model和view分开的好处就是,你没必要把model的属性直接映射到view的属性上面去。比如,你的玩家在x轴方向上移动,但是,你想让它总是在距离屏幕左边10px的位置。这时候,你就可以移动CCLayer了,而不是真的在移动sprite。当把model对象显示出来的时候,你必须考虑单位,如果你使用的是米作为计量单位,你在渲染的时候必须转化为像素。(你可以像box2d里面一样,定义一个PTM_RATIO)那么你的model怎么和view打交道呢?你可以从controller里面得到view,或者你可以把game model制作成一个单例,然后使用静态方法来处理它。

控制器(Controller)

    controlller负责把view和model联系起来。它的主要职责就是处理用户输入。由于我们需要实例化model和view,我发现在controller里面来做非常合适。我是把controller类继承到CCScene类,然后我们需要建立一个初始的controller类,它由appDelegate来实例化。然而,这里会有一个问题,touch事件是由CCLayer来处理的,而它在我的设计里面的角色是view。而我又不想让view来处理用户输入,所以,我需要传递一个view的引用给controller(不是直接传递,而是通过delegate),然后通过delegate来执行controller的touch事件处理代码,以此来处理view里面的touch事件。好了,现在我的controller类就能够处理来自view的用户事件了。然后,它可以根据用户的输入来操作model,要么通过修改model的属性,或者调用model的方法。再更新完model之后,我们的view也需要得到通知并更新。所有这些,我都在game loop里面完成,实际上它就是一个controller。controller的职责只是负责调用view的update方法,然后剩下的就交给view去完成啦。

 

还有一件事情…

   游戏并不仅仅是根据model状态的更改来更新一下view就可以了,它还需要播放音乐和音效。由于controller负责处理用户交互,它肯定知道何时该播放什么音效。但是,有些时候也会有例外。如果一个player掉到platform上面,但是controller并不知道,因为这部分逻辑判断在model里面。那我们可以从model里面播放音效吗?。。。不,我们不能这样做。因为这样就破坏了SoC的原则了,model就应该只负责游戏逻辑。那么,我们该怎么做呢?在下一篇博文中,我将向大家展示我是怎么做的,我打赌,你肯定差不多也想到呢,对吧?

 

后记:cocos2d mvc这个系列一共有7篇,我会统一更新到这个目录,方便大家索引。

posted @ 2012-03-11 22:39 子龙山人 阅读(3230) 评论(2) 编辑
仅列出标题  下一页

公告

统计