一条SQL搞定 插入数据主键重复或数据已经存在,则更新这条数据

数据库atool 发表了文章 • 0 个评论 • 516 次浏览 • 2016-04-22 14:20 • 来自相关话题

在做数据库开发的时候,经常会遇到这样的一种情景:

当一条数据不存在的时候,插入这条数据,如果这条数据的主键已经在数据库中存在,那么更新这条数据。

你们一般怎么做呢?先根据主键查询数据,然后判断是否存在数据,如果存在数据,则update字段,否则insert数据。

这样做的弊端就是需要两次连接数据库服务器,然后利用高级语言来判断是否存在的逻辑。

下面教你一条SQL语句,教你解决这类问题!

例如数据表weixin_user的表结构如下所示:(博客转移,图片丢失)
$sql = "insert into weixin_user(wx_id, wx_name, wx_state, wx_info, wx_lasttime) values ('$wx_id', '$wx_name', '$wx_state', '$wx_info', NOW()) ON DUPLICATE KEY UPDATE wx_name='$wx_name', wx_state = '$wx_state', wx_info = '$wx_info', wx_lasttime = NOW();";上面这段SQL语句,是本博客的微信和易信接口开发代码中的一条SQL语句(weixin_user用于记录微信易信粉丝的状态),下面图片中显示的就是这段代码,看到这个,你就懂了吧?

ON DUPLICATE KEY UPDATE(当出现DUPLICATE KEY主键重复错误的时候触发Update操作,当然要求就是表在设计的时候一定要有主键primary key)

具体的效果是什么?本微信号就在使用,你关注一下就知道了... 查看全部
在做数据库开发的时候,经常会遇到这样的一种情景:

当一条数据不存在的时候,插入这条数据,如果这条数据的主键已经在数据库中存在,那么更新这条数据。

你们一般怎么做呢?先根据主键查询数据,然后判断是否存在数据,如果存在数据,则update字段,否则insert数据。

这样做的弊端就是需要两次连接数据库服务器,然后利用高级语言来判断是否存在的逻辑。

下面教你一条SQL语句,教你解决这类问题!

例如数据表weixin_user的表结构如下所示:(博客转移,图片丢失)
$sql = "insert into weixin_user(wx_id, wx_name, wx_state, wx_info, wx_lasttime) values ('$wx_id', '$wx_name', '$wx_state', '$wx_info', NOW()) ON DUPLICATE KEY UPDATE wx_name='$wx_name', wx_state = '$wx_state', wx_info = '$wx_info', wx_lasttime = NOW();";
上面这段SQL语句,是本博客的微信和易信接口开发代码中的一条SQL语句(weixin_user用于记录微信易信粉丝的状态),下面图片中显示的就是这段代码,看到这个,你就懂了吧?

ON DUPLICATE KEY UPDATE(当出现DUPLICATE KEY主键重复错误的时候触发Update操作,当然要求就是表在设计的时候一定要有主键primary key)

具体的效果是什么?本微信号就在使用,你关注一下就知道了...

xcode6打包ipa重签失败——无法安装

游戏开发atool 发表了文章 • 0 个评论 • 798 次浏览 • 2016-04-22 14:16 • 来自相关话题

升级xocde6之后,使用achieve打包, 然后zip成ipa包之后,送去重签,发现安装问题。

使用itools安装提示证书问题;

使用itunes安装,直接是灰色图标

原因很简单,改两项xcode配置即可:

1.Project - Target - Building Setting - Code Signing中的Code Signing Resource Rules Path项,这一项在Xcode5中是不存在的,升级之后,这一项默认为空,设置为:$(SDKROOT)/ResourceRules.plist 即可,Xcode会自动换成相应的路径;

2.targets中的名字都不能带有空格,很多项目默认生成的target name是: project空格iOS 和 project空格Mac,双击修改即可。

具体看如下的图片:(博客转移,图片丢失)
  查看全部
升级xocde6之后,使用achieve打包, 然后zip成ipa包之后,送去重签,发现安装问题。

使用itools安装提示证书问题;

使用itunes安装,直接是灰色图标

原因很简单,改两项xcode配置即可:

1.Project - Target - Building Setting - Code Signing中的Code Signing Resource Rules Path项,这一项在Xcode5中是不存在的,升级之后,这一项默认为空,设置为:$(SDKROOT)/ResourceRules.plist 即可,Xcode会自动换成相应的路径;

2.targets中的名字都不能带有空格,很多项目默认生成的target name是: project空格iOS 和 project空格Mac,双击修改即可。

具体看如下的图片:(博客转移,图片丢失)
 

Cocos2d-x Lua/Javascript脚本代码加密实现

游戏开发atool 发表了文章 • 0 个评论 • 682 次浏览 • 2016-04-22 14:15 • 来自相关话题

在游戏开发中,脚本作为一种资源文件,就像图片视频一样,被引擎所引用,使用脚本做游戏的好处就在于可以在线patch更新,特别对于苹果App Store审核期很长的情况。

如果不对脚本进行加密,不怀好意的人松松解压出脚本文件,给你瞬间复制一个游戏出来。

1.异或加密解密

