《游戏脚本的设计与开发》- 游戏背包和任务系统

本节重点来讲一下背包系统,以及简单的先来认识一下任务系统。

先看一下背包系统的效果预览:

背包系统在游戏中是必不可少的,在游戏中,所有获得的物品都会储存在背包里面。背包的种类,我一般将它分成两大类,一种是类似于《吞食天地》的“个人背包”,在游戏中每个人物都有一个背包,每个人的背包都互不影响,并且每个人只能使用自己背包中的物品。另一种是“集体背包”,游戏中所有己方的人物都共用一个背包,大多数游戏都采用这种形势。下面我们就来实现一下“集体背包”。

我们先来做一个按钮,用来打开游戏菜单,按钮的添加很简单,界面如下。

根据我制作《三国记》的经验,游戏菜单的UI需要注意的是,按钮要尽量大一些,否则在手机上就很难被点到。先来创建游戏菜单的相关文件。

控制器MenuController,模型MenuModel和视图MenuView。

https://github.com/lufylegend/lsharp/blob/3.5/Controllers/MenuController.js

https://github.com/lufylegend/lsharp/blob/3.5/Models/MenuModel.js

https://github.com/lufylegend/lsharp/blob/3.5/Views/MenuView.js

关于菜单,我们需要有一个下面这样的背景框

并且这个背景框在其他许多地方也都会被用到,所以我们把它做成一个共同的外部类,如下。

https://github.com/lufylegend/lsharp/blob/3.5/Libraries/window/WindowBackground.js

这个类的实现原理如下,将一个只有左上角的图片,通过分割,旋转,拉伸等操作,组合成一个背景框

也就是说,我们只需要多准备几张类似于如下的图片,就可以实现不同的背景框了。

游戏菜单中的按钮,由图标和文字两部分组成,为了方便,我们也封装一个外部类。

https://github.com/lufylegend/lsharp/blob/3.5/Libraries/button/ButtonText.js

接下来准备好相应的菜单按钮图标,在菜单的视图中添加如下一系列代码。

 

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. ......  
  2. var backpack = new ButtonText("backpack","背包",true);  
  3.     backpack.x = startX + xindex*step;  
  4.     backpack.y = startY + yindex*step;  
  5.     self.menuLayer.addChild(backpack);  
  6.     backpack.addEventListener(LMouseEvent.MOUSE_UP, function(event){  
  7.         LTweenLite.to(self.menuLayer,0.3,{x:LGlobal.width,onComplete:function(){  
  8.             self.controller.showBackpack();  
  9.         }});  
  10.     });  
  11. ......  

最后,给画面中的菜单按钮添加打开菜单的点击事件,打开菜单的代码如下。

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. MapController.prototype.openmenuClick = function(){  
  2.     var self = this;  
  3.     self.loadMvc("Menu",self.openmenuComplete);   
  4. };  
  5. MapController.prototype.openmenuComplete = function(){  
  6.     var self = this;  
  7.     var menu = new MenuController();  
  8.     menu.baseView = self.view;  
  9.     self.view.parent.addChild(menu.view);  
  10.     //移动端的时候,为了提高效率,将地图隐藏  
  11.     if(LGlobal.canTouch){  
  12.         self.view.visible = false;  
  13.     }  
  14. };  

下面是菜单效果。

为了好看,我临时添加了背包,装备,武将,任务,设置等几个功能按钮,本次只讲一下背包部分的具体实现,其他部分我们后面再慢慢讲。

 

背包部分我们也为其创建相关的文件。

控制器BackpackController,模型BackpackModel和视图BackpackView。

从文章开头的预览图片中,我们看到恢复用品,武器装备,特殊物品三个按钮其实是tab选项卡,有是否被选中两种状态,所以我们为这个功能封装下面一个类。

https://github.com/lufylegend/lsharp/blob/3.5/Libraries/button/ButtonSelect.js

