存档

‘基础知识’ 分类的存档

使用 sass/scss 编写CSS 快速上手

2015年4月9日 没有评论

Why Scss

CSS不是一种编程语言,它只是个配置文件,并没有生命。但我等大程序  怎么能容忍自己写出来的东西不能动态变化,不能封装继承,不能xxoo呢,于是就有了css预处理的概念。即,写是一套,用是一套。Scss是css预处理的一个选择,它依托于Ruby,算是逼格比较高的。相似的还有Less等,语言优劣之争意义不大,用好一个其他也是大同小异。

安装

Mac上自带Ruby,直接运行:

gem install sass

安装对应模块,然后可以使用:

sass --watch style.scss:style.css

命令来监视style.scss,当它有更改时,会自动编译成style.css。

友情提示: 我运行gem时完全不动,还以为是掉网了。后来听说是我大GFW威武,可以将源更改为x宝的服务器来解决:

$ gem sources -l
$ gem sources --remove https://rubygems.org/  
$ gem sources -a http://ruby.taobao.org/
$ gem sources -l

//然后我顺手更新了一下Ruby的版本
$ sudo gem update --system

经过上面的折腾,顺利安装sass 使用 sass/scss 编写CSS 快速上手

常用语法

变量

//定义
$magin : 30px; //px
$blue : #1875e7; //color
$side : left; //str Usage: boder-#{$side}-radius

所有的数字类型的变量都可以进行相应的计算。

嵌套

nav {
    ul {...}
    border : { //注意冒号 相当于树形属性 会编译成 border-color:red
        color : red;
    }
    a {
        &:hover { color :$blue;} //&表示引用上层 会编译成 a:hover{...}
    }
}

注释

标准的CSS注释 /* comment */ ,会保留到编译后的文件。

单行注释 // comment,只保留在SASS源文件中,编译后被省略。

