|
本帖最后由 Bee9999 于 2016-9-5 12:51 编辑
Papyrus脚本语言完全解析 (TES5,Fallout4)
Bee9999 presents
其他作品
什么是Papyrus
Papyrus是一种简单,易上手的脚本语言,本质上也是一种程序,但是有别于底层的程序,它通过自有的编译器编译成一种中间语言,在Runtime通过解释器与底层框架进行通信,通常来说,它只能执行底层框架暴露的接口(API)由于是一种解释性语言,执行效率相对较低,通常只用在较为简单的GamePlay逻辑,并不适合做大量的运算
Papyrus可以做的事很多,通常用在魔法效果,任务进程,物品添加与移除等等,Creation kit编辑器本身很健壮,提供了很多简单的逻辑判断,可以完成较为简单的需求,但要进行更复杂的Gameplay逻辑时,Papyrus就派上用场了
就使用范围来说,Papyrus使用范围很窄,但并不代表学习它毫无意义,如果你是一个Gameplay策划或者立志做一个Gameplay策划,学习它可以更快的通过Creantion kit去印证自己的想法,并带到实际工作中或者是未来的工作内容中来.实际编程中,重要的不是用哪个语言,而是编程思路
Papyarus按照通用的语言标准来进行设计,尽管与其他程序语言有很多相异之处,由于它的易用性,不失为是一个编程入门的好工具.
学习编程是枯燥的,很多语言学习时只能对着单调的控制台,感谢Bethesda提供了一个环境,让我们编写完自己的papyrus脚本后可以立即在游戏中看到运行的结果,这对学习编程是很有帮助的.
基础知识
变量与基本数据类型
1.变量
2.基本数据类型
1.变量
1.概念
用于在内存中存储数据.
2.变量的语法
例:int Property InteriorFleeRadius = 0 Auto ;变量类型 /属性关键字 /变量名(可直接在声明变量时对变量进行赋值) /填充方法
3.变量的使用规则
先声明,后赋值,再使用.
4.变量的命名规则
①必须以字母开头,不能以数字开头
②后面可以跟任意的 字母 数字 下划线
5.变量的命名注意事项
①在Papyrus中,大小写是不敏感的
②同一个变量名不允许重复定义
6.变量的命名规范
尽管Papyrus对大小写不敏感,但尽量在声明时规范的命名
通常使用的变量命名方法:驼峰命名法 [Camel]
要求变量名首单词的首字母要小写,其余的每个单词的首字母要大写
例如: time, playerName, vipLevel
2.基本数据类型
1.int 类型
整数类型,只能存储整数,不能存储小数。
取值范围:-2,147,483,648 到 2,147,483,647
2.float 类型
单精度小数类型, 既能存储整数,又能存储小数
取值范围:小数点后面的位数是 7 位;
3.1415926
3.bool 类型
bool 类型,用来描述对或错,bool 类型的值只有两个:true false。
4.string 类型(?)
字符串类型,用来存储文本,也可以存储空,字符串类型的值需要用双引号引起来;
"Hello World"
5.Objects 无类型,可以引用所有物体
6.Arrays
数组类型,储存数组
运算符与表达式
1.“=”号与“+”号
1.“=”号
表示赋值的意思,表示把等号右边的值,赋值给等号左边的变量。
int laserChargeTime = 15
2.“+”号
在Papyrus中“+”号有两个作用:
①连接【当“+”号两边有一边是字符串的时候,“+”号就起到连接的作用】
Debug.Trace("ammo is" + selfRef.GetItemCount(AmmoAssaultron) + " on combat")
②相加【当“+”号两边都是数字的时候,“+”号就起到相加的作用】
laserState = laserState + 1 //这个表达式可以简写为laserState++;
2.算数运算符
1.+ - * /
加减乘除,四则运算。
优先级:先乘除,后加减,有括号先算括号里的,相同级别的从左至右运算。
2.%
取模(取余数)
例:15 % 4 = 3;
3.“++”与“--”
1.前++ 后++
如果单独使用,不管是前++还是后++,最终的结果都是给这个变量加 1。
例:itemcount++ , ++itemcount
如果是和表达式配合使用:
前++,先给这个变量自身加 1,然后带着这个加 1 后的值去参与运算。
后++,则先拿原值参与运算,运算完毕后,再将这个变量自身加 1。
例:sum = ++itemcount; sum = itemcount++;
2.前"--" 后"--" 同上。
3.复合赋值运算符
+= ;加等于
-= ;减等于
*= ;乘等于
/= ;除等于
%= ;取余等于
laserState += 5 等同于 laserState = laserState + 5 laserState -= 5 等同于 laserState = laserState - 5
laserState *= 5 等同于 laserState = laserState * 5
laserState /= 5 等同于 laserState = laserState / 5
laserState %= 5 等同于 laserState = laserState % 5
逻辑运算
1.关系运算符
>, <, >= ,<= ,== ,!=
概念:关系运算符是用来描述两个事物之间的关系。
由关系运算符连接的表达式称之为“关系表达式”。
关系表达式的运算结果是 bool 类型。
例 5 > 4 = true; 10 > 15 = false;
2.逻辑运算符
1.&& 逻辑与
&& 两边的表达式结果都为 true 的时候,这个逻辑与表达式的结果就为 true;
两边的表达式结果只要一个是 false,那么整个逻辑与表达式的结果就是 false。
例:10 > 5 && 10 < 20
2.|| 逻辑或
|| 两边的表达式结果只要有一边为 true,整个逻辑或表达式的结果就为 true;
两边的表达式的结果都为 false,整个逻辑或表达式的结果才为 false。
例:5 > 3 || 3 > 10
3.! 逻辑非
!真的变假的,假的变真的。
例:!(9 > 4)
Papyrus关键字
1.const 常量
一个不能变化的量,常量一旦声明,就不可以再重新赋值。
Spell Property Arena_PacifyCombatant const auto
2.native
含有native关键字说明该方法(在程序语言中,函数一般称为方法)不是Papyrus实现的,而是底层语言实现的,Creation引擎底层语言是C++,说明这个方法是由C++实现的,(C++全称:C Plus Plus,简称C++);
Function QuitGame() native global
3.global
含有global关键字的方法说明改方法可以在所有的脚本中使用这个方法,不需要继承
Papyrus脚本命名
例:Scriptname BirdSpawnerScript extends ObjectReference
格式: 脚本头 / 脚本名 / 扩展关键字 / 基类
papyrus没有类的概念,但仍要继承一个基础类型来对脚本进行扩展,这样脚本才能对目标类型执行脚本逻辑
如果想引用其他脚本的方法,使用import 关键字
例:import mylib
这样可以继承自定义的脚本库
语句结构
分支结构之 if 语句
1.if 语句
1.语法
if( 判断条件 )
要执行的代码;
endif
int Property number = 1 Auto
Event OnLoad()
if number == 1
Debug.MessageBox("Hello World!")
EndIf
EndEvent
注:“判断条件”一般为关系表达式或者 bool 类型的值。运算符必须为关系表达符( >, <, >= ,<= ,== ,!= )
2.执行过程
程序运行到 if 处,首先判断 if 后面的判断条件,如果条件成立,也就
是返回 true,则执行 if 下的代码,如果判断条件不成立,也就是
返回 false,则跳过 if 结构,继续向下执行
3.特点
先判断,再执行,有可能一行代码都不执行。
用于一种情况的判断。
2.if-else 语句
1.语法
if( 判断条件 )
要执行的代码;
else
要执行的代码;
endif
2.执行过程
程序执行到 if 处,首先判断 if 所带的判断条件是否成立,
如果成立,也就是返回一个 true,则执行 if 下的代码,执行完毕
后,跳出 if-else 结构;
如果不成立,也就是返回一个 false,则跳过 if 语句,执行 else 下的代码,执行完毕后,同样跳出 if-else 结构
int Property number = 1 Auto
Event OnLoad()
if number == 1
Debug.MessageBox("Hello World!")
else
Debug.MessageBox("Hey guy")
EndIf
EndEvent
3.特点
先判断,再执行,最少都要执行一条代码。
用于两种情况的判断。
分支结构之 switch 语句
1.if else-if 语句
1.语法
if(判断条件)
要执行的代码;
else if(判断条件)
要执行的代码;
....... else
要执行的代码;
endif
(有多少个if,就要有多少个endif结尾)
if aiTimerID == iTimer_PlayerGoToCorner && FightState == Arena_FightWaiting.value
if IsCombatant1Player && PlayerRef.GetDistance(Corner1.GetReference()) < 150
setObjectiveCompleted(objPlayerGoToCorner)
elseif IsCombatant2Player && PlayerRef.GetDistance(Corner2.GetReference()) < 150
setObjectiveCompleted(objPlayerGoToCorner)
endif
startTimer(iInterval_PlayerGoToCorner, iTimer_PlayerGoToCorner)
endif
2.执行过程
①程序首先判断第一个 if 所带的判断条件,如果条件成立,也就是
返回一个 true,则执行该 if 所带的代码,执行完成后,立即跳出 if
else-if 结构。
②如果第一个 if 所带的判断条件不成立,也就是返回一个 false,则继续向下进
行判断,依次的判断每一个 if 的判断条件,如果成立,就执行该 if 所带的代码,如果不成立,则继续向下判断。
③如果每个 if 所带的判断条件都不成立,就看当前这个 if else-if 结构中是否存
在 else,如果有 else,则执行 else 中所带的代码,如果没有 else,则整个 if
else-if 就神马都不做。
④else 可以省略。
3.特点
用来处理多条件(条件>2 个)的区间判断。
例:龙吼获取条件,任务执行条件,电浆枪攻击目标判断
循环结构之 while 语句
1.while 语句
1.语法
while(循环条件)//当括号内条件为真时,执行循环体
循环体;
注:
循环条件:一般为关系表达式或者一个 bool 类型的值。
循环体:要重复执行的代码。
2.执行过程
程序运行到 while 处,首先判断 while 所带的小括号内的循环条件是否成立,
如果成立的话,也就是返回 true,则执行循环体;
执行完一遍循环体后,再次回到循环条件进行判断,如果依然成立,则继续执行
循环体,如果不成立,则跳出 while 循环。
在 while 循环当中,一般总会有那么一行代码,能够改变循环条件,使之终有
一天不再成立,如果没有那么一行代码能够改变循环条件,也就是循环条件永远
都成立,我们称这种循环叫做死循环。
例:1加到100(1+2+3+4...+100)
int Function DoWhile(int i)
int number = i;
int j = 0;
while (j <= 100)
if(j == 50)
Debug.MessageBox("Number is " + j);
number += j;
j++;
endwhile return number;EndFunction
在其他地方调用DoWhile()
Event OnInit()
int k;
k = DoWhile(0)
Debug.MessageBox(k);
EndEvent
则会弹出两个对话框
Number is 50
5050
游戏中应用:循环持续刷新一定数量敌人,批量对NPC执行某些动作等等
目前来说,Papyrus通常只用到if语句与while语句,switch,for很少会用到,就不讨论了
函数(方法)之基本语法
1.函数的概念
一段特定功能的代码,有自己的名字,通过名字可以重复调用这段代码来帮我们
完成特定的事情。 Papyrus允许自定义函数,所以我们可以自定义一个函数库,以便在多个脚本中使用,通常来说,动作类的MOD都会自定义一个函数库,例如臭名远扬的SL.
2.函数的声明与调用
1.Pascal 命名法
帕斯卡命名规范,要求每个单词的首字母都要大写,其余字母小写。
用途:多用于给脚本名或者函数(方法)命名。
例如:OnActivate,RegisterForDeletionEvent,OnTriggerEnter,OnTriggerLeave
2.函数的声明(语法)
int Function DoMyThing(Actor myActor = None)
格式:返回类型 / 声明函数关键字 / 函数名 / (函数参数)
int:说明返回类型是整数类型,一个函数执行完毕后返回一个数据,在其他脚本或函数中调用,可以返回多种类型,但一次只能返回一种类型,一个数据
返回值类型:如果不需要返回值则不写
Function关键字声明这个语句块是一个函数
函数参数,需要用小括号括起来,做为函数的一个输入值,在函数中进行调用
int Function SayMyThing(Actor myActor = None)
if CustomTopicKeyword && myActor
debug.trace(self + "Actor will say Topic Subtype " + CustomTopicKeyword)
myActor.SayCustom(CustomTopicKeyword)
endif
RETURN 1 ;returning a value for possible extendability needs
EndFunction
在这个函数中,函数返回类型是int整数型,函数参数为myActor并且默认赋值为空
执行函数后判断条件,判断条件:如果存在关键字,并且myActor存在,则执行debug.trace(),myActor.SayCustom(),并且返回一个值,值为1
3.函数的调用
函数名([实际参数]);
注意:
如果函数只声明不调用,则函数中的代码不会被执行。
3.函数的参数与返回值
1.形参与实参
形参:形式参数,在定义函数的时候,在参数列表中定义的参数。
OnActivate(ObjectReference akActionRef)
实参:实际参数,在调用函数的时候,传递给函数的具体参数。
debug.trace(self + "Actor will say Topic Subtype " + CustomTopicKeyword)
2.返回值
关键字 return
作用:
①在函数中返回要返回的值;
②立即结束函数;
事件
事件是skyrim - papyrus里很重要的组成部分,绝大部分的游戏逻辑由事件完成,以下是可用事件列表
格式
Event OnSomething()
EndEvent
OnActivate - ObjectReference \\在激活时
OnAnimationEvent - ActiveMagicEffect \\在播放动画时
OnAnimationEvent - Alias \\在播放动画时
OnAnimationEvent - Form \\在播放动画时
OnAnimationEventUnregistered - ActiveMagicEffect \\在动画事件结束
OnAnimationEventUnregistered - Alias \\在动画事件结束
OnAnimationEventUnregistered - Form \\在动画事件结束
OnAttachedToCell - ObjectReference
OnBeginState \\初始化时
OnCellAttach - ObjectReference
OnCellDetach - ObjectReference
OnCellLoad - ObjectReference
OnClose - ObjectReference \\在关闭时(柜子,箱子等等)
OnCombatStateChanged - Actor \\在战斗状态改变时(正常,潜行,战斗,警戒)
OnContainerChanged - ObjectReference \\在物品改变容器时(拿取物品,丢弃物品,放入箱子)
OnDeath - Actor \\在角色死亡时
OnDestructionStageChanged - ObjectReference
OnDetachedFromCell - ObjectReference
OnDying - Actor \\在角色正在死亡时
OnEffectFinish - ActiveMagicEffect \\在魔法效果结束时(附加脚本效果)
OnEffectStart - ActiveMagicEffect \\在魔法效果开始时(附加脚本效果)
OnEndState
OnEnterBleedout - Actor
OnEquipped - ObjectReference
OnGainLOS - ActiveMagicEffect
OnGainLOS - Alias
OnGainLOS - Form
OnGetUp - Actor
OnGrab - ObjectReference
OnHit - ObjectReference \\在受到攻击时
OnInit \\在初始化时
OnItemAdded - ObjectReference \\在添加物品时
OnItemRemoved - ObjectReference \\在移除物品时
OnLoad - ObjectReference
OnLocationChange - Actor
OnLockStateChanged - ObjectReference
OnLostLOS - ActiveMagicEffect
OnLostLOS - Alias
OnLostLOS - Form
OnMagicEffectApply - ObjectReference
OnObjectEquipped - Actor
OnObjectUnequipped - Actor
OnOpen - ObjectReference
OnPackageChange - Actor
OnPackageEnd - Actor
OnPackageStart - Actor
OnPlayerBowShot - Actor
OnPlayerLoadGame - Actor
OnRaceSwitchComplete - Actor
OnRead - ObjectReference
OnRelease - ObjectReference
OnReset - Alias
OnReset - ObjectReference
OnReset - Quest
OnSell - ObjectReference
OnSit - Actor
OnSleepStart - Form
OnSleepStop - Form \\在睡眠结束时(兄弟会任务触发)
OnSpellCast - ObjectReference
OnStoryActivateActor - Quest
OnStoryAddToPlayer - Quest
OnStoryArrest - Quest
OnStoryAssaultActor - Quest
OnStoryBribeNPC - Quest
OnStoryCastMagic - Quest
OnStoryChangeLocation - Quest
OnStoryCraftItem - Quest
OnStoryCrimeGold - Quest
OnStoryCure - Quest
OnStoryDialogue - Quest
OnStoryDiscoverDeadBody - Quest
OnStoryEscapeJail - Quest
OnStoryFlatterNPC - Quest
OnStoryHello - Quest
OnStoryIncreaseLevel - Quest
OnStoryIncreaseSkill - Quest
OnStoryInfection - Quest
OnStoryIntimidateNPC - Quest
OnStoryJail - Quest
OnStoryKillActor - Quest
OnStoryNewVoicePower - Quest
OnStoryPayFine - Quest
OnStoryPickLock - Quest
OnStoryPlayerGetsFavor - Quest
OnStoryRelationshipChange - Quest
OnStoryRemoveFromPlayer - Quest
OnStoryScript - Quest
OnStoryServedTime - Quest
OnStoryTrespass - Quest
OnTrackedStatsEvent - Form
OnTranslationAlmostComplete - ObjectReference
OnTranslationComplete - ObjectReference
OnTranslationFailed - ObjectReference
OnTrapHit - ObjectReference
OnTrapHitStart - ObjectReference
OnTrapHitStop - ObjectReference
OnTrigger - ObjectReference
OnTriggerEnter - ObjectReference
OnTriggerLeave - ObjectReference
OnUnequipped - ObjectReference
OnUnload - ObjectReference
OnUpdate - ActiveMagicEffect
OnUpdate - Alias
OnUpdate - Form
OnUpdateGameTime - Form
OnWardHit - ObjectReference
示例
Event OnAnimationEvent(ObjectReference source, string eventName)
if ((eventName == BloodbugFeedEventName) && (sacState == 0) && (Self.GetValue(AvailableCondition2) > 0))
Debug.Trace(Self + " received Event: " + eventName)
if (Self.GetCombatTarget() != None && Self.GetCombatTarget().HasKeyword(ActorTypeIrradiated))
Self.AddItem(AmmoBloodbug, 4 - Self.GetItemCount(AmmoBloodbug)) ;Give Blood Ammo
Self.EquipItem(AmmoBloodbug) ;Force equip it.
sacState = 2 ;Record that we're filled w/ Green Blood
Self.SetValue(AvailableCondition1, 1) ;Reduce sac health to 1.
Self.playSubgraphAnimation("FillGreen") ;Fill w/ Green Blood
Self.EvaluatePackage()
;Debug.Trace("BLOODBUG: Sac State Updated")
DrinkBloodSpell.Cast(self)
else
Self.AddItem(AmmoBloodbug, 4 - Self.GetItemCount(AmmoBloodbug)) ;Give Blood Ammo
Self.EquipItem(AmmoBloodbug) ;Force equip.
sacState = 1 ;Record that we're filled w/ Red Blood
Self.SetValue(AvailableCondition1, 1) ;Reduce sac health to 1.
Self.playSubgraphAnimation("FillRed") ;Fill w/ Red Blood
Self.EvaluatePackage()
;Debug.Trace("BLOODBUG: Sac State Updated")
DrinkBloodSpell.Cast(self)
endif
endif
if ((eventName == BloodbugSpitEventName) && (sacState != 0))
Self.playSubgraphAnimation("emptyRed")
sacState = 0
Self.EvaluatePackage()
;Debug.Trace("BLOODBUG: Sac State Updated")
endIf
EndEvent
标示(flag)
标示作为脚本的扩展关键字,可以对脚本进行一些特殊操作
脚本标示
Hidden:当一些脚本你不希望出现在脚本列表里时,使用这个关键字
Scriptname BloodbugScript extends Actor Hidden
Conditional:旗本脚本作为可视的条件系统。创建工具包将不会让您在同一个对象上附加一个条件脚本。
属性标示(property)
Auto:自动赋值
MiscObject property BloodbugSacFilledRed auto
AutoReadOnly:自动赋值,并且数值只读
Hidden:在Creation kit中隐藏显示数值,但其他脚本可以改变这个数值
String property BloodbugFeedEventName = "FillingRed" auto hidden const
Conditional:按条件显示数值,脚本标示必须同时有Conditional
|
评分
-
10
查看全部评分
-
|