存档

2014年10月 的存档

红孩儿3D引擎开发课程《3ds max导出插件初步》

2014年10月31日 没有评论

 

          [置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

       前言:今天网易的《乱斗西游》上线AppStore ,将继完美世界《黑暗黎明》后再次证明自研引擎的实力!如果你想成为引擎研发高手,那么,一切,将从3ds max导出插件起步~

 

 

章课程《3ds max导出插件初步》

 

一.3ds max导出插件简介:

      在游戏开发中,我们最多接触到的资源就是模型,一款游戏的模型量是一个巨大的数字,这么多模型,只能交给美术进行制作。一般的开发流程是:美术使用3ds max或maya等建模软件对原画设定进行建模,之后导出相应的数据文件给游戏使用。

 

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

    在这个流程里,最关键的问题是如何能够将建模软件中的模型解析到程序中,要解决这个问题,就要了解如何取得建模转件中编辑的模型数据并导出为文件。在3ds max的sdk中,提供有导出插件的编程框架与示例,做为一个3D引擎程序员,按照引擎的需求编写3ds max导出插件将3ds max中的模型按照自已的需要格式导出,是非常基本和重要的工作。

    比如下图,这是一个典型的3ds max导出插件:

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

 

一般导出插件通过获取模型数据后,可以导出的信息有:

(1).顶点位置

(2).法线向量

(3).纹理坐标

(4).贴图名称

(5).骨骼及蒙皮信息

等等,这些数据都通过3ds max sdk中的接口函数得到相应的顶点数据结构指针及材质结构指针获取。

下面,我们来学习一下如何为3ds max 编写一个导出插件。

 

      

二.环境架设:

 

         要为 3ds max编写相应的导出插件,首先要根据美术需求的3ds max版本安装3ds max 及 3ds max sdk,然后是跟据3ds max sdk的版本安装相应的visual studio ,比如 3ds max 8要用vs2005, 3ds max 2010要用到vs2008, 3ds max 2012要用vs2010,这些都有相应的匹配,要注意根据美术的需求进行调整相应的开发工具。

 

        在安装好相应的3ds max, 3ds max sdk,visual studio等软件后,我们就可以开始为3ds max开发导出插件了。首先是打开3ds max sdk下的howto目录,按照readme.txt的说明为visual studio增加相应的max导出插件开发向导。

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

比如:

 

1. 3dsmaxPluginWizard.ico, 3dsmaxPluginWizard.vsdir, 3dsmaxPluginWizard.vsz等三个文件拷到VSVC/VCProjects目录下。

2. 3dsmaxPluginWizard.vsz文件的只读属性去掉,然后修改ABSOLUTE_PATH为3ds max sdk中howto下的3dsmaxPluginWizard目录。

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

 

保存退出后,我们打开VS,找到向导页:

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

输入你想要设定的工程名字后点击确定,会弹出一个对话框:

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

这个页面列出了很多插件种类,我们只需要开发能进行模型的文件导出功能的插件,所以选择“FileExport”就可以了。

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

点击“下一步”,会需要设置3ds max目录,插件目录以及3ds max的可执行程序目录:

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

     注意:如果你的向导页如上图所示,则要求你必须手动选择相应的路径.你也可以在电脑的环境变量中设置相应的路径值.之后再创建导出插件工程时,这一向导页会自动显示出相应的路径值.

 

选择三个输入框要求的路径后点击“Finish”,即可生成一个新的导出插件工程。

解决方案中生成的文件如下:

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

三.编译运行调试:

首先编译一下项目,幸运的话,当前版本的VS可以顺利编译通过,但有时候也不免不太顺利,比如下面这种情况:

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

 

平台工具集要改为V100才可以顺利编译通过。

想要调试导出插件,需要设置工程->属性->调试->命令设为3ds max的可执行程序路径:

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

这样就可以将咱们调试的导出插件加载到3ds max中,当然,一定一定要确定当前工程的配置管理器中平台要与3ds max,操作系统保存一致,如果你的系统是64位的,这里要改成x64,否则启动程序后3ds max会提示“不是有效的win32程序”之类的对话框。

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

然后要将输入文件设为3ds max下的plugins目录:

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

之后启动程序,如果提示“无法找到3dsmax.exe的调试信息,或者调试信息不匹配,是否继续调试?”,选择“是”就可以继续调试了。

会发现在程序中收到断点:

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

 

F5后,我们会发现3ds max也启动起来了,这样,我们的导出插件就被3ds max加载了。

   在3ds max 中创建一个立方体,然后在主菜单里选择“导出”,之后在下拉列表中可以看到有一个(*)的奇怪文件格式,那就是我们当前调试中的导出插件所对应的文件格式,因为还没有为导出插件设置导出文件信息,所以默认为空。

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

输入一个文件名并确定后,会进入到maxProject1::DoExport函数,这个函数即是场景导出插件类maxProject1在3ds max进行文件导出时被调用的函数了,它将是我们3ds max导出插件编程的入口函数。

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

    按F5略过断点后,我们可以看到弹出了一个对话框:

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

    这个就是我们导出插件的默认导出设置对话框,它对应maxProject1.rc中的IDD_PANEL对话框资源。

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

    通过修改这个对话框资源,我们可以在导出时进行相应的设置。

    下面,我们就来尝试导出一个简单的模型。

四.导出一个简单的模型到文件中:

 

首先,我们先修改一下设置对话框,改成这样:

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

一个模型名称的输入框,一个显示信息的列表框和响应“导出”和“退出”的按钮。

然后我们在场景导出插件类maxProject1中增加一些变量保存DoExport函数传入的参数指针变量。

private:
		ExpInterface*		m_pExpInterface;		//导出插件接口指针
		Interface*		m_pInterface;			//3ds max接口指针
		BOOL			m_exportSelected;		//是否只导出选择项
		char			m_szExportPath[_MAX_PATH];	//导出目录名

并增加一个导出场景的处理函数:

//导出模型
int				ExportMesh(const char* szMeshName);

对应函数实现:

int	  maxProject1::ExportMesh(const char* szMeshName)
{
	return 0;
}

在构造函数中进行置空设置,并在maxProject1::DoExport中加入

int	 maxProject1::DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts, DWORD options)
{
	#pragma message(TODO("Implement the actual file Export here and"))
	//保存变量
	strcpy(m_szExportPath,name);
	m_pExpInterface = ei;
	m_pInterface = i;
	m_exportSelected = (options & SCENE_EXPORT_SELECTED);
    ...

我们可以看到maxProject1::DoExport函数中的实现就是调用创建对话框并设置对话框的消息处理函数为maxProject1OptionsDlgProc(嘿嘿,看名称就知道是选项设置对话框):

	if(!suppressPrompts)
		DialogBoxParam(hInstance, 
				MAKEINTRESOURCE(IDD_PANEL), 
				GetActiveWindow(), 
				maxProject1OptionsDlgProc, (LPARAM)this);

我们想做到点一下点击“确定”就导出模型,点击“取消”就退出对话框。首先需要在maxProject1.cpp头部增加:

#include "resource.h"
//列表框句柄
HWND	G_hListBox = NULL;	
//输出字符串到列表框
void	AddStrToOutPutListBox(const char* szText)
{
	if( G_hListBox )
	{
		SendMessage(G_hListBox,LB_ADDSTRING,0,(LPARAM)szText);
	}
}

然后我们找到

INT_PTR CALLBACK maxProject1OptionsDlgProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)

在这个函数中,为初始化消息WM_INITDIALOG增加:

			imp = (maxProject1 *)lParam;
			CenterWindow(hWnd,GetParent(hWnd));
			G_hListBox = ::GetDlgItem(hWnd,IDC_LIST1);

			// 得到文件名
			std::string strPathName = imp->GetExportPathName() ;
			std::string strFileName;
			std::string::size_type pos1 = strPathName.find_last_of('//');
			std::string strFileName_NoExt;
			if (pos1 != std::string::npos)
			{
				strFileName = strPathName.substr(pos1+1);
			}
			else
			{
				strFileName = strPathName;
			}
			//去掉扩展名
			std::string::size_type pos2 = strFileName.find_last_of('.');
			if (pos2 != std::string::npos)
			{
				strFileName_NoExt = strFileName.substr(0, pos2);
			}
			else
			{
				strFileName_NoExt = strFileName ;
			}
			//将字符串设为模型名
			HWND hNameEdit = ::GetDlgItem(hWnd,IDC_EDIT1);
			SetWindowText(hNameEdit,strFileName_NoExt.c_str());

同时增加WM_COMMAND消息:

		case WM_COMMAND:
			{
				switch(wParam)
				{
				case IDC_BUTTON1:
				{
					if(imp)
					{
						HWND hNameEdit = ::GetDlgItem(hWnd,IDC_EDIT1);
						char szMeshName[64];
						GetWindowText(hNameEdit,szMeshName,64);
						//导出场景
						imp->ExportMesh(szMeshName);
					}
				}
					break;
				case IDC_BUTTON2:
					{
						//退出对话框
						EndDialog(hWnd, 0);
						return 0;
					}
					break;
				}
			}
			break;

这样输入模型名称后点击“确定”,我们将调用 ExportMesh 函数进行相应处理。

点击“退出”时会退出对话框。

