Unreal2初步分析(u3的底层架构基本不变,跟u2相同)
Unreal2打造一套和脚本紧密结合的引擎
总概:
具体过程:
一。生成类信息体PrivateStaticClass:
dll装载时:
dll各类全局静态类信息对象PrivateStaticClass的构造函数中产生对应类的信息(对应包名,分配函数,静态构造函数),
各对象存入GAutoRegister链表.
二。初始各类信息体:
appInit()调用时:
1. 遍历GAutoRegister链表, 注册每一个静态类信息对象, 使用对应包名(所在dll)为其产生对应UPackage对象,获得对应dll句柄。
2. 并构造类成员Defaults缺省对象,将基类全局静态对象中成员default数据拷给它(所谓继承), 并用其作为this调用类对应静态构造函数,
3. 可在静态构造函数中注册类成员变量,注册的变量将作为原PrivateStaticClass对象的children结点.
5. 接着遍历全局静态对象的Children,将它们归类放在PropertyLink和ConfigLink链中(其中PropertyLink是全部所有属性)
6. 再来就是可通过config装载它自己的属性初始值到Default对象中。
三。 引擎初始:
1. 先初始引擎类:
EngineClass = UObject::StaticLoadClass( UGameEngine::StaticClass(), NULL, TEXT("ini:Engine.Engine.GameEngine"), NULL, LOAD_NoFail, NULL );
a. 为其产生ULinkerLoad:
在ULinkerLoad构造函数中:
1). 打开对应的包文件Engine.u, 并将它的LinkerRoot指向该类对应的包对象,
2). 从文件中读取Summary,名字表,导入导出表,最后将此ULinkerLoad对象存入GObjLoaders列表中,
3). 再通过 VerifyImport(i)来遍历导入表检查每一个导入项的有效性,
下面具体分析a. 步骤中VerifyImport(i)的内容:
每个导入表记录了在哪个包的名称,对应哪个类的名称,这个校验首先查找创建对应包对象(存dll句柄)以及对应ULinkerLoad对象(存包对应的.u文件句柄)
而在为导入项产生ULinkerLoad的构造函数中又回到了 1). 的步骤中,如此循环不停地装载所有依赖的dll及对应的.u文件。
b. 通过ULinkerLoad对象来产生对应的GameEngine对象
Linker->Create()即是通过.u文件读入的导入类来产生该对象(同时让SuperField指向产生根结点)并加到GObjLoaded链表中,如果产生失败,则
转换Result = StaticFindObject( ObjectClass, InOuter, InName );来通过dll中的类来产生它。
c. EndLoad()
遍历GObjLoaded链表,装载用Object->_LinkerIndex取出对应FObjectExport,将其属性内容读入该对象内容。
[注意]是先从基对象SuperField->Preload()开始再到其派生类逐步读入。另外在UStruct的Serialize函数中会装载children,
这又会依赖其它导入导出库,因此会重复多次触发导入导出相关处理。
另外如果是Native类,就不会通过link的create,而是通过StaticFindObject返回一个
2. 再产生引擎实例:
UEngine* Engine = ConstructObject<UEngine>( EngineClass );
这是使用第1步生成的EngineClass(即GameEngine对象)来创建一个新的名为GameEngine0的对象.(这会将GameEngine对象的内容克隆给GameEngine0)
然后再调用类的构造函数UGameEngine::UGameEngine()来初始成员。
3. 初始引擎:
Engine->Init();
遍历GObjObjects链表,进行加载
四。关于.u文件的生成:
由uccdepend转调用ucc.exe来遍历查找所有packages来读取uc脚本生成。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/flipcode/archive/2010/04/06/5453730.aspx
posted @
2010-11-03 23:13 flipcode 阅读(313) |
评论 (0) |
编辑 收藏
每个种族对应一个AiInterace对象,这个对象有所有该族的所有角色对象指针,并拥有以下AiRule成员:
AiRuleWorkerHarvest
AiRuleRefreshHarvester
AiRuleScoutPatrol
AiRuleRepair
AiRuleReturnBase
AiRuleMassiveAttack
AiRuleAddTasks
AiRuleBuildOneFarm
AiRuleProduceResourceProducer
AiRuleProduce
AiRuleBuild
AiRuleUpgrade
AiRuleExpand
一。遍历所有种族的AiInterace对象,进行AiRule的更新:
1. 在AiRuleXXXX的::test()测试该话是否要进行处理
2. 在AiRuleXXXX的::execute()中进行处理,为对应要处理的角色giveCommand(CommandType)
二。Command的更新:
每种cmd调用它成员UnitUpdater中对应的更新函数(比如HarvestCommandType::update中调用unitUpdater->updateHarvest),
在这更新中为对应角色指定对应的技能类形, 据技能类型进行处理。
AIRule-->CMD-->skilltype
另外操作一般都是以种族为类别进行的,所以选中一个单位后双击可以选中所有相同类型的角色。搜矿搜树什么的也归类搜索就减少消耗。
posted @
2010-11-03 23:12 flipcode 阅读(167) |
评论 (0) |
编辑 收藏
天龙早期版本武侠世界浏览看了下,发现
服务端用的select轮询方式,风格比较统一工整,消息处理成buf再组成pak进行发送或处理。
客户端界面用xml配置加lua事件触发
游戏对象使用结点树目录结构(这个n年前nebula引擎所用的技术,个人也喜欢这个方式)
游戏中的事件处理系统简单实用。
水用比较老的处理方式:系列图+深度图过渡,效果一般效率高
region+dijkstra广度优先的导航系统(天龙已修改成网络导航)
lightmap静态光照及动态光源
后期处理bloom以及热浪之类,效果都挺废.
渲染效果不太好但游戏整体比较完整
编辑器地表贴图列表加载被注释,对应文件也不见(打开并重写该文件后可用)
它的地表只有两层啊,其实没什么技术含量,分开刷的,那个自动拼接硬编码,刷第2层时通过if else判断居然有1200多行。。。
不过这种地表处理可以方便刷翻转墙壁。这很好。
小道消息暴下料:
1. 网络代码中居然残留韩文乱码(通过转换可看到对应韩文,转成其它语种则不行,可能是当初借鉴了些少代码)
2. 天龙八部是武侠世界版本基础上开发的(新改动不算大,寻路应该是由原来的dijkstra(不知道为什么用广度搜索不用a*)+region变成了网格导航)
3. 成吉是在天龙基础上做的(这个大家都知道,他们在打官司)
4. 天龙八部的前身武侠世界确实是是北京红图公司的,那个公司有一款名叫武侠世界web版本的mmorpg.(网上搜索不出他们之间有什么关系)
我觉得天龙2和成吉2基本上都是针对上一产品换个视角而已,技术及内容上没什么变化。。。
其实现有国产网游效果都比较朴实(除了剑3天下2之类外),却往往打着次世代的晃子。。。
目前网游已进入山寨抄袭换肤量产时代,当然高丽棒也是酱样子。。。
posted @
2010-10-21 22:08 flipcode 阅读(454) |
评论 (0) |
编辑 收藏
地表的处理方式很多:
1. 神迹的预生成处理,渲染时只有一层
2. glest加载时自动计算过渡层处理,渲染时只有一层。
3. hon的顶点alpha混合过渡处理,渲染时最多只有两层
4. 天龙的自动拼接,渲染时最多只有2层(天龙为了实现这个可谓花费大力气if else写了1000来行。。。。)
5. 海盗王的自动拼接,渲染时最多4层混合
6. 三国争霸,war3和梦3的自动拼接(带随机细节图块),渲染时最多4层混合
7. 当前流行的splatting地表使用alphamap进行混合,如: 虚幻2,wow, 泰坦之旅(这个可无限层,越多层越慢)
另外天机,神鬼也是这种方式(限死最多4层每个patch块)
posted @
2010-09-19 18:09 flipcode 阅读(199) |
评论 (0) |
编辑 收藏
测试了下实时shadowmap,比较简单取巧的方法:使用project shadowmap动态调整正交区域,这个取巧目前只适合这种视角游戏,而且速度也很快。(这种2.5d游戏也可以使用平面阴影速度快地渲染,而且没这么多锯齿) 上述水用了gem2中wave挠动,反射处理,没折射,边缘没作过渡(可简单传入水高图处理)
gif动画图:
posted @
2010-09-10 09:36 flipcode 阅读(312) |
评论 (0) |
编辑 收藏
有地方需要用,这里临时放下
posted @
2010-01-19 10:26 flipcode 阅读(262) |
评论 (0) |
编辑 收藏
动态类型识别以及池创建
由于频敏的new效率较低,需要避免直接new,这有两种办法:
1. 让程序重载new统一将内存分级别链来管理分配内存(可参考nebula1中的内存管理)
2. 使用对象池(可参考boost object pool)
我这里临时使用boost的object pool以及u2的rtti类概念写了一个池创建类
这样处理后就可以很方便地将类似派生树这样的类分别使用池方式统一接口来管理。类似
class kWindow;
class kButton : public kWindow;
class kLabel : public kWindow;
假如有很多这样的控件,如果你用类boost的object pool,那你要手写每个类对应一个pool mgr,
在释放时还要知道释放的对象属于哪个poolmgr, 再用这个poolmgr来释放。。。。
使用下面的类可简化为:
kButton *p = kTClass::Create("kButton"); // 统一创建接口(只需控件类名称)
p->Release(); // 统一释放接口
template <typename T, bool bTPool>
struct kAllocMgr;
template <typename T>
struct kAllocMgr<T, false>
{
static object_new_delete<T, default_user_allocator_new_delete> pool;
};
template <typename T>
object_new_delete<T, default_user_allocator_new_delete> kAllocMgr<T, false>::pool;
template <typename T>
struct kAllocMgr<T, true>
{
static object_pool<T, default_user_allocator_new_delete> pool;
};
template <typename T>
object_pool<T, default_user_allocator_new_delete> kAllocMgr<T, true>::pool;
//------------------------------------------------------------
//
flipcode@msn.comclass kRoot;
class kClass
{
friend class kKernelServer;
LPCTSTR m_szClassName;
INT m_nObjSize;
kRoot* (*m_pfnCreate)();
kClass* m_pBaseClass;
kClass* m_pNextClass;
public:
kClass (LPCTSTR szClassName, int nObjSize, kRoot* (*NewObject)(), kClass *baseClass);
LPCTSTR GetName(){
return m_szClassName;
}
};
//根据类名取得定义类
#define GETCLASS(ClassName) (&ClassName::m_class##ClassName)
//类定义声明宏
#define K_RTTI_DEC(ClassName) \
public: \
static kClass m_class##ClassName; \
virtual const kClass* GetClass(VOID) const; \
static kRoot* Create(); \
void Destory();
//类定义实现宏
#define K_RTTI_IMP(ClassName, baseClass) \
static TCHAR sz##ClassName[] = _T(#ClassName); \
kClass ClassName::m_class##ClassName(sz##ClassName, sizeof(ClassName), ClassName::Create, baseClass); \
const kClass* ClassName::GetClass() const \
{ return &ClassName::m_class##ClassName; } \
kRoot* ClassName::Create() \
{ \
ClassName *p = kAllocMgr<ClassName, true>::pool.construct(); \
return p; \
} \
void ClassName::Destory(){ \
kAllocMgr<ClassName,true>::pool.destroy(this); \
}
另外配合nebula的结点结象树的方法可以作到比较的对象管理(几年前我已写过教程):
class kKernelServer : public kSingleton<kKernelServer>
{
friend class kClass;
map<STRING, kClass*> m_ClassMap;
kClass *m_pFirstClass;
kRoot m_Root;
kRoot *m_pCWD;
kRoot* _NewObject( LPCTSTR szClassName, LPCTSTR szName );
void ReisgerClass(kClass* pNewClass);
public:
kKernelServer(void);
~kKernelServer(void);
kRoot* NewNode( LPCTSTR szClassName, LPCTSTR szPath );
kRoot* NewNode( LPCTSTR szClassName, LPCTSTR szPath, LPCTSTR szName);
kRoot* Lookup( LPCTSTR szPath );
void SetCwd( kRoot *o ){
this->m_pCWD = o ? o : &m_Root;
}
kClass* GetClass(LPCTSTR szClassName);
BOOL IsKindOf(const kClass* pThisClass, const kClass* pBaseClass) const;
BOOL LoadPlugin(LPCTSTR szPluginName, void * pParam);
void ShutDown(void);
protected:
struct PLUGININFO
{
HMODULE hModule;
STRING strPluginFile;
FUNDLLINIT pfnDllInit;
FUNDLLRELEASE pfnDllRelease;
};
void FreeAllPlugin(void);
std::vector< PLUGININFO > m_vecPlugin;
};
posted @
2009-12-10 10:44 flipcode 阅读(968) |
评论 (1) |
编辑 收藏
查找游戏中突然卡机问题小结
一。 查其原因主要是
lua局部表引起的,类似这样:
local tt[] = {}
tt[1] = {1,2,3}
tt[2] = {1,2}
...
总共就20个元素左右,并不多,这样的表格在极端情况下偶然发生消耗几百毫秒(在我们测试机上似乎是500ms左右)
二。 是new
脚本有些类似调用
for i=100, 5000, 1
。。。
addkeypos(i, p1, p2)
end
由于程序在addkeypos函数中使用了new,这导致有时偶然有些new消耗10多ms,
4000次下来累计偶然会占高。解决办法就是让程序去掉new处理成池的方式, 具体参考我的另一文章:
http://www.cppblog.com/flipcode/archive/2009/12/10/102902.html三。还有就是频率调用字符转换函数,类似sprintf("%s%d", "sdfdf", 2100 )也是一个非常消耗的地方!
解决办法就是用itoa以及atoi来转换
四。哦,还有就是lua的回收也是消耗很大,简单办法就是用它提供的分步回收(不在同一帧收完),看到云风博客提到分state方法,感觉比较烦,我们没有使用.
posted @
2009-12-10 10:23 flipcode 阅读(178) |
评论 (0) |
编辑 收藏
三国争霸论坛中玩家制作的漂亮截图
www.7fgame.com
posted @
2009-11-17 10:47 flipcode 阅读(230) |
评论 (0) |
编辑 收藏
高光+暴光效果
posted @
2009-10-25 00:27 flipcode 阅读(343) |
评论 (0) |
编辑 收藏