Cocos2dx 3.x scheduler是怎么工作的

PS:由于CCNode.cpp中的关于schedule的方法最终都要调用schedule本身的方法,所以这里就不看了。

多说一句:CCNode中的_scheduler也是从Director中拿过来的。跟一篇中的_actionManager一样。



进入正题:

下面我们看看CCScheduler.h,它里面一共定义了5个类。先一点一点看:

<span style="font-size:14px;">#ifndef __CCSCHEDULER_H__
#define __CCSCHEDULER_H__
#include <functional>
#include <mutex>
#include <set>
#include "base/CCRef.h"
#include "base/CCVector.h"
#include "base/uthash.h"
NS_CC_BEGIN
class Scheduler;

typedef std::function<void(float)> ccSchedulerFunc; </span>
定义了一个返回值为void, 接收一个float参数的函数类型ccSchedulerFunc,schedule中的回调函数都是这个类型。

timer.h,这个类是一个抽象类。作为TimerTargetSelector.h和TimerTargetCallback.h还有TimerScriptHandler.h的基类。

先看一下timer.h:

<span style="font-size:14px;">class CC_DLL Timer : public Ref
{
protected:
    Timer();
public:
    /** get interval in seconds */
    inline float getInterval() const { return _interval; };
    /** set interval in seconds */
    inline void setInterval(float interval) { _interval = interval; };
    
    void setupTimerWithInterval(float seconds,unsigned int repeat,float delay);
    
    virtual void trigger() = 0;
    virtual void cancel() = 0;
    
    /** triggers the timer */
    void update(float dt);
    
protected:
    
    Scheduler* _scheduler;  // weak ref
    float _elapsed;         //上一次执行到现在的时间
    bool _runForever;       // 状态变量,标记是否永远的运行。
    bool _useDelay;         //是否使用延时
    unsigned int _timesExecuted;    //执行次数
    unsigned int _repeat;   //0 = once,1 is 2 x executed// 定义要执行的总次数,0为1次  1为2次
    float _delay;           //延时次数
    float _interval;        //执行间隔
};</span>

参数都是一些基本参数。这里不看了。


在这里我们主要看以下timer的update方法:

<span style="font-size:14px;">void Timer::update(float dt)
{
    if (_elapsed == -1)//如果_elapsed == -1,表示这个定时器第一次进入到update方法中
    {
        _elapsed = 0;
        _timesExecuted = 0;
    }
    else
    {
        if (_runForever && !_useDelay)
        {//standard timer usage
            _elapsed += dt;
            if (_elapsed >= _interval)
            {
                trigger();

                _elapsed = 0;//达到触发条件,将_elapsed置为0
            }
        }    
        else
        {//advanced usage
            _elapsed += dt;
            if (_useDelay)
            {
                if( _elapsed >= _delay )
                {
                    trigger();
                    
                    _elapsed = _elapsed - _delay;//减去延时时间,剩下的是真正的第一次触发后到现在的时间
                    _timesExecuted += 1;
                    _useDelay = false;//延时已经过去,触发了第一次, 按正常处理(不永久执行,没有延时)
                }
            }
            else                        //不适用延时
            {
                if (_elapsed >= _interval)
                {
                    trigger(); //触发函数,是一个纯虚函数,实际作用在子类方法中
                    
                    _elapsed = 0;
                    _timesExecuted += 1;

                }
            }

            if (!_runForever && _timesExecuted > _repeat)//触发次数达到重复次数
            {    //unschedule timer
                cancel(); //取消定时器
            }
        }
    }
}</span>
在这个update方法中主要调用了tigger()和cancel()方法。由于timer类中,这两个方法是纯虚函数。所以会动态调用子类中的方法。

然后看看TimerTargetSelector.h

<span style="font-size:14px;">class CC_DLL TimerTargetSelector : public Timer
{
public:
    TimerTargetSelector();

    /** Initializes a timer with a target,a selector and an interval in seconds,repeat in number of times to repeat,delay in seconds. */
    bool initWithSelector(Scheduler* scheduler,SEL_SCHEDULE selector,Ref* target,float seconds,float delay);
    
    inline SEL_SCHEDULE getSelector() const { return _selector; };
    
    virtual void trigger() override;
    virtual void cancel() override;
    
protected:
    Ref* _target;               //执行定时器的对象
    SEL_SCHEDULE _selector;     //执行定时器会回调的方法
};</span>
参数也很好理解。_target执行定时器的对象,_selector执行定时器的回调方法。可能会对SEL_SCHEDULE这个新类型比较陌生,我们可以看一下他的实现:

<span style="font-size:14px;">typedef void (Ref::*SEL_SCHEDULE)(float);</span>
定义了一个关联Ref类的函数指针。这个函数返回值为void,参数类型为float,SEL_SCHEDULE就是这个函数指针的类型名。

然后看一下TimerTargetSelector中的initWithSelector方法:

<span style="font-size:14px;">bool TimerTargetSelector::initWithSelector(Scheduler* scheduler,float delay)
{
    _scheduler = scheduler;
    _target = target;
    _selector = selector;
    setupTimerWithInterval(seconds,repeat,delay);
    return true;
}</span>
这个方法也就是一些简单的初始化操作。这里会调用setupTimerWithInterval这个方法,本类中没有,所以会跑到父类中找。

然后看一下TimerTargetSelector中的trigger方法:

<span style="font-size:14px;">void TimerTargetSelector::trigger()
{
    if (_target && _selector)
    {
        (_target->*_selector)(_elapsed);
    }
}</span>
这个方法很简单,执行回调函数。

再看一下cancel方法吧:

<span style="font-size:14px;">void TimerTargetSelector::cancel()
{
    _scheduler->unschedule(_selector,_target);
}</span>

现在TimerTargetSelector.h分析完了,其实很简单,就两个成员变量。主要方法就是tigger方法,他会执行回调函数。其他两个子类跟这个类大同小异.

TimerTargetCallback.h:

<span style="font-size:14px;">class CC_DLL TimerTargetCallback : public Timer
{
public:
    TimerTargetCallback();
    
    /** Initializes a timer with a target,a lambda and an interval in seconds,delay in seconds. */
    bool initWithCallback(Scheduler* scheduler,const ccSchedulerFunc& callback,void *target,const std::string& key,float delay);
    
    inline const ccSchedulerFunc& getCallback() const { return _callback; };
    inline const std::string& getKey() const { return _key; };
    
    virtual void trigger() override;
    virtual void cancel() override;
    
protected:
    void* _target;  //一个void类型指针,应该是记录一个对象的 
    ccSchedulerFunc _callback;  //回调函数
    std::string _key;           //定时器的另一个别名
};</span>
这里说一下成员变量_key表示回调函数的别名。
然后看一下 它的trigger方法:

<span style="font-size:14px;">void TimerTargetCallback::trigger()
{
    if (_callback)
    {
        _callback(_elapsed);
    }
}</span>

这里我还有一个没有弄明白的地方就是回调函数。先画个问号明天就看这个回调函数。

终于要到Scheduler.h中了,由于这里面代码比较多。我主要是看了一下成员变量还有几个主要的方法,要看成员变量,第一眼看到的就是几个结构体。

先对这几个结构体做个解析:

<span style="font-size:14px;">typedef struct _listEntry <span style="white-space:pre">		</span> //<span style="color: rgb(51,51); font-family: 'Helvetica Neue',serif; line-height: 25.2000007629395px;">这个结构体是为scheduler自带的update定义的,(定时器的基本属性)</span>
{
    struct _listEntry   *prev,*next;
    ccSchedulerFunc     callback;
    void                *target;            //key
    int                 priority;<span style="white-space:pre">	</span>    //优先级,越小越先执行
    bool                paused;
    bool                markedForDeletion; //是否需要删除
} tListEntry;</span>

<span style="font-size:14px;">typedef struct _hashUpdateEntry  <span style="white-space:pre">	</span>//<span style="color: rgb(51,serif; line-height: 25.2000007629395px;">这个结构体是为</span><span style="font-family: 'Helvetica Neue',serif; line-height: 25.2000007629395px;">scheduler自带的update定义的。</span>
{
    tListEntry          **list;        // 这里需要注意一单,这个list存放的是上边结构体的内容
    tListEntry          *entry;        // entry in the list
    void                *target;
    ccSchedulerFunc     callback;
    UT_hash_handle      hh;
} tHashUpdateEntry;</span>