下面,我们来实现一下ExportMesh函数,这个函数将完成获取模型信息,并导出为二进制文件的功能,首先我们来获取一下模型的材质信息。

	//通过m_pInterface取得场景中的材质库
	MtlBaseLib * scenemats = m_pInterface->GetSceneMtls();

	if (scenemats)
	{	
		char	tText[200];
		int tCount = scenemats->Count();

		sprintf(tText,"共有材质%d个",tCount);
		AddStrToOutPutListBox(tText);

		if(tCount > 0)
		{
			m_AllMaterialVec.clear();
			m_AllMaterialSize = 0;
			//取得材质数量
			for (int i = 0; i < tCount ; i++)
			{ 
				MtlBase * vMtl = (*scenemats)[i];
				if (IsMtl(vMtl))
				{		
					SParseMaterial*	pParseMaterial = new SParseMaterial;
					memset(pParseMaterial,0,sizeof(SParseMaterial));
					pParseMaterial->m_MaterialID = m_AllMaterialSize++;
					strcpy(pParseMaterial->m_MaterialName,vMtl->GetName());
					//遍历材质所用的贴图
					SubTextureEnum(vMtl,pParseMaterial->m_SubTextureVec,m_AllMaterialSize);
					m_AllMaterialVec.push_back(pParseMaterial);
				}
			}
		}
	}

这里通过m_pInterface->GetSceneMtls()函数取得场景中的材质库,之后遍历每一个材质并列举出这个材质的贴图。为了方便列举材质的贴图,我们创建了一个函数 SubTextureEnum 

//子纹理列举
BOOL	maxProject1::SubTextureEnum(MtlBase *		vMtl,vector<SParseTexture>&	vTextureVec,int&	vMaterialSize)
{
	// 取得纹理数量
	int tTextureNum = vMtl->NumSubTexmaps();
	//sprintf(tText,"材质%s,共有%d个贴图",mtl->GetName(),tTextureNum);

	for (int j = 0; j < tTextureNum ; j++)
	{
		Texmap * tmap = vMtl->GetSubTexmap(j);
		if (tmap)
		{
			if (tmap->ClassID() == Class_ID(BMTEX_CLASS_ID, 0))
			{
				BitmapTex *bmt = (BitmapTex*) tmap;
				//纹理
				SParseTexture	tParseTexture;

				tParseTexture.m_Index = j;
				memset(tParseTexture.m_FileName,0,sizeof(tParseTexture.m_FileName));
				tParseTexture.m_TexMapPtr = bmt;
				std::string strMapName = bmt->GetMapName();

				if (false == strMapName.empty())
				{
					// 得到文件名
					std::string strFullName;
					std::string::size_type pos = strMapName.find_last_of('//');
					if (pos != std::string::npos)
					{
						strFullName = strMapName.substr(pos+1);
					}
					else
					{
						strFullName = strMapName;
					}

					// 得到扩展名
					std::string strEx   = "png";
					std::string strName = strFullName;
					pos = strFullName.find_last_of(".");
					if (pos != std::string::npos)
					{
						strEx = strFullName.substr(pos+1);
						strName = strFullName.substr(0, pos);
					}

					// 扩展名转小写
					transform(  strEx.begin(), strEx.end(), strEx.begin(), tolower ) ;
					_snprintf(	tParseTexture.m_FileName, 60, "%s", strFullName.c_str());
				}
				vTextureVec.push_back(tParseTexture);
			}
		}
	}
	return TRUE;
}

    最终我们将材质信息存放到了m_AllMaterialVec中。

    我们接着获取模型的顶点信息和面索引信息,在3ds max中,渲染对象也是由一套结点系统来组织关系的。我们可以从根节点开始遍历所有子结点来查询我们需要的对象:

//取得根节点的子节点数量
int numChildren = m_pInterface->GetRootNode()->NumberOfChildren();
if(numChildren > 0)
{
	for (int idx = 0; idx < numChildren; idx++)
	{
			//列举对应节点信息			   NodeEnum(m_pInterface->GetRootNode()->GetChildNode(idx),NULL);
	}
}

通过NodeEnum对结点进行遍历:

//列举结点信息
BOOL maxProject1::NodeEnum(INode* node,SMeshNode*  pMeshNode) 
{
	if (!node)
	{
		return FALSE;
	}

	//模型体
	SMeshNode		tMeshNode;	
	// 取得0帧时的物体
	TimeValue		tTime = 0;
	ObjectState os = node->EvalWorldState(tTime); 

	// 有选择的导出物体
	if (os.obj)
	{
		//char tText[200];
		//sprintf(tText,"导出<%s>----------------------<%d : %d>",node->GetName(),os.obj->SuperClassID(),os.obj->ClassID());
		//AddStrToOutPutListBox(tText);
		//取得渲染物体的类型ID
		DWORD	SuperclassID = os.obj->SuperClassID();
		switch(SuperclassID)
		{
			//基础图形
		case SHAPE_CLASS_ID:
			//网格模型
		case GEOMOBJECT_CLASS_ID: 
			ParseGeomObject(node,&tMeshNode); 
			break;
		default:
			break;
		}
	}

	// 递归导出子节点
	for (int c = 0; c < node->NumberOfChildren(); c++)
	{
		if (!NodeEnum_Child(node->GetChildNode(c),&tMeshNode))
		{
			break;
		}
	}

	if(tMeshNode.m_SubMeshVec.size() > 0)
	{
		//将子模型放入VEC
		m_MeshNodeVec.push_back(tMeshNode);
	}
	return TRUE;
}
//列举子结点信息
BOOL maxProject1::NodeEnum_Child(INode* node,SMeshNode*  pMeshNode) 
{
	if (!node)
	{
		return FALSE;
	}
	// 取得0帧时的物体
	TimeValue		tTime = 0;
	ObjectState os = node->EvalWorldState(tTime); 

	// 有选择的导出物体
	if (os.obj)
	{
		char tText[200];
		sprintf(tText,"导出<%s>----------------------<%d : %d>",node->GetName(),os.obj->SuperClassID(),os.obj->ClassID());
		AddStrToOutPutListBox(tText);
		//取得渲染物体的类型ID
		DWORD	SuperclassID = os.obj->SuperClassID();
		switch(SuperclassID)
		{
			//基础图形
		case SHAPE_CLASS_ID:
			//网格模型
		case GEOMOBJECT_CLASS_ID: 
			ParseGeomObject(node,pMeshNode); 
			break;
		default:
			break;
		}
	}

	// 递归导出子节点
	for (int c = 0; c < node->NumberOfChildren(); c++)
	{
		if (!NodeEnum_Child(node->GetChildNode(c),pMeshNode))
		{
			break;
		}
	}

	return TRUE;
}

    如果我们学过结点系统,对这个子结点遍历流程是很容易理解的。我们可以看到在3ds max中,通过结点INode调用某一帧时间的EvalWorldState函数可以获取渲染物体,再通过渲染物体调用SuperClassID函数获取渲染物体类型,可以判断是否是网络模型。

    如果是网络模型,我们可以创建一个函数来对这个模型的信息进行读取:

void maxProject1::ParseGeomObject(INode * node,SMeshNode*  pMeshNode)
{
	char			tText[200];
	//获取渲染对象
	TimeValue		tTime = 0;
	ObjectState os = node->EvalWorldState(tTime); 
	if (!os.obj)
		return;
	//如果不是有效网格模型格式,则返回。
	if (os.obj->ClassID() == Class_ID(TARGET_CLASS_ID, 0))
		return;

	sprintf(tText,"导出对象<%s>.............",node->GetName());
	AddStrToOutPutListBox(tText);

	//新建一个子模型信息结构并进行填充
	SSubMesh		tSubMesh;
	tSubMesh.m_pNode = node;
	strcpy(tSubMesh.m_SubMeshName,node->GetName());
	tSubMesh.m_MaterialID = -1;

	// 取得模型对应的材质。
	Mtl * nodemtl = node->GetMtl();
	if (nodemtl)
	{
		//取得材质库
		MtlBaseLib * scenemats = m_pInterface->GetSceneMtls();
		//遍历材质库,找到本结点所用的材质。
		int tCount = scenemats->Count();
		for(int i = 0 ; i < tCount ; i++)
		{
			MtlBase * mtl = (*scenemats)[i];
			if(strcmp(mtl->GetName(),nodemtl->GetName()) == 0)
			{
				tSubMesh.m_MaterialID = i;
				break;
			}
		}
		sprintf(tText,"对应材质<%s>",nodemtl->GetName());
		AddStrToOutPutListBox(tText);
	}

	//如果模型是由
	bool delMesh = false;
	Object *obj = os.obj;
	if ( obj )
	{
		//如果当前渲染物体能转换为网格模型
		if(obj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID, 0)))
		{
			//将当前渲染物体能转换为网格模型
			TriObject * tri = (TriObject *) obj->ConvertToType(0, Class_ID(TRIOBJ_CLASS_ID, 0));
			//如果当前渲染物体本身来是网格模型类型,它经过转换后会生成新的网格模型。所以在处理结束后要进行释放。
			if (obj != tri) 
			{
				delMesh = true; 
			}

			if (tri)
			{
				//
				CMaxNullView maxView;
				BOOL bDelete = TRUE;
				//通过GetRenderMesh来获取模型信息结构。
				Mesh * mesh = tri->GetRenderMesh(tTime, node, maxView, bDelete);
				assert(mesh);
				//重建法线
				mesh->buildNormals();
				//重建法线后要调用一下checkNormals检查法线。
				mesh->checkNormals(TRUE);

				sprintf(tText,"模型<%s> 顶点数 :<%d> 面数:<%d>",node->GetName(),mesh->getNumVerts(),mesh->getNumFaces());
				AddStrToOutPutListBox(tText);

				int    tVertexNum = mesh->getNumVerts(); 
				int	   tFaceNum   = mesh->getNumFaces();

				//取得当前结点相对于中心点的矩阵信息。
				Matrix3		tTMAfterWSMM = node->GetNodeTM(tTime);
				//扩展成4X4矩阵
				GMatrix		tGMeshTM(tTMAfterWSMM);
				//保存到模型信息结构的矩阵信息中。
				for(int m = 0 ; m < 4 ; m++)
				{
					for(int n = 0 ; n < 4 ; n++)
					{
						tSubMesh.m_SubMeshMatrix.m[m*4+n] = tGMeshTM[m][n];
					}
				}
				//开始获取顶点信息结构并存放到容器中。
				vector<SVertex>		tVertexVec;
				//顶点信息
				for (int i = 0; i < tVertexNum; i++)
				{
					SVertex		tVertex;
					//位置,要注意的是在3ds max中z值是朝上的,y值是朝前的,而在我们的游戏中,y值朝上,z值朝前。所以要做下处理。
					Point3		vert = mesh->verts[i];
					tVertex.m_PosX = vert.x;
					tVertex.m_PosY = vert.z;
					tVertex.m_PosZ = vert.y;

					//法线,同样Y轴和Z轴要切换下。
					Point3		norm = mesh->getNormal(i);
					tVertex.m_NPosX = norm.x;
					tVertex.m_NPosY = norm.z;
					tVertex.m_NPosZ = norm.y;

					//顶点色
					tVertex.m_Red	= 1.0f;
					tVertex.m_Green	= 1.0f;
					tVertex.m_Blue  = 1.0f;

					//纹理坐标
					tVertex.m_U		= 0.0f;
					tVertex.m_V		= 0.0f;

					tVertexVec.push_back(tVertex);
				}
				//获取顶点色信息
				//如果有顶点有色彩赋值。
				if( mesh->numCVerts > 0)
				{
					//遍历每个三角面
					for (int i = 0; i < tFaceNum; i++)
					{
						//色彩信息也以类似顶点的方式存放在模型的色彩信息数组vertCol中,而描述每个三角面的三个顶点都对应色彩信息数组的哪个值,也有类似面索引的信息结构TVFace存放在模型的vcFace数组中。
						TVFace   tface = mesh->vcFace[i];
						//取得色彩数组中对应三角面各顶点色彩值的三个索引。
						int		tSrcColorIndex1 = tface.getTVert(0);
						int		tSrcColorIndex2 = tface.getTVert(1);
						int		tSrcColorIndex3 = tface.getTVert(2);
						//取得模型三角面的三个索引。
						int		tDestColorIndex1 = mesh->faces[i].v[0];
						int		tDestColorIndex2 = mesh->faces[i].v[1];
						int		tDestColorIndex3 = mesh->faces[i].v[2];

						//将色彩数组vertCol中对应三角面各顶点色彩的值赋值给相应的顶点。
						tVertexVec[tDestColorIndex1].m_Red = mesh->vertCol[tSrcColorIndex1].x;
						tVertexVec[tDestColorIndex1].m_Green = mesh->vertCol[tSrcColorIndex1].y;
						tVertexVec[tDestColorIndex1].m_Blue = mesh->vertCol[tSrcColorIndex1].z;

						tVertexVec[tDestColorIndex2].m_Red = mesh->vertCol[tSrcColorIndex2].x;
						tVertexVec[tDestColorIndex2].m_Green = mesh->vertCol[tSrcColorIndex2].y;
						tVertexVec[tDestColorIndex2].m_Blue = mesh->vertCol[tSrcColorIndex2].z;

						tVertexVec[tDestColorIndex3].m_Red = mesh->vertCol[tSrcColorIndex3].x;
						tVertexVec[tDestColorIndex3].m_Green = mesh->vertCol[tSrcColorIndex3].y;
						tVertexVec[tDestColorIndex3].m_Blue = mesh->vertCol[tSrcColorIndex3].z;
					}
				}
				//获取顶点纹理坐标
				//如果有顶点有纹理坐标赋值。
				if( mesh->numTVerts > 0)
				{
					//顶点
					for (int i = 0; i < tFaceNum; i++)
					{
						//纹理坐标信息也以类似顶点的方式存放在模型的色彩信息数组tVerts中,而描述每个三角面的三个顶点都对应纹理坐标信息数组的哪个值,也有类似面索引的信息结构TVFace存放在模型的tvFace数组中。
						TVFace tface = mesh->tvFace[i];
						//取得纹理坐标数组中对应三角面各顶点纹理坐标值的三个索引。
						int		tSrcTexIndex1 = tface.getTVert(0);
						int		tSrcTexIndex2 = tface.getTVert(1);
						int		tSrcTexIndex3 = tface.getTVert(2);
						//取得模型三角面的三个索引。	
						int		tDestTexIndex1 = mesh->faces[i].v[0];
						int		tDestTexIndex2 = mesh->faces[i].v[1];
						int		tDestTexIndex3 = mesh->faces[i].v[2];

						//将纹理坐标数组tVerts中对应三角面各顶点纹理坐标的值赋值给相应的顶点。
						SVertex tV1 = tVertexVec[tDestTexIndex1];
						SVertex	tV2 = tVertexVec[tDestTexIndex2];
						SVertex	tV3 = tVertexVec[tDestTexIndex3];
						//注意:在纹理的纵向上,3ds max与我们游戏中是反的,也需要做下处理。
						tV1.m_U = mesh->tVerts[tSrcTexIndex1].x;
						tV1.m_V = 1.0 - mesh->tVerts[tSrcTexIndex1].y;
						tSubMesh.m_VertexVec.push_back(tV1);
		
						tV2.m_U = mesh->tVerts[tSrcTexIndex2].x;
						tV2.m_V = 1.0 - mesh->tVerts[tSrcTexIndex2].y;
						tSubMesh.m_VertexVec.push_back(tV2);
	
						tV3.m_U = mesh->tVerts[tSrcTexIndex3].x;
						tV3.m_V = 1.0 - mesh->tVerts[tSrcTexIndex3].y;
						tSubMesh.m_VertexVec.push_back(tV3);

						//将三角面索引信息保存到容器中。
						SFace		tFace;
						tFace.m_VertexIndex1 = i*3;
						tFace.m_VertexIndex2 = i*3+1;
						tFace.m_VertexIndex3 = i*3+2;

						tSubMesh.m_FaceVec.push_back(tFace);

					}
				}
				else
				{
					//顶点
					tSubMesh.m_VertexVec = tVertexVec ;
					// 导出面数
					for (int i = 0; i < tFaceNum; i++)
					{
						//将三角面索引信息保存到容器中。
						SFace		tFace;
						tFace.m_VertexIndex1 = mesh->faces[i].v[0];
						tFace.m_VertexIndex2 = mesh->faces[i].v[1];
						tFace.m_VertexIndex3 = mesh->faces[i].v[2];

						tSubMesh.m_FaceVec.push_back(tFace);
					}
				}
				//如果在转换时有新的渲染模型生成,在这里进行释放。
				if (delMesh)
				{
					delete tri;
				}
			}
		}
	}

	//保存信息
	pMeshNode->m_SubMeshVec.push_back(tSubMesh);
}

    上面的代码较长,可能不易理解,我再详尽解释下:

 

    首先,一个结点的本地矩阵(即相对于自身中心点的变换矩阵)通过结点的GetNodeTM可以获得,但获得的是3×3的矩阵,如果要想保存成游戏中用的Mat4这种类型,需要做下扩展。

    第二,在3ds max中z值是朝上的,y值是朝前的,而在我们的游戏中,y值朝上,z值朝前。所以要做下处理。

    第三,在3ds max中顶点中的信息,是每种类型都存放在Mesh的各自信息结构容器中,通过对应的面索引结构来指定从容器的哪个位置取出来赋值给实际的顶点。比如:

 

   (1).顶点位置信息存放在Meshverts数组中,对应的三角面索引信息存放在Meshfaces数组中。

   (2).顶点色彩信息结构存放在MeshvertCol数组中,用来指定三角面的各顶点色彩值对应vertCol数组哪个结构的索引信息是存放在MeshvcFace数组中。

   (3).顶点纹理坐标信息结构存放在MeshtVerts数组中,用来指定三角面的各顶点纹理坐标值对应tVerts数组哪个结构的索引信息是存放在MeshtvFace数组中。

 