最简单的一种加密方式,虽然简单,但是也比较实用。但是防破解方面确实一般,如果你有其他严格的仿破解需求,可以将这部分加密算法换成你自己的复杂算法,不过保证解密效率。下面是采用C++简单实现的对文件进行加密之后保存到原文件中(注意对原始未加密文件进行备份)
#include "stdafx.h"
#include<iostream>
#include<ctime>
#include<fstream>
using namespace std;
void Makecode(char *pstr,int *pkey);
void Cutecode(char *pstr,int *pkey);
void encode_file(char *f);
int _tmain(int argc, _TCHAR* argv[])
{
encode_file("e:/src/ResultScene.lua");
/*
encode_file("e:/src/ReadyScene.lua");
encode_file("e:/src/GameScene.lua");
encode_file("e:/src/PutHeadScene.lua");
encode_file("e:/src/TutorialsScene.lua");
encode_file("e:/src/WordsCategoryScene.lua");
encode_file("e:/src/common/DictHelper.lua");
encode_file("e:/src/common/LJ.lua");
encode_file("e:/src/common/DQueue.lua");
encode_file("e:/src/common/UIHelper.lua");
*/
int c;
cin>>c;
return 0;
}
void encode_file(char *f)
{
FILE *fp = NULL;
fopen_s(&fp, f,"rb");
fseek(fp,0,SEEK_END); //定位到文件末
int nFileLen = ftell(fp); //文件长度
cout << "file len = " << nFileLen << endl;
fseek(fp, 0, SEEK_SET);
char *fileContent = NULL;
fileContent = (char *) malloc ((nFileLen + 1) * sizeof(char));//增加一位
memset(fileContent, 0, nFileLen + 1);
fileContent[nFileLen] = '';//最后一位置为结束位
fread_s(fileContent,nFileLen, 1, nFileLen, fp);
//fread(buf,nFileLen, 1, fp);
//cout<<"解密前:"<<fileContent<<endl;
fclose(fp);
cout<<"文件:"<<f<<endl;
cout<<"解密前文件大小:"<<strlen(fileContent)<<endl;
int key[]={1, 2, 6, 1, 2, 6};//加密字符
char *p=fileContent;
cout<<"====="<<endl;
Makecode(fileContent,key);//加密
//cout<<"加密后:"<<p<<endl;
cout<<"加密后文件大小:"<<strlen(fileContent)<<endl;
FILE *stream = NULL;
fopen_s(&stream, f,"wb");
if (stream == NULL) /* open file TEST.$$$ */
{
fprintf(stderr, "Cannot open output file.
");
}
else {
fwrite(p, nFileLen, 1, stream); /* 写的struct文件*/
fclose(stream); /*关闭文件*/
}
cout<<"====="<<endl;
Cutecode(fileContent,key);//解密
//cout<<"解密后:"<<fileContent<<endl;
}
//单个字符异或运算
char MakecodeChar(char c,int key){
return c=c^key;
}
//单个字符解密
char CutcodeChar(char c,int key){
return c^key;
}
//加密
void Makecode(char *pstr,int *pkey){
int len=strlen(pstr);//获取长度
for(int i=0;i<len;i++)
*(pstr+i)=MakecodeChar(*(pstr+i),pkey[i%5]);
}
//解密
void Cutecode(char *pstr,int *pkey){
int len=strlen(pstr);
for(int i=0;i<len;i++)
*(pstr+i)=CutcodeChar(*(pstr+i),pkey[i%5]);
}2.修改Cocos2d-x引擎中加载lua脚本文件(或者js文件)的入口,在加载的时候对其进行解密。可能不同版本引擎有不同的入口文件,在Cocos2d-x3.0中,对应的是文件Cocos2dxLuaLoader.cpp文件中的int cocos2dx_lua_loader(lua_State *L)方法,对其进行修改成如下:
#include "Cocos2dxLuaLoader.h"
#include <string>
#include <algorithm>
#include<iostream>
using namespace cocos2d;
extern "C"
{
//单个字符异或运算
char MakecodeChar(char c,int key){
return c=c^key;
}
//单个字符解密
char CutcodeChar(char c,int key){
return c^key;
}
//加密
void Makecode(char *pstr,int *pkey){
int len=strlen(pstr);//获取长度
for(int i=0;i<len;i++)
*(pstr+i)=MakecodeChar(*(pstr+i),pkey[i%5]);
}
//解密
void Cutecode(char *pstr,int *pkey){
int len=strlen(pstr);
for(int i=0;i<len;i++)
*(pstr+i)=CutcodeChar(*(pstr+i),pkey[i%5]);
}
int cocos2dx_lua_loader(lua_State *L)
{
std::string filename(luaL_checkstring(L, 1));
size_t pos = filename.rfind(".lua");
if (pos != std::string::npos)
{
filename = filename.substr(0, pos);
}

pos = filename.find_first_of(".");
while (pos != std::string::npos)
{
filename.replace(pos, 1, "/");
pos = filename.find_first_of(".");
}
filename.append(".lua");

Data data = FileUtils::getInstance()->getDataFromFile(filename);

if (!data.isNull())
{

//====code decode start==================================
log("===encode filename:%s===", filename.c_str());
//如果filename == 'main.lua',则解密
char *fileContent = (char*)data.getBytes();
int key[]={1, 2, 6, 1, 2, 6};//加密字符
char *fileContentDecoded = NULL;
if (strcmp(filename.c_str(),"ReadyScene.lua")==0 ||
strcmp(filename.c_str(),"GameScene.lua")==0 ||
strcmp(filename.c_str(),"PutHeadScene.lua")==0 ||
strcmp(filename.c_str(),"TutorialsScene.lua")==0 ||
strcmp(filename.c_str(),"WordsCategoryScene.lua")==0 ||
strcmp(filename.c_str(),"DictHelper.lua")==0 ||
strcmp(filename.c_str(),"LJ.lua")==0 ||
strcmp(filename.c_str(),"ResultScene.lua")==0 ||
strcmp(filename.c_str(),"DQueue.lua")==0 ||
strcmp(filename.c_str(),"UIHelper.lua")==0 ) {
if (data.getSize() < strlen(fileContent)) {
fileContentDecoded = (char *) malloc ((data.getSize() + 1) * sizeof(char));//增加一位
memset(fileContentDecoded, 0, data.getSize() + 1);
fileContentDecoded[data.getSize()] = '';//最后一位置为结束位
strncpy(fileContentDecoded,fileContent,data.getSize());
fileContent = NULL;
}
else {
fileContentDecoded = fileContent;
}
Cutecode(fileContentDecoded,key);//解密
}
else {
fileContentDecoded = fileContent;
}
//====code decode end==================================

if (luaL_loadbuffer(L, fileContentDecoded, data.getSize(), filename.c_str()) != 0)
{
luaL_error(L, "error loading module %s from file %s :
%s",
lua_tostring(L, 1), filename.c_str(), lua_tostring(L, -1));
}
}
else
{
log("can not get file data of %s", filename.c_str());
}
return 1;
}
}注意加密解密的key保证一致。

代码注释应该挺完善的,不进行解释了,Enjoy~ 查看全部
在游戏开发中,脚本作为一种资源文件,就像图片视频一样,被引擎所引用,使用脚本做游戏的好处就在于可以在线patch更新,特别对于苹果App Store审核期很长的情况。

如果不对脚本进行加密,不怀好意的人松松解压出脚本文件,给你瞬间复制一个游戏出来。

1.异或加密解密

最简单的一种加密方式,虽然简单,但是也比较实用。但是防破解方面确实一般,如果你有其他严格的仿破解需求,可以将这部分加密算法换成你自己的复杂算法,不过保证解密效率。下面是采用C++简单实现的对文件进行加密之后保存到原文件中(注意对原始未加密文件进行备份)
#include "stdafx.h"
#include<iostream>
#include<ctime>
#include<fstream>
using namespace std;
void Makecode(char *pstr,int *pkey);
void Cutecode(char *pstr,int *pkey);
void encode_file(char *f);
int _tmain(int argc, _TCHAR* argv[])
{
encode_file("e:/src/ResultScene.lua");
/*
encode_file("e:/src/ReadyScene.lua");
encode_file("e:/src/GameScene.lua");
encode_file("e:/src/PutHeadScene.lua");
encode_file("e:/src/TutorialsScene.lua");
encode_file("e:/src/WordsCategoryScene.lua");
encode_file("e:/src/common/DictHelper.lua");
encode_file("e:/src/common/LJ.lua");
encode_file("e:/src/common/DQueue.lua");
encode_file("e:/src/common/UIHelper.lua");
*/
int c;
cin>>c;
return 0;
}
void encode_file(char *f)
{
FILE *fp = NULL;
fopen_s(&fp, f,"rb");
fseek(fp,0,SEEK_END); //定位到文件末
int nFileLen = ftell(fp); //文件长度
cout << "file len = " << nFileLen << endl;
fseek(fp, 0, SEEK_SET);
char *fileContent = NULL;
fileContent = (char *) malloc ((nFileLen + 1) * sizeof(char));//增加一位
memset(fileContent, 0, nFileLen + 1);
fileContent[nFileLen] = '';//最后一位置为结束位
fread_s(fileContent,nFileLen, 1, nFileLen, fp);
//fread(buf,nFileLen, 1, fp);
//cout<<"解密前:"<<fileContent<<endl;
fclose(fp);
cout<<"文件:"<<f<<endl;
cout<<"解密前文件大小:"<<strlen(fileContent)<<endl;
int key[]={1, 2, 6, 1, 2, 6};//加密字符
char *p=fileContent;
cout<<"====="<<endl;
Makecode(fileContent,key);//加密
//cout<<"加密后:"<<p<<endl;
cout<<"加密后文件大小:"<<strlen(fileContent)<<endl;
FILE *stream = NULL;
fopen_s(&stream, f,"wb");
if (stream == NULL) /* open file TEST.$$$ */
{
fprintf(stderr, "Cannot open output file.
");
}
else {
fwrite(p, nFileLen, 1, stream); /* 写的struct文件*/
fclose(stream); /*关闭文件*/
}
cout<<"====="<<endl;
Cutecode(fileContent,key);//解密
//cout<<"解密后:"<<fileContent<<endl;
}
//单个字符异或运算
char MakecodeChar(char c,int key){
return c=c^key;
}
//单个字符解密
char CutcodeChar(char c,int key){
return c^key;
}
//加密
void Makecode(char *pstr,int *pkey){
int len=strlen(pstr);//获取长度
for(int i=0;i<len;i++)
*(pstr+i)=MakecodeChar(*(pstr+i),pkey[i%5]);
}
//解密
void Cutecode(char *pstr,int *pkey){
int len=strlen(pstr);
for(int i=0;i<len;i++)
*(pstr+i)=CutcodeChar(*(pstr+i),pkey[i%5]);
}
2.修改Cocos2d-x引擎中加载lua脚本文件(或者js文件)的入口,在加载的时候对其进行解密。可能不同版本引擎有不同的入口文件,在Cocos2d-x3.0中,对应的是文件Cocos2dxLuaLoader.cpp文件中的int cocos2dx_lua_loader(lua_State *L)方法,对其进行修改成如下:
#include "Cocos2dxLuaLoader.h"
#include <string>
#include <algorithm>
#include<iostream>
using namespace cocos2d;
extern "C"
{
//单个字符异或运算
char MakecodeChar(char c,int key){
return c=c^key;
}
//单个字符解密
char CutcodeChar(char c,int key){
return c^key;
}
//加密
void Makecode(char *pstr,int *pkey){
int len=strlen(pstr);//获取长度
for(int i=0;i<len;i++)
*(pstr+i)=MakecodeChar(*(pstr+i),pkey[i%5]);
}
//解密
void Cutecode(char *pstr,int *pkey){
int len=strlen(pstr);
for(int i=0;i<len;i++)
*(pstr+i)=CutcodeChar(*(pstr+i),pkey[i%5]);
}
int cocos2dx_lua_loader(lua_State *L)
{
std::string filename(luaL_checkstring(L, 1));
size_t pos = filename.rfind(".lua");
if (pos != std::string::npos)
{
filename = filename.substr(0, pos);
}

pos = filename.find_first_of(".");
while (pos != std::string::npos)
{
filename.replace(pos, 1, "/");
pos = filename.find_first_of(".");
}
filename.append(".lua");

Data data = FileUtils::getInstance()->getDataFromFile(filename);

if (!data.isNull())
{

//====code decode start==================================
log("===encode filename:%s===", filename.c_str());
//如果filename == 'main.lua',则解密
char *fileContent = (char*)data.getBytes();
int key[]={1, 2, 6, 1, 2, 6};//加密字符
char *fileContentDecoded = NULL;
if (strcmp(filename.c_str(),"ReadyScene.lua")==0 ||
strcmp(filename.c_str(),"GameScene.lua")==0 ||
strcmp(filename.c_str(),"PutHeadScene.lua")==0 ||
strcmp(filename.c_str(),"TutorialsScene.lua")==0 ||
strcmp(filename.c_str(),"WordsCategoryScene.lua")==0 ||
strcmp(filename.c_str(),"DictHelper.lua")==0 ||
strcmp(filename.c_str(),"LJ.lua")==0 ||
strcmp(filename.c_str(),"ResultScene.lua")==0 ||
strcmp(filename.c_str(),"DQueue.lua")==0 ||
strcmp(filename.c_str(),"UIHelper.lua")==0 ) {
if (data.getSize() < strlen(fileContent)) {
fileContentDecoded = (char *) malloc ((data.getSize() + 1) * sizeof(char));//增加一位
memset(fileContentDecoded, 0, data.getSize() + 1);
fileContentDecoded[data.getSize()] = '';//最后一位置为结束位
strncpy(fileContentDecoded,fileContent,data.getSize());
fileContent = NULL;
}
else {
fileContentDecoded = fileContent;
}
Cutecode(fileContentDecoded,key);//解密
}
else {
fileContentDecoded = fileContent;
}
//====code decode end==================================

if (luaL_loadbuffer(L, fileContentDecoded, data.getSize(), filename.c_str()) != 0)
{
luaL_error(L, "error loading module %s from file %s :
%s",
lua_tostring(L, 1), filename.c_str(), lua_tostring(L, -1));
}
}
else
{
log("can not get file data of %s", filename.c_str());
}
return 1;
}
}
注意加密解密的key保证一致。

代码注释应该挺完善的,不进行解释了,Enjoy~

mysql数据库AUTO_INCREMENT 引起的id异常

数据库atool 发表了文章 • 0 个评论 • 606 次浏览 • 2016-04-22 14:13 • 来自相关话题

mysql设置id为自增属性,由于这个属性导致的一个bug。详细场景不描述了。

问题解决:

后来在MySQL官方找到对应的答案,原因是那天开新服时程序重启了数据库而暴露出这个BUG:

If you specify an AUTO_INCREMENT column for an InnoDBtable, the table handle in the InnoDB data dictionarycontains a special counter called the auto-increment counter that is used inassigning new values for the column. This counter is stored only in mainmemory, not on disk.

From:http://dev.mysql.com/doc/refma ... .html

简单来说,在InnoDB的表制定一个递增初始值时,innodb会为其维护一个auto-incrememt的计数器;但是这个计数器只保存在内存中,而不是在disk上。

而重启之后获得索引通过下面这种语法:

SELECTMAX(ai_col) FROM t FOR UPDATE;

获取到递增列的最大的值来开始递增,如果该表为空的话,那么就会从1开始递增。

【解决方案】

在创表时插入一个以1000开始的无效数据,确保重启mysql后新的递增初始值正常。同时查看其他表是否有类似问题,在后面涉及新建表时回归此类情况。 查看全部
mysql设置id为自增属性,由于这个属性导致的一个bug。详细场景不描述了。

问题解决:

后来在MySQL官方找到对应的答案,原因是那天开新服时程序重启了数据库而暴露出这个BUG:

If you specify an AUTO_INCREMENT column for an InnoDBtable, the table handle in the InnoDB data dictionarycontains a special counter called the auto-increment counter that is used inassigning new values for the column. This counter is stored only in mainmemory, not on disk.

From:http://dev.mysql.com/doc/refma ... .html

简单来说,在InnoDB的表制定一个递增初始值时,innodb会为其维护一个auto-incrememt的计数器;但是这个计数器只保存在内存中,而不是在disk上。

而重启之后获得索引通过下面这种语法:

SELECTMAX(ai_col) FROM t FOR UPDATE;

获取到递增列的最大的值来开始递增,如果该表为空的话,那么就会从1开始递增。

【解决方案】

在创表时插入一个以1000开始的无效数据,确保重启mysql后新的递增初始值正常。同时查看其他表是否有类似问题,在后面涉及新建表时回归此类情况。

uwsgi部署到nginx出现invalid request block size: 4161 (max 4096)...skip问题

服务端atool 发表了文章 • 0 个评论 • 1640 次浏览 • 2016-04-22 14:11 • 来自相关话题

使用Flask制作一个网页平台之后,登陆使用openid登陆,然后使用uwsgi服务部署到nginx上,运行起来没有什么问题,但是偶尔在登陆的时候出现502的错误,一般登陆成功之后后面的任何操作都不会出错。

查看uwsgi的log之后,发现出现这样的一个错误:

invalid request block size: 4161 (max 4096)...skip

之前一个没有去详细搜索过,也没有具体去看错误产生的原因,因为只是偶尔出现,并且有时候重试的时候是可以登陆的,所以没有太多的去关注,今天因为在会议演示的过程中又出现这个问题,所以不得不重视了。

搜索一下,其实问题很简单:url地址长度超过了4096个字符,而4096就是uwsgi配置中buffer-size的默认值,所以只需要将buffer-size改大一点即可。

我是使用uwsgi -x 指定uwsgi配置文件来启动服务器的,所以只需要修改成以下方式启动即可:

uwsgi -x platform.uwsgi.xml --buffer-size 32768

即在后面增加一个 --buffer-size 32768

问题基本就解决了,至于为什么这个问题是偶尔出现?那是因为openid登陆的时候会携带一个参数叫next_url,这个地址是用来指定登陆成功之后返回到哪里地址,如果这个next_url太长就会导致url地址超过4096,有时候next_url=/,即网站根地址,url地址长度就不会超过4096。另外还和openid返回的登陆人信息长度有关系,导致有些人从来不会出现这个错误,有些人偶尔出现这个问题。

Enjoy~ 查看全部
使用Flask制作一个网页平台之后,登陆使用openid登陆,然后使用uwsgi服务部署到nginx上,运行起来没有什么问题,但是偶尔在登陆的时候出现502的错误,一般登陆成功之后后面的任何操作都不会出错。

查看uwsgi的log之后,发现出现这样的一个错误:

invalid request block size: 4161 (max 4096)...skip

之前一个没有去详细搜索过,也没有具体去看错误产生的原因,因为只是偶尔出现,并且有时候重试的时候是可以登陆的,所以没有太多的去关注,今天因为在会议演示的过程中又出现这个问题,所以不得不重视了。

搜索一下,其实问题很简单:url地址长度超过了4096个字符,而4096就是uwsgi配置中buffer-size的默认值,所以只需要将buffer-size改大一点即可。

我是使用uwsgi -x 指定uwsgi配置文件来启动服务器的,所以只需要修改成以下方式启动即可:

uwsgi -x platform.uwsgi.xml --buffer-size 32768

即在后面增加一个 --buffer-size 32768

问题基本就解决了,至于为什么这个问题是偶尔出现?那是因为openid登陆的时候会携带一个参数叫next_url,这个地址是用来指定登陆成功之后返回到哪里地址,如果这个next_url太长就会导致url地址超过4096,有时候next_url=/,即网站根地址,url地址长度就不会超过4096。另外还和openid返回的登陆人信息长度有关系,导致有些人从来不会出现这个错误,有些人偶尔出现这个问题。

Enjoy~

cocos2d-x中使用FMOD音效引擎基本知识介绍

游戏开发atool 发表了文章 • 0 个评论 • 2105 次浏览 • 2016-04-22 13:23 • 来自相关话题

大致研究了2.5天的Fmod音效引擎,从对音效没有什么概念到有一点点理解。fmod是一个很不错的音效引擎,从它的API demo运行效果可以感受到引擎音效非常好,它提供了很简单的API来控制音效的3D播放特性。

一、FMOD引擎API版本介绍

fmod引擎api因为其版本的原因,分成两种:

1. 对应FMOD Designer版本的API,FMOD Designer为旧版本引擎,导出文件为fev和fsb,fev定义为音效的播放规则,音乐索引(大致可以这么理解)等;fsb主要是音乐文件的打包,目前音效组使用的FMOD Designer做音效开发。

2. 对应FMOD Studio版本的API,FMOD Studio是新版本引擎,导出文件为.bank文件,其中包含有播放规则,音乐索引,音乐文件(新版本将旧版本中的两个导出文件变成一个,提供了一个很好的特性:可以播放apk中的音效文件)。

二、在cocos2d-x中FMOD API调用方式

同样有两种途径:

1. 使用FMOD提供的android api和ios api分别开发播放接口,然后再lua中调用。

    1.1 优点在于难点少,相比方法2简单。

    1.2 缺点在于需要做两套代码,工作量大,另外一个致命的问题是:音效延迟播放(因为LUA调用Java只能是静态方法,所以在Java采用线程扫描状态的方式,存在一定的播放延迟),个人觉得对于音效来说不能忍,人的视觉几乎无法区分1/60s内的变换,但是听觉就不一样了。点击一个按钮,在20ms内响应完全没有问题,但是如果声音在20ms后播放就存在很大的问题了。

2. 使用FMOD的cpp接口,首先在cocos2d-x的底层平台写好cpp的播放代码接口,然后采用tolua++导出lua可以使用的内库,然后编写lua调用的接口,最后可以在cocos2d-x + lua上进行播放。

    1.1 优点在于跨平台,一次解决可以适用于android和ios,同时对于以后的项目也可以直接使用,非常有必要做这个工作。

    1.2 缺点在于难点多,漏洞多,涉及到cocos2d-x底层接口开发,以及cpp和lua之间的接口开发,这些之前都没有做过,难度比较大,另外,涉及到cpp底层代码,容易造成一些难以解决的漏洞。

三、FMOD Designer VS FMOD Studio

两个版本一个旧,一个新,大致说说他们最致命的问题:

1. FMOD Designer公司音效人员在使用,这是其优点,但是FMOD Designer版本的缺点很明显就是在android平台无法播放apk中的音效,这意味着和PC游戏一样,安装工程或者第一次运行的时候需要解压apk中的音效文件到应用目录中,并且在以后的每次运行过程中必须检测音效文件是否存在,如果不存在或者缺失,需要将音效重写解压出来。这不仅增加游戏的另一份工作量,也减少了游戏的运行效率。(一般的手机清理软件都会造成app,目录被删除)

2. FMOD Studio新版本很好的一个特性就是可以直接读取apk中音效文件(网络上关于FMOD资源不多,这一点还是发邮件和官方沟通得知的);但是FMOD Studio并没有被公司音效使用。

四、FMOD适用环境

FMOD具有很简单的API操作,并提供不简单的3D音效特性,然而在cocos2d-x中使用FMOD引擎需要做引擎文件的解析操作,会增加一定的工作量,因此建议在游戏存在3D特性的情况下使用,例如:音效距离、音效方向、音效源移动等情况使用;而在一般的UI界面操作中不要使用fmod,而是使用普通的wav,mp3,ogg等cocos2d-x可以解析的音效即可。 查看全部
大致研究了2.5天的Fmod音效引擎,从对音效没有什么概念到有一点点理解。fmod是一个很不错的音效引擎,从它的API demo运行效果可以感受到引擎音效非常好,它提供了很简单的API来控制音效的3D播放特性。

一、FMOD引擎API版本介绍

fmod引擎api因为其版本的原因,分成两种:

1. 对应FMOD Designer版本的API,FMOD Designer为旧版本引擎,导出文件为fev和fsb,fev定义为音效的播放规则,音乐索引(大致可以这么理解)等;fsb主要是音乐文件的打包,目前音效组使用的FMOD Designer做音效开发。

2. 对应FMOD Studio版本的API,FMOD Studio是新版本引擎,导出文件为.bank文件,其中包含有播放规则,音乐索引,音乐文件(新版本将旧版本中的两个导出文件变成一个,提供了一个很好的特性:可以播放apk中的音效文件)。

二、在cocos2d-x中FMOD API调用方式

同样有两种途径:

1. 使用FMOD提供的android api和ios api分别开发播放接口,然后再lua中调用。

    1.1 优点在于难点少,相比方法2简单。

    1.2 缺点在于需要做两套代码,工作量大,另外一个致命的问题是:音效延迟播放(因为LUA调用Java只能是静态方法,所以在Java采用线程扫描状态的方式,存在一定的播放延迟),个人觉得对于音效来说不能忍,人的视觉几乎无法区分1/60s内的变换,但是听觉就不一样了。点击一个按钮,在20ms内响应完全没有问题,但是如果声音在20ms后播放就存在很大的问题了。

2. 使用FMOD的cpp接口,首先在cocos2d-x的底层平台写好cpp的播放代码接口,然后采用tolua++导出lua可以使用的内库,然后编写lua调用的接口,最后可以在cocos2d-x + lua上进行播放。

    1.1 优点在于跨平台,一次解决可以适用于android和ios,同时对于以后的项目也可以直接使用,非常有必要做这个工作。

    1.2 缺点在于难点多,漏洞多,涉及到cocos2d-x底层接口开发,以及cpp和lua之间的接口开发,这些之前都没有做过,难度比较大,另外,涉及到cpp底层代码,容易造成一些难以解决的漏洞。

三、FMOD Designer VS FMOD Studio

两个版本一个旧,一个新,大致说说他们最致命的问题:

1. FMOD Designer公司音效人员在使用,这是其优点,但是FMOD Designer版本的缺点很明显就是在android平台无法播放apk中的音效,这意味着和PC游戏一样,安装工程或者第一次运行的时候需要解压apk中的音效文件到应用目录中,并且在以后的每次运行过程中必须检测音效文件是否存在,如果不存在或者缺失,需要将音效重写解压出来。这不仅增加游戏的另一份工作量,也减少了游戏的运行效率。(一般的手机清理软件都会造成app,目录被删除)

2. FMOD Studio新版本很好的一个特性就是可以直接读取apk中音效文件(网络上关于FMOD资源不多,这一点还是发邮件和官方沟通得知的);但是FMOD Studio并没有被公司音效使用。

四、FMOD适用环境

FMOD具有很简单的API操作,并提供不简单的3D音效特性,然而在cocos2d-x中使用FMOD引擎需要做引擎文件的解析操作,会增加一定的工作量,因此建议在游戏存在3D特性的情况下使用,例如:音效距离、音效方向、音效源移动等情况使用;而在一般的UI界面操作中不要使用fmod,而是使用普通的wav,mp3,ogg等cocos2d-x可以解析的音效即可。

一个轻量级 Python 装饰器的缓存库——wrapcache

服务端atool 发表了文章 • 0 个评论 • 842 次浏览 • 2016-04-22 13:22 • 来自相关话题

A python Function / Method OUTPUT cache system base on function Decorators.
github 地址:https://github.com/hustcc/wrapcache




 
一、使用场景

经常会在某些很小的场合需要缓存一些数据,提高一些性能,而这种缓存又不是经常需要,比如:

两个进程共享数据库,其中只读进程读取数据做一些操作,这个时候,可以将数据库内容缓存一下,避免重复读数据库;

一个web页面数据太多,然而页面并不需要完全的实时性,这个时候就可以将页面内容完全缓存,在过期时间之后,不读数据库,不进行大量计算,这种在一些报告页面非常常见。

这两个场景其实挺常见的,要完成也不难,无非就是存到python 字段,加一个时间戳,判断过期,如果是redis,就直接存redis,并赋予timeout时间就ok了。

自己因为经常遇到这种场景,所以将其封装成一个python库,方便使用。

二、如何使用

使用简单,只需要要在方法上面加一个装饰器即可缓存,并且设置缓存过期时间。import wrapcache
@wrapcache.wrapcache(timeout = 60)
def need_cache_function(input, t = 2, o = 3): sleep(2)
return random.randint(1, 100)以上即可,第一次运行需要 2 秒,第二次运行(过期时间 60 秒之内)瞬间给出缓存结果。适合于小场景的方法缓存。
 
三、安装方法
 
首先 pip install wrapcache,支持python2和python3。

然后import wrapcache。

最后在需要缓存的方法上加上装饰器即可@wrapcache.wrapcache(timeout = 3)

其中 @wrapcache.wrapcache(timeout = 3, adapter = RedisAdapter) 有两个参数:

timeout, 过期时间,默认为-1,不缓存数据

adapter,存储器,默认为MemoryAdapter(存到python的全局字典中),可选RedisAdapter(存储到redis中)

注意:如果选择adapter = RedisAdapter,则需要在使用前设置redis市里 调用 RedisAdapter.db = redis_instanceREDIS_CACHE_POOL = redis.ConnectionPool(host = 'xx.xxx.xx.xxx', port = 6379, password = 'redis_pwd', db = 2)
REDIS_CACHE_INST = redis.Redis(connection_pool = REDIS_CACHE_POOL, charset = 'utf8')
RedisAdapter.db = REDIS_CACHE_INST #初始化装饰器缓存四、TODO

目前是使用内存 dict 和 redis 存储缓存,后续要支持将 memcached 等服务器中,只需要补充 adapter 中代码,实现对应方法即可。

欢迎 push requst 和 issue 。 查看全部
A python Function / Method OUTPUT cache system base on function Decorators.
github 地址:https://github.com/hustcc/wrapcache
未命名.png

 
一、使用场景

经常会在某些很小的场合需要缓存一些数据,提高一些性能,而这种缓存又不是经常需要,比如:

两个进程共享数据库,其中只读进程读取数据做一些操作,这个时候,可以将数据库内容缓存一下,避免重复读数据库;

一个web页面数据太多,然而页面并不需要完全的实时性,这个时候就可以将页面内容完全缓存,在过期时间之后,不读数据库,不进行大量计算,这种在一些报告页面非常常见。

这两个场景其实挺常见的,要完成也不难,无非就是存到python 字段,加一个时间戳,判断过期,如果是redis,就直接存redis,并赋予timeout时间就ok了。

自己因为经常遇到这种场景,所以将其封装成一个python库,方便使用。

二、如何使用

使用简单,只需要要在方法上面加一个装饰器即可缓存,并且设置缓存过期时间。
import wrapcache
@wrapcache.wrapcache(timeout = 60)
def need_cache_function(input, t = 2, o = 3): sleep(2)
return random.randint(1, 100)
以上即可,第一次运行需要 2 秒,第二次运行(过期时间 60 秒之内)瞬间给出缓存结果。适合于小场景的方法缓存。
 
三、安装方法
 
首先 pip install wrapcache,支持python2和python3。

然后import wrapcache。

最后在需要缓存的方法上加上装饰器即可@wrapcache.wrapcache(timeout = 3)

其中 @wrapcache.wrapcache(timeout = 3, adapter = RedisAdapter) 有两个参数:

timeout, 过期时间,默认为-1,不缓存数据

adapter,存储器,默认为MemoryAdapter(存到python的全局字典中),可选RedisAdapter(存储到redis中)

注意:如果选择adapter = RedisAdapter,则需要在使用前设置redis市里 调用 RedisAdapter.db = redis_instance
REDIS_CACHE_POOL = redis.ConnectionPool(host = 'xx.xxx.xx.xxx', port = 6379, password = 'redis_pwd', db = 2)
REDIS_CACHE_INST = redis.Redis(connection_pool = REDIS_CACHE_POOL, charset = 'utf8')
RedisAdapter.db = REDIS_CACHE_INST #初始化装饰器缓存
四、TODO

目前是使用内存 dict 和 redis 存储缓存,后续要支持将 memcached 等服务器中,只需要补充 adapter 中代码,实现对应方法即可。

欢迎 push requst 和 issue 。

Error Domain=AVFoundationErrorDomain Code=-11823 "无法存储"

移动开发atool 发表了文章 • 0 个评论 • 2520 次浏览 • 2016-04-22 13:19 • 来自相关话题

在IOS上使用AVFoundation的库进行视频录制,出现错误:

Error Domain=AVFoundationErrorDomain Code=-11823 "无法存储" UserInfo=0x1782f8a00 {NSUnderlyingError=0x17064f4e0 "The operation couldn’t be completed. (OSStatus error -12672.)", AVErrorRecordingSuccessfullyFinishedKey=false, NSLocalizedRecoverySuggestion=请重试存储。, NSLocalizedDescription=无法存储},video.mov -- file:///var/mobile/Applications/D0CA65C7-F218-46F1-80BC-B0FA886EDF3B/Documents/

对于一些英文语言的机器,显示的是:

Error Domain=AVFoundationErrorDomain Code=-11823 "Cannot Save" UserInfo=0x16fb20 {NSLocalizedRecoverySuggestion=Try saving again., NSLocalizedDescription=Cannot Save}

出现Error Domain=AVFoundationErrorDomain Code=-11823这个错误的原因都是需要写文件,但是文件是存在的。不知道为什么ios不允许直接覆盖?在android上,同样的逻辑没有任何问题。

知道问题的原因,那么解决也非常简单,每次再录制视频之前(写文件之前),检查文件是否存在,存在则删除即可:

//如果文件存在,则删除
if ([[NSFileManager defaultManager] fileExistsAtPath:[documentsDirPath stringByAppendingPathComponent:@"video.mp4"]]) {
NSError *error;
if ([[NSFileManager defaultManager] removeItemAtPath:[documentsDirPath stringByAppendingPathComponent:@"video.mp4"] error:&error] == NO) {
NSLog(@"removeitematpath %@ error :%@", [documentsDirPath stringByAppendingPathComponent:@"video.mp4"], error);
}
}修改之后,没有任何问题。Enjoy~ 查看全部
在IOS上使用AVFoundation的库进行视频录制,出现错误:

Error Domain=AVFoundationErrorDomain Code=-11823 "无法存储" UserInfo=0x1782f8a00 {NSUnderlyingError=0x17064f4e0 "The operation couldn’t be completed. (OSStatus error -12672.)", AVErrorRecordingSuccessfullyFinishedKey=false, NSLocalizedRecoverySuggestion=请重试存储。, NSLocalizedDescription=无法存储},video.mov -- file:///var/mobile/Applications/D0CA65C7-F218-46F1-80BC-B0FA886EDF3B/Documents/

对于一些英文语言的机器,显示的是:

Error Domain=AVFoundationErrorDomain Code=-11823 "Cannot Save" UserInfo=0x16fb20 {NSLocalizedRecoverySuggestion=Try saving again., NSLocalizedDescription=Cannot Save}

出现Error Domain=AVFoundationErrorDomain Code=-11823这个错误的原因都是需要写文件,但是文件是存在的。不知道为什么ios不允许直接覆盖?在android上,同样的逻辑没有任何问题。

知道问题的原因,那么解决也非常简单,每次再录制视频之前(写文件之前),检查文件是否存在,存在则删除即可:

//如果文件存在,则删除
if ([[NSFileManager defaultManager] fileExistsAtPath:[documentsDirPath stringByAppendingPathComponent:@"video.mp4"]]) {
NSError *error;
if ([[NSFileManager defaultManager] removeItemAtPath:[documentsDirPath stringByAppendingPathComponent:@"video.mp4"] error:&error] == NO) {
NSLog(@"removeitematpath %@ error :%@", [documentsDirPath stringByAppendingPathComponent:@"video.mp4"], error);
}
}
修改之后,没有任何问题。Enjoy~

Cocos2d-x 横屏竖屏异常uncaught exception“UIApplicationInvalidInterfaceOrientation”

游戏开发atool 发表了文章 • 0 个评论 • 867 次浏览 • 2016-04-22 13:18 • 来自相关话题

在Cocos2d-x横屏游戏中集成社会化分享的时候,会出现异常

*** Terminating app due to uncaught exception 'UIApplicationInvalidInterfaceOrientation', reason: 'Supported orientations has no common orientation with the application, and shouldAutorotate is returning YES'

大致意思是cocos2d-x游戏设置的可支持屏幕方式找不到需要的方向。这是因为游戏中设置的是只支持横屏方式,但是在RootViewController中shouldAutorotate返回的又是YES,恰好分享的时候弹出的授权页面时竖屏的,所以弹出框自动旋转到竖屏,确又找不到可以支持的竖屏方向,所以抛出异常,程序也卡死了,解决办法如下,仅仅针对于Cocos2d-x代码,其他APP如果出现类似问题,可以参考:

1.AppController.mm加入方法
-(NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
return UIInterfaceOrientationMaskAll;
}
else {
return UIInterfaceOrientationMaskAllButUpsideDown;
}
}2.RootViewController.mm 加入方法
//For ios6, use supportedInterfaceOrientations & shouldAutorotate instead
- (NSUInteger) supportedInterfaceOrientations{
#ifdef __IPHONE_6_0
return UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskLandscapeLeft;//方向也可以仅定义为横屏中的一个方向
#endif
}如此就可以了,反正我是解决了~兼容性已测,不存在问题!Enjoy~ 查看全部
在Cocos2d-x横屏游戏中集成社会化分享的时候,会出现异常

*** Terminating app due to uncaught exception 'UIApplicationInvalidInterfaceOrientation', reason: 'Supported orientations has no common orientation with the application, and shouldAutorotate is returning YES'

大致意思是cocos2d-x游戏设置的可支持屏幕方式找不到需要的方向。这是因为游戏中设置的是只支持横屏方式,但是在RootViewController中shouldAutorotate返回的又是YES,恰好分享的时候弹出的授权页面时竖屏的,所以弹出框自动旋转到竖屏,确又找不到可以支持的竖屏方向,所以抛出异常,程序也卡死了,解决办法如下,仅仅针对于Cocos2d-x代码,其他APP如果出现类似问题,可以参考:

1.AppController.mm加入方法
-(NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
return UIInterfaceOrientationMaskAll;
}
else {
return UIInterfaceOrientationMaskAllButUpsideDown;
}
}
2.RootViewController.mm 加入方法
//For ios6, use supportedInterfaceOrientations & shouldAutorotate instead
- (NSUInteger) supportedInterfaceOrientations{
#ifdef __IPHONE_6_0
return UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskLandscapeLeft;//方向也可以仅定义为横屏中的一个方向
#endif
}
如此就可以了,反正我是解决了~兼容性已测,不存在问题!Enjoy~

