项目总结之截屏细节考虑

前端之家收集整理的这篇文章主要介绍了项目总结之截屏细节考虑前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

项目总结之截屏细节考虑

dionysosLai(906391500@qq.com) 2014/12/22

2014项目总算告一段落,这个产品顺顺利利从开始到最后的上市,圆满成功。借着项目总结,回顾自己做的几个游戏,将一些细节问题归纳。第一篇,就以目前正在做的的新游戏《圣诞节》开篇,讲讲游戏截屏问题。

对于截屏,本身技术,并不是很复杂,一般有两种方法,一种是使用Opengl像素取点方式;另一种是使用RenderTexture纹理方法,详细内容,可以参考,之前写的一篇文章http://www.jb51.cc/article/p-bxxfyjsw-qs.html

两个技术方案考究:

在我游戏中,我使用的是RenderTexture方法,之所以我要使用的是RenderTexture方法,而不是Opengl中获取像素点glReadPixels的方法,是基于以下几点考虑:

1.二者在效率方面,基本没有差别,可以不考虑;(实际上RenderTexture方法,效率应该更低点);

2.由于在截屏时,我们不可能是将整个当前屏幕所有的元素全部截取下来,必然存在一些元素并不是我们需要的,还有一些元素需要我们临时贴上去,比方说logo之类的。这样的话,使用Opengl方法,必然在截屏之前,必须先去掉一些元素、添加上一些元素,截屏结束,也必须将元素反过来处理一遍。这之间,就存在着一些时间差,做的不好,就是出现闪屏效果。而使用Rendertexture方法,就可以在begin和end之外对元素进行处理,不会有闪屏效果

3.如果游戏中,我们使用了蒙版技术或者贴上一些半透明图片,难么很抱歉,如果不对图片进行特殊出处理,游戏中图片和截屏出来的图片是不一样的。而使用Rendertexture方法,我们可以很容易先对图片进行混合处理,使截屏图片和游戏画面一致。

截屏细节把握:

正是基于以上三点,我采用的RenderTexture方法。下面,详细介绍截屏细节。

1. 文件读写问题:

文件读写问题,包括图片保存和获取,由于图片读取,基本上不用考虑很多,因此重点是文件写问题。

对于文件写问题,首先考虑的第一个问题就是系统容量检测问题,这点十分必要,如果系统容量不够,而程序强制性写入,导致的第一个问题必然是程序卡机,甚至是系统挂掉(我们平板是第一次试水,在防护方面做得比较差,曾经有音效加载问题,导致系统奔溃)。这可是一个不折不扣的A类bug啊,如果没做这不处理,那么就等着测试妹子找你麻烦吧。

检测系统代码如下:

.h

///@brief 检测sd卡是否可用容量足够
///@param[in] size---检测容量  注意:这里是已MB来就算
///@return 0---sd卡不可用 1---sd卡容量不够 2---sd卡可用,容量足够
	int checkAvailableSDSize(const unsigned int& size);

.cpp

int HomeScene::checkAvailableSDSize( const unsigned int& size )
{
	int availableOk = 0;
	/// 目前,只做android平台检测,其他平台一律默认通过
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
	JniMethodInfo methodInfo;
	jint ret = 0;
	if (JniHelper::getStaticMethodInfo(methodInfo,"com.mesrjni.MesrJni","checkAvailableSDSize","(I)I"))
	{
		ret = methodInfo.env->CallStaticIntMethod(methodInfo.classID,methodInfo.methodID,size);
		if (0 == ret)
		{
			CCLOG("sd's state is abnormal!");
			availableOk = 0;
		}
		else if (1 == ret )
		{
			CCLOG("sd's availabel size is not enough!");
			availableOk = 1;
		}
		else if (2 == ret )
		{
			CCLOG("sd's availabel size is enough!");
			availableOk = 2;
		}
		else
		{
			CCAssert(false,"There is some wrong");
		}
	}
#else
	availableOk = 2;			 
#endif
	return availableOk;
}

.java

/*
     * 检测sd卡是否可用容量足够
     */
    public static int checkAvailableSDSize(int size){
    	/// 先检测SD卡是否可用
    	String state = Environment.getExternalStorageState(); 
        if(!Environment.MEDIA_MOUNTED.equals(state)){
        	/// 对sd卡上的存储可以进行读/写操作
        	Log.d("DEBUG","SD's state is abnormal!");
/*        	new  AlertDialog.Builder(activity)    
        	.setTitle("圣诞节" )  
        	.setMessage("小朋友,目前不能保存相册,快叫爸爸妈妈来解决吧!" )  
        	.setPositiveButton("确定",null )  
        	.show();  */
        	return 0;
        }
        /// 获取sd卡用容量
        File path = Environment.getExternalStorageDirectory(); //取得sdcard文件路径
        StatFs stat = new StatFs(path.getPath());       
        long blockSize = stat.getBlockSize();      
        long availableBlocks = stat.getAvailableBlocks();
        long availableSize = (availableBlocks * blockSize)/1024/1024;
        Log.d("DEBUG","可用空间:" + availableSize + "Mb");
        /// 判断容量是否足够
        if(size > availableSize){
        	Log.d("DEBUG","The sd's availavle size is not enough!");
/*        	new  AlertDialog.Builder(activity)    
        	.setTitle("圣诞节" )  
        	.setMessage("小朋友,SD卡空间不足了哦,不能保存相册。快叫爸爸妈妈清理一下吧!" )  
        	.setPositiveButton("确定",null )  
        	.show();*/
        	return 1;
        }
        /// 容量足够
        Log.d("DEBUG","The sd's availavle size is enough!");
/*        new  AlertDialog.Builder(activity)    
    	.setTitle("圣诞节" )  
    	.setMessage("小朋友,SD卡空间不足了哦,不能保存相册。快叫爸爸妈妈清理一下吧!" )  
    	.setPositiveButton("确定",null )  
    	.show();*/
        return 2;
    }

这里调用JNI方法,因此这段代码"com.mesrjni.MesrJni",要根据自己游戏进行适配;同时这里注意我注释的一段话,我会调用一个AlertDialog类,来提醒玩家出现问题。这里我将代码注释掉了,因为会出现一些很奇怪问题,其实与我下面JNI调用Toast类一样,后面讲。

保存图片,一般是保存在自己的一个文件夹中,因此我们要首先判断我们是否创建了文件夹,没有,就直接创建一个新的文件夹。这里代码简单,ps,在在创建文件之间,要确保SD可用,这个工作也是必须要做的,可参考上面代码

创建文件代码

File destDir = new File("/sdcard/Christmas/");
		  if (!destDir.exists()) {
		   destDir.mkdirs();
		  }

这里我创建了一个Christmas文件夹。

由于保存图片工作,在Android端比较慢,大约会有2s左右时间,这个时间还得看这个平台的硬件水平,在我们公司的平板上,一般是2s左右。因此,点击保存图片时,会出现游戏卡顿问题,那么玩家还以为是游戏没反应,会一直点击保存图片图标(这个现象,基本是玩过这个游戏的玩家,都会如此操作)。因此,很有必要添加一个提示保存图片中的提示标签

保存提示

保存提示功能,这里我直接使用了Android自带的Toast类,同时使用Toast类,只能是使用JNI非静态类调用,因此要先同时JNI静态类调用,或者App的object对象。

获取App的object对象:

.cpp

#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
	JniMethodInfo methodInfo;
	jobject jobj;
	bool isHave = JniHelper::getStaticMethodInfo(methodInfo,"getRunActivity","()Ljava/lang/Object;");
	if (isHave)
	{
		jobj = methodInfo.env->CallStaticObjectMethod(methodInfo.classID,methodInfo.methodID); 
	}

#else

.java

 /*
     * 获取this
     */
    public static Object getRunActivity() {
        System.out.println("----------GetRunActivity");
        return activity;
}

下面就是调用调用Toast类方法类,给出两端java不同代码,仔细分析问题出现的原因:

.cpp

#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
	JniMethodInfo methodInfo2;
	/// 中文转码
	std::string strInfo("图片已保存在系统相册中。");
	XtcUtils::GBKToUTF8(strInfo);
	const char* info	= strInfo.c_str();
	if (JniHelper::getMethodInfo(methodInfo2,"showToast","(Ljava/lang/String;)V"))
	{

		methodInfo2.env->CallVoidMethod(jobj,methodInfo2.methodID,methodInfo2.env->NewStringUTF(info));
	}
#else

#endif

.java

代码1:
  public void showToast(final String str) {
    	Handler handler = new Handler();
    	handler.post(new Runnable() {

    	   @Override
    	   public void run() {
    	    Toast.makeText(getApplicationContext(),str.toString(),Toast.LENGTH_SHORT).show();

    	   }
    	  });
}
代码2:
public void showToast(final String str) {
    	Handler handler = new Handler(getApplicationContext().getMainLooper());
    	handler.post(new Runnable() {

    	   @Override
    	   public void run() {
    	    Toast.makeText(getApplicationContext(),Toast.LENGTH_SHORT).show();

    	   }
    	  });
}

前一段代码,会导致一个Can't create handler inside thread that has not calledLooper.prepare(),至于具体,可参考文章http://www.cnblogs.com/sonicit/archive/2013/01/13/2858475.html,这里详细讲解了原因。

上一段代码调用函数GBKToUTF8,这个函数是用来c++与java转码问题,我在文章http://www.jb51.cc/article/p-wbwumdwo-qs.htmlhttp://www.jb51.cc/article/p-vyrtmmot-qs.html 中详细方法的原理与解决方法

到此文件读写操作细节,基本阐述完毕。

截屏细节:

截屏的效果,大家要参考的话,可以玩一下游戏Toca Boca 游戏《FairyTales》中截屏效果,ps:这个游戏的装扮做的真好。

对于截屏具体技术,上面已经分析过了。但是,由于这次游戏出现一个新问题,就是使用了CCLayerColor类,在游戏关灯时,会罩一个黑色但有透明的图层,表示晚上的感觉。下面两张图片,分别表示白天和夜晚。


由于夜晚使用的是的CCLayerColor,本身带有透明度,因此在截屏时,获取图片跟看起来不大一样。如果没有做混合处理,实际截取出来的图片,类似如下所示;

如何处理呢?就必须使用混出处理。什么是混出呢?这里可以参考以前写的一篇文章

http://www.jb51.cc/article/p-yepuiwgm-qs.html 这篇文章开头详细介绍了混合原理,以及如何做擦除效果,ps:类似刮彩票效果,也可以使用这个原理来做。

对于混合处理,比较关键是设置混合参数。这里提供一个网站:http://www.andersriggelsen.dk/glblendfunc.php, 这个网站可以非常方面的帮助我们调试参数。

根据调试结果,混合参数为:源--GL_ONE,目标--GL_ONE_MINUS_SRC_ALPHA。

下面给出详细代码

void HomeScene::ShowPicAlbum()
{
	SimpleAudioEngine::sharedEngine()->playEffect(CHR_MF_CAMERA_EXPOSURE);
	//根据要截取屏幕大小,定义一个渲染纹理  
	m_renderTextureSplot = CCRenderTexture::create(1280,800);

	CCScene* pCurScene = CCDirector::sharedDirector()->getRunningScene();
	CCPoint ancPos = pCurScene->getAnchorPoint();
	visibleNode();

	//渲染纹理开始捕捉  
	int outOpa = m_colorlayerTreeOut->getOpacity();
	int inOpa = m_colorlayerTreeIn->getOpacity();
	ccBlendFunc blendFunc = { GL_ONE,GL_ONE_MINUS_SRC_ALPHA};	///< 设置混合模式,这里不设置混合的话,关灯是,遮罩会有一种透明关系。
	ccBlendFunc blendFuncB = m_colorlayerTreeIn->getBlendFunc();
	m_colorlayerTreeOut->setBlendFunc(blendFunc);	
	m_colorlayerTreeIn->setBlendFunc(blendFunc);
	m_renderTextureSplot->begin();
	//绘制当前场景  
	pCurScene->visit();
	//结束  
	……(下面是一些动作代码)
}
基本上,这次做的游戏截屏细节差不多就这些了,还有一些测试出来的bug,跟这个没什么关联,因此不阐述了。由于代码是从项目里抽出来的,因此不可能完整将代码公式出来,就不是上传代码到自己的Githup上了。

希望上述,对大家有些有帮助。

猜你在找的Cocos2d-x相关文章