其中的GetButton是我另创建的一个辅助函数,功能就是返回一个带文字或者不带文字的按钮,代码如下

https://github.com/lufylegend/lsharp/blob/3.5/Helpers/GetButton.js

在背包的视图中首先添加背景框,然后添加类似于下面一系列代码。

 

 

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. ......  
  2.     var closeButton = GetButton(LMvc.datalist["BtnClose01"],null,0);  
  3.     closeButton.x = 710;  
  4.     closeButton.y = 25;  
  5.     self.addChild(closeButton);  
  6.     closeButton.addEventListener(LMouseEvent.MOUSE_UP, function(){  
  7.         self.controller.closeBackpack();  
  8.     });  
  9.       
  10.     var recoveryButton = new ButtonSelect("BtnSelect01",{label:"恢复用品",width:180,height:48},1);  
  11.     recoveryButton.x = 550;  
  12.     recoveryButton.y = 110;  
  13.     self.addChild(recoveryButton);  
  14.     self.recoveryButton = recoveryButton;  
  15.     recoveryButton.addEventListener(LMouseEvent.MOUSE_UP, function(){  
  16.         self.controller.recoveryPropsSelect();  
  17.     });  
  18. ......  

效果如下。

 


按钮选项卡的选中和非选中状态,我们只要像下面这样。

 

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. specialButton.setSelected(true);  

选项卡是否处于选中状态,我们也可以很轻松的判断。

 

 

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. if(specialButton.selected)return;  

好了,接下来为了给背包中添加物品,我们需要先定义物品,在script/initialization文件夹下添加props.json文件,先简单的来定义三个物品。

[ruby] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. {  
  2. "props1":{  
  3.     "Index":1,  
  4.     "Name":"包子",  
  5.     "Type":"Recovery",  
  6.     "Max":99,  
  7.     "Icon":1,  
  8.     "Hp":30,  
  9.     "Mp":0,  
  10.     "Force":0,  
  11.     "Intelligence":0,  
  12.     "Command":0,  
  13.     "Agile":0,  
  14.     "Luck":0,  
  15.     "Introduction":"普通的馒头,可以恢复一点HP。"  
  16.     },  
  17. "props2":{  
  18.     "Index":2,  
  19.     "Name":"天书残卷",  
  20.     "Type":"Special",  
  21.     "Max":1,  
  22.     "Icon":2,  
  23.     "Introduction":"特殊物品之天书残卷,暂时没有详细介绍。"  
  24.     },  
  25. "props3":{  
  26.     "Index":3,  
  27.     "Name":"倚天剑",  
  28.     "Type":"Weaponry",  
  29.     "Max":1,  
  30.     "Icon":3,  
  31.     "Introduction":"倚天剑,介绍略。"  
  32.     }  
  33. }  

几个属性分别代表Index:物品序号,Name:物品名称,Type:物品分类,Max:背包中一个物品位可盛放的最大数量,Icon:图标。这几个属性只是为了能在背包中显示,其他的属性,等用到的时候我再具体说。
为了更方便在游戏程序中操作它们,我们为物品的每个种类,分别创建相应的类。

 

恢复物品

 

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. function PropsRecovery(data){  
  2.     var self = this;  
  3.     self.index = data.Index;  
  4.     self.name = data.Name;  
  5.     self.type = data.Type;  
  6.     self.icon = data.Icon;  
  7.     self.max = data.Max;  
  8.     self.count = 1;  
  9.     self.data = data;  
  10. }  

武器装备

 

 

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. function PropsWeaponry(data){  
  2.     var self = this;  
  3.     self.index = data.Index;  
  4.     self.name = data.Name;  
  5.     self.type = data.Type;  
  6.     self.icon = data.Icon;  
  7.     self.max = data.Max;  
  8.     self.count = 1;  
  9.     self.data = data;  
  10. }  

