Lang - 2014-03-24
LANG 脚本语言

我们设定一套机制用来完成对NPC行为的补充和扩展,采用Lang脚本 该脚本是自主研发,这里将会介绍这门语言。

Lang 语言本身不提供变量 函数的支撑 只支持一些组合节点 通过这些节点来完成相应的逻辑。总共有三种节点:控制节点,逻辑节点,变量节点(不建议使用) 我们只是用控制节点和逻辑节点即可完成丰富逻辑。

控制节点: or节点 and节点 while 节点 when 节点 foreach节点 condition 节点 
逻辑节点: C++ 扩展的模块 由程序员提供 该节点能够在执行成功时 将继续执行子节点,否则执行下一个节点
Lang 关系
我们需要描述当前节点 子节点 下一个节点 举例来说 下面一段脚本
or(){
    print(“”);
    action(name=”hello”){
    say(“”);
}
}

or 节点设定为当前节点时 那么print为下一个节点, action 为print 的下一个节点 say为action 的子节点
Lang 执行
一般节点只有两种执行状态 失败或者成功
or 节点的运算规则是 若其中一个子节点执行成功 则不再继续执行子节点 执行or的下一个节点 本节点算是执行成功 否则执行失败

and 节点的运算规则是 若其中一个子节点执行失败 则不再继续执行字节点 执行and的下一个节点 本节点算是执行失败否则执行成功

while 节点 当condition 节点执行成功时 继续执行 该节点慎用

foreach 节点 遍历执行由程序员提供的集合 当condition 节点存在时 若condition执行失败 则退出遍历 

condition 节点 是一个语句块 可以被 while foreach when 节点引用

when节点 相当于 if  等价于逻辑节点 若condition 执行成功 则 执行子节点。

通过这几个节点的配合 我们将使用复杂的逻辑变换。前提我们有丰富的 逻辑节点库

Lang元素列表:
逻辑节点:只支持一下形式 其他形式将不合法
OBJECT.ACTION(NAME=VALUE,NAME1=VALUE2);  // 若无子节点 必须使用 ‘;’ 结束
OBJECT.ACTION(NAME=VALUE){} 
控制节点:
or(){}  
while(){}  
when(condition=””){}  
and(){}  
foreach(targets=””,object=””){}
condition(name=””){}
详细的使用可以继续查看本文档的AI应用模块
Lang适用场景
该Lang 脚本 可以适用于很多场景 包括任务系统,打造装备,网络库解码等 这里我们使用与一个较为复杂的模块 NPC 之AI 我们将为NPC 提供基本的逻辑库,通过对该库的组合 相应的就拥有了一套完善的NPC机制,可以实现NPC的有限智能,使NPC更加有趣 丰富。

Lang 变量
1.  对象变量、脚本变量
可以使用由C++程序员提供的变量 var(name=”OBJECT.A”,value=””,arg=””);

