【RPG Maker MV】制作一个简易的中文输入插件( ̄︶ ̄)↗

随笔7个月前发布 温柔无声无息
207 0 0

作者:邻家校长
mv本身主要由canvas标签构成,该标签不能直接获得焦点,因此无法直接调用系统输入法键盘,如果想获取自由输入文本那该怎么办呢?当然是创建一个隐藏的input标签啦,在合适的情况下使input标签获取焦点,就能调用系统的输入法了!但是本文偏不这样做,而是采用mv风格的方法自己绘制一个简易输入法,至于具体的方式嘛就放在下面啦!

内容:

  • 制作中文词典管理器及获取数据
  • 创建输入法窗体

1.制作中文词典管理器及获取数据

首先创建一个文件Dictionnary.js并在其中创建一个字典管理类DictionaryManager,再将其导入到mv的插件管理器中。

function DictionaryManager(){
    throw new Error('This is a static class');
}

然后创建字典对象,数据可以在任意字典网站按拼音、部首、五笔导出即可,此文以拼音格式排序。最后存入一个数据对象中ChineseDictionary, 每个拼音即字典对象的key,后面会使用到,每个拼音对应的字即字典对象的value,大致结构如下:

DictionaryManager.ChineseDictionary = {
    a    : ['吖阿呵啊锕腌嗄錒'],
    ai   : ['砹唉娭挨埃捱娾啀....'],
    an   : ['俺隌谙偣腤安暗黯....'],
    ang  : [.......]
    ao   : [.......]
    .......
}

字典模板有了,接下来就是每次输入拼音时能获取到的数据处理:

处理函数其一

将符合条件的数据以字符串的方式拼接并返回数据:getDictionaryString_Zh_Cn

接收拼音字符串value并将其转换为小写.toLowerCase(),循环遍历字典对象的key值,符合以下条件的数据将拼接到一个变量中在最后进行返回:
1.使用hasOwnProperty(key)判断key值只属于字典对象ChineseDictionary的;
2:key的长度大于等于拼音字符串的长度;
3:截取key和拼音字符串等长的字符长度然后等于拼音的;
结构如下 。

DictionaryManager.getDictionaryString_Zh_Cn = function(value){
    let texts = "";
    value = value.toLowerCase();
    for (let key in DictionaryManager.ChineseDictionary) {
        if(!DictionaryManager.ChineseDictionary.hasOwnProperty(key)){
            continue;
        }
        if(key.substring(0, value.length) !== value){
            continue;
        }
        texts+=DictionaryManager.ChineseDictionary[key];
    }
    return texts;
}

处理函数其二

将符合条件的数据以数组的方式返回数据:getDictionaryList_Zh_Cn

DictionaryManager.getDictionaryList_Zh_Cn = function(value){
    let texts = this.getDictionaryString_Zh_Cn(value);
    return texts.split("");
}

以上字典管理器部分完成

2.创建输入法窗体

支持中文输入的界面构成分为:输入框区域 / 拼音区域 / 候选字区域 / 功能按键区域 / 字母区域

2.1 创建一个文件KeyboardWindow.js并在其中创建一个继承自Window_Base的窗体对象Window_Keyboard,再将其导入到mv的插件管理器中。

function Window_Keyboard() {
    this.initialize.apply(this, arguments);
}

Window_Keyboard.prototype = Object.create(Window_Base.prototype);
Window_Keyboard.prototype.constructor = Window_Keyboard;

2.2 输入法窗体的基础框架搭建

2.2.1:初始化大小位置以及添加窗体的一些基础参数

//窗体模式 中文,英文,数字
Window_Keyboard.MODE_Zh_cn = "Chinese";
Window_Keyboard.MODE_BE = "BigChar";
Window_Keyboard.MODE_SE = "SmallChar";
Window_Keyboard.MODE_N = "Number";
Window_Keyboard.prototype.initialize = function() {
    //这里初始化配置窗体占用整个画面,具体大小可按需配置。
    Window_Base.prototype.initialize.call(this, 0, 0, Graphics.boxWidth, Graphics.boxHeight);
    this._text = "";   //当前输入框中的文字
    this.initMember();
    this.name = "KeyboardWindow";
    this.hide(); //窗体初始化完成后就隐藏
};
  