typedef struct _hashSelectorEntry //这个是为Ref类型的对象和非Ref类型的对象定义的结构体(主要是为自定义的结构体用到的类型)
{
    ccArray             *timers;<span style="white-space:pre">		</span> //存放target相关的所有timer
    void                *target;<span style="white-space:pre">		</span>//开启定时器的node(一般作为key来找对应的Hash表元素)
    int                 timerIndex; <span style="white-space:pre">		</span>//当前定时器的下标
    Timer               *currentTimer; <span style="white-space:pre">		</span>//一个node可以开启多个定时器(指向当前的定时器)
    bool                currentTimerSalvaged;  //当前状态是否可回收
    bool                paused;
    UT_hash_handle      hh;
} tHashTimerEntry;

然后看一下scheduler.h中的成员变量:

    float _timeScale;
    struct _listEntry *_updatesNegList;        // list of priority < 0
    struct _listEntry *_updates0List;            // list priority == 0
    struct _listEntry *_updatesPosList;        // list priority > 0
    struct _hashUpdateEntry *_hashForUpdates; // hash used to fetch quickly the list entries for pause,delete,etc

    // Used for "selectors with interval"
    struct _hashSelectorEntry *_hashForTimers;      //用来记录所有的 tHashTimerEntry 的链表头指针
    struct _hashSelectorEntry *_currentTarget;
    bool _currentTargetSalvaged;
    // If true unschedule will not remove anything from a hash. Elements will only be marked for deletion.
    bool _updateHashLocked;

_hashForUpdates表示的是schedule自身开启的定时器链表中的第一个元素,这个元素是_hashUpdateEntry类型的,他的list指向_updatesNegList、_updates0List、_updatesPosList。


_hashForTimers表示的是自定义的定时器链表中的第一个元素,这个元素类型是_hashSelectorEntry。

_currentTarget表示的是当前遍历到的那一个元素。

_currentTargetSalvaged表示当前指向的元素是否可以回收(为true表示可以回收)

_updateHashLocked这个东西不太懂有什么作用。英文注释说的是:如果为true在unschedule的时候不会从Hash表中移除任何一个元素,需要移除的元素会标记为可移除状态。


上边的这些成员变量我也是一直半解。


然后来看看schedule函数:

<span style="font-size:14px;">void Scheduler::schedule(SEL_SCHEDULE selector,Ref *target,float interval,float delay,bool paused)
{
    CCASSERT(target,"Argument target must be non-nullptr");
    
    //_hashForTimers 这个数组中找与&target相等的元素,用element来返回
    tHashTimerEntry *element = nullptr;
    HASH_FIND_PTR(_hashForTimers,&target,element);//根据target查找对应的hash表元素
    /*
        tHashTimerEntry  这个结构体是用来记录一个Ref 对象的所有加载的定时器
        _hashForTimers 是用来记录所有的 tHashTimerEntry 的链表头指针。
    */
    if (! element)
    {   //如果没有找到,创建一个
        element = (tHashTimerEntry *)calloc(sizeof(*element),1);
        element->target = target;
        //添加到hash表链中
        HASH_ADD_PTR(_hashForTimers,target,element);
        
        // Is this the 1st element ? Then set the pause level to all the selectors of this target
        element->paused = paused;
    }
    else
    {
        CCASSERT(element->paused == paused,"");
    }
    //检查这个元素的定时器列表,如果列表为空 则new 10个数组出来备用
    if (element->timers == nullptr)
    {
        element->timers = ccArrayNew(10);
    }
    else
    {   //循环查找定时器数组,看看是不是曾经定义过相同的定时器,如果定义过,则只需要修改定时器的间隔时间
        for (int i = 0; i < element->timers->num; ++i)
        {
            TimerTargetSelector *timer = dynamic_cast<TimerTargetSelector*>(element->timers->arr[i]);
            
            if (timer && selector == timer->getSelector())
            {
                CCLOG("CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f",timer->getInterval(),interval);
                timer->setInterval(interval);
                return;
            }
        }
        //扩展1个定时器数组   //给 ccArray分配内存,确定能再容纳一个timer
        ccArrayEnsureExtraCapacity(element->timers,1);
    }


    //创建一个定时器,并且将定时器加入到当前链表指针的定时器数组中。。创建timer并把timer添加到element->timers数组中是
    //schedule()的目的。。此时这个timer已经进入到schedule的检测数组中了。。
    TimerTargetSelector *timer = new (std::nothrow) TimerTargetSelector();
    timer->initWithSelector(this,selector,interval,delay);
    ccArrayAppendObject(element->timers,timer);
    timer->release();
}</span>