在/*后面加一个感叹号,表示这是”重要注释”。即使是压缩模式编译,也会保留这行注释,通常可以用于声明版权信息。

继承

使用@extend可以继承相应的css:

.class1 {
    border:1px solid #ddd;
}

.class2 {
    @extend .class1;
    border-color: green;
}

写的时候要注意顺序,编译时,css是不会调顺序的,谁先谁后得想好了。

Mixin

这个是一个函数与宏的私生子。实现像函数,使用像宏。关键词为@mixin和@include

@mixin left($color, $value:10px) {
    color:$color;
    margin-left:$value;
}

.mydiv {
    @include left($blue,15px);
}

颜色处理函数

lighten(#cc3, 10%) // #d6d65c
darken(#cc3, 10%) // #a3a329
grayscale(#cc3) // #808080
complement(#cc3) // #33c

$linkColor: #08c;
a {
    text-decoration:none;
    color:$linkColor;
    &:hover{
      color:darken($linkColor,10%);
    }
}

用这个方法就能制作一个链接变灰的效果

引入文件

@import “style2.css”;

逻辑编译

想要真正的动起来,就得有判断啦,循环啦常规流程函数。

@if可一个条件单独使用,也可以和@else结合多条件使用

$type: monster;
p {
  @if $type == ocean {
    color: blue;
  } @else if $type == matador {
    color: red;
  } @else if $type == monster {
    color: green;
  } @else {
    color: black;
  }
}

for循环有两种形式,分别为:@for $var from <start> through <end>@for $var from <start> to <end>。$i表示变量,start表示起始值,end表示结束值,这两个的区别是关键字through表示包括end这个数,而to则不包括end这个数。

@for $i from 1 through 3 {
  .item-#{$i} { width: 2em * $i; }
}

each语法为:@each $var in <list or map>。其中$var表示变量,而list和map表示list类型数据和map类型数据。

$animal-list: puma, sea-slug, egret, salamander;
@each $animal in $animal-list {
  .#{$animal}-icon {
    background-image: url('/images/#{$animal}.png');
  }
}

$headings: (h1: 2em, h2: 1.5em, h3: 1.2em);
@each $header, $size in $headings {
  #{$header} {
    font-size: $size;
  }
}

sublime 分页

其实这个也不能算是Scss的知识了,只不过是用到它更方便一些。Sublime可以左右分屏,我们可以将源文件放在左侧的窗口中编译后的放在右侧,方便我们做检查。Mac上的快捷键比较变态:

cmd+option+ctrl+2

左右移动使用

cmd+shift+[

如果你觉得这篇文章对你有帮助,可以顺手点个,不但不会喜当爹,还能让更多人能看到它… 使用 sass/scss 编写CSS 快速上手

分类: 基础知识, 未分类 标签:

vim 安装Emmet插件

2015年1月26日 没有评论

Why html

最近说好的要学做移动App,想来想去,还是先从基础搞起,先把失散多年的html捡捡。虽然我会原生语言,不过用惯了跨平台的东西:

vim 安装Emmet插件

 

想来想去,既然是应用,效率应该还成,html应该够用了。跨得一手好平台。作为vim党,我找到了个插件,看着还不错。

what’s Emmet

Emmet是vi的一个插件,它可以让我等更方便的编辑html。你能在 这里 找到它的最新信息。

安装

下载项目解压缩。找到文件中的pluginautoload将它复制到

~/.vim/.

重启终端就成了。

测试

创建一个文件,用vi打开,输入:

html:5

注意要将光标至于最后的位置,我觉得它的原理是,检测从行首到光标位置的表达式,因此如果你光标在其他位置,表达式就不完整了。

接下来就是见证奇迹的时刻,在输入模式下,按下ctrl+y然后按下”,”。就会自动生成如下代码:

<!DOCTYPE HTML>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    _
</body>
</html>

看起来好酸爽。

More

当然它还有更多的用法。自动生成格式文本、图片啥的,你可以参考 官方的文档 英文比较简单,我就不人肉翻译了 ; )

 

分类: 基础知识 标签:

红孩儿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) 。将一个动画模型导出序列帧模型,并在引擎中加载显示控制每一帧。

分类: 基础知识 标签:

第一章《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)

 

分类: 基础知识 标签:

场景中的巨量几何体替代方案

2010年4月21日 没有评论

 人群:

 

     在3D游戏中,有时需要显示大量的人物或NPC动画。如果这些动画都使用模型,相信你的机器难以承受巨量的人物动画所需的计算量。

今天介绍一种方法。

 

     这是在RenderWare引擎的例子中看到的。如图,一个体育馆中的大量观众场景。这些观众都是实时动画。但FPS数量很高。做法就是使用片来代替模型。

场景中的巨量几何体替代方案

 

 

这是人物的图例。

 

场景中的巨量几何体替代方案

 

只用一张纹理,设好UV动画,提供给大量的片粒子,就OK了。

 

 

 

 

这一招我们也可以许多地方也看到。比如对OGRE 进行海量几何体优化的PagedGeometry代码包中对于场景中的树的LOD分级,近时是棵树的模型,远时过渡为片。其实效果也不错。毕竟远嘛。省了不少内存和计算量。FPS很高。

 

             

分类: 基础知识 标签:

2D横纵版与斜视角游戏地图开发原理

2009年12月8日 没有评论

    一个学生问的问题,借机做了个文档。发到博客。

                 

           
2D横纵版与斜视角游戏地图

               
开发原理

 

 

 

                                                      
作者
Honghaier

                                        
QQ
285421210

                                     
日期:2009-12-8

 

 

 

 

开发前提

 

 

1.假设您已经常握了C++语言,并能够熟练使用VC++开发工具。

 

2.假设您已经能够成功的封装了一个C2DPicture类。它具有以下函数。

 

//载入图片文件,成功返回一个索引值,失败返回-1

Int 
LoadImage(const char*  szFileName);

        
  

    
//
取得纹理

       
 LPDIRECT3DTEXTURE9          GetTexture();

 

    
//
取得图片信息

    
Int                                                GetImageWidth();

        
 Int                                               
GetImageHeight();

 

    
如果您无法做出这样的类,可以参考D3D的可变顶点格式D3DFVF_XYZW,有很多例子。

    
3.
出于效率的考虑,我认为您应该写一个管理器来管理这些C2DPicture类。假设叫C2DPictureManage.它具有以下函数.

Public:

    
//
载入所有图片,在这里你可以加载图片

    
BOOL   Init();

        
 //
取得对应的C2DPicture指针

        
C2DPicture*    GetPicture(int PictureIndex);

        
 //
在指定位置显示指定图片

        
 Void        ShowPicture(int PictureIndex,int Left,int Right,int Width,int Height);

 

Private:

        
 //
在指定位置渲染图片

        
Void           Render(
LPDIRECT3DTEXTURE9      
pTexture
int Left,int Right,int Width,int Height);

 

        
注:为什么不在C2DPicture类中做这个函数呢,因为出于效率的考虑,我们可以在C2DPictureManage定义四个顶点就行了,在ShowPicture函数中通过PictureIndex来获取C2DPicture对象指针。然后取得纹理和图片通过Render进行D3D渲染;

 

原理简述:

 
   2D的横纵版地图:

 

     
整个地图由横,纵,二个方向的TILE块组成。每个TILE块即是一层或几层图片。具体多少层看您的游戏设置了,一般来说,一个TILE里可以放三层,最底层是背景,第二层是远建筑,第三层是近建筑。当然,这只是基于拼合关系的TILE图,还有大量的草,树,星星什么的。是不基于拼合关系的。

 

     
所以您需要好好设计数据结构,比如这样,分为两种结构,

 

1.基于拼合的TILE

Struct
     SMapTile

{

        
Int    mPosX;                      
//
在地图中绝对位置X

        
Int  mPosY;                      
//
在地图中绝对位置Y

        
Int    Picture[3];                 
//
三层图片的
ID
}

;

2.不固定的地图元素

Struct      
SMapElement

{

        
Int    mPosX;                      
//
在地图中绝对位置X

        
Int  mPosY;                      
//
在地图中绝对位置Y

        
Int    mPictureIndex;        
//
图片索引

};

 

 

 2D横纵版与斜视角游戏地图开发原理

        
1.1

 

        
在这里,我引入了一个典型的2D横纵版场景。每个TILE45
* 32
像素的。我们这里的TILE三层为别是背景墙,斜桥,近墙,放在SMapTile结构中。不固定元素有草,灯光台。放在SMapElement结构中。

 

 

        
这只是一屏。实际上一个游戏场景需要至少几十屏,也可能上百屏。所以这一屏,只是整个场景中的一小区块。

        

        
所以一般我们会定义一个地图类CMap,它可能有以下成员

        
Private:   

        

        
Int                                                  
m_MapWidth;                             //
地图的TILE横向数量

        
Int                                                  
m_MapHeight;                           //
地图的TILE纵向数量

        
Int                                                  
m_TileWidth;                               //Tile
的像素宽

        
Int                                                  
m_TileHeight;                             //Tile
的像素高                                   

        
SMapTile*                                    m_pTileArray;          
                  //TILE
数组指针

        
Vector<SMapElement>             m_MapElementVec[3];            
//
介与各层间的元素数组

        
Public:

        

        
BOOL                 CreateNewMap(int vWidth,int vHeight,);      
//
创建地图,动态为TILE数组申请内存并初始化,如m_pTileArray
= new SMapTile[vWidth*vHeight];

 

        
VOID                  SetTilePicture(int vTileX,int vTileY,int vLayerIndex,int vPictureIndex);  
//
为指定的TILE的指定层设置图片索引

 

        
VOID                  AddElement(int vLayerIndex,int vPosX,int vPosY,int vPictureIndex); 
//
放入元素

        

        
VOID                  RenderMap(int vLeft,int vTop,int vWidth,int vHeight);                    
//
显示场景中处于vLeft, vTop, vWidth, vHeight区块的地图。        
这个理所应当就是屏幕矩形了。

 

        

        
VOID                  RenderElement(int 
vLayerIndex, int vLeft,int vTop,int vWidth,int vHeight);      
//
显示处于对应矩形中的元素

 

        
那么,怎么能够显示任意区块呢?这个就是2D场景漫游了。

 

        
首先,我们先创建一个地图,假设设为200*100TILE横纵向数量。并设置TILE像素高,宽为48*48。这样我们就知道场景有多大了。一个屏幕一般为800*600大小。

200*48 * 100 *48 / (800*600) = 96.  
嗯,貌似还可以。

 

        
我们在RenderMap函数中应能够正确的处理TILE和元素的渲染。下面是实现过程。

 

 

我们假设有全局对象C2DPictureManage            
G_PictureManage;

 

VOID                 
CMap::RenderMap(int vLeft,int vTop,int vWidth,int vHeight)

{

        

        
Int             TileX
      
=  vLeft / m_TileWidth ;       
         //
计算格子位置

        
Int             TileIY
     
=  vTop / m_ TileHeight;

        
        

        
Int             OffSetX   =
 
vLeft% m_TileWidth;                  
//
计算偏移

        
Int             OffSetY     =
 
vTop % m_ TileHeight;                         

 

//计算从哪开始贴图

        
Int             Left          
=  OffSetX > 0 ? (OffSetX  - 
m_TileWidth) : 0;

        
Int             Top           
=  OffSetY > 0 ? (OffSetY  - 
m_TileHeight) : 0;

 

//计算总共多少TILE

        
Int             TileNumX         
= OffSetX > 0 ? (vWidth / m_TileWidth +1) : (vWidth / m_TileWidth;)

 

        
Int             TileNumY         
= OffSetY > 0 ? (vHeight / m_ TileHeight +1) : (vHeight / m_ TileHeight;)

 

        
//
背景

        
For(int     I = 0  ;
 
I < TileNumY; I ++)

        
For(int      J = 0         ; 
j < TileNumX ; j ++)

        
{

                  
Int  TileIndex = I * m_MapWidth + j;

                  
Int    PictureIndex  = m_pTileArray[TileIndex]. Picture[0];

                  
If(PictureIndex  >= 0)

                  
{

                           
G_PictureManage. ShowPicture (PictureIndex, Left + j* m_TileWidth , Top + i* m_TileHeight, m_TileWidth, m_TileHeight);

                  
}

        
}

 

        
//
背间与远建筑层间元素

        
RenderElement(0, vLeft, vTop, vWidth, vHeight); 

        

        
//
远建筑层

        
For(int     I = 0  ; 
I <  TileNumY; I ++)

        
For(int      J = 0         ; 
j < TileNumX ; j ++)

        

                   Int 
TileIndex = I * m_MapWidth + j;

                  
PictureIndex  = m_pTileArray[TileIndex]. Picture[1];

                  
If(PictureIndex  >= 0)

                  
{

                           
G_PictureManage. ShowPicture (PictureIndex, Left + j* m_TileWidth , Top + i* m_TileHeight, m_TileWidth, m_TileHeight);

                  
}

        
}

        

        
//
远建筑层与近建筑层间元素

        
RenderElement(1, vLeft, vTop, vWidth, vHeight); 

                  

        
//
近建筑层

        
For(int     I = 0  ; 
I < TileNumY; I ++)

        
For(int      J = 0         ; 
j < TileNumX ; j ++)

        
{

                     Int 
TileIndex = I * m_MapWidth + j;

 

                  
PictureIndex  = m_pTileArray[TileIndex]. Picture[2];

                  
If(PictureIndex  >= 0)

                  
{

                           
G_PictureManage. ShowPicture (PictureIndex, Left + j* m_TileWidth , Top + i* m_TileHeight, m_TileWidth, m_TileHeight);

                  
}

        
}

 

        
//
近建筑层上元素

        
RenderElement(2, vLeft, vTop, vWidth, vHeight); 

}

 

 

//显示处于对应屏幕矩形中的元素

VOID                 
CMap::RenderElement(int  vLayerIndex, int vLeft,int vTop,int vWidth,int vHeight)

{       

        
//
取得数量

        
Int    size = m_MapElementVec[vLayerIndex].size();

 

        
For(int I = 0 ; I < size ; i++)

        
{

                  
//
图片索引

                  
Int PictureIndex = m_MapElementVec[vLayerIndex][i]. mPictureIndex;

                  
If(PictureIndex > 0)

                  
{       

                           
C2DPicture*    tpPicture = G_PictureManage.GetPicture(PictureIndex);

                           
If(tpPicture)

                           
{       

 

                           
         Int    Left = m_MapElementVec[vLayerIndex][i].mPosX;

                           
         Int    Top = m_MapElementVec[vLayerIndex][i].mPosY;

                           
         Int    Right = Left + tpPicture->
GetImageWidth();

        
                            Int   
Bottom = Top + tpPicture->
GetImageHeight ();

                                    

                                    
//
判断与格子是否有交集

 

                                    
If(Left > (vLeft + vWidth))continue;

                                    
If(Top > (vTop + vHeight))continue;

                                    
If(Right < (vLeft))continue;

                                    
If(Bottom < (vTop))continue;

 

                                    
//
如果有交集,则渲染

 

G_PictureManage. ShowPicture (PictureIndex, m_MapElementVec[vLayerIndex][i].mPosX – Left , m_MapElementVec[vLayerIndex][i].mPosY 
- Top);

 

                           
}
                   }
         }

}

 

 

                  
以上是核心显示代码,为了测试,您可以写一个MFC程序并完善SetTilePictureAddElement等函数。通过鼠标消息处理来增加对应的TILE和元素,并通过键盘移动来改变屏幕的矩形。来制做一个可以漫游的简单的场景编辑器。

 

                  
OK,2D
横纵版的场景原理讲述完了。

 

 

        
斜视角的地图:

 

                  
斜视角地图一般分为两类:45度角和30度角的。理解起来就是45度角的TILE是正方形,
30
度角的TILE是宽高比为21.  
45
度角的斜视角拼合会感觉眼角立体感陡一些,用得较少,一般都是30度的,

 

                  
2D横纵版与斜视角游戏地图开发原理

                  
1.2

 

                  
如图所示,所有的TILE中图片都是斜30度的。

        

                  
其实从绘制上与之前讲的横纵绘制并没有什么不同,算法不需要什么大的改动。但是在场景移动时。加上向左上30度移动。向左下30度移动。向右下30度移动,向右上30度移动。这样就能产生立体感了。

 

                  
一般视角会随着主角人物的移动来漫游,人物移动,带动屏幕矩形移动。如果要实现斜视角。则一般为人物八个方向的图片。然后在移动时通过人物的位置来取得屏幕在整个地图的矩形位置,然后绘制地图就行了。

 

                  
好了,基本的原理都讲解完了,看起来不难。但需要您亲自动手来实现它。开始吧,祝好运!

分类: 基础知识 标签: