F's Blog

博客 收藏夹
cocos2d-x

29 Jun 2015

本文中使用的是3.6版。

开发过iOS,会发现很像,包括界面生命周期、内存管理等。本来它就是从cocos2d进化来的,加了个x是表示多平台。

看着绚丽的游戏,开发起来就知道了背后的琐碎。动画、位置、场景、界面布局等都要用代码去完成。但当看到自己的代码让静态的图片动起来的时候,仿佛自己创造了这个世界,也是很兴奋的!上帝创造这个世界,就是为了让它展示惊喜。

坐标

Directot

Node

一切皆node。这样便于遍历和管理,逻辑上也归一了。

一直认为设计语言也好,框架也好,所以概念一定要有一个最终的归一。

Scene

Scene类是Node的子类,和Node相比,只多了一个特性:拥有自己的锚点。

创建时默认会创建一个layer。

Layer

层。可以组织不同的逻辑功能。

在layer内部是忽略锚点的。

Sprit

Sprit就是图片。

Action

Action是一个东西在移动。有:

定义一个action后,让node调用runAction方法执行之,并设定多久执行完,

Sequence

一个动作接着一个动作,串行。只有动作的执行时间不定时才用scheduleOnce来代替,来执行延时。

// 延时性动作,即FiniteTimeAction
MoveTo* moveTo = MoveTo::create(1.0f, Vec2(100,100));

// C++的lambda,一般需要使用“&”来引用外部变量,需要值引用的话用“=”,或直接写变量名
CallFunc* callFunc = CallFunc::create([&](){

});

Action* actions = Sequence::create(moveTo, callFunc, NULL);

// 这样在sprit执行完moveTo后就会执行回调了
sprit->runAction(actions);

说来可笑,在我没有发现Sequence前,我用scheduleOnce来衔接各个动作,计算好时间,来实现回调功能。当时还想怎么逻辑会这么复杂,且紧耦合呢。

Spawn

所有动作一起动,并行。

Schedule

每隔一段时间执行一次回调函数。如果就执行一下,那就是延时了。

关于回调函数,可以重写系统的那个每帧都会执行的,virtual void update(float dt),然后需要开始执行时让那个node调用sceduleUpdate()方法。

也可以自己定义,然后调用schedule(schedule_selector(MyClass::myUpdate), 2.0),即开始执行,调用scheduleOnce的话就执行一次,否则每隔大约两秒执行一次,直到 调用unSchedule(schedule_selector(MyClass::myUpdate))方法。

注意这个计时不准,跟每帧执行的时间有关,总是会慢点儿的。

Animation

Animation是以一定的频率在换不同的东西显示。

内存管理

使用的Objective C的计数方式。

通过工厂方法create创建的对象都是autorelease的。一个对象,只有先调用autorelease,后面的retain和release才有意义,而且要成对出现。

而在create里发生了什么?

对,依旧是new和release成对出现。auto的意思是下一次释放,叫nextrelease会不会更好呢?但如果在下次检测前被retain了,引用就成1了,所以不会被释放了。

在每帧的结束,会对pool里的每个对象release,即AutoreleasePool::clear()被执行。在CCDirector.cpp里可以看到:

void DisplayLinkDirector::mainLoop()
{
    if (_purgeDirectorInNextLoop)
    {
        _purgeDirectorInNextLoop = false;
        purgeDirector();
    }
    else if (_restartDirectorInNextLoop)
    {
        _restartDirectorInNextLoop = false;
        restartDirector();
    }
    else if (! _invalid)
    {
        drawScene();

        // release the objects
        PoolManager::getInstance()->getCurrentPool()->clear();
    }
}

autorelease原理

调用OC

1. 在Classes里定义头文件,如“header.h”,

// head.h
class MyClass {
  void hi();
}

2. 然后在proj.ios_mac/ios文件夹里实现。

// MyClass.mm

#import”header.h”
void MyClass::hi() {

//Your Objective-C++ code

}

这样在编译时就ok,使用时直接#include “header.h”即可。

广告

支持iOS国内没有那么多,我用多盟和广点通。它们相互补充,因为有时多盟没有广告,这是在回调了显示GDT的。

用上面的方法,把多盟的广告合了进去。官方给的例子太简单。

有些收获:

吐槽:OC好久没用,竟然忘了加号表示的就是class方法,还以为是public方法呢。一门语言就是这样,许久不用就忘了,也是当初用的少吧。要不C++,Java怎么不会忘呢?

友盟

友盟对cocos2d-x的支持还是比较好的,统计和分享功能很实用。

下载下分享的SDK后:

  1. Cocos2dx:实现cocos2d-x中跨平台分享功能,需拷贝到您项目的Classes文件夹中。
  2. Platforms:原生的Android和iOS社会化组件SDK,需要您将库和资源拷贝到对应平台的项目中。

然后有些坑:

C++

游戏真的很适合C++,面向对象,内存管理。

没有银弹,无论何种语言,逻辑都少不了,按钮都得画上。

CocoStudio

Android

将游戏发布到Android平台(要不白瞎了这跨平台)。

由于之前一直在Mac上开发iOS版,第一版已经出来了才往Android上移植,自然问题比较多,还好一一解之。

之前以为像Mac下,直接打开工程文件就可以了。确实,如果是一个新的工程,直接进入proj.android文件下执行

$ ./build_native.py

就能生成apk文件在bin/debug/android目录下了。对,是要执行脚本的,而不是在eclipse里直接run就能跑起来的,因为要编译native代码。

但是,在开发的过程中,加入了新的cpp文件,引入了新的库,如广告、分享、购买等,这些都是基于iOS的,所以不能让它们在Android里被编译到。

所以在写代码时就应该考虑到隔离,用预处理来区分:

#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS
  // iOS code
#endif

#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
  // android code
#endif

因为C++是静态语言嘛,对于Ruby这类的动态语言,不需要编译,直接跑在虚拟机环境里,也就不需要做跨平台处理了。

吐槽:当然这都是在我改了一晚上血累的总结啊!

Android.mk

因为eclipse没有xcode那样智能,直接把各种编译环境都配置好,所以Android需要自己写。

其实也主要就是源文件和头文件啦:

#一个个添加自己的CPP文件,当然可以写脚本(以我的经验还是算了吧,一分钟的事情)
LOCAL_SRC_FILES := hellocpp/main.cpp \
                  ../../Classes/HelloScene.cpp \
                  ... \
                  ../../Classes/xxx.cpp

# 自动包含Classes下的头文件(这个是可以自动滴)
FILE_INCLUDES := $(shell find $(LOCAL_PATH)/../../Classes -type d)
LOCAL_C_INCLUDES := $(FILE_INCLUDES)

只要把上面的配置好,剩下的就是一个个解决编译中的问题了。否则不会出现问题,直接就能编译出apk文件,可是一运行就报类似下面的错:

cocos2dx Fatal signal 11 (SIGSEGV) at 0x00000000 (code=1)

这是最让人头疼的,错了但是没有log。

当然,把以上问题都解决了,即编译的问题,就可以在eclipse里直接调试应用了。

可细看:

Manifest.xml

这里就是注意配置各种权限,屏幕方向等,因为iOS不需要这玩意。

第三方库

其实在开始做到“干湿分离”,问题就没有什么。

跨平台的代码放到Classes目录下,原生的放到各个proj里,然后通过在Classes里预处理调用。

Android JNI

这里主要是C++调用Java,利用Java的反射机制来实现。

看一下GameSharing的打开成就面板的例子:

// "GameSharing.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
#include "platform/android/jni/JniHelper.h"
#include <jni.h>
#include <android/log.h>
#endif

// "GameSharing.cpp"
void GameSharing::ShowAchievementsUI(){
#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
    //Android only:
    //Can the Google Play API be used?
    if(IsGPGAvailable()){
        JniMethodInfo t;
        if (JniHelper::getStaticMethodInfo(t
                                           , "org/cocos2dx/cpp.AppActivity"
                                           , "showAchievements"
                                           , "()V"))
        {
            t.env->CallStaticVoidMethod(t.classID, t.methodID);
        }
    }
    else{
        errorHandler();
    }
#endif
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS
    openAchievementUI();
#endif
}
  1. 定义方法t: JniMethodInfo t;
  2. 从Java类里获得方法:JniHelper::getStaticMethodInfo(t, “class Name”, “method name”, “method define type”);
  3. 调用: t.env->CallStaticVoidMethod(t.classID, t.methodID);

三步走,比Java调用C简单多了,如果会smali汇编就更妙了。

参考:

一些坑

Lua

动态嵌入,不用每次编译。它和C++用栈进行交互。

发布

xcode是很智能的,处理了大部分琐碎的编译工作。

一些小提示:

多尺寸截屏生成工具:https://launchkit.io/screenshots/onboard,可以生成iTunes需要提交的4种尺寸,还可以设计说明,很是方便。

一些小坑:

感想

看两年前实习时跟着导师做的游戏项目,他的代码就是堆砌,我现在竟然不自觉的会建个父类。这也让我看到了自己的进步。 而且当时糊里糊涂的写,现在也都有概念。

用了些框架,里面的想法都相近:MVC、解耦合等。

时间,在游戏里很重要,如果它在现实里那样重要一样。它控制着整个游戏的显示秩序。

做游戏是让人兴奋的,因为它可以让你去创造一个世界!

第一个游戏

独自开发了自己的第一个游戏,用了一个月的时间。因为就一个游戏场景,比较简单,主要是把那些图片拼凑起来,然后写写如何动的逻辑。

One more thing, 游戏的名叫《剁手指》,官网在此,有iOS和Android版的下载。

iOS上线后一天发现已经有30个玩家玩,等级有的还很高了,而且很多国外的。很开心,让我想把该死的广告去掉用更好的方式去赚钱,同时提供英语版。

版本发布

版本之所以存在,是因为有路线图,路线图上有时间节点。版本发布就要按时间节点,否则你的app总有新功能去完成,如果一直要开发完所有想到的需求,那就一直等待着上线吧!

一个版本,定好需求后,就去做,新的需求那就是下一个版本了。

如果非要在功能和上线时间有一个取舍,那么我认为上线时间更重要,可以为了上线减少功能。因为越早与用户见面,就能够越早知道真正的需求和检测新加的功能。

而且应用不上线,压力也会很大,因为什么都没有,这是最可怕,没有结果。

参考

本文由 付豪 创作,采用署名 4.0 国际(CC BY 4.0)创作共享协议进行许可,详细声明