非个人的全部理解,部分摘自cocos官网教程,感谢cocos官网。



在<CCScheduler.h>头文件中,定义了关于调度器的五个类:Timer,TimerTargetSelector,TimerTargetCallback,TimerScriptHandler和Scheduler,Timer和Scheduler直接继承于Ref类,TimerTargetSelector,TimerTargetCallback和TimerScriptHandler继承自Timer类。


先看看Timer类:

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; // 时间间隔
};

类中定义了一个Scheduler指针变量 _scheduler,根据注释可以看出,这是一个弱引用,弱引用不会增加它所引用的对象的引用计数;

根据分析Timer类的成员变量,可以知道这是一个用来描述计时器的类;

  1. 每隔 _interval 来触发一次;

  2. _useDelay可以设置定时器触是否使用延迟; _delay是延迟时间;

  3. _repeat可以设置定时器触发的次数; _runforever设置定时器永远执行。

然后看一下Timer类的update函数:

void Timer::update(float dt) //参数dt表示距离上一次update调用的时间间隔
{
    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,这里可能会有一小点的问题,因为 _elapsed值有可能大于_interval这里没有做冗余处理,所以会吞掉一些时间,比如 1秒执行一次,而10秒内可能执行的次数小于10,吞掉多少与update调用的频率有关系。
            }
        }    
        else
        {//advanced usage
            _elapsed += dt;
            if (_useDelay)
            {
                if( _elapsed >= _delay )
                {
                    trigger();
                     
                    _elapsed = _elapsed - _delay;//延迟执行的计算
                    _timesExecuted += 1;
                    _useDelay = false; //延迟已经过了,清除_useDelay标记。
                }
            }
            else
            {
                if (_elapsed >= _interval)
                {
                    trigger();
                     
                    _elapsed = 0;
                    _timesExecuted += 1;
 
                }
            }
 
            if (!_runForever && _timesExecuted > _repeat)//触发的次数已经满足了_repeat的设置就取消定时器。
            {    //unschedule timer
                cancel();
            }
        }
    }
}

在update函数里,调用了trigger和cancel函数,trigger是触发函数,cancel是取消定时器。


然后继续看<Scheduler.h>,是TimerTargetSelector,它继承自Timer:

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; // 关联一个Ref对象,应该为执行定时器的对象
    SEL_SCHEDULE _selector; // _selector是一个函数,是定时器触发时的回调函数
};

然后看看TimerTargetSelector的trigger和cancel函数,它重载了父类Timer的同名虚函数:
void TimerTargetSelector::trigger()
{
    if (_target && _selector)
    {
        (_target->*_selector)(_elapsed);
    }
}

void TimerTargetSelector::cancel()
{
    _scheduler->unschedule(_selector,_target);
}

可以看出,在trigger中,执行了_selector这个回调函数,cancel函数则调用了unscheduler函数来结束,稍后分析。


继续看下一个类TimerTargetCallback:

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);
    
    /**
     * @js NA
     * @lua NA
     */
    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;
    ccSchedulerFunc _callback;
    std::string _key;
};

TimerTargetCallback 和TimerTargetSelector类似,然后可以看看它的trigger和cancel:
void TimerTargetCallback::trigger()
{
    if (_callback)
    {
        _callback(_elapsed);
    }
}

void TimerTargetCallback::cancel()
{
    _scheduler->unschedule(_key,_target);
}

trigger就是调用了回调函数并将_elapsed传进去,cancel和TimerTargetSelector和cancel一样。

然后还有个跟脚本相关的,暂时不会~


然后现在可以看看Scheduler类了,在Scheduler之前声明了几种结构体:

struct _listEntry;
struct _hashSelectorEntry;
struct _hashUpdateEntry;

#if CC_ENABLE_SCRIPT_BINDING
class SchedulerScriptHandlerEntry;

估计和Scheduler的数据结构有关,接着看看Scheduler的数据:
    float _timeScale; // 速度控制,1.0f为正常速度,大于1表示快放,小于1表示慢放
    //
    // "updates with priority" stuff
    //
    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;
    struct _hashSelectorEntry *_currentTarget;
    bool _currentTargetSalvaged;
    // If true unschedule will not remove anything from a hash. Elements will only be marked for deletion.
    bool _updateHashLocked;
    
#if CC_ENABLE_SCRIPT_BINDING
    Vector<SchedulerScriptHandlerEntry*> _scriptHandlerEntries;
#endif
    
    // Used for "perform Function"
    std::vector<std::function<void()>> _functionsToPerform;
    std::mutex _performMutex;

Scheduler定义了一些和调度器相关的一些“容器”,后面慢慢分析。

来看看Scheduler的构造和析构函数:

Scheduler::Scheduler(void)
: _timeScale(1.0f),_updatesNegList(nullptr),_updates0List(nullptr),_updatesPosList(nullptr),_hashForUpdates(nullptr),_hashForTimers(nullptr),_currentTarget(nullptr),_currentTargetSalvaged(false),_updateHashLocked(false)
#if CC_ENABLE_SCRIPT_BINDING,_scriptHandlerEntries(20)
#endif
{
    // I don't expect to have more than 30 functions to all per frame
    _functionsToPerform.reserve(30);
}

Scheduler::~Scheduler(void)
{
    unscheduleAll();
}
在构造函数中,官方给出的建议是每帧中的回调函数个数不要超过30个。析构函数中调用了uncheduleAll。


然后看看Scheduler中非常重要的函数schedule,它有几个重载版本:

void Scheduler::schedule(SEL_SCHEDULE selector,Ref *target,float interval,float delay,bool paused)
{
    CCASSERT(target,"Argument target must be non-nullptr");
    
    tHashTimerEntry *element = nullptr;
    HASH_FIND_PTR(_hashForTimers,&target,element);
    
    if (! element)
    {
        element = (tHashTimerEntry *)calloc(sizeof(*element),1);
        element->target = target;
        
        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,"");
    }
    
    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;
            }
        }
        ccArrayEnsureExtraCapacity(element->timers,1);
    }
    
    TimerTargetSelector *timer = new (std::nothrow) TimerTargetSelector();
    timer->initWithSelector(this,selector,interval,repeat,delay);
    ccArrayAppendobject(element->timers,timer);
    timer->release();
}


先调用了 HASH_FIND_PTR(_hashForTimers,element); 这行代码的含义是在 _hashForTimers 这个数组中找与&target相等的元素,用element来返回。
而_hashForTimers是一个链表。
接下来的if判断是判断element的值,看看是不是已经在_hashForTimers链表里面,如果不在那么分配内存创建了一个新的结点并且设置了pause状态;

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

ccArrayEnsureExtraCapacity(element->timers,1);

这行代码是给 ccArray分配内存,确定能再容纳一个timer。
函数的最后四行代码,就是创建了一个新的 TimerTargetSelector 对象,并且对其赋值 还加到了 定时器列表里。
这里注意,调用了 timer->release() 减少了一次引用,不会造成timer被释放。看一下ccArrayAppendobject方法后, 知道里面已经对 timer进行了一次retain操作所以 调用了一次release后保证 timer的引用计数为1。


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

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

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

void Scheduler::schedule(SEL_SCHEDULE selector,bool paused)
{
    this->schedule(selector,CC_REPEAT_FOREVER,0.0f,paused);
}

这个重载版本和上一个基本类似,不同的只是设置它的执行次数为永久执行。


接着看另一个重载版本:

void Scheduler::schedule(const ccSchedulerFunc& callback,bool paused,const std::string& key)
{
    CCASSERT(target,"Argument target must be non-nullptr");
    CCASSERT(!key.empty(),"key should not be empty!");

    tHashTimerEntry *element = nullptr;
    HASH_FIND_PTR(_hashForTimers,element);

    if (! element)
    {
        element = (tHashTimerEntry *)calloc(sizeof(*element),1);
        element->target = target;

        HASH_ADD_PTR(_hashForTimers,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,"");
    }

    if (element->timers == nullptr)
    {
        element->timers = ccArrayNew(10);
    }
    else 
    {
        for (int i = 0; i < element->timers->num; ++i)
        {
            TimerTargetCallback *timer = dynamic_cast<TimerTargetCallback*>(element->timers->arr[i]);

            if (timer && key == timer->getKey())
            {
                cclOG("CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f",interval);
                timer->setInterval(interval);
                return;
            }        
        }
        ccArrayEnsureExtraCapacity(element->timers,1);
    }

    TimerTargetCallback *timer = new (std::nothrow) TimerTargetCallback();
    timer->initWithCallback(this,callback,key,timer);
    timer->release();
}

这个重载版本跟上个基类相同,只是它使用的是void* target,上个重载版本使用的是Ref*版本, 因此这个重载版本可以自定义调度器,因此才使用了TimerTargetCallback。