Window_Keyboard.prototype.initMember = function() {
    this._callWindow = null;   //唤醒输入法窗体的对象
    this._callFun = null;      //完成输入后回调的函数
    this._mode = "";           //窗体输入模式
    this._modeText = "";       //窗体输入模式显示的文本
    this._modeFixed = false;   //固定窗体输入模式
    this.clear();
}
Window_Keyboard.prototype.clear = function() {
    this._keys = [];        //对应模式下的按键
    this._buttons = [];     //功能按键
    this._words = [];       //展示的候选字
    this._wordsbase = [];   //所有候选字
    this._phonetic = "";    //输入的拼音
    this._wordsIndex = 0;   //候选字分页索引
}

2.2.2:设置唤醒输入法的窗体及回调的函数

/**
 * 设置打开输入法窗体的父级窗体 
 * 在确认打开该窗体时再调用
 * 调用该函数的同时父级窗体停止活动,并设置当前输入法窗体活动
 * */
Window_Keyboard.prototype.setCallWindow = function(win) {
    this._callWindow = win;
    win.deactivate();//父级窗体停止活跃
    this._text = "";
    this.activate();//当前窗体活跃
    this.show();
    this.refresh();
}
//设置完成输入后的回调函数
Window_Keyboard.prototype.setCallFun = function(fun) {
    this._callFun = fun;
}
//设置初始文本
Window_Keyboard.prototype.setText = function(text) {
    this._text = text;
    this.refresh();
}

2.3 输入法的布局样式

2.3.1 不同模式下字母、数字按键的矩形区域及显示的文本:
非数字模式时键盘显示26个字母和一个空格共计27个按键,数字模式时显示10个数字和一个小数点合计11个按键:

//创建基本按键
Window_Keyboard.prototype.createKeysData = function() {
    let keys= "";
    switch(this._mode){
        case Window_Keyboard.MODE_Zh_cn:
        case Window_Keyboard.MODE_BE:
            keys = "QWERTYUIOASDFGHJKLZXCVBNMP ";//9
            break;
        case Window_Keyboard.MODE_SE:
            keys = "qwertyuioasdfghjklzxcvbnmp ";//9
            break;
        case Window_Keyboard.MODE_N:
            keys ="1234567890."//4
            break;
    }
    keys = keys.split("");
    this._keys = [];
    keys.forEach((key, index) => {
        this._keys.push(this.keyRect(index, key));
    }, this);
}
//根据按键索引配置对应的矩形区域,整个区域置于窗体底部
Window_Keyboard.prototype.keyRect = function(index, key){
    let contentsWidth = this.contentsWidth() ;
    let contentsHeight = this.contentsHeight();
    let lineHeight = this.lineHeight();
    //每行按键的数量,数字为4个,非数字为9个
    let lineNum = this._mode == Window_Keyboard.MODE_N? 4 : 9;

    let rect = new Rectangle();
    rect.width = contentsWidth / lineNum;
    rect.height = lineHeight;
    rect.x = (index % lineNum) * rect.width;
    rect.y = contentsHeight - ((3 - Math.floor(index / lineNum)) * lineHeight);

    rect.x2 = rect.x + rect.width;
    rect.y2 = rect.y + rect.height;

    rect.key = key; //按键的原本值
    rect.text = key === " " ? "空格" : key;//按键的显示值
    rect.isKey = true;//该矩形区域是按键区域
    return rect;
}

2.3.2 功能按键的区域绘制
切换键盘模式的切换键,切换中文模式候选字的上/下一页建,每次点击删除拼音末尾字符或输入框末尾字符的删除键,清空拼音和输入框的清空键,确认键,取消键;
其它按键可按需添加,最后实现其对应功能即可。