OK,在完成了模型解析后,我们需要的材质,顶点,索引等信息都放在了容器中,准备好了,就开始导出!

	//遍历3ds max中的模型并导出二进制文件。
	int		nMeshCount = m_MeshNodeVec.size();
	for(int m = 0 ; m < nMeshCount ; m++)
	{
		char szExportFileName[_MAX_PATH];
		//如果只有一个模型,就用模型名称。
		if( 1 == nMeshCount )
		{
			strcpy(m_MeshNodeVec[m].m_MeshName,szMeshName);
			strcpy(szExportFileName,m_szExportPath);
		}
		else
		{
			//如果有多个模型,就按照“模型名称_序列号”的命名方式
			sprintf(m_MeshNodeVec[m].m_MeshName,"%s_%d",szMeshName,m);
			std::string strExportPath = m_szExportPath;

			// 得到扩展名
			std::string strEx   = "";
			std::string strName = strExportPath;
			std::string::size_type pos = strExportPath.find_last_of(".");
			if (pos != std::string::npos)
			{
				strEx = strExportPath.substr(pos+1);
				strName = strExportPath.substr(0, pos);
				_snprintf(	szExportFileName, _MAX_PATH, "%s_%d.%s", strName.c_str(),m,strEx);
			}
			else
			{
				_snprintf(	szExportFileName, _MAX_PATH, "%s_%d", strName.c_str(),m);
			}
				
		}
		//进行二进制文件的写入。
		FILE*	hFile = fopen(m_szExportPath,"wb");
		fwrite(m_MeshNodeVec[m].m_MeshName,sizeof(m_MeshNodeVec[m].m_MeshName),1,hFile);
		int	nSubNum = m_MeshNodeVec[m].m_SubMeshVec.size();
		fwrite(&nSubNum,sizeof(int),1,hFile);

		for( int s = 0 ; s < nSubNum ; s++)
		{
			SSubMeshHeader	tSubMeshHeader;
			strcpy(tSubMeshHeader.m_SubMeshName,m_MeshNodeVec[m].m_SubMeshVec[s].m_SubMeshName);
			int nMaterialID = m_MeshNodeVec[m].m_SubMeshVec[s].m_MaterialID ;
			SParseMaterial*	tpParseMaterial = GetMaterial(nMaterialID);
			if(tpParseMaterial && false == tpParseMaterial->m_SubTextureVec.empty())
			{
				strcpy(tSubMeshHeader.m_Texture,tpParseMaterial->m_SubTextureVec[0].m_FileName);
			}
			else
			{
				tSubMeshHeader.m_Texture[0]='/0';
			}
			tSubMeshHeader.m_VertexCount = m_MeshNodeVec[m].m_SubMeshVec[s].m_VertexVec.size();
			tSubMeshHeader.m_IndexCount = m_MeshNodeVec[m].m_SubMeshVec[s].m_FaceVec.size() * 3;	
			tSubMeshHeader.m_PrimitiveType = PT_TRIANGLES ;
			tSubMeshHeader.m_IndexFormat = INDEX16 ;
			fwrite(&tSubMeshHeader,sizeof(SSubMeshHeader),1,hFile);
			if(tSubMeshHeader.m_VertexCount > 0 )
			{
				fwrite(&m_MeshNodeVec[m].m_SubMeshVec[s].m_VertexVec.front(),sizeof(SVertex),tSubMeshHeader.m_VertexCount,hFile);
			}
			if(tSubMeshHeader.m_IndexCount > 0 )
			{
				fwrite(&m_MeshNodeVec[m].m_SubMeshVec[s].m_FaceVec.front(),sizeof(SFace),m_MeshNodeVec[m].m_SubMeshVec[s].m_FaceVec.size(),hFile);
			}
			fwrite(&m_MeshNodeVec[m].m_SubMeshVec[s].m_SubMeshMatrix,sizeof(SSubMeshMatrix),1,hFile);
		}
		fclose(hFile);
	}
	

	//释放材质
	vector<SParseMaterial*>::iterator	Iter;
	for(Iter = m_AllMaterialVec.begin(); Iter != m_AllMaterialVec.end(); Iter++)
	{
		delete (*Iter);
	}
	m_AllMaterialVec.clear();
	//释放模型
	m_MeshNodeVec.clear();
	AddStrToOutPutListBox("导出完毕!");

这样我们就基本完成了模型解析和导出的实现!但现在我们还有些事需要做,就是为导出文件做描述和扩展名设置,我们可以找到以下函数,并在返回值中做赋值:

const TCHAR *maxProject1::LongDesc()
{
#pragma message(TODO("Return long ASCII description (i.e. /"Targa 2.0 Image File/")"))
	return _T("Game Mesh File");
}

const TCHAR *maxProject1::ShortDesc() 
{			
#pragma message(TODO("Return short ASCII description (i.e. /"Targa/")"))
	return _T("Mesh File");
}

const TCHAR *maxProject1::AuthorName()
{			
#pragma message(TODO("Return ASCII Author name"))
	return _T("Honghaier");
}

OK,这样模型导出的处理大体就基本完成了,详尽的代码大家可以参考工程,下面我们来打开3ds max做一下具体的导出测试。

首先,我们打开3ds max,并创建一个茶壶。

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

然后我们右键单击,在弹出菜单里选择“全部解冻”和“平移”,在最下部面板的X,Y,Z中将模型置到0,0,0的位置。

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

然后我们在菜单上查找“渲染”项,再找其子菜单项“材质编辑器”,选择“精简材质编辑器”。

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

 

在“精简材质编辑器”对话框中,我们按图示,设置一个贴图。

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

 

这里我们将 HelloWorld 的 Cocos2d-x背景图做为贴图设置给茶壶。

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

 

然后我们在菜单上选择“导出”,找到我们的格式,在想要存放的目录中进行保存设置。

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

输入teapot.mes,并点击“确定”。

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

 

然后,我们就可以看到我们编写的导出插件对话框。

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

在输入模型名称后,点击“导出”按钮,可以看到在导出信息显示列表框中,输出了相应的导出信息。完成后我们点击“退出”关闭对话框,这样,我们就完成了导出插件部分的编程。

五.模型文件的读取:

在之前的课程中,我们有完成模型的导出与加载,现在只需要改进一下就可以了。

//从文件中读取并创建模型
bool	C3DSubMesh::LoadMeshFromFile(FILE* pFile)
{
	Release();

	if(pFile)
	{
		stSubMeshHeader	tHeader;
		fread(&tHeader,sizeof(stSubMeshHeader),1,pFile);
		SetName(tHeader.m_SubMeshName);
		//设置纹理
		SetTexture(tHeader.m_Texture);

		m_VertexCount = tHeader.m_VertexCount;
		m_IndexCount = tHeader.m_IndexCount;
		m_PrimitiveType = tHeader.m_PrimitiveType;
		m_IndexFormat = tHeader.m_IndexFormat; 	
		//创建顶点与索引数组并读取数据
		m_VertexArray = new stShapeVertices[m_VertexCount];
		fread(m_VertexArray,sizeof(stShapeVertices),m_VertexCount,pFile);
		m_IndiceArray = new GLushort[m_IndexCount];
		fread(m_IndiceArray,sizeof(GLushort),m_IndexCount,pFile);
	
		//矩阵
		Mat4 tSubMatrix;
		fread(&tSubMatrix,sizeof(Mat4),1,pFile);
		
		tSubMatrix.decompose(&m_Scale_Self,&m_Rotate_Self,&m_Translate_Self);
		m_Translate_Parent = Vec3(0,0,0);
		m_Scale_Parent = Vec3(1,1,1);
		m_Rotate_Parent.identity();

		//创建VB与IB
		glGenBuffers(1, &m_VertexBuffer);
		glGenBuffers(1, &m_IndexBuffer);

		//绑定数据到VB中。
		glBindBuffer(GL_ARRAY_BUFFER_ARB, m_VertexBuffer);
		glBufferData(GL_ARRAY_BUFFER_ARB,
			m_VertexCount * sizeof(stShapeVertices),
			m_VertexArray,
			GL_STATIC_DRAW);

		//绑定数据到IB中。
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, m_IndexBuffer);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER_ARB, m_IndexCount*sizeof(GLushort), m_IndiceArray, GL_STATIC_DRAW);

		BuildShader();
		return true;
	}
	return false;
}

将贴图拷到我们的工程资源目录下,运行一下,我们可以看到:

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

贴图效果不对,这是由于图片没有进行可自动重复贴图寻址的设置,我们需要改进一下贴图设置。

//使用贴图
void	C3DShape::SetTexture(const char* szTextureFileName)
{
	m_Texture = CCTextureCache::sharedTextureCache()->addImage(szTextureFileName);
	if(m_Texture)
	{
		m_TextureFileName = szTextureFileName ;

		//寻址方式为GL_REPEAT。
		Texture2D::TexParams	tRepeatParams;
		tRepeatParams.magFilter = GL_LINEAR;
		tRepeatParams.minFilter = GL_LINEAR;
		tRepeatParams.wrapS = GL_REPEAT;
		tRepeatParams.wrapT = GL_REPEAT;
		m_Texture->setTexParameters(&tRepeatParams);
		m_Texture->setAntiAliasTexParameters();
		
	}
}

    注意:需要将图片改成2的幂次方才能使用重复寻址。

再次运行后我们看到了与3ds max一样的结果:

[置顶]        万圣节福利:红孩儿3D引擎开发课程《3ds max导出插件初步》

 

是不是觉得很棒!

同学们,经过本章的学习,我们已经可以学会从3ds max中导出模型了,虽然实际项目中的导出插件功能远非如此,包括骨骼蒙皮,多重材质,平滑组拆分等等复杂处理,但我们在掌握了基本的3ds max导出插件编程之后,那些终将不是问题!希望你在以后的时间里继续努力。相信我,你离高手不远了!

 

六.作业:

(1) 。做一个简单的模型并用自已的导出插件进行导出,之后加载到引擎中显示。

(2) 。将一个动画模型导出序列帧模型,并在引擎中加载显示控制每一帧。

分类: 基础知识 标签:

How to property hold a reference to the children nodes with ARC in Cocos2d

2014年10月30日 没有评论

I have a custom CCNode class that has a bunch of children nodes, and I want to keep references to the children in order to make some custom transitions.

For instance for the child background the custom class would look like this:

@interface MyNode : CCNode
@property (nonatomic, strong) CCNode *background;
@end

@implementation
- (void)setBackground:(CCNode *)background {
    if (_background) {
        [self removeChild:_background];
    }
    if (background) {
        [self addChild:background];
    }
    _background = background;
}
- (void)runTransition {
    if (_background)
        [_background runAction:[…]];
}
@end

The problem is that this causes a retain cycle on ARC with the node background never being freed from memory.

There are no hard rules for memory management. You need to look at your code and decide the best method to use.

In your case though as you only ever have one background you could just make that property weak. Cocos2d holds a strong reference to all nodes that are in the scene graph so it will hold onto it for you.