Cocos2d-x android使用onKeyDown监听返回键实现二次返回退出

游戏开发atool 发表了文章 • 0 个评论 • 2547 次浏览 • 2016-04-22 13:16 • 来自相关话题

一般的游戏或者软件,都会在android版本上做退出程序的功能,一般的实现方式有两种:

1.点击返回按键,弹出确认是否退出;

2.点击返回,toast提示再次点击退出程序。

这两种方式实现都需要android上监听用户按下了android手机上的返回键,监听到了之后执行相应的操作。

通过搜索,很容易知道可以实现activity类的方法
public boolean onKeyDown(final int pKeyCode, final KeyEvent pKeyEvent)实现对用户按键的监听,但是将代码加入到cocos2d-x生成的Java代码中的AppActivity中,发现根本不起作用,没法监听到用户的按键操作,找官方api文档,发现:

public boolean onKeyDown (int keyCode, KeyEvent event)

Since: API Level 1

Called when a key was pressed down and not handled by any of the views inside of the activity. So, for example, key presses while the cursor is inside a TextView will not trigger the event (unless it is a navigation to another object) because TextView handles its own key presses.

If the focused view didn't want this event, this method is called.

这段文字我需要强调的是红色标记部分,翻译的意思是:当用户按下一个按键的时候调用,但是前提是这个事件没有被其他的任何views监听并处理。