//所有按钮矩形区域
Window_Keyboard.prototype.createButtonData = function(){
    let buttons = ["上一页","下一页","删除", "清空", "确认", "取消"];
    //输入模式不固定的时候加入该按键以切换输入模式
    if(!this._modeFixed){ buttons.unshift("切换")}

    this._buttons = [];
    let len = buttons.length;
    buttons.forEach((button, index) => {
        this._buttons.push(this.buttonRect(index, button, len));
    }, this);
}
//根据按钮索引配置对应的矩形区域,整个区域置于窗体底部按键区域的上部
Window_Keyboard.prototype.buttonRect = function(index, button, len){
    let contentsWidth = this.contentsWidth() ;
    let contentsHeight = this.contentsHeight();
    let lineHeight = this.lineHeight();

    let rect = new Rectangle();
    //根据按钮总数动态获取宽
    rect.width = contentsWidth / len; 
    rect.height = lineHeight;
    rect.x = (index % len) * rect.width;
    //按键区占3行,在按键区上面则y轴定位要再加1等于4;
    rect.y = contentsHeight - (4 * lineHeight); 

    rect.x2 = rect.x + rect.width;
    rect.y2 = rect.y + rect.height;

    rect.key = button;
    //当该按键为切换时显示的文本为输入模式的文本
    rect.text = button == "切换" ? this._modeText : button;
    rect.isButton = true;//该区域是按钮区域
    return rect;
}

2.3.3 候选字区域绘制

//所有候选字矩形区域
Window_Keyboard.prototype.createWordData = function(){
    if(this._phonetic){
        //通过拼音在字典管理器获取字的集合
        this._wordsbase = DictionaryManager.getDictionaryList_Zh_Cn(this._phonetic);
    }else{
        this._wordsbase = [];
    }
    this.updateWordData();
}
//根据索引切换候选字
Window_Keyboard.prototype.updateWordData = function(){
    let listbase = this._wordsbase;
    let list = [];
    this._words = [];
    //存在候选字且当前索引页没有候选字时降低索引值以保证有候选字展示
    while(listbase.length > 0){
        list = listbase.slice(this._wordsIndex * 9, (this._wordsIndex + 1) * 9);
        if(list.length > 0){
            break;
        }
        this._wordsIndex -=1;
    }
    list.forEach((word, index) => { this._words.push(this.wordRect(index, word))}, this);
    this.refresh();

}
//根据候选字索引配置对应的矩形区域,整个区域置于窗体底部按钮区域的上部
Window_Keyboard.prototype.wordRect = function(index, word){
    let contentsWidth = this.contentsWidth() ;
    let contentsHeight = this.contentsHeight();
    let lineHeight = this.lineHeight();

    let rect = new Rectangle();
    //一页显示9个候选字
    rect.width = contentsWidth / 9;
    rect.height = lineHeight;
    rect.x = (index % 9) * rect.width;
    rect.y = contentsHeight - (5 * lineHeight);

    rect.x2 = rect.x + rect.width;
    rect.y2 = rect.y + rect.height;

    rect.key = word;
    rect.isWord = true; //该区域是候选字区域
    return rect;
}

2.3.4 拼音矩形区域

//拼音区域,整个区域置于窗体底部候选字区域的上部
Window_Keyboard.prototype.phoneticRect = function(){
    let contentsWidth = this.contentsWidth() ;
    let contentsHeight = this.contentsHeight();
    let lineHeight = this.lineHeight();

    let rect = new Rectangle();
    rect.width = contentsWidth;
    rect.height = lineHeight;
    rect.x = 0;
    rect.y = contentsHeight - (6 * lineHeight);

    rect.x2 = rect.x + rect.width;
    rect.y2 = rect.y + rect.height;

    rect.key = this._phonetic;
    rect.isInputChar = true; //该区域是拼音区域
    return rect;
}

2.3.5 输入框矩形区域

//输入框区域
Window_Keyboard.prototype.inputRect = function() {
    let rect = new Rectangle();
    rect.width = this.width * 0.8;
    rect.height = this.lineHeight();
    rect.x = (this.width - rect.width) / 2;
    rect.y = this.standardPadding() + rect.height * 5;
    return rect;
}