This doesn’t mean that every node should be stored as weak, sometimes you want the node to hang around if its not in the scene graph.

Change your interface to use a zeroing weak reference:

@interface MyNode : CCNode
@property (nonatomic, weak) CCNode *background;
@end

If the background node deallocates, the _background ivar will become nil automatically. That way you won’t have a retain cycle anymore. This is generally good practice if you store a node reference in a node that is a child or grandchild of the node because those situations will always cause a retain cycle.

Your code as is should work fine with a weak reference.

Note however that you need to be careful when creating and assigning to a weak reference, for instance this will fail with a nil node being added as child:

_background = [CCNode node];
[self addChild:_background];

The problem here is that after the assignment, there is nothing strongly holding on to the background node, so it deallocates and is set to nil before the addChild: line.

There’s a simple workaround:

CCNode* bg = [CCNode node];
[self addChild:bg];
_background = bg;

After the node has been added as child, the children array will hold a strong reference to the node. So after the addChild: line you can assign the temporary bg node to the _background ivar.

分类: cocos2d, stackoverflow精选 标签:

【cocos2d-js官方文档】十、log

2014年10月29日 没有评论

api改动情况,左边为新增,右边为原来的。

cc.log  不变
cc.warn 新增
cc.error 新增
cc.assert <-- cc.Assert

此次改造有以下几点原因:

  • 添加原来没有的api:cc.warn、cc.error。

  • 修改cc.Assert名称,使其符合开发规范。

新的api的使用范例:

cc.log("this is a log");
cc.log("this is a log for %s", "Cocos2d-html5");

cc.warn("this is a waring");
cc.warn("this is a waring for %s", "Cocos2d-html5");

cc.error("this is an error");
cc.error("this is an error for %s", "Cocos2d-html5");

cc.assert(false, "this is an assert");
cc.assert(false, "this is an assert for %s", "Cocos2d-html5");

然后今后会定义出msgCode.js

cc.msgCode = {
    log1 : "this is a log",
    log2 : "this is a log for %s",
    warn1 : "this is a waring",
    warn2 : "this is a waring for %s",
    error1 : "this is an error",
    error2 : "this is an error for %s",
    assert1 : "this is an assert",
    assert2 : "this is an assert for %s",
    throw1 : "this is an throw",
    throw2 : "this is an throw for %s", "Cocos2d-html5"
}

这样一来可以让消息字符串等到最大程度的复用并且利于管理。

转载:http://www.douapp.com/post/2454

分类: cocos2d, cocos2d-js 标签:

运用椭圆画法,45行代码画出任意正多边形

2014年10月26日 没有评论

最近做Box2dWeb开发时,想写个创建正多边形的功能,可是由于学识尚浅,我在草稿纸上画了,想了一个上午也没有研究出什么好方法。后来翻抽屉的时候,找出了以前哥哥画的一张用同心圆画椭圆的示意图。看到这幅画,我不禁在想椭圆不就是一个N边形吗?圆不就是一个正N边形吗?如果把两个同心圆的半径设定为相等,画出来的椭圆不就是一个圆吗?因此,我立刻开始实验。原本我以为比较难,会用到圆的解析式之类的,没想到就45行代码就搞定了,主要用到的数学知识就是sin和cos。

也许有人不明白如何用同心圆画椭圆,我就借用网上找的一张图片给大家展示吧

运用椭圆画法,45行代码画出任意正多边形

这个画法很经典,做法简要概括一下就是:

画一个同心圆,然后以圆心为原点画一个二维坐标系;接着用N条过圆心的直线将圆等分,图中所示就是4条,把圆等分为了12份。每条直线都会与两个圆有交点,这时候我们就可以确定椭圆上的一个点——设直线与小圆的交点为(a, b),与大圆的交点为(c, d),确定的那个点的坐标则为(c, b);我们有N条直线就会得出2 * N + 4这样的点。可以看出,得到的点的坐标通式为:(与大圆的交点的x坐标, 与小圆的交点的y坐标)。要得到这些坐标,我们只用知道直线的条数N和大圆小圆的半径(或直径),然后用sin和cos进行计算即可。得到这些点后,把这些点连接起来就大致是个椭圆了;如果你把N的数目设定的越大,那么画出来的图形就更接近于椭圆。

与画椭圆不同的是,画正多边形不需要这么复杂,只需要一个圆就够了。而且也不要什么坐标系了,你要N 边形就画N条过圆心的直线,然后这些直线与圆的交点就是多变形的顶点。把顶点连接起来就是多变形。

现在,我们可以上代码了:

<!DOCTYPE html>
<html>
<head>
	<title>Make Regular Polygon</title>
	<meta charset="utf-8" />
	<script type="text/javascript">
	window.onload = function () {
		var canvasTag = document.getElementById("mycanvas");
		var c = canvasTag.getContext("2d");

		var vertices = getPolygonVertices(7, 100);

		c.beginPath();
		c.fillStyle = "lightgray";
		c.fillRect(0, 0, canvasTag.width, canvasTag.height);
		c.translate(canvasTag.width / 2, canvasTag.height / 2);
		c.moveTo(vertices[0][0], vertices[0][1]);
		for (var i = 1; i < vertices.length; i++) {
			c.lineTo(vertices[i][0], vertices[i][1]);
		}
		c.lineWidth = 5;
		c.closePath();
		c.stroke();
	};

	function getPolygonVertices (edges, r) {
		var ca = 0, aiv = 360 / edges, ata = Math.PI / 180, list = new Array();

		for (var k = 0; k < edges; k++) {
			var x = Math.cos(ca * ata) * r,
			y = Math.sin(ca * ata) * r;

			list.push([x, y]);

			ca += aiv;
		}

		return list;
	}
	</script>
</head>
<body>
	<canvas id="mycanvas" width="800" height="450"></canvas>
</body>
</html>

包括canvas渲染和html tag部分,一共45行。真正意义上的算法部分就只在getPolygonVertices函数里。 这个getPolygonVertices有两个参数,第一个参数是edges边数,第二个参数是圆的半径,决定多变形的大小。
算法在前面已经讲解过了,很简单很基础吧~

运行代码,画出七边形:

运用椭圆画法,45行代码画出任意正多边形

大家可以试着将getPolygonVertices的第一个参数改一改,画出其他多边形。

Ok,搞定收工~

本文到此结束,欢迎大家交流~

—————————————————————-

欢迎大家转载我的文章。

转载请注明:转自Yorhom’s Game Box

http://blog.csdn.net/yorhomwang

欢迎继续关注我的博客

分类: 未分类 标签:

The app references non-public selectors in payload With Xcode6.1

2014年10月26日 没有评论

猴子原创,欢迎转载。转载请注明: 转载自Cocos2D开发网–Cocos2Dev.com,谢谢!

原文地址: http://www.cocos2dev.com/?p=591

今天上传appStore的时候出现The app references non-public selectors in payload错误,这个主要是Xcode升级到6.1导致的一个Xcode issue,下一个Xcode版本应该会解决。

The app references non-public selectors in payload With Xcode6.1

目前如果你遇到这个问题,两个解决办法:

第一个办法,有点挫,使用Xcode6.01上传。(不推荐这个办法)

第二个办法,推荐:
1、你的Archives里面,该App一定存在以前的老版本Archive,你删除该App的所有Archive。
2、重新Archive一个新的发布版本,再次上传,就没有问题了。

分类: 未分类 标签:

修改app名字

2014年10月24日 没有评论

IOS

修改plist文件

修改显示名字
<key>CFBundleDisplayName</key>
修改应用名字
<key>CFBundleName</key>

 

Android

修改AndroidManifest.xml或者platforms/android/res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.supdo.demos"
    android:versionCode="1"
    android:versionName="1.0" >
  
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />
  
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"  #设置应用列表中应用名称
        android:theme="@style/AppTheme" >
          
        <activity
            android:name="com.supdo.demos.LoginActivity"
            android:label="@string/app_name" #设置手机桌面上图标下面的名称
            android:windowSoftInputMode="adjustResize|stateVisible" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
  
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
  
</manifest>

 

分类: cocos2d 标签:

PhysicsCollision Cocos2d/Swift

2014年10月17日 没有评论

I am writing 2d game using Cocos2d-iphone in new Apple programming language Swift. I am having problem with collision detection. I have added CCPhysicsCollisionDelegate and implemented CCPhysicsCollisionBegin method in my class but when I try to run it I get following error:

'CCPhysicsCollisionBegin delegate methods must return a BOOL.'

Here is what my method looks like:

func ccPhysicsCollisionBegin(pair: CCPhysicsCollisionPair!, aMan nodeA: CCNode!, fuel nodeB: CCNode!) -> Bool {
        return true
    }

EDIT: Looks like this may have been a bug in Swift. I cannot recreate this anymore with the current version of Xcode. Seems like you can just return Bool now.

You’ll need to return the ObjCBool type since Cocos2D-swift is written in Objective-C.

Your method should look like:

func ccPhysicsCollisionBegin(pair: CCPhysicsCollisionPair!, aMan nodeA: CCNode!, fuel nodeB: CCNode!) -> ObjCBool {
        return true
}

You can also use:

func ccPhysicsCollisionBegin(pair: CCPhysicsCollisionPair!, aMan: CCNode!, fuel: CCNode!) -> ObjCBool {
        return true
}

And then reference the collided nodes with aMan and fuel instead of nodeA and nodeB.

I just ran into this problem with my project. I had to change my ccPhysicsCollisionBegin methods to return ObjCBool in order to run on an physical device. I can get away with using Bool when it’s running in the simulator, but a physical device needs ObjCBool to run without throwing an exception.

