一:Cocos游戏与平台交互过程
1:Cocos游戏与Android平台交互过程
简单来说Android平台的Cocos所有游戏交互都是通过Jni与游戏端交互。其中包括Cocos引擎交互和引擎之外的交互,具体交互流程分别举例:
Cocos引擎交互流程之游戏启动过程分析
再此之前,说一下公司的游戏的依赖情况:
(1).pragon_common:为基础库,无论是纯应用类开发,Cocos游戏或者是Unity3D游戏都必须包含此依赖,依赖中的内容主要有:Application,BaseActivity,WelcomeActivity,反馈,DBT统计,友盟统计,在线参数,Bugly,工具类函数集合等内容
(2).C2DXPdragonAndroid/C2DXPdragonAndroid_316:为Cocos游戏引擎依赖,所有的Cocos游戏均需要包含此依赖,并且依赖于pragon_common库;依赖中的主要内容由:Cocos引擎部分的交互文件和游戏接口代码两部分;其中游戏接口包括:分享、广告、支付、游戏震动、游戏拍照、游戏保存图片等接口合集
(3).具体游戏工程:依赖于C2DXPdragonAndroid/C2DXPdragonAndroid_316库
Cocos游戏由OpenGL底层进行渲染,在Android端的上层接口为:GLSurfaceView,而Cocos自定义了一个Cocos2dxGLSurfaceView和渲染器Cocos2dxRenderer两个类。在Cocos2dxActivity的onCreate函数中,初始化了Cocos引擎入口,
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
CocosPlayClient.init(this, false);
onLoadNativeLibraries();
sContext = this;
GameActHelper.initOrientation();
this.mHandler = new Cocos2dxHandler(this);
Cocos2dxHelper.init(this);
//(1)初始化游戏引擎,并获取OpenGL属性
this.mGLContextAttrs = getGLContextAttrs();
this.init();
if (mVideoHelper == null) {
mVideoHelper = new Cocos2dxVideoHelper(this, mFrameLayout);
}
if (mAdsViewHelper == null) {
mAdsViewHelper = new Cocos2dxAdsViewHelper(this, mFrameLayout);
}
if(mWebViewHelper == null){
mWebViewHelper = new Cocos2dxWebViewHelper(mFrameLayout);
}
}
//(2)初始化游戏引擎,并获取OpenGL属性
private static native int[] getGLContextAttrs();
在Cocos引擎的cocos2d\cocos\platform\android\javaactivity-android.cpp文件中实现getGLContextAttrs函数:
JNIEXPORT jintArray JNICALL Java_org_cocos2dx_lib_Cocos2dxActivity_getGLContextAttrs(JNIEnv* env, jobject thiz)
{
//(1)初始化Cocos引擎,并创建Application
cocos_android_app_init(env, thiz);
cocos2d::Application::getInstance()->initGLContextAttrs();
GLContextAttrs _glContextAttrs = GLView::getGLContextAttrs();
int tmp[6] = {_glContextAttrs.redBits, _glContextAttrs.greenBits, _glContextAttrs.blueBits,
_glContextAttrs.alphaBits, _glContextAttrs.depthBits, _glContextAttrs.stencilBits};
jintArray glContextAttrsJava = env->NewIntArray(6);
env->SetIntArrayRegion(glContextAttrsJava, 0, 6, tmp);
return glContextAttrsJava;
}
cocos_android_app_init函数实现在:当前工程/jni/hellocpp/main.cpp文件中
void cocos_android_app_init (JNIEnv* env, jobject thiz) {
LOGD("cocos_android_app_init");
AppDelegate *pAppDelegate = new AppDelegate();
}
初始化引擎代码,并获取到OpenGL属性后,进行GLSurfaceView的创建,并在初始化完成之后设置GLSurfaceView渲染器,代码如下:
private void addmGLSurfaceView(){
//(1)创建GLSurfaceView
this.mGLSurfaceView = onCreateView();
//modify by hqm 固定Layout的位置
FrameLayout.LayoutParams layout_params1 =
new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT);
layout_params1.gravity = Gravity.TOP|Gravity.LEFT;
layout_params1.leftMargin=0;
layout_params1.topMargin=0;
// ...add to FrameLayout
mFrameLayout.addView(mGLSurfaceView, layout_params1);
// Switch to supported OpenGL (ARGB888) mode on emulator
if (isAndroidEmulator())
this.mGLSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
//解决clippingnode不能正常显示问题
//mGLSurfaceView.setEGLConfigChooser(5, 6, 5, 0, 16, 8);//pjy
//(2)给创建好的GLSurfaceView设置绘制器
mGLSurfaceView.setCocos2dxRenderer(new Cocos2dxRenderer());
mGLSurfaceView.setCocos2dxEditText(edittext);
}
渲染器在onSurfaceCreated函数中,通过JNI调用了Cocos引擎的初始化函数:Cocos2dxRenderer.nativeInit(int widtn, int height),并且传入了GLSurfaceView的宽高。
@Override
public void onSurfaceCreated(final GL10 GL10, final EGLConfig EGLConfig) {
//(1)在onSurfaceCreated函数中调用引擎的初始化函数。
Cocos2dxRenderer.nativeInit(this.mScreenWidth, this.mScreenHeight);
this.mLastTickInNanoSeconds = System.nanoTime();
mNativeInitCompleted = true;
}
//(2)引擎初始化函数入口
private static native void nativeInit(final int width, final int height);
在Cocos引擎的cocos2d\cocos\platform\android\javaactivity-android.cpp文件中实现nativeInit函数:
JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(JNIEnv* env, jobject thiz, jint w, jint h)
{
auto director = cocos2d::Director::getInstance();
auto glview = director->getOpenGLView();
if (!glview)
{
//(1)Cocos引擎未创建,新建OpenGLView
glview = cocos2d::GLViewImpl::create("Android app");
glview->setFrameSize(w, h);
director->setOpenGLView(glview);
//cocos_android_app_init(env, thiz);
//(2)开始运行Cocos引擎
cocos2d::Application::getInstance()->run();
}
else
{
....
}
}
run函数会调用Application的applicationDidFinishLaunching函数
int Application::run()
{
// Initialize instance and cocos2d.
if (! applicationDidFinishLaunching())
{
return 0;
}
return -1;
}
在applicationDidFinishLaunching函数中,会设置游戏引擎的Director、openGLView界面大小,设计界面大小,设置完成之后,运行游戏场景:
bool AppDelegate::applicationDidFinishLaunching() {
//(1)游戏进入时各种初始化:设置资源搜索路径,网络请求获取参数,设置默认语言等
.....
//(2)获取Director和OpenGLView,设置界面大小、设计界面大小、显示帧率
auto director = Director::getInstance();
auto glview = director->getOpenGLView();
if(!glview) {
glview = GLViewImpl::create("Enclose Cat");
director->setOpenGLView(glview);
}
#ifdef WIN32
//设置win32模拟器大小
glview->setFrameSize(480.0f,800.0f);
#endif
//设置适配分辨率
//设置适配分辨率
if (glview->getFrameSize().width/9 == glview->getFrameSize().height/16)
{
glview->setDesignResolutionSize(720.0f,1280.0f,ResolutionPolicy::EXACT_FIT);
}
else
{
glview->setDesignResolutionSize(720.0f,1280.0f,ResolutionPolicy::SHOW_ALL);
}
director->setDisplayStats(false);
director->setAnimationInterval(1.0 / 60);
//(3)创建游戏场景,并且通过director运行场景。
auto scene = HelloWorldScene::create();
director->runWithScene(scene);
return true;
}
以上过程便是Cocos游戏初始化的整体流程。
引擎之外的交互之信息流大图交互流程
信息流大图分为两部分:主图+底图,由于广告平台信息流素材差异,导致,部分平台没有主图,部分平台底图元素缺失,导致我们的信息流大图存在多种情况的组合:
(a)主图+底框
(b)主图
(c)底框
(d)主图+底框(无icon)
游戏暂停界面,会先在游戏界面创建整个信息流大图框(因为这个框要根据每个游戏色彩进行调整,所以由游戏进行渲染)
/**********************************************************************************
* 游戏创建新的广告位大图接口,相对于之前的接口修改:
* 1 广告位大小有调整,之前是610*340,调整后是562*436
* 2 广告位大图里增加了icon,title,download button,直接封装在了里面,外部不需要设置大小,只需要设置颜色即可
* 3 增加部分参数,根据不同界面风格设置
* 参数说明:
* ---------------------------------------- ----------------------------------------
* | ---------------------------------- | | ---------------------------------- |
* | | | | | | | |
* | | | | | | | |
* | | Ads Picture or Default picture | | | | Ads Picture or Default picture | |
* | | | | | | | |
* | | | | | | | |
* | ---------------------------------- | | ---------------------------------- |
* | ------ Game Title ---------- | | ---------------------------------- |
* | | icon | | dwonload | | | | Default Cover Image | |
* | ------ Game Content ---------- | | ---------------------------------- |
* ---------------------------------------- ----------------------------------------
* parent : 父节点
* pDefaultImage : 默认图片
* pos : 位置
* iZorder :层级
* sBgPicPath : 背景图片
* titleColor : 与上图中的 Game Title,Game Content共用
* actionBackgroundColor :dwonload Button的背景颜色
* actionColor :dwonload Button 上面的字体颜色
* sCoverPicPath : 对应右边图片的Default Cover Image,显示广告时填充在广告底部
* sAdsTitle : 广告之后更精彩提示语,广告没有大图时,放在Ads Picture位置
* iShowInterstitial : 界面上是否显示插屏,展示传1,不显示传0
**********************************************************************************/
extern Node* CreateGameOverLargeAdsImageNew(Node* parent, const char* pDefaultImage, Vec2 pos, int iZorder, std::string sBgPicPath, Color3B titleColor,
Color3B actionBackgroundColor, Color3B actionColor, std::string sCoverPicPath, std::string sAdsTitle, int iShowInterstitial = 0);
在CreateGameOverLargeAdsImageNew回调用自定义的游戏控件:DBTAdsView。DBTAdsView中继承了Cocos引擎的Widget控件,这样就可以把广告调用当做一个游戏控件来使用,对游戏开发来说也更为熟悉。在DBTAdsView中除了Widget控件函数外,还新增了:设置广告大图位置、底框位置、设置底框中,标题文字颜色,按钮背景颜色,按钮文字颜色、加载信息流广告等函数。与之对应的,在Android平台也自定义了Cocos2dxAdsView和Cocos2dxAdsViewHelper两个java类,用于给游戏层调用和回调游戏层;
游戏交互流程过程第一,创建DBTAdsView类:
Node* CreateGameOverLargeAdsImageNew(Node* parent, const char* pDefaultImage, Vec2 pos, int iZorder, std::string sBgPicPath, Color3B titleColor,
Color3B actionBackgroundColor, Color3B actionColor, std::string sCoverPicPath, std::string sAdsTitle, int iShowInterstitial)
{
...
#if (CC_TARGET_PLATFORM != CC_PLATFORM_WIN32)
auto adsView = ns_common::AdsView::create();
adsView->setPosition(pos);
adsView->setContentSize(Size(562.0*fScale, 436.0*fScale));
Vec2 picturePoint = Vec2(pos.x, pos.y + 44.0*fScale);
Vec2 footerPoint = Vec2(pos.x, pos.y -148.0*fScale);
//(1)设置大图尺寸和位置
adsView->setBigAdsPictureRect(Rect(picturePoint.x, picturePoint.y, 608.0f*0.8*fScale, 342.0f*0.8*fScale));
//(2)设置底框尺寸和位置
adsView->setBigAdsFooterRect(Rect(footerPoint.x, footerPoint.y, 493.0*fScale, 91.0*fScale));
//(3)设置底框中,标题文字颜色,按钮背景颜色,按钮文字颜色
adsView->setBigAdsViewColor(titleColor, actionBackgroundColor, actionColor);
//(4)加载信息流大图广告,返回信息流大图的样式:
int isLoad = adsView->loadAds(2, 99);
if (isLoad != 0) {
parent->addChild(adsView, iZorder);
//(4)-(1)大图+底框模式和大图模式,需要在底框加一个遮罩,遮住背景框的元素。
if((2 == isLoad) || (3 == isLoad))
{
auto footerBg = Sprite::create(sCoverPicPath);
footerBg->setPosition(footerPoint);
footerBg->setName("BigAdsCoverImg");
footerBg->setContentSize(Size(493.0*fScale, 91.0*fScale));
parent->addChild(footerBg, iZorder);
}
//(4)-(2)底框模式,需要在大图位置添加“广告之后更精彩”
if (3 == isLoad)
{
auto adsTitle = Sprite::create(sAdsTitle);
adsTitle->setPosition(pos.x, pos.y + 44.0*fScale);
adsTitle->setName("AdsTitle");
parent->addChild(adsTitle, iZorder);
}
return adsView;
}
#endif
...
return NULL;
}
其中setBigAdsPictureRect、setBigAdsFooterRect两个个函数分别通过JNI,调用到Cocos2dxAdsViewHelper.java文件中的setAdsWidgetRectNew函数把对应的坐标传入平台。
/**
* 设置信息流尺寸,新版本
* @param index
* @param name : root整个信息流大框尺寸和位置,picture:大图尺寸和位置,footer:底框尺寸和位置
* @param left
* @param top
* @param maxWidth
* @param maxHeight
*/
public static void setAdsWidgetRectNew(final int index, String name, final int left, final int top, final int maxWidth, final int maxHeight) {
Message msg = new Message();
msg.what = AdsTaskSetRect;
msg.arg1 = index;
// 设置广告位摆放尺寸
FeedAdsGameRect gameRect = new FeedAdsGameRect(name, left, top, maxWidth, maxHeight);
msg.obj = gameRect;
mAdsHandler.sendMessage(msg);
}
setBigAdsViewColor函数通过JNI调用Cocos2dxAdsViewHelper.java文件中的setBigAdsViewColor函数把对应的颜色传入平台。
/**
* 设置大图广告各个元素View的字体颜色和背景颜色
* @param index
* @param name vc_title:标题文字颜色,vbc_action:按钮背景颜色 , vc_action:按钮文字颜色
* @param r
* @param g
* @param b
*/
public static void setBigAdsViewColor(final int index, String name, int r, int g, int b) {
r = (r > 255 || r < 0)?255:r;
g = (g > 255 || g < 0)?255:g;
b = (b > 255 || b < 0)?255:b;
UserApp.LogD(TAG, String.format("设置"+name+"颜色{%d,%d,%d}", r,g,b));
Message msg = new Message();
msg.what = AdsTaskSetViewColor;
msg.arg1 = index;
msg.arg2 = Color.rgb(r, g, b);
msg.obj = name;
mAdsHandler.sendMessage(msg);
}
收集到了信息流大图、底框尺寸和位置以及底框个元素色值后,会保存到Cocos2dxAdsView对象对应的变量当中。
/**
* 添加广告各元素尺寸
* @param gameRect
*/
public void addGameRect(FeedAdsGameRect gameRect){
gameRectUtils.addRect(gameRect);
//设置根View大小
if(Cocos2dxAdsViewHelper.VNRoot.equals(gameRect.name)){
setAdsViewRect(gameRect.left, gameRect.top, gameRect.width, gameRect.height);
}
}
/**
* 添加广告元素的颜色
* @param name
* @param color
*/
public void addBigAdsViewColor(String name, int color){
gameColorUtil.addColor(name, color);
}
之后便是,调用Cocos2dxAdsViewHelper.java文件中的load函数加载信息流大图广告了。
/**
* 加载信息流广告,只判断是否能展示
* @param index
* @param scene
* @param id
* @return
* 广告渲染方式:0:默认有问题,或者不应该展示的广告,1:只有主图,2:主图+icon/标题 3:icon/标题(没有主图)
*/
public static int loadAdsNew(final int index, final int scene, final int id) {
final int canLoad = FeedAdsGameHelper.canLoadAdsDataStatic(scene, id);
if(canLoad == 0){
removeAdsWidget(index);
}
return canLoad;
}
在整个过程开始之前,调用了AdsManager.java中的requestFeedAds函数请求信息流,如果信息流返回失败,会在游戏调用load的时候,再次调用requestFeedAds进行重新请求。那么其实本次load的时候,只是把本地的信息流取出来获取信息流样式返回给游戏。游戏获取到load结果之后,会调用Widget控件的show函数,show函数会通过JNI调用Cocos2dxAdsViewHelper.java文件中的show函数展示信息流大图了
/**
* 展示信息流广告
* @param index
* @param scene
* @param id
*/
public static void showAds(final int index, final int scene, final int id) {
Message msg = new Message();
msg.what = AdsTaskShowAds;
msg.arg1 = index;
msg.arg2 = scene;
msg.obj = id;
mAdsHandler.sendMessage(msg);
}
调用show的过程,会调用Cocos2dxAdsView对象的loadAds函数进行view的渲染,Android平台的信息流大图渲染是通过获取到的广告的信息后,根据不同的广告样式和广告平台机型不同的绘制,具体代码在FeedAdsGame.java类中的show函数中调用不同的Controller来进行不同的广告展示。信息流大图广告每一个元素的设置,均会在不同的Controller函数中实现。每个Controller中还兼容老版本信息流大图和新版本信息流大图的渲染样式。
/**
* 加载并展示广告
* @param adsId
* @param parentView
*/
public void showAds(final int adsId, final ViewGroup parentView, FeedAdsGameRectUtils gameRectUtils, FeedAdsGameColorUtil gameColorUtil){
if(info == null){
UserApp.LogE(TAG, "游戏load返回0时,不能addView");
return;
}
UserApp.LogD(TAG, "即将展示"+info.gameAdsId+"号广告");
BaseController baseController = null;
if(info.type.equals(FeedAdsType.DATA)){
baseController = new DataController();
}
else if(info.type.equals(FeedAdsType.DATA_VIEW)){
baseController = new DataViewController();
}
else if(info.type.equals(FeedAdsType.DATA_VIEW_ADMOB)){
if(info.company.equalsIgnoreCase("adtiming")){
baseController = new AdtimingController();
}
else if(info.company.equalsIgnoreCase("huawei")){
baseController = new HWController();
}
else{
baseController = new AdmobController();
}
}
baseController.showAds(ctx, parentView, info, gameRectUtils, gameColorUtil);
}
2:Cocos游戏与IOS平台交互过程
3:Cocos2dx与Cocos-js发布App区别与联系
(1)区别:开发语言不同c++/js,js通过Js Binding与Cocos2dx进行桥接;Cocos-js底层引擎控件比Cocos2dx引擎少,并且没有Scene,Button....等元素。
(2)联系:cocos-js发布App时底层还是C++语言实现,js调用广告,支付,分享,统计等功能,均通过C++调用到平台层,与Cocos游戏一致。
二:Unity游戏与平台交互过程(以后在完善)
1:Unity游戏与Android平台交互过程
2:Unity游戏与IOS平台交互过程
三:如何判断测试反馈的问题是否为游戏问题
游戏一启动就闪退,未进入GameAct.java等情况必然是非游戏问题。以上两点在App启动时,可以直接判断,更多的需要通过打印日志来判断:
日志判断依据:在游戏调用入口打印日志,并且日志中打印参数,有返回值的打印返回值。
不管是闪退,还是接口调用问题,都可以根据当前操作,来判断游戏是否调用接口,接口参数是否正确,返回游戏结果是否正确来定位问题。
四:分享活动回顾
1:Android信息流大图交互流程是否可以优化,优化点有哪些?请概述优化方案。
2:如果C++游戏中有一个全局的结构体中,引用了一个未实例化的对象导致App过了开屏界面后,立马闪退,我们从以上流程分析中哪一段可以大概定位为游戏问题还是平台问题。