因此不难得知,在cocos2d-x游戏中,在实现的onKeyDown方法监听不到返回键,是因为这个返回键事件已经被其他的onKeyDown处理掉了,具体是谁呢?看cocos2d-x的android版本代码(framework/scocos2d-x/cocos/2d/platform/android/java/src/org/cocos2dx/lib/Cocos2dx/GLSurfaceView.java)。其中实现了onKeyDown方法:
@Override
public boolean onKeyDown(final int pKeyCode, final KeyEvent pKeyEvent) {
switch (pKeyCode) {
case KeyEvent.KEYCODE_BACK:

case KeyEvent.KEYCODE_MENU:
this.queueEvent(new Runnable() {
@Override
public void run() {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleKeyDown(pKeyCode);
}
});
return true;
default:
return super.onKeyDown(pKeyCode, pKeyEvent);
}
}这个里面,按键KeyEvent.KEYCODE_BACK被监听了,只要在这里不作处理即可(return false),改成如下代码:
@Override
public boolean onKeyDown(final int pKeyCode, final KeyEvent pKeyEvent) {
switch (pKeyCode) {
case KeyEvent.KEYCODE_BACK:
return false;
case KeyEvent.KEYCODE_MENU:
this.queueEvent(new Runnable() {
@Override
public void run() {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleKeyDown(pKeyCode);
}
});
return true;
default:
return super.onKeyDown(pKeyCode, pKeyEvent);
}
}这样在你自己的cocos2d-x游戏Activity中就可以监听返回键了,在AppActivity中重写onKeyDown方法,如下:
private long mkeyTime = 0;
public boolean onKeyDown(int keyCode, KeyEvent event) {
//二次返回退出
if (keyCode == KeyEvent.KEYCODE_BACK) {
if ((System.currentTimeMillis() - mkeyTime) > 2000) {
mkeyTime = System.currentTimeMillis();
Toast.makeText(this, "再按一次退出游戏", Toast.LENGTH_LONG).show();
} else {
finish();
System.exit(0);
}
return false;
}
return super.onKeyDown(keyCode, event);
}然后运行游戏, 点击返回键试试,有没有Toast?Enjoy~ 查看全部
一般的游戏或者软件,都会在android版本上做退出程序的功能,一般的实现方式有两种:

1.点击返回按键,弹出确认是否退出;

2.点击返回,toast提示再次点击退出程序。

这两种方式实现都需要android上监听用户按下了android手机上的返回键,监听到了之后执行相应的操作。

通过搜索,很容易知道可以实现activity类的方法
public boolean onKeyDown(final int pKeyCode, final KeyEvent pKeyEvent)
实现对用户按键的监听,但是将代码加入到cocos2d-x生成的Java代码中的AppActivity中,发现根本不起作用,没法监听到用户的按键操作,找官方api文档,发现:

public boolean onKeyDown (int keyCode, KeyEvent event)

Since: API Level 1

Called when a key was pressed down and not handled by any of the views inside of the activity. So, for example, key presses while the cursor is inside a TextView will not trigger the event (unless it is a navigation to another object) because TextView handles its own key presses.

If the focused view didn't want this event, this method is called.

这段文字我需要强调的是红色标记部分,翻译的意思是:当用户按下一个按键的时候调用,但是前提是这个事件没有被其他的任何views监听并处理。

因此不难得知,在cocos2d-x游戏中,在实现的onKeyDown方法监听不到返回键,是因为这个返回键事件已经被其他的onKeyDown处理掉了,具体是谁呢?看cocos2d-x的android版本代码(framework/scocos2d-x/cocos/2d/platform/android/java/src/org/cocos2dx/lib/Cocos2dx/GLSurfaceView.java)。其中实现了onKeyDown方法:
@Override
public boolean onKeyDown(final int pKeyCode, final KeyEvent pKeyEvent) {
switch (pKeyCode) {
case KeyEvent.KEYCODE_BACK:

case KeyEvent.KEYCODE_MENU:
this.queueEvent(new Runnable() {
@Override
public void run() {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleKeyDown(pKeyCode);
}
});
return true;
default:
return super.onKeyDown(pKeyCode, pKeyEvent);
}
}
这个里面,按键KeyEvent.KEYCODE_BACK被监听了,只要在这里不作处理即可(return false),改成如下代码:
@Override
public boolean onKeyDown(final int pKeyCode, final KeyEvent pKeyEvent) {
switch (pKeyCode) {
case KeyEvent.KEYCODE_BACK:
return false;
case KeyEvent.KEYCODE_MENU:
this.queueEvent(new Runnable() {
@Override
public void run() {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleKeyDown(pKeyCode);
}
});
return true;
default:
return super.onKeyDown(pKeyCode, pKeyEvent);
}
}
这样在你自己的cocos2d-x游戏Activity中就可以监听返回键了,在AppActivity中重写onKeyDown方法,如下:
private long mkeyTime = 0;
public boolean onKeyDown(int keyCode, KeyEvent event) {
//二次返回退出
if (keyCode == KeyEvent.KEYCODE_BACK) {
if ((System.currentTimeMillis() - mkeyTime) > 2000) {
mkeyTime = System.currentTimeMillis();
Toast.makeText(this, "再按一次退出游戏", Toast.LENGTH_LONG).show();
} else {
finish();
System.exit(0);
}
return false;
}
return super.onKeyDown(keyCode, event);
}
然后运行游戏, 点击返回键试试,有没有Toast?Enjoy~