3DMGAME 3DM首页 新闻中心 前瞻 | 评测 游戏库 热门 | 最新 攻略中心 攻略 | 秘籍 下载中心 游戏 | 汉化 购买正版 论坛

注册 登录

QQ登录

只需一步,快速开始

查看: 48818|回复: 120
打印 上一主题 下一主题

[原创] Papyrus 脚本语言完全解析 ---------(上古卷轴5+辐射4)

  [复制链接]

7

主题

434

帖子

1335

积分

3DM创意工坊 原创组

Rank: 12Rank: 12Rank: 12

贡献度
212
金元
4473
积分
1335
精华
4
注册时间
2008-7-18
跳转到指定楼层
主题
发表于 2016-8-22 15:57 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 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

查看全部评分

回复

使用道具 举报

0

主题

14

帖子

14

积分

新手玩家

Rank: 1

贡献度
0
金元
140
积分
14
精华
0
注册时间
2021-1-11
舒服的沙发
发表于 2021-1-11 12:45 | 只看该作者
大佬,虽然不想挖坟,但还是想请教一下,如何能触发sexlab等其他mod
回复 支持 反对

使用道具 举报

0

主题

49

帖子

51

积分

初级玩家

Rank: 2

贡献度
0
金元
510
积分
51
精华
0
注册时间
2021-5-27
硬硬的板凳
发表于 2021-9-27 20:10 | 只看该作者
支持一下
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|3DMGAME ( 京ICP备14006952号-1  沪公网安备 31011202006753号

GMT+8, 2025-7-9 01:08 , Processed in 0.105113 second(s), 16 queries , Memcache On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

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