侧栏导航

热血传奇 - NPC对话



本篇文章记录了:

  1. 介绍 传奇 游戏中的NPC交互过程
  2. 设计NPC交互的脚本
  3. NPC脚本的服务端和客户端的交互过程

传奇游戏中的NPC交互过程

npc的交互过程分为3步,

  1. 用户选中一个npc,开始会话
  2. 服务端返回会话内容
  3. 客户端展示会话内容
  4. 玩家中断会话或者做出选择,重复1~3步

详细交互过程如下流程图:

npc

c 客户端, S 服务端, U 玩家

@startuml
C -> S: 1. 请求当前地图的NPC列表
S -> C: 2. 返回 NPC 列表 
U -> C: 3. 用户点击NPC,触发NPC交互
C -> S: 4. 根据点击的NPC名称,请求服务端返回会话内容
S -> C: 5. 根据NPC名称,查找NPC的定义,并返回@main脚本段
C -> U: 6. 解析main脚本段,展示会话框UI
U -> C: 7. 点击对话框中的选项@funcX
C -> S: 8. 根据用户的选择的选项名称@funcX,请求下一个对话框
S -> C: 9. 根据@funcX查找脚本段,执行脚本,结束或者返回下一步脚本段
C -> S: 10. 重复上述步骤5-9
@enduml

设计NPC交互的脚本

在理解交互过程后, 开始规划脚本的结构组成.

NPC数据结构

首先, 我们应该有脚本来配置NPC所在的地图,坐标,外观,及具体功能定义等各项属性, 设计它的数据结构如下

[
    {
        name: '<新人接待员>',//别名,用于客户端展示
        def: '新手接待员',//功能定义
        map: '新手村',//NPC所在地图
        x: 50,//NPC所在地图坐标
        y: 50,//NPC所在地图坐标
        appear: 1//NPC外观,图片资源索引
    },
    {
        name: '<传送员>',
        def: '传送员',
        map: '新手村',
        x: 52,
        y: 52,
        appear: 2
    }
]
NPC配置脚本的定义和维护

如下示例脚本<NPC_新手村.js>定义了两个NPC,

他们都被配置在 新手村 这种地图上,为了便于维护, NPC的配置脚本,应该可以按地图来分类,位于不同的目录下,并且统一放在config目录下

1563852857781

NPC的功能定义

NPC具有和玩家交互的作用, 具体是做哪些的交互呢, 我们通过一个脚本来定义其功能,简称为 NPC功能定义

<新手村接待员.js> 为例 ,我们约定

  1. 每个NPC的功能定义脚本应该有一个 main 函数,且必须返回一个字符串作为客户端展示的对话框内容
  2. 字符串中约定可以使用一对尖括号 <> 来作写表达式
  3. 表达式中使用 / 来分割文本和表达式操作符
  4. 约定@开头为选项表达式, = 后面为表示式的参数值, @表达式后面接函数名称,对应此选项下一步的脚本函数
  5. 约定SCOLOR为调色盘表达式, = 后面为前面文本的渲染颜色
  6. 约定非<>的内容为普通文本

1563853644471

其次, NPC的功能可能重复,比如 传送员,仓库管理员,药店老板,

所以我们应该支持不同的NPC的功能定义可以相同,允许不同的NPC配置其功能定义指向同一个

为了便于管理,我们把NPC的功能定义放在def目录下

1563853126524

NPC脚本服务端和客户端的交互

加载和渲染脚本
  1. 服务端启动时,加载所有的*.js文件. 包含了NPC的配置和定义
  2. 客户端进入地图是, 获取该地图下NPC列表数据,进行渲染
  3. 客户端响应玩家的操作, 调用该NPC脚本def中的main函数,并渲染main中的字符串和表达式
  4. 客户端响应玩家的下一步选项, 调用表达式中的函数funcX
服务端调用函数设计

服务端定义一个脚本用来查找和调用NPC的def,约定命名为npc_call函数,其函数定义如下:

function npc_call() {
    var funcName = arguments[0];


    console.info("npc_call ,funcName:" + funcName);
    var def = null;
    if (funcName.indexOf(".")>=0) {
        var split = funcName.split(".");
        var temp = this;
        for (var i = 0; i < split.length; i++) {
            temp = temp[split[i]];
        }
        def = temp;
    } else {
        def = this[funcName];
    }
    if (typeof def == "undefined") {
        console.info("not find the function:{}", funcName);
        return;
    }
    var pars = [];
    for (var i = 1; i < arguments.length; i++) {
        pars.push(arguments[i]);
    }
    def.apply(this, pars);
}
调用示例代码
    public static void main(String[] args) throws IOException, ScriptException {
        //启动数据库模块
        DBUtil.init();
        //启动脚本模块
        ScriptManger scriptManger = new ScriptManger();
        //加载脚本文件
        scriptManger.setup("..\\PK\\server\\PKServer\\script\\");

        HumProperty hum = HumProperty.dao.findById(1);
        //触发脚本函数
        scriptManger.getEngine().getContext().setAttribute("$p0",hum, ScriptContext.ENGINE_SCOPE);
        scriptManger.invokeFunction("npc_call","新手接待员.checkLevel",hum);
    }

下一篇 <热血传奇 - 装备掉落>

最后更新于 23rd Jul 2019
微信二维码
在微信上关注我