《游戏脚本的设计与开发》-(RPG部分)3.8 通过脚本来自由控制游戏(一)

原创 2014年09月02日 21:31:01

注意:本系列教程为长篇连载无底洞,半路杀进来的朋友,如果看不懂的话,请从第一章开始看起,文章目录请点击下面链接。

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中处理如下,代码很简单就不多解释了。
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;
		}
	}
};
这部分的脚本的解析部分尤其简单,加上本节其他代码,完整的脚本解析可以直接打开下面的连接看一下。
https://github.com/lufylegend/lsharp/blob/3.8/js/lufylegend.lsharp.js
人物角色离开场景效果

人物角色进入场景效果


六,小结

还是我刚开始说的那样,RPG的脚本指令是列举不完的,关键是了解本节所介绍的方法,然后就可以自己根据需要进行扩展了。
本节所实现的功能,可以打开下面连接,点击关羽右边的那个NPC来进行测试。
http://lufylegend.com/demo/test/lsharp/rpg-3.8/index.html

最后,给出本次的代码下载:
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

游戏中的脚本语言原理与发展

作者:陈嘉栋(慕容小匹夫)     源地址:http://www.cnblogs.com/murongxiaopifu/p/4557365.html 从游戏脚本语言说起,剖析Mono所搭...

游戏外挂基本原理及实现

游戏外挂已经深深地影响着众多网络游戏玩家,今天在网上看到了一些关于游戏外挂编写的技术,于是转载上供大家参考    1、游戏外挂的原理    外挂现在分为好多种,比如模拟键盘的,鼠标的,修改...

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

《游戏脚本的设计与开发》-目录&序

简单说,游戏脚本就是依据一定的格式编写的可执行文件,游戏可以通过脚本中自定义的语句来执行相应的逻辑。 举个例子,舞蹈演员随着音乐翩翩起舞,其实音乐在这里就担当了脚本的角色,音乐里记录了每一个舞蹈动作,...

《游戏脚本的设计与开发》-1.6 按钮,脚本的暂停和标签

按钮按钮在任何程序中都是必不可少的,本次先来看看如何脚本来实现按钮的各种功能。文章中要实现的几个脚本如下。/* 游戏脚本的设计与开发 第六章 */ //添加按钮 Button.add(layer01,...

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

《游戏脚本的设计与开发》-1.1 读取和解析一个脚本文件

上一篇《游戏脚本的设计与开发》-序中我介绍了游戏脚本的基本概念和准备工作,本篇来说说具体如何解析一个脚本所谓解析脚本,就是按照自己定义的语法,将每一个脚本命令还原成不同的代码逻辑进行执行,比如,我规定...

微信小程序-利用wxParse将html转为wxml

1、到https://github.com/icindy/wxParse下载2.1 在使用的View中引入WxParse模块var WxParse = require('../../wxParse/w...

《游戏脚本的设计与开发》-(RPG部分)3.7 战斗系统之自动战斗(一)

所谓自动战斗系统就是战斗从开始到结束无需任何操作,其实自动战斗的胜负结果在战斗开始的时候已经决定了,战斗的画面只是还用来显示或者说回放这一战斗的过程,这种战斗方式开发成本较低,而且因为不用长时间的操作...

《游戏脚本的设计与开发》-(RPG部分)3.6 队员列表和人物属性

队员列表的作用就是保存我方能够参战的人员的信息,简单来说一个数组就可以完成。但是我们需要考虑,这个数组里需要保存哪些信息,对于早期简单的RPG游戏来说,比如《勇者斗恶龙》,《吞食天地》等,每个人的相应...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:《游戏脚本的设计与开发》-(RPG部分)3.8 通过脚本来自由控制游戏(一)
举报原因:
原因补充:

(最多只允许输入30个字)