泰坦帝国

 找回密码
 加入帝国

QQ登录

只需一步,快速开始

查看: 3911|回复: 4

[教程文章] [DT MOD制作组出品]单人任务傻瓜教程(陆续更新,谢绝回复)

[复制链接]
发表于 2010-12-14 21:22:29 | 显示全部楼层 |阅读模式
本帖最后由 萨尤克教主 于 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)。

好了!对原版第一关坦尼斯任务脚本无比蛋疼无比恶心的讲解到此结束,谢谢大家的水果和蔬菜,我们下一期再见!

FunctionReference(感谢某徐提供的链接).rar

134.63 KB, 下载次数: 833

全套LUA(感谢xangle13的网盘).part01.rar

390.63 KB, 下载次数: 836

全套LUA(感谢xangle13的网盘).part02.rar

390.63 KB, 下载次数: 727

全套LUA(感谢xangle13的网盘).part03.rar

177.86 KB, 下载次数: 722

 楼主| 发表于 2010-12-29 16:09:42 | 显示全部楼层
本帖最后由 萨尤克教主 于 2010-12-29 18:34 编辑

    嗨,大家好!欢迎进入第二阶段的学习,在这一阶段,我们将会分别熟练家园2使用的脚本语言——LUA的一些基本编程技巧,以及家园2内置函式中诸多重要概念和一些常用函式的使用。
    间隔了这么多天才继续,主要是时间问题,嘛……不过还有一个原因就是……后面不少内容我并不是完全理解,目前也在学习中= =,所以在这里再次感谢妖大和某徐……

    2.1  重要概念:SobGroup

    目的:了解SobGroup的概念,了解与之相关的常用函式并能够熟练掌握它们,了解其他与SobGroup相关的函式。

    SobGroup,太重要了,我们必须在第二章一开始就提到它。你会发现,相当多的家园2内置函式都与SobGroup有关,那么,SobGroup究竟是什么呢?
    之前已经简单地提到过一次,SobGroup,从数学的角度讲,是一个集合,里面的每一个元素都是一艘船,像集合一样,它有自己的名字,它还可以是空集,也就是没有船。仅此而已,这

就是SobGroup,就是这么简单,但是以这一概念为基础,我们可以进行相当多的工作。
    让我们从头开始讲起。首先,创建一个SobGroup,你既可以在地图里也可以在脚本里做到这一点,不过用到的函式会不同,比如,你想创建一个名为A的SobGroup,那么你需要写下:
createSOBGroup("A")(地图里)或是
SobGroup_Create("A")(脚本里)
    如此,一个名为A的SobGroup就被创建了,不过现在它是一个空集,如果我们想让它里面有船的话,在地图里,只需写下形如:addToSOBGroup("Player_Mothership", "A")这样的东西,

Squadron是单艘的船,如果你看过地图教程,对它应该不陌生,例中的"Player_Mothership"便是一个Squadron的名字,看你之前添加了一个叫什么的什么Squadron了……比如,地图中已经添

加了一个希格拉之耀并命名为"Player_Mothership"的话,例子就将这艘希格拉之耀放到了A这个SobGroup里。用同样的方法,你也可以创建一个叫B的SobGroup,同样把这艘希格拉之耀放进去

,那么它就会既在A里又在B里,这是可以的。同样,我们可以把更多的Squadron放到SobGroup里。但是,这些Squadron是在地图里创建的,如果是在游戏中造出来的船呢?它们将是一堆无名

氏,我们怎么把它们放到SobGroup里呢?有几个函式可以使用,不过,可以看到,这并不那么随心所欲:
Player_FillSobGroupInVolume:把某玩家在某Volume(Volume在地图教程中有说明,以后我们也会讲到)中的船放到某SobGroup里
Player_FillShipsByType:把某玩家的某种船放到某SobGroup里
Player_FillProximitySobGroup:把某玩家靠近某SobGroup里的船放到某SobGroup里
    诸如此类……
    我们可以看到,由于没有名字,我们必须通过很多间接的方式来为SobGroup添加舰船,另外,这些添加方式多数也可以用在SobGroup之间,也就是把例中“某玩家”变为“某SobGroup”

,相应函式可以在SobGroup开头的函式中找到,SobGroup之间还可以进行并集、交集和差集运算。需要注意的是,为某SobGroup添加舰船时,这个SobGroup必须被事先创建好,游戏不会同时

为你做两件事。
    接下来我们就该来看看利用SobGroup的相关函式,我们可以做到什么了:几乎所有你能想到的,用于判断的,用于控制的,用于取得信息的,用于改变状态的……这些以SobGroup开头的

函式足以使你成为家园世界中的半个上帝!总共600余个函式中,以SobGroup开头的就占了五分之一!

    我在这里列举我目前确切了解的所有与SobGroup相关的函式并做简单介绍,详细的使用方法以及更多函式还是要亲自去看functionreferance:

控制SobGroup相关:

SobGroup_Create:创建一个SobGroup
SobGroup_Clear:清除某SobGroup
SobGroup_FillCompare:两SobGroup取交
SobGroup_FillSubstract:两SobGroup取差
SobGroup_FillUnion:两SobGroup取并
SobGroup_SobGroupAdd:把某SobGroup全部加到某SobGroup里
SobGroup_FillShipsByType:把某SobGroup的某种船放到某SobGroup里
SobGroup_RemoveType:把某种船从某SobGroup中剔除(船不会消失,只是不属于这个SobGroup了)
SobGroup_FillProximitySobGroup:把某SobGroup靠近某SobGroup里的船放到某SobGroup里
SobGroup_GetSquadronsInsideDustCloud:把某SobGroup在某尘云中的船放到某SobGroup里
SobGroup_GetSquadronsInsideNebula:把某SobGroup在某星云中的船放到某SobGroup里
SobGroup_FillSobGroupInVolume:把某SobGroup在某Volume中的船放到某SobGroup里
SobGroup_GetSobGroupBeingCapturedGroup:把某SobGroup中正在被登陆/渗透的船放到某SobGroup里
SobGroup_GetSobGroupCapturingGroup:把某SobGroup中正在进行登陆/渗透的船放到某SobGroup里
SobGroup_GetSobGroupDockedWithGroup:把某SobGroup中正在某SobGroup中停泊的船放到某SobGroup里
SobGroup_GetSobGroupRepairingGroup:把某SobGroup中正在进行维修的船放到某SobGroup里
SobGroup_GetSobGroupSalvagingGroup:把某SobGroup中正在进行打捞的船放到某SobGroup里

获取信息:

SobGroup_Count:获取某SobGroup中包含的Squadron数目
SobGroup_CountByPlayer:获取某SobGroup中属于某玩家的Squadron数目
SobGroup_GetActualSpeed:获取某SobGroup首船的运动速度
SobGroup_GetHardPointHealth:获取某SobGroup某硬点的装甲(0-1)
SobGroup_GetPosition:获取某SobGroup所在位置
SobGroup_GetSpeed:获取某SobGroup首船的速度相比于正常状态的系数
SobGroup_GetTactics:获取某SobGroup的战术
SobGroup_HealthPercentage:获取某SobGroup的装甲(0-1)
SobGroup_OwnedBy:获取某SobGroup中首船的所有者(玩家的Index)

进行控制:

SobGroup_Attack:令某SobGroup攻击某SobGroup
SobGroup_AttackPlayer:令某SobGroup攻击某玩家
SobGroup_AttackSobGroupHardPoint:令某SobGroup攻击某SobGroup的硬点(引擎啊,导弹阵列啊,或是子系统啊等等……)
SobGroup_CaptureSobGroup:令某SobGroup登陆/渗透某SobGroup
SobGroup_CreateShip:令某SobGroup瞬间造出某种船
SobGroup_CreateSubSystem:令某SobGroup瞬间造出某子系统
SobGroup_DockSobGroup:令某SobGroup停泊进某SobGroup
SobGroup_DockSobGroupAndStayDocked:令某SobGroup停泊进某SobGroup并保持停泊状态
SobGroup_DockSobGroupInstant:令某SobGroup瞬间停泊进某SobGroup
SobGroup_Launch:令某SobGroup从某SobGroup里发射出来(注:停泊有待发射SobGroup的船必须是其所在SobGroup的首船,即SobGroup里所有船中第一个被添加进该SobGroup里的船……本人在此处被困N久= =)
SobGroup_EnterHyperSpaceOffMap:令某SobGroup进入超空间
SobGroup_ExitHyperSpace:令某SobGroup在某处脱离超空间
SobGroup_ExitHyperSpaceSobGroup:令某SobGroup在某SobGroup附近脱离超空间
SobGroup_Spawn:令某SobGroup瞬间进入超空间
SobGroup_Despawn:令某SobGroup瞬间在某处脱离超空间
SobGrSobGroup_ForceStayDockedIfDocking:令某停泊中的SobGroup保持停泊状态
SobGroup_GuardSobGroup:令某SobGroup为某SobGroup护航
SobGroup_Move:令某SobGroup向某Volume移动
SobGroup_MoveToSobGroup:令某SobGroup向某SobGroup移动
SobGroup_RepairSobGroup:令某SobGroup维修某SobGroup
SobGroup_Resource:令某SobGroup中所有属于某玩家的资源船开始采集
SobGroup_SalvageSobGroup:令某SobGroup打捞某SobGroup
SobGroup_SetAsDeployed:令某SobGroup的所有一次性移动装置全部定位,不可再移动
SobGroup_SpawnNewShipInSobGroup:瞬间在某Volume处变出一个属于某玩家的Squadron到某SobGroup里
SobGroup_Stop:令某SobGroup中属于某玩家的船停止行动
SobGroup_FormHyperspaceGate:令某SobGroup和某SobGroup中的超空间跳跃门连接
SobGroup_UseHyperspaceGate:令某SobGroup使用某超空间跳跃门

设置状态:

SobGroup_AbilityActivate:开启/关闭某SobGroup的某项能力
SobGroup_ChangePower:设置某SobGroup某武器硬点是否运作
SobGroup_MakeSelectable:设置某SobGroup可否被选中
SobGroup_MakeUntargeted:设置某SobGroup可否被攻击
SobGroup_RestrictBuildOption:令某SobGroup不可建造某种东西
SobGroup_UnRestrictBuildOption:取消对某SobGroup某个建造项目的限制
SobGroup_SetAutoLaunch:设置某SobGroup的发射选项(保持停泊,自动发射等)
SobGroup_SetBuildSpeedMultiplier:设置某SobGroup的建造速度相比于正常状态的系数
SobGroup_SetHardPointHealth:设置某SobGroup某硬点的装甲(0-1)
SobGroup_SetHealth:设置某SobGroup的装甲(0-1)
SobGroup_SetMadState:设置某SobGroup的动画状态(还记得第一节中讲到的母舰开启舱门的那个吗?)
SobGroup_SetSpeed:设置某SobGroup的速度相比于正常状态的系数
SobGroup_SwitchOwner:将某SobGroup变换所有者
SobGroup_SetSwitchOwnerFlag:设置某SobGroup在变换所有者后(如被登陆/渗透之类)是否从该SobGroup中脱离
SobGroup_SetTactics:设置某SobGroup的战术
SobGroup_SetTeamColours:设置某SobGroup的舰队颜色
SobGroup_TakeDamage:令某SobGroup受到某种程度的伤害

进行判断:

SobGroup_AreAllInHyperspace:判断某SobGroup里的船是否都在超空间
SobGroup_AreAllInRealSpace:判断某SobGroup里的船是否都在正常空间
SobGroup_AreAnyOfTheseTypes:判断某SobGroup里的船中是否含有某种船
SobGroup_AreAnySquadronsInsideDustCloud:判断某SobGroup里是否有船在某DustCloud里
SobGroup_AreAnySquadronsInsideNebula:判断某SobGroup里是否有船在某Nebula里
SobGroup_AreAnySquadronsOutsideDustCloud:判断某SobGroup里是否有船在某DustCloud外
SobGroup_AreAnySquadronsOutsideNebula:判断某SobGroup里是否有船在某Nebula外
SobGroup_CanDoAbility:判断某SobGroup是否拥有某项能力
SobGroup_IsDoingAbility:判断某SobGroup是否正在使用某种能力
SobGroup_Empty:判断某SobGroup是否为空
SobGroup_GroupInGroup:判断某SobGroup是否含有某SobGroup的船
SobGroup_HasFiredAtSobGroup:判断某SobGroup是否用某武器攻击了某SobGroup
SobGroup_HasUpgrade:判断某SobGroup是否进行了某种升级
SobGroup_IsBuilding:判断某SobGroup是否正在建造某种东西
SobGroup_IsCloaked:判断某SobGroup是否正在隐形
SobGroup_IsDocked:判断某SobGroup是否正在停泊
SobGroup_IsDockedSobGroup:判断某SobGroup是否正在某SobGroup里停泊
SobGroup_IsGateDeployed:判断某SobGroup里的超空间跳跃门是否已经安置完毕(已经移动一次)
SobGroup_IsGuardingSobGroup:判断某SobGroup是否正在为某SobGroup护航
SobGroup_IsInVolume:判断某SobGroup是否在某Volume里
SobGroup_IsShipNearPoint:判断某SobGroup是否靠近某处多远的距离
SobGroup_PlayerIsInSensorRange:判断某SobGroup是否在某玩家的感应器范围之内

其他:

SobGroup_LoadPersistantData
SobGroup_ParadeSobGroup
(与单人任务的舰队继承有关,以后再做详细介绍)

    呼……真多啊……以上涵盖了大部分与SobGroup有关的函式,也是我确切知道其功用的函式,可以看到,借助它们,我们已经可以进行相当多的操作了,到FunctionReferance里看看它们吧,至少要保证当你想要使用一个什么样的函式时能够找到它并正确使用。SobGroup的讲解就告一段落了,但是这远没有结束,以后我们会一直与它打交道,没办法,它实在是太重要了,OK,今天就到这里,我们下一节继续……

评分

参与人数 1声望 +1 收起 理由
ati800 + 1 我很赞同

查看全部评分

 楼主| 发表于 2010-12-14 21:24:19 | 显示全部楼层
本帖最后由 萨尤克教主 于 2010-12-16 22:52 编辑

1.2 初步建立自定义战役

嗨,大家好,我又回来了,希望上一次讲的东西你还没忘,并且看了一些家园2内置函式,对它们有了一定的了解。

目的:小试牛刀,掌握建立一个任务需要的基本步骤,熟练任务的创建,复习脚本的简单使用。

我们今天……哈,看题目就很给力!初步建立我们的自定义战役!
喔……没错,快开始吧!
很好……但是你想过要建立一个什么样的战役么?
哦,当然!先是大批的舰队!敌人也是!他们出现了!更加大量的舰队!我们要这样……这样……然后这样……!
……唔,快打住吧,以后你想怎样随你,不过,我们目前应该来点符合我们目前现状的东西,我们已经较为详尽地解说了原版第一关的第一个事件及其相关的规则建立,我想我们现在应该有能力做一个类似的。有的教程里是建一队侦察机(切……),如果把上一讲的东西拿来做任务,那就是采集足够的资源(切!),我们来点不一样的……研究一项科技怎么样?(= =……撇他!)
……呼!不管怎么样,我已经决定了!我们马上就这么做!
哎……哎……别走啊喂……别想着一口吞个胖子,我们总要一点一点来的对吧?……很好,那么,我们继续……
我们先把脚本的建立放到最后,先思考一下,如何让游戏运行我们的单人任务,而不是运行那个已经被我讲烂了的坦尼斯呢?最简单的思路是把m01_tanis.lua和m01_tanis.level里的内容调包,换成我们自己的,不过,这样做的局限性太大,何况如果你想做第16关呢?一定有东西决定了整个战役的结构,以及从哪里读取战役的地图和脚本等。让我们退回到campaign目录,我们看到,除了ascension文件夹外,还有一个tutorial文件夹,以及两个与这两个文件夹同名的两个campaign文件。显然,那个叫tutorial的应该是教学那三关的任务信息。我们倒也可以把我们的任务放在那里面,就像FX的“伊甸”那样。不过,放在哪里并没有本质上的不同,所以我们打开ascension.campaign文件,看一看这东西是干嘛的。
之前的几篇相关内容的教程对此都有说明,我在这里更详细地说一下:
displayName = "$5500"
这个是任务选择列表里那个不起眼的“进度”二字。= =
Mission = {}
初始化任务结构。
Mission[1] =
以下将定义第一关的基本信息:
{
        postload = function()       
                playAnimaticNis("data:animatics/A00.lua", "nis/NIS01A")
        end,
任务一开始从"data:animatics/A00.lua"这个文件中找到动画信息并播放, 然后播放"nis/NIS01A"这个NIS。
        directory = "M01_Tanis",
        level = "M01_Tanis.level",
哈哈,这里定义了游戏从哪里读取任务的基本信息以及地图,如果我们把这里改了,在campaign文件夹里就可以命名我们自己的任务了!
        postlevel = function(bWin)       
                if (bWin == 1) then       
                        playAnimatic("data:animatics/A01.lua", 1, 0)
                else
                        postLevelComplete()
                end
        end,
这个结构……你可以跟后面比对一下,在这里出现的还会有另一种结构:
        postlevel = function()       
                postLevelComplete()
        end,
经过我的推测,如果你想在任务结束时播放动画,然后读条,开始下一关任务,则采用前一种结构,如果想先读条,则采用后一种,然后在下一关一开始定义播放的动画。如果我的推测不对,请告诉我- -U
        displayName = "$4000",
        description = "$4001",
}
这两行是任务选择列表里显示的任务名称及简略说明文字,我们终于看到改它们的地方了。
==========================================================================
没了,对一个关卡的定义就是这些……还是蛮简单的对吧?那么……接下来该开始我们的工作了,首先,在你的家园2data文件夹里创建:leveldata\campaign目录,在里面建立一个ascension.campaign文件及一个名叫ascension的文件夹,在ascension文件夹里建立一个文件夹……叫什么好呢……为了图简单,取名“m01”,这个名字你可以取别的,不过建议前三个字符最好是“m(M)XX”的形式,否则游戏可能不认(= =U,这似乎是真的……很诡异……),在m01里建立地图及脚本,名字应该和所在文件夹名字一样,所以是:m01.level和m01.lua。
等等……我记得坦尼斯里有五个文件,为什么我们只弄两个?
哈……不错……记忆力很好……我要说……另外三个文件都是小事,如果你看看原版其他战役的文件夹会发现除此之外还有更多前所未见的东西……我们会在以后讲到它们,目前,只要这两个最重要的文件就够了。
打开刚才建立的ascension.campaign文件,按照我们刚才剖析的结构参照原来的Mission[1]稍作修改,建立我们的第一关:
displayName = "$5500"
Mission = {}

Mission[1] =
{
        directory = "M01",
        level = "M01.level",
        postlevel = function()       
                postLevelComplete()
        end,
        displayName = "$4000",
        description = "$4001",
}
呼……只有这么一点?
对,没错,因为我们暂时不需要它播放任何动画,更何况只有一关。你会看到:
        directory = "M01",
        level = "M01.level",
这样,在游戏里点击单人游戏时就会加载我们建立的m01那个任务了。好了,让我们去搞定任务的主体:地图,和脚本吧。
打开m01.level文件,输入以下内容:
maxPlayers = 1

player = {}

player[0] = {
        name = "",
        id = 0,
        raceID = 1,
        resources = 1000,
        startPos = 0,
}

function DetermChunk()
        addSquadron("hgn_mothership", "hgn_mothership", {0, 0, 0}, 0, {0, 0, 0}, 0, 0)
        setWorldBoundsInner({0, 0, 0}, {30000, 30000, 30000})
end

function NonDetermChunk()
  fogSetActive(0)
  setGlareIntensity(0)
  setLevelShadowColour(0, 0, 0, 1)
  loadBackground("m03")
  setSensorsManagerCameraDistances(5000, 35000)
  setDefaultMusic("Data:sound/music/Ambient/amb_03")
end
由于我会在后面讲解地图里的东西,更何况之前已经提供给你们一篇很好的地图说明的教程了,所以在这里暂且不做过多解释,只要知道,这地图里有一艘属于玩家的希格拉之耀,玩家有1000RU,这就够了。

保存,退出,进入m01.lua,我们上一讲已经详细地解说过任务脚本里最基本得一些东西了,现在应该到了实战演练的时候了。我不会把整段文字直接给你,因为我希望你真正理解它们每一行的作用。
首先想一下我们的任务是个什么样子:
让母舰研究一项科技……呃……就增强感应器吧,这个不错……= =
所以,我们要设置一个让玩家研究增强感应器科技的目标。
然后要有初始事件,由指挥官下达这一目标。
在规则部分,我们要检查玩家是否研究了这一科技。
好了,就分析到这里,让我们开始吧:
dofilepath("data:scripts/SCAR/SCAR_Util.lua")
引用外部文件,这一行你应该还记得,接下来定义目标:
obj_prim_research = "研究 增强感应器 科技"
obj_prim_research_id = 0
嗯……我们在最初定义了我们任务中的目标,它在脚本中的名字叫做“obj_prim_research”,在游戏里将是“研究 增强感应器 科技”,让我们继续:
Events = {}
初始化事件表。一定要有这一行,否则游戏不会认为你的脚本里有任何事件。接下来就是我们的初始事件了:
Events.intelevent_start =
事件的名字是“intelevent_start”,定义的时候别忘了前面要加“Events.”。
    {
        {
            { "Universe_EnableSkip(1)", "", },
            { "Sound_SetMuteActor('Fleet')", "", },
            { "Sound_EnableAllSpeech( 1 )", "", },
            { "Sound_EnterIntelEvent()", "", }, HW2_Letterbox(1), },
        { HW2_Wait(2), },
        { HW2_SubTitleEvent(Actor_FleetCommand, "欢迎试验DT教程测试任务", 4), },
        { HW2_Wait(1), },
        { HW2_SubTitleEvent(Actor_FleetCommand, "现在请研究 增强感应器 科技", 5), },
        { HW2_Wait(1), },
        {
            { "obj_prim_research_id = Objective_Add( obj_prim_research, OT_Primary )", "", },
            { "Objective_AddDescription( obj_prim_research_id, '研究 增强感应器 科技')", "", }, },
        {
            { "Sound_ExitIntelEvent()", "", },
            { "Sound_SetMuteActor('')", "", }, HW2_Letterbox(0), HW2_Wait(2),
            { "Universe_EnableSkip(0)", "", },
        },
    }
实在简单得不得了,比上一讲我们分析的第一关初始事件简单多了。指挥官会说两句话,然后下达目标。你可以看一看,里面应该不会有你看不懂的东西了接下来是functions的部分了:
function OnInit()
    Rule_Add("Rule_Init")
end
在游戏开始的地方,我们加载了规则“Rule_Init”,然后我们自然要把它定义如下:
function Rule_Init()
让我们想一想接下来应该有什么内容呢?我们应该加载一个判断玩家是否研究了增强感应器科技的规则,但要知道,一开始母舰什么都没有,想要让玩家能够研究,它就必须要有一个研究模块。因此我们在任务一开始给母舰贴一帖“研究模块”牌膏药:
    SobGroup_Create("hgn_mothership")
    Player_FillShipsByType("hgn_mothership", 0, "hgn_mothership")
    SobGroup_CreateSubSystem("hgn_mothership", "Hgn_MS_Module_Research")
这三行如果你现在看不懂的话也没关系,我们以后会重点讲SobGroup的基本概念及与之相关的一些函式,到时候你就会了解了,现在我并不要求你弄懂它们,我们继续编写脚本:
    Event_Start("intelevent_start")
    Rule_Add("Rule_PlayerWins")
    Rule_Remove("Rule_Init")
end
这里就很简单了,开始之前我们定义好的初始事件,加载判断玩家是否研究了增强感应器科技的规则,移除Rule_Init,现在只剩Rule_PlayerWins了,让我们开始吧:
function Rule_PlayerWins()
呃……接下来该怎么写呢?如果有一个能够在玩家研究完增强感应器科技后返回true的函式就好了 ……
看吧……你已经懂得如何思考使用什么样的函式了,只要你掌握、了解足够多的函式,又有什么是你干不了的呢?我可以告诉你,检查玩家是否研究了某项科技的函式是“Player_HasResearch(<iPlayerIndex>, <sResearch>)”。<iPlayerIndex>相当于玩家的编号,在一个任务中,每个玩家的编号都是不同的,每个电脑对手或是盟友也有编号,而你的编号将始终是0。<sResearch>是科技名,当然不会是“增强感应器”了……你不会认为是“增强感应器”的对吧?它应该与H族科技列表里的名字相同,也就是“ScoutPingAbility”,如果你接触过建造研究列表想必这东西难不倒你,没接触过也不要紧,你可以在H族研究列表中找到它,相应的文件是“scripts\buildandresearch\hiigaran\research.lua”。所以,我们的if条件式应该是这个样子:
    if  Player_HasResearch(0, "ScoutPingAbility") == 1 then
然后呢?如果玩家研究了增强感应器科技……那么,任务就该结束了,我们应该把之前下达的目标设为完成,然后设置任务完成。这需要setMissionComplete函式,在后面的括号里写“1”代表任务完成,如果这不是你的最后一个任务,那么游戏就会进入下一个任务,在这里,游戏则会直接退回到主菜单(= =)。那么就是下面这个样子:
        Objective_SetState(obj_prim_research_id, OS_Complete)
        setMissionComplete( 1 )
    end
end
好了!全部搞定!这是一个再简单不过的脚本了,但是,我们毕竟在这个领域迈出了第一步,日后,我们将会不断丰富、完善我们的任务,让它看上去确实是那么回事……
==========================================================================
sorry,我们今天的教程还没有完,你一定想去看看今天的这个小任务对吧?在桌面上建立快捷方式,目标栏后面加上“ -overrideBigFile”,进入,点击单人游戏!
……
哈哈,是不是跳出了?那说明之前你的任务选择列表里有多于一个的任务,游戏会记住这些信息,但这一次,它发现ascension.campaign里只有一个任务,百思不得其解的它罢工了!
不要紧,再次进入游戏,我们新建一个玩家档案,这回游戏会认为你没有玩过任何任务,所以点击单人游戏,就可以玩了!

今天的教学就到这里,祝大家玩得愉快……(- -U汗!……怎么可能,研究一项科技而已嘛……)

1.2 end……
 楼主| 发表于 2010-12-16 22:49:08 | 显示全部楼层
1.3 有关任务的其他杂项修改

目的:学会本土语言化的使用,更改任务相关的预览图、名称、预览文字、载入图、头像、舰队颜色、背景音乐等杂项内容,了解游戏语音、动画的工作方式。

唔……怎么样?各位?上次的增强感应器科技研究的如何?……呃……怎么一个个眼神这么不友好?好吧……我想说……上次的任务应该是有史以来最蛋疼的单人任务了……没有像样的关卡名,预览文字和图片,也没有自己独有的加载图片,过场动画,舰队颜色,语音,背景音乐和头像,而且最重要的,如果你不懂汉语,那么很抱歉,里面的对白只有中文……(不懂汉语能看这么多?!= =U)
今天,我就会教你如何修改这些东西。其实很简单,只是项目比较多,比较杂,所以我放到这里统一讲一下。如果你有一定的游戏修改经验,那么这些东西几乎不需要我来讲解,你就能够修改,只要知道对应的文件都在哪里,而且……不包括本土语言化。
所以我们先来谈一谈本土语言化,然后,不愿意听的就可以离开了……= =U
本土语言化的目的很显然,让你只需要一套已翻译成你母语的文件,游戏里的内容就可以显示为你能看懂的语言。如果使用上一讲中脚本里的对白那种形式,无论如何,它也只能显示那些汉字,游戏怎么可能给你翻译呢,一切都要你自己来。我要说的是,就算你没打算再翻译一套其他语言的文件,我也建议你弄成本土语言化的形式,这样能够规范一些……
说了这么多,那么那些本土语言化文件到底在哪里呢?我们用MODpacker打开English.big文件,里面就是所有的本土语言化文件了。(我用的是被人骂的游侠汉化,由于至今未出任何问题,所以还在用,不知其他汉化版本情况是否一样= =U,散装汉化,data文件夹里应该有一个locale\english目录吧,本土语言化的文件都在那里面。)
我们随便打开一个,就会发现,每一行开头都是一串数字,后面是文字内容,我想你应该猜到了,之前见过的那些“$XXXX”的形式,都对应于这里!没错,在那些文件中,比如ascension.campaign里面那个$5500,游戏会在本土语言化文件中找到开头为5500的那一行,并用其后的内容来代替“$5500”,于是就显示为“进度”。这是中文的本土语言化文件,自然,英文的,后面的内容就是英语,法文的,那就是法语……
那么……它们为什么被分为了这么多文件呢?这个别来问我,游戏就设计成了这个样子……由此,我需要说明一下在本土语言化的过程中的一些注意事项。
首先,每个文件都有自己的字符串范围,不能重复,而且这个范围不是间断的,而是连续的。比如这个文件有1000和2000,那么它也就占据了从1000到2000的所有字符,其他的文件就不能够使用这一范围的字符,而不光只是不能使用1000和2000。
这些文件有很多里面并没有多少内容,因此它们对应于游戏中的哪些内容是一目了然的,我说一下那些内容比较多的,以及那三个文件夹里的东西都覆盖了游戏中的哪些文字。
engine.ucs:基本覆盖了对话框中的文字,进去一看便知。
ui.ucs:覆盖了几乎所有操作界面中用到的文字,涵盖量非常之大,足足占据了3000多个字符,虽然里面有大量空档。
ships.ucs:覆盖了所有舰船及子系统的名字和功能说明,也就是点击它们时在屏幕下方显示的文字。
buildandresearch.ucs:覆盖了所有建造及研究列表里的文字,主要是项目名及其说明文字。
leveldesc.ucs:里面是多人任务的地图名称及原版单人任务的自动存档名。
leveldata:里面是所有任务及过场动画中涉及到的文字,主要是对白,也有诸如目标名称及描述之类的,在里面还能看到不少当年游戏制作时被砍掉的情节(话说我当年订正《家园2全剧情对白》时咋不知道这一说呢……唉……)
animatics:这里面的文件有些特殊,它们并不是标准的本土语言化文件,它们的作用主要是定义过场动画播放的哪些时刻出现什么对白及其相应的语音。
scripts:里面只有一个文件,而且相信我,你不会用到它的……
本土语言化有ucs和dat两种形式,我们现在看到的就是ucs,这实际上并不好,在做MOD时会受到一定的约束,因为ucs并没有定义字符串的范围,这些似乎是原版游戏默认的。比如leveldata\campaign\ascension里的本土语言化文件:m01_tanis.ucs,它的字符串MS默认为40500-40999,占了500个字符,如果我们删掉其中一部分,让第三关的本土语言化文件覆盖这些字符,则这些字符会失效。这意味着第三关的字符必须是从41000开始(原版这里出现了错乱,第二关本土语言化文件占用字符范围是40000-40499)。这很浪费,因为一个任务用到的信息量远远不足500条之多,100左右就很多了。那么,如何才能节约字符串的使用呢?目前我看到的办法是把它们由ucs格式变为dat格式。具体做法是:在编写本土语言化文件的开头,确定其占用的字符范围:
filerange XXXX XXXX
rangestart XXXX XXXX
(XXXX是此文件占用的字符范围,如1000-2000,则是:
filerange 1000 2000
rangestart 1000 2000)
然后中间同ucs是一样的,最后加上:
rangeend
然后另存为ANSI编码,并替换原文件,扩展名改为dat,这样,每个本土语言化文件的范围都能够被精确定义,你甚至能做到一个字符串都不浪费的程度。
好了,现在你应该知道如何将你的任务本土语言化了,将那些汉字替换成$XXXX这样的字符串,然后建立相应的本土语言化文件,就OK了。
现在我们可以去更改任务名及其预览文字了!打开ui.ucs,我们看到,从4000开始,是原版游戏的相关内容。
……哈……又是坦尼斯……让我改了它!
喂喂喂!着什么急!往下看看,你会发现……哈,从4030开始出现了空档……后面直接是……4500!喔!看来游戏制作者给MODer们以很大的发挥空间呢……所以,不要去破坏原版的本土语言化内容了,我们建立我们自己的。
在4029这一行后面另起一行,输入:
4030  ……
4031  ……
后面的内容随便你怎么写吧……写好后进入上一讲当中建立的ascension.campaign文件,将4000和4001改为4030和4031,任务名及预览文字就这样改好了,哈,多么简单!哦,对了,你的本土语言化文件应该还没有进入散装的正确位置,正确位置如下:data\locale\english,把更改的文件放进去吧。
游戏对白的本土语言化原理是一样的,不过我们最好用原版的方式,在data\locale\english目录下建立leveldata\campaign\ascension目录,然后创建m01.dat,按上述方法输入正确的内容(参照原版,我们就从40500开始吧,很保险不是么……),然后进入上一讲中的任务脚本——m01.lua,将里面的汉字改掉。……不过,你还需要一个文件,那就是datfiles.lua,它需要被创建在我们的第一关任务文件夹中,与地图和脚本在一起。它的作用是告知游戏从何处读取本土语言化文件,内容如下:
Dictionaries =
    {
    {
        name = "locale:leveldata/campaign/ascension/m01.dat", },
    }
那个大括号里包住的正是我们之前创建的本土语言化文件所在路径。
好了,本土语言化的相关内容基本结束了,接下来都属于任务周边的修改了。
==========================================================================
我们又进入了工具下载时间!想轻松搞定接下来的修改吗?你将会需要以下工具:
ROTTool 2.5.0.0、DDS Converter 2和Relic Audio Encoder
详情请见本讲最后!
==========================================================================
任务预览图:这个东西……相关文件在“Data\ui\mapthumbnails\campaign\ascension”这个目录下,如果你有ACDSEE或是Photoshop之类的,就可以拿一张tga格式的图片放在这里作为预览图了,图片必须与任务名称相同,以我们的教程为例,应是m01.tga,现在我们随便打开一张原版的预览图……
(注:这里文件将很可能是rot格式的,请下载ROTTool查看图片)
呃……怎么是这个样子?
没错,很诡异,哈?图像是512*512的,可是在任务列表中,这并不会被全部显示,只会显示位于左上角484*388这个区域内的图像,所以图像会是我们现在看到的样子。我想说的是,更让人感到蛋疼的是,如果使用你自己的图片,游戏还会将图片上下颠倒,因此我们在使用之前还需要翻转一下!(= =U……这个……可能跟格式有关吧……还请其他大大不吝赐教……)
基于此种诡异状况,我目前使用的预览图是这个样子的:512*512,左下角484*388的区域有图像,其余地方空白,而且图像还是上下颠倒的……= =

载入图:这东西也跟任务的地图、脚本放在一起。原版图像大小是1024*512……你会发下它也不会全部显示,而且……它也是上下颠倒的!……蛋疼指数和我的教程有一拼……= =,总之弄一个你喜欢的图,上下颠倒,保存为tga格式跟任务信息放在一起吧,图像大小可以自己摸索,想换的话跟UI有关了,恕我无能为力……= =

舰队颜色:同样跟任务的地图、脚本放在一起,一个名叫teamcolour.lua的文件,里面会定义任务中每一个玩家的舰队颜色。以下是原版第一关的teamcolour.lua:
teamcolours =
{
        [0] =
        {
                {0.365,0.553,0.667,},
                {0.8,0.8,0.8,},"DATA:Badges/Hiigaran.tga",
                {0.365,0.553,0.667,},"data:/effect/trails/hgn_trail_clr.tga",
        },
        [1] =
        {
                {1,0.494,0,},
                {1,0.494,0,},"DATA:Badges/Hiigaran.tga",
                {0.365,0.553,0.667,},"data:/effect/trails/hgn_trail_clr.tga",
        },
        [2] =
        {
                {0.752,0.694,0.556,},
                {1,1,1,},"DATA:Badges/Hiigaran.tga",
                {0.365,0.553,0.667,},"data:/effect/trails/hgn_trail_clr.tga",
        },
        [3] =
        {
                {0.9,0.9,0.9,},
                {0.1,0.1,0.1,},"DATA:Badges/Vaygr.tga",
                {0.921,0.75,0.419,},"data:/effect/trails/vgr_trail_clr.tga",
        },
}
如果我没猜错,这东西定义了玩家的舰队基色、条纹颜色、舰徽,应该还有尾焰颜色什么的……= =U
那些数字是RGB码,如果你习惯255的,应该可以用分式表达,将255作为分母。

头像:相关文件在“Data\ui\speechicons”这个目录下。目前我还不能新添头像,不过可以覆盖原来的。头像图片是128*64的rot文件(经测试,只有rot可用,tga不行,所以下载ROT TOOL吧),32位未压缩(不知其他形式能否可用)。想要新添头像,可以取名为原版的那些头像,在此列表如下:
fleetintel0.rot    脚本中用 Actor_FleetIntel 调用
fleetcommand0.rot  脚本中用Actor_FleetCommand调用
makaan0.rot        脚本中用   Actor_Makaan   调用
keeper0.rot        脚本中用   Actor_Keeper   调用
chimera0.rot       脚本中用   Actor_Chimera  调用
bentusi0.rot       脚本中用   Actor_Bentusi  调用
talorn0.rot        脚本中用   Actor_Talorn   调用
bishop0.rot        脚本中用   Actor_Bishop   调用
shipyard0.rot      脚本中用  Actor_Shipyard  调用
tanis0.rot         脚本中用    Actor_Tanis   调用
allships0.rot      脚本中用  Actor_Allships  调用

背景音乐:背景音乐在Music.big里,ambient文件夹用于存放地图背景音乐,battle用于存放战斗音乐,二者都可以在任务中被调用,在脚本的规则里使用Sound_MusicPlayType函式可以切换音乐,比如:
Sound_MusicPlayType("data:sound/music/ambient/AMB_03", MUS_Ambient)
则呼叫此函式后游戏会播放AMB_03.fda……
fda……fda……呃……没听说过啊……这格式……怎么改……
其实,这东西可不是我们任务独有的内容,所以我不打算介绍太多,扔给你一篇教程吧,应该写得够详尽的了:(http://blog.sina.com.cn/s/blog_583d36920100048k.html
如果还不明白,尽可以来问我……

接下来本讲的内容就只剩下游戏的动画和语音了,先说语音,这个东西都在EnglishSpeech.big里,其中与我们内容相关的是animatic和missions两个文件夹里的内容,它们分别存放了过场动画和任务的语音,看到里面的文件是如何命名的了吗?哈,跟本土语言化很像呢!这里我要说一点东西了,每当游戏在上面显示对白时,就会搜寻指定目录里的语音,任务用Sound_SpeechSubtitlePath函式指定语音目录,比如第一关的脚本中我们就看到了:
Sound_SpeechSubtitlePath("speech:missions/m_01/")
这样,每当游戏显示一个对白,比如其内容为$40530,那么,游戏就会在speech:missions/m_01/这个文件夹里找到40530这个语音文件并播放,这也正是为什么我让你将你的任务本土语言化的原因所在。我还要说明一点就是,一旦对白有相应的语音,其显示时间也由语音播放时间决定,什么时候语音播放完了,那么对白就会消失,否则它才会根据你定义的秒数来显示,所以在做好你的语音之前不要在任务中呼叫Sound_SpeechSubtitlePath函式(话说……这东西真的有人做么= =U)。

然后我们来说动画,在上一讲中对ascension.campaign文件的说明时我们已经知道了游戏在哪里读取动画信息了,是“data:animatics”文件夹里的lua文件,所以,我们去看一下吧。
这里以data:animatics/A01.lua文件为例,打开它,我们看到:
MovieScreen =
{
    displayName = "$4606",
    helpTip = "$4607",
这里是【动画重放】里显示的动画名称
    size =
        { 0, 0, 800, 600, },
设置屏幕大小
    stylesheet = "HW2StyleSheet",
    RootElementSettings =
    {
        backgroundColor =
            { 0, 0, 0, 255, }, },
背景颜色设置
    speechFilename = "locale:animatics/A01_speech.lua",
;
对白及语音信息读取位置
{
    type = "Movie",
    filenameV = "data:animatics/animatic_01-02.avi",
播放的视频文件,我们可以在这里修改以播放我们自己的视频
    filenameA = "data:Sound/Music/ANIMATIC/A01_02",
播放的音频文件,我们可以在这里修改以播放我们自己的音乐
    position =
        { 0, 100, },
    size =
        { 800, 400, },
    name = "MyMovie", },
}
播放的位置及大小

这些应该还是不难看懂的对吧?我们趁着本土语言化和游戏语音刚讲完,去看看那个locale:animatics/A01_speech.lua
这个在之前提到过,我们打开它:
SetSpeechFolder("Data:Sound/Speech/Animatic/ANI_01");
指定语音读取目录,所用的函式与任务脚本中指定语音目录的函式不同,请注意。
AddLocalizedText("locale:leveldata/campaign/ascension/ANI_01-02_M01_Tanis.dat");
指定本土语言化文件。
AddSubtitleEvent(6.26, Actor_LocationCard, "$55020", 0);
AddSubtitleEvent(10.02, Actor_LocationCard, "$55021", 0);
AddSubtitleEvent(18, Actor_LocationCard, "$55022", 0);
AddSubtitleEvent(26.14, Actor_LocationCard, "$55023", 0);
AddSubtitleEvent(32.04, Actor_LocationCard, "$55024", 0);
AddSubtitleEvent(42.23, Actor_LocationCard, "$55025", 0);
这就是动画中的对白了,定义了动画播放多少秒后出现什么对白,而且由于有语音的存在,它们无需定义显示时间,全都是0。Actor_LocationCard也是一种头像的呼叫,只不过它的意思是……没有头像。

音乐和语音很好改,只要做好后按我给的教程所说转为fda格式放到相应位置并正确呼叫就可以了,但你很可能会发现,你所制作的avi格式动画在播放时会跳出,这是因为家园2只认DXGM编码(其实本质上应该就是DIVX编码MS……但游戏不会认DIVX的……)的avi视频,修改它还是比较简单的,如果你的avi文件是DIVX或XVID编码,那么用Uedit32这样的编辑工具将之打开,你会看到两个DIVX或是XVID,把它们改成DXGM应该就没问题了,经本人亲测是好使的,如果不行请及时与我联系。你可能会需要一款视频压缩之类的工具,在这里我推荐:WisMencoder,经其压缩后的XVID编码avi视频经修改后可以在游戏中播放无误。

OK,本讲到此结束,希望你已经会用本讲所学知识做一个有模有样的的任务了!在接下来的教程中,我们将会掌握更多有关任务脚本的技巧,毕竟这才是一个单人任务的核心所在,也只有它能让我们的任务越来越复杂而有趣!第一章到此结束……

ROTTool 2.5.0.0.zip (23.69 KB, 下载次数: 709)
DDS Converter 2.part1.rar (390.63 KB, 下载次数: 749)
DDS Converter 2.part2.rar (390.63 KB, 下载次数: 772)
DDS Converter 2.part3.rar (31.92 KB, 下载次数: 784)
Relic Audio Encoder.zip (62.43 KB, 下载次数: 728)
发表于 2012-12-6 13:05:00 | 显示全部楼层
挖坟,{:soso_e149:}不过没看懂
您需要登录后才可以回帖 登录 | 加入帝国

本版积分规则

小黑屋|手机版|Archiver|泰坦帝国

GMT+8, 2023-5-30 23:34 , Processed in 0.128006 second(s), 9 queries , Gzip On, Redis On.

Powered by Discuz! X3.4 Designed by 999test

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表