回合制战斗设计四 资源加载和Soldier初始化及攻击动画的实现[本人原创,转载请注明出处]如前文所述,Soldier下有多个动作资源需要加载。如果战斗人物比较多,就需要提前加载战斗相关的资源。
我们首先要构造资源的格式,一个动作的SWF的名字可以为:id_action.swf,比如naruto_attack.swf,表示为
naruto的攻击动作swf。
我事先使用神仙道资源做了一个naruto的3个动作,naruto_attack.swf,naruto_defense,naruto_stand.swf.
如何做这个资源swf呢?使用JSFL可以批量生成SWF的,并且要保持一致性。
目前这三个swf都是手动编辑的,内部都是导出BMP为AnimateData对象,并且实现IAnimateData接口。每个动作图片的大小和序列数量是不一样的,故实际上,每个动作都需要单独创建一个AnimateData对象,用来生成各自动作的SWF,这些使用脚本+JSFL是可以实现的,难度实际上也不大,后续如果有时间我会把这个脚本贡献出来,当然也欢迎牛人贡献一下。
BulkLoader资源加载如下:
bulkloader=new BulkLoader("fight");
bulkloader.addEventListener(BulkProgressEvent.COMPLETE,onComplete);
bulkloader.add("assets/naruto_defense.swf",{"id":"naruto_defense"});
bulkloader.add("assets/naruto_attack.swf",{"id":"naruto_attack"});
bulkloader.add("assets/naruto_stand.swf",{"id":"naruto_stand"});
bulkloader.start();
另外为了方便的获得资源中类,我定义两个一个AnimateLoader对象
AnimateLoader
var _loader:BulkLoader
public function AnimateLoader(loader:BulkLoader){
this._loader=loader;
}
public function getAssetClass(id:String):Class{
var domain:ApplicationDomain = this._loader.getMovieClip(id).loaderInfo.applicationDomain;
if(domain) return domain.getDefinition("AnimateData") as Class;//资源中对BMP进行导出
return null;
}
//这里在compete的时候,我生成了2个Soldier
function onComplete(e:BulkProgressEvent):void{
soldierA=new Soldier();
soldierA.init(animateLoader,"naruto");
this.addChild(soldierA);
soldierA.x=400;
soldierB=new Soldier(Soldier.RIGHT);
soldierB.init(animateLoader,"naruto");
soldierB.x=300;
this.addChild(soldierB);
}
考虑到人的朝向,Soldier添加direction来出来朝向问题
static var LEFT:int=1;//面朝左
static var RIGHT:int=-1;
vardirection:int;
init方法如下:
public function init(loader:AnimateLoader,id:String):void{
var AssetClass:Class;
var animate:Animate
for each(var action:String in actions){
AssetClass = loader.getAssetClass(id+"_"+action);
animate=new Animate(new AssetClass() as IAnimateData);
actionDict[action]=animate;
animate.scaleX=direction;
if(action==Command.STAND){
animate.loop=true;
}else{
animate.addStopListener(onStop);
}
}
accept(Command.create(Command.STAND));//默认为待命状态
}
actionDict就是普通的Object对象,用来保存所有动作引用。
对于Soldier动作名都由actions数组保存。
static var actions:Array=[Command.ATTACK,Command.DEFENCE,Command.STAND];
这里注意:
1 STAND需要loop为true
2 使用scaleX处理direction
3 对于非Stand动作,添加onStop的监听
有了Soldier A和B就能实现A攻击B
我们加一个键盘监听:
this.stage.addEventListener(KeyboardEvent.KEY_UP,onKeyUp);
function onKeyUp(e:KeyboardEvent):void{
var code:int = e.charCode;
var cmd:Command;
switch(code){
case 97://按键a
cmd=new Command(Command.ATTACK,soldierB);
soldierA.accept(cmd);
break;
default:
break;
}
}
这样就能实现一个普通攻击动画了,虽然目前现在看起来还是隔空打击。
回合制战斗设计五 自动回合的战斗设计
[本人原创,转载请注明出处]
前面我们已经可以控制两个玩家的交互动作了,下面我们可以考虑战斗了。
回合制一个基本特点是轮询机制
1.选择攻击者
2.选择被攻击者
选定这两个soldier之后,就可以构成一个攻击回合。
对于自动回合制,这部分处理通常在后端处理,对于交互性的可能要更为复杂点。
先考虑自动回合制,自动回合意味着前端仅仅播放动画,执行后端已经生成好的Command。
我们设定一个Battle对象用于处理战斗过程。
基本逻辑:Battle双方为A和B(左右各一个Soldier),其中一个Health<=0,回合结束,显示战斗结果。
1.初始化A,B站位(后面我们会加入阵型处理)
2.执行后端的命令
3.显示战斗结果
Battle中Soldier有两个,攻击方和被攻击方法,我们是A和B来区分
var soldierDict={};
soldierDict['A']=soldierA;
soldierDict['B']=soldierB;
soldierA和soldierB的生成请参考前面文章。
一系列的cmd数据我们定义为一个数据
var cmdTestData:Array=[
{"attacker":"A","target":"B","hurt":310,"cmd":"attack"},
{"attacker":"B","target":"A","hurt":90,"cmd":"attack"},
{"attacker":"A","target":"B","hurt":370,"cmd":"attack"},
{"attacker":"B","target":"A","hurt":360,"cmd":"attack"}//这个CMD执行完之后,B应该挂了
]
动画播是按次序的播放的,我们将Command数据解析成单个动作数据,即ActionItem数据ActionItem结构:private var mSoldiers:Array;//动作执行者,可能有多个。如果是群攻,防御者将为多个
private var mActionType:String;//动作类型
对于回合制战斗,一个Command通常包括两个ActionItem,一个是攻击者,另外一个是防守者。更为复杂的情况我们后面再讨论。//解析命令数据为ActionItempublic function interpret(data:Array):void{
var cmdData:Object;
var attacker:Soldier;
var defenser:Soldier;
var item:ActionItem;
for each(cmdData in data){
attacker=soldierDict[cmdData["attacker"]];
defenser=soldierDict[cmdData["target"]];
item=new ActionItem([attacker],ActionItem.Atk);
this.actionItems.push(item);
item=new ActionItem([defenser],ActionItem.Def);
this.actionItems.push(item);
}
}}解析为之后需要执行这些ActionItem
对于之前Animate和Soldier对象我们需要适当重构一下首先,我们将Animate中对动画的数据处理提取出来,放入到ActionData中,并在ActionData保存
ActionData: private var mAnimateData:Object;
private var mId:String; private var mFrames:Array;
public function ActionData(data:Object,id:String){
this.mAnimateData=data;
this.mId=id;
this.mFrames=new Array();
init();
}
private function init():void{
var count:int=this.mAnimateData.imageCount;
var content:BitmapData=this.mAnimateData.animateContent;
var h:int=content.height;
var w:int=content.width/count;
var bitmapData:BitmapData;
for(var i:int=0;i
bitmapData = new BitmapData(w, h, true, 0);
bitmapData.copyPixels(content, new Rectangle(i*w, 0 , w, h), new Point());
this.mFrames.push(bitmapData);
}
}
Animate只处理动画播放,同时添加一个帧播放结束的函数回调。重构后的主要代码如下:protected static var mActionDataDict:Object={};public function Animate(frameRate:int=12):void{
this.mBitmap=new Bitmap(null,"auto",true);
this.addChild(mBitmap);
this.mFrameRate=frameRate;
}
protected function onTimer(e:TimerEvent):void{
this.mBitmap.bitmapData= this.mFrames[this.mCurrentFrame++];
if (this.mCurrentFrame == this.mFrames.length){
this.mCurrentFrame = 0;
if (!this.mLoop){
this.stop();
}
//very important:flash是顺序执行的,最好是将onTimer执行结束之后再调用
if(this.mFrameEndAction is Function){
this.mFrameEndAction();
}
}
}
private function stop():void{
if(this.mTimer){
this.mTimer.stop();
}
}
public function init(loader:AnimateLoader,id:String):void{
this.mTypeId=id;
var AssetClass:Class;
for each(var action:String in ActionItem.Actions){
if(!mActionDataDict[id+"_"+action]){//保存动画资源
AssetClass = loader.getAssetClass(id+"_"+action);
mActionDataDict[id+"_"+action]=new ActionData(new AssetClass(),id+"_"+action);
}
}
}
protected function getActionData(action:String):ActionData{
return mActionDataDict[this.mTypeId+"_"+action] as ActionData;
}
//播放某个动作
public function play(action:String,loop:Boolean=false):void{
if (this.mTimer){
this.mTimer.stop();
} else {
this.mTimer = new Timer(1000 / this.mFrameRate);
this.mTimer.addEventListener(TimerEvent.TIMER, this.onTimer, false, 0, true);
};
this.mCurrentFrame=0;
this.mLoop=loop;
this.mCurrentAction=action;
var adata:ActionData=getActionData(action);
this.mFrames=adata.frames;
this.mTimer.start();
}
public function set frameEndAction(value:Function):void{
this.mFrameEndAction=value;
}
Soldier继承Animate对象:public class Soldier extends Animate{
public static var LEFT:int=1;//面朝左
public static var RIGHT:int=-1;
public function Soldier(name:String,direction:int=1,rate:int=12){
this.mDirection=direction;
this.soldierName=name;
this.scaleX=this.scaleX*this.mDirection;
this.name=name;
super(rate);
}
override public function init(loader:AnimateLoader,id:String):void{
super.init(loader,id);
standby();//战士默认的动作是待命状态
}
public function standby():void{
play(ActionItem.Standby,true);//默认动作,代码状态始终播放
}
}
Battle对象的作用1. 初始化战场2. 初始化战斗成员3. 初始化战斗数据,生成ActionItem序列4. 执行战斗序列5. 显示战斗结果关键是第四步,如何按顺序的执行战斗序列,我们通过在Animate中的frameEndAction,来判断一个战斗序列是否执行结束。通过添加匿名函数: s.frameEndAction=function():void{
s.frameEndAction=null;
s.standby();
doNextAction();
}可能达到我们的目的。
Battle:/*** main参数为当前战斗舞台,Battle本身不继承Sprite,是一个普通AS类**/public function Battle(main:Sprite,a:Soldier,b:Soldier):void{
soldierDict['A']=a;
soldierDict['B']=b;
soldierLayer=new Sprite();
main.addChild(soldierLayer);
this.soldierLayer.addChild(a);
this.soldierLayer.addChild(b);
this.actionItems=new Array();
}
public function start():void{
interpret(this.cmdDataArr);//解析ActionItem序列
var item:ActionItem=actionItems.shift();
doAction(item);
}
//执行ActionItem序列public function doAction(item:ActionItem):void{
var s:Soldier=item.soldiers.shift();
s.frameEndAction=function():void{ ...//如上
}
s.play(item.type);
if(item.soldiers.length>0){
for each(s in item.soldiers){
s.frameEndAction=function():void{
...//如上
}
s.play(item.type);
}
}
}
//执行下一个序列public function doNextAction():void{
if(actionItems.length==0) return;
var item:ActionItem=this.actionItems.shift();
doAction(item);
}
在主程序中:在资源价值完毕后,onComplete(e:BulkProgressEvent):soldierA=new Soldier("A",Soldier.RIGHT);
soldierA.init(animateLoader,"naruto");
soldierA.x=300;
soldierB=new Soldier("B");
soldierB.init(animateLoader,"naruto");
soldierB.x=500;
var battle:Battle=new Battle(this,soldierA,soldierB);
battle.start();
即可看到一个简单回合战斗过程。