特殊物品

 

 

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. function PropsSpecial(data){  
  2.     var self = this;  
  3.     self.index = data.Index;  
  4.     self.name = data.Name;  
  5.     self.type = data.Type;  
  6.     self.icon = data.Icon;  
  7.     self.max = data.Max;  
  8.     self.count = 1;  
  9.     self.data = data;  
  10. }  

然后给LRPGObject类添加物品数组,用来盛放所有的物品。

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. LRPGObject.propsList = [];  

我们再继续给LRPGObject类添加两个函数,分别是添加物品和删除物品,操作很简单,就是往propsList里添加和删除元素。

 

 

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. LRPGObject.addProps = function(index){  
  2.     var propsData = LMvc.datalist["props"]["props"+index],i,l,props;  
  3.     if(!propsData)return;  
  4.     for(i=0,l=LRPGObject.propsList.length;i<l;i++){  
  5.         props = LRPGObject.propsList[i];  
  6.         if(props.index == propsData.Index){  
  7.             if(props.count < props.max){  
  8.                 props.count = props.count + 1;  
  9.                 return;  
  10.             }  
  11.         }  
  12.     }  
  13.     switch(propsData.Type){  
  14.         case "Recovery":  
  15.             LRPGObject.propsList.push(new PropsRecovery(propsData));  
  16.             break;  
  17.         case "Special":  
  18.             LRPGObject.propsList.push(new PropsSpecial(propsData));  
  19.             break;  
  20.         case "Weaponry":  
  21.             LRPGObject.propsList.push(new PropsWeaponry(propsData));  
  22.             break;  
  23.     }  
  24. };  
  25. LRPGObject.removeProps = function(index){  
  26.     var propsData = LMvc.datalist["props"]["props"+index],i,l,props;  
  27.     if(!propsData)return;  
  28.     for(i=LRPGObject.propsList.length - 1;i>=0;i--){  
  29.         props = LRPGObject.propsList[i];  
  30.         if(props.index == propsData.Index){  
  31.             props.count--;  
  32.             if(props.count <= 0){  
  33.                 LRPGObject.propsList.splice(i,1);  
  34.                 break;  
  35.             }  
  36.         }  
  37.     }  
  38. };  

当点击不同的选项卡的时候,我们需要先把LRPGObject.propsList数组中的物品筛选出来,如下

 

 

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. BackpackModel.prototype.propsSelect=function(propsType){  
  2.     var self = this,i,l,props;  
  3.     self.data.length = 0;  
  4.     self.dataIndex = 0;  
  5.     for(i=0,l=LRPGObject.propsList.length;i<l;i++){  
  6.         props = LRPGObject.propsList[i];  
  7.         if(props.type == propsType){  
  8.             self.data.push(props);  
  9.         }  
  10.     }  
  11. };  

然后规定几个物品一组,来分页获取需要显示的物品列表。

 

 

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. BackpackModel.prototype.getProps=function(){  
  2.     var self = this,i,l,props,list=[];  
  3.     if(self.dataIndex < 0 || self.dataIndex >= self.data.length){  
  4.         return [];  
  5.     }  
  6.     for(i=self.dataIndex,l=self.data.length>i+self.dataMax?i+self.dataMax:self.data.length;i<l;i++){  
  7.         props = self.data[i];  
  8.         list.push(props);  
  9.     }  
  10.     return list;  
  11. };  

得到了物品列表之后,将物品添加到背包的视图中就行了。

 

 

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. BackpackView.prototype.showProps=function(){  
  2.     var self = this,num,l,icon,props;  
  3.     var list = self.model.getProps();  
  4.     num = 0;  
  5.     l = list.length;  
  6.     self.propsLayer.die();  
  7.     self.propsLayer.removeAllChild();  
  8.     for(var i=0;i<3 && num<l;i++){  
  9.         for(var j=0;j<2 && num<l;j++){  
  10.             props = list[num];  
  11.             icon = new PropsIcon(props);  
  12.             icon.x = self.propsStartX + 120*i;  
  13.             icon.y = self.propsStartY + 135*j;  
  14.             self.propsLayer.addChild(icon);  
  15.             num++;  
  16.         }  
  17.     }  
  18. };  

