混合跨平台应用程序:在原生 iOS 和 Android 组件之上使用 Cocos2d-x 的方法
本文介绍了一种在原生组件之上使用 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 视图来完成此操作。
背景
为了实现介绍的目标,需要考虑以下几点:
- 为每个操作系统实现原生组件。
- 为原生组件提供一个容器视图。该视图必须位于视图层次结构中 Cocos2d-x 主视图的下方。
- 使 Cocos2d-x 主视图透明,以便我们可以透过它看到原生组件。
- 准备一个机制,用于在 Cocos2d-x 的跨平台代码中处理触摸事件,以便与原生代码进行交互。
- 在每个操作系统的原生代码中支持触摸机制。
触摸需要特殊的处理方法,因为默认情况下它们不会从 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 文件添加到源代码中。