Cocos2d-x   发布时间:2022-05-03  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了cocos2d-x 3.7 源码分析 EventDispatcher大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
#ifndef __CC_TOUCH_H__
#define __CC_TOUCH_H__

#include "base/CCRef.h"
#include "math/CCGeometry.h"

NS_CC_BEGIN



/** @class Touch
 * @brief Encapsulates the Touch information,such as touch point,id and so on,and provides the methods that commonly used.
 */
class CC_DLL Touch : public Ref
{
public:
    /** 
     * Dispatch mode,how the touches are dispathced.
     * @js NA
     */
    enum class DispatchMode
    {
        all_AT_ONCE,/** All at once. */
        ONE_BY_ONE,/** One by one. */
    };

    // 构造函数.cocos2dx源码中 void GLView::handleTouchesBegin(int num,intptr_t ids[],float xs[],float ys[])调用过。
    Touch() 
    : _id(0),_startPointCaptured(false)
    {
    
    }
    
    /** Set the touch infomation. It always used to monitor touch event.
     *
     * @param id A given id
     * @param x A given x coordinate.
     * @param y A given y coordinate.
     */
    void setTouchInfo(int id,float x,float y)
    {
        _id = id;
        _prevPoint = _point;
        _point.x   = x;
        _point.y   = y;
        
        if (!_startPointCaptured)   // 首次设置touchInfo,startPoint、prevPoint 与 point相等。
        {
            _startPoint = _point;
            _startPointCaptured = true;
            _prevPoint = _point;
        }
    }
    
    // in openGL coordinates.  (开始坐标,前一个坐标,当前坐标)
    Vec2 getStartLOCATIOn() const;
    Vec2 getPreviousLOCATIOn() const;
    Vec2 getLOCATIOn() const;


    // in screen coordinates.  (开始坐标,前一个坐标,当前坐标)
    Vec2 getStartLOCATIOnInView() const;
    Vec2 getPreviousLOCATIOnInView() const;
    Vec2 getLOCATIOnInView() const;
    
    
    
    // 当前坐标和前一个坐标之间的差值。
    Vec2 getDelta() const;
    
    
    /** Get touch id.
     * @js getId
     * @lua getId
     *
     * @return The id of touch.
     */
    int getID() const
    {
        return _id;
    }

private:
    int _id;                    // id
    bool _startPointCaptured;   // 是否捕获过开始点
    Vec2 _startPoint;           // 开始点
    Vec2 _point;                // 当前点
    Vec2 _prevPoint;            // 上一次点坐标
};



NS_CC_END

#endif
#include "base/CCTouch.h"
#include "base/CCDirector.h"

NS_CC_BEGIN

// returns the current touch LOCATIOn in screen coordinates
Vec2 Touch::getLOCATIOnInView() const 
{ 
    return _point; 
}

// returns the previous touch LOCATIOn in screen coordinates
Vec2 Touch::getPreviousLOCATIOnInView() const 
{ 
    return _prevPoint; 
}

// returns the start touch LOCATIOn in screen coordinates
Vec2 Touch::getStartLOCATIOnInView() const 
{ 
    return _startPoint; 
}

// returns the current touch LOCATIOn in OpenGL coordinates
Vec2 Touch::getLOCATIOn() const
{ 
    return Director::geTinstance()->convertToGL(_point); 
}

// returns the previous touch LOCATIOn in OpenGL coordinates
Vec2 Touch::getPreviousLOCATIOn() const
{ 
    return Director::geTinstance()->convertToGL(_prevPoint);  
}

// returns the start touch LOCATIOn in OpenGL coordinates
Vec2 Touch::getStartLOCATIOn() const
{ 
    return Director::geTinstance()->convertToGL(_startPoint);  
}

// returns the delta position between the current LOCATIOn and the previous LOCATIOn in OpenGL coordinates
Vec2 Touch::getDelta() const
{     
    return getLOCATIOn() - getPreviousLOCATIOn();
}

NS_CC_END

Touch类相对比较简单。

待解决问题:

1.Touch对象在CCGLview的创建,以及整个流程。

2.Director::geTinstance()->convertToGL(_point); 坐标转换问题。

#ifndef __CCEVENT_H__
#define __CCEVENT_H__