效果。

接下来就是脚本了,我们如何来利用脚本添加或者删除一个物品呢?看下面的脚本。

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. //添加一个物品,参数:物品序号  
  2.     RPGProps.add(1);  
  3.     //删除一个物品,参数:物品序号  
  4.     RPGProps.remove(2);  

下面是脚本解析,很简单不是吗

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. /* 
  2. * LRPGPropsScript.js 
  3. **/  
  4. LRPGPropsScript = function(){};  
  5. LRPGPropsScript.analysis=function(value){  
  6.     var start = value.indexOf("(");  
  7.     var end = value.indexOf(")");  
  8.     switch(value.substr(0,start)){  
  9.         case "RPGProps.add":  
  10.             LRPGPropsScript.add(value,start,end);  
  11.             break;  
  12.         case "RPGProps.remove":  
  13.             LRPGPropsScript.remove(value,start,end);  
  14.             break;  
  15.         default:  
  16.             LGlobal.script.analysis();  
  17.     }  
  18. };  
  19. LRPGPropsScript.add = function (value,start,end){  
  20.     var params = value.substring(start+1,end).split(",");  
  21.     LRPGObject.addProps(params[0]);  
  22.     LGlobal.script.analysis();  
  23. };  
  24. LRPGPropsScript.remove = function (value,start,end){  
  25.     var params = value.substring(start+1,end).split(",");  
  26.     LRPGObject.removeProps(params[0]);  
  27.     LGlobal.script.analysis();  
  28. };  

好了,背包系统,基本上就是这样了。

下面我们来介绍另一个非常重要的功能,就是任务系统,任务系统其实是比较复杂的,涉及到任务列表,任务奖励,任务的不同阶段的判定等等,这些我们后面会单独写一篇来详细讲解,本次先来利用现有的变量功能来完成一个简单的任务功能。
我假设关羽借了酒店老板一样东西,自己又懒得去还,想让刘备当个跑腿儿的,去帮忙把书还上,然后刘备拿着这个东西去还给酒店老板,然后老板给了刘备两个包子作为奖励。
在这一过程中,我们首先需要判断是否有任务,任务是否在进行中,任务是否已经结束等。

看下面点击关羽时的脚本

 

[ruby] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. function characterclick2();  
  2.     if(@task1000==1);    
  3.         RPGTalk.set(2,0,你知道lufy吗,听说那家伙除了做游戏,啥都不会。);  
  4.         RPGTalk.set(1,0,怪不得啊,哈哈哈。);  
  5.     elseif(@task1001==1);    
  6.         RPGTalk.set(2,0,《天书残卷》交给酒店老板了吗?);   
  7.     else;    
  8.         RPGTalk.set(2,0,这本《天书残卷》是我前两天从酒店老板那里借来的,你能帮我还回去吗?);   
  9.         RPGTalk.set(1,0,好啊,我正想去酒店喝点儿酒呢。);  
  10.         RPGProps.add(2);  
  11.         Var.set(task1001,1);   
  12.         RPGMessageBox.show(得到了《仙书残卷》。请到酒店把它交给酒店老板吧。);  
  13.     endif;   
  14. endfunction;  

变量task1000=1表示任务已经结束,task1001=1表示任务正在进行中,否则就是任务还没有开始。
当任务没有开始的时候,所做的处理是先让刘备接受任务,然后把东西《仙书残卷》交给刘备。然后把变量task1001设置为1,表示任务开启,正在进行。

 

再看点击酒店老板的脚本。

 

