Nowpaper 十五英寸的世界

Rich Games Developer

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

游戏从出现以来,一直提升着玩家的需求,游戏开发者不止是考虑简单的位置移动,例如将游戏中各种演员做成动画,就可以大大提升游戏的品质,在咱们的这个三国统帅的游戏中,小兵和将领是有各种动作的,这些动作对应动画,让游戏的互动感觉更加优秀,这次使用cocos2d-xna中的CCAnimate来实现角色动画。

首先我们要先准备游戏中所需要的动画资源,按照设计,游戏中至少有这样的角色和职业:主将、小兵(步兵、枪兵、骑兵、弓兵),它们的各种所扮演的角色因所属势力只是样子不一样。

角色的动画祯按照一个规则命名,这样就能方便的管理:

{id}_{n}表示的是正方向,而{id}f_{n}表示的是反方向。比如说

QQ截图20121021222940

A1表示的是角色的id,按照编号的显示动作分别为:

0-3:攻击动作

4-5:行走动作

6-6:站立动作

7-8:死亡动作

在这个实验游戏中,我们有两个势力对阵,分别为义军和黄巾军,按照这样的规则制作了如下的资源:

A1:黄巾军步兵

A2:黄巾军枪兵

A3:黄巾军骑兵

A4:黄巾军弓兵

B1:义军步兵

B2:义军枪兵

B3:义军骑兵

B4:义军弓兵

另外还加了两个英雄:

Hero02:关羽

Hero11:张角

然后将他们的各种帧制作完成并命名完毕(这里也许你需要美术的帮助,我已将其完成,可以在最终的文件中浏览),用TexturePackerGUI打包。

图片包下载地址:https://files.cnblogs.com/nowpaper/SanguoCommander5_actors.rar

QQ截图20121020192249

发布一下它,保存成一个plist和png图,命名为ActorsPack1,以后也许有Pack2,所以单独分开保存:

QQ截图20121020192410

最终的plist文件放入工程Content下,可以建立一个例如plist的文件夹,并且将.plist文件的内容管线设置正确:

QQ截图20121020192600

这张图并不正确,你应该建立子文件夹Images,并将ActorsPack1.png放入其中。

好了,下一步就可以开始对工程进行代码编写了。

从游戏本身的设计经验而言,最好的方式是数据驱动逻辑,所以,当我们描写一个角色动画类的时候,最先有一个比较明确的数据类,这里我们可以将游戏底层角色的复杂数据包含,并且做处理,例如角色最基本的特性、职业、类型等等:

enum ActorType
{
    None, Soldier,Hero
}
enum ActorPro
{
    None, Infantry, Pikeman, Cavalvy, Archer
}
enum ActorDir
{
    None, Right, Left
}
class ActorData
{
    public ActorData(string id)
    {
        ActorID = id;
    }
    //演员id
    public string ActorID { get; private set; }
    //演员分组
    public string GroupID { get; private set; }
    //类型
    public ActorType ActorType { get; private set; }
    //职业
    public ActorPro ActorPro { get; private set; }
    //获得一个数据
    public static ActorData getActorData(string id, string groupid, ActorType type, ActorPro pro)
    {
        ActorData data = new ActorData(id);
        data.GroupID = groupid;
        data.ActorType = type;
        data.ActorPro = pro;
        return data;
    }
}

在最上面,我们定义了三个枚举,分别来表示类型、职业和方向,方向是用来做动画用的,而类型和职业则是基本数据的需求,大家可以注意到,设计了一个getActorData静态方法,用来方便的制作基本测试数据,因为按照游戏数据的处理惯例而言,这些数据都是通过表单的方式配置,程序读取配置解析成为程序数据,方便游戏设计师随时调整。

下一步就是建立动画类了,我们使用类继承的方式抽象动画类,建立ActorBase类,这个类只是管理角色最基本的动画,继承自CCSprite,通过CCAnimate行为来控制动画:

class ActorBase : CCSprite
{
    public ActorData ActorData { get; private set; }
    private CCAnimate _action_attack;
    private CCAnimate _action_attack_flip;
    private CCAnimate _action_run;
    private CCAnimate _action_run_flip;
    private CCAnimate _action_stand;
    private CCAnimate _action_stand_flip;
    private CCAnimate _action_dead;
    private CCAnimate _action_dead_flip;
    public ActorDir ActorDir { get; set; }
    public ActorBase(ActorData data)
    {
        ActorData = data;
        //创建攻击动画
        List _attackFrames = new List();
        List _attackFrames_flip = new List();
        for (int i = 0; i < 4; i++)
        {
            _attackFrames.Add(CCSpriteFrameCache.sharedSpriteFrameCache().spriteFrameByName(data.ActorID + "_" + i + ".png"));
            _attackFrames_flip.Add(CCSpriteFrameCache.sharedSpriteFrameCache().spriteFrameByName(data.ActorID + "f_" + i + ".png"));
        }
        _action_attack = CCAnimate.actionWithAnimation(CCAnimation.animationWithFrames(_attackFrames, 0.1f));
        _action_attack_flip = CCAnimate.actionWithAnimation(CCAnimation.animationWithFrames(_attackFrames_flip, 0.1f));
        //创建行走动画
        List _runFrames = new List();
        List _runFrames_flip = new List();
        for (int i = 4; i < 6; i++)
        {
            _runFrames.Add(CCSpriteFrameCache.sharedSpriteFrameCache().spriteFrameByName(data.ActorID + "_" + i + ".png"));
            _runFrames_flip.Add(CCSpriteFrameCache.sharedSpriteFrameCache().spriteFrameByName(data.ActorID + "f_" + i + ".png"));
        }
        _action_run = CCAnimate.actionWithAnimation(CCAnimation.animationWithFrames(_runFrames, 0.1f));
        _action_run_flip = CCAnimate.actionWithAnimation(CCAnimation.animationWithFrames(_runFrames_flip, 0.1f));
        //创建站立动画
        List _standFrames = new List();
        List _standFrames_flip = new List();
        for (int i = 6; i < 7; i++)
        {
            _standFrames.Add(CCSpriteFrameCache.sharedSpriteFrameCache().spriteFrameByName(data.ActorID + "_" + i + ".png"));
            _standFrames_flip.Add(CCSpriteFrameCache.sharedSpriteFrameCache().spriteFrameByName(data.ActorID + "f_" + i + ".png"));
        }
        _action_stand = CCAnimate.actionWithAnimation(CCAnimation.animationWithFrames(_standFrames, 0.2f));
        _action_stand_flip = CCAnimate.actionWithAnimation(CCAnimation.animationWithFrames(_standFrames_flip, 0.2f));
        //创建死亡动画
        List _deadFrames = new List();
        List _deadFrames_flip = new List();
        for (int i = 7; i < 9; i++)
        {
            _deadFrames.Add(CCSpriteFrameCache.sharedSpriteFrameCache().spriteFrameByName(data.ActorID + "_" + i + ".png"));
            _deadFrames_flip.Add(CCSpriteFrameCache.sharedSpriteFrameCache().spriteFrameByName(data.ActorID + "f_" + i + ".png"));
        }
        _action_dead = CCAnimate.actionWithAnimation(CCAnimation.animationWithFrames(_deadFrames, 0.3f));
        _action_dead_flip = CCAnimate.actionWithAnimation(CCAnimation.animationWithFrames(_deadFrames_flip, 0.3f));
        //初始化默认帧
        base.initWithSpriteFrame(_standFrames[0]);
    }
        
    CCAction _currentAnimateAction;
    public void StateToRun()
    {
        if(ActorDir == Roles.ActorDir.Left)
            RunAnimateAction_RepeatForever(_action_run);
        else
            RunAnimateAction_RepeatForever(_action_run_flip);
    }
    //攻击状态
    public void StateToAttack()
    {
        currentAnimateActionStop();
        if (ActorDir == Roles.ActorDir.Left)
            RunAnimateAction_RepeatForever(_action_attack);
        else
            RunAnimateAction_RepeatForever(_action_attack_flip);
    }
    //死亡动画
    public void StateToDead()
    {
        currentAnimateActionStop();
        if (ActorDir == Roles.ActorDir.Left)
            _currentAnimateAction = runAction(_action_dead);
        else
            _currentAnimateAction = runAction(_action_dead_flip);
    }
    //站立动画
    public void StateToStand()
    {
        if (ActorDir == Roles.ActorDir.Left)
            RunAnimateAction_RepeatForever(_action_stand);
        else
            RunAnimateAction_RepeatForever(_action_stand_flip);
            
            
    }
    //停止当前的动画
    private void currentAnimateActionStop()
    {
        if (_currentAnimateAction != null)
            this.stopAction(_currentAnimateAction);
    }
    //播放循环动画的统一方法
    private void RunAnimateAction_RepeatForever(CCAnimate action)
    {
        currentAnimateActionStop();
        _currentAnimateAction = runAction(CCRepeatForever.actionWithAction(action));
    }
}

上述代码加了一些注释,希望能够帮助你的阅读,动画行为的制作流程是这样的:

首先我们要知道有那些帧,它们形成的集合变成CCAnimation的处理类,然后CCAnimate将其加载并形成特定的动画行为,有兴趣的配有可以看cocos2d的底层代码,CCSprite实际上是带了一个CCTexture来表示图像,CCAnimate是按照逻辑变化CCTexture。

在下面的代码中:StateToRun、StateToAttack、StateToDead、StateToStand等方法都是用来处理角色状态的动画,例如对应到当攻击的时候调用StateToAttack方法。

currentAnimateActionStop和RunAnimateAction_RepeatForever是为了方便处理动画的特定状态,因为诸如行走、站立、攻击,这一类的动画都是循环性质,统一代码比较方便。

那么我们就测试一下上面的代码看看直接的效果,还是为了方便,在本节中,我们直接将动画放在了开始界面这样浏览很方便了,打开SceneStart.cs,在构造函数中写如下代码:

//测试动画的角色
List id_buff = new List()
{
    "B1","B2","B3","B4","Hero02","A1","A2","A3","A4","Hero11"
};
for (int i = 0; i < 2; i++)
{

    for (int j = 0; j < 5; j++)
    {
        var actor = new ActorBase(new ActorData(id_buff[i*5 + j]));
        actor.ActorDir = (ActorDir)(i + 1);
        actor.position = new CCPoint(64 + i * 64, 64 + j * 64);
        if(j % 2 ==1)
            actor.StateToRun();
        else
            actor.StateToAttack();
        this.addChild(actor);
    }
}
//

上面代码里,用一个List结构id_buff来描述ID,其实就是角色的前缀名,然后按照顺序排成两列,并且按照单数为行走动画,双数为攻击动画的形式显示。

QQ截图20121021231721

上面是运行测试的效果,后面我们会将它们加到游戏界面中,让游戏真正的可以玩起来。

本篇例子工程:https://github.com/Nowpaper/SanguoCommander_cocos2dxna_Sample
本例子项目名为:SanguoCommander6

posted on 2012-10-22 09:42  nowpaper  阅读(6177)  评论(15编辑  收藏  举报