- 声望
- 点
- 金币
- 枚
- 银币
- 枚
- 铜币
- 枚
- 注册时间
- 2009-7-25
- 最后登录
- 1970-1-1
|
本帖最后由 萨尤克教主 于 2010-12-17 16:35 编辑
由于本人所知有限,教程中如发现有任何错误之处,请及时提出并告知我,万分感谢!
本帖谢绝跟帖,如欲发表评论,请前往百度家园2吧同步文章:http://tieba.baidu.com/f?kz=955318937
教程正文:
嗯,你没有看错,这的确是一篇MOD教程,还往哪里瞄?作者?你也没有看错,作者是我。好吧,我知道这并非我的本意,刚刚接触MOD也不过两个月不到,写一篇教程简直是在忽悠人。嘛……我承认,这只是为了招人而写的,让广大同两个月前的我一样的家园玩家们有信心成为一个MODer,并且,DTMOD制作组随时欢迎你们,群号:57143478。(……被各种蔬菜水果淹没,同时不忘感谢这段时间妖大和某徐给我的帮助……= =)
……
呼,终于爬出来了……
……
嘛……正文开始,首先说一下这篇教程的详细目的,这是一篇有关单人任务的制作及其相关东西的修改的教程。类似的教程你也可以找到几篇,并且似乎说的足够详细,但是,这些教程只是把做此类修改创作的门槛降到所有人都能跨过的程度,但这不意味着会有很多人愿意去跨过它,因此,我要做的是,把门槛完全移走,让所有人都可以直接进入到MOD大厦里这间别有一番风景的房间并能够很好地欣赏它。事实上,你将发现,这门槛本就不高,相比于其他房间来说,已经低多了,但此前几乎所有人都认为其它房间更值得进去,所以这间房一直无人问津,嘛……希望我把门槛移走后,能有人来,而且最好加入到我们DTMOD制作组中来……(再次被淹……)
……
听众里有卖果蔬的么,哦,忘了,我们在菜市场……嘛……下次换个地方……= =
1.1 原版单人任务脚本详解
主要目的:初步认识单人任务,基本了解任务脚本中规则和事件的大体结构,了解一些家园2内置函式,了解目标、指针的添加。
……
好了,接下来是真的进入到教程的正文了。要做单人任务,首先仔细想一下原版里单人任务包括了哪些元素?也就是说,制作我们自己的单人任务,需要修改、创作哪些元素?
首先,我们在主菜单中点击单人游戏,出现任务选择画面,很好,这是我们要修改的第一个元素集合。
它主要包括了:
1、左上角的任务预览图片
一般用于告知玩家该任务中的场景和主角,因此,等你的任务做好后,在任务中截个图作为预览图是再合适不过的了,修改它也十分容易,因此,考虑到读者的大脑兴奋度,我们放到后面讲它。
2、关卡名及简略的介绍文字
这个东西视你的任务剧情而定,我只负责在后面告诉你如何修改它。
3、其他各种界面内容
界面内容,嗯,如果你想改的话,我只能说,这东西跟UI有关,我涉猎不多,而且,这属于该大厦内每个房间的公有设施,所以,想修改它的话,请询问MOD大厦一楼大厅内的管理人员。
好了,让我们选择一个任务,第一关?没错,毕竟你的任务也要从第一关开始做起的对吧?点击开始游戏。
呼,熟悉的读条过程,等等,这东西可以修改的吧?
嗯,很好,你已经有标准的MODer思维了,看到什么都要改它一改……属于这个房间独有的东西,是那个大大的加载画面,这其实相当于一个大的任务预览图片,所以,我也放到后面去讲怎样修改它。
嘿!我们成功进入游戏了……珍惜这一时刻吧,以后你会怀念的……等做你自己的任务时,你就会明白了……
是那段熟悉的背景介绍动画!我们按Esc吧……
等等!你刚才的思维哪里去了,不想改一改这东西吗?你自己的剧情肯定是需要自己的过场动画的对吧?……很好,我们放到后面去讲该怎么导入我们自己的动画……什么?怎么制作动画?呃……你可以出了大门左转去旁边那座大厦碰碰运气……
接下来是转运超空间核心的动画,不按Esc了?……好吧,我想说,这东西的确是这房间里第三大家具,但是我至今缺少东西去打开那把大锁,不过我曾亲耳听到某徐说过他打开过这东西,你可以去问他,我这里,我只能告诉你,它叫NIS。
哈哈,接下来是游戏部分了!让资源船采集资源,建造一个战机生产设施,建造拦截机,去打靶机!……
咳……咳!任务玩了多少遍了?你还没腻?!忘了我们是来干嘛的么?很好,你还没忘……摆在我们面前的就是这个房间里最重要的两大家具了——地图和任务脚本,地图……嗯,这东西你应该比较熟悉,在其他房间里肯定看到过……目前我做地图只用了Skunks HW2 Map Editor 1.2和记事本,所以让我教你地图制作……哦,饶了我吧……你可以去看看《地图参数说明及两种地图格式的合并方法》这篇教程,这之后应该会对地图有个比较透彻的认识(http://blog.sina.com.cn/s/blog_583d3692010008f1.html)。
让我们来看这个最大的家具:任务脚本。这是本篇文章的主要内容,接下来我将一步一步详细地告诉你如何配合地图编写它,没错,配合地图,这不同于人机对战的地图,所以,还是先看一下上面提到的那篇地图教程吧。
……
看完了?好,接下来,让我告诉你一个对战地图和一个任务地图的不同。让我们重新审视一下原版第一关坦尼斯这个任务。
……仔细想,坦尼斯的地图和对战地图有什么不同?
……背景?
……好吧,我想说,这确实是最显眼的不同,但体现在地图的*.level文件至多只是几个字符的差别,而且这并不影响一个地图是任务地图还是对战地图。
是敌人!
对,没错,在任务中你一开始是一艘母舰,或者是上一关的所有舰队,而你的敌人绝不会是一艘旗舰一艘航母六艘资源船,更别说友军单位……凯米尔,坦尼斯,主教,纳巴船坞……这些东西如果放到对战地图里将是一堆碍事的东西,但在任务里,由于剧情的存在,它们变得不可或缺,因此,任务地图里一般会视你的剧情需要而添加一些东西才行,不过,我要说的是,这些其实可以通过它旁边的这个叫*.lua的东西来实现,尽管我一般不会这么做,如此看来,我们应该真正进入到主题了——强大到几乎万能的任务脚本,*.lua文件!(才进入正题?!兄弟们,撇他!……= =)
继续回忆坦尼斯这个任务,NIS暂且撇开不谈,这些等你的任务做好了再来考虑也不迟,那么,我们会发现,首先,你可以进行控制了,摆在你面前的是一个资源船,然后电脑卡了一下,怎么回事?哦,原来是在自动存档……恭喜你看到了第一个由任务脚本所执行的东西!不过,对于一个自制任务,自动存档这东西并不是必须的对吧?那么我们把它也放到后面再说。(呼……后面被我撇下了好多东西……跟一堆果蔬在一起……= =)
接下来,视线移向了母舰,舰队情报官说话了!唔,好啰嗦!按Es……不,哈哈,我们不是在玩,而是在从脚本的角度思考,所以,不要错过任何的细节。嗯,屏幕被切换到了感应器,圈住了你的资源船并且有一个指针指着旁边的资源箱,上下的黑框消失了,你发现【目标】这个东西在闪烁,点开它,会发现里面是情报官布置给我们的任务:采集资源。
……
等啊等,等啊等……资源船慢吞吞地采集了资源……呼……黑框出现了!建造战机生产设施……哎?干嘛?!
……
好了……我们已经回忆的够多了,下面该开始我们的教学了……让我们思考一下……首先,这是一个事件……
嗯……
这个事件里包含了很多的话……
嗯……
并且用圆圈和指针指示了你的资源船和集装箱……
嗯……
然后布置了任务……
嗯……
当我们完成任务后,又会触发新的事件……
嗯……
……别玩了!听我讲!……我讲到哪里了?
……讲到……用拦截机去打靶机……
嗯……嗯?!……= =,你……抬头看一下……周围的观众手里……拿的是什么?
……呃……不玩了,你快讲!
……好吧……总之,先记住截止到第二个事件发生前的这些事情是怎么发生的,我们随后将去看一下任务脚本是如何做到这一切的。
==========================================================================
接下来是我们的工具下载时间!
想做好自己的单人任务吗?你最好搞到以下工具:
原版全套的lua脚本或者MODpacker和LuaDC!
==========================================================================
好了,回到正题,我们要去……
看看原版任务脚本……
对!没错!就是这样!进入全套lua里,leveldata\campaign\ascension\m01_tanis,如果没有全套lua,就用MODpacker拆开Homeworld2.big,然后进入这一级目录,你会看到五个文件,分别是datfiles.lua,m01_tanis.level,m01_tanis.rot(tga),teamcolour.lua,以及……我们的任务脚本:m01_tanis.lua!如果是后一种情况,那么用LuaDC将之解码,打开它。
第一行你将看到:dofilepath("data:scripts/SCAR/SCAR_Util.lua")
很明显,它引用了一个文件:data/scripts/SCAR/SCAR_Util.lua,以后你有兴趣的话可以看一看。这一行出现的目的是为了,在接下来的脚本中,可以直接呼叫在这个文件里的函式。总之,你想做任务的话,一般也要有这么一行,所以我就不多说了,下面是……呼,好大一堆,都以obj开头,中间夹个prim或sec,后面是各式各样的名字,举个例子,和我们之前看到的内容相关的:
obj_prim_beginharvesting = "$40500"
obj_prim_beginharvesting_id = 0
这两行,一个定义的是目标名称,"$40500",这个等我讲到本土语言化的时候你就知道了;另一个定义的是它的索引,0,很常见的一种定义,后面还有好多呢。这两个东西会在后面被用到,所以在一开始它们被提前定义好了,以便接下来能够使用。prim代表了它是个首要目标,然而这只是为了能够一眼看出,并不是说这样它就是个首要目标了。理论上名字你可以随便起,只要不和其他定义的东西重名了就好,但是我们应该尽量采用原版的这种命名,你能一眼看到它是个目标,首要或次要,看到它的名字,知道这大概是个什么样的目标,这在脚本制作过程中是很重要的,随便起名字的话,简单脚本还行,复杂一点你到后来可能会乱掉……
接下来……是一大串以ping开头的东东,这个,是指针索引,还记得那个指示资源集装箱的指针吗?你在这里可以看到:
ping_resource_operation = "$40800"
ping_resource_operation_id = 0
这跟目标索引的格式是一样的,只是到后来会用不同的函式引用它们,于是它们将分别成为目标和指针。
继续向下……一大串g开头的东西,你会发现这次有点不一样,之前是每两行一组,而这里每行都不一样,每行都是独立的。它们一般都是变量,在脚本运行过程中,它们的值会有所变化,而不同的值代表着某些信息,从而使得游戏知道何时该干什么,因此也在一开始被定义。一个稍微复杂、高级点的脚本一般都会用到变量,我会在后面告诉你如何灵活运用它们。在这里你会发现一些不同的东西,例如g_now_time = Universe_GameTime()
g_playerID = Universe_CurrentPlayer()
等号后面不像其他的变量一样是0,而是一些奇怪的东西,感到陌生?那说明你没太接触过家园2的脚本,这些都是家园2的内置函式,它们几乎构成了家园2脚本的核心内容,它们数量繁多,所以等到后面我再逐一讲解它们,从最有用的开始讲起。
接下来你看到了什么?
Events = {}
Events.intelevent_beginharvesting =
{
{
……
很好,终于和刚才不一样了,而且……呼,这后面一堆什么乱七八糟的东西……
嗯……没错,乱七八糟,跟在游戏里一样,难怪你那么急于按Esc,感谢后面这个吧:
{ "Universe_EnableSkip(1)", "", },
它使得你按Esc可以直接跳过这堆乱七八糟的东西。但是,这些东西存在于这里,总有它的用处对吧?游戏里你按Esc,情报官没有唠叨,但指针依然会出现,目标依然会下达,都是因为这堆乱七八糟的东西的存在。从这里一直到Events.intelevent_subsystemconstruction = 之前,就是我们刚才所回忆的第一关的第一个事件的全部内容了,还记得都有什么吗?这个事件的主要内容有……
全国人大第……
咳咳咳!把收音机关掉!
……“视线移向母舰,情报官说了一堆话,屏幕被切换到了感应器,圈住了你的资源船并且有一个指针指着旁边的资源箱,黑框消失了,但你发现【目标】这个东西在闪烁,点开它,会发现里面是情报官布置给我们的任务:采集资源。”,这就是第一个事件,我们在这部分内容里会看到它是如何运作的。现在我们先不看它,直接跳到后面,一直跳到没有大括号的地方……呼……我拉……在中间,这一堆堆的大括号终于消失了,我们看到了什么?
function Rule_PlaySaveGameLocationCard()
Subtitle_Message("$3651", 3)
Rule_Remove("Rule_PlaySaveGameLocationCard")
end
嗯……学过程序的对这东西会不会感觉有些眼熟呢?没学过也不要紧,你很快就会掌握它们的使用。
刚才这几行不是重点,不过我可以告诉你,它跟自动存档时出现的“正在保存游戏”“游戏已保存”有莫大的关系。让我们往后看:
function OnInit()
恭喜你看到了脚本最初运作的地方。从现在起,我将告诉你游戏是如何运行这东西的。首先function OnInit()是一个规则,这个函数有别于其他的规则,你可以找找看,除了它下面这行:print("oninit issued")之外,整个脚本里再没有OnInit,那么它是如何被启动的呢?
……别找了,游戏在开始一个任务时首先就会自动启动一个名叫OnInit的规则,所以,我们才说它在这里就是整个脚本开始的地方。
到这里我将不得不讲一些实实在在的东西了。任务里有各式各样的事件,满足某些条件时,事件发生,再满足某些条件时,新的事件发生,决定事件何时发生就靠这些functions,而事件的内容,我们一般会放在前面那些乱七八糟的东西里面,也有一些二者都能实现的东西,例如让飞船干这干那之类的。function里可以开始某一事件,也可以加载某一function,不过,事件里虽然可以加载某个function,但不能开始某一事件,因为事件同一时间只能进行一个,否则,正在进行的事件会乱的一塌糊涂。function的部分擅长于对各种条件进行判定,events部分则擅长于对剧情的叙述,于是,一个任务就能够这样被建立起来,并有序地进行。
要开始一个事件,可以用Event_Start("XXXX"),里面是你要发生的事件,给它加上一个条件,使其在条件满足时发生,可以用if …… then …… end的形式,我们很快就会看到这样的形式。要加载一个规则,可以用XXXX()或Rule_Add("XXXX")或是Rule_AddInterval("XXXX, T")的形式,XXXX是你要加载的规则名称,你应该在此函式外另起一段,以function XXXX()开头,为脚本添加XXXX这个规则并初始化它,后面每一行都是此规则中的内容,最后以end结尾,你会在后面发现每个规则都是这样的结构。第一种规则加载方式会使规则立即运行一次,第二种会使规则立即开始不断地运行,第三种会在时间T后开始运行一次这个规则,并在以后每隔时间T运行一次,对于后两种,如果你想在规则里的东西运行完之后不再运行,可以在里面加上Rule_Remove("XXXX"),则此规则将不会被再次加载。这很重要,比如,你之前运行了Rule_Add("A"),打算在这之后一旦某条件满足后开始事件B,然后你定义了A规则如下:
function A()
if …… then
Event_Start("B")
end
end
由于没有Rule_Remove("A"),所以一旦“……”所表示的条件满足,事件B就会发生,紧接着,规则会被再次加载,一般情况下,由于时间间隔太短,条件将仍然满足,于是事件B又会开始一次,如此一来,游戏就会不停地开始事件B,解决的办法不是将Rule_Add("A")换成A(),因为这样一来规则只会被加载一次,而这一次的加载,条件很可能没有满足,等到满足时,这个规则并没有被加载,于是事件B不会如我们希望的那样发生,所以,我们加上Rule_Remove("A"),注意加的位置,由于我们希望条件没满足时让游戏不停地检查条件是否满足,所以应该加在事件开始前后,这样,当条件不满足时,规则不会被移除,事件B也不会发生,一旦条件满足,则事件B发生,同时规则A移除,OK,搞定。注意,如果你加在结束if的end后面,那就和使用A()没有分别了,规则加载后,检查一次,紧接着规则就被移除了。
如此,你应该能看懂简单的规则的结构了,没错,结构,不是内容,因为你还不知道那些数量众多的家园2内置函式都代表着什么,这也是为什么我之前的条件用“……”代替的原因。不要急,随着内容的增多,你会了解到越来越多的家园2内置函式,并且越来越运用自如,越来越感觉你简直是个上帝……
让我们继续往下看:
Rule_Add("Rule_Init")
Rule_Add("Animate_MothershipDoorOpen")
很明显,游戏一开始,有两个规则:Rule_Init和Animate_MothershipDoorOpen被加载了,注意,此时,超空间核心还在转运中,也就是说,游戏在进行NIS。好了,让我们往下看看这两个规则是什么样子的。
Sound_SetMuteActor("All_")
Sound_EnableAllSpeech(0)
这两个其实都属于家园2内置函式,我们先不看它们……
首先看到的是function Animate_MothershipDoorOpen(),它有如下内容:
if Universe_GameTime()>=1 then
SobGroup_SetMadState("Mothership", "NIS00")
Rule_Remove("Animate_MothershipDoorOpen")
end
end
它的结构是显然的,一旦某个条件满足后,则怎么怎么样,就没了,十分简单的一个规则。还记得NIS里母舰的大舱门是开启的吗,直到超核进去门才关上,这个规则是用来让母舰的舱门开启的。它下面你可以看到function Animate_MothershipDoorClose(),哈,一样的结构……
接下来让我们找到Rule_Init:
找到了……我咧个……这么多内容?!
没错,这是游戏开始后加载的最重要的规则了,让我们看看都有什么,首先你会发现,这里面没有条件式if,它不会去检查什么条件。首先有一串:
UI_BindKeyEvent(IKEY, "cheat_i")
UI_BindKeyEvent(CKEY, "cheat_c")
UI_BindKeyEvent(DKEY, "cheat_d")
UI_BindKeyEvent(EKEY, "cheat_e")
UI_BindKeyEvent(AKEY, "cheat_a")
UI_BindKeyEvent(PKEY, "cheat_p")
UI_BindKeyEvent(OKEY, "cheat_o")
UI_BindKeyEvent(LKEY, "cheat_l")
Rule_Add("icdeadpeople")
哈哈……这个东西你以前要是不看脚本肯定不会知道,这么跟你说吧,你再进入第一关时,等到没有事件发生时 ,输入"icdeadpeople"看看……呵呵,这相当于游戏制作者当年留下的一个小把戏,我们就不管它了……接下来有很多都是家园2内置函数的呼叫,它们多与游戏一开始的设置有关,我们只看加载规则的函式:
Rule_Add("Animate_MothershipDoorOpen")
Rule_Add("Animate_MothershipDoorClose")
呃……又有Animate_MothershipDoorOpen?!哈,它跟OnInit里的Rule_Add("Animate_MothershipDoorOpen")重复了,看来这是当年制作时的小失误呢,两个函式会被几乎同时呼叫,然后去加载同一个规则,这对脚本的运行不会有什么影响,但看上去OnInit里的那个删掉会好看得多……总之,我们制作自己的任务时要尽量避免失误就是了,有些失误是致命的,决不像这个这样可有可无……接下来是:
DisableMothership()
它是一个一次性的加载,DisableMothership这个规则在上面有定义,里面对母舰做出了很多的限制,以使第一关的母舰是我们看上去的那个样子。这与剧情关系不大,我们继续看下面:
Rule_Add("Rule_NIS01AComplete")
Rule_Remove("Rule_Init")
Rule_Add("Rule_PlayerWins")
Rule_Add("Rule_PlayerLose")
end
加载了一个Rule_NIS01AComplete的规则……然后,呃……Rule_Remove("Rule_Init")?!那Rule_Init这个规则不就被移除了?它后面的东西还会运行吗?
当然,Rule_Remove不一定非要加在最后的,规则的一次加载,其中的所有内容都会运行一次,Rule_Remove是不让它下次继续加载,所以位置无所谓,除非出现上面我说过的的那种情况。
所以,最后面一共加载了三个规则,它们是:
"Rule_NIS01AComplete"
"Rule_PlayerWins"
"Rule_PlayerLose"
后两个从名字上就看得出来,它们决定了玩家的输赢,这个我们以后可以自己弄,所以,真正决定剧情走向的,看来就是"Rule_NIS01AComplete"这个规则了,让我们找到它:
function Rule_NIS01AComplete()
if NISComplete(g_NISState)==1 then
SobGroup_SetMadState("Mothership", "Normal")
g_NISState = 0
UI_UnBindKeyEvent(ESCKEY)
Sound_MusicPlayType("data:sound/music/ambient/AMB_01", MUS_Ambient)
Event_Start("intelevent_beginharvesting")
PostNISInit()
Rule_Add("Rule_OpeningIntelEventComplete")
Rule_Remove("Rule_NIS01AComplete")
end
end
嗯,熟悉的结构,只是条件满足后干的事情比较多,我们……
好吧,到这里,我不得不详细地跟你讲一下if条件式了,先不看条件内容,那么就是这个样子:
if ……==1 then
呃……那个“==1”是什么东西呢?
这涉及到一点lua语法了,“==”相当于“是”,有相等的意味,但在这里我们不能用“=”来代替“==”,因为“=”被用于赋值,就像我们一开始看到的那些。“1”在这里是一个布尔值,代表true。现在我们可以来看看NISComplete(g_NISState)了,它是个包含变量的函式,我们先不做深入讲解,总之,它被呼叫后,游戏会在这个位置返回一个布尔值。
于是初步翻译一下就是这个样子:
如果 NISComplete(g_NISState)返回的布尔值是ture,那么……
从这个规则的结构我们可以看到,如果条件不满足,那么这个规则会被不断地加载,直到条件满足为止。那么,这个函式又究竟是什么样的呢?它在当转运超核这个NIS进行时会返回false,也就是0,一旦NIS播放完毕,它就会返回true,于是,这个条件是的意思就成了:
如果 转运超核这个NIS播放完毕,那么……
很好,这才是人类的语言,呼……让我们往下看吧,还是只看关键的东西:
Event_Start("intelevent_beginharvesting")
PostNISInit()
Rule_Add("Rule_OpeningIntelEventComplete")
嗯,开启了一个名为"intelevent_beginharvesting"的事件,加载了PostNISInit和Rule_OpeningIntelEventComplete两个规则。我们一会儿来详细讲解"intelevent_beginharvesting"这个事件,先来看PostNISInit这个规则。就在上面,嗯,只有三行,而且一个都不认识= =
嘛,这也是对单人任务中玩家的一些限制,与剧情无关,我们还是来看Rule_OpeningIntelEventComplete这个规则吧:
function Rule_OpeningIntelEventComplete()
if Event_IsDone("intelevent_beginharvesting")==1 then
UI_ClearEventScreen()
Camera_SetLetterboxStateNoUI(0, 0)
Rule_Add("Rule_BuildSubsystems")
Rule_Remove("Rule_OpeningIntelEventComplete")
Rule_Add("Rule_PlaySaveGameLocationCard")
Rule_AddInterval("Rule_SaveTheGameMissionStart", 1)
end
end
如果什么什么返回的布尔值是true,那么……
喂喂喂……你不会不懂一点英文吧= =,那个Event_IsDone明显是事件结束的意思嘛……所以,第一行的意思应该是:
如果 事件"intelevent_beginharvesting"结束了,那么……
话说……你最好还是懂点英语,日后我虽然会介绍很多家园2的内置函式,但也只是一些常见的,你的任务总会有一些特别之处,当你以后熟练了,任务制作的过程中往往自然而然地就会想去找一个什么什么样的函式来使用。虽然家园2内置函式是有限的,但只要你懂点英语,对这些函式有了一些了解之后,大部分情况下,你还是会找到符合你要求的函式的。
所以,下载FunctionReference,里面有所有家园2的内置函式的英文简介,它们在游戏中被广泛地使用,不光是lua哦,还有level,ship,subs,weapon等等等等……(= =本质上不都是lua吗……),在接下来的教学出现之前,简单地看一看他们,有个了解……
==========================================================================
这算是……预习?
……嘛……随你怎么想吧……= =
好了,言归正传,我们继续:
UI_ClearEventScreen()
我上找下找,就是没找到UI_ClearEventScreen这个规则!
……唔,所以说,让你先了解一下家园2的内置函式,UI_ClearEventScreen()其实是它们之一,接下来一行也是,所以跳过……
Rule_Add("Rule_BuildSubsystems")
Rule_Remove("Rule_OpeningIntelEventComplete")
Rule_Add("Rule_PlaySaveGameLocationCard")
Rule_AddInterval("Rule_SaveTheGameMissionStart", 1)
加载Rule_BuildSubsystems和Rule_PlaySaveGameLocationCard这两个规则,并每隔一秒加载Rule_SaveTheGameMissionStart这个规则,同时移除目前的规则Rule_OpeningIntelEventComplete。
我们先来看Rule_PlaySaveGameLocationCard……Rule_PlaySaveGameLocationCard……怎么这么熟悉?!哈!还记得么?在OnInit之前我们见过它,现在孤独寂寞的它终于被加载了……
再去看看一秒以后将要加载的Rule_SaveTheGameMissionStart这个规则:
function Rule_SaveTheGameMissionStart()
Rule_Remove("Rule_SaveTheGameMissionStart")
g_save_id = (g_save_id + 1)
Campaign_QuickSaveNb(g_save_id, "$6464")
end
唔,大体上你应该能看出来这似乎是在存档……让我们回想一下游戏里,果然,在第一个事件结束后,不正是自动存了个档吗?“正在保存游戏……”,一秒钟之后,游戏自动存档,出现“游戏已保存”,当我们从脚本的角度再来看这些东西时,是多么的美妙!
好了,让游戏继续进行的规则又只剩下了一个:Rule_BuildSubsystems,让我们看看它:
function Rule_BuildSubsystems()
if Player_GetRU(g_playerID)>=700 then
Ping_Remove(ping_resource_operation_id)
Objective_SetState(obj_prim_beginharvesting_id, OS_Complete)
Event_Start("intelevent_subsystemconstruction")
Rule_Add("Rule_HasBuiltFighterSubsystem")
Rule_Remove("Rule_BuildSubsystems")
Rule_Add("Rule_SaveAfterBuildFacility")
end
end
嗯……还是那个结构,可以看到,如果条件满足,那么,触发事件"intelevent_subsystemconstruction"……呃……这应该是第二个事件了吧?是什么来着?
是……是……
哦……对了,我们刚刚并没有对第二个事件进行回忆,那么,我们现在就来回忆一下:第二个事件是如何发生的?
资源船采集了资源……然后……事件就发生了……
哈,没错,一箱资源有700RU呐……我们再来看这个if条件式,不难猜到它的意思是:
如果 玩家有不少于700的RU,那么……
后面的,我们只需要看这个内容,其他的就不用管了:
Objective_SetState(obj_prim_beginharvesting_id, OS_Complete)
由于我们还没有去看"intelevent_beginharvesting"这个事件,所以暂且记住它就好。
==========================================================================
哈!没错!就是这样!好了!functions部分的旅程到此结束了!让我们回过头去看看……Events{},那堆乱七八糟的东西。
还记得,刚刚functions的部分,有什么事件被开启了么?是intelevent_beginharvesting,嗯,不错,事件的名字显而易见,开始采集,正是任务中的第一个事件。这结构……跟functions很不一样,不是么?很多“{”和“}”,内容全被包起来了。没错,这就是事件的结构,指挥官说的话,目标的下达一般都会在这里面进行。每个Event必须只有一组“{}”,但是每一组“{}”里都可以有无数多组“{}”,这就是为什么它看起来如此乱七八糟。
开启一个事件,那么就像加载一个规则一样,你需要为这个事件定义,不同的是,你的定义必须在Events{}后面,以
Events.XXXX =
{
}
这样的结构来表示,里面是事件的主要内容。
啊,这么多“{}”,真有点受不了,我们试着把它们分的清楚一些,最外面的“{}”包住了整个事件,它只有一组,里面的“{}”就要低一等了,它们包住了一些细节,许多个细节依次发生,组成了整个事件,有些细节里面还有更多组“{}”,这些“{}”里面一般是一两个函式,一般的事件也就到此为止了,很少有第四等的“{}”存在。从第二等开始,每组“{}”后面还有个逗号,同等级别的多组“{}”里的内容理论上会被同时加载,但有一个东西可以使得它们依次发生,且能决定中间的间隔时间,那就是HW2_Wait(T),里面的那个T就是间隔时间,单位是秒。按我的理解,只要有它在,它所在的这组“{}”内位于它后面的内容就必须要等到它前面的内容进行完毕后T秒再进行。而这组“{}”有可能与同级的其他“{}”一起进行,这时就需要把它自己单独包在一个“{}”里,然后用它对这些“{}”进行分隔。
……呃……听明白了么……
……没有……
……我自己都糊涂了……好吧……我承认,事件表我还没有真正熟练的掌握,一直都在照葫芦画瓢,不过已经够用了,让我为你简单解释一下以使你对事件表有一个比较清晰的认识吧。
首先看:
{
{
{ "Universe_EnableSkip(1)", "", },
这是一个标准的事件表里的函式呼叫。被呼叫的函式要加引号,后加逗号,再加上“"",”,最后由“}”将之封闭。 我说过,它使得你能够按Esc跳过这个事件直接开始游戏,这样的话,游戏会省去事件中的所有内容,但还是会过一遍,把里面添加的目标、指针显示出来。
然后是:
HW2_LocationCardEvent("$40520", 4), },
一定要始终记得你的事件有几个“{”和“}”,二者数量应该相等,但如果一路数下去,将只会在数完最后一个“}”时才会相等,也就是说,还要满足只有一组“{}”这个条件,刚接触这方面的话很容易在这方面出错,所以一定要注意。可以看到,现在第二个“{”已经被封闭了,第一个“{”只能由最后那个“}”来封闭,好了,现在希望你已经会检查这些“{”和“}”了。至于这一句,记得事件一开始画面中下方显示的“坦尼斯——巨大的遗迹”吗,这东西让它在那里显示4秒钟。“"$40520"”定义了内容,而“4”定义了显示时间。
{
{ "Sound_SetMuteActor('Fleet')", "", },
{ "Sound_EnableAllSpeech( 1 )", "", },
{ "Sound_EnterIntelEvent()", "", }, HW2_Wait(1), },
这几行同样也是事件开始时的设置,有关声音的,一般的事件都会有这样的设置,我们做自己的任务时,虽然没有语音,但你把它粘上也无所谓,万一你哪天心血来潮打算给你的任务来个配音呢?有一行你应该愿意添加:Sound_EnterIntelEvent(),还记得每次进入一个事件,伴随着黑框拉下的,还有一小段声音吗?这句话使得这个声音发出。
我们看到了HW2_Wait(1), 而且后面还有一个
{ HW2_Wait(1), },
让我们深呼吸1秒钟,看看下面的内容:
{
{ "Camera_Interpolate( 'here', 'camera_focusonMothership', 3)", "", }, HW2_SubTitleEvent(Actor_FleetIntel, "$40530", 5), },
这个函式使得视角从目前的位置在3秒内转换到“camera_focusonMothership”这样一个位置,“camera_focusonMothership”要在地图里定义,我们后面详细地讲地图时再讲它。它后面这个……是让舰队情报官说:“这里是舰队情报官。”,“5”代表字会在屏幕上显示5秒钟,“Actor_FleetIntel”让游戏调用舰队情报官的头像,你应该没有忘记,原版任务里有好几张头像呢……如果想让马大帅出现,只需要把这里改为“Actor_Makaan”。
接下来……
{ HW2_Wait(1), },
{ HW2_SubTitleEvent(Actor_FleetIntel, "$40531", 8), },
{ HW2_Wait(1), },
{ HW2_SubTitleEvent(Actor_FleetIntel, "$40532", 10), },
{ HW2_Wait(1), },
{ HW2_SubTitleEvent(Actor_FleetIntel, "$40533", 10), },
{ HW2_Wait(1), },
{ HW2_SubTitleEvent(Actor_FleetIntel, "$40534", 10), },
{ HW2_Wait(1), },
呼……一行行都是一样的格式,不过每一个都被{ HW2_Wait(1), }, 分开了,你不想让舰队情报官同时说四句话,对吧?
{
{ "Sensors_EnableCameraZoom( 0 )", "", },
{ "Sensors_Toggle( 1 )", "", }, HW2_SubTitleEvent(Actor_FleetIntel, "$40535", 5),
{ "Camera_Interpolate( 'here', 'camera_FocusOnResources', 2 )", "", },
},
后两个已经介绍过了,就先不讲了,看前面两个。还记得任务中视角从正常切换到感应器中吗?第一个:Sensors_EnableCameraZoom( 0 )是不让玩家在二者间进行切换,“0”代表不能,Sensors_Toggle( 1 )就是将视角切到感应器了,如果你的视角已经处于感应器状态,那么这个函式就不起作用也不用起作用了。
再次深呼吸两秒钟,让我们看看下面的内容:
{
{ "Player_FillShipsByType('tempSobGroup', 0, 'Hgn_ResourceCollector')", "", },
{ "g_pointer_default1 = HW2_CreateEventPointerSobGroup( 'tempSobGroup' )", "", }, HW2_SubTitleEvent(Actor_FleetIntel, "$40536", 10), },
{ HW2_Wait(1), },
这里有一个很重要的概念:SobGroup,我打算在后面着重讲一下它。它之所以重要,是因为很多指令都是对SobGroup而非对船下达的,SobGroup可以包含任意种类、数目的船,也可以是空的。在这里,这几个函式产生了一个临时的SobGroup,并将你的资源船归入其中,然后让感应器中以这个SobGroup,也就是你的资源船为中心,产生一个圆圈。情报官说了10秒钟的话,又一秒钟之后,发生了下面的事情:
{
{ "EventPointer_Remove(g_pointer_default1)", "", },
{ "g_pointer_default1 = HW2_CreateEventPointerVolume( 'vol_Resources' )", "", },
{ "obj_prim_beginharvesting_id = Objective_Add( obj_prim_beginharvesting, OT_Primary )", "", },
{ "Objective_AddDescription( obj_prim_beginharvesting_id, '$40950')", "", },
{ "ping_resource_operation_id = HW2_PingCreateWithLabelPoint ( ping_resource_operation, 'vol_Resources' )", "", },
{ "Ping_AddDescription(ping_resource_operation_id, 0, '$40900')", "", }, HW2_SubTitleEvent(Actor_FleetIntel, "$40537", 10), },
g_pointer_default1上面出现了,其实就是那个圆圈,这里用EventPointer_Remove将之移除了。
紧接着出现了和刚才一样的东西,哈,圆圈又出现了,这次不是你的资源船,而是一个名叫“vol_Resources”的点,实际上,就是那个资源箱。
后面两个是不是很熟悉呢?嗯,我们一开始被定义的目标在这里被用到了,通过Objective_Add和Objective_AddDescription将一开始定义的名为"$40500"的目标加进目标列表,同时加入它的描述:'$40950'。他是一个首要目标,因为“OT_Primary”,如果是次要目标,则改为“OT_Secondary”。我们还要回忆一下,在看functions最后是,有一行
Objective_SetState(obj_prim_beginharvesting_id, OS_Complete)
他是在什么时候被呼叫的呢?是在玩家拥有了不少于700RU的时候。它的作用是,将obj_prim_beginharvesting这一目标设为“完成”,如果想将某一目标设为失败,则把“OS_Complete”变为“OS_Failed”。前面的“obj_prim_beginharvesting”则应该和之前设置的目标名称相同。现在你应该知道如何添加目标并将其在满足何种条件时设为完成或失败了吧?
{ "ping_resource_operation_id = HW2_PingCreateWithLabelPoint ( ping_resource_operation, 'vol_Resources' )", "", },
{ "Ping_AddDescription(ping_resource_operation_id, 0, '$40900')", "", }, HW2_SubTitleEvent(Actor_FleetIntel, "$40537", 10), },
嗯……记得最前面那些以“ping”开头的东西么?这里,ping_resource_operation被添加到感应器中作为指针,指着……也是vol_Resources这个点,唔……跟添加目标的格式很像,因为指针和目标一样,有自己的名称和描述。
接下来是这个事件结束的地方了:
{
{ "EventPointer_Remove(g_pointer_default1)", "", },
{ "Camera_Interpolate( 'here', 'camera_focusOnCollector', 2 )", "", },
{ "Sensors_Toggle( 0 )", "", }, HW2_Wait(2), },
{
{ "Sound_ExitIntelEvent()", "", },
{ "Sound_SetMuteActor('')", "", }, HW2_Letterbox(0), HW2_Wait(2),
{ "Sensors_EnableCameraZoom( 1 )", "", },
{ "Universe_EnableSkip(0)", "", },
},
}
首先,移除了那个指着资源箱的圆圈,用两秒钟将镜头切换到camera_focusOnCollector这个视角,同时从感应器中切回到正常空间,由于切换镜头要花费两秒钟,所以我们看到了HW2_Wait(2),以使后面的内容能在镜头切换完毕后进行。
后面这些,跟事件开始时几乎是对立的,发出退出事件的声音,允许玩家进行正常视角和感应器之间的切换,不允许按Esc(这个……好像没有这句的话,退出事件后按Esc,游戏会跳出到Windows= =)。
这里我们看到了:HW2_Letterbox(0),它的意思是让黑框消失,为什么我们之前没有看到HW2_Letterbox(1)——拉出黑框呢?这是因为这个事件是紧接着NIS进行的,黑框本来就没消失过。如果你看一眼后面的事件,你会发现,在事件开始的部分,几乎都会有HW2_Letterbox(1)。
好了!对原版第一关坦尼斯任务脚本无比蛋疼无比恶心的讲解到此结束,谢谢大家的水果和蔬菜,我们下一期再见!
|
|