本帖最后由 Oven_N 于 2012-6-5 18:15 编辑
侠盗猎车手:圣安地列斯
编程圣经
Sanny Builder脚本编写教程
翻译 / Translator:Oven_N,From GTA虚拟世界
未经许可 谢绝转载
第一部分:一切的开始
前言:
大家好,一直以来都我希望能够编写一部适合新手的教程,虽然它不会告诉你你应该做什么,但是它会帮助你达成你的目的。并且它同时将是一个SannyBuilder的教程。我会尝试讲得尽量简单些,方便那些没有任何编程知识的人理解。如果你认为你已经掌握了编写脚本的基本技巧,可以跳过这一章直接阅读第二部分。
(我建议你把帖子打印出来方便阅读,打印GTAForums帖子的方法是Blahblahblah……略)
你可以使用任务脚本编写技巧来创建自己的任务、MOD(本文内的MOD专指SCM代码MOD,下同),或者直接修改现有的MOD(你可以在 MissionMods和 MissionCoding这两个GTAForums的帖子里找到现成的,有问题也可以在那里提出)。
任务代码保存在位于SA主文件夹\data\script里的main.scm和script.img这两个文件中。Main.scm是已经编译过的,所以它里面保存的都是方便计算机理解的机器代码。如果用16进制编辑器打开main.scm文件就会像下面这样:
- D6 00 04 00 19 01 02 45 0E 4D
- 00 01 FE 3D 87 02 A6 00 02 45 0E
- …
复制代码 英语专业八级也看不懂是吧?正因如此,你需要反编译这些代码,将它们变为人类可以阅读的格式,这样我们就能进行编程了。不用去管script.img文件,它是编译时自动生成的,每个不同的main.scm文件都有一个对应的script.img。
要反编译main.scm文件,你需要使用SannyBuilder。当然也有其他的反编译器,不过SannyBuilder是初学者的最佳伙伴,因为它有不断的更新及支持、运行速度快的优点,还带有颜色(=w =)!!!而且这部教程用的就是SannyBuilder。
SannyBuilder跟Barton写的Waterduck'sSan Andreas Mission builder比较相像,不过语法有差异。我还是强烈推荐使用SannyBuilder,不过如果你对它有兴趣,那么这里就是下载地址:Barton'sWaterduck Mission Builder ,Craig写的版本,膜拜他吧。
还有一点很重要,SannyBuilder的帮助文档很丰富,里面有各种有用的信息,不要错过。
像一般软件那样安装并打开SannyBuilder,然后选择Tools菜单下的options项(或者按热键“F10”)。在general选项卡中把Show progress、Showreport、Showwarning、Conditioncheck、Rangescheck、Writes opcodes、Insert Original mission names、Addextra info to SCM这些复选框全都勾选上(Quickgame loading和Replacemission numbers可根据需要勾选)。稍后我会解释这样做的原因。
像这样:
重启Sanny Builder,你就可以开始了。
(这一段介绍其它的教程,虚拟世界的教程区已经有汇总贴,咱就不翻了)
1.Main.scm的结构
首先将原版的main.scm和script.img这两个文件复制到新的专门用于存放代码的文件夹中(或者其它任何你想要放的地方,切记要备份原文件!否则一不小心改坏的话后果你知道的……),然后用SannyBuilder打开main.scm,会出现一个进度条显示反编译的进度(这就是要在设置里勾选“Showprogress”的原因)。
—定义部分—
开头你会看到很多行这样子的代码: - // This file was decompiled using sascm.ini published by Seemann (http://sannybuilder.com/files/SASCM.rar) on 13.10.2007
- DEFINE OBJECTS 389
- DEFINE OBJECT SANNY BUILDER 3.03
- DEFINE OBJECT INFO // Object number -1
- DEFINE OBJECT KEYCARD // Object number -2
- DEFINE OBJECT AD_FLATDOOR // Object number -3
- DEFINE OBJECT KB_BANDIT_U // Object number -4
- ……
复制代码如你所见,这些代码用于定义各种将要用到的物体,共有389个,这个总数也是在开头做了定义(“DEFINEOBJECTS 389”这行)。想要添加物体的话在这一部分末尾参照相应的格式自己写一行即可。(虽然作者没说,但个人认为“DEFINEOBJECTS 389”这行可能也要改一下。)
往下找到: - DEFINE MISSIONS 135
- DEFINE MISSION 0 AT @INITIAL
- DEFINE MISSION 1 AT @INITIL2
- DEFINE MISSION 2 AT @INTRO
- DEFINE MISSION 3 AT @NONE
- ……
复制代码这里是定义任务的代码,共有135个任务(第一行的“DEFINEMISSIONS 135”)。如果你用的是Xbox、Playstation2的游戏文件,或者是装了 jarjar2-player双人游戏补丁的PC版,你会发现任务数比上面提的更多,因为多出来的那些都是双人合作任务,与主线没多大关系……
- DEFINE EXTERNAL_SCRIPTS 78 // Use -1 in order not to compile AAA script
- DEFINE SCRIPT PLAYER_PARACHUTE AT @PLCHUTE // 0
- DEFINE SCRIPT PARACHUTE AT @PARACH // 1
- DEFINE SCRIPT BCESAR2 AT @BCESAR2 // 2
- DEFINE SCRIPT BCESAR3 AT @COKEC // 3
- DEFINE SCRIPT SLOT_MACHINE AT @BANDIT // 4
- ……
复制代码这一部分定义一些附加代码,在游戏需要时会被触发,如你所见有78个附加代码段。这些代码有些用于处理诸如降落伞动作(玩家的“PLAYER_PARACHUTE”和NPC的“PARACHUTE”),赌场的小游戏,各个商店,以及其它一些乱七八糟的东西,详细的稍后再讲。
然后你会看到这两行: - DEFINE
- UNKNOWN_EMPTY_SEGMENT 0
- DEFINE UNKNOWN_THREADS_MEMORY 574
复制代码 它们的用处尚未明了,因此目前不要改它们,我们也用不到这些。
—主代码段—
以上两行之后你将看到如下的代码: - {$VERSION 3.1.0027}
- //-------------MAIN---------------
- 03A4: name_thread 'MAIN'
- 016A: fade 0 time 0
- 042C: set_total_missions_to 147
- 030D: set_max_progress 187
- 0997: set_total_respect_points_to 1339
- 01F0: set_max_wanted_level_to 6
- 0111: set_wasted_busted_check_to 0
- 00C0: set_current_time_hours_to 8 current_time_minutes_to 0
- 04E4: unknown_refresh_game_renderer_at 2488.562 -1666.864
- 03CB: set_rendering_origin_at 2488.562 -1666.864 13.3757
- 062A: change_stat 165 to 800.0 // floating-point values
- 062A: change_stat 23 to 50.0 // floating-point values
- 062A: change_stat 21 to 200.0 // floating-point values
- 062A: change_stat 160 to 0.0 // floating-point values
- 0629: change_stat 181 to 4 // integer values
- 0629: change_stat 68 to 0 // integer values
- 0053: $PLAYER_CHAR = create_player #NULL at 2488.562 -1666.864 12.8757
- ……
复制代码 到下面这一段前为止(用“查找”工具检索“mission 0”):
- //-------------Mission 0---------------
- // Originally: Initial 1
- :INITIAL
- 03A4: name_thread
- 'INITIAL'
- 06C8: enable_riot 0
- 0004: $1515 = 0
- 0005: $1500 = 5.0
- ……
复制代码 中间这一部分叫主要部分/主代码段(或者也可以叫它主线程——虽然这叫法不准确)。这一段里有大量小段的功能代码,如创造地上的任务圆圈,处理“打电话”的动作,触发女友系统等,同时也是各种MOD添加它们代码的地方(CLEO的原理即为将外部文件内的代码嵌入该部分并执行)。主代码段由各种较小的线程组合而成,由游戏主线程触发及处理。主线程(游戏运行期间有且仅有一个主线程)又由游戏引擎创建,你不一定需要使用创建新线程的opcode“004F”(稍后会讲这是什么)来创建一个给自己用。之后我会专门讲一下这部分。
在主代码段你能使用局部变量(0@,1@, 2@,……)最多可到31@。32@和33@是局部计时器变量(稍后会讲)。33@往后的变量在主代码段用不了。如果你使用了过多的变量,SannyBuilder在编译时会出错,然后告诉你本地变量数目超限:“thelocal var. is out of range”(这就是在设置时勾选“Rangecheck”的原因)。你也可以使用任意全局变量,但数目不能超过16383个,不过估计你也用不完。
接下来是任务代码段:
从以下开始:
- //-------------Mission 0---------------
- // Originally: Initial 1
- :INITIAL
- 03A4: name_thread 'INITIAL'
- 06C8: enable_riot 0
- 0004: $1515 = 0
- 0005: $1500 = 5.0
- ……
复制代码 这里是第一个任务(实际上它在新游戏第一个过场动画前几毫秒内就结束了,并不是真正的任务)。这个任务主要是在创建停车点、武器刷新点及其它一些东西。
然后是任务1(用“查找”来检索) - //-------------Mission1---------------
- // Originally: Initial 2
- :INITIL2
- 03A4: name_thread 'INITIL2'
- 0004: $3407 = 25
- 0004: $3408 = 100
- 0004: $3409 = 250
- ……
复制代码 跟“mission0”一样,它做一些变量的初始化,再创建一些停车点、门及传送点等。
任务2: - //-------------Mission 2---------------
- // Originally: Intro
- :INTRO
- 03A4: name_thread 'INTRO'
- 0050: gosub @INTRO_47
- 00D6: if
- 0112: wasted_or_busted // mission only
- 004D: jump_if_false @INTRO_38
- 0050: gosub @INTRO_9715
- ……
复制代码 这一部分从播放过场动画开始到进入CJ家门前的红圈为止。
之后就是游戏里的“街机”小游戏,还有跳舞,“隐藏任务”等,到“Bigsmoke”这里(任务11)就是开始主线剧情了。
直到任务134结束处为止。
如果要添加新任务,就写在任务134后面,然后到定义部分里写个定义代码(详细稍后)。
附加代码段(最后一部分)
最后一段任务代码下面就是所有的附加代码,第一个就是
- //-------------External script 0 (PLAYER_PARACHUTE)---------------
- :PLCHUTE
- 03A4: name_thread 'PLCHUTE'
- 0247: request_model #GUN_PARA
- :PLCHUTE_16
- ……
复制代码 以及
- //-------------External script 1 (PARACHUTE)---------------
- :PARACH
- 03A4: name_thread 'PARACH'
- 0004: $8275 = 0
- ……
复制代码 等等直到第77个。它们的用途从名字中可以看出。
主代码段目前是编写并存放MOD最主要的地方。
有关于main.scm的结构还有一些部分需要注意:它的某些数值有上限,具体可以看 这里。主代码段(开头到结束之间的一切)还有200,000字节的大小限制。SannyBuilder编译完成的报告中会告诉你这些信息。
这就是设置里勾选“Showreport”的原因。
2.简化版的SCM文件及Opcodes:
A.简化版SCM文件:
在正式开始编写代码前,你需要一个简单点的SCM文件(原版的实在是让人眼花缭乱),并且里面没有任何多余的代码。这样可以节省重新编译所用的时间,不用等过场动画,以及方便你编写更多其它的东西(详见刚才介绍的“SCM编译限制”)。
总之简化版SCM就是一个删除所有任务代码、附加代码以及主代码的main.scm文件。除了在游戏世界里创建玩家以外什么也不做。
你可以在安装SannyBuilder的文件夹的data\sa中找到名为“stripped.txt”的文本文件,直接打开的话你可能看不太懂,不过现在我暂时不会作详细讲解,你可以在帮助文件中自行了解一下。并不是说不负责什么的,只是在你了解完什么是“opcode”后很容易就能看懂这些了。
你看到的应该是像下面这样的代码: - {
- use macro (Ctrl+J) "headsa"
- to insert a file header
- }
- {$VERSION 3.0.0000}
- var
- $PLAYER_CHAR: Player
- end // var
- 03A4: name_thread 'MAIN'
- 01F0: set_max_wanted_level_to 6
- 0111: toggle_wasted_busted_check 0
- 00C0: set_current_time_hours_to 8 minutes_to 0
- 04E4: unknown_refresh_game_renderer_at 2488.56 -1666.84
- 03CB: set_rendering_origin_at 2488.56 -1666.84 13.38
- 0053: $PLAYER_CHAR = create_player #NULL at 2488.56 -1666.84 13.38
- 01F5: $PLAYER_ACTOR = create_player_actor $PLAYER_CHAR
- 07AF: $PLAYER_GROUP = player $PLAYER_CHAR group
- 0373: set_camera_directly_behind_player
- 01B6: set_weather 0
- 0001: wait 0 ms
- 087B: set_player $PLAYER_CHAR clothes_texture "PLAYER_FACE" model "HEAD" body_part 1
- 087B: set_player $PLAYER_CHAR clothes_texture "JEANSDENIM" model "JEANS" body_part 2
- 087B: set_player $PLAYER_CHAR clothes_texture "SNEAKERBINCBLK" model "SNEAKER" body_part 3
- 087B: set_player $PLAYER_CHAR clothes_texture "VEST" model "VEST" body_part 0
- 070D: rebuild_player $PLAYER_CHAR
- 01B4: toggle_player $PLAYER_CHAR can_move 1
- 016A: fade 1 time 0
- 04BB: select_interior 0
- 0629: change_integer_stat 181 to 4
- 016C: restart_if_wasted_at 2027.77 -1420.52 15.99 angle 137.0 town_number 0
- 016D: restart_if_busted_at 1550.68 -1675.49 14.51 angle 90.0 town_number 0
- 0180: set_on_mission_flag_to $ONMISSION // Note: your missions have to use the variable defined here
- 0004: $DEFAULT_WAIT_TIME = 250
- 03E6: remove_text_box
- // put your create_thread commands here
- :MAIN_LOOP
- 0001: wait $DEFAULT_WAIT_TIME ms
- 00BF: $TIME_HOURS = current_time_hours, $TIME_MINS = current_time_minutes
- 0002: jump @MAIN_LOOP
- //put your mods (threads) here
- //-------------Mission 0---------------
- // put your missions here
- //-------------External script 0---------------
- // put your external scripts here
复制代码 或许你会注意到里面没有用于定义的代码,不用担心,其实也没多少字是需要你打的。按照开头大括号中说的那样,按下组合键CTRL+J然后选择“headsa”,如下图:
然后SannyBuilder就会自动插入相应的代码,就像下面这样: - DEFINE MISSIONS 0
- //DEFINE MISSION {ID} 0 AT {LABEL} @
- DEFINE EXTERNAL_SCRIPTS 0 // Use -1 in order not to compile AAA script
- //DEFINE SCRIPT {NAME} AT {LABEL} @
- DEFINE UNKNOWN_EMPTY_SEGMENT 0
- DEFINE UNKNOWN_THREADS_MEMORY 0
复制代码现在你就可以定义自己的任务及附加代码了。
要添加MOD的话,你需要把创建线程的代码放到标有“//put your create_thread commands here"那行的下面,然后把线程的其它所有内容放到标有“//put your create_thread commands here" 那行的下面。
B.Opcodes:
以上大部分代码每行开头都有4位数字(16进制)后加一个英文冒号,如0001:,087B:, 或者016A:。这些叫“opcodes”!!!!而且也是我们要在设置中勾选“Writeopcodes”的原因。
Opcode是告诉游戏引擎应该执行那些命令的代码,大部分opcode都需要提供一些参数用于执行命令(只有极少部分不需要)。
参数(有时简写为“Params”,或P1、P2、%p)是各种执行命令时所需要的数据(如各种数值,某个NPC、车辆,一段文字等)。Sannybuilder窗口状态栏的左边会显示当前光标所在的opcode中需要多少个参数。
以下有两个例子:
简单的 - 000A:3@ += 1 // integer values
复制代码
000A:用于做整数加法的Opcode 3@和1:Opcode的两个参数
这是把局部变量(详细稍后)3@的值加1的命令,如果3@的值是1的话执行完后它就变成2了!!!
稍微复杂的 - 0605:actor $PLAYER_ACTOR perform_animation_sequence "ATM" from_file "PED" 4.0 loop 0 0 0 0 6000 ms
复制代码
这条命令让一个“演员”(游戏里的角色)$PLAYER_ACTOR做出“ATM”这个保存在“PEDS”文件里的动作,数值4.0表示动作的播放速度,loop 0表示不循环播放该动作,设为1则表示循环播放直到你让他干其它的事情为止(或销毁,“释放”——不再控制他,让他跟一般的行人一样自由活动,用01C2的Opcode),其它的参数先不讲,最后一个参数是控制该动作的时长,让游戏引擎在规定时间后停止该动作。
要想查询Opcode,你可以看: - Sanny Builder内置了全新的“Opcode search tool” Opcode搜索工具。在菜单栏“Tools” -> “IDE tools” -> “opcode search”里面。你想找的东西全都在这里。在Sanny Builder 的帮助文件中4. Opcode Search Tool里会教你如何复制/粘贴Opcode(选择即输入),如何缩减Opcode等等……这个工具使用由Sanny Builder自动创建的opcodes.txt文件,如果你修改了原版的sacm.ini文件,那么你就必须手工创建一个新的opcodes.txt
- 反编译过的原版main.scm文件(最可信的文档。有详细的说明,绝对有效——不然你怎么玩?)。感谢R☆公司创造了这个游戏!!!感谢Barton Waterduck,Sanny Builder的作者以及那些帮忙反编译scm文件的人们。
- http://sa-db.webtools4you.net/在线互动式数据库。不用在搜索栏里写任何东西,直接用鼠标点击“go”,就会列出所有的Opcode,然后用页面搜索功能查询。
但它们都应仅用于参考!!!
Opcode的说明保存在位于Sannybuilder安装文件夹中data\sa下名为“sascm.ini”的文件里。更新这个文件会改变Opcode的说明及参数设定(如顺序等)。
要应用新的sascm.ini文件,首先把所有你写的代码文件(scm或者cleo文件)全部编译完,关掉SannyBuilder,覆盖旧的sascm.ini(建议做个备份),最后反编译之前编写的代码。每次更换sascm.ini文件你都要这么做以避免无法使用旧的代码。
!!!!!!注意!!!!!!
不要过于频繁地更换sascm.ini文件,因为这不仅要将你写的代码全部编译后再反编译一次,你还得注意Opcode的参数设置,有时这会搞乱你的代码并造成游戏崩溃或Bug(而且通常这些错误都难以被发现) 同时你还要重新生成opcodes.txt文件以便你使用F1键的自动完成功能及Opcode搜索工具,在“Tools”菜单下的“Makeopcodes.txt”项会帮你做到这点。 我建议你只使用以上提到的那几个文件中固定的一个,或者用默认的,并且仅在必要的情况下才做更换。 这篇教程使用的是默认的sascm.ini文件。
3.参数与变量:
不同的参数有不同的类型: Integer 整型(缩写int),或者称为整数,比如1,-2,或321这样子的。 Floats 浮点型或叫浮点数,指带小数点的数字,如 1.2或 -2372.3845。 深入了解 Actor 演员,游戏里的人物,比如CJ(玩家人物)一般储存于全局变量$PLAYER_ACTOR中,Ryder(莱德尔)一般储存在$SA_RYDER中,或某个Ryder的任务里看门的便当脸士兵是储存于局部变量@102中的。这些与演员(或车辆)关联的变量叫做句柄,它们用于给不同的物体明名,方便你调用相应的数据。 Car 车辆(在SCM代码中任何载具——包括天上飞的,地上跑的,水里游的——都算作车辆)。比如$PLAYER_CAR保存着你经常开的车,车辆跟演员一样可以被保存在全局或局部变量中。 Model identifier 模型标识,如#CHEEAH,#ARMY,#PARACHUTE,用于标识演员、车辆及物体的3D模型。与gta3.img里的dff文件关联。当创建演员、车辆及物体时需要先读取相应的模型(载入内存)。 Short text strings 短字符串(长度为8字节),如'STRAP_4'和'MTIME3',主要用于游戏引擎读取和搜索对应名字的文件——或文件里对应名字的某一行。最多且一般都是7个字符(8字节为什么不能有8个字符?因为最后要有一个特殊的字符用来表示该字符串到此结束),主要与文件名、线程名、文字项目(gxt文件里的)、动画(ifp文件中的动画名),以及很多其它的东西。 Long text strings 长字符串(16字节或更长),如 ”ATM”, ”JEANSDENIM”。跟短字符串差不多,只是它可以处理更长的字符串(Sanny Builder的帮助文档里称最长可以有255个字符),以及更短的(小于7个字符)。有关于长短字符串的更多解释可以参考 这个。 Labels 标签,如MAIN_4059,用于0002: jump @;004D: jump_if_false @;0050: gosub @;及004F: create_thread @这些Opcode,让游戏引擎跳转至这些地方后继续执行代码,要创建标签就在名字前面加一个英文冒号。
例如:
- :label
- 0001: wait 0 ms
- 0002: jump @label
复制代码
变量是游戏引擎在内存中开辟的一段用于储存数据的空间。当你完成了变量的定义,游戏引擎会一直保存着这个变量,甚至是重新启动游戏时(保存游戏进度会保存一切变量,读取游戏时全部读取),直到你修改或清除它。比如当你创建了一个演员时与一个变量进行关联,以后就可以通过这个变量调用这个演员了(就像起了个名字)。你可以用变量保存以上除了标签以外的任意类型(字符串变量需要特定的格式,稍后会讲)。
变量有两大类: 全局变量:名字以$开头的字符串,例如:$ONMISSION,$PLAYER_ACTOR。定义之后可以在main.scm文件内的任何地方使用。比如你在主代码段定义了一个全局变量,在任务代码段你还可以调用并读取它的内容。 局部变量:以@结尾的整数,如1@,2@。每个进程都有它自己的一系列局部变量,比如进程A的局部变量1@与进程B的局部变量1@是不同的两个变量,它们互相独立。主代码段使用的局部变量数不能超过33@以上,但任务代码段可以使用的局部变量则多达1024@。
局部计时器变量:32@和33@是局部计时器变量,它们计算游戏中经过的时间,以毫秒作为单位(1秒=1000毫秒)。比如你将它们设为0,过10秒后它们就变成10000,设成1000的话5秒后它们就会变成6000。
长短字符串:
字符串需要使用特殊格式的变量来保存。在SannyBuilder中以红色高亮表示。共有4种格式的字符串变量,2个用于短字符串(8字节,'用单引号表示'),2个用于长的(16字节,”用双引号表示”)。
短字符串:以s$+变量名表示全局短字符串变量,如s$1169或s$ACTOR_SPEECH_GFX_REFERENCE。与其它全局变量一样可任意访问;以序号+@s表示局部短字符串变量,如170@s或5@s。只能由创建它的线程访问。
长字符串:以v$+变量名表示全局长字符串变量,如v$1225或v$MYLONGSTRING;以序号+@v表示局部长字符串变量,如28@v。
有一点要注意!像0@s跟0@会互相覆盖,因为它们都指的是同一个变量,只不过保存数据的格式不同而已。
同时还有,要记住在游戏引擎里整型和浮点型是不同类型的数据(1跟1.0是完全不兼容的!!!)。如果某个Opcode的参数需要浮点型而你给了它整型(或其它类型的),游戏会崩溃。 你可以使用Opcode 008D把整形转换成浮点型,或者用008C把浮点型转换成整形。
待续……
|