小结:

Ref和非Ref类型对象的定时器处理基本一样,都加到了调度控制器的_hashFroTimers链表里;

调用schedule方法将指定的对象与回调函数做为参数加到schedule定时器列表里面,加入时会判断是否重复添加。


接下来看看几种unschedule方法 ,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);
    
    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]);
            
            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--;
                }
                
                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 将其删除。

这些代码过程还是很好理解的,不过程小鱼在看这几行代码的时候有一个问题还没看明白,就是用到了_currentTarget 与 _currentTargetSalvaged 这两个变量,它们的作用是什么呢?下面我们带着这个问题来找答案。


再看另一个unschedule重载版本,基本都是大同小异,都是执行了这几个步骤,只是查找的参数从 selector变成了 std::string &key 对象从 Ref类型变成了void*类型。


现在来看一下update实现,每帧都会调用该函数,它是引擎驱动的”灵魂“:
void Scheduler::update(float dt)
{
    _updateHashLocked = true;// 这里加了一个状态锁,应该是线程同步的作用。
 
    if (_timeScale != 1.0f)
    {
        dt *= _timeScale;// 时间速率调整,根据设置的_timeScale 进行了乘法运算。
    }
 
    //
    // Selector callbacks
    //
 
    // 定义了两个链表遍历的指针。
    tListEntry *entry,*tmp;
 
    // 处理优先级小于0的定时器,这些定时器存在了_updatesNegList链表里面,具体怎么存进来的,在后面继续分析
    DL_FOREACH_SAFE(_updatesNegList,entry,tmp)
    {
        if ((! entry->paused) && (! entry->markedForDeletion))
        {
            entry->callback(dt);// 对活动有效的定时器执行回调。 
        }
    }
 
    // 处理优先级为0的定时器。
    DL_FOREACH_SAFE(_updates0List,tmp)
    {
        if ((! entry->paused) && (! entry->markedForDeletion))
        {
            entry->callback(dt);
        }
    }
 
    // 处理优先级大于0的定时器
    DL_FOREACH_SAFE(_updatesPosList,tmp)
    {
        if ((! entry->paused) && (! entry->markedForDeletion))
        {
            entry->callback(dt);
        }
    }
 
    // 遍历_hashForTimers里自定义的计时器对象列表
    for (tHashTimerEntry *elt = _hashForTimers; elt != nullptr; )
    {
        _currentTarget = elt;// 这里通过遍历动态设置了当前_currentTarget对象。
        _currentTargetSalvaged = false;// 当前目标定时器没有被处理过标记。
 
        if (! _currentTarget->paused)
        {
            // 遍历每一个对象的定时器列表 
            for (elt->timerIndex = 0; elt->timerIndex < elt->timers->num; ++(elt->timerIndex))
            {
                elt->currentTimer = (Timer*)(elt->timers->arr[elt->timerIndex]);// 这里更新了对象的currentTimer
                elt->currentTimerSalvaged = false;
 
                elt->currentTimer->update(dt);// 执行定时器过程。
 
                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.
                   // currentTimerSalvaged的作用是标记当前这个定时器是否已经失效,在设置失效的时候我们对定时器增加过一次引用记数,这里调用release来减少那次引用记数,这样释放很安全,这里用到了这个小技巧,延迟释放,这样后面的程序不会出现非法引用定时器指针而出现错误
                    elt->currentTimer->release();
                }
                // currentTimer指针使用完了,设置成空指针
                elt->currentTimer = nullptr;
            }
        }
 
        // elt,at this moment,is still valid
        // so it is safe to ask this here (issue #490)
        // 因为下面有可能要清除这个对象currentTarget为了循环进行下去,这里先在currentTarget对象还存活的状态下找到链表的下一个指针。
        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);
        }
    }
 
    // 下面这三个循环也是清理工作
    // 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
    //
    // 上面都是对象的定时任务,这里是多线程处理函数的定时任务。
    //
 
    // 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();
        }
         
    }
}

在整个函数中:

_currentTarget实在update主循环过程中用来标记当前执行到哪个target对象;

_currentTargetSalvaged是标记_currentTarget是否需要进行清除操作的变量。


在Scheduler中有一个scheduleUpdate函数,什么时候调用这个呢,帧帧调用时会调用这个,来看看Node中的两个函数:

void scheduleUpdate(void);
void scheduleUpdateWithPriority(int priority);

在Node定义默认都是0级别的结点,这两个函数最后还是调用了Scheduler中的scheduleUpdate函数:
void scheduleUpdate(T *target,int priority,bool paused)
{
    this->schedulePerFrame([target](float dt){
        target->update(dt);
    },priority,paused);
}

然后来看看schedulePerFrame函数都做了些什么:
void Scheduler::schedulePerFrame(const ccSchedulerFunc& callback,bool paused)
{
    tHashUpdateEntry *hashElement = nullptr;
    HASH_FIND_PTR(_hashForUpdates,hashElement);
    if (hashElement)
    {
        // check if priority has changed
        if ((*hashElement->list)->priority != priority)
        {
            if (_updateHashLocked)
            {
                cclOG("warning: you CANNOT change update priority in scheduled function");
                hashElement->entry->markedForDeletion = false;
                hashElement->entry->paused = paused;
                return;
            }
            else
            {
            	// will be added again outside if (hashElement).
                unscheduleUpdate(target);
            }
        }
        else
        {
            hashElement->entry->markedForDeletion = false;
            hashElement->entry->paused = paused;
            return;
        }
    }

    // most of the updates are going to be 0,that's way there
    // is an special list for updates with priority 0
    if (priority == 0)
    {
        appendIn(&_updates0List,paused);
    }
    else if (priority < 0)
    {
        priorityIn(&_updatesNegList,paused);
    }
    else
    {
        // priority > 0
        priorityIn(&_updatesPosList,paused);
    }
}

在这里可以看出将调度器添加到相应权限的列表中。


Scheduler类在官方文档的帮助下就分析了这么多,但是我知道我还是初步了解了调度器的工作原理,以后会更加努力的!