#include "base/CCRef.h"
#include "platform/CCPlatformMacros.h"


NS_CC_BEGIN

class Node;


// 所有事件的基类
class CC_DLL Event : public Ref
{
public:
    // 内部枚举类Type(使用方法,Event::Type::TOUCH)
    enum class Type
    {
        TOUCH,KEYBOARD,ACCELERATION,MOUSE,FOCUS,GAME_CONTROLLER,CUSTOM
    };
    
public:
    Event(Type typE);
    virtual ~Event();

    // 类型
	inline Type getType() const { return _type; };
    
    // 停止传播事件
    inline void stopPropagation() { _isStopped = true; };
    
    // 事件是否停止
    inline bool isStopped() const { return _isStopped; };
    
    // 与node关联的事件才有用,fixedPriority 返回0.(It onlys be available when the event listener is associated with node. It returns 0 when the listener is associated with fixed priority.)
    inline Node* getCurrentTarget() { return _currentTarget; };
    
protected:
    inline void setCurrentTarget(Node* target) { _currentTarget = target; };
    

	Type _type;             // 描述当前对象的事件类型。
    bool _isStopped;        // 描述当前事件是否已经停止。
    Node* _currentTarget;   // 是侦听事件的Node类型的对象。
    
    friend class EventDispatcher;
};

NS_CC_END

#endif

#include "base/CCEvent.h"

NS_CC_BEGIN

Event::Event(Type typE)
: _type(typE),_isStopped(false),_currentTarget(nullptr)
{
}

Event::~Event()
{
}


NS_CC_END
Event类是事件类的基类,有3个重要属性:

1.target

2.type

3.isStopped

#ifndef __cocos2d_libs__TouchEvent__
#define __cocos2d_libs__TouchEvent__

#include "base/CCEvent.h"
#include <vector>


NS_CC_BEGIN

class Touch;

#define TOUCH_PERF_DEBUG 1

class CC_DLL EventTouch : public Event
{
public:
    static const int MAX_TOUCHES = 15;  // 事件的最大Touch个数。
    
    // 内部枚举类EventCode。在dispatcher event时会用到。
    enum class EventCode
    {
        BEGAN,MOVED,ENDED,CANCELLED
    };

    EventTouch();

    inline EventCode getEventCode() const { return _eventCode; };
    
    // 当前触摸屏幕所有点的列表
    inline const std::vector<Touch*>& getTouches() const { return _touches; };

    // 测试相关方法
#if TOUCH_PERF_DEBUG
    void setEventCode(EventCode eventCodE) { _eventCode = eventCode; };
    void setTouches(const std::vector<Touch*>& touches) { _touches = touches; };
#endif
    
private:
    EventCode _eventCode;           // 事件code(began,moved,ended,cancelled)
    std::vector<Touch*> _touches;   // touch数组 获取当前触摸屏幕所有点的列表

    friend class GLView;
};


NS_CC_END


#endif
#include "base/CCEventTouch.h"
#include "base/CCTouch.h"

NS_CC_BEGIN

EventTouch::EventTouch()
: Event(Type::TOUCH)
{
    _touches.reserve(MAX_TOUCHES);
}

NS_CC_END
EventTouch类继承自Event,加上Event的3个属性,EventTouch共有5个属性:

1.target

2.type

3.isStopped

4.touches:当前触摸屏幕所有点的列表

5.eventCode:该属性在dispatcher中会用到,后面分析。


#ifndef __cocos2d_libs__CCCustomEvent__
#define __cocos2d_libs__CCCustomEvent__

#include <String>
#include "base/CCEvent.h"


NS_CC_BEGIN

class CC_DLL EventCustom : public Event
{
public:
    EventCustom(const std::string& eventName);
    
    inline void setUserData(void* data) { _userData = data; };
    inline void* getUserData() const { return _userData; };
    
    inline const std::string& getEventName() const { return _eventName; };
protected:
    void* _userData;        // User data
    std::string _eventName;
};

NS_CC_END

#endif

#include "base/CCEventCustom.h"
#include "base/CCEvent.h"

NS_CC_BEGIN

EventCustom::EventCustom(const std::string& eventName)
: Event(Type::CUSTOM),_userData(nullptr),_eventName(eventName)
{
}

