zoukankan      html  css  js  c++  java
  • 使用 NPC,NPCManager 在 XNA 中创建 NPC

    使用 NPC,NPCManager 在 XNA 中创建 NPC

    平方已经开发了一些 Windows Phone 上的一些游戏,算不上什么技术大牛。在这里分享一下经验,仅为了和各位朋友交流经验。平方会逐步将自己编写的类上传到托管项目中,没有什么好名字,就叫 WPXNA 吧,最后请高手绕道而行吧,以免浪费时间。(为了突出重点和减少篇幅,有些示例代码可能不够严谨。)

    NPC

    NPC 是游戏中重要的内容,也就是非玩家控制单位。所以,平方创建了 NPC 类和其他相关的类。下面是 NPC 类的一些字段。

    静态字段 InjuredSoundName 和 DeadSoundName 表示 NPC 受伤的声音和死亡的声音。由于使用静态字段,所以所有的 NPC 都将使用此声音。

    事件 Injured 表示 NPC 受伤之后,你可以在这个事件中让 NPC 显示受伤的效果。

    字段 frameCount 用来计算时间,将根据 frameCount 来执行动作。

    复制代码
    internal static string InjuredSoundName;
    internal static string DeadSoundName;
    
    internal event EventHandler<NPCEventArgs> Injured;
    
    private long frameCount = 0;
    
    protected readonly List<NPCAction> actions = new List<NPCAction> ( );
    protected readonly List<NPCAction> loopActions = new List<NPCAction> ( );
    private int currentActionIndex;
    protected int life;
    protected int protoLife;
    internal bool IsDied = false;
    
    internal NPCManager Manager;
    
    protected NPC ( IPlayScene scene, int type, Vector2 location, string movieName, string extendMovieName, float speed, int angle, HitArea hitArea, int width, int height, int life, IList<NPCAction> actions, double destroySecond, bool isMovieRotable, bool isAreaLimited, bool isAreaEntered, double areaSecond )
        : base ( scene, type, location, movieName, extendMovieName, speed, angle, hitArea, width, height, destroySecond, isMovieRotable, isAreaLimited, isAreaEntered, areaSecond )
    {
    
        this.life = life <= 0 ? 1 : life;
        this.protoLife = this.life;
    
        if ( null != actions )
            this.actions.AddRange ( actions );
    
        this.currentActionIndex = 0;
    }
    复制代码

    字段 actions,loopActions,currentActionIndex 都和动作相关。actions 和 loopActions 用来存放动作,currentActionIndex 表示当前动作的索引。

    字段 life 表示 NPC 当前的生命值,字段 protoLife 表示 NPC 生命的原始值。

    字段 IsDied 表示 NPC 是否已经死亡。字段 Manager 表示 NPC 管理器。

    继承自 NPC 的类可以修改方法 execute,这样就可以完成他们的自定义动作,比如:敌人的战斗机在 1 秒后发射子弹。

    在 updating 方法中,我们将会判断动作的执行时间,如果可以则执行这些动作,并判断这些动作是否可以重复执行,如果可以则将动作添加到 loopActions 字段中。

    复制代码
    protected virtual void execute ( NPCAction action )
    { }
    
    protected override void updating ( GameTime time )
    {
        this.frameCount++;
    
        foreach ( NPCAction action in this.loopActions.ToArray() )
            if ( action.FrameIndex <= this.frameCount )
            {
                this.execute ( action );
    
                if ( !action.Next ( ) )
                    this.loopActions.Remove ( action );
    
            }
    
        for ( int index = this.currentActionIndex; index < this.actions.Count; index++ )
        {
            NPCAction action = this.actions[ index ];
    
            if ( action.FrameIndex > this.frameCount )
                break;
            else
            {
                this.execute ( action );
    
                if ( action.IsLoop )
                {
                    this.loopActions.Add ( action );
                    action.Next ( );
                }
    
                this.currentActionIndex++;
            }
    
        }
    
        base.updating ( time );
    }
    复制代码

    方法 Injure 将扣除 NPC 的生命值,如果生命值小于等于 0,那么我们将播放 NPC 死亡的电影,但是并不会调用 Destroy 方法,而是在 movieEnded 方法中判断死亡电影是否播放完毕,在播放完毕后我们才会调用 Destroy 方法。

    派生类可以修改 dying 方法来添加一些代码,这些代码将在 NPC 死亡前执行。

    复制代码
    protected override void movieEnded ( object sender, MovieEventArgs e )
    {
    
        if ( e.SequenceName == "dead" )
            this.Destroy ( );
    
    }
    
    protected virtual void dying ( )
    { }
    
    public virtual bool Injure ( int life, int type )
    {
    
        if ( this.IsDied )
            return true;
    
        this.scene.AudioManager.PlaySound ( InjuredSoundName );
        
        int injuredLife;
    
        if ( this.life < life )
            injuredLife = this.life;
        else
            injuredLife = life;
    
        this.life -= injuredLife;
    
        if ( null != this.Injured )
            this.Injured ( this, new NPCEventArgs ( this, injuredLife, type ) );
    
        if ( this.life <= 0 )
        {
            this.scene.AudioManager.PlaySound ( DeadSoundName );
            
            this.IsDied = true;
            this.PlayMovie ( "dead" );
    
            this.dying ( );
        }
        else
            this.PlayExtendMovie ( "flash" );
    
        return this.life <= 0;
    }
    复制代码

    NPC 管理器

    NPCManager 类派生自类 SpiritManager<T>。

    复制代码
    internal class NPCManager
        : SpiritManager<NPC>
    {
        protected long frameCount = 0;
    
        internal event EventHandler<SpiritEventArgs> Destroyed;
    
        internal event EventHandler<NPCEventArgs> Injured;
    
        internal NPCManager ( )
            : base ( )
        { }
    
        protected override void spiritDestroyed ( object sender, SpiritEventArgs e )
        {
    
            if ( null != this.Destroyed )
                this.Destroyed ( sender, e );
    
            NPC npc = sender as NPC;
            npc.Injured -= this.Injured;
            npc.Manager = null;
            base.spiritDestroyed ( sender, e );
        }
    
    
        internal override void Append ( NPC spirit, int order )
        {
    
            spirit.Manager = this;
            spirit.Injured += this.Injured;
            base.Append ( spirit, order );
        }
    
        internal override void Update ( GameTime time )
        {
            this.frameCount++;
        }
    
        internal List<NPC> HitTest ( HitArea area )
        {
            List<NPC> npcs = new List<NPC> ( );
    
            foreach ( NPC npc in this.Spirits )
                if ( !npc.IsDied && area.HitTest ( npc.HitArea ) )
                    npcs.Add ( npc );
    
            return npcs;
        }
    
        internal List<NPC[]> HitTest ( )
        {
            List<NPC[]> npcs = new List<NPC[]> ( );
    
            for ( int index1 = 0; index1 < this.Spirits.Count; index1++ )
            {
                NPC npc1 = this.Spirits[ index1 ];
    
                if ( npc1.IsDied )
                    continue;
    
                for ( int index2 = index1 + 1; index2 < this.Spirits.Count; index2++ )
                {
                    NPC npc2 = this.Spirits[ index2 ];
    
                    if ( !npc2.IsDied && npc1.HitArea.HitTest ( npc2.HitArea ) )
                        npcs.Add ( new NPC[] { npc1, npc2 } );
    
                }
    
            }
    
            return npcs;
        }
    
        internal void SetReelSpeed ( DirectionType direction, float speed )
        {
    
            foreach ( NPC npc in this.Spirits )
                npc.SetReelSpeed ( direction, speed );
    
        }
    
    }
    复制代码

    事件 Destroyed 和 Injured 用来通知外界 NPC 已经被摧毁或者受伤,你可以在这些事件中为玩家加分。

    方法 spiritDestroyed 会在 NPC 被摧毁后执行,在这个方法中,我们移除了 NPC 的 Injured 事件,而在基类中会移除 Destroyed 事件。

    第一个 HitTest 方法接收了一个 HitArea 类型的参数 area,我们将判断哪些 NPC 和 area 发生了碰撞,然后将这些 NPC 作为列表返回。而第二个 HitTest 方法,我们测试互相碰撞的 NPC 并返回,比如:在竞速类游戏中,我们需要检测车辆之间的碰撞。

    方法 SetReelSpeed 用来设置所有 NPC 的卷轴速度。

    NPC 动作

    每一个 NPC 在被创建之后,都可能会改变自己当前的状态。因此,平方创建了类 NPCAction,他包含了一些信息,这些信息说明了如何改变 NPC 的状态。

    有时候要完成一个动作还需要其他的类,所以使用字段 Type 来说明动作的类型。字段 frameIndex,IsLoop,loopedCount,intervalFrameCount 用来说明动作是否可以被重复执行,第一次执行的时间,以及频率,执行次数。

    当动作执行一次之后,将调用 Next 方法来获取下一次的执行时间,或者判断是否可以继续执行。

    复制代码
    internal abstract class NPCAction
    {
    
        internal readonly int Type;
    
        private long frameIndex;
        internal long FrameIndex
        {
            get { return this.frameIndex; }
        }
    
        internal readonly bool IsLoop;
    
        private readonly int loopCount;
    
        private int loopedCount = 0;
        internal int LoopedCount
        {
            get { return this.loopedCount; }
        }
    
    
        private readonly long intervalFrameCount;
    
        protected NPCAction ( NPCAction action )
        {
            this.Type = action.Type;
    
            this.frameIndex = action.frameIndex;
    
            this.IsLoop = action.IsLoop;
            this.loopCount = action.loopCount;
            this.intervalFrameCount = action.intervalFrameCount;
        }
        protected NPCAction ( int type, float second, float intervalSecond, int loopCount )
        {
            this.Type = type;
    
            this.frameIndex = World.ToFrameCount ( second );
    
            this.IsLoop = intervalSecond > 0;
            this.loopCount = loopCount;
            this.intervalFrameCount = World.ToFrameCount ( intervalSecond );
        }
    
        internal virtual bool Next ( )
        {
    
            if ( !this.IsLoop || ( this.loopCount > 0 && this.loopedCount >= this.loopCount ) )
                return false;
    
            this.frameIndex += this.intervalFrameCount;
            this.loopedCount++;
            return true;
        }
    
        internal abstract NPCAction Clone ( );
    
    }
    复制代码

    示例

    场景 SceneT20 是 SceneT19 的扩展,除了有 SceneT19 的功能,还将创建一些 NPC,这些 NPC 将自动旋转。

    类 MyNPC 继承自 NPC,方法 execute 可以用来执行自定义动作,也就是 MyNPCAction。类 MyNPCAction 的 OffsetAngle 字段表示旋转角度。

    复制代码
    internal class MyNPC
        : NPC
    {
    
        internal MyNPC ( IPlayScene scene, Vector2 location, int angle, IList<NPCAction> actions )
            : base ( scene, 1, location,
            "mynpc", null,
            3f, angle,
            new SingleRectangleHitArea ( new Rectangle ( -20, -20, 40, 40 ) ),
            40, 40,
            1,
            actions,
            0, true, true, true, 0 )
        { this.isMoving = true; }
    
        protected override void execute ( NPCAction action )
        {
            MyNPCAction myAction = action as MyNPCAction;
    
            this.Angle += myAction.OffsetAngle;
        }
    
        protected override void updateSpeed ( )
        {
            this.xSpeed = Calculator.Cos ( this.angle ) * this.speed;
            this.ySpeed = Calculator.Sin ( this.angle ) * this.speed;
    
            base.updateSpeed ( );
        }
    
        protected override void move ( )
        {
            this.Location.X += this.xSpeed;
            this.Location.Y += this.ySpeed;
        }
    
    }
    
    internal class MyNPCAction
        : NPCAction
    {
        internal readonly int OffsetAngle;
    
        internal MyNPCAction ( MyNPCAction action )
            : base ( action )
        {
            this.OffsetAngle = action.OffsetAngle;
        }
        internal MyNPCAction ( int offsetAngle )
            : base ( 1, 0f, 0.5f, 10 )
        { this.OffsetAngle = offsetAngle; }
    
        internal override NPCAction Clone ( )
        { return new MyNPCAction ( this ); }
    
    }
    复制代码

    接下来,我们为 SceneT20 增加了一个 NPCManager。

    private NPCManager npcManager;

    最后,我们在 goButtonSelected 方法增加了代码,创建一个新的 MyNPC。

    复制代码
    private void goButtonSelected ( object sender, ButtonEventArgs e )
    {
        this.bulletManager.Append ( new MyBullet ( this, new Vector2 ( 10, 10 ), 45 ) );
        this.itemManager.Append ( new MyItem ( this, new Vector2 ( 420, 30 ), 135 ) );
    
        this.npcManager.Append ( new MyNPC ( this, new Vector2 ( 420, 420 ), 100,
            new NPCAction[] {
            new MyNPCAction ( 20 )
            }
            ) );
    }
    复制代码

    本期视频 http://v.youku.com/v_show/id_XNjAxNDczMTIw.html

    项目地址 http://wp-xna.googlecode.com/

    更多内容 WPXNA
    平方开发的游戏 http://zoyobar.lofter.com/
    QQ 群 213685539

    欢迎访问我在其他位置发布的同一文章:http://www.wpgame.info/post/decc4_858ce9



     
     
    分类: XNA
    标签: XNAWindows Phonewp8WP7WPXNA
  • 相关阅读:
    python 包管理工具 pip 的配置
    Python 变量作用域 LEGB (下)—— Enclosing function locals
    Python 变量作用域 LEGB (上)—— Local,Global,Builtin
    2020 Java 面试题 小结 (答案慢慢补上,有错误请指出)
    mysql 根据日期(date)做年,月,日分组统计查询
    jvm指令
    正则表达式 分割地址 获取省市区详细地址
    .Net 异常记录
    WCF设计服务协议(一)
    plsql ORA-01789:查询块具有不正确的结果列数
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3286181.html
Copyright © 2011-2022 走看看