[ruby] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. function characterclick6();  
  2.     RPGTalk.set(6,0,欢迎光临小店,客官要点儿什么?);  
  3.     if(@task1001==1);  
  4.         RPGTalk.set(1,0,关羽让我把这本《天书残卷》还给你。);  
  5.         RPGTalk.set(6,0,哎呀,太谢谢了,我正想去跟他要呢。);  
  6.         RPGProps.remove(2);  
  7.         Var.set(task1000,1);   
  8.         Var.set(task1001,2);   
  9.         RPGProps.add(1);  
  10.         RPGProps.add(1);  
  11.         RPGMessageBox.show(获得奖励:两个包子。);  
  12.     else;  
  13.         RPGTalk.set(1,0,我比较关心那边弹琴的美女是谁。);  
  14.         RPGTalk.set(6,0,她天下第一美女貂蝉啊。);  
  15.     endif;  
  16. endfunction;  

当task1001=1任务正在进行的时候,老板会接收刘备拿来的东西,然后给刘备两个包子作为奖励。最后把变量task1000设置为1,表示任务结束。然后为了防止任务再次出发,把task1001设置为2。
好了,上面的脚本的效果如下。


眼尖的朋友可以看到,我在脚本里用到了RPGMessageBox.show这个脚本,这个脚本会弹出提示框,如下

 

MessageBox的代码如下

https://github.com/lufylegend/lsharp/blob/3.5/Helpers/MessageBox.js

脚本解析部分

 

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. /* 
  2. * LRPGMessageBoxScript.js 
  3. **/  
  4. LRPGMessageBoxScript = function(){};  
  5. LRPGMessageBoxScript.analysis=function(value){  
  6.     var start = value.indexOf("(");  
  7.     var end = value.indexOf(")");  
  8.     switch(value.substr(0,start)){  
  9.         case "RPGMessageBox.show":  
  10.             LRPGMessageBoxScript.show(value,start,end);  
  11.             break;  
  12.         default:  
  13.             LGlobal.script.analysis();  
  14.     }  
  15. };  
  16. LRPGMessageBoxScript.show = function (value,start,end){  
  17.     var params = value.substring(start+1,end).split(",");  
  18.     MessageBox.show(params[0]);  
  19. };  

这样,一个简单的任务就完成了,当然这个不是任务系统的全部,这只是我用变量来完成一个任务的简单的小例子,关于任务,我后面再详细说。
另外,有朋友反映,图片不好找,好不容易找到了图片,又不能用,所以我修改了一下Character类和Action类,代码如下

 

https://github.com/lufylegend/lsharp/blob/3.5/Libraries/Character.js

https://github.com/lufylegend/lsharp/blob/3.5/Libraries/Action.js

这样就可以根据实际的图片,在chara.json里定义相关的参数了

https://github.com/lufylegend/lsharp/blob/3.5/script/initialization/chara.json

比如刘备

 

[ruby] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. "peo1":{  
  2.     "Index":1,  
  3.     "Name":"刘备",  
  4.     "Face":1,  
  5.     "R":1,  
  6.     "RRect":[140,95,40,90],  
  7.     "Introduction":"刘备即蜀汉昭烈帝,字玄德,汉中山靖王刘胜的后代,三国时期蜀汉开国皇帝。"  
  8.     }  

RRect表示人物在一个动作图片中的有用范围,

 

另外默认的每贞动作图片的大小是320x240,如果你的图片大小不一样,那还需要定义一下图片的大小,比如下面这样

 

[ruby] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. "peo8":{  
  2.     "Index":8,  
  3.     "Name":"测试",  
  4.     "Face":3,  
  5.     "R":6,  
  6.     "RRect":[10,6,50,82],  
  7.     "RWidth":70,  
  8.     "RHeight":92,  
  9.     "Introduction":"测试"  
  10.     }  

要想看看自己定义的大小是不是正确的话,开启debug模式就可以看到效果了,效果如下

 

好了,还是自己测试一下效果吧。

http://lufylegend.com/demo/test/lsharp/rpg-lsharp-05/index.html

最后,给出本次的代码下载:

https://github.com/lufylegend/lsharp/archive/3.5.zip

Para obtener más información sobre las optimizaciones del compilador, consulte el aviso sobre la optimización.