NS_CC_END

EventCustom类继承自Event,加上Event的3个属性,EventCustom共有5个属性:

1.target

2.type

3.isStopped

4.eventName

5.userData

使用EventCustom的地方还是很多的,这里只粗略介绍下。

#ifndef __CCEVENTLISTENER_H__
#define __CCEVENTLISTENER_H__

#include <functional>
#include <String>
#include <memory>

#include "platform/CCPlatformMacros.h"
#include "base/CCRef.h"


NS_CC_BEGIN

class Event;
class Node;

/** @class EventListener
 *  @brief The base class of event listener.
 *  If you need custom listener which with different callBACk,you need to inherit this class.
 *  For instance,you could refer to EventListenerAcceleration,EventListenerKeyboard,EventListenerTouchOneByOne,EventListenerCustom.
 */
class CC_DLL EventListener : public Ref
{
public:
    /** Type Event type.*/  // 这个类型与Event的类型有一小点不同,就是将触摸事件类型分成了 One by One (一个接一个) 与  All At Once (同时一起)两种。
    enum class Type
    {
        UNKNOWN,TOUCH_ONE_BY_ONE,TOUCH_all_AT_ONCE,CUSTOM
    };

    typedef std::string ListenerID;

 
public:
    EventListener();
    bool init(Type t,const ListenerID& listenerID,const std::function<void(Event*)>& callBACk);
    virtual ~EventListener();

    
    // 纯虚函数监测监听器是否可用
    virtual bool checkAvailable() = 0;
    
    // 纯虚函数,因为clone的方式不同,又需要这个功能接口,所以在设置为纯虚函数,使子类必需实现。
    virtual EventListener* clone() = 0;

    // 当EventListener enabled并且not paused时,EventListener才能接受事件。
    inline void setEnabled(bool enabled) { _isEnabled = enabled; };
    inline bool isEnabled() const { return _isEnabled; };

protected:
    /** Gets the type of this listener
     *  @note It's different from `EventType`,e.g. TouchEvent has two kinds of event listeners - EventListenerOneByOne,EventListenerAllAtOnce
     */
    inline Type getType() const { return _type; };
  
    // eventType && listenerID ===> listeners.  EventListenerTouchOneByOne::LISTENER_ID = "__cc_touch_one_by_one"; EventListenerTouchAllAtOnce::LISTENER_ID = "__cc_touch_all_at_once"; (When event is being dispatched,listener ID is used as key for searching listeners according to event type.)
    inline const ListenerID& getListenerID() const { return _listenerID; };
    
    // 注册
    inline void setRegistered(bool registered) { _isRegistered = registered; };
    inline bool isRegistered() const { return _isRegistered; };
    
    /************************************************
     Sets paused state for the listener
     The paused state is only used for scene graph priority listeners.
     `EventDispatcher::resumeAllEventListenersForTarget(nodE)` will set the paused state to `false`,while `EventDispatcher::pauseAllEventListenersForTarget(nodE)` will set it to `true`.
     @note 
            1) Fixed priority listeners will never get paused. If a fixed priority doesn't want to receive events,call `setEnabled(false)` instead.
            2) In `Node`'s onEnter and onExit,the `paused state` of the listeners which associated with that node will be automatically updated.
     
     sceneGraphpriorityListener:
     fixedPriorityListener:不使用_paused属性。
     
     paused状态在Node 的“onEnter”和“onExit”中改变
     ************************************************/
    inline void setPaused(bool paused) { _paused = paused; };
    inline bool isPaused() const { return _paused; };
    
    // 获取与listener相关联的node。fixed priority listeners为nullptr。
    inline Node* getAssociatedNode() const { return _node; };
    inline void setAssociatedNode(Node* nodE) { _node = node; };
   

    // 用于fixed priority listeners,可以设为不为0的值。
    inline void setFixedPriority(int fixedPriority) { _fixedPriority = fixedPriority; };
    inlinE int getFixedPriority() const { return _fixedPriority; };

    
protected:
    Type _type;                             // Event listener type
    ListenerID _listenerID;                 // Event listener ID
    std::function<void(Event*)> _onEvent;   // Event callBACk function
    
    bool _isRegistered;                     // Whether the listener has been added to dispatcher.
    bool _isEnabled;                        // Whether the listener is enabled
    bool _paused;                           // Whether the listener is paused
    
    Node* _node;                            // scene graph based priority
    
    int  _fixedPriority;                    // The higher the number,the higher the priority,0 is for scene graph base priority.
    
    friend class EventDispatcher;
};

NS_CC_END

#endif

#include "base/CCEventListener.h"

NS_CC_BEGIN

EventListener::EventListener()
{}
    
EventListener::~EventListener() 
{
	CCLOGINFO("In the destructor of EventListener. %p",this);
}

bool EventListener::init(Type t,const std::function<void(Event*)>& callBACk)
{
    _onEvent = callBACk;
    _type = t;
    _listenerID = listenerID;
    _isRegistered = false;
    _paused = true;
    _isEnabled = true;
    
    return true;
}

bool EventListener::checkAvailable()
{ 
	return (_onEvent != nullptr);
}

NS_CC_END
EventListener是事件监听器的基类,有8个属性:

1.type

2.listenerID

3.onEvent

4.register

5.paused

6.enabled

7.node

8.fixedPriority

#ifndef __cocos2d_libs__CCTouchEventListener__
#define __cocos2d_libs__CCTouchEventListener__

#include "base/CCEventListener.h"
#include <vector>



NS_CC_BEGIN

class Touch;

// 单点触摸监听器
class CC_DLL EventListenerTouchOneByOne : public EventListener
{
public:
    static const std::string LISTENER_ID;
    
    // 创建单点触摸事件监听器
    static EventListenerTouchOneByOne* create();
    EventListenerTouchOneByOne();
    bool init();
    virtual ~EventListenerTouchOneByOne();
    
    // 是否吞噬掉触摸点,如果true,触摸将不会向下层传播。
    bool isSwallowTouches();
    void setSwallowTouches(bool needSwallow);
    
    /// Overrides 重写夫类的纯虚函数。
    virtual EventListenerTouchOneByOne* clone() override;
    virtual bool checkAvailable() override;
    
public:

//    typedef std::function<bool(Touch*,Event*)> ccTouchBeganCallBACk;
//    typedef std::function<void(Touch*,Event*)> ccTouchCallBACk;

    // public方法:创建监听器时,需手动设置 onTouch方法(onTouchBegan,onTouchMoved,onTouchEnded,onTouchCancelled)。
    std::function<bool(Touch*,Event*)> onTouchBegan;
    std::function<void(Touch*,Event*)> onTouchMoved;
    std::function<void(Touch*,Event*)> onTouchEnded;
    std::function<void(Touch*,Event*)> onTouchCancelled;
    
private:
    std::vector<Touch*> _claimedTouches;    // 触摸点列表。(onTouchBegan 返回true)&&(listener已经注册)过的Touch对象才会被添加。
    bool _needSwallow;                      // 是否需要吞噬触摸点
    
    friend class EventDispatcher;
};

// 多点触摸监听器
class CC_DLL EventListenerTouchAllAtOnce : public EventListener
{
public:
    static const std::string LISTENER_ID;
    
    static EventListenerTouchAllAtOnce* create();
    EventListenerTouchAllAtOnce();
    bool init();
    virtual ~EventListenerTouchAllAtOnce();
    
    /// Overrides
    virtual EventListenerTouchAllAtOnce* clone() override;
    virtual bool checkAvailable() override;
public:

    typedef std::function<void(const std::vector<Touch*>&,Event*)> ccTouchesCallBACk;

    ccTouchesCallBACk onTouchesBegan;
    ccTouchesCallBACk onTouchesMoved;
    ccTouchesCallBACk onTouchesEnded;
    ccTouchesCallBACk onTouchesCancelled;
    
    
private:
    
    friend class EventDispatcher;
};

NS_CC_END

#endif

#include "base/CCEventListenerTouch.h"
#include "base/CCEventDispatcher.h"
#include "base/CCEventTouch.h"
#include "base/CCTouch.h"

#include <algorithm>

NS_CC_BEGIN

const std::string EventListenerTouchOneByOne::LISTENER_ID = "__cc_touch_one_by_one";

EventListenerTouchOneByOne* EventListenerTouchOneByOne::create()
{
    auto ret = new (std::nothrow) EventListenerTouchOneByOne();
    if (ret && ret->init())
    {
        ret->autorelease();
    }
    else
    {
        CC_SAFE_deletE(ret);
    }
    return ret;
}

EventListenerTouchOneByOne::EventListenerTouchOneByOne()
: onTouchBegan(nullptr),onTouchMoved(nullptr),onTouchEnded(nullptr),onTouchCancelled(nullptr),_needSwallow(false)
{
}

bool EventListenerTouchOneByOne::init()
{
    if (EventListener::init(Type::TOUCH_ONE_BY_ONE,LISTENER_ID,nullptr))
    {
        return true;
    }
    
    return false;
}

EventListenerTouchOneByOne::~EventListenerTouchOneByOne()
{
    CCLOGINFO("In the destructor of EventListenerTouchOneByOne,%p",this);
}


void EventListenerTouchOneByOne::setSwallowTouches(bool needSwallow)
{
    _needSwallow = needSwallow;
}

bool EventListenerTouchOneByOne::isSwallowTouches()
{
    return _needSwallow;
}

bool EventListenerTouchOneByOne::checkAvailable()
{
    // EventDispatcher will use the return value of 'onTouchBegan' to determine whether to pass following 'move','end'
    // message to 'EventListenerTouchOneByOne' or not. So 'onTouchBegan' needs to be set.
    // onTouchBegin 必须需要。
    if (onTouchBegan == nullptr)
    {
        CCassERT(false,"Invalid EventListenerTouchOneByOne!");
        return false;
    }
    
    return true;
}

EventListenerTouchOneByOne* EventListenerTouchOneByOne::clone()
{
    auto ret = new (std::nothrow) EventListenerTouchOneByOne();
    if (ret && ret->init())
    {
        ret->autorelease();
        
        ret->onTouchBegan = onTouchBegan;
        ret->onTouchMoved = onTouchMoved;
        ret->onTouchEnded = onTouchEnded;
        ret->onTouchCancelled = onTouchCancelled;
        
        ret->_claimedTouches = _claimedTouches;
        ret->_needSwallow = _needSwallow;
    }
    else
    {
        CC_SAFE_deletE(ret);
    }
    return ret;
}

/////////

const std::string EventListenerTouchAllAtOnce::LISTENER_ID = "__cc_touch_all_at_once";

EventListenerTouchAllAtOnce::EventListenerTouchAllAtOnce()
: onTouchesBegan(nullptr),onTouchesMoved(nullptr),onTouchesEnded(nullptr),onTouchesCancelled(nullptr)
{
}

EventListenerTouchAllAtOnce::~EventListenerTouchAllAtOnce()
{
    CCLOGINFO("In the destructor of EventListenerTouchAllAtOnce,this);
}

bool EventListenerTouchAllAtOnce::init()
{
    if (EventListener::init(Type::TOUCH_all_AT_ONCE,nullptr))
    {
        return true;
    }
    
    return false;
}

EventListenerTouchAllAtOnce* EventListenerTouchAllAtOnce::create()
{
    auto ret = new (std::nothrow) EventListenerTouchAllAtOnce();
    if (ret && ret->init())
    {
        ret->autorelease();
    }
    else
    {
        CC_SAFE_deletE(ret);
    }
    return ret;
}

bool EventListenerTouchAllAtOnce::checkAvailable()
{
    // onTouchesBegin、onTouchesMoved、onTouchesEnded、onTouchesCancelled 只需要任意一个不为空就行了
    if (onTouchesBegan == nullptr && onTouchesMoved == nullptr&& onTouchesEnded == nullptr && onTouchesCancelled == nullptr)
    {
        CCassERT(false,"Invalid EventListenerTouchAllAtOnce!");
        return false;
    }
    
    return true;
}

EventListenerTouchAllAtOnce* EventListenerTouchAllAtOnce::clone()
{
    auto ret = new (std::nothrow) EventListenerTouchAllAtOnce();
    if (ret && ret->init())
    {
        ret->autorelease();
        
        ret->onTouchesBegan = onTouchesBegan;
        ret->onTouchesMoved = onTouchesMoved;
        ret->onTouchesEnded = onTouchesEnded;
        ret->onTouchesCancelled = onTouchesCancelled;
    }
    else
    {
        CC_SAFE_deletE(ret);
    }
    return ret;
}

NS_CC_END
EventListenerTouch是EventListener的子类,具有EventListener的所有属性,加上自身4个属性,共14个属性:

1.onTouchBegan

2.onTouchMove

3.onTouchEnded

4.onTouchCanceled

5.claimedTouches

6.needSwallow

7.type

8.listenerID

9.onEvent

10.paused

11.enabled

12.registered

13.node

14.fixedPriority


#ifndef __cocos2d_libs__CCCustomEventListener__
#define __cocos2d_libs__CCCustomEventListener__

#include "base/CCEventListener.h"

NS_CC_BEGIN

class EventCustom;


/****************************************************************************
 用法:
    // 1.dispatcher
    auto dispatcher = Director::geTinstance()->getEventDispatcher();
 
    // 2.addListener
    auto listener = EventListenerCustom::create("your_event_type",[](EventCustom* event){ do_some_thing(); });
    dispatcher->addEventListenerWithSceneGraphpriority(listener,one_nodE);
 
    // 3.dispatcherEvent,触发事件。
    EventCustom event("your_event_type");
    dispatcher->dispatchEvent(&event);
 
 ****************************************************************************/
// EventCustom
class CC_DLL EventListenerCustom : public EventListener
{
public:
    // 当特定的事件被分发,对应callBACk将会被调用
    static EventListenerCustom* create(const std::string& eventName,const std::function<void(EventCustom*)>& callBACk);
    EventListenerCustom();
    bool init(const ListenerID& listenerId,const std::function<void(EventCustom*)>& callBACk);
    
    
    /// Overrides
    virtual bool checkAvailable() override;
    virtual EventListenerCustom* clone() override;
protected:
    std::function<void(EventCustom*)> _onCustomEvent;
    
    friend class LuaEventListenerCustom;
};

NS_CC_END

#endif

#include "base/CCEventListenerCustom.h"
#include "base/CCEventCustom.h"

NS_CC_BEGIN

EventListenerCustom::EventListenerCustom()
: _onCustomEvent(nullptr)
{
}

EventListenerCustom* EventListenerCustom::create(const std::string& eventName,const std::function<void(EventCustom*)>& callBACk)
{
    EventListenerCustom* ret = new (std::nothrow) EventListenerCustom();
    if (ret && ret->init(eventName,callBACk))
    {
        ret->autorelease();
    }
    else
    {
        CC_SAFE_deletE(ret);
    }
    return ret;
}

bool EventListenerCustom::init(const ListenerID& listenerId,const std::function<void(EventCustom*)>& callBACk)
{
    bool ret = false;
    
    // callBACk ==> listener.
    // EventListenerCustom的onCustomEvent 与 EventListener的onEvent相关联。
    _onCustomEvent = callBACk;
    auto listener = [this](Event* event){
        if (_onCustomEvent != nullptr)
        {
            _onCustomEvent(static_cast<EventCustom*>(event));
        }
    };
    
    // event参数的传入
    // EventListener::init(Type t,ListenerID listenerID,std::function<void(Event*)>& callBACk)
    if (EventListener::init(EventListener::Type::CUSTOM,listenerId,listener))
    {
        ret = true;
    }
    return ret;
}

EventListenerCustom* EventListenerCustom::clone()
{
    EventListenerCustom* ret = new (std::nothrow) EventListenerCustom();
    if (ret && ret->init(_listenerID,_onCustomEvent))
    {
        ret->autorelease();
    }
    else
    {
        CC_SAFE_deletE(ret);
    }
    return ret;
}

bool EventListenerCustom::checkAvailable()
{
    bool ret = false;
    if (EventListener::checkAvailable() && _onCustomEvent != nullptr)
    {
        ret = true;
    }
    return ret;
}

NS_CC_END
EventListenerCustom是EventListener的子类,共有9个属性:

1.onCustomEvent

2.onEvent

3.type

4.listenerID

5.registered

6.paused

7.enabled

8.node

9.fixedPriority

大佬总结

以上是大佬教程为你收集整理的cocos2d-x 3.7 源码分析 EventDispatcher全部内容,希望文章能够帮你解决cocos2d-x 3.7 源码分析 EventDispatcher所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。