zoukankan      html  css  js  c++  java
  • 《游戏脚本的设计与开发》-(RPG部分)3.8 通过脚本来自由控制游戏(一)

    注意:本系列教程为长篇连载无底洞。半路杀进来的朋友,假设看不懂的话。请从第一章開始看起。文章文件夹请点击以下链接。

    http://blog.csdn.net/lufy_legend/article/details/8888787


    一,内容预览

    算起来,游戏脚本系列文章已经非常久没更新了,尽管该系列文章更新缓慢,可是确实还是能够帮到一些朋友。前段时间。仅仅由于做毕业设计通过邮件联系我的就有4位学生。有鉴于此。我还是挤点儿时间来继续慢慢更新一下了。另外,我想再声明一下,眼下该脚本引擎还处在移植开发阶段,所以功能优先,当中有大量须要优化的地方还没有处理,所以在手机上測试的时候,在一些性能比較差的手机上。假设效果不太好的话,不要奇怪。

    本节来介绍一下怎样通过脚本来控制正在执行的游戏。
    一个RPG游戏中会触发各种各样的剧情,让某个人物的动作改变,或者让某个人移动到还有一处,或者让某个人物从战场上消失,或者会播放一段动画,或者会切换游戏场景,等等吧,这些都要通过脚本来动态的控制。
    我在序章中就已经说了,一个优秀的游戏脚本,通常能控制游戏中的一切。

    本次先来实现一下几个游戏指令,以后会再进行扩展。

    ・人物角色依照绝对坐标进行移动
    ・人物角色以自己为參照物,依照相对坐标进行移动
    ・人物角色以指定人物为參照物,依照相对坐标进行移动
    ・人物方向和动作的改变(等待动作结束)
    ・人物方向和动作的改变(不等待动作结束)
    ・人物角色进入场景
    ・人物角色离开场景
    当然,还有最主要的对话游戏指令,这个已经在之前实现过了,所以能够直接使用。还有我上面提到的或者没有提到的其它指令。以后可能会继续进行扩展。可是RPG的游戏指令是列举不完的,大家须要做的是掌握方法。然后依据自己的实际需求来进行扩展。


    二。功能屏蔽

    正在执行的游戏中。假设没有触发特定的剧情,玩家们使用鼠标或者键盘来进行游戏。可是一旦触发了剧情。在剧情结束之前,就须要游戏依照脚本指令来进行,这时候可能须要屏蔽原来的鼠标或者键盘功能,比方点击地图,主角会发生移动,等等,或者控制菜单有时候也会禁用,当然。这个看自己的须要了。总之,须要这么一条脚本指令来屏蔽一些功能,然后让游戏依照制作者期望的方向进行发展。
    这个功能实现起来并不难,仅仅须要设定一个变量,游戏中的须要屏蔽的一些功能。依据这个变量的值来决定是否有效,然后通过脚本来改变这个变量的值来实现功能屏蔽。
    先来规定以下的脚本
    RPGRunMode.set(1);
    RPGRunMode.set(0);
    參数为1的时候表示功能屏蔽,參数为0的时候表示解除功能屏蔽。
    首先,在LRPGObject.js中建一个变量
    LRPGObject.runMode = false;
    默认是false,表示剧情模式没有开启,也就是不须要屏蔽功能,反之。则表示开启了剧情模式。也就须要屏蔽功能了。
    而脚本解析部分也能够非常轻松的实现。
    /*
     * LRPGRunMode.js
     **/
    LRPGRunMode = function(){};
    LRPGRunMode.analysis=function(value){
    	var start = value.indexOf("(");
    	var end = value.indexOf(")");
    	var params = value.substring(start+1,end).split(",");
    	switch(value.substr(0,start)){
    		case "RPGRunMode.set":
    			LRPGObject.runMode = (parseInt(params[0]) == 1);
    			LGlobal.script.analysis();
    			break;
    		default:
    			LGlobal.script.analysis();
    	}
    };
    然后。在须要屏蔽的功能的代码中,增加以下一句话就能够实现屏蔽了。
    if(LRPGObject.runMode)return;

    三,人物角色移动指令

    控制人物角色移动的时候,大致能够分为以下三种情况。
    ・人物角色依照绝对坐标进行移动
    ・人物角色以自己为參照物,依照相对坐标进行移动
    ・人物角色以指定人物为參照物。依照相对坐标进行移动
    以下一个个来看
    1,首先看一下[人物角色依照绝对坐标进行移动],就是向绝对坐标的移动,规定脚本例如以下。

    //角色ID,坐标x,坐标y。是否等待
    RPGCharacter.moveTo(2,60,13,1);
    先不说这个脚本怎样进行解析。

    首先,先在MapController中增加一个控制角色的行走的函数,然后在解析脚本的时候仅仅须要调用这个函数即可了。控制人物行走的代码,在实现控制主角行走的时候已经有了,如今仅仅须要稍加调整,把主角换成指定的角色(角色ID)就能够了,改动后的部分代码例如以下。

    MapController.prototype.characterMoveTo = function(chara,cx,cy,callback){
    	var self = this;
    	chara = self.getCharacter(chara);
    	if(!chara)return;
    	if(chara.hasEventListener(Character.MOVE_COMPLETE)){
    		chara.removeEventListener(Character.MOVE_COMPLETE);
    	}
    	var coordinate = chara.getTo();
    	var fx = coordinate[0] , fy = coordinate[1];
    	var returnList = self.query.queryPath(new LPoint(fx,fy),new LPoint(cx,cy));
    	if(returnList.length > 0){
    		chara.setRoad(returnList);
    		if(callback){
    			chara.addEventListener(Character.MOVE_COMPLETE,callback);
    		}
    	}
    };
    MapController.prototype.getCharacter = function(value){
    	var self = this;
    	if(LString.isInt(value)){
    		var childList = self.view.charaLayer.childList,child;
    		for(var i=0,l=childList.length;i<l;i++){
    			child = childList[i];
    			if(value != child.index)continue;
    			return child;
    		}
    	}else if(typeof value == "object"){
    		return value;
    	}
    	return null;
    };
    有了这个函数。解析前面的脚本就简单多了。看以下的解析代码。
    LRPGCharacter.moveTo = function (value,start,end){
    	var params = value.substring(start+1,end).split(","), wait;
    	if(params.length == 3){
    		wait = false;
    	}else{
    		wait = (parseInt(params.pop()) == 1);
    	}
    	//params:index,x,y
    	LRPGObject.RPGMap.characterMoveTo.call(LRPGObject.RPGMap,params[0],parseInt(params[1]),parseInt(params[2]),LRPGCharacter.getMoveCallback(wait));
    };
    LRPGCharacter.getMoveCallback = function (wait){
    	var lineValue, callback = LGlobal.script.analysis.bind(LGlobal.script);
    	if(!wait && LGlobal.script.lineList.length > 0){
    		lineValue = LMath.trim(LGlobal.script.lineList[0]);
    		if(lineValue.indexOf("RPGCharacter.move") == 0){
    			callback = null;
    			LGlobal.script.analysis();
    		}
    	}
    	return callback;
    };
    上面的LRPGObject.RPGMap.characterMoveTo.call就是调用前面已经准备好的角色移动的函数了。须要说明的时候。里面还出现了一个新的函数getMoveCallback。
    这是由于。有时候。人物角色不是一个一个发生移动的,可能须要同一时候让多个人物角色发生移动,这种话,一行移动脚本解析完之后。假设该行脚本设定为不须要等待,则须要检查一下下一行的脚本是不是也是人物移动,假设是的话,就马上解析下一行脚本,如此循环。假设脚本指令发生了改变,或是设置了须要等待。就将最后一个移动指令的回调函数设定为LGlobal.script.analysis。这样最后一个人物移动完之后,就会自己主动解析下一行的脚本了。
    效果例如以下。



    2,接着看[人物角色以自己为參照物,依照相对坐标进行移动]规定脚本例如以下。
    //角色ID,相对坐标x,相对坐标y
    RPGCharacter.move(2,-4,0);
    相同,先在MapController中增加对应的函数。由于已经有了前面的characterMoveTo函数,所以仅仅须要计算好对应的绝对坐标,然后调用characterMoveTo函数即可,代码例如以下。
    MapController.prototype.characterMove = function(chara,cx,cy,callback){
    	var self = this;
    	chara = self.getCharacter(chara);
    	self.characterMoveToCharacter(chara,chara,cx,cy,callback);
    };
    接下来是脚本解析。

    LRPGCharacter.move = function (value,start,end){
    	var params = value.substring(start+1,end).split(","), wait;
    	if(params.length == 3){
    		wait = false;
    	}else{
    		wait = (parseInt(params.pop()) == 1);
    	}
    	//params:index,x,y
    	LRPGObject.RPGMap.characterMove.call(LRPGObject.RPGMap,params[0],parseInt(params[1]),parseInt(params[2]),LRPGCharacter.getMoveCallback(wait));
    };
    效果例如以下。


    3。再看[人物角色以指定人物为參照物,依照相对坐标进行移动]。规定脚本例如以下。
    //移动角色ID,參照角色ID,相对坐标x。相对坐标y
    RPGCharacter.moveToCharacter(2,1,0,4);
    相同,先在MapController中增加对应的函数
    MapController.prototype.characterMoveToCharacter = function(chara,toChara,cx,cy,callback){
    	var self = this;
    	chara = self.getCharacter(chara);
    	toChara = self.getCharacter(toChara);
    	var coordinate = toChara.getTo();
    	self.characterMoveTo(chara,coordinate[0] + cx,coordinate[1] + cy,callback);
    };
    脚本解析。
    LRPGCharacter.moveToCharacter = function (value,start,end){
    	var params = value.substring(start+1,end).split(","), wait;
    	if(params.length == 4){
    		wait = false;
    	}else{
    		wait = (parseInt(params.pop()) == 1);
    	}
    	//params:index,index2,x,y
    	LRPGObject.RPGMap.characterMoveToCharacter.call(LRPGObject.RPGMap,params[0],parseInt(params[1]),parseInt(params[2]),parseInt(params[3]),LRPGCharacter.getMoveCallback(wait));
    };
    效果例如以下。


    4。多个人物同一时候移动的时候,脚本例如以下。
    RPGCharacter.move(1,4,0,0);
    RPGCharacter.move(2,4,0,0);
    效果如图。




    四,人物角色方向和动作改变指令

    通常动作发生改变的时候,有以下两种情况的话,应该就够用了。
    ・人物方向和动作的改变(等待动作结束)
    ・人物方向和动作的改变(不等待动作结束)
    规定脚本例如以下。

    //參数:角色ID,动作,方向(或者角色ID),动作是否循环。是否等待
    RPGCharacter.changeAction(2,move,up,0,1);
    RPGCharacter.changeAction(2,move,left,1);
    RPGCharacter.changeAction(1,stand,2,1);
    当第三个參数指定为数值型的时候,表示转向该指定角色。

    而最后一个參数必须是当动作不循环的时候才有效,否则默认不等待。

    先在MapController中提供动作方向改变的函数。

    MapController.prototype.setActionDirection = function(chara,action,direction,loop,callback){
    	var self = this;
    	chara = self.getCharacter(chara);
    	if(LString.isInt(direction)){
    		var toChara = self.getCharacter(direction);
    		var coordinate = chara.getTo();
    		var coordinateTo = toChara.getTo();
    		var angle = Math.atan2(coordinateTo[1] - coordinate[1],coordinateTo[0] - coordinate[0])*180/Math.PI + 180;
    		if(angle <= 22.5 || angle >= 337.5){
    			direction = CharacterDirection.LEFT;
    		}else if(angle > 22.5 && angle <= 67.5){
    			direction = CharacterDirection.LEFT_UP;
    		}else if(angle > 67.5 && angle <= 112.5){
    			direction = CharacterDirection.UP;
    		}else if(angle > 112.5 && angle <= 157.5){
    			direction = CharacterDirection.RIGHT_UP;
    		}else if(angle > 157.5 && angle <= 202.5){
    			direction = CharacterDirection.RIGHT;
    		}else if(angle > 202.5 && angle <= 247.5){
    			direction = CharacterDirection.RIGHT_DOWN;
    		}else if(angle > 247.5 && angle <= 292.5){
    			direction = CharacterDirection.DOWN;
    		}else{
    			direction = CharacterDirection.LEFT_DOWN;
    		}
    	}
    	chara.setActionDirection(action,direction);
    	if(callback){
    		if(loop){
    			callback();
    		}else{
    			var fun = function(){
    				chara.actionObject.anime.stop();
    				chara.removeEventListener(LEvent.COMPLETE,fun);
    				callback();
    			};
    			chara.addEventListener(LEvent.COMPLETE,fun);
    		}
    	}
    };
    上面的代码并不难理解。须要解释一下的是当传入的方向是角色ID的时候,须要通过当前人物角色和指定人物角色的位置。来推断人物详细应该转向哪个方向,上面的代码是通过计算两个人物之间的向量夹角。与八个方向的角度进行比較,接近哪个方向就设定为对应的方向。
    接着,是脚本解析的代码。

    LRPGCharacter.changeAction = function (value,start,end){
    	var params = value.substring(start+1,end).split(","), wait;
    	//params:index,action,direction,loop,wait
    	params[3] = (parseInt(params[3]) == 1);
    	if(params.length == 4){
    		wait = false;
    	}else{
    		wait = (parseInt(params.pop()) == 1);
    	}
    	params.push(LRPGCharacter.getActionCallback(wait));
    	LRPGObject.RPGMap.setActionDirection.apply(LRPGObject.RPGMap,params);
    };
    LRPGCharacter.getActionCallback = function (wait){
    	var lineValue, callback = LGlobal.script.analysis.bind(LGlobal.script);
    	if(!wait && LGlobal.script.lineList.length > 0){
    		lineValue = LMath.trim(LGlobal.script.lineList[0]);
    		if(lineValue.indexOf("RPGCharacter.changeAction") == 0){
    			var start = lineValue.indexOf("(");
    			var end = lineValue.indexOf(")");
    			var params = lineValue.substring(start+1,end).split(",");
    			if(parseInt(params[3]) == 0)return callback;
    			callback = null;
    			LGlobal.script.analysis();
    		}
    	}
    	return callback;
    };
    上面的getActionCallback函数是为了实现同一时候处理多个人物的动作或方向,当指定动作不等待的时候,会紧接着解析下一行脚本。
    效果如图。


    同一时候控制多个角色的动作的时候,脚本例如以下。

    RPGCharacter.changeAction(1,stand,2,1,0);
    RPGCharacter.changeAction(2,stand,1,1,0);
    效果例如以下。



    五,最后人物角色进入和离开场景的指令

    首先规定脚本例如以下。

    //人物角色离开场景
    //參数:角色ID
    RPGCharacter.remove(2);
    //人物角色进入场景
    //參数:角色ID。方向,动作,坐标x。坐标y,是否可控
    RPGCharacter.add(2,stand,down,52,13,false);
    对应的MapController中处理例如以下。代码非常easy就不多解释了。
    MapController.prototype.addCharacter=function(index,action,direction,x,y,ishero,callback){
    	var self = this;
    	self.view.addCharaLayer(index,action,direction,x,y,ishero);
    	if(typeof callback == "function")callback();
    };
    MapController.prototype.removeCharacter=function(index,callback){
    	var self = this;
    	self.view.removeCharaLayer(index);
    	if(typeof callback == "function")callback();
    };
    在MapView中,增加角色是原来就有的,以下仅仅看一下移除角色部分。代码例如以下。
    MapView.prototype.removeCharaLayer=function(index){
    	var self = this;
    	var childList = self.charaLayer.childList,child;
    	for(var i=0,l=childList.length;i<l;i++){
    		child = childList[i];
    		if(index == child.index){
    			self.charaLayer.removeChildAt(i);
    			break;
    		}
    	}
    };
    这部分的脚本的解析部分尤其简单,加上本节其它代码,完整的脚本解析能够直接打开以下的连接看一下。
    人物角色离开场景效果

    人物角色进入场景效果


    六。小结

    还是我刚開始说的那样,RPG的脚本指令是列举不完的,关键是了解本节所介绍的方法,然后就能够自己依据须要进行扩展了。
    本节所实现的功能,能够打开以下连接,点击关羽右边的那个NPC来进行測试。

    最后,给出本次的代码下载:
    https://github.com/lufylegend/lsharp/archive/3.8.zip

    预告:RPG部分下一节可能会讲一下变量的应用,简介一下游戏的读档和存档的原理。

    《游戏脚本的设计与开发》系列文章文件夹

    http://blog.csdn.net/lufy_legend/article/details/8888787


    本章就说到这里,欢迎继续关注我的博客

    转载请注明:转自lufy_legend的博客http://blog.csdn.net/lufy_legend

  • 相关阅读:
    Centos系统修改时间临时和永久生效
    Oracle数据泵恢复用户数据实例
    Oracle修改用户密码错误次数及解锁用户
    MongoDB的启动与停止
    pip常用命令
    mysql执行拉链表操作
    Python实现人脸识别
    Mysql触发器学习
    WordCount程序
    Java学习--多态
  • 原文地址:https://www.cnblogs.com/blfbuaa/p/6888792.html
Copyright © 2011-2022 走看看