2.4 各按键区域数据已经能获取到了,接下来在合适的时机去获取并绘制

加入能设置窗体模式的函数, 在设置完成模式数据后调用createAllLayout创建各按键区域

//设置窗体的模式
Window_Keyboard.prototype.setMode = function(mode, fixed) {
    this._mode = mode;
    this._modeFixed = !!fixed; //是否固定模式
    switch (this._mode) {
        case Window_Keyboard.MODE_Zh_cn: this._modeText = "中文"; break;
        case Window_Keyboard.MODE_BE: this._modeText = "大写"; break;
        case Window_Keyboard.MODE_SE: this._modeText = "小写"; break;
        case Window_Keyboard.MODE_N: this._modeText = "数字"; break;
    }
    //设置及更换模式时需要清空的数据
    this._wordsIndex = 0;
    this._words = [];
    this._phonetic = "";
    //重新创建所有按键布局
    this.createAllLayout();
}
Window_Keyboard.prototype.setChinese = function(fixed) {
    this.setMode(Window_Keyboard.MODE_Zh_cn, fixed);
}
Window_Keyboard.prototype.setBigEnglish = function(fixed) {
    this.setMode(Window_Keyboard.MODE_BE, fixed);
}
Window_Keyboard.prototype.setSmallEnglish = function(fixed) {
    this.setMode(Window_Keyboard.MODE_SE, fixed);
}
Window_Keyboard.prototype.setNumber = function(fixed) {
    this.setMode(Window_Keyboard.MODE_N, fixed);
}

绘制区域,由createAllLayout,drawAllKeyItem,drawInputText三部分

//不需要频繁刷新的按键配置
Window_Keyboard.prototype.createAllLayout = function() {
    this.createButtonData();
    this.createKeysData();
    this.refresh();
}

Window_Keyboard.prototype.refresh = function() {
    //刷新时重新创建整个区域
    this.createContents();
    this.drawAllKeyItem();
}
//获取所有已经配置好矩形的区域数据,文本居中显示
Window_Keyboard.prototype.drawAllKeyItem = function() {
    let list = this.allKeys();
    list.forEach(rect => {
        this.drawText(rect.text || rect.key, 
            rect.x, rect.y, 
            rect.width, "center");
    }, this);
    //单独绘制输入框的数据
    this.drawInputText();
}
//获取所有已经配置好的区域矩形,拼音矩形区域
Window_Keyboard.prototype.allKeys = function() {
    let list = this._words.concat(this._buttons).concat(this._keys);
    list.push(this.phoneticRect())
    return list;
}

//绘制输入框的数据
Window_Keyboard.prototype.drawInputText = function() {
    let rect = this.inputRect();
    //清空该区域
    this.contents.clearRect(rect.x, rect.y, rect.width, rect.height);
    //绘制该区域背景
    this.contents.fillRect(rect.x, rect.y, rect.width, rect.height, this.gaugeBackColor());
    this.drawText(this._text, rect.x, rect.y, rect.width, "center");
}

2.5 窗体展示

当前输入法窗体展示效果由标题场景改造(文件rpg_scenes.js标题场景Scene_Title)。因为使用简单,所以你可以在任意合适的场景按照下面的例子加入即可。
2.5.1:场景搭建时在最后加入输入法窗体this.createKeyboardWindow();

Scene_Title.prototype.create = function() {
    Scene_Base.prototype.create.call(this);
    this.createBackground();
    this.createForeground();
    this.createWindowLayer();
    this.createCommandWindow();
    this.createKeyboardWindow();//<-这里
};

Scene_Title.prototype.createKeyboardWindow = function() {
    let win = new Window_Keyboard();
    win.setChinese();//创建时设为中文输入模式
    win.name = "keyboardWindow";
    this._keyboardWin = win;
    this.addChild(win);
}

2.5.2:在命令窗体创建时的函数createCommandWindow中加入一条指令
this._commandWindow.setHandler('input', this.commandInput.bind(this));,并实现其绑定的commandInput函数, 也就是在点击该指令时的行为处理:

Scene_Title.prototype.createCommandWindow = function() {
    this._commandWindow = new Window_TitleCommand();
    this._commandWindow.setHandler('newGame',  this.commandNewGame.bind(this));
    this._commandWindow.setHandler('continue', this.commandContinue.bind(this));
    //打开输入法窗体的指令'input'
    this._commandWindow.setHandler('input',  this.commandInput.bind(this)); 
    this._commandWindow.setHandler('options',  this.commandOptions.bind(this));
    this.addWindow(this._commandWindow);
};
Scene_Title.prototype.commandInput = function() {
    win = this._keyboardWin;
    win.setChinese();
    win.setCallWindow(this._commandWindow);
    win.setText("测试文字");
    win.setCallFun(this.keyboardOk.bind(this));
};
Scene_Title.prototype.keyboardOk = function(text) {
    //控制台打印输入法窗体返回的文本;
    console.log(text);
};

2.5.3:指令在窗体中的文本显示。
找到指令窗体(rpg_windows.js文件Window_TitleCommand窗体),在命令列表制作函数中加入数据:

Window_TitleCommand.prototype.makeCommandList = function() {
    this.addCommand(TextManager.newGame,   'newGame');
    this.addCommand(TextManager.continue_, 'continue', this.isContinueEnabled());
    //显示的文本,指令需要和场景的一致为input
    this.addCommand('测试内置键盘',   'input');
    this.addCommand(TextManager.options,   'options');
};

2.5.4:界面样式(按键功能未实现版)
完成以上步骤后,就可以启动游戏查看效果了。

【RPG Maker MV】制作一个简易的中文输入插件( ̄︶ ̄)↗

点击指令“测试内置键盘效果”,因为键盘是采用addChild加入到场景的,所以是独立的一层,不会覆盖抹除下层的指令窗体,因此能透过键盘看到下层的窗体

【RPG Maker MV】制作一个简易的中文输入插件( ̄︶ ̄)↗

2.6 按键的功能实现

这里只实现鼠标点击效果,实体键盘操作效果和按钮操作效果可用同样的方式实现;
2.6.1: 确认鼠标为有效操作,在update中加入点击处理,缺少的函数isOpenAndActive,isTouchedInsideFrame,isContentsArea可以在Window_Selectable中找到并且直接copy过来使用;

Window_Keyboard.prototype.update = function() {
    Window_Base.prototype.update.call(this);
    //处理鼠标点击
    this.processTouch();
}
Window_Keyboard.prototype.processTouch = function() {
    //当前窗体打开并活跃状态的校验
    if (!this.isOpenAndActive()) return;
    //进行了鼠标点击并且在窗体区域内
    if (TouchInput.isTriggered() && this.isTouchedInsideFrame()) {
        this.onTouch();
    } else if (TouchInput.isCancelled()) {
        this.close();
    }
    
};

Window_Keyboard.prototype.onTouch = function() {
    let x = this.canvasToLocalX(TouchInput.x)- this.padding, 
    y = this.canvasToLocalY(TouchInput.y)- this.padding;
    
    let obj = null;
    let rect = null;
    if (this.isContentsArea(x, y)) {
        let list = this.allKeys();
        for (var i = 0; i < list.length; i++) {
            rect = list[i];
            if (x >= rect.x && y >= rect.y && x < rect.x2 && y < rect.y2) {
                //找到点击的区域
                obj = rect;
                break;
            }
        }
    }
    if(obj){
        SoundManager.playCursor();
        //执行对应区域的效果
        this.execKey(obj);
    }
};
//右键处理关闭该窗体
Window_Keyboard.prototype.close = function(isCallBack) {
    //当窗体点击确认按钮并且有回调函数时将当前窗体的文本返回;
    if(isCallBack){
        if(this._callFun){
            this._callFun(this._text);
        }
    }
    //当前窗体由父窗体唤醒时,关闭当前窗体的同时唤醒父窗体
    if(this._callWindow){
        this._callWindow.activate();
        this._callWindow.show();
    }
    this.hide();
}