下面看一下schedule的函数过程:
先调用了 HASH_FIND_PTR(_hashForTimers,element); 有兴趣的同学可以跟一下 HASH_FIND_PTR这个宏,这行代码的含义是在 _hashForTimers 这个数组中找与&target相等的元素,用element来返回。

而_hashForTimers不是一个数组,但它是一个线性结构的,它是一个链表。

下面的if判断是判断element的值,看看是不是已经在_hashForTimers链表里面,如果不在那么分配内存创建了一个新的结点并且设置了pause状态。
再下面的if判断的含义是,检查当前这个_target的定时器列表状态,如果为空那么给element->timers分配了定时器空间
如果这个_target的定时器列表不为空,那么检查列表里是否已经存在了 selector 的回调,如果存在那么更新它的间隔时间,并退出函数。

1
ccArrayEnsureExtraCapacity(element->timers,1);//这行代码是给 ccArray分配内存,确定能再容纳一个timer。


函数的最后四行代码,就是创建了一个新的 TimerTargetSelector 对象,并且对其赋值 还加到了 定时器列表里。

这里注意一下,调用了 timer->release() 减少了一次引用,会不会造成timer被释放呢?当然不会了,大家看一下ccArrayAppendObject方法里面已经对 timer进行了一次retain操作所以 调用了一次release后保证 timer的引用计数为1。

看过这个方法,我们清楚了几点:

  1. tHashTimerEntry 这个结构体是用来记录一个Ref 对象的所有加载的定时器

  2. _hashForTimers 是用来记录所有的 tHashTimerEntry 的链表头指针。


然后看看update方法:

void Scheduler::update(float dt)
{
    _updateHashLocked = true;       // 这里加了一个状态锁,应该是线程同步的作用。

    if (_timeScale != 1.0f)
    {
        dt *= _timeScale;           // 时间速率调整,根据设置的_timeScale 进行了乘法运算。
    }
    // Selector callbacks
    // Iterate over all the Updates' selectors
    tListEntry *entry,*tmp;    //定义了两个遍历链表的指针

    // updates with priority < 0
    DL_FOREACH_SAFE(_updatesNegList,entry,tmp)
    {
        if ((! entry->paused) && (! entry->markedForDeletion))
        {
            entry->callback(dt);
        }
    }

    // updates with priority == 0
    DL_FOREACH_SAFE(_updates0List,tmp)
    {
        if ((! entry->paused) && (! entry->markedForDeletion))
        {
            entry->callback(dt);
        }
    }
    // updates with priority > 0
    DL_FOREACH_SAFE(_updatesPosList,tmp)
    {
        if ((! entry->paused) && (! entry->markedForDeletion))
        {
            entry->callback(dt);
        }
    }

    // Iterate over all the custom selectors        // 遍历_hashForTimers里自定义的计时器对象列表
    //_hashForTimers指向的是tHashTimerEntry 的链表头指针
    for (tHashTimerEntry *elt = _hashForTimers; elt != nullptr; )
    {
        _currentTarget = elt;
        _currentTargetSalvaged = false;

        if (! _currentTarget->paused)
        {
            // The 'timers' array may change while inside this loop
            //遍历每一个对象的定时器列表
            for (elt->timerIndex = 0; elt->timerIndex < elt->timers->num; ++(elt->timerIndex))
            {
                elt->currentTimer = (Timer*)(elt->timers->arr[elt->timerIndex]);
                elt->currentTimerSalvaged = false;

                elt->currentTimer->update(dt); // 执行定时器过程。在这里边可能会改变currentTimerSalvaged(例如在回调函数中关闭调度器)
                /*
                    因为TimerTargetSelector中没有update函数,这里会先调用父类即Timer中的update函数,但是穿进去的是
                    TimerTargetSelector对象的指针,在update方法中会动态的调用子类中的trigger()方法,因为在Timer类中
                    trigger()方法是virtual的
                */
                if (elt->currentTimerSalvaged)
                {
                    // The currentTimer told the remove itself. To prevent the timer from
                    // accidentally deallocating itself before finishing its step,we retained
                    // it. Now that step is done,it's safe to release it.
                    //的作用是标记当前这个定时器是否已经失效,在设置失效的时候我们对定时器增加过一次引用记数,这里调用release来减少那次引用记数,
                    //这样释放很安全,这里用到了这个小技巧,延迟释放,这样后面的程序不会出现非法引用定时器指针而出现错误
                    elt->currentTimer->release();
                }

                elt->currentTimer = nullptr;
            }
        }

        // elt,at this moment,is still valid
        // so it is safe to ask this here (issue #490)
        elt = (tHashTimerEntry *)elt->hh.next;

        // only delete currentTarget if no actions were scheduled during the cycle (issue #481)
        // 如果_currentTartetSalvaged 为 true 且这个对象里面的定时器列表为空那么这个对象就没有计时任务了我们要把它从__hashForTimers列表里面删除。
        if (_currentTargetSalvaged && _currentTarget->timers->num == 0)
        {
            removeHashElement(_currentTarget);
        }
    }

    // delete all updates that are marked for deletion

    //回收Node自带的update方法开启的调度器(如果表示为可回收)
    // updates with priority < 0
    DL_FOREACH_SAFE(_updatesNegList,tmp)
    {
        if (entry->markedForDeletion)
        {
            this->removeUpdateFromHash(entry);
        }
    }

    // updates with priority == 0
    DL_FOREACH_SAFE(_updates0List,tmp)
    {
        if (entry->markedForDeletion)
        {
            this->removeUpdateFromHash(entry);
        }
    }

    // updates with priority > 0
    DL_FOREACH_SAFE(_updatesPosList,tmp)
    {
        if (entry->markedForDeletion)
        {
            this->removeUpdateFromHash(entry);
        }
    }

    _updateHashLocked = false;
    _currentTarget = nullptr;

#if CC_ENABLE_SCRIPT_BINDING
    //
    // Script callbacks
    //

    // Iterate over all the script callbacks
    if (!_scriptHandlerEntries.empty())
    {
        for (auto i = _scriptHandlerEntries.size() - 1; i >= 0; i--)
        {
            SchedulerScriptHandlerEntry* eachEntry = _scriptHandlerEntries.at(i);
            if (eachEntry->isMarkedForDeletion())
            {
                _scriptHandlerEntries.erase(i);
            }
            else if (!eachEntry->isPaused())
            {
                eachEntry->getTimer()->update(dt);
            }
        }
    }
#endif
    //
    // Functions allocated from another thread
    //
    // 上面都是对象的定时任务,这里是多线程处理函数的定时任务。
    // Testing size is faster than locking / unlocking.
    // And almost never there will be functions scheduled to be called.
    if( !_functionsToPerform.empty() ) {
        _performMutex.lock();
        // fixed #4123: Save the callback functions,they must be invoked after '_performMutex.unlock()',otherwise if new functions are added in callback,it will cause thread deadlock.
        auto temp = _functionsToPerform;
        _functionsToPerform.clear();
        _performMutex.unlock();
        for( const auto &function : temp ) {
            function();
        }
        
    }
}


再看看unschedule函数:

void Scheduler::unschedule(SEL_SCHEDULE selector,Ref *target)
{
    // explicity handle nil arguments when removing an object
    if (target == nullptr || selector == nullptr)
    {
        return;
    }
    
    //CCASSERT(target);
    //CCASSERT(selector);
    
    //对象定时器列表_hashForTimers里找是否有 target 对象
    tHashTimerEntry *element = nullptr;
    HASH_FIND_PTR(_hashForTimers,element);
    
    if (element)
    {
        for (int i = 0; i < element->timers->num; ++i)
        {
            TimerTargetSelector *timer = static_cast<TimerTargetSelector*>(element->timers->arr[i]);
             //如果正在执行的Timer是需要被unschedule的timer,将其移除并且标识当前正在执行的Timer需要被移除状态为true。  
            /*
                在 对象定时器列表_hashForTimers里找是否有 target 对象
                在找到了target对象的条件下,对target装载的timers进行逐一遍历
                遍历过程 比较当前遍历到的定时器的 selector是等于传入的 selctor
                将找到的定时器从element->timers里删除。重新设置timers列表里的 计时器的个数。
                最后_currentTarget 与 element的比较值来决定是否从_hashForTimers 将其删除。
            */

            if (selector == timer->getSelector())
            {
                if (timer == element->currentTimer && (! element->currentTimerSalvaged))
                {
                    element->currentTimer->retain();
                    element->currentTimerSalvaged = true;
                }
                
                ccArrayRemoveObjectAtIndex(element->timers,i,true);
                
                // update timerIndex in case we are in tick:,looping over the actions
                if (element->timerIndex >= i)
                {
                    element->timerIndex--;
                }
                
                //当前timers中不再含有timer。但是如果正在执行的target是该target,则将正在执行的target将被清除标识为true  
                //否则,可以直接将其从hash中移除 
                if (element->timers->num == 0)
                {
                    if (_currentTarget == element)
                    {
                        _currentTargetSalvaged = true;
                    }
                    else
                    {
                        removeHashElement(element);
                    }
                }
                
                return;
            }
        }
    }
}

我们按函数过程看,怎么来卸载定时器的。

  • 参数为一个回调函数指针和一个Ref 对象指针。

  • 在 对象定时器列表_hashForTimers里找是否有 target 对象

  • 在找到了target对象的条件下,对target装载的timers进行逐一遍历

  • 遍历过程 比较当前遍历到的定时器的 selector是等于传入的 selctor

  • 将找到的定时器从element->timers里删除。重新设置timers列表里的 计时器的个数。

  • 最后_currentTarget 与 element的比较值来决定是否从_hashForTimers 将其删除。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


    本文实践自 RayWenderlich、Ali Hafizji 的文章《How To Create Dynamic Textures with CCRenderTexture in Cocos2D 2.X》,文中使用Cocos2D,我在这里使用Cocos2D-x 2.1.4进行学习和移植。在这篇文章,将会学习到如何创建实时纹理、如何用Gimp创建无缝拼接纹
Cocos-code-ide使用入门学习地点:杭州滨江邮箱:appdevzw@163.com微信公众号:HopToad 欢迎转载,转载标注出处:http://blog.csdn.netotbaron/article/details/424343991.  软件准备 下载地址:http://cn.cocos2d-x.org/download 2.  简介2.1         引用C
第一次開始用手游引擎挺激动!!!进入正题。下载资源1:从Cocos2D-x官网上下载,进入网页http://www.cocos2d-x.org/download,点击Cocos2d-x以下的Download  v3.0,保存到自定义的文件夹2:从python官网上下载。进入网页https://www.python.org/downloads/,我当前下载的是3.4.0(当前最新
    Cocos2d-x是一款强大的基于OpenGLES的跨平台游戏开发引擎,易学易用,支持多种智能移动平台。官网地址:http://cocos2d-x.org/当前版本:2.0    有很多的学习资料,在这里我只做为自己的笔记记录下来,错误之处还请指出。在VisualStudio2008平台的编译:1.下载当前稳
1.  来源 QuickV3sample项目中的2048样例游戏,以及最近《最强大脑》娱乐节目。将2048改造成一款挑战玩家对数字记忆的小游戏。邮箱:appdevzw@163.com微信公众号:HopToadAPK下载地址:http://download.csdn.net/detailotbaron/8446223源码下载地址:http://download.csdn.net/
   Cocos2d-x3.x已经支持使用CMake来进行构建了,这里尝试以QtCreatorIDE来进行CMake构建。Cocos2d-x3.X地址:https://github.com/cocos2d/cocos2d-x1.打开QtCreator,菜单栏→"打开文件或项目...",打开cocos2d-x目录下的CMakeLists.txt文件;2.弹出CMake向导,如下图所示:设置
 下载地址:链接:https://pan.baidu.com/s/1IkQsMU6NoERAAQLcCUMcXQ提取码:p1pb下载完成后,解压进入build目录使用vs2013打开工程设置平台工具集,打开设置界面设置: 点击开始编译等待编译结束编译成功在build文件下会出现一个新文件夹Debug.win32,里面就是编译
分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!http://www.captainbed.net前言上次用象棋演示了cocos2dx的基本用法,但是对cocos2dx并没有作深入的讨论,这次以超级马里奥的源代码为线索,我们一起来学习超级马里奥的实
1. 圆形音量button事实上作者的本意应该是叫做“电位计button”。可是我觉得它和我们的圆形音量button非常像,所以就这么叫它吧~先看效果:好了,不多解释,本篇到此为止。(旁白: 噗。就这样结束了?)啊才怪~我们来看看代码:[cpp] viewplaincopyprint?CCContro
原文链接:http://www.cnblogs.com/physwf/archive/2013/04/26/3043912.html为了进一步深入学习贯彻Cocos2d,我们将自己写一个场景类,但我们不会走的太远,凡是都要循序渐进,哪怕只前进一点点,那也至少是前进了,总比贪多嚼不烂一头雾水的好。在上一节中我们建
2019独角兽企业重金招聘Python工程师标准>>>cocos2d2.0之后加入了一种九宫格的实现,主要作用是用来拉伸图片,这样的好处在于保留图片四个角不变形的同时,对图片中间部分进行拉伸,来满足一些控件的自适应(PS: 比如包括按钮,对话框,最直观的形象就是ios里的短信气泡了),这就要求图
原文链接:http://www.cnblogs.com/linji/p/3599478.html1.环境和工具准备Win7VS2010/2012,至于2008v2版本之后似乎就不支持了。 2.安装pythonv.2.0版本之前是用vs模板创建工程的,到vs2.2之后就改用python创建了。到python官网下载版本2.7.5的,然后
环境:ubuntu14.04adt-bundle-linux-x86_64android-ndk-r9d-linux-x86_64cocos2d-x-3.0正式版apache-ant1.9.3python2.7(ubuntu自带)加入环境变量exportANDROID_SDK_ROOT=/home/yangming/adt-bundle-linux/sdkexportPATH=${PATH}:/$ANDROID_SDK_ROOTools/export
1开发背景游戏程序设计涉及了学科中的各个方面,鉴于目的在于学习与进步,本游戏《FlappyBird》采用了两个不同的开发方式来开发本款游戏,一类直接采用win32底层API来实现,另一类采用当前火热的cocos2d-x游戏引擎来开发本游戏。2需求分析2.1数据分析本项目要开发的是一款游
原文链接:http://www.cnblogs.com/linji/p/3599912.html//纯色色块控件(锚点默认左下角)CCLayerColor*ccc=CCLayerColor::create(ccc4(255,0,0,128),200,100);//渐变色块控件CCLayerGradient*ccc=CCLayerGradient::create(ccc4(255,0,0,
原文链接:http://www.cnblogs.com/linji/p/3599488.html//载入一张图片CCSprite*leftDoor=CCSprite::create("loading/door.png");leftDoor->setAnchorPoint(ccp(1,0.5));//设置锚点为右边中心点leftDoor->setPosition(ccp(240,160));/
为了答谢广大学员对智捷课堂以及关老师的支持,现购买51CTO学院关老师的Cocos2d-x课程之一可以送智捷课堂编写图书一本(专题可以送3本)。一、Cocos2d-x课程列表:1、Cocos2d-x入门与提高视频教程__Part22、Cocos2d-x数据持久化与网络通信__Part33、Cocos2d-x架构设计与性能优化内存优
Spawn让多个action同时执行。Spawn有多种不同的create方法,最终都调用了createWithTwoActions(FiniteTimeAction*action1,FiniteTimeAction*action2)方法。createWithTwoActions调用initWithTwoActions方法:对两个action变量初始化:_one=action1;_two=action2;如果两个a
需要环境:php,luajit.昨天在cygwin上安装php和luajit环境,这真特么是一个坑。建议不要用虚拟环境安装打包环境,否则可能会出现各种莫名问题。折腾了一下午,最终将环境转向linux。其中,luajit的安装脚本已经在quick-cocos2d-x-develop/bin/中,直接luajit_install.sh即可。我的lin
v3.0相对v2.2来说,最引人注意的。应该是对触摸层级的优化。和lambda回调函数的引入(嗯嗯,不枉我改了那么多类名。话说,每次cocos2dx大更新。总要改掉一堆类名函数名)。这些特性应该有不少人研究了,所以今天说点跟图片有关的东西。v3.0在载入图片方面也有了非常大改变,仅仅只是