【Cocos2d-x 3.x】 调度器Scheduler类源码分析的更多相关文章

  1. HTML实现代码雨源码及效果示例

    这篇文章主要介绍了HTML实现代码雨源码及效果示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  2. Html5 滚动穿透的方法

    这篇文章主要介绍了Html5 滚动穿透的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  3. 使用layui实现左侧菜单栏及动态操作tab项的方法

    这篇文章主要介绍了使用layui实现左侧菜单栏及动态操作tab项的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  4. HTML5 拖放(Drag 和 Drop)详解与实例代码

    本篇文章主要介绍了HTML5 拖放(Drag 和 Drop)详解与实例代码,具有一定的参考价值,有兴趣的可以了解一下

  5. 跨域修改iframe页面内容详解

    这篇文章主要介绍了跨域修改iframe页面内容详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  6. ios – Xcode找不到Alamofire,错误:没有这样的模块’Alamofire’

    我正在尝试按照github(https://github.com/Alamofire/Alamofire#cocoapods)指令将Alamofire包含在我的Swift项目中.我创建了一个新项目,导航到项目目录并运行此命令sudogeminstallcocoapods.然后我面临以下错误:搜索后我设法通过运行此命令安装cocoapodssudogeminstall-n/usr/local/bin

  7. ios – 仅在异步函数完成执行后运行代码

    所以,例如:如果问题是你不知道要调用什么函数,你可以配置你周围的函数/对象,这样有人可以给你一个函数,然后你在我上面说“调用函数”的地方调用你的函数.例如:

  8. ios – 如何使用Objective C类中的多个参数调用Swift函数?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  9. ios – 暂停调度队列是否会暂停其目标队列?

    我想创建两个串行队列A&B.队列B是队列A的目标.我想在B上排队一些块,并暂停它直到我准备执行它们,但是我想继续在队列A上执行块.如果我暂停B,这还会暂停它的目标队列(队列A)吗?我的想法是,我想安排这些特定的块在稍后日期执行但是我不希望它们同时运行而我不这样做想要处理信号量.但我希望队列A继续处理它的块,而B则被暂停如果不清楚这里是一些示例代码解决方法queueB被挂起,但queueA未被挂起.queueA和queueB被挂起.

  10. iOS 7,用于断开调用的私有API CTCallDisconnect不起作用

    谢谢!

随机推荐

  1. 【cocos2d-x 3.x 学习笔记】对象内存管理

    Cocos2d-x的内存管理cocos2d-x中使用的是上面的引用计数来管理内存,但是又增加了一些自己的特色。cocos2d-x中通过Ref类来实现引用计数,所有需要实现内存自动回收的类都应该继承自Ref类。下面是Ref类的定义:在cocos2d-x中创建对象通常有两种方式:这两中方式的差异可以参见我另一篇博文“对象创建方式讨论”。在cocos2d-x中提倡使用第二种方式,为了避免误用第一种方式,一般将构造函数设为protected或private。参考资料:[1]cocos2d-x高级开发教程2.3节[

  2. 利用cocos2dx 3.2开发消灭星星六如何在cocos2dx中显示中文

    由于编码的不同,在cocos2dx中的Label控件中如果放入中文字,往往会出现乱码。为了方便使用,我把这个从文档中获取中文字的方法放在一个头文件里面Chinese.h这里的tex_vec是cocos2dx提供的一个保存文档内容的一个容器。这里给出ChineseWords,xml的格式再看看ChineseWord的实现Chinese.cpp就这样,以后在需要用到中文字的地方,就先include这个头文件然后调用ChineseWord函数,获取一串中文字符串。

  3. 利用cocos2dx 3.2开发消灭星星七关于星星的算法

    在前面,我们已经在GameLayer中利用随机数初始化了一个StarMatrix,如果还不知道怎么创建星星矩阵请回去看看而且我们也讲了整个游戏的触摸事件的派发了。

  4. cocos2dx3.x 新手打包APK注意事项!

    这个在编译的时候就可以发现了比较好弄这只是我遇到的,其他的以后遇到再补充吧。。。以前被这两个问题坑了好久

  5. 利用cocos2dx 3.2开发消灭星星八游戏的结束判断与数据控制

    如果你看完之前的,那么你基本已经拥有一个消灭星星游戏的雏形。开始把剩下的两两互不相连的星星消去。那么如何判断是GameOver还是进入下一关呢。。其实游戏数据贯穿整个游戏,包括星星消除的时候要加到获得分数上,消去剩下两两不相连的星星的时候的加分政策等,因此如果前面没有做这一块的,最好回去搞一搞。

  6. 利用cocos2dx 3.2开发消灭星星九为游戏添加一些特效

    needClear是一个flag,当游戏判断不能再继续后,这个flag变为true,开始消除剩下的星星clearSumTime是一个累加器ONE_CLEAR_TIME就是每颗星星消除的时间2.连击加分信息一般消除一次星星都会有连击信息和加多少分的信息。其实这些combo标签就是一张图片,也是通过控制其属性或者runAction来实现。源码ComboEffect.hComboEffect.cpp4.消除星星粒子效果消除星星时,为了实现星星爆裂散落的效果,使用了cocos2d提供的粒子特效引擎对于粒子特效不了

  7. 02 Cocos2D-x引擎win7环境搭建及创建项目

    官网有搭建的文章,直接转载记录。环境搭建:本文介绍如何搭建Cocos2d-x3.2版本的开发环境。项目创建:一、通过命令创建项目前面搭建好环境后,怎样创建自己的Cocos2d-x项目呢?先来看看Cocos2d-x3.2的目录吧这就是Cocos2d-x3.2的目录。输入cocosnew项目名–p包名–lcpp–d路径回车就创建成功了例如:成功后,找到这个项目打开proj.win32目录下的Hello.slnF5成功了。

  8. 利用cocos2dx 3.2开发消灭星星十为游戏添加音效项目源码分享

    一个游戏,声音也是非常的重要,其实cocos2dx里面的简单音效引擎的使用是非常简单的。我这里只不过是用一个类对所有的音效进行管理罢了。Audio.hAudio.cpp好了,本系列教程到此结束,第一次写教程如有不对请见谅或指教,谢谢大家。最后附上整个项目的源代码点击打开链接

  9. 03 Helloworld

    程序都有一个入口点,在C++就是main函数了,打开main.cpp,代码如下:123456789101112131415161718#include"main.h"#include"AppDelegate.h"#include"cocos2d.h"USING_NS_CC;intAPIENTRY_tWinMain{UNREFERENCED_ParaMETER;UNREFERENCED_ParaMETER;//createtheapplicationinstanceAppDelegateapp;return

  10. MenuItemImage*图标菜单创建注意事项

    学习cocos2dx,看的是cocos2d-x3.x手游开发实例详解,这本书错误一大把,本着探索求知勇于发现错误改正错误的精神,我跟着书上的例子一起调试,当学习到场景切换这个小节的时候,出了个错误,卡了我好几个小时。

返回
顶部