游戏狂人
 
- 贡献度
- 248
- 金元
- 7843
- 积分
- 1776
- 精华
- 0
- 注册时间
- 2007-2-8
|
字符串,数组,进而是用户自定义函数,obse团队为脚本打造了一个完整的支持平台,按理说脚本类MOD现在才真正到了发力的时候。
但现实往往就是这么让人嗟叹,当我们把所有的工具都准备好时,回首一望,却人去楼空,只留下前辈们夺目的艺术成果,和一个个离去的脚印。。。
基本没有更新的wiki,豪华璀璨但后继乏力的T网,论坛中已经神隐或即将神隐的高人。。。
这些残酷的现状都在告诉俺们,上古卷轴4的路已经快走完了,辉煌的顶点已经离我们远去,一切都将成为过眼烟云。。。
俺真的很扼腕,如此晚才接触这样的神作,错过了如此多的高人,错过了如此开放共享的团队。。。
不过俺仍然一直在努力,既是为下一次机会未雨绸缪,也是为有兴趣加入游戏modder行列的新人们降低一点点门槛。。。
俺知道能看完这样一大篇纯文字帖子的人不多,俺只是对从各位高人手里伸手拿东西的一点点小回报。。。
好了,废话说完,进入正题!!!
为何拿破衣框架来说事?并不是因为这个带点邪恶性质的mod能吸引眼球(说实话,俺觉得还是有点)。。。
而是这个小小的mod使用到了obse新的数据结构string,array和强大的用户自定义函数function,对于脚本进阶的童鞋有重要的参考和学习意义。。。
作者将框架代码和使用代码作为txt开放出来,对阅读和分析应该没有做限制,俺使用1.01版为大家做个介绍
首先对作者表示由衷的感谢,BUFramework是作为一个esm存在的,也就是说任何esp都可以很方便的使用其中定义的内容和函数,就像它们本来就存在与老滚中一样。。。
破衣框架使用原文:
; 個々の装備に貼り付けるオブジェクトスクリプト
scn BUDWDScript
array_var param
int ret
ref rEquip
array_var aEquip
array_var aMesP
array_var aMesA
Begin gamemode
let ret := Call BUCmnFnc param aEquip aMesP aMesA
if (ret == 0)
return
endif
if (ret == 2)
let param := aEquip := aMesP := aMesA := ar_Null
return
endif
; 初期化処理
let aEquip := ar_Construct Array
let aMesP := ar_Construct Array
let aMesA := ar_Construct Array
; 個別に変更が必要なのはここから↓
; 対象装備のEditorIDを指定(EditorIDが数字で始まる場合は""でくくる)
set rEquip to BUDWD
; 各破壊段階でのnifファイルを指定。段階数は任意。
; 初期状態
let aEquip[ar_Size aEquip] := "BreakUndiesDWDShortDressE1.nif"
let aEquip[ar_Size aEquip] := "BreakUndiesDWDShortDressE2.nif"
let aEquip[ar_Size aEquip] := "BreakUndiesDWDShortDressE3.nif"
let aEquip[ar_Size aEquip] := "BreakUndiesDWDShortDressE4.nif"
; 耐久度0の状態
; 上の段階数-1個分のメッセージを設定(プレイヤー側)
let aMesP[ar_Size aMesP] := "激しい戦闘で服が破けた…!(1回目)"
let aMesP[ar_Size aMesP] := "激しい戦闘で服が破けた…!(2回目)"
let aMesP[ar_Size aMesP] := "激しい戦闘で服が破けた…!(3回目)"
; メッセージ設定(敵側)
let aMesA[ar_Size aMesA] := "激しい戦闘で敵の服が破けた…!(1回目)"
let aMesA[ar_Size aMesA] := "激しい戦闘で敵の服が破けた…!(2回目)"
let aMesA[ar_Size aMesA] := "激しい戦闘で敵の服が破けた…!(3回目)"
; ↑ここまで
let param := ar_List 0, 0, 0, (ar_Size aEquip) - 1, rEquip
; 初期化処理ここまで
End
首先来看看怎么使用,使用破衣框架很简单:
1:在你的装备上做一个Object script
2:将上面的原文贴进去,把第一行的scn BUDWDScript换成你自己取的名字
scn XXX
3:在aEquip数组中填入装备各阶段的nif路径
4:将rEquip赋值为自己装备的Editor ID
set rEquip to XXX
好了,搞定了,就这么简单,你的装备现在具有了破衣属性,阶段数就是你填入aEquip数组的元素个数,其它一切都不需要你操心,破衣框架会自动帮你完成。
当然,你也可以把破衣时输出的提示换成其它文字,中文英文日文德文任君所好。。。
如果你是个装备modder,只想使用破衣框架来制作破损盔甲,那么按上面这样使用,就一切都ok了。
如果你是个script modder,想研究学习破衣框架的实现细节,并对obse中较新的几种数据结构和强大的用户自定义函数感兴趣,那么请往下看
下文需要一些编程基础知识,虽然老滚本身的脚本很直白,但obse的扩展,不借助一些术语确实不好解释,抱歉。。。
一:obse新数据结构
1,字符串
定义方式:
string_var s
赋值:
字符串可以显式赋值,如
string_var string1
set string1 to sv_Construct "First string"
或者通过隐式赋值,如破衣框架中的
let s := aEquip[0]
set语法是老滚脚本默认支持的赋值语句。
let,eval等语法是obse的扩展语句,当使用字符串,数组等obse扩展数据类型时,最好使用obse扩展赋值语句。
说明:
字符串是OBSE v0016新定义的数据类型,在v0017中完善,使用字符串可以很灵活的处理文本和信息。
注意:
* 释放
字符串使用完后,需要释放掉,否则导致内存泄漏。
当没有参考指向一个字符串的内存时,obse会自行释放,不过作为一个好的习惯,当一个字符串不需要时,最好主动释放:
sv_Destruct s
* 拷贝和赋值
字符串变量间的直接赋值使用的是引用拷贝,而非内存拷贝,例如
string_var string1
string_var string1
set string1 to sv_Construct "First string"
set string2 to string1
string2和string1都会指向数据"First string",改变其中一个同样也会改变另外一个
set string2 to sv_Construct "First string"
这时,虽然string1和string2都是内容"First string",但指向了不同的内存地址,改变一个不会影响另一个
tips:
当遇到需要使用字符串作为参数的老滚函数时,可以使用“$”来将一个字符串变量变成一个立即数,框架中同样也用到了
2,数组
定义方式:
array_var a
赋值:
数组可以显式赋值,如
array_var a
let a := ar_Construct Array
let a := ar_Construct Map
let a := ar_Construct StringMap
或者通过隐式赋值,某些obse的函数会返回一个数组类型
let a := 某个返回数组的函数
说明:
数组是OBSE v0017新定义的数据类型,在v0018中完善,obse的数组是一个包含传统array和关联array的强大数据结构
array_val类型有三种内部实现,Array(普通数组,以正整数为key),Map(以负数或浮点数作为key的map表),StringMap(以string作为key的map表)
注意:
* 释放
数组使用完后,需要释放掉,否则导致内存泄漏。
当没有参考指向一个数组的内存时,obse会自行释放,同样,好的习惯是当一个数组不需要时,主动释放掉:
let a := ar_Null
* 拷贝和赋值
跟字符串一样,数组间的直接赋值使用的是引用拷贝,而非内存拷贝
二:用户自定义函数
感谢obse伟大的努力!
在obse v0018中,实现了长久以来的script modder的一个怨念:自定义函数!!!
熟悉编程的朋友都知道,函数库是代码共享和复用的根本,老滚现在终于有了可复用的自定义函数结构,使用函数的好处俺就不多废话了,直接看语法
1,定义
函数脚本必须是Object script,且只能有一个块:Begin Function,参数使用{}确定,参数的类型使用预声明的本地变量来指定
scn BUCmnFnc
array_var param
array_var aEquip
array_var aMesP
array_var aMesA
Begin Function { param aEquip aMesP aMesA }
SetFunctionValue 0
return
End
这就是一个完整的函数定义
2,参数
所有使用的参数必须都预先声明出类型,参数个数最多10个
3,返回值
SetFunctionValue语句设置函数的返回值
任何obse支持的类型都可以作为返回值,包括string和array
如果没有指定任何返回值,函数默认返回数字0
4,tips:
自定义函数支持递归,但不要多于10层
自定义函数因为是Object类的脚本,所以无法使用fquestdelaytime,执行频率取决了外部调用
5,调用
使用Call语句来调用自定义函数,例如:
let ret := Call BUCmnFnc param aEquip aMesP aMesA
ret保存为函数的返回值
Call后的第一个参数BUCmnFnc是函数名,也就是函数脚本第一行的scn BUCmnFnc
函数名后的参数是真正传递给函数的参数,务必与函数定义中{}中的参数个数和类型相符,虽然函数定义中的参数以“,”分隔,但调用的时候使用空格来分开各个参数
破衣框架原文:
; Break Undies Common Function
; 各装備には状態保持用の配列と初期化処理、最低限のコードだけを貼り付け
; 大半の処理はここで行う。
scn BUCmnFnc
array_var param
array_var aEquip
array_var aMesP
array_var aMesA
ref who
int nEquip
ref rEquip
int break
int state
string_var s
Begin Function { param aEquip aMesP aMesA }
set who to getcontainer
if (who.IsActor == 0 || who.GetDead)
; アクターが所持していないか死んでる場合
if (param == ar_Null)
SetFunctionValue 0
return
endif
if eval(param[2] != 0) ; break?
; 念のため装備のNif指定を初期に戻す。
let rEquip := param[4]
let s := aEquip[0]
; 装備し直さないのでその場で表示は変わらない。
SetFemaleBipedPath $s rEquip
sv_Destruct s
let param[2] := 0
endif
SetFunctionValue 2 ; 解放
return
endif
if (param == ar_Null)
SetFunctionValue 1 ; 初期化が必要
return
endif
SetFunctionValue 0
if eval(param[0] > 0) ; need wait?
let param[0] := param[0] - 1
return
endif
; 再装備の必要があるか?
if eval(param[1] & 1) ; change?
let rEquip := param[4]
who.equipItemNS rEquip
let param[0] := 1 ; wait
let param[1] := param[1] & 2147483646 ; flags &= 0x7FFFFFFE
return
endif
if eval((param[1] & 2) == 0)
let param[1] := param[1] | 2 ; flags |= 2
let s := aEquip[0]
let rEquip := param[4]
SetFemaleBipedPath $s rEquip
sv_Destruct s
if (who.GetEquipped rEquip)
; 表示を反映させるために脱着。
who.UnequipItemNS rEquip
let param[0] := 1 ; wait
let param[1] := param[1] | 1 ; flags |= 1
return
endif
endif
; 現在の装備の状態
let nEquip := param[3]
set state to nEquip * (1.0 - (GetCurrentHealth / GetObjectHealth))
; 上限下限チェック
if (state < 0)
set state to 0
elseif (state > nEquip)
set state to nEquip
endif
let break := param[2]
if (GetGameLoaded == 1)
let break := 0
endif
if (break != state) ; 表示と状態が異なる場合
if (break < state) ; 破壊が進行
if (who.IsInCombat)
if (who == player) ;-- 戦闘中で、プレイヤーが着ていたら以下を実行
let s := aMesP[break]
else ;-- Actorであり戦闘中ならメッセージを流す
let s := aMesA[break]
endif
message $s
; effectの発生 effectBreakArmor を0.3秒発生させる
who.pms effectBreakArmor 0.3
endif
let break += 1
else ; 修復した場合
let break -= 1
endif
; 後で装備した時に表示が一致するように
; 装備しているかどうかに関わらずパスを変えておく。
let rEquip := param[4]
let s := aEquip[break]
SetFemaleBipedPath $s rEquip
sv_Destruct s
if (who.GetEquipped rEquip && break == state)
; 表示を反映させるために脱着。
who.UnequipItemNS rEquip
let param[0] := 1 ; wait
let param[1] := param[1] | 1 ; flags |= 1
endif
endif
let param[2] := break
End
有了string,array和function的基础知识,上面这段代码自然也就比较容易读懂了,需要注意的几个地方如下:
1,位操作
obse支持位操作,逻辑与逻辑或什么的,但不支持16进制立即数,所以,要将最后一位置0,只能这样干
let param[1] := param[1] & 2147483646
后面的注释写得很明确,这行的意思跟let param[1] := param[1] & 0x7FFFFFFE一样
2,SetFemaleBipedPath
这是设定一件装备的nif文件路径的函数,当装甲需要破损时,使用这个函数将装备的nif文件设置成下一阶段
需要先卸下再装备,效果才能出现
3,字符串变量转为立即数
SetFemaleBipedPath $s rEquip
SetFemaleBipedPath函数的第一个参数需要指定一个字符串立即数,也就是用""包含的文本,obse字符串的“$”操作符可以将string变量转换为立即数
4,效果渲染
who.pms effectBreakArmor 0.3
pms是PlayMagicShaderVisuals函数的简称,意思是播放一段可视化效果
效果名为第一个参数,这里是effectBreakArmor,这是破衣框架自己实现的一个效果:碎片四散
播放时间是第二个参数,这里是0.3秒,俺个人认为改成1秒更好
5,注意
IsInCombat
这个函数是用来判断一个actor是否处于战斗状态,使用时一定注意用actorRef.IsInCombat的语法,不要用IsInCombat actorRef
虽然cs不会报错,但你的所有脚本将会失效,这是个bug ! |
评分
-
2
查看全部评分
-
|