Using Xcode 6.2

分类: stackoverflow精选, unity3d 标签:

第一章《3D理论初步》

2014年10月15日 没有评论

      小伙伴们,你们好!

 

      经历了两年多的Cocos2d-x的学习与开发,我相信你们都已经成长为一名合格的Cocos2d-x程序员。但是,千万不要觉得这样就可以万事无忧了!3D时代已经来临,3D手游的产品越来越多,怎么办?使用Unity3D?嗯,是啊,Unity3D看起来不错的样子,不过,你是愿意放弃长期习惯的VC++的开发方式?你是否愿意放弃开源引擎自由掌控代码的感觉?你是否愿意从此站在引擎底层之外,只是做一个使用者?


      如果你想快速的基于现有的Cocos2d-x经验或项目来增加3D部分功能,或者你的志向是和我一样,成为一个引擎开发者,那么,勇敢的踏出这一步吧。相信自已经过短期的学习也有能力做出完整的3D引擎。

 

       注:限于时间仓促,水平有限,错误及不妥之处在所难免,望各位兄台海涵并及时指正。


第一章《3D理论初步》


  

           配套视频:http://my.tv.sohu.com/us/231143039/74549915.shtml

 

一.2D3D:


    相信大家都学过平面几何与立体几何,这2D3D就是这两种几何空间,我们之前一直在平面几何里去展示自已的游戏内容,进入到立体几何空间后,XY增加了一个纵深Z,但难度却不仅仅只是增加了1/23D以摸拟现实为最高目标。除了位置的表现方式不同,其在大小,质感,光与影,环境氛围上均有更多深入的表现。

   哈哈,不要怪我,实在找不到太合适的图进行对比:

   这是一个2D场景中的美女:

[置顶]        第一章《3D理论初步》


    这个则是3D场景中的美女:


[置顶]        第一章《3D理论初步》


    同样都是美女,但3D场景中的建筑,人物,环境都有了具体的提升,它非常逼真,几乎和现实没有什么太大的不同。

一切,都源于它多了一个Z轴,使形状,位置,色深,质感有了更细腻的变化。

 

    千里之行,始于足下,在做出非常棒的3D游戏之前,我们先要学习好3D的空间理论,掌握3D空间的位置,旋转,偏移的处理,只有这样,才可能一步步真正的了解3D的精髓。

 

    首先来回顾一下,在2D空间中,我们使用笛卡尔坐标系:

[置顶]        第一章《3D理论初步》

    但在3D空间中,它变成了: 


[置顶]        第一章《3D理论初步》

    左手坐标系与右手坐标系。

 

    坐标系也好,右手系也罢,总归是用手心朝向,大拇指方向,手伸出的方向代表三个轴,XYZ。所有的空间计算也都基于这三个轴。下面我们来详细学习一下。

二.向量:


         空间的点,即一个位置的表示单位,学名向量,程序中就是一个结构,容纳了x,y,z三个变量。在Cocos2d-x中,这个结构体的名称叫做Vec3,它在libcocos2d/ math目录下的Vec3.h中定义。

[置顶]        第一章《3D理论初步》

class Vec3
{
public:
//X,Y,Z
float x;
float y;
float z;
//构造与拷贝构造
    Vec3();
    Vec3(float xx, float yy, float zz);
    Vec3(const float* array);
    Vec3(const Vec3& p1, const Vec3& p2);
Vec3(const Vec3& copy);
//从一个DWORD的色彩值中获取向量,即将R,G,B对应到X,Y,Z上返回。
static Vec3 fromColor(unsigned int color);
//析构
~Vec3();
//是否是零
bool isZero() const;
//是否是一
bool isOne() const;
//取得两个向量之间的角度
static float angle(const Vec3& v1, const Vec3& v2);
//与参数指定向量相加
void add(const Vec3& v);
//取得两个向量相加的和
static void add(const Vec3& v1, const Vec3& v2, Vec3* dst);
//将当前向量设置最大和最小值。
void clamp(const Vec3& min, const Vec3& max);
//对一个向量设置最大和最小值。
static void clamp(const Vec3& v, const Vec3& min, const Vec3& max, Vec3* dst);
//与参数指定向量进行叉积计算。
void cross(const Vec3& v);
//取得两个向量的叉积。
static void cross(const Vec3& v1, const Vec3& v2, Vec3* dst);
//与参数指定向量进行距离计算。
float distance(const Vec3& v) const;
    //与参数指定向量进行距离的平方计算。因为向量长度的计算公式是sqrt(x^2+y^2+z^2);但sqrt比较消耗CPU,所以呢,一般我们在进行长度的使用时,如果是对比操作而不是取值操作,那可以直接用这个函数,相对会快一些,毕竟,两个长度的平方对比和两个长度的对比没有什么区别。
float distanceSquared(const Vec3& v) const;
//与参数指定向量进行点积计算
float dot(const Vec3& v) const;
//取得两个向量的点积
static float dot(const Vec3& v1, const Vec3& v2);
//取得当前向量的长度
float length() const;
//取得当前向量的长度的平方。
float lengthSquared() const;
//对当前向量取反
void negate();
//向量归一化。
void normalize();
//取得当前向量的归一化向量值。
Vec3 getNormalized() const;
//对当前向量进行缩放。
void scale(float scalar);
//设置当前向量的值。
    void set(float xx, float yy, float zz);
    void set(const float* array);
    void set(const Vec3& v);
void set(const Vec3& p1, const Vec3& p2);
//与参数指定向量进行相减。
void subtract(const Vec3& v);
//取得两个向量的差
static void subtract(const Vec3& v1, const Vec3& v2, Vec3* dst);
//通过参数指定向量与时间值来取得插值向量值。
void smooth(const Vec3& target, float elapsedTime, float responseTime);
//操作符重载
    inline const Vec3 operator+(const Vec3& v) const;
    inline Vec3& operator+=(const Vec3& v);
    inline const Vec3 operator-(const Vec3& v) const;
    inline Vec3& operator-=(const Vec3& v);
    inline const Vec3 operator-() const;
    inline const Vec3 operator*(float s) const;
    inline Vec3& operator*=(float s);
    inline const Vec3 operator/(float s) const;
    inline bool operator<(const Vec3& v) const;
    inline bool operator==(const Vec3& v) const;
    inline bool operator!=(const Vec3& v) const;
    
    //x,y,z都为0的静态常量向量
    static const Vec3 ZERO;
   //x,y,z都为1的静态常量向量
    static const Vec3 ONE;
   //x轴静态常量向量
    static const Vec3 UNIT_X;
    //y轴静态常量向量
    static const Vec3 UNIT_Y;
    //z轴静态常量向量
    static const Vec3 UNIT_Z;
};

三.四元数:

      四元数,“Quaternion”,这个东西是用来表示一个旋转变换,即绕着一个轴转动了一定的角度。它没有什么独立存在的意义,需要与向量配合才有意义。

      它的结构定义在quaternion.h中。

class Quaternion
{
    friend class Curve;
    friend class Transform;

public:
    float x;
    float y;
    float z;
    float w;

    //构造与拷贝构造
    Quaternion();
    Quaternion(float xx, float yy, float zz, float ww);
Quaternion(float* array);
Quaternion(const Mat4& m);
    Quaternion(const Vec3& axis, float angle);
    Quaternion(const Quaternion& copy);
//析构
~Quaternion();
//返回单位四元数
    static const Quaternion& identity();
//返回x,y,z,w都为0的四元数
    static const Quaternion& zero();
//是否是单位四元数
bool isIdentity() const;
//是否x,y,z,w都为零
    bool isZero() const;
//从矩阵中创建四元数
static void createFromRotationMatrix(const Mat4& m, Quaternion* dst);
//以一个轴和旋转角度生成一个四元数。
    static void createFromAxisAngle(const Vec3& axis, float angle, Quaternion* dst);
//对当前四元数取反
void conjugate();
//返回当前四元数的取反四元数值。
Quaternion getConjugated() const;
//逆四元数。
    bool inverse();
//返回当前四元数的逆四元数。
Quaternion getInversed() const;
//四元数相乘
void multiply(const Quaternion& q);
//返回两个四元数相乘的结果
static void multiply(const Quaternion& q1, const Quaternion& q2, Quaternion* dst);
//对当前四元数进行归一化操作。
void normalize();
//取得当前四元数的归一化四元数值。
Quaternion getNormalized() const;
//设置四元数的值
void set(float xx, float yy, float zz, float ww);
void set(float* array);
    void set(const Mat4& m);
    void set(const Vec3& axis, float angle);
void set(const Quaternion& q);
//对当前四元数进行单位化操作。
    void setIdentity();
    //将四元数再按照指定轴转动一定角度。
float toAxisAngle(Vec3* e) const;
//对两个四元数按指定比例进行线性插值。
    static void lerp(const Quaternion& q1, const Quaternion& q2, float t, Quaternion* dst);
//对两个四元数按指定比例进行球面线性插值,更精确但比上面函数操作更慢。
    static void slerp(const Quaternion& q1, const Quaternion& q2, float t, Quaternion* dst);
   //对两个四元数按指定比例进行球面样条插值。
    static void squad(const Quaternion& q1, const Quaternion& q2, const Quaternion& s1, const Quaternion& s2, float t, Quaternion* dst);

