7
\$\begingroup\$

I'm just getting started with XNA and game development in general. I followed these instructions to create a basic game, then this article to get sprite animations working.

Now, I'm trying to abstract my design, in particular the stuff with sprite animations. Before I get much deeper, I want to make sure I have a good foundation to work with.

First, I have a Sprite class:

public class Sprite
{
    public Sprite()
    {
        // Default values for public members
        Position = Vector2.Zero;
        Scale = 1f;
        LayerDepth = 0f;
    }

    public IAnimation Animation { get; set; }

    public IMovement Movement { get; set; }

    public string AssetName { get; set; }

    public Texture2D Texture { get; set; }

    public Vector2 Position { get; set; }

    public Rectangle Frame { get; set; }

    public float Scale { get; set; }

    public float LayerDepth { get; set; }

    public void Update( GameTime gameTime )
    {
        if ( Animation != null )
        {
            Frame = Animation.GetNextFrame( gameTime, Frame );
        }

        if ( Movement != null )
        {
            Position = Movement.GetNextPosition( gameTime, Position );
        }
    }

    public void Draw( SpriteBatch spriteBatch )
    {
        spriteBatch.Draw( Texture, Position, Frame, Color.White, 0f, Vector2.Zero, Scale, SpriteEffects.None, LayerDepth );
    }
}

I have a SpriteGroup class for keeping groups of sprites organized and making them easy to use:

public class SpriteGroup
{
    public IList<Sprite> Sprites { get; set; }

    public void Update( GameTime gameTime )
    {
        foreach ( var sprite in Sprites )
        {
            sprite.Update( gameTime );
        }
    }

    public void Draw( SpriteBatch spriteBatch )
    {
        foreach ( var sprite in Sprites )
        {
            sprite.Draw( spriteBatch );
        }
    }
}

This is inherited by the Character class (nothing special there), of which I have several static instances:

public static class Characters
{
    public static Character Tycho = new Character
        {
            Sprites = new List<Sprite>
                {
                    new Sprite
                        {
                            Animation = new Animation
                                {
                                    FirstFrame = 2,
                                    LastFrame = 9
                                },
                            AssetName = "characters/walkcycle/BODY_male",
                            Position = new Vector2( 100, 100 ),
                            Frame = new Rectangle( 64, 64, 64, 64 ),
                            LayerDepth = 1f
                        },
                    new Sprite
                        {
                            Animation = new Animation
                                {
                                    FirstFrame = 2,
                                    LastFrame = 9
                                },
                            AssetName = "characters/walkcycle/LEGS_pants_greenish",
                            Position = new Vector2( 100, 100 ),
                            Frame = new Rectangle( 64, 64, 64, 64 ),
                            LayerDepth = 0f
                        }
                }
        };

    // Several more static Characters...
}

I plan to have other static objects that inherit from SpriteGroup as well such as scenery, buildings, etc.

Here are the relevant bits of my Game class (or rather the class that inherits the Game class from the XNA library):

private IList<Sprite> sprites { get; set; }

protected override void LoadContent()
{
    // Create a new SpriteBatch, which can be used to draw textures.
    spriteBatch = new SpriteBatch( GraphicsDevice );

    sprites = new List<Sprite>();

    var characters = new List<Character>
        {
            Characters.Aradesh,
            Characters.Ian,
            Characters.Tycho
        };

    foreach ( var character in characters )
    {
        foreach ( var sprite in character.Sprites )
        {
            sprite.Texture = Content.Load<Texture2D>( sprite.AssetName );
            sprites.Add( sprite );
        }
    }
}

protected override void Update( GameTime gameTime )
{
    foreach ( var sprite in sprites )
    {
        sprite.Update( gameTime );
    }

    // Other uninteresting stuff...
}

protected override void Draw( GameTime gameTime )
{
    spriteBatch.Begin( SpriteSortMode.BackToFront, BlendState.AlphaBlend );

    foreach ( var sprite in sprites )
    {
        sprite.Draw( spriteBatch );
    }

    spriteBatch.End();

    // Etc...
}

Is this a good foundation for an XNA project? Are there any glaring flaws or design traps I'm likely to run into with what I have so far? Replace XNA with MonoGame where applicable. Either way, I'm just looking for general design advice. I haven't decided if I will make the switch to MonoGame or something else yet.

Ideally, I'd like to build up a reusable base class library for other projects moving forward, or for others to use or fork or whatever.

In case it helps, you can browse the code on GitHub.

\$\endgroup\$
  • 2
    \$\begingroup\$ Hm, you should consider moving to an other technology because XNA is dead. I think MonoGame is the new open source implementation of XNA to fill the void a little, but there's probably better and up-to-date stuff for game dev. \$\endgroup\$ – Pierre-Luc Pineault Sep 12 '13 at 4:42
  • \$\begingroup\$ That really sucks, because I did a lot of research ahead of time that suggested XNA is the way to go (at least to start). I heard that Microsoft was discontinuing XNA development but thought it was just a rumor. I can't imagine why they would abandon it, since Xbox seems like a high priority for them these days along with Windows Phone and Tablet. Any idea what I should try next? \$\endgroup\$ – David Kennedy Sep 12 '13 at 15:43
  • \$\begingroup\$ Also, what about the design in general? Any suggestions? (I updated it since you last commented.) \$\endgroup\$ – David Kennedy Sep 12 '13 at 15:58
  • \$\begingroup\$ XNA is dead, but MonoGame is alive and well. The community is active and lots of solid games are being made with it everyday. \$\endgroup\$ – craftworkgames May 19 '14 at 2:51
3
\$\begingroup\$

Since you superficially asked about abstracting the design, in particular the stuff with sprite animations let me talk about that.

My concern comes from the Single responsibility principle (SRP) and the Open/closed principle (OCP) in the SOLID principles way of thinking http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)

Before you added animation and movement your Sprite class fit SRP pretty well. Unfortunately, now you've said that the Sprite class is also responsible for Animation. When your animation code changes (and it will) the sprite class will also need changing (this violates the OCP rule).

Okay, so how do you fix it?

Well, it seems to me that Sprites are about Drawing and Animations are about Updating over time. Therefore, you should split the Update method out into an Animation class.

Consider this:

public interface IFrameable
{
    Rectangle Frame { get; set; }
}

public class Animation
{
    private IFrameable _target;

    public Animation(IFrameable target)
    {
        _target = target;
    }

    public void Update( GameTime gameTime )
    {
        _target.Frame = GetNextFrame( gameTime, _target.Frame );
    }
}

What I've created here is essentially a "framed animation" class based on your code that can take any type of IFrameable object in it's constructor. Your Sprite class could of course implement IFrameable and become the target for this type of Animation.

Doing something like this removes the Animation responsibility from the Sprite class and let's it deal with Drawing (what it's good at). It then allows you to implement different types of animations (e.g. movement, rotation, scale or color) and chain them together ultimately making the animation code more flexible.

It also allows you to "compose" objects of Sprites, Animations and whatever else you can think of without the need to change too many existing classes.

There's lot's more I could talk about on this subject, it's something I've thought about a lot in my own games, but for now I'll just leave it as food for thought :)

Happy coding!

\$\endgroup\$
3
\$\begingroup\$
  1. I would move the inner loop which loads the texture into the SpriteGroup class as well as moving the texture loading for the single sprite into the Sprite class:

    public class Sprite
    {
        ....
        public void LoadTexture()
        {
             Texture = Content.Load<Texture2D>(AssetName);
        }
    }
    
    public class SpriteGroup
    {
         ...
         public void LoadTextures()
         {
             foreach (var s in Sprites)
             {
                  s.LoadTexture();
             }
         }
    }
    
  2. In general you should try to not expose how stuff is stored internally in a class directly (e.g. don't make your Sprites list a public property). If you ever consider changing the internal implementation then this could mean a lot of work to change existing code. You could make SpriteGroup either IEnumerable<Sprite> or simply not give public access to the underlying sprite collection at all.
    • Consider adding a constructor for SpriteGroup taking an IEnumerable<Sprite> which are added to the internal sprite list by the constructor code.
    • If you need to add/remove sprites on the fly then add Add/Remove methods.
  3. Your Game class should not deal with sprites directly but rather with the "world" objects.
    • Call Update and Draw on the character/sprite group objects and let them forward it to the internal sprites (you have some of that code in there anyway)
\$\endgroup\$
  • \$\begingroup\$ Sorry for neglecting your answer for so long. I have implemented changes 1 and 3 in my new project here. As for no. 2, the reason I make everything public on entities is so I can use object initializers, but I'm starting to wonder if I need to give those up. \$\endgroup\$ – David Kennedy Dec 9 '13 at 20:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.