1
\$\begingroup\$

I've been trying to use this modified version of GLEED2D that includes WP7 support,

The progress so far, is I've been able to load the level XML file to the project and read it successfully thanks to This answer

I got in touch with the author of the modified version, he added the answer to the wiki

The problem now is I cannot implement the following steps, according to the wiki, I now need to:

"enumerate the levels and then enumerate each item in each level. For each item, add the object/sprite into XNA."

My initialize() method now looks like this

    protected override void Initialize()
    {

        Stream stream = TitleContainer.OpenStream("Content/leveltest.xml");
        //Stream stream = TitleContainer.OpenStream("Content/level.gleed");
        XElement xml = XElement.Load(stream);
        level = LevelLoader.Load(xml);

        base.Initialize();
    }

When I debug I find the level object loaded successfully and has list of layers and list of items inside the layers, but I'm not sure how to continue my way from here to Display the level items on the screen.

Any idea?

--UPDATE--

I added

foreach (Layer layer in level.Layers)
{
   foreach (LayerItem item in layer.Items)
   {
      item.Properties.Visible = true;
      //something should be added here to draw
   }
}

I am not sure what to add now to draw the item into it's position on screen !?

\$\endgroup\$
2
2
\$\begingroup\$

The wiki you linked says the following:

It is here you can see which variation of ItemProperties you have and then build your own in-game object.

And I agree with it. Instead of drawing directly from the Gleed2D classes I think you should be using them as a bridge between the level editor and your classes. This gives you more flexibility in deciding exactly what to do with the data from your level. And that's really not hard to do, so I'll give you an example that I just wrote from memory (i.e. it's untested). But that's all it is - an example - and you should be writing something specific to your game.

Usage Example

I'll start at the end and show the interface I aimed for. This is the only thing you will need to do on your game for loading and rendering the level after you implement this:

MyLevel myLevel; // NEW

// On LoadContent
using(Stream stream = TitleContainer.OpenStream("Content/leveltest.xml"))
{
    XElement xml = XElement.Load(stream);
    Level level = LevelLoader.Load(xml);
    myLevel = MyLevel.FromGleed2D(level, Content); // NEW
}

// On Draw
spriteBatch.Begin();
myLevel.Draw(spriteBatch); // NEW
spriteBatch.End();

As you can see, I created a class called MyLevel which took care of both loading and rendering of the scene, through the FromGleed2D and Draw methods respectively. But before going into the implementation of the MyLevel class, I'll show another class that is required.

The Sprite Class

The first step I did was create a Sprite class which stores the texture and all properties that were relevant (in this case I settled for only the sprite's transformation, but you could store other properties too) and which knows how to Draw itself using a SpriteBatch. Very straightforward implementation:

public class Sprite
{
    public TextureSprite(Texture2D texture)
    {
        Texture = texture;    
        Origin = new Vector2(Texture.Width/2f, Texture.Height/2f);
    }

    public Vector2 Position { get; set; }
    public float Rotation { get; set; }
    public Vector2 Scale { get; set; }
    public Vector2 Origin { get; set; }
    public Texture2D Texture { get; set; }

    public void Draw(SpriteBatch spriteBatch)
    {
        spriteBatch.Draw(Texture, Position, null, Color.White, Rotation, Origin, Scale, SpriteEffects.None, 0f);
    }
}

The MyLevel Class

Here's the class that loads, stores and draws the level. For now, I omitted the implementation of the FromGleed2D method purposely, but I'll show it later. For now notice that the MyLevel class is basically a wrapper around an array of lists of sprites. Each position in this array is one layer from the level, and the constructor takes how many layers exist in the level. There's a method that lets you add new sprites to a specific layer, and the Draw method simply iterates over the sprites in each layer drawing them in order.

public class MyLevel
{
    public MyLevel(int count)
    {
        // Create layers array
        _layers = new List<Sprite>[count];

        // Initialize each position in the array
        for(int i=0; i!=count; ++i)
            _layers[i] = new List<Sprite>();
    }

    public static MyLevel FromGleed2D(Gleed2D.InGame.Level level, ContentManager content)
    {
        // Implementation omitted - See below
    }

    public void AddSpriteAt(Sprite sprite, int layer)
    {
        _layers[layer].Add(sprite);
    }

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

    private readonly List<Sprite>[] _layers;
}

And finally the most important method, FromGleed2D which takes care of converting all the relevant data from the Gleed2D level object into our own. First it discovers how many layers there are in the level, and creates the MyLevel object. Then it simpy iterates over all TextureItems in the level, creates a Sprite from them, and adds the sprite to our level. I've commented the code for clarity:

public static MyLevel FromGleed2D(Gleed2D.InGame.Level level, ContentManager content)
{
    // Get layer count and create our level
    int count = level.Layers.Count();
    MyLevel myLevel = new MyLevel(count);

     // Iterate over each layer and item in the Gleed2D level
    int i = 0;
    foreach(var layer in level.Layers)
    {
        foreach(var item in layer.Items)
        {
            // Verify if Item is a TextureItem
            TextureItemProperties properties = item.Properties as TextureItemProperties;
            if(properties != null)
            {
                // Create sprite from TextureItem
                Texture2D texture = content.Load<Texture2D>(properties.TexturePathRelativeToContentRoot);
                Sprite sprite = new Sprite(texture);
                sprite.Position = properties.Position;
                sprite.Rotation = properties.Rotation;
                sprite.Scale = properties.Scale;

                // Add sprite to level at current layer
                myLevel.AddSpriteAt(sprite, i);
            }
        }
        ++i;
    }

    // Finally return the level we created
    return myLevel;
}
\$\endgroup\$
1
  • \$\begingroup\$ Good answer. I've updated the Wiki page to make it clearer that Gleed 2D makes no attempt to build any drawable game components. \$\endgroup\$ – Steve Dunn Mar 13 '12 at 7:41
1
\$\begingroup\$

in the Draw() method add the following:

spriteBatch.Begin();           
foreach (Layer layer in level.Layers)
{
    foreach (LayerItem item in layer.Items)
    {
        TextureItemProperties textureprop = new TextureItemProperties();
        textureprop =(TextureItemProperties) item.Properties;
        string path=textureprop.TexturePathRelativeToContentRoot;
        spriteBatch.Draw(Content.Load<Texture2D>(path), textureprop.Position, Color.White);                    
    }

}           
spriteBatch.End();

This will draw the the textures in the places specified, it's a bit amateur but it gets the job done.


Suggestions from David Gouveia (I'm not sure what's the protocol for suggesting a correction when there's so much text that it would be cumbersome to do so in the comments, so I'll add this instead)

There's a problem with your code. It is possible that not all ItemProperties are TextureItemProperties, in which case you'd get an InvalidCastException. I suggest the following amendment which uses the as operator to downcast safely.

Also, you were instantiating a new TextureItemProperties() and then assigning on top of it right away. There's no reason to do that so I removed it.

spriteBatch.Begin();           
foreach (Layer layer in level.Layers)
{
    foreach (LayerItem item in layer.Items)
    {
        TextureItemProperties textureprop = item.Properties as TextureItemProperties;
        if(textureprop != null)
        {
            string path = textureprop.TexturePathRelativeToContentRoot;
            spriteBatch.Draw(Content.Load<Texture2D>(path), textureprop.Position, Color.White);
        }                    
    }

}           
spriteBatch.End();
\$\endgroup\$
3
  • \$\begingroup\$ Yes that's a bit hackish. Give me a moment and I'll post what I would do. \$\endgroup\$ – David Gouveia Jan 30 '12 at 1:39
  • 1
    \$\begingroup\$ There I posted it. It's a bit more work but I think it's worth it, because it completely separates the Gleed2D implementation details from your game. And of course, what I wrote is just an example. You should write classes to fit your specific game. \$\endgroup\$ – David Gouveia Jan 30 '12 at 2:27
  • \$\begingroup\$ yeah, thanks... that's of course much better implementation. Thanks \$\endgroup\$ – smohamed Jan 30 '12 at 13:49
0
\$\begingroup\$

Gleed 2D, by design, makes no attempt to create anything drawable. It's primary purpose is to let the user visually design levels and then work with strongly typed objects to load the level in their game.

So, when you load the level in your game, you end up with a Level. A Level has Layers, and Layers have objects that derive from ItemProperties. So ultimately, you'll want to check what derived type of ItemProperties you get when enumerating, and then build your in-game objects from that. Here's an example from a game:

public static class GleedConverter
{
  public static void ConvertGleedObjects(LevelScreen levelScreen, Level level)
  {
    var pathConverter = new ConvertToGameObject<PathItemProperties>(
    s => s.GetType(  )==typeof(PathItemProperties) && s.Name.StartsWith( @"path", StringComparison.OrdinalIgnoreCase ),
    ti =>
        {
            return new PathObject(
                levelScreen,
                ti.WorldPoints.ToArray(  ),
                Convert.ToInt32( ti.CustomProperties[ @"speed" ].Value, CultureInfo.InvariantCulture ),
                ti.CustomProperties[ @"followed_by" ].Value as string ) ;
        } ) ;

    var spikeConverter = new ConvertToGameObject<TextureItemProperties>(
    s => s.GetType(  )==typeof(TextureItemProperties) && s.Name.StartsWith( @"Spike",StringComparison.OrdinalIgnoreCase ),
    ti =>
        {
            var spike = new SpikeObject( levelScreen, levelScreen.ScreenManager.Textures[ ti.AssetName ] ) ;

            ti.CopyPropertiseTo( spike, true ) ;

            return spike ;
        } ) ;

   var boundaryConverter = new ConvertToGameObject<TextureItemProperties>(
      s => s.GetType(  )==typeof(TextureItemProperties) && s.Name.StartsWith( @"BoundaryBlock",StringComparison.OrdinalIgnoreCase ),
    ti =>
        {
            var bondaryBlock = new BoundaryBlock( levelScreen,levelScreen.ScreenManager.Textures[ ti.AssetName ] ) ;

            ti.CopyPropertiseTo( bondaryBlock, true ) ;

            return bondaryBlock ;
        } ) ;

    var enemyConverter = new ConvertToGameObject<TextureItemProperties>(
        s => s.GetType(  )==typeof(TextureItemProperties) && s.Name.StartsWith( @"enemy",StringComparison.OrdinalIgnoreCase ), ti =>
                        {
                            var enemyType = (string)ti.CustomProperties[@"type"].Value ;

                            if (enemyType == @"WindMan")
                            {
                                WindMan windMan = WindMan.FromGleed( ti, levelScreen, levelScreen.ScreenManager.Textures[ @"WindMan60x135" ] ) ;

                                return windMan ;
                            }

                            throw new InvalidOperationException(@"Enemy type '{0}' unknown".FormatWith( enemyType ));

                        }) ;

                   [other converters omitted for brevity]

        level.ConvertToGameObjects(
            levelScreen.ScreenLayers,
            new IConvertToGameObject[ ]
                {
                    spikeConverter,
                    boundryConverter,
                    enemyConverter,
                                            [other converters omitted for brevity]
                } ) ;

LevelScreen, as passed in the parameters, is a container that contains my drawable in-game objects.

This method creates several objects that convert different ItemProperties into proper in-game objects. In my game, my some of the in-game objects are spikes, enemies, boundary blocks and paths. Note that the converters don't, at this point, create the in-game object, they're just converters that are ready to convert to in-game objects. Also note that they're entirely lamda based. If you have more complex converters, you can implement IConvertToGameObject yourself, which is simply this:

public interface IConvertToGameObject
{
    bool InterestedIn( ItemProperties gleedItem ) ;
    GameObjectBase Convert(  ItemProperties gleedItem ) ;
}

At the end of the ConvertGleedObjects method above, it makes a call to ConvertToGameObjects. This is an extension method that looks like this:

public static class GleedExtensions
{
    public static void ConvertToGameObjects(
        this Level level, 
        ScreenLayerCollection screenLayers,
        IEnumerable<IConvertToGameObject> converters)
    {
        level.Layers.ForEach( l => screenLayers.AddLayer( l.Properties.Name, l.Properties.ScrollSpeed )  ) ;

        var pairs = from layer in level.Layers where layer.Properties.Visible
                    from item in layer.Items 
                    select new
                            {
                                LayerName=layer.Properties.Name,
                                Converter = converters.Single( c => c.InterestedIn( item.Properties ) ),
                                Item = item
                            } ;
        pairs.ForEach(
            p =>
                {
                    GameObjectBase gameObject = p.Converter.Convert( p.Item.Properties ) ;
                    screenLayers.Add( p.LayerName, gameObject ) ;
                } )  ;
    }
}

This takes a Level (the Level you get from Gleed2D). It also takes a ScreenLayerCollection (which is a collection of my layers where each layer contains drawable items), and a list of IConvertToGameObject objects. It then does this:

  • Adds layers to the screenLayers that were passed
  • Creates pairs; each pair consists of the visible Layer and a list of objects that describe: the layer name, a converter that converts the item (the item from Gleed), and the item itself
  • Then for each pair, the converter is called which returns a GameObjectBase (each game object in my game derives from this). The new object is then added to the correct layer.

This looks like a lot of work, but when you have dozens/hundreds/thousands of different in-game objects, it's nice to have this separation between the gleed level, the thing that converts the gleed item to your in-game item, and the behaviour of your in-game item.

So, in summary, the flow I use is:

  • Load the Gleed level
  • Declare the things that convert the Gleed items into in-game items
  • Enumerate the Gleed items and ask the converters to convert to in-game items
  • Add the in-game items to whatever mechanism is used to store and handle in-game items.
\$\endgroup\$

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.