    //操作符重载
    inline const Quaternion operator*(const Quaternion& q) const;
    inline Quaternion& operator*=(const Quaternion& q);

private:
//插值所用函数
    static void slerp(float q1x, float q1y, float q1z, float q1w, float q2x, float q2y, float q2z, float q2w, float t, float* dstx, float* dsty, float* dstz, float* dstw);
//球面样条插值所用函数
    static void slerpForSquad(const Quaternion& q1, const Quaternion& q2, float t, Quaternion* dst);
};

       四元数的功能主要就是旋转,它的最大价值就是可以用最少的数据表示旋转!这里所谓“最少的数据”,是相对于谁说的呢?下面我们来介绍下矩阵。

四.矩阵:

       矩阵,“Matrix”,完全的变换行为,也没有独立存在的意义,需要与向量配合。矩阵的主要行为就是使向量平移,旋转,缩放。下面我们来看一下它的定义,在mat4.h中可以找到:

/*

A 4×4 matrix

 

        | 0   4   8  12 |

mat =   | 1   5   9  13 |

        | 2   6  10  14 |

        | 3   7  11  15 |

*/

class Mat4
{
public:
    //16个浮点值
    float m[16];

    //构造与拷贝构造
    Mat4();
    Mat4(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24,float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44);
    Mat4(const float* mat);
    Mat4(const Mat4& copy);
    //析构
    ~Mat4();
//创建观察矩阵,这个观察矩阵是用于定位当前摄像机的空间状态的。它通过摄像机的位置,观察目标位置以及上方向来创建。

[置顶]        第一章《3D理论初步》

    static void createLookAt(const Vec3& eyePosition, const Vec3& targetPosition, const Vec3& up, Mat4* dst);
    static void createLookAt(float eyePositionX, float eyePositionY, float eyePositionZ,
                             float targetCenterX, float targetCenterY, float targetCenterZ,
                             float upX, float upY, float upZ, Mat4* dst);

   //创建投影矩阵这个投影矩阵,主要是用于计算摄像机成像的投影变换,它是一个视锥体,有上下,左右,近远截面,当你绘制好一个图像时,屏幕要计算在哪里显示它,它在3D空间中的透视大小等,就需要用到这个投影矩阵。

 

如图所示:

[置顶]        第一章《3D理论初步》

    static void createPerspective(float fieldOfView, float aspectRatio, float zNearPlane, float zFarPlane, Mat4* dst);
//创建正交投影矩阵,这个怎么理解呢,就是去除透视后的投影矩阵,它的视锥就变成一个长方体了:

[置顶]        第一章《3D理论初步》

而它看到的物体就失去了透视,与透视投影变换后的结果对比看下图:

[置顶]        第一章《3D理论初步》

static void createOrthographic(float width, float height, float zNearPlane, float zFarPlane, Mat4* dst);
    static void createOrthographicOffCenter(float left, float right, float bottom, float top,
                                            float zNearPlane, float zFarPlane, Mat4* dst);
//创建公告板矩阵
    static void createBillboard(const Vec3& objectPosition, const Vec3& cameraPosition,
                                const Vec3& cameraUpVector, Mat4* dst);
    static void createBillboard(const Vec3& objectPosition, const Vec3& cameraPosition,
                                const Vec3& cameraUpVector, const Vec3& cameraForwardVector,
                                Mat4* dst);

   //创建缩放矩阵
    static void createScale(const Vec3& scale, Mat4* dst);
    static void createScale(float xScale, float yScale, float zScale, Mat4* dst);
//创建旋转矩阵。注意:在这里可以解答上面四元数省数据量的疑问了,对于只有旋转变换的行为,用矩阵需要16个FLOAT,而四元数只需要4个FLOAT。在只使用旋转变换的处理时,这一点对引擎在CPU计算上的消耗是有降低的。比如骨骼动画。

static void createRotation(const Quaternion& quat, Mat4* dst);
//通过指定轴向和旋转角度创建旋转矩阵
    static void createRotation(const Vec3& axis, float angle, Mat4* dst);

    //由绕X轴旋转指定角度创建一个旋转矩阵
    static void createRotationX(float angle, Mat4* dst);
    //由绕Y轴旋转指定角度创建一个旋转矩阵
    static void createRotationY(float angle, Mat4* dst);
    //由绕Z轴旋转指定角度创建一个旋转矩阵
    static void createRotationZ(float angle, Mat4* dst);

    //创建一个平移矩阵
    static void createTranslation(const Vec3& translation, Mat4* dst);
    static void createTranslation(float xTranslation, float yTranslation, float zTranslation, Mat4* dst);

    //矩阵所有浮点值加一个数
    void add(float scalar);

    //返回矩阵所有浮点值加一个数的结果
    void add(float scalar, Mat4* dst);

    //当前矩阵与参数指定矩阵相加
    void add(const Mat4& mat);

    //返回两个矩阵相加的结果
    static void add(const Mat4& m1, const Mat4& m2, Mat4* dst);

    //将矩阵分解为旋转,平移,缩放的值。
    bool decompose(Vec3* scale, Quaternion* rotation, Vec3* translation) const;

    //反回行列式的计算结果
    float determinant() const;

    //取得缩放值
    void getScale(Vec3* scale) const;

    //取得旋转信息,返回为一个四元数
    bool getRotation(Quaternion* rotation) const;

    //取得平移信息,返回一个向量
    void getTranslation(Vec3* translation) const;

    //如果当前矩阵是观察矩阵,返回上方向
    void getUpVector(Vec3* dst) const;

    //如果当前矩阵是观察矩阵,返回下方向
    void getDownVector(Vec3* dst) const;

 	//如果当前矩阵是观察矩阵,返回左方向
    void getLeftVector(Vec3* dst) const;

    //如果当前矩阵是观察矩阵,返回右方向
    void getRightVector(Vec3* dst) const;

   //如果当前矩阵是观察矩阵,返回前方向
    void getForwardVector(Vec3* dst) const;

//如果当前矩阵是观察矩阵,返回后方向
    void getBackVector(Vec3* dst) const;

    //将当前矩阵进行逆矩阵操作
    bool inverse();

    //取得当前矩阵的逆矩阵
    Mat4 getInversed() const;

    //是否是单位矩阵
    bool isIdentity() const;

    //当前矩阵与一个浮点值相乘
    void multiply(float scalar);

    //计算当前矩阵与一个浮点值相乘的结果,输出到参数矩阵中
    void multiply(float scalar, Mat4* dst) const;

    //计算一个矩阵与一个浮点值相乘的结果,输出到参数矩阵中
    static void multiply(const Mat4& mat, float scalar, Mat4* dst);

    //当前矩阵与参数指定矩阵进行相乘
    void multiply(const Mat4& mat);

    //返回两个矩阵相乘的结果
    static void multiply(const Mat4& m1, const Mat4& m2, Mat4* dst);

    //对当前矩阵的所有值取反
    void negate();

    //取得对当前矩阵的所有值取反的结果
    Mat4 getNegated() const;

    //对当前矩阵用指定的四元数进行旋转
    void rotate(const Quaternion& q);

    //计算当前矩阵跟据指定的四元数进行旋转,返回结果
    void rotate(const Quaternion& q, Mat4* dst) const;

//对当前矩阵绕指定轴旋转指定角度
void rotate(const Vec3& axis, float angle);

    //对当前矩阵绕指定轴旋转指定角度并输出结果给参数
    void rotate(const Vec3& axis, float angle, Mat4* dst) const;

    //向X轴旋转一定角度
    void rotateX(float angle);

    //向X轴旋转一定角度来并输出结果给参数
    void rotateX(float angle, Mat4* dst) const;

    //向Y轴旋转一定角度
void rotateY(float angle);

    //Y轴旋转一定角度来并输出结果给参数
    void rotateY(float angle, Mat4* dst) const;

//向Z轴旋转一定角度
    void rotateZ(float angle);

    //向Z轴旋转一定角度并输出结果给参数
    void rotateZ(float angle, Mat4* dst) const;

    //对当前矩阵进行统一值缩放
    void scale(float value);

    //对当前矩阵进行统一值缩放并输出结果给参数
    void scale(float value, Mat4* dst) const;

    //对当前矩阵进行不同的缩放值
    void scale(float xScale, float yScale, float zScale);

    //对当前矩阵进行不同的缩放值并输出结果给参数
    void scale(float xScale, float yScale, float zScale, Mat4* dst) const;

    //同上,只是参数为一个向量
    void scale(const Vec3& s);
    void scale(const Vec3& s, Mat4* dst) const;

    //对当前矩阵赋值操作
    void set(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24,
             float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44);

    //通过一个浮点数组的地址对当前矩阵赋值操作
    void set(const float* mat);

    //通过另一个矩阵对对当前矩阵赋值操作
    void set(const Mat4& mat);

    //设置当前矩阵为单位矩阵
    void setIdentity();

    //设置当前矩阵所有值都为0
    void setZero();

    //当前矩阵与参数指定矩阵进行减法操作
    void subtract(const Mat4& mat);

    //计算两个矩阵相减并将结果输出到参数
    static void subtract(const Mat4& m1, const Mat4& m2, Mat4* dst);

    //对一个向量进行矩阵变换操作(不带平移)并将结果输出到本身的向量中。
    void transformPoint(Vec3* point) const;

    //对一个向量进行矩阵变换操(不带平移)作并将结果输出到参数。
    void transformPoint(const Vec3& point, Vec3* dst) const;

    //对一个向量进行矩阵变换操作(带平移)并将结果输出到本身的向量中。
    void transformVector(Vec3* vector) const;

   //对一个向量进行矩阵变换操作(带平移)并将结果输出到参数。
    void transformVector(const Vec3& vector, Vec3* dst) const;
    void transformVector(float x, float y, float z, float w, Vec3* dst) const;

