65.9K
CodeProject 正在变化。 阅读更多。
Home

混合跨平台应用程序:在原生 iOS 和 Android 组件之上使用 Cocos2d-x 的方法

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (16投票s)

2016 年 9 月 9 日

CPOL

4分钟阅读

viewsIcon

29530

downloadIcon

247

本文介绍了一种在原生组件之上使用 Cocos2d-x 的通用方法,以及 Cocos2d-x、iOS 和 Android 的相关编码细节。

引言

众所周知,Cocos2d-x 是一个用 C++ 编写的开源游戏框架,它有一个轻量级的平台依赖层。它被广泛用于构建游戏、应用程序和其他基于 GUI 的跨平台交互式程序(http://cocos2d-x.org)。该引擎本身专注于提供方便的跨平台图形元素(精灵、纹理等)创建和操作手段。

尽管它提供了一套称为小部件(按钮、文本字段、滚动视图等)的 GUI 组件,但它们的功能非常有限。此外,其中一些组件基于每个操作系统的原生实现。这在 Cocos2d-x 的源代码(编辑框、Web 视图、视频播放器等)中可以看到。因此,原生 UI 组件是如何在 Cocos2d-x OpenGL 层之上使用的,这一点很清楚。

但是,在某些情况下,项目需要 Cocos2d-x OpenGL 层之下的原生组件

  • 在后台播放电影;
  • 将 Web 内容作为应用程序的一部分使用;
  • 基于原生实现创建其他特定组件。

Cocos2d-x 开箱即用地不提供此类功能。有时这会迫使开发人员寻找不常见于该引擎的解决方案。

让我们考虑一种在原生组件之上使用 Cocos2d-x GUI 的通用方法,并探索 Cocos2d-x、iOS 和 Android 中的一些编码细节。我们将通过在 Cocos2d-x 框架的默认演示示例中实现原生 Web 视图来完成此操作。

背景

为了实现介绍的目标,需要考虑以下几点:

  1. 为每个操作系统实现原生组件。
  2. 为原生组件提供一个容器视图。该视图必须位于视图层次结构中 Cocos2d-x 主视图的下方。
  3. 使 Cocos2d-x 主视图透明,以便我们可以透过它看到原生组件。
  4. 准备一个机制,用于在 Cocos2d-x 的跨平台代码中处理触摸事件,以便与原生代码进行交互。
  5. 在每个操作系统的原生代码中支持触摸机制。

触摸需要特殊的处理方法,因为默认情况下它们不会从 Cocos2d-x OpenGL 视图转发。所选的解决方案基于 Cocos2d-x 组件是否订阅了“触摸开始”事件并返回 true 处理了该事件。

例如,如果存在某个 Cocos2d-x GUI 小部件正在处理该事件,则该事件不会被转发到原生组件视图。否则,事件将传递到原生组件视图,并将由原生控件处理。

因此,如果存在一些默认情况下会忽略触摸的 Cocos2d-x 组件(例如,图像视图),则对这些组件的触摸将由原生组件视图处理。

下图演示了所述的思路。

使用代码

代码基于 Cocos2d-x 版本 3.8.1 及其演示示例。在编译之前,必须将其与完整的 Cocos2d-x 框架结合。

建议的编码解决方案是众多可能解决方案之一。选择它是因为它的简单性和最少的更改,以便快速看到结果并获得某种“概念验证”。

Cocos2d-x 代码

准备一个方法,用于将交互状态返回到原生代码。如果事件由 Cocos2d-x 处理,则返回 true。

// CGLView.cpp

bool GLView::handleTouchesBegin(int num, intptr_t ids[], float xs[], float ys[])
{
    //...
    touchEvent._eventCode = EventTouch::EventCode::BEGAN;
    auto dispatcher = Director::getInstance()->getEventDispatcher();
    dispatcher->dispatchEvent(&touchEvent);

    // EK: cancel the event, otherwise it stays in the cache and can block similar events
    if (!touchEvent.isClaimedByCocos())
    {
        handleTouchesCancel(num, ids, xs, ys);
    }

    // EK: return the status into the native code
    return touchEvent.isClaimedByCocos();
}

如果事件由 Cocos2d-x 处理,则更新状态。

// CCEventDispatcher.cpp

void EventDispatcher::dispatchTouchEvent(EventTouch* event)
{
    //...
    auto onTouchEvent = [&](EventListener* l) -> bool {
        EventListenerTouchOneByOne* listener = static_cast<EventListenerTouchOneByOne*>(l);
            if (!listener->_isRegistered)
                return false;
            event->setCurrentTarget(listener->_node);
            bool isClaimed = false;

            std::vector<Touch*>::iterator removedIter;
            EventTouch::EventCode eventCode = event->getEventCode();
            if (eventCode == EventTouch::EventCode::BEGAN)
            {
                if (listener->onTouchBegan)
                {
                    isClaimed = listener->onTouchBegan(*touchesIter, event);
                    if (isClaimed && listener->_isRegistered)
                    {
                        listener->_claimedTouches.push_back(*touchesIter);
                    }
                }
            }
            //...

            // EK: update the status whether cocos takes care of the event
            event->setClaimedByCocos(isClaimed);

为 Cocos2d-x 帧缓冲区设置清晰的颜色。

// CCDirector.cpp

void Director::setOpenGLView(GLView *openGLView)
{
    //...
    _defaultFBO = experimental::FrameBuffer::getOrCreateDefaultFBO(_openGLView);

    // EK: clear color
    _defaultFBO->setClearColor(cocos2d::Color4F(0.0, 0.0, 0.0, 0.0));
    _defaultFBO->retain();
}

更新 JNI 实现以在 Android 中接收事件状态。

// TouchesJni.cpp

extern "C" {
    JNIEXPORT jboolean JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeTouchesBegin(JNIEnv * env, jobject thiz, jint id, jfloat x, jfloat y) {
        intptr_t idlong = id;

        // EK: pass the status to Android
        return (jboolean) (cocos2d::Director::getInstance()->getOpenGLView()->handleTouchesBegin(1, &idlong, &x, &y) != false);
    }
iOS 代码

实现原生容器视图和 Web 视图。

// AppController.mm

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {   
    //...   

    // EK: create container view
    UIView *containerView = [[UIView alloc] initWithFrame:[window bounds]];
 
    // EK: create web view
    UIWebView *webview=[[UIWebView alloc]initWithFrame:[window bounds]];
    NSString *url=@"https://www.google.com";
    NSURL *nsurl=[NSURL URLWithString:url];
    NSURLRequest *nsrequest=[NSURLRequest requestWithURL:nsurl];
    [webview loadRequest:nsrequest];
   
    // EK: add subviews
    [containerView addSubview:webview];
    [containerView addSubview:eaglView];

    _viewController = [[RootViewController alloc] initWithNibName:nil bundle:nil];
    _viewController.wantsFullScreenLayout = YES;

    // EK: set container view instead of eagle view
    _viewController.view = containerView;

    // EK: set eagl view as transparent
    eaglView.opaque = NO;

将以下方法添加到 Cocos2d-x 的命中测试。从“触摸开始”的 hitTest 返回有效指针或 nil 会决定所有其他相关事件,如“触摸移动”、“触摸结束”和“触摸取消”,将转发到哪个视图。因此,只需要检查 Cocos2d-x 端的“触摸开始”。由于这是对“触摸开始”的模拟,如果事件被 Cocos2d-x 处理,则该事件将被取消。返回有效指针后,所有后续事件(包括“触摸开始”)都将由现有方法处理:touchesBegan、touchesMoved 等。否则,事件将被转发到原生组件视图。

// CCEAGLView-ios.mm

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView* view = nil;
    UIView* ids[IOS_MAX_TOUCHES_COUNT] = {0};
    float xs[IOS_MAX_TOUCHES_COUNT] = {0.0f};
    float ys[IOS_MAX_TOUCHES_COUNT] = {0.0f};

    ids[0] = self;
    xs[0] = point.x * self.contentScaleFactor;
    ys[0] = point.y * self.contentScaleFactor;
   
    // EK: simulate hit test
    auto glview = cocos2d::Director::getInstance()->getOpenGLView();
    if (glview->handleTouchesBegin(1, (intptr_t*)ids, xs, ys)) {
        view = self;
        glview->handleTouchesCancel(1, (intptr_t*)ids, xs, ys);
    }

    return view;
}
Android 代码

实现 Web 视图,并将现有的帧布局用作容器视图。

// Cocos2dxActivity.java

public void init() {
    //...

    // EK: create web view
    ViewGroup.LayoutParams pars = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
        ViewGroup.LayoutParams.MATCH_PARENT);
    WebView webView = new WebView(this);
    webView.setLayoutParams(pars);
    webView.setWebViewClient(new WebViewClient());
    webView.loadUrl("https://google.com");

    // EK: add view to layout
    mFrameLayout.addView(webView);

    this.mGLSurfaceView = this.onCreateView();

    // EK: set z order to make transparency working
    this.mGLSurfaceView.setZOrderOnTop(true);

    mFrameLayout.addView(this.mGLSurfaceView);

检查 Cocos2d-x 端的事件,并将所有相关事件转发到 Cocos2d-x 或原生组件视图。从 onTouchEvent 返回 bool 状态以处理“触摸开始”事件,会决定所有其他相关事件,如“触摸移动”、“触摸结束”和“触摸取消”,将转发到哪个视图。因此,在 Android 中也只需要检查 Cocos2d-x 端的“触摸开始”。

// Cocos2dxGLSurfaceView.java

@Override
public boolean onTouchEvent(final MotionEvent pMotionEvent) {
    //...
    EventRunnable runnable = null;
    switch (pMotionEvent.getAction() & MotionEvent.ACTION_MASK) {
        //...
        case MotionEvent.ACTION_DOWN:
            final int idDown = pMotionEvent.getPointerId(0);
            final float xDown = xs[0];
            final float yDown = ys[0];

            runnable = new EventRunnable() {
                @Override
                public void run() {
                    this.setIsClaimed(Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionDown(idDown, xDown, yDown));
                    synchronized (this) {
                        this.setIsProcessed(true);
                        notifyAll();
                    }
                }
            };
            break;
    //...

    boolean result = true;
    if (runnable != null) {
        this.queueEvent(runnable);
        synchronized (runnable) {
            if (!runnable.isProcessed()) {
                try {
                    runnable.wait();
                } catch (InterruptedException e) {
                }
            }
            result = runnable.isClaimed();
        }
    }

    return result;
}

该示例产生了以下结果,其中 Cocos2d-x 关闭按钮可点击,原生 Web 视图功能齐全。

 

关注点

为了节省时间和演示目的,Web 视图已被硬编码为原生组件。因此,通过 Cocos API 实现添加此类组件的方法是一个很好的改进领域。

历史

  • 已将 Cocos2dxRenderer.java 文件添加到源代码中。

 

© . All rights reserved.