2.6.2:确定当前点击为有效操作后找到对应的点击区域
按键处理分为三个部分:1.点击候选字区域,2。点击功能按钮区域,3.点击字母/数字按键区域;

Window_Keyboard.prototype.execKey = function(obj) {
    //点击这三个区域有光标亮显效果
    if(obj.isWord || obj.isKey || obj.isButton){
        this.setCursorRect(obj.x, obj.y, obj.width, obj.height);
    }
    if(obj.isWord){//处理候选字
        this.exec_Word(obj);
    }else if(obj.isKey){//处理按键
        this.exec_Key(obj);
    }else if(obj.isButton){//处理按钮
        this.exec_Button(obj);
    }
}

候选字区域:当候选字点击后,需要将选中的文本追加到输入框区域中,然后清空当前拼音输入

Window_Keyboard.prototype.exec_Word = function(obj) {
    this._text += obj.key; //追加文本
    this._phonetic = "";    //清空拼音
    this.drawInputText();//重绘输入框数据
}

功能按钮区域:功能按键需要逐个处理

Window_Keyboard.prototype.exec_Button = function(obj) {
    switch(obj.key){
        case "切换": 
            //点击该按钮顺序切换输入模式
            switch(this._mode){
                case Window_Keyboard.MODE_Zh_cn: this.setBigEnglish(); break;
                case Window_Keyboard.MODE_BE: this.setSmallEnglish(); break;
                case Window_Keyboard.MODE_SE: this.setNumber(); break;
                case Window_Keyboard.MODE_N: this.setChinese(); break;
            }
            break;
        case "上一页": 
            //点击该按钮将候选字分页索引往前减少
            if(this._wordsIndex > 0) {
                this._wordsIndex -= 1
                this.createWordData();
            }; 
        break;
        case "下一页": 
            //点击该按钮将候选字分页索引往后增加
            this._wordsIndex += 1
            this.createWordData();
            break;
        case "删除": 
            //点击该按钮将如果有拼音就抹除拼音最后一个字符,没有拼音就抹除输入框最后一个字符
            let text = this._phonetic;
            if(text.length > 0){
                this._phonetic = text.substring(0, text.length - 1);
            }else if(this._text){
                let text = this._text;
                this._text = text.substring(0, text.length - 1);
            }
            //需要重置索引及候选字数据
            this._wordsIndex = 0;
            this.createWordData();
        break;
        case "清空": 
            //清除所有并重新绘制该窗体
            this._text = "";
            this.clear();
            this.createAllLayout();
        break;
        //以下按钮点击后都将关闭输入框窗体,传入一个参数用于后续操作
        case "确认": this.close(true) ; break;
        case "取消": this.close(false); break;
    }
}

字母/数字按键区域:当窗体的输入模式是中文时,此时的按键应追加到拼音变量中,同时去字典管理器获取匹配的所有文本数据,其它情况仅需将按键的文本值直接追加到输入框中即可

Window_Keyboard.prototype.exec_Key = function(obj) {
    //是中文模式,并且当前点击的不是空格按键
    if(this._mode == Window_Keyboard.MODE_Zh_cn && obj.text != "空格"){
        this._phonetic += obj.key;//追加到拼音变量
        this._wordsIndex = 0;//拼音的变更需要重置候选字分页索引
        this.createWordData();//根据拼音重新构建候选字矩形区域
    }else{
        this._text += obj.key;//直接追加到输入框
        this.refresh();
    }
}

3.结尾

到这里整个输入法窗体就完成了,在任意场景制作一个选项或者按钮按照步骤2.5的方式就能完成调用。除此之外,可以在drawAllKeyItem函数中加入背景图片绘制,使每个按钮都有背景图;也可以加入其他语言字典,设置对应的语言模式; 还可以把字典管理器的每个字细分为一个个对象集合,在其中加入一个键统计每个字的使用次数,再按照使用次数排序显示到候选字区域中等等;

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...