    //对一个四元向量进行矩阵变换操作并将结果输出到本身的向量中。
    void transformVector(Vec4* vector) const;

    //对一个四元向量进行矩阵变换操作并将结果输出到参数。
    void transformVector(const Vec4& vector, Vec4* dst) const;

    //对当前矩阵进行平移
    void translate(float x, float y, float z);
//对当前矩阵进行平移并将结果输出到参数。
    void translate(float x, float y, float z, Mat4* dst) const;

    //通过向量对当前矩阵进行平移
    void translate(const Vec3& t);
//通过向量对当前矩阵进行平移并将结果输出到参数。
    void translate(const Vec3& t, Mat4* dst) const;

    //对当前矩阵进行转置操作
    void transpose();

    //返回当前矩阵的转置矩阵
    Mat4 getTransposed() const;

    //操作符重载
    inline const Mat4 operator+(const Mat4& mat) const;
    inline Mat4& operator+=(const Mat4& mat);
    inline const Mat4 operator-(const Mat4& mat) const;
    inline Mat4& operator-=(const Mat4& mat);
    inline const Mat4 operator-() const;
    inline const Mat4 operator*(const Mat4& mat) const;
    inline Mat4& operator*=(const Mat4& mat);

    //16个浮点值都为0的矩阵
    static const Mat4 ZERO;
//单位矩阵
static const Mat4 IDENTITY;

private:
//创建公告板用到的处理
    static void createBillboardHelper(const Vec3& objectPosition, const Vec3& cameraPosition,
                                      const Vec3& cameraUpVector, const Vec3* cameraForwardVector,
                                      Mat4* dst);
};

五.作业:

 

(1) 定义一个向量,给它赋值,并计算它的模。

(2) 对两个向量进行加减操作。

(3) 判断两个两量朝向是否一致。

(4) 通过绕Z轴旋转60度定义一个四元数,并将向量(1,0,0)进行相应变换。

(5) 对一个向量(0,1,0)缩放2倍后,绕X轴旋转90度,之后平移到(0,1,0)

 

分类: 基础知识 标签:

使用 python 读写中文json

2014年10月13日 没有评论

读写中文json

想要 读写中文json ,可以使用python中的 json 库可以对json进行操作。读入数据可以使用 json.load

1
2
3
f = file(path)
data = json.load(f)
 

json被载入到一个dict类型的object对象中。

使用 json.dump可以输出json。不过输出的文本并不是中文,而是转换为 utf-8的格式。此处需要:

1
2
output = json.dump(jsonData,targetFile,ensure_ascii=False,indent=4)
 

输出中文的json。通过使用 ensure_ascii=False,输出原有的语言文字。indent参数是缩进数量。

更改写文件格式

将上一步导出的 string 直接写文件会报错(可能只在Python2.7中出现):

UnicodeEncodeError: 'ascii' codec can't encode characters in position 1-9: ordinal not in range(128)

这是由于此处输出的一些ascii编码不支持,所以报错。

解决的办法是,在输出的时候,对文件制定特定的UTF-8编码:

1
2
3
4
5
import codecs
 
with codecs.open(path,‘w’,‘utf-8′) as w:
    #write to w
 

使用这种方式可以成功输出到文件。

本篇博客出自阿修罗道,转载请注明出处,禁止用于商业用途:http://blog.csdn.net/fansongy/article/details/40051413 

分类: 未分类 标签:

Cocos2d-x 更改文字换行风格 ( cocos2dx change line )

2014年10月11日 没有评论

Cocos2dx change line

在 cocos2dx change line 的实现中,我们可以简单的使用 dimensions属性控制换行。使用它只需将对应的参数值传入构造函数,或者调用 setDimensions 函数即可。

它的换行策略是:当一个单词超出限制长度时,将它移动到下一行。有时这种策略并不合适。例如,在某些语言中,单词都比较长。如果采用这种策略,会出现每一行中只有一个单词。因此需要更改换行策略。当超出限制长度时,使用 - 作为一个分隔单词的标志。

Cocos2d-x 更改文字换行风格 ( cocos2dx change line )

换行策略调用

通过查看Cocos2d-x中换行的实现方式,我发现换行策略是区分平台编程出来的代码。也就是说,为了更改换行策略,我需要分别编写 IOS 和 Java 两部分代码。因为我的目标平台是 Android 平台,因此我只对Java文件进行了更改。IOS 平台的换行策略调用的是系统函数,有兴趣研究的读者可以跟踪源码更改。入口函数为:

bool CCTexture2D::initWithString(const char *text, ccFontDefinition *textDefinition)
{
    ......
 
    CCImage* pImage = new CCImage();
    do
    {
        CC_BREAK_IF(NULL == pImage);
        
        bRet = pImage->initWithStringShadowStroke(text,
                                                  (int)textDefinition->m_dimensions.width,
                                                  (int)textDefinition->m_dimensions.height,
                                                  eAlign,
                                                  textDefinition->m_fontName.c_str(),
                                                  textDefinition->m_fontSize,
                                                  textDefinition->m_fontFillColor.r / 255,
                                                  textDefinition->m_fontFillColor.g / 255,
                                                  textDefinition->m_fontFillColor.b / 255,
                                                  shadowEnabled,
                                                  shadowDX,
                                                  shadowDY,
                                                  shadowOpacity,
                                                  shadowBlur,
                                                  strokeEnabled,
                                                  strokeColorR,
                                                  strokeColorG,
                                                  strokeColorB,
                                                  strokeSize);
        
        
        CC_BREAK_IF(!bRet);
        bRet = initWithImage(pImage);
        
    } while (0);
    
    ......
}

更改Java实现

Android 平台对应的 Java 文件位于 $(2DX-Root)/cocos2dx/platform/android/java/src/org/cocos2dx/lib 文件夹中,文件名是 Cocos2dxBitmap.java

首先,我增加了 divideStringWithMaxWidthByFlag 函数,将它作为另一个换行策略的实现函数。

// add by fansy for "—" style words
    private static LinkedList<String> divideStringWithMaxWidthByFlag(
           final String pString, final int pMaxWidth, final Paint pPaint) {
        final int charLength = pString.length();
        int start = 0;
        int tempWidth = 0;
        LinkedList<String> strList = new LinkedList<String>();
        
        if( !isChinese(pString) )
        {
            /* Break a String into String[] by the width & should wrap the word. */
            for (int i = 1; i < charLength-1; ++i) {
                tempWidth = (int) FloatMath.ceil(pPaint.measureText(pString, start,i+1));
                if (tempWidth >= pMaxWidth) {
                    if(pString.charAt(i) == ' ') //end with " "
                    {
                        //change line at i
                        strList.add(pString.substring(start, i));
                        i = i + 1; // skip space
                    }
                    else if(i>1 && pString.charAt(i-2) == ' ') //only one "-" left after change line
                    {
                        //change line at i-2
                        strList.add(pString.substring(start, i-2));
                        i = i -2; // skip space
                    }
                    else if(i>0 && pString.charAt(i-1) == ' ') //only one "-" left after change line
                    {
                        //change line at i-1
                        strList.add(pString.substring(start, i-1));
                        i = i -1; // skip space
                    }
                    else if(i>0) //replace "-" at i-2
                    {
                        //split at i-1 add "-" at tail change line at i-1
                        strList.add(pString.substring(start, i-1)+"-");
                        i--;
                    }
                    
                    /* Remove spaces at the beginning of a new line. */
                    while (pString.charAt(i) == ' ') {
                        ++i;
                    }
                    
                    start = i;
                }
            }
            
            /* Add the last chars. */
            if (start < charLength) {
                strList.add(pString.substring(start));
            }
        }
        else
        {
            strList = divideStringWithMaxWidth(pString, pMaxWidth, pPaint);
        }
        return strList;
    }
    
    // 根据Unicode编码完美的判断中文汉字和符号
    private static boolean isChinese(char c) {
        Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
        if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
            || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
            || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
            || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B
            || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
            || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS
            || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION) {
            return true;
        }
        return false;
    }
    
    // 完整的判断中文汉字和符号
    public static boolean isChinese(String strName) {
        char[] ch = strName.toCharArray();
        for (int i = 0; i < ch.length; i++) {
            char c = ch[i];
            if (isChinese(c)) {
                return true;
            }
        }
        return false;
    }
    
 //end add by fansy

增加函数之后,修改在 splitString 中的调用:

private static String[] splitString(final String pString,
            final int pMaxWidth, final int pMaxHeight, final Paint pPaint) {
        final String[] lines = pString.split("//n");
        String[] ret = null;
        final FontMetricsInt fm = pPaint.getFontMetricsInt();
        final int heightPerLine = (int) Math.ceil(fm.bottom - fm.top);
        final int maxLines = pMaxHeight / heightPerLine;
 
        if (pMaxWidth != 0) {
            final LinkedList<String> strList = new LinkedList<String>();
            for (final String line : lines) {
                /*
                 * The width of line is exceed maxWidth, should divide it into
                 * two or more lines.
                 */
                final int lineWidth = (int) FloatMath.ceil(pPaint
                        .measureText(line));
                if (lineWidth > pMaxWidth) {
                    strList.addAll(Cocos2dxBitmap.divideStringWithMaxWidthByFlag(
                            line, pMaxWidth, pPaint));
                } else {
                    strList.add(line);
        ......
}

更换调用函数后,编译打包,运行程序即可看到不同的换行效果。

本篇博客出自阿修罗道,转载请注明出处,禁止用于商业用途:http://blog.csdn.net/fansongy/article/details/39992241 

分类: 未分类 标签: