作者归档:keyboardmeow

自研新VR游戏《猎影特攻VR》上线Pico商店

近日,自研新VR游戏《猎影特攻VR》已上线Pico商店,同时,Steam商店页面也已公开,即将发布,敬请期待!该游戏曾屡获殊荣,包括高通XR大赛Pico优质游戏专项奖、2023星鲨杯全球虚拟现实大赛最佳体验奖等。

《猎影特攻VR》是一款基于VR一体机的猎杀狙击类VR游戏,玩家将化身主角“猎影”,从起初单纯的复仇行动,到逐渐担负起重建世界秩序的使命,进而踏上一场超越时空的猎杀之旅。

《猎影特攻VR》游戏宣传片

使用Unity进行RokidAR开发相关记录

硬件相关要注意的:

Rokid max studio+眼镜

眼镜端的线口不要插反

Rokid max studio的正面朝上,然后右下角的带蓝色包围的typec口才是真正充电和连眼镜的口;有时插电脑上没有磁盘显示,即使开了开发者模式也不行,可以用官方论坛中的一个软件慧眼助手类似360手机助手的一个软件来进行安装。当然adb应该也可以。

开发流程按照官方文档来就可以https://custom.rokid.com/prod/rokid_web/c88be4bcde4c42c0b8b53409e1fa1701/pc/cn/8d684c36da714eadaa23e47b6ab5a3a2.html?documentId=0858e7044d8e4218933cdb1775cf3c08

开发环境是Unity2021.3,在装XR Interaction Toolkit 时找不到这个包,可以点左上角的+号,选择 Add Package by name,输入 com.unity.xr.interaction.toolkit,即可导入。

不同的 Unity 版本可能导入 XR Interaction Toolkit 的方式会有点不一样,具体可以参考官方文档中的 Guides -> Installation(https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.3/manual/installation.html,在里面找到自己正在使用的版本)

Unity安卓平台调用打印机进行打印的几种方式

总体来说分为三种方式,打印图片、打印HTML、打印PDF文档,这三者实现复杂度依次增大,但自定义和灵活程度也是依次增高,适用于不同的需求。

安卓官方文档资料:

https://developer.android.com/training/printing?hl=zh-cn

安卓官方视频资料:

https://www.youtube.com/watch?v=Iub67ic87KI
https://www.youtube.com/watch?v=M_JGeGLpOKs

游戏开发中常用的设计模式

游戏开发中常用的设计模式
2009-02-23 01:43

使用设计模式来提高程序库的重复利用性是大型程序项目开发必须的。但是在“四人帮”的设计模式概述中提到了23种标准设计模式,伍但难以记住,而且有些设计模式更多的适用于应用程序开发,对游戏项目引擎设计并没有很多的利用价值。根据经验,精挑细选后,笃志在这里记录一些自认为有利用价值的设计模式,以便之后自己设计时使用。

一:观察者Observer

观察者的设计意图和作用是: 它将对象与对象之间创建一种依赖关系,当其中一个对象发生变化时,它会将这个变化通知给与其创建关系的对象中,实现自动化的通知更新。

游戏中观察者的适用环境有:

1:UI控件管理类。当我们的GUI控件都使用观察者模式后,那么用户的任何界面相关操作和改变都将会通知其关联对象-----我们的UI事件机。

2:动画管理器。很多时候我们在播放一个动画桢的时候,对其Frame有很大兴趣,此时我们设置一个FrameLister对象对其进行监视,获得我们关心的事件进行处理是必须的。

观察者伪代码:

//-------------------------------------------------------------------?-----------------------------------

// 被观察对象目标类

Class Subject

{

// 对本目标绑定一个观察者 Attach( Observer );

// 解除一个观察者的绑定 DeleteAttach( Observer );

// 本目标发生改变了,通知所有的观察者,但没有传递改动了什么

Notity()

{

For ( …遍历整个ObserverList …)

{ pObserver ->Update(); }

}

// 对观察者暴露的接口,让观察者可获得本类有什么变动GetState();

}

//-------------------------------------------------------------------------------------------------------

// 观察者/监听者类
Class Observer

{

// 暴露给对象目标类的函数,当监听的对象发生了变动,则它会调用本函数通知观察者

Void Update ()

{

pSubject ->GetState(); // 获取监听对象发生了什么变化

TODO:DisposeFun(); // 根据状态不同,给予不同的处理

}

}

//-------------------------------------------------------------------------------------------------------

非程序语言描述:

A是B的好朋友,对B的行为非常关心。B要出门,此时A给了B一个警报器,告诉B说:“如果你有事,立刻按这个警报器告诉我。”。结果B在外面遇上了麻烦,按下警报器(Update()),B就知道A出了事,于是就调查一下B到底遇到了什么麻烦(GetState()),当知道B原来是因为被人打了,于是立刻进行处理DisposeFun(),派了一群手下帮B打架。

当然关心A的人可以不止一个,C,D可能也对A很关心,于是A这里保存一个所有关心它的人的链表,当遇到麻烦的时候,轮流给每个人一份通知。

二:单件模式Singleton
单件模式的设计意图和作用是: 保证一个类仅有一个实例,并且,仅提供一个访问它的全局访问点。

游戏中适用于单件模式的有:

1:所有的Manger。在大部分的流行引擎中都存在着它的影子,例如SoundManager, ParticeManager等。

2:大部分的工厂基类。这一点在大部分引擎中还是见不到的,实际上,我们的父类工厂采用唯一实例的话,我们子类进行扩展时也会有很大方便。

单件模式伪代码:

//-------------------------------------------------------------------------------------------------------

{

Static MySingleton; // 单件对象,全局唯一的。

Static Instance(){ return MySingleton; } // 对外暴露接口

}

//-------------------------------------------------------------------------------------------------------

三:迭代器Iterator

迭代器设计意图和作用是: 提供一个方法,对一个组合聚合对象内各个元素进行访问,同时又不暴露该对象类的内部表示。

游戏中适用于迭代器模式的有: 因为STL的流行,这个设计已经广为人知了,我们对任何形式的资源通一管理时,不免会将其聚合起来,或者List,或者Vector,我们都需要一个对其进行访问的工具,迭代器无疑是一个利器。

迭代器伪代码:

//--------------?----------------------------------------------------------------------------------------

// 迭代器基类

Class Iterator

{

Virtual First();

Virtual Next();

Virtual End();

Virtual CurrentItem(); // 返回当前Item信息

}

//-------------------------------------------------------------------------------------------------------

// 聚合体的基类

Class ItemAggregate

{

Virtual CreateIterator(); // 创建访问自身的一个迭代器

}

//-----------------?-------------------------------------------------------------------------------------

// 实例化的项目聚合体

Class InstanceItemAggregate : public ItemAggregate

{

CreateIterator(){ return new InstanceIterator(this); }

}

//-------------------------------------------------------------------------------------------------------

四:访问者模式Visitor:

访问者设计意图和作用是: 当我们希望对一个结构对象添加一个功能时,我们能够在不影响结构的前提下,定义一个新的对其元素的操作。(实际上,我们只是把对该元素的操作分割给每个元素自身类中实现了而已)

漸戏中适用于访问者模式的有: 任何一个比较静态的复杂结构类中都适合采用一份访问者。这里的“比较静态的复杂结构类”意思是,该结构类中元素繁多且种类复杂,且对应的操作较多,但类很少进行变化,我们就能够将,对这个结构类元素的操作独立出来,避免污染这些元素对象。

1:例如场景管理器中管理的场景节点,是非常繁多的,而且种类不一,例如有Ogre中的Root, Irrchit中就把摄象机,灯光,Mesh,公告版,声音都做为一种场景节点,每个节点类型是不同的,虽然大家都有共通的Paint(),Hide()等方法,但方法的实现形式是不同的,当我们外界调用时需要统一接口,那么我们很可能需要需要这样的代码

Hide( Object )

{ if (Object == Mesh) HideMesh(); if (Object == Light) HideLight(); … }

此时若我们需要增加一个Object新的类型对象,我们就不得不对该函数进行修正。而我们可以这样做,让Mesh,Light他们都继承于Object,他们都实现一个函数Hide(),醣么就变成

Mesh::Hide( Visitor ) { Visitor.Hide (Mesh); }

Light::Hide(Visitor ){ Visitor.Hide (Light); }

我们在调用时只需要Object.Hide(Visitor){ return Visitor.Hide(Object); }

这样做的好处,我们免去了对重要函数的修正,Object.Hide(Visitor){}函数我们可以永久不变,但是坏处也是很明显的,因为将方法从对象集合结构中抽离出来,就意味着我们每增加一个元素,它必须继承于一个抽象的被访问者类,实现其全部函数,这个工作量很大。

所以,访问者是仅适合于一个装载不同对象的大容器,但同时又要求这个容器的元素节点不应当有大的变动时才使用。另外,废话一句,访问者破坏了OO思想的。

访问者伪代码:

//-----------------------------------------------------------------------?-------------------------------

// 访问者基类

Class Visitor

{

Virtual VisitElement( A ){ … }; // 访问的每个对象都要写这样一个方法

Virtual VisitElement( B ){ … };

}

// 访问者实例A

Class VisitorA

{

VisitElement( A ){ … }; // 实际的处理函数

VisitElement( B ){ … }; // 实际的处理函数

}

// 访问者实例B

Class VisitorB

{

VisitElement( A ){ … }; // 实际的处理函数

VisitElement( B ){ … }; // 实际的处理函数

}

// 被访问者基类

Class Element

{

Virtual Accept( Visitor ); // 接受访问者

}

// 被访问者实例A

Class ElementA

{

Accecpt( Visitor v ){ v-> VisitElement(this); }; // 调用注册到访问者中的处理函数

}

// 被访问者实例B

Class ElementB

{

Accecpt( Visitor v ){ v-> VisitElement(this); }; // 调用注册到访问者中的处理函数

}

//-------------------------------------------------------------------------------------------------------

五:外观模式Fa?ade

外观模式的设计意图和作用是: 将用户接触的表层和内部子集的实现分离开发。实际上,这个模式是个纸老虎,之后我们看伪代码立刻就会发现,这个模式实在用的太频繁了。

游戏中需要使用外观模式的地方是: 这个非常多了,举几个比较重要的。

1:实现平台无关性。跨平台跨库的函数调用。

2:同一个接口去读取不同的资源。

3:硬件自动识别处理系统。

外观模式伪代码

//-------------------------------------------------------------------------------------------------------

// 用户使用的接口类

Class Interface

{

// 暴露出来的函数接口函数,有且仅有一个,但内部实现是调用了两个类

Void InterfaceFun()

{
// 根据某种条件,底层自主的选择使用A或B的方法。用户无须关心底层实现

If ( XXX )

{

ActualA->Fun();

}

Else

{

ActualB->Fun();

}

};

}

// 实际的实现,不暴露给用户知道

Class ActualA

{

Void Fun();

}

// 实际的实现,不暴露给用户知道

Class ActualB

{

Void Fun();

}

怎么样,纸老虎吧,看起来很高深摸测的命名而已。

//----------------------------------------------------------------------------------------------------?--

六:抽象工厂模式AbstractFactory

抽象工厂的设计意图和作用是: 封装出一个接口,这个接口负责创建一系列互相关联的对象,但用户在使用接口时不需要指定对象所在的具体的类。从中文命名也很容易明白它是进行批量生产的一个生产工厂的作用。

游戏中使用抽象工厂的地方有: 基本上任何有批量的同类形式的子件地方就会有工厂的存在。(补充一句:下面代码中的ConcreteFactory1实例工厂就是工厂,而抽象工厂仅仅是工厂的一个抽象层而已。)

1:例如,在音频方面,一个音频的抽象工厂派生出不同的工厂,有音乐工厂,音效工厂。音效工厂中又有一个创建3D音效节点的方法,一个创建普通音效节点的方法。最终用户只需要SoundFactory->Create3DNode( pFileName ); 就可以创建一个节点了。

2:场景对象。

3:渲染对象。

4:等等……

工厂与单件,管理器Manager关系一定是非常紧密的。

抽象工厂伪代码:

//-------------------------------------------------------------------------------------------------------

class AbstractProductA {}; // 抽象的产品A基类
class AbstractProductB {}; //抽象的产品B基类

// 抽象工厂基类
class AbstractFactory
{
public:
virtual AbstractProductA* CreateProductA() = 0 ; // 创建ProductA
virtual AbstractProductB* CreateProductB() = 0 ; // 创建ProductB
} ;

class ProductA1 : public AbstractProductA {}; // 产品A的实例1
class Produ?tA2 : public AbstractProductA {}; // 产品A的实例2

class ProductB1 : public AbstractProductB {}; // 产品B的实例1
class ProductB2 : public AbstractProductB {}; // 产品B的实例2

// 实例工厂1

class ConcreteFactory1 : public AbstractFactory
{
virtual AbstractProductA* CreateProductA() { return new ProductA1() ; }
virtual AbstractProductB* CreateProductB() { return new ProductB1() ; }
static ConcreteFactory1* Instance() { }    // 实例工厂尽量使用单件模式
} ;

// 实例工厂2

class ConcreteFactory2 : public AbstractFactory
{
virtual Ab?tractProductA* CreateProductA() { return new ProductA2() ; }
virtual AbstractProductB* CreateProductB() { return new ProductB2() ; }
static ConcreteFactory2* Instance() {} // 实例工厂尽量使用单件模式
} ;

}

//-------------------------------------------------------------------------------------------------------

客户端代码:

Void main()
{
AbstractFactory *pFactory1 = ConcreteFactory1::Instance() ;
AbstractProductA *pProductA1 = pFactory1->CreateProductA() ;
? AbstractProductB *pProductB1 = pFactory1->CreateProductB() ;
AbstractFactory *pFactory2 = ConcreteFactory2::Instance() ;
AbstractProductA *pProductA2 = pFactory2->CreateProductA() ;
AbstractProductB *pProductB2 = pFactory2->CreateProductB() ;
}

//-------------------------------------------------------------------------------------------------------

=======================================================

Flash中oop的设计模式

人问我flash的as应该怎么写,我可以很负责任地告诉他,想怎么写就怎么写,因为as以及flash内部的构成模式决定了它的高度自由化。理论上来说,用按钮的on事件,加上stop(),play(),gotoAndStop(),gotoAndPlay(),就可以实现一个flash里大部分的逻辑关系,而且源代码简单易懂。但是大多数人不会这么做,是因为这种方法实在太让人敬佩。稍有常识的程序员都会知道面对对象与面对过程的区别。Flash 的编程虽然只是以脚本的形式出现,并且还很不完善,比如,没有多继承,但已经初步体现了oop的思想。这篇文章现在总结一下flash中面对对象的设计模式问题,以及一些自创的思路。

设计模式是美国一位建筑大师(同时也是信息工程师,画家,机械工程师…的)克里斯蒂安.亚历山大首先提出来的,很快被软件界的技术员们所接受推广,成为软件工程里至高无上的法则之一(有兴趣的人可以找他的《建筑的永恒之道》一书看看,相信会受益非浅)。简单地说就是在面对对象的基础上,包括面对对象,把要设计的整体的各个部分模式化,层次化,细粒度化,高度复用化,可控化,人性化。其中至高无上的原则是建立在需求的基础之上,也就是说,无论做什么,人的需求要放在第一位考虑,从这个角度考虑整个系统是否足够合理。这门学问是非常有趣的,尤其在flash中,可以应用到很多很好玩的实例中去。下面我按照一些通用的设计模式,举例说明,有错误的地方,敬请高手指正:

1.抽象工厂模式(Abstract Factory)食堂里吃的东西很多,而我只想吃一样,那么食堂这个概念对我来说就是个抽象工厂,每个窗口可以看成它的一个具体实现,我要做的就是,去食堂,找到那个窗口,从窗口里买我要吃的东西。
举例:flash前台与asp后台的交互,访问某个动态页面,从数据库里取出需要的数据,通常的做法是在后台就把数据集解析成xml字符串,再送给swf。每个业务逻辑模块,所取出的数据结构,也就是xml的结构是不一样的,我们要针对各个具体的业务逻辑,对相应的xml字符串解析,转换成可供显示的数组。也要把flash里文本输入的内容转换成 xml字符串,提交给后台也面
AbstractFactory.as

程序代码 程序代码
//抽象工厂的接口
Interface AbstractFactory{
//生成xml解析工厂的具体实现
function createXmlParseFactory();
}

XMLParserGetFactory.as

程序代码 程序代码
//生成解析读入的xml的对象的工厂
class XMLParserGetFactory implements AbstractFactory.{
var xmlParser;
function XMLParserGetFactory(str:String){
//生成解析器的具体实现,在后面会提到
}
function createXmlParser(){
return xmlParser;
}
}

XMLParserPostFactory.as

程序代码 程序代码
//生成解析输出的xml的对象的工厂
class XMLParserPostFactory implements AbstractFactory.{
var xmlParser;
function XMLParserPostFactory(str:String){
//生成解析器的具体实现
}
function createXmlParser(){
return xmlParser;
}
}

这样,我们读入某个xml字符串时,在onLoad里面加入

程序代码 程序代码
//生成对留言板的留言列表解析的工厂
var xmlParser=new XMLParserGetFactory(“xmlParseGuestbookList”)
xmlParser= XMLParserGetFactory. createXmlParser()

备注:抽象工厂模式是软件工程里最常用的设计模式之一,实现过程在于,需要某个类的实例时,通过某个工厂创建,而不是直接创建,坦白地说,它加大了开发工作量,但是对程序的层次性变得分明和降低耦合度有极大帮助。

2.生成器模式(builder)还是那个说法,我要吃东西就去相应的食堂窗口,但我不能吃食堂窗口,窗口里的东西也许不少,我要跟师傅说,要这个,这个,还有这个。
举例:我已经建立了 xml解析器的工厂,现在要返回解析器本身,就让工厂创建,返回给我。
XMLParserGetFactory.as

程序代码 程序代码
//生成解析读入的xml的对象的工厂
class XMLParserGetFactory implements AbstractFactory.{
var xmlParser;
function XMLParserGetFactory(str:String){
//如果要求留言板列表解析器,就生成一个
if(str==” xmlParseGuestbookList”){
xmlParser=new xmlParserGuestbookList();
}
}
function createXmlParser(){
//返回所要求的解析器
return xmlParser;
}
}

我到了食堂窗口,如果师傅跟那儿抽烟,我还是吃不着东西。我说:师傅,打饭!师傅才会完成打饭这一动作。这是工厂方法模式,抽象工厂的实现通常用工厂方法模式来完成。
举例:还是上一条,我本来想用一句话带一个参数就实现具体xml解析器的实现,无奈构造函数没有返回值,所以必须用

程序代码 程序代码
xmlParser= XMLParserGetFactory. createXmlParser(xml,arrayID,arrayTitle);

实现。

3.抽象工厂模式,生成器模式和工厂方法模式需要灵活应用。 我前面一个人买了一条巨大的鸡腿,我说我也要一条,师傅说,就这一条
举例:单件模式的应用是相当广泛的,它确保每个实例在全局范围内只被创建一次,我们flash里的mc大多数是单件。内核里的核心组件也只是单件,比如我的消息映射列表(见后)。
按照单件模式的严格定义,应该让类负责保存它的唯一实例。但是我在Flash里还想不到怎么实现这一点,或者实现它的意义所在,但另外一点我们可以做到,就是在全局范围内只提供该对象的唯一访问点。这可以由层次关系做到,把对该对象的访问具体实现全部封装在下层,只给上层提供唯一的访问点(原因是,上层不知道这个单件的具体信息,比如路径)。
看我内核文件的一部分:

程序代码 程序代码
Core.as
//内核
class Core {
var strucGlobalParam:ConfigVariables;
//站点信息
var xmlConfig:XML;
//站点信息的xml化对象
var ArrayStructureInitial:Array;
//用来提供给loadObject对象的数组
var ArrayForBtn:Array;
//用来初始化导航条组件的数组
var objInitial:loadObject;
//读取影片的对象
var objMessageMap:MessageMap;
//消息映射组件
……
}

这是我的内核类也就是全站最核心类的数据结构。里面的数据只有通过下层的BasicMovie,OriginalFunctionObject等类(见后)直接访问。
备注,核心思想是,确保只有一个。

5.原型模式(protoType)到小炒窗口,看前面的哥们炒的青椒炒肉不错的样子。“师傅,我也要这样的。”
举例:这对flash的用户来说再熟悉不过了,我们经常用duplicateMovieClip()和
attachMovie()这两个函数。按照一个原型复制相应的实例,各自执行自己的动作。在我的blog列表,导航条的生成。。几乎用得到多项数据的地方就要用原型模式。

6.责任链模式
食堂里厨房最远的窗口没有白菜了,要告诉厨房,快送过来。
责任链模式:一个窗口一个窗口地传话,一直传到食堂,食堂一看不妙,赶快做好送过去。
中介者模式:专门派一个人负责传话,任何窗口没菜了,就要这个人赶快去厨房催。
观察者模式:厨房那边派一个盯着,看哪个窗口没菜了就开始大声嚷嚷。
举例:之所以要把这三个设计模式放在一块儿,是因为我在我的站里面结合这三者建立了一个好玩的东西,可以说是我的网站的核心所在。它解决了我的flash里面各个mc的通信问题。
比如,影片A放完了,要通知影片B开始播放,直接的做法是在A的最后一帧,写从A到B的相对路径或B的绝对路径,让B play()。这样做A和B的耦合性是相当高的,也就是说,相互依赖程度太高。运用设计模式的解决方案如下:

程序代码 程序代码
MessageMap.as
//消息映射类
class MessageMap extends Object {
var Message:String;
var MessageWatcher:Function;
var Target;
var MessageList:Array;
var Num_Msg:Number;
function MessageMap() {
Num_Msg = 0;
MessageList = new Array();
Message = "HANG_UP";
MessageWatcher = function (prop, oldVar, newVar, Param) {
for (var i = 0; i<Num_Msg+1; i++) {
if (newVar == MessageList[0]) {
MessageList[1].apply(MessageList[3], MessageList[2]);
if (!MessageList[4]) {
MessageList.splice(i, 1);
Num_Msg--;
i-=1;
}
}
}
};
this.watch("Message", MessageWatcher, "test");
}
function SendMessage(Msg:String, mc:MovieClip) {
Message = Msg;
}
function UpdateMessageMap(Msg:String, objFunction:Function, ArrayParam:Array, objRefer,IsMultiUsed:Boolean) {
MessageList[Num_Msg] = new Array();
MessageList[Num_Msg][0] = new String();
MessageList[Num_Msg][0] = Msg;
MessageList[Num_Msg][1] = new Function();
MessageList[Num_Msg][1] = objFunction;
MessageList[Num_Msg][2] = new Array();
MessageList[Num_Msg][2] = ArrayParam;
MessageList[Num_Msg][3] = objRefer;
MessageList[Num_Msg][4] = IsMultiUsed;
Num_Msg++;
}
function DeleteMessageMap(objRefer) {
for (var i = 0; i<Num_Msg; i++) {
if (MessageList[2] == objRefer) {
MessageList.splice(i, 1);
Num_Msg--;
}
}
}
}
class SubTemplateMovie extends BaseMovie {
var MovieRemoveFunction:Function;
function SubTemplateMovie() {
this.stop();
MovieStartFunction = function () {
Lock();
this.play();
};
MovieEndFunction = function () {
Lock();
this.play();
};
MovieRemoveFunction = function () {
this.stop();
SendMsg("SUB_TEMPLATE_REMOVED", this);
_parent.unloadMovie();
};
MovieMainFunction = function () {
stop();
SendMsg("SUB_TEMPLATE_OPEN", this);
};
UpdateMessage("LOADING_BAR_OVER", MovieStartFunction, null, this, false);
UpdateMessage("BACK_TO_INDEX", MovieEndFunction, null, this, false);
}
}

整个消息映射类相当于一个中介者,内部生成一个观察器,一旦触发消息,以责任链的方式执行。

9.桥接模式(Bridge)菜太淡,不合有些人的胃口,所以要求食堂的师傅,专门开一个窗口,专门在做好的菜里多加些辣椒。
我在自己的站里运用了桥接模式:所有的影片都继承自我定义的BasicMovie 类(BasicMovie继承自MovieClip类),但是在四个下级栏目的影片里,需要定义相同的方法和事件来响应消息,BasicMovie没有这些函数,不符合要求,这时候,在四个影片里都写一遍是愚蠢的,我又写了一个SubTemplateMovie类继承自BaseMovie,里面加进一些通用的方法,然后四个下级模板影片都继承它,这样大大简化了后期开发。

BasicMovie.as

程序代码 程序代码
//基类影片
/所有影片的原始类,一切影片的父类都继承此类而来
class BaseMovie extends MovieClip {
var isLocked:Boolean;
//初始类开始影片函数
var MovieStartFunction:Function;
//初始类影片主功能函数
var MovieMainFunction:Function;
//初始类结束影片函数
var MovieEndFunction:Function;
var GlobalParam
//初始类构造函数
function BaseMovie() {
}
//
//发送消息
function SendMsg(Msg:String, Mc:MovieClip) {
_root.objCore.objMessageMap.SendMessage(Msg, Mc);
}
//添加消息映射
function UpdateMessage(Msg:String, MsgMapFunction:Function, ArrayParam, obj, IsMultiUsed) {
_root.objCore.objMessageMap.UpdateMessageMap(Msg, MsgMapFunction, ArrayParam, obj, IsMultiUsed);
}
//删除消息映射
function DeleteMessage(obj) {
_root.objCore.objMessageMap.DeleteMessageMap(obj);
}
function GetGlobalParam() {
GlobalParam=_root.objCore.strucGlobalParam;
}
}

SubTemplateMovie.as

程序代码 程序代码
//下级模板影片类
class SubTemplateMovie extends BaseMovie {
var MovieRemoveFunction:Function;
function SubTemplateMovie() {
this.stop();
MovieStartFunction = function () {
Lock();
this.play();
};
MovieEndFunction = function () {
Lock();
this.play();
};
MovieRemoveFunction = function () {
this.stop();
SendMsg("SUB_TEMPLATE_REMOVED", this);
_parent.unloadMovie();
};
MovieMainFunction = function () {
stop();
SendMsg("SUB_TEMPLATE_OPEN", this);
};
UpdateMessage("LOADING_BAR_OVER", MovieStartFunction, null, this, false);
UpdateMessage("BACK_TO_INDEX", MovieEndFunction, null, this, false);
}
}

我要一碗汤,但是只有纸饭盒,还没勺,所以食堂的师傅给了我一次性的汤碗和勺,这叫适配器。
适配器解决的是某一个类的对外接口不合用的问题,可能是参数或者返回值类型不符等问题造成的,这时候我们需要在工作对象和这个类之间加一层间接的层次。
这个模式我在底层的数据交换层用过。我说过,flash和asp.net之间交换数据全以xml为载体。返回xml在底层只有三层,数据库操作,数据操作,数据显示,由数据操作层返回给数据显示层一个xml字符串就可以了。然后我就遇到一个小问题,在另一方面,我需要提交数据到数据库,也是提交一个xml字符串,但是我需要数据库里对应的表的数据集的xml表现形式的xsd验证!(一口气说完,差点没憋死)。就是说我至少需要取出这个表里的一条记录,问题在于,我封装的类从来只返回xml,没有返回xsd的。解决办法就是适配器,新建一个项目,加了一层专用于获得xml验证格式,这样就完成了不同接口之间的转换。
备注:适配器和桥接很象,都是在已有类不符合要求的时候,加入一层间接的元素以达到目的。不同的是适配器是解决不兼容接口之间的转换,桥接一般不涉及这个问题,只是完成一个一对多的转换。

11.外观模式(Facade)每天都要去食堂,每个人去不同的窗口吃不同的菜,很累,今天全寝室推举猴子去打饭:
你吃这个,三两饭,我吃那个,五两饭,所有人都只跟猴子一个人交涉,食堂所有的师傅也只见猴子一个人。
举例:这个模式在程序的上下层的通信之间可以应用得十分广泛。Asp的每个模块要去不同的数据,访问数据库的不同表,就要跟不同的下层数据访问组件打交道。就是说,每个mc模块必须知道,我要去哪个具体的数据访问组件取数据。每个模块要维持自己的一个,至少是字符串。
如果运用外观模式。我们可以让所有的需要数据交互的mc访问同一个aspx页面,比如getStrXml.aspx。只要传送一个标示符,就可以通知这个唯一的取数据的页面,访问哪个下层组件获取数据。下层组件不知道哪个mc要求数据,mc也不知道数据的具体来源,这样,上下层之间互相都显得不透明。这就降低了耦合度。

12.代理模式(Proxy)可能我们不是每个人每天都想吃饭,所以我们要求猴子每天中午必须在寝室,如果我们要吃,他就去,如果我们都不吃,他爱干嘛干嘛。
举例:这恐怕是每个人在flash里都会无意中用到的模式。比如,一个网站,它的下级栏目不用在整个网站初始化的时候一开始就读进来,但是我们要确保,在浏览者想看并且点击导航条上的某个按钮时,能够正确地读进相应的影片文件,前提是,我们必须在内部保留一个索引,可以称作代理。通常是一个空mc

13.策略模式(strategy)我每天先在食堂找座位,再打饭,再打菜,再买杯酸奶。这已经模式化。要是食堂有服务员,我也会要他这么做。
举例,策略模式是把一系列的算法封装起来,形成一个类。这个模式几乎是随时随地都可以整合到别的模式里去的,我的那一堆xml解析器实际上就是策略模式的应用,这个模式还应用到我网站的下层,因为flash提交给aspx页面的数据也是xml字符串,下层模块也需要相应的解析算法。同样的,我把对xml的解析封装进了一个类。

程序代码 程序代码
//Cs文件里的解析函数
Class DataModel.BlogMsgs{

Public DataSet parseXML(string strXml){
DataSet ds=new DataSet();
//。。把xml装载到DataSet 里
Return ds
}

}

东西不够吃?给你摆20面镜子~
师傅,东西还是只有一份。。。
关于这个模式十分抱歉,我暂时还没想到在flash里面的实现。需要举例说明的是,浏览器的机制是,在有大量文字的英文文档里,相同的字母共享一个 Flyweight,在内存里其实只占一份空间,然后在文档不同的地方显示,这样对于大量细粒度的效果来说,可以节省很多资源。有哪位同志想到了请一定告诉我,不胜感激。

15.访问者模式(Visitor)只要愿意,我随时都可以跑到哪个窗口打要吃的东西,前提是,我必须跑这一趟。
举例:我说过,我的所有mc都继承自BasicMovie这个类,但不是我的所有mc都要从后来获取数据库数据。获取数据库数据所要访问的信息,比如ip,路径,文件保存在配置文件里,初始化的时候读入内核,并且只有内核那里有一份。在BasicMovie里加入对这些全局变量的引用是不合适的,因为只有少数mc要用到,而且由于某些原因我无法再使用桥接模式(我已经有了SubTemplateMovie,不能多继承),所以我用了访问者模式。

BasicMovie.as

程序代码 程序代码
//获取全局变量
function GetGlobalParam() {
GlobalParam=_root.objCore.strucGlobalParam;
}

如果上级mc不执行这个函数,是不能获取全局变量的,如果要用,就执行。
也就是说,需要的时候,我去访问它。
备注:声明一个visit操作,使得访问者可以正确访问需要的类。

16.状态模式(state)我今天想吃面,师傅问我:要什么料?西红柿鸡蛋,排骨还是牛肉?
举例:状态模式是指将对象当前的某些状态局部化,当对象改变状态时,看起来好像改变了类。例子还是我的滚动条。如果要滚动的是文本框,就要引用一个TextField的Scroll, maxscroll属性,如果是mc,引用的是_y,_height属性,我用一个参数将二者区分,由一个if语句控制,让滚动条可以自由区别状态。
另外一个解决方案是定义ScrollBar的不同子类,这两者本质区别不大,在状态比较多时,可能要维持一个庞大的if算法,这样就用生成子类的方法比较好。

ScrollBar.as

程序代码 程序代码
//滚动条组件
function BindTo(mc,type:String,intMcHeight:Number,yinitial:Number){
ScrollType=type;
if(type=="TXT"){
scrollTxt=mc;
}
if(type=="MC"){
initialY=yinitial;
McHeight=intMcHeight;
scrollMc=mc;
}
}
function Scroll() {
if(ScrollType=="TXT")
this.onEnterFrame = function() {
scrollTxt.scroll = scrollTxt.maxscroll*mcBlock._y/(BgLength-BlockLength*3/2)
};
if(ScrollType=="MC"){
this.onEnterFrame=function(){
if(scrollMc._height>McHeight){
scrollMc._y=initialY-(scrollMc._height-McHeight)*mcBlock._y/(BgLength-BlockLength*3/2)}
}
}
}
[code][/code]
备注:这也是常见模式,在flash的逻辑控制里尤其随处可见

17.装饰模式(Decorator)在食堂吃饭,没筷子怎么行?我是从来不带饭盆的。师傅很人性化,每个窗口都放着一大把筷子,随用随拿。
这个模式如果用好,有的地方可以很省力。比如,我网站里的滚动条:

ScrollBar.as
[code][/code]
//滚动条组件
class ScrollBar extends BaseMovie {
var BgLength:Number;
var BlockLength:Number;
var mcBlock:MovieClip
var Width:Number;
var ScrollType;
var scrollTxt:TextField;
var scrollMc:MovieClip;
var McHeight:Number
var initialY:Number
function ScrollBar() {
}
function InitialScrollBar(BgLength, BlockLength) {
this.BlockLength = BlockLength;
this.BgLength = BgLength;
}
function BindTo(mc,type:String,intMcHeight:Number,yinitial:Number){
ScrollType=type;
if(type=="TXT"){
scrollTxt=mc;
}
if(type=="MC"){
initialY=yinitial;
McHeight=intMcHeight;
scrollMc=mc;
}
}
function Scroll() {
if(ScrollType=="TXT")
this.onEnterFrame = function() {
scrollTxt.scroll = scrollTxt.maxscroll*mcBlock._y/(BgLength-BlockLength*3/2)
};
if(ScrollType=="MC"){
this.onEnterFrame=function(){
if(scrollMc._height>McHeight){
scrollMc._y=initialY-(scrollMc._height-McHeight)*mcBlock._y/(BgLength-BlockLength*3/2)}
}
}
}
function ScrollMc() {
}
function StopScroll() {
this.onEnterFrame=null;
}
function Reset(){
mcBlock._y=0;
}
}

核心函数是BindTo(),把这个滚动条的实例绑定到某个动态文本框或者某个mc上,就可以实现滚动。
备注:装饰模式的思想是,在不影响其他对象的情况下,以动态,透明的方式给单个对象添加职责。

18.组合模式(composite)我中午吃六两饭,猪肉炖粉条,辣子鸡,鱼丸,咸鸭蛋,外加两杯酸奶(猪!)
这些东西都是对象,他们共同组成了我的午饭。
举例:应该说在Flash里组合模式是无处不在的,因为只要还有mc的嵌套,就有组合模式存在。几个mc装在一个mc里,这个装载用的mc称作容器。
但是就这么说,恐怕没人会重视这个模式,因为不管理不理解他我们都在用。他的确有很多种实现方式,我的方式之一是这样的。

blog.as

程序代码 程序代码
//我的Blog
class Blog extends BaseMovie {
//blog第一界面,包括日记列表,日历,最近留言
var mcBlogList: mcBlogList;
//blog第二界面,包括日记全文,回复,对该日记的留言。
var mcBlogDairy:MovieClip;
var currentState:String;
var currentDairyID:Number;
function blog(){
}
}

mcBlogList.as

程序代码 程序代码
//blog第一界面
class mcBlogList extends BaseMovie {
//最近留言
var recentMsgs:blogMsgsSC;
//日记列表
var blogList:BlogList;
//日历
var calendar:CalenderForBlog;

mcblogDairy.as
//blog第二界面
class mcBlogDairy extends BaseMovie {
//日记全文显示
var BlogDairy:BlogDairy;
//留言板
var GuestBook:BlogInputMsg;
//留言列表显示
var BlogMsgs:BlogMsgs;
}

然后里面每个组件都还包含另外的组件,比如滚动条,动态文本框什么的,我想说的是,我写在as里的mc嵌套模式并不一定就符合fla文件里的具体物理嵌套模式,有很多装饰性的mc不需要包含进来,也有很多具体原因,需要改写路径。比如在BlogDairy下,滚动条的实际路径是 BlogDairy.mc1.ScrollBar,不做成BlogDairy.ScrollBar可能是因为我需要mc1的动画,也可能是别的具体原因。但是我们可以在mc1的时间轴上加一句
_parent. ScrollBar =This.ScrollBar
把路径指向改写,达到我们要使用的模式,ScrollBar就是BlogDairy的直接成员。另外我没有语句初始化mc,我习惯在库里设置mc的linkage。
备注:虽然组合模式是每个人必不可少要用的,但正因为如此,我们可以想出他更多更好更灵活的实现方式

19.备忘录模式(memento)~师傅,晚上的鸡腿没中午的新鲜啊。~胡说!这就是中午的。
举例:开个玩笑,上面两句话不是备忘录模式的本意,实际上我一时想不出在食堂里备忘录是什么样子。备忘录的意思是,在不破坏对象封装型的前提下,保存对象的当前状态,当需要时,恢复这个状态或提取这个状态。
备忘录被广泛地运用到逻辑关系里,它似乎是目前我提到的唯一跟时间有关的模式,在控制论里可以涉及到因果系统。备忘录的应用非常广泛,最常见的是浏览器或网站的后退功能,返回到上一个界面,但是在我的站里没有用到,因为我的站是树状拓扑结构,每个栏目都只能唯一地从主界面进入或退回到主界面,那些网状拓扑结构的站点,每个栏目都可以通向其他的任何栏目,很可能就需要这么一个功能,其实现方法往往是维持一个堆栈型的数组。
另外一个我想实现的东西是网站内部自动记录,由于某些原因我暂时没有把它加到网站里。很简单,就是监视浏览者的每一步操作,在哪个栏目,停了多久,做了些什么之类,如果离开了,下次访问网站时让他选择是否直接进入上次浏览的界面(比如他上次在看我写的小说而没看完)。这个可以通过sharedObject实现,差不多也就是 flash内部的cookie。
备注:备忘录的实现要求还是很高的,在提取对象状态的时候不要破坏对象的封装性。理想的备忘录作为一个对象需要两个借口,一个给管理者,它不知道备忘录里封装了什么状态,只负责地把状态移交给需要的对象,一个留给原发者,原发者只能了解并操作备忘录里封装的状态,不能做别的操作。

小结:我会随时补充这篇文章。但是我要举的一些例子暂时已经说完了。其实设计模式远远不止这几种,经典的有23种,我已知的有41种,而这里只有19种,还有命令模式,解释器模式,迭代器模式,模板方法模式我没写到。我的站还用了一些我没有提到的,自己都还没细想过的设计模式。但是一一列举其实是意义不大的,有兴趣的同志们要做的就是接触一下这些设计模式的细节和思想,然后统统忘掉,就和张无忌学太极拳,先学后忘是一样的。招式要看,要学,但是要领会其中的深意,灵活地,创造地,相互结合,融会贯通地使用招式,不能拘泥于招式,更不能为了用招而用招。设计模式作为UML的重要组成部分,能够极大程度地促进我们的开发效率,使我们养成好的书写习惯,做出更人性,更和谐,更清晰,复用性和可控性更高的作品。

不要说你不是程序员,就可以跟着感觉走,随心所欲,当然我们无权干涉。我也说了,as本来就是想怎么写就怎么写,但接不接触,学不学习先进的思维模式,这也是你的自由。更不要说你是搞艺术的,头脑偏重感性,不适合接触这些,我之所以要顺带拿食堂举例,是想说明,我要说的其实是就Christian Alexander先生的,建筑的永恒之道,无论是建筑本身,还是任何软件工程,或者Flash的as,甚至Flash的矢量绘图,美工,平面设计,一直到世界这一整体,无不是遵循一个结构化的,由模式构成的永恒之道,有着惊人的高度相通和相似性。参悟了这一点,不仅对做Flash,对整个生活都会有新的看法。区别仅仅在于,从哪里开始入手思考,在这里,可以从Flash开始。

 

关于某些事务的小感

最近公司有人离职了,而且因为是做渲染的,他走了后,我的任务变重了,目前接手世界编辑器关于地形啊等等事务,还是蛮忙的,这也是没办法的事啦。我还有一个特效编辑器没做完,等做完差不多,我也该走了。说实话,创业公司真是挺累的,偶也算是这公司的元老级人物了,记得当时来的时候就只有个项目经理和主程,现在连主程都走了。哎,感慨万千啊。创业公司总有自己的难处的,算了,这些就不说了。

目前就剩下个烘焙了吧,偶参考的痞子龙的这篇文章,说是简单烘焙的方法,我也快实现了。大家也可以去看看。http://blog.csdn.net/pizi0475/archive/2010/11/18/6020060.aspx基本就是使用Ogre的动态阴影,然后渲到纹理当lightmap用,等我都实现了,再写篇文章记录一下心得,呵呵。

地形前段时间搞得也差不多了,主要搞了下ogre1.7地形的材质问题,地形受光跟物件的寿光程度不一样,导致颜色差别很大。再就是地形不支持无限点光的问题,再过几天吧,偶把这些问题的解决方法和代码贴出来给大家看一下。

现在真的很忙,身体也不太好了。把这个忙完,就剩特效编辑器了,做完特效差不多就得9月份了吧,到时看项目进展怎么样,到目前为止,对这个项目的风格玩法等等还算是比较认可的。说实话,现在在这公司里,薪水的确是蛮低的,这点很不满意啊,期待着涨涨薪水,O(∩_∩)O~到9月份项目要是看不到希望的话,哎,偶就该跳槽了,到那时候开个上w的薪水应该不过分吧,嘎嘎。所以,现在给自己打打气,我一定会努力的。

按照计划,之前已经看完了《head-first设计模式》和《代码大全》,接下来,要把四人组的《设计模式》给看完,再在项目里面应用一下,这样关于设计方面的知识就算是补得差不多了(之前由于关于设计方面知识的欠缺被老大狠狠的鄙视了一番,实在是强汗~~醉过醉过啊)。大家可以小小期待一下下篇关于Ogre地形问题和烘焙相关的心得,嘎嘎。好了,就先写到这里吧,胡扯了半天,看《设计模式》去了。

有关游戏开发的几点体会

1。还没有真正的次世代网游,更别指望它能赚钱好几次有朋友问我,次世代网游到底是什么东西我说,次世代网游就是模型至少上万面,贴图每张都起码2048,法线图,高光图,多层蒙版一个都不能少;动态光满天飘,还都是开着阴影的;体积云,体积雾,体积光,全都是立体的,酷;水面的反射,折射,波纹,浪花,样样精彩;超大型,超豪华场景,无限视野。就一个字:真实!哦不对,是两个字,超真实。这样的游戏用现在能配到的最好的机器,跑起来FPS也一定不允许超过15,否则那就不能叫次世代了。说白了,次世代就是你用当下的机器完全跑不起来的游戏,要么怎么能叫“次”世代呢。这样的游戏,不是让大多数玩家能玩得起来的,不是拿来卖钱的,就跟北京的商品房一样,是给大家去追捧的。最多,会等Intel,Nvidia来给开发商返点利。嗯,我是说,大概可能估计会有的吧。次世代的游戏其实已有不少了,比如战争机器,比如战神,比如彩虹六号……但次世代的网游还没有。魔兽是次世代吗?不是,完全不是。永恒之塔是次世代吗?也不是,它也还差的远。天下二,剑网三就差的更远了。2。真正赚钱的游戏技术都很普通也许你会说,真正的次世代游戏都还没有出来,你怎么就敢预言他们不能赚钱呢?是的,我不能,如果我有未卜先知的本领,那我早就不再需要靠做游戏来养活自己了。可是,我们却能够看到现在赚钱的游戏是什么样的,这些是明明白白摆在那里的。魔兽世界:把这个游戏誉为国内3D游戏的教程书完全不为过,不过你承不承认,策划也好,程序也好,美术也好,都从这里面学到了很多东西,模仿了很多东西,在目前国内的游戏里面,到处都能找到魔兽的影子。可是魔兽又用到了多神奇的技术?主角模型算上所有部件,3000多面,各部件的贴图组合在一起,512大小,没有法线没有高光,绝大多数还都只是一层贴图,偶尔有一些多层混合的。地形最简单的分块4层混合,最简单的lightmap。水面,把镜头拉远一点都能看出来贴图形状。天空盒,一个普通的m2模型。可是,魔兽所表现出来的整体场景效果,有哪一个游戏敢说超越呢?天龙八部,基于开源引擎Ogre制作的典范,也因为天龙八部鼓舞了国内好多使用Ogre开发的小团队。不得不承认,Ogre所使用的技术是最朴实的,朴实到我这样一个3D新手都能拿来修修改改,做点简单的demo。同样不得不承认,天龙的画面效果确实很一般,2.5D的场景,固定的视角,轻盈的有些像纸片人的模型,可是,这并不妨碍他每月近两亿的收入。梦幻,大话,DNF,征途,传奇……除了这些表面上能看到的技术以外,背后的技术是同样的道理。早期的单服务器,分线方式,依然沿用在现在很多主流的游戏服务器端,并且依然是非常赚钱的项目。而类似于BigWorld的高深架构,事实上也并没有成功的项目。如果把天下二的商业结果跟其他项目一比较的话。3。2D游戏比3D游戏赚钱我一样很认同,未来的趋势是3D,但是,那时候赚钱的3D项目不应该是现在这个样子的。以国内游戏玩家的年龄及文化层次来看,要让他们接受“右键旋转朝向,左键旋转视角”太过于困难,而即使是一个很熟悉3D操作模式的老玩家,进入到一个新的场景中,要分辨出“上北下南,左西右东”也是很烦人的一件事。如何尽可能的使用上3D的表现力,但又避免掉目前3D游戏的复杂操作模式,这要看未来谁先能走好这一步。但是,在3D真正应用起来之前,目前还是2D的天下。国内最赚钱的梦幻,还有大话系列,同样最高在线超过200万的DNF、征途,还有那不应被忘记了传奇。不用历数这些名字,文化部2009网游行业发展报告上统计的结果是,2D游戏收入占整个游戏行业收入达70%多。这也无怪乎腾迅到现在还在开发2D新项目,以3D起家的完美也要开2D项目,网易的大话到3代了还是2D,把unreal3应用得纯熟的韩国人也同样还在制作2D游戏。4。游戏开发并没有什么高深的技术首先需要明确的一点,游戏项目是工程项目,不是科研项目。工程项目的目的是在有限的人力跟财力之下实现出既定的需求,而这个需求从前面的分析可以知道,要求并不高,所以,需求的实现过程也就并没有多么高深。至少在我经历过的项目里,没有什么惊天地泣鬼神似的英雄人物,没有创造出多么伟大的算法,我们所做的,只是使用现在的技术,现有的方法,拼合成一个软件产品,一个融合了程序、美术、策划劳动力的软件产品。游戏开发的过程里,没有,也不需要多厉害的技术高手,需要的仅仅只是有耐心,有责任心的普通技术人员。5。游戏的卖点在内容而不是画面,但是画面不够好却没有机会去展现内容说这一点不是想强调到底是程序重要还是美术重要,或者是策划更重要。这三者是缺一不可,而且哪一方弱都不行的。我想说的是,游戏真正留住玩家靠的还是内容。一样是拿现在赚钱的游戏来说,梦幻没有华丽的3D场景跟画面,天龙有3D,但没人会说那里面有华丽的场景,DNF的2D画面还是非常粗糙的,唯独好一点的魔兽,但他的市场表现在国内游戏里面来说,并不算太强。但是好的画面在最开始的几分钟里却是相当重要的,这就好比是长的帅的人能够更吸引女孩子一样。也许你能用你的魅力,你的钱袋子来打动女人,但如果你穿着一件破衣服,脸上只有着残缺美,那你后面那些魅力,那些优点永远没有机会展示出来。游戏也是一样。至少,你的新手村一定要做到富丽堂皇。6。游戏并不需要追求太多的游戏性,提供一个交流的平台就行这是我最近的感悟。很多人玩游戏其实就是为了打发时间,我也问过很多沉迷于魔兽,沉迷于偷菜,沉迷于这些那些游戏的人,包括偶尔玩一下的,包括职业玩家,包括像我这样,为了游戏而玩一下的人。游戏靠什么来留住人,在这一点上达成共识并不难,那就是里面的朋友。所以,给玩家营造一个更好的交流氛围,交流环境,做到这一点了,游戏玩法可以要多俗有多俗。又在游戏里面,还有社区里面接触了一些新生代的玩家们,似乎家族是一个很流行的东西。这其实可以看作是以前游戏里公会的升级版。在某个儿童游戏里,一个玩家带着我去参观他们的家族,带我一个个拜见他们的官员。可我并没有看到这些官员的头衔,于是我问,你们这些官员是怎么来的?答曰:自己封的。就好像公园里的小道一样,有时候,游人们会按照自己的喜好在草地上走出一些新的路来,这些路才是最合理的。为什么不顺着这些玩家的路,把这些功能做的更强大一点呢。其实,把社群的功能做得更强大,更高级一点,那就像文明。或者做的更容易,更低龄一点,那就像过家家。不管是怎样,应该在系统里就增强了交流的便利性,甚至可以在玩家一加入到游戏中,就开始引导着他加入社群。只有在社群里,他才能找到家的感觉,他才会因为朋友们而留下来。当然,怎么找对这条路,走好这条路,可不像写下这几行字这么简单。

快速LightMap烘焙

什么是烘焙? 简单地说, 就是把物体光照的明暗信息保存到纹理上, 实时绘制时不再进行光照计算, 而是采用预先生成的光照纹理(lightmap)来表示明暗效果. 那么, 这样有什么意义呢?

好处:

  1. 由于省去了光照计算, 可以提高绘制速度
  2. 对于一些过度复杂的光照(如光线追踪, 辐射度, AO等算法), 实时计算不太现实. 如果预先计算好保存到纹理上, 这样无疑可以大大提高模型的光影效果
  3. 保存下来的lightmap还可以进行二次处理, 如做一下模糊, 让阴影边缘更加柔和

当然, 缺点也是有的:

  1. 模型额外多了一层纹理, 这样相当于增加了资源的管理成本(异步装载, 版本控制, 文件体积等). 当然, 也可以选择把明暗信息写回原纹理, 但这样限制比较多, 如纹理坐标范围, 物体实例个数...
  2. 模型需要隔外一层可以展开到一张纹理平面的UV(范围只能是[0,1], 不能重合). 如果原模型本身就是这样, 可以结省掉. 但对于大多数模型来说, 可能会采用WRAP/MIRROR寻址, 这只能再做一层, 再说不能强制每个模型只用一张纹理吧? 所以, lightmap的UV需要美术多做一层, 程序展开算法这里不提及....
  3. 静态的光影效果与对动态的光影没法很好的结合. 如果光照方向改变了的话, 静态光影效果是无法进行变换的. 而且对于静态的阴影, 没法直接影响到动态的模型. 这一点, 反而影响了真实度

肯定不只这几点,但我暂时只想到这几点

那么怎么生成lightmap呢?

最直接的办法: 光线追踪....(原理想想很简单, 按照物体定律来就可以了)

但是光线追踪这东西......就算用来离线生成我都嫌慢-_-

下面说的这个是利用GPU进行计算的, 跟实时光照没什么两样:

原理:

想想实时渲染的顶点变换流程: pos * WVP之后, 顶点坐标就变换到屏幕空间了[-1, 1]

如果VertexShader里直接把纹理坐标做为变换结果输出(注意从[0,1]变换到[-1,1]), 那么相当于直接变换到了纹理坐标系, 这时在PixelShader里还是像原来那样计算光照, 输出的结果就可以拿来做lightmap了

示例:

这是一个典型的Phong光照模型下的球(这里不考虑阴影效果, 对它不需要进行特殊处理):

这是VS:

 

  1. VS_OUTPUT vs_main( VS_INPUT Input )
  2. {
  3. VS_OUTPUT Output        = (VS_OUTPUT)0;
  4. Output.Position         = mul( Input.Position, matViewProjection );
  5. Output.Texcoord         = Input.Texcoord;
  6. float3 fvObjectPosition = mul( Input.Position, matView );
  7. Output.ViewDirection    = fvEyePosition - fvObjectPosition;
  8. Output.LightDirection   = fvLightPosition - fvObjectPosition;
  9. Output.Normal           = mul( Input.Normal, matView );
  10. return( Output );
  11. }

 

把原来的WVP变换改成变换到纹理坐标系:

 

  1. //Output.Position         = mul( Input.Position, matViewProjection );
  2. // transform to projection space
  3. Output.Position.xy      = Input.Texcoord * float2(2, -2) + float2(-1, 1);
  4. Output.Position.w       = 1;

 

输出的结果就成这样了:

保存下来可以直接使用. 这里我用的模型比较特殊, 本身的UV就满足前面提到的条件, 所以直接跟原纹理叠加就可以. 当然, 如果只保存明暗信息的话, 就不影响原纹理的复用, 因为通常lightmap不需要很高的精度:

有了lightmap, 再次画的时候就简单了, 只需要贴纹理, 光照大可以关掉:

如果还想要一更好的效果, 可以加入一些实时的全局光照算法, 如Dynamic Ambient Occlusion之类...阴影同理...

 

世界十大游戏培训学校

引言: 在开始阅读这篇文章之前,我们有必要先大概了解一下全球游戏行业的发展历史和研发分布情况。
北美游戏行业可以追溯到七十年代,迄今为止已经有四十年时间,日本游戏行业从八十年代到现在有三十年的时间,韩国游戏产业从九十年代末开始发展,有二十年的发展时间,中国的游戏行业虽然可以追溯到九十年代,但正式崛起却是在二十一世纪之初。

在北美、欧洲或日本,电视游戏占据了绝大部分市场,网络游戏只占不到10%。韩国和中国的情况则正好相反,几乎没有电视游戏市场,网络游戏几乎垄断游戏行业。
北美的游戏开发业集中于西海岸,自加拿大的温哥华向南,穿过西雅图到达加利福尼亚,最后延伸至德克萨斯。跨过大洋,在英国和法国也有基础扎实的游戏产业。在亚洲市场,很明显日本具有最强的开发实力。近十年来韩国的网游产业也在奋起直追,但受制于韩国本身的市场容量,其网游研发的实力逐渐被中国迎头赶上。同时由于人力成本相对较低,中国正在成为全球最重要的电视游戏外包研发基地之一。

而通过下文,我们不难发现,游戏教育的发展和游戏行业的发展密切相关。这足以说明,正是因为游戏产业的蓬勃发展才催生了对专业人才的大量需求,游戏教育产业才得以发展。               温哥华电影学院(加拿大) 一句话点评:老瓶(电影)装新酒(游戏)
除了强大的电影、电视、动画产业,温哥华还是为北美重要的游戏开发基地,存在EA等多个开发商。温哥华电影学院是加拿大最负盛名的电影学院,最早成立于八十年代,主校区处于温哥华的市中心,在大温哥华地区及蒙特利尔和多伦多市另有三个校区及实习场所。

温哥华电影学院的电影制作和3D制作专业是最悠久、最优秀的专业。2004年开始设立专门的游戏教育专业,主要开设课程有游戏设计,3D游戏美术,角色动画,声效课程等,课程周期一般为12个月。官网:http://www.vfs.com DigiPen Institute of Technology(美) 一句话点评:全球首家“电子游戏大学”
DigiPen创建于1988年,从1994年开始设立“电子游戏”项目班,是全球首家“电子游戏大学”。DigiPen总部座落于美国华盛顿州雷蒙德市,和大名鼎鼎的任天堂北美公司(Nintendo of America Inc.任天堂美洲和欧洲的运营中心)仅一条走廊之隔。
DigiPen在业界享有盛誉,专门培训电脑游戏设计相关人才,并提供学士和硕士学位课程,学士课程主要包括动画、游戏设计(分艺术与科学方向)、实时交互、计算机工程,硕士课程则以计算机科学为主。DigiPen要求学生每个学期都要制作独立游戏项目,其学生作品曾23次在独立游戏节奖项。每年申请该校入学的学生都在2.5万人左右,不过最后能被录取的只有200人。

DigiPen于2007年在新加坡设立了分校。官网:https://www.digipen.edu Guildhall at SMU (美)一句话点评:游戏专业也有本硕连读
SMU(Southern Methodist University南卫理公会大学)成立于1911年,是美国的一所教会私立大学,位于美国德克萨斯州,该校实力强劲,在2004年被NSCA排在美国20位,布什夫人劳拉即从该校毕业生。

Guildhall at SMU拥有美国首屈一指的游戏教育课程,提供正式学位。主要课程与实际的游戏开发挂钩,包括游戏美术、游戏程序、关卡设计(策划),其中硕士课程为17月,专业认证培训周期为22个月,此外还革命性的推出了教育领域的首个5年制本硕连读(计算机科学或艺术本科学位+数字娱乐领域的硕士学位)。官网:http://guildhall.smu.edu
Full Sail University一句话点评:娱乐行业职业教育的佼佼者
Full Sail公司位于美国佛罗里达州,是美国最著名的从事影视艺术和技术教育的集团公司之一,旗下包括洛杉矶电影学院和洛杉矶录音学院等高等院校。自1979年成立以来,Full Sail拥有超过31,000校友,毕业作品包括奥斯卡获奖作品,艾美奖和格莱美获奖项目,最畅销的视频游戏,票房排名第一的美国巡回演唱会等。
目前Full Sail同时有8000多人在校,提供28个学位课程,包括电影、媒体、音乐、游戏、设计等各个领域,其中游戏相关专业有游戏开发、电脑动画、游戏美术三个理学士课程,课程周期为21个月,以及为时1年的游戏设计硕士学位课程。更具特色的是,除了传统的校园教育,Full Sail的在线教育也提供正式学位。官网:http://www.fullsail.edu/
The Art Institutes(美) 一句话点评:北美最大的连锁教育机构
The Art Institutes在整个北美地区拥有超过40个教育分部,主要提供艺术创作和应用领域的硕士,学士和副学士学位(相当于中国的专科)课程以及其他无学位的教育与培训,涉及美食、设计、时装和媒体艺术等各个领域,以培养出符合行业需要的实际动手能力的人才为主。

在游戏教育方面,The Art Institutes于2003年开始获得电子游戏学位资格,提供以文学士为主的本科和专科学历教育,主要课程以美术方面为主,包括三维建模、动画、特效等,并兼顾游戏设计与编程。同时提供面对面的校园学习以及在线培训课程。官网:http://www.artinstitutes.edu
Gnomon School of Visual Effects (美)一句话点评:好莱坞的私家CG培训学校
GNOMON坐落于美国大名鼎鼎的好莱坞,与Zbrush开发公司Pixlogicmon在同一幢小楼里,这让它有得天独厚的条件,邀请资深业内人士以及好莱坞的艺术家们来为学生授课。报名者需要经过筛选才能获得入学资格,很多业内人士也会来进修以求不断提高。

GNOMON成立于1997年,建立的初衷是满足好莱坞电影公司的视觉需要,后来逐渐扩展到电子游戏领域。其课程涵盖了电影、电视和游戏几个不同行业所需要的技术技能,课程设置非常细致,游戏方面的有三维建模,角色动画,特效等。课程时间不等,既有长达三年的认证课程,也有短短9周的软件提高课,以全日制为主,同时提供在线课程。官网:http://www.gnomonschool.com/ Qantm College(澳大利亚及欧盟国家等)一句话点评:跨国连锁游戏培训
Qantm于1996年在澳大利亚成立,2004年被SCE教育集团收购,随后即在欧洲各国大力发展。目前Qantm在世界上众多城市设有分支,如澳大利亚的布里斯本、悉尼、墨尔本,英国的伦敦,荷兰的阿姆斯特丹,瑞士的苏黎世,德国的慕尼黑,奥地利的维亚纳,新加坡等。

Qantm每个分部的具体课程稍有不同,但总体上以游戏课程为主,如游戏设计与开发,3D动画,游戏音效等,另外还开设有网页设计课程。课程为全日制,时间从6个月到两年不等,某些学院学满两年可获得相关学位证书。官网:http://www.qantm.com AMG学院(日本)一句话点评:日本培训动漫游戏不分家
AMG于1993年成立,是日本著名的动漫游戏培训机构,在东京拥有4个培训基地,并在大阪设有分部。多年来AMG培养了大批动漫游戏人才,在业界享有很高的声誉,与日本的漫画、动画、游戏等行业联系紧密。此外,AMG还涉足电影投资与发行、动画制作、游戏开发等领域。

AMG的教育培训内容包括:游戏、动画、角色设计、漫画、剧本创作、配音等,课程一般为全日制两年。官网:http://www.amgakuin.co.jp KGCA(韩)一句话点评:韩国知名的专业游戏培训
1997年,韩国爆发经济危机,迫于生活压力人们更加渴望从游戏世界中获得满足,韩国的网吧及本土网络游戏如雨后春笋般发展起来,韩国政府也开始大力扶持游戏制作及相关产业。发展至今,韩国号称是世界上网络游戏产业最发达的国家之一,游戏产品大量出口到中国。

在这种大形势下,KGCA游戏学院于2001年在首尔成立,发展多年至今已经成为韩国业界知名的游戏学校,与卡普空公司、韩国著名大学明知大学建立了合作关系,其毕业生曾多次在韩国各类游戏大奖中获奖。

KGCA 游戏学院采用全日制教学,学期1年,课程包括程序、策划、美术。 官网:http://www.kgcaschool.com      GA游戏教育基地(中国)一句话点评:中国游戏培训的黄埔军校
上海是中国游戏研发的大本营,UBISOFT、KONAMI、EA、SEGA、2K、EPIC、ACTIVISION等全球著名游戏开发公司纷纷落户上海。网游兴起后上海的研发人才优势也成为网游企业的主要输血来源,盛大、九城、久游等总部均设在上海。

GA是中国最早成立的专业游戏教育机构,创办于2004年,位于上海商业中心徐家汇,和EPIC的中国公司在同一栋大楼里。不难看出,GA享尽上海研发人员众多的师资优势,大量邀请行业内资深在职开发人员授课,并且成为EPIC在中国唯一的教育合作伙伴,独家教授虚幻引擎3。

GA以职业教育为主,主要招收有一定基础的高校毕业生,以培养符合行业需要的实际动手能力为主,学生被要求在结业前参与基于商业游戏引擎的游戏项目制作,拿出可玩的游戏关卡作品来。GA具体课程包括美术和策划两大方面,具体有3D场景、3D角色、动画动作、原画、游戏策划,以及与EPIC合作开设的定向课程等,课程周期从2个月到6个月不等。官网:http://www.gamea.com.cn