可以定义脚本的全局变量 var(name=”A”,value=””,arg=””);
value 指定语句表达式 value=”1+2 * 3” 按完全的左边最优先计算 赋值给name指定的变量
arg 存在时 表示将 name指定的值 放入临时计算池 可以使用#引用
2.  临时变量 calc(name=””){} // 将”” 当做表达式进行计算 表达式中只能使用局部变量或数值
3.  使用变量
calc(name=”#a>100”){//当通过时会执行子节点} 否则执行下一个节点 或者受限于上一节点的规则
and(){calc(name=”#a>100”);do();} //若calc不通过 则 do不执行
4.生存周期
当前节点提供的变量只能在子节点中存在
Lang 中文支持
    若C++ 扩展时 将 逻辑节点命名为中文 则可以使用中文进行编程
Lang 回调
    我们可以设定回调代码段 用于定时行为 该种机制用来需要异步处理的地方
OBJECT.delay(time)
{
    print(#time); // 当需要延时执行时 可以使用回调语句块 需要程序支撑
}
NPC 基本行为
内置对象npc 我们所有的逻辑节点均针对于npc进行操作
npc.isState(value=””){
    // 检查当前NPC的状态 value 为状态名字 你可以自由设定
} 
npc.changeState(value=””){
    // 设置当前NPC的状态 value 为状态的名字
}
npc.setTimer(id=””,lastTime=””,code=””)
{
    // 设置延时执行 id为时钟的编号 lastTime 表示延时 code 延时到后执行的代码名字
}
npc.moveTo(x=””,y=””){
    // 移动到 x,y 指定的点
}
npc. checkNeerDestination(distance=””){
    // 检查当前npc是否接近目的地 distance 为检查的距离
}
npc. setDestination(x=””,y=””){
    // 设置npc当前的目的地 x 为横坐标 y 为纵坐标
}
npc.talk(text=””){
    // npc说出一段话 text 为说话的文本
}
npc. goToDestination()
{
    // npc向 目的地移动一次
}
npc.nextStep(lasttime=””)
{
    // 下一步执行的时间 我们的AI 需要设定下次启动时间 lasttime 为延时
}
npc. randomMove()
{
    // 随机移动
}
npc. findEnemy(target=”enemy”)
{
    // 找到对象放入临时对象 enemy中
}
npc.doEnemy(target=””)
{
    // 处理target 指定的对象
}
npc.useSkill(target=””)
{
    // 触发npc 的攻击技能 攻击target 指定的对象
}
npc. isPercent(src=””,dest=””,valuetotag=””)
{
    // 判断src 是否在1~dest范围内 若valutoarg 存在 则将src 的值放入临时变量中
    //src,dest 可以获取临时变量 当指定存在的临时变量时
}
npc.checkArg(src=””,op=”” dest=””)
{
    // 判断src 和 dest 的关系
    // op 为 ==,<,<,had 当为had时 查看是否存在src指定的临时变量
//src,dest 可以获取临时变量 当指定存在的临时变量时
}
npc.opArg(src=””,dest=””,op=””){
    // 将dest 指定的变量 以op指定的方式 操作 src所指定的值
}
npc.findObjects(targets=”objects”,state=”1”,range=””,result="count")
{
    // 在range指定的范围内 查找 指定类型的对象 
    // state 1指定敌人 state 0 指定我方     
    // 将数量放入count 临时变量中
}
npc.checkXRange(min=””,max=””,target=””)
{
    // 检查与target指定的对象 横坐标距离是否 在 min ~max 之前
}
npc.checkYRange(min=””,max=””,target=””)
{
    // 检查与target指定的对象 纵坐标距离是否 在 min ~max 之前
}
npc. checkAngle (min=””,max=””,target=””)
{
    // 检查与target指定的对象 角度是否 在 min ~max 之前
}
npc.checkHP(min=””,max=””,percent=”true”)
{
    // percent 为true时 判断当前血量百分比 是否在min max 之间
    // percent为false时 判断当前血量 是否在min max 之间
}
npc. dam_getHighTarget(count="5",targets="objects")
{
    // npc获取仇恨在前count的对象 被放入objects 临时集合中 
}
npc. dam_getRandomTarget (count="5",targets="objects")
{
    // npc在仇恨值列表中 获取随机count的对象 放入objects 临时集合中 
}
npc.addVar(name=””,value=””,lasttime=””,clear=”true”)
{
    // 为npc增加变量 name 为变量名字 value为增加的值 lasttime 表示存在的时间 
    // clear=”true” 时 将更新变量值
}
npc.removeVar(name=””)
{
    // 删除name指定的变量
}
npc.checkVar(name=””,value=””,op=””)
{
    // 检查name 指定的变量 与 value指定的变量 是否构成 op指定的关系
}
npc.jumpTo(x=””,y=””)
{
    // 跳到x,y 指定的位置
}
npc.summonOther(id=””,newai=””,x=””,y=””)
{
    // 在x,y 处召唤id 的NPC 同时指定 newai的code
}
以上语句可以使用在 NPC 的执行帧里 code(name=”NAME”, lib=”ai”){}

为应对需要,我们引入的仇恨值机制 来方便NPC选取攻击对象
npc.dam_setCode(name=”xxx”); // xxx为设定的被攻击响应代码
code(name=”xxx”,lib=”ai”){
    // 那么xxx中可以出现几个特别的逻辑扩展,智能使用在该代码段内
    npc.dam_addValue(value=””,add=”true”,maxcount=””)
    {
        // 增加当前攻击者的仇恨值 value 为hp 时 增加攻击血量 否则增加指定的数值     // add为false时 增加之前清除已有的仇恨值
        // maxcount 若仇恨值列表大于该值 则不新增仇恨者  
}
npc.dam_checkSkill(id=””)
{
    // 检查当前攻击的技能是否为id指定的值
}
}

实例场景
说多无益,我们知道了这么多接口,怎么组合他们,才是关键的,这里结合实际举几个例子。
首先我们需要指定npc的执行帧 代码 //summon id=”xxx” newai=”xxx” (GM 指令已提供)
在Boss.ai 里编写code
code(name=”1234”,lib=”ai”) // 名字为1234 的 Lang脚本 我们该脚本的库是 ai 由C++支撑
{
    // 我们希望 npc 没隔1.5s说句话
    npc.talk(text=”hell,你好”);
    npc.nextStep(1500); // 如果不指定 那么该执行帧 将不再继续执行 1500 秒继续执行
}
//loadai 我们刷新下该脚本 //summon id=”20130” newai=”1234” 后将看到20130 的NPC每隔1500 说话了 你已经入门了。

接下来我们做个复杂的事情
code(name=”1235”,lib=”ai”)
{
    // 我们希望npc不停的沿着三个点行走
    or(){
    npc.isState(value="")
    {
        npc.setDestination(x="126",y="24");
        npc.changeState(value="moving1");
        npc.talk(text="我要移动1");
        npc.nextStep(lasttime="500");
    }
    npc.isState(value="moving1")
    {
        npc.talk(text="我要移动到2");
        or(){
            npc.checkNeerDestination(distance="1")
            {
                npc.setDestination(x="134",y="22");
                npc.changeState(value="moving2");
            }
            npc.goToDestination();
        }
        npc.nextStep(lasttime="500");
    }
    npc.isState(value="moving2")
    {
        npc.talk(text="我要移动3");
        or()
        {
            npc.checkNeerDestination(distance="1")
            {
                npc.setDestination(x="127",y="21");
                npc.changeState(value="moving3");
            }
            npc.goToDestination();
        }
        npc.nextStep(lasttime="500");
    }
    npc.isState(value="moving3")
    {
        npc.talk(text="我要移动1");
        or()
        {
            npc.checkNeerDestination(distance="1")
            {
                npc.setDestination(x="126",y="24");
                npc.changeState(value="moving3");
            }
            npc.goToDestination();
        }
        npc.nextStep(lasttime="500");
    }
 }
}

你发现用状态描述行为的丰富性,也可能你怕了 如果实现复杂的行为会不会对应很多的状态 然后切换呢? 我们将后继开发可视化的工具来做这件事。

好在我们可以用简单的方法做很多看似很复杂的事
//实现站桩BOSS 的智能攻击
攻击策划案描述:
1.  在玩家进入视野范围内时 召唤3个小怪 若小怪远离视野内 则继续填满 
2.  在玩家进入特定攻击区域时 将招到攻击(绝杀)
3.  在自身血量低于 %50 时 群伤
4.  在自身血量低于 %20 大于%10 时 召唤防御壁 场景设定阻挡点(需要将boss力压,否则关卡过不了)
code(name=”1236”,lib=”ai”)
{
npc.findObjects(targets="objects",state="1",range="4") // 当前视野范围内有人
{
    npc.findObjects(targets="objects1",stater="0",range="4",result="count") // 查找当前视野范围内是否有足够数量的友方
    {
        calc(”#count<3”)
{
            calc(count=”3-#count”){ // 优化后语句
                for(start="0",end="#count",step="1")
                {
                    npc.summon(id="20130",newai="1236"); //summon 3-n 个小怪
                }
}
        }
        // count恢复到result 的值
    }
    foreach(targets="objects",object="object")
    {
        npc.checkXRange(min="0",max="4",target="object")
        {
            npc.checkYRange(min="0",max="4",target="object")
            {
                npc.kill(target="object"); // 绝杀该区域的角色 该区域拥有宝箱
            }
        }
        npc.checkHP(min="20",max="50",percent="true") // 在血量低于%50 时 群伤
        {
            npc.useSkill();
        }
        npc.checkHP(min="10",max="20",percent="true")
        {
            not(action=”npc.checkVar”,name=”summon”,value=”1”){
                npc.summon(id="防御壁npcid",newai=""); // 召唤新怪
                for(start="0",end="10",step="1")
                {
                    npc.setBlock(x="index",y="1"); // 设定一排的阻挡点
                }
                npc.addVar(name=”summon”,value=”1”); // 设定npc 变量
}
        }
    }
}
npc.nextStep(lasttime="800");
}

活用CheckVar /addVar/removeVar: 可以实现一些定时行为
code(name=”timer”,lib=”ai”){
    or(){
npc.checkVar(name=”1”,op=”timeout”)
    {
npc.removeVar(name=”1”); // 1s 定时行为
} // 当没超时继续执行
npc.checkVar(name=”1”,op=”had”); 当存在变量时不继续执行
npc.addVar(name=”1”,value=”0”,lasttime=”1000”,clear=”true”); // 增加变量 存在时间1s
}
}
你可以挖掘更多
结语
    愿使用得力,愿 Lang 一直发展,壮大