Spread the love

Suffice it to say, the first step this time happens to be bringing SokoBomber back as a whole. The same game, with cleaner implementation, and it’s helped me see what adjustments I need in logic.

The gif doesn’t do it justice, FPS lowered for mobile

I’ve started to get the animations going in 2D, I’ve started to mess with shaders (not shown above), and I’m thankfully going through the entire idea behind the original SokoBomber. It’s a fun nostalgic move. I need to find a new way to do my gifs, but that’s not today’s task.

With that all showing here, you can tell I’m slowly ticking off each element of movement and play in the mechanics. I sort of need to adjust the frequency of some of the things, but it can be adjusted over time. The special notes today happen to be a few ideas I had in how eVX handles a few things.

1. Draw Queue

I didn’t want to have a second set of for loops through the map layout; it seems redundant to me. So, as a simple update to the Engine draw method, I added a way to add an additional queue system for 2D rendering. I daresay it might need more than 1 queue, but the ideas seems solid with a single queue for now.

I might be dropping GC.Collect() too often, but I doubt it’s troublesome.

    /// <summary>
    /// Private DrawQueue Structure
    /// </summary>
    private struct QueuedDraw
    {
        public Texture2D Tex;
        public Rectangle Dest;
        public Color Color;
    }
    private List<QueuedDraw> _drawQueue = new List<QueuedDraw>();

    public void QueueDraw(ETex2D tex, Rectangle dest)
    {
        QueueDraw(tex.Texture2D, dest, Color.White);
    }

    public void QueueDraw(Texture2D tex, Rectangle dest)
    {
        QueueDraw(tex, dest, Color.White);
    }

    public void QueueDraw(ETex2D tex, Rectangle dest, Color color)
    {
        QueueDraw(tex.Texture2D, dest, color);
    }

    /// <summary>
    /// Add an item into the DrawQueue
    /// </summary>
    /// <param name="tex"></param>
    /// <param name="dest"></param>
    /// <param name="color"></param>
    public void QueueDraw(Texture2D tex, Rectangle dest, Color color)
    {
        _drawQueue.Add(new()
        {
            Tex = tex,
            Dest = dest,
            Color = color
        });
    }

    /// <summary>
    /// Render all items in the queue
    /// </summary>
    public void DrawQueue()
    {
        for (int i = 0; i < _drawQueue.Count; ++i)
        {
            Engine.Content.Draw(_drawQueue[i].Tex, _drawQueue[i].Dest, _drawQueue[i].Color);
        }
        _drawQueue.Clear();
        GC.Collect();
    }

    /// <summary>
    /// Clear DrawQueue if you didn't feel like rendering it
    /// </summary>
    public void FlushQueue()
    {
        _drawQueue.Clear();
        GC.Collect();
    }

A simple structure to go through after your first drawing; for instance from inside the first switch used for map tiles, it leads to a separate cycle to draw on top of the first sets:

                    case SBTile.Trigger10:
                        C.Draw(t_m_FloorTile[(int)SBTile_SubFloor.Plain1], r_Tile);
                        switch ((swap_gameTick + x * y) % 4)
                        {
                            case 0: C.Draw(t_m_FuseIgniter[(int)DBTile_SubFuseIgnite.Burn1], r_Tile); break;
                            case 1: C.Draw(t_m_FuseIgniter[(int)DBTile_SubFuseIgnite.Burn2], r_Tile); break;
                            case 2: C.Draw(t_m_FuseIgniter[(int)DBTile_SubFuseIgnite.Burn3], r_Tile); break;
                            case 3: C.Draw(t_m_FuseIgniter[(int)DBTile_SubFuseIgnite.Burn4], r_Tile); break;
                        }
                        trigger = true;
                        break;
                }
                if (trigger)
                {
                    switch (Map[x, y])
                    {
                        case SBTile.Trigger0:
                            C.Draw(t_m_FuseTile[(int)SBTile_SubTrigger.Trigger0], r_Tile);
                            break;

This way, after if (trigger) I call C.DrawQueue() before the character and UI render.

Simple, right? I can do instant Draw calls for things which should go behind other things, and then Draw the things in front. I can do this several times in a single render, but I doubt that would ever be needed.

2. Tex2DSplit

I forget were I got this from StackOverflow, but it didn’t work as a simple copy paste – I made a few adjustments. (Will find the URL if need be in the future). What I needed, with my base SokoBomber tile set sprites, was a way to cut up the loaded texture in memory. This means I can draw sections in a speedy manner, thankfully.

    /// <summary>
    /// Splits a texture into an array of smaller textures of the specified size.
    /// - TODO: Revamp, perhaps
    /// </summary>
    /// <param name="original">The texture to be split into smaller textures</param>
    /// <param name="partWidth">The width of each of the smaller textures that will be contained in the returned array.</param>
    /// <param name="partHeight">The height of each of the smaller textures that will be contained in the returned array.</param>
    public Texture2D[] Tex2DSplit(Texture2D original, int partWidth, int partHeight)
    {
        var yCount = original.Height / partHeight;
        var xCount = original.Width / partHeight;
        Texture2D[] result = new Texture2D[xCount * yCount];
        int dataPerPart = partWidth * partHeight;

        Color[] originalData = new Color[original.Width * original.Height];
        original.GetData(originalData);

        int index = 0;
        for (int y = 0; y < yCount * partHeight; y += partHeight)
            for (int x = 0; x < xCount * partWidth; x += partWidth)
            {
                Texture2D part = new Texture2D(original.GraphicsDevice, partWidth, partHeight);
                Color[] partData = new Color[dataPerPart];

                for (int py = 0; py < partHeight; py++)
                {
                    for (int px = 0; px < partWidth; px++)
                    {
                        int partIndex = px + py * partWidth;
                        if (y + py >= original.Height || x + px >= original.Width)
                            partData[partIndex] = Color.Transparent;
                        else
                            partData[partIndex] = originalData[(x + px) + (y + py) * original.Width];
                    }
                }

                part.SetData(partData);
                result[index++] = part;
            }
        return result;
    }

As an example, all the floor tiles are inside a single source Texture2D:

        // Subtextures
        var t_FloorTile = C.LoadTex2D("temp", "sb/FloorTile.png");
        t_m_FloorTile = C.Tex2DSplit(t_FloorTile.Texture2D, 32, 32);

This way, I can use an easy readable enum to manage what I draw on the screen, from the array:

public enum SBTile_SubFloor
{
    Green1 = 0,
    Green2 = 1,
    Green3 = 2,
    Cracked1 = 3,
    Plain1 = 4,
    Plain2 = 5,
    Plain3 = 6,
    Cracked2 = 7,
    Drop1 = 8,
    Plain4 = 9,
    Ice1 = 10,
    Ice2 = 11,
    IceLeft = 12,
    IceTop = 13,
    IceBottom = 14,
    IceRight = 15
}

The one thought of any development projects: keep code readable, that way anyone that needs to ever work on it with you can understand it. You, no doubt, saw how it’s easier to maintain above for the DBTile_SubFuseIgnite collection, and it hadn’t been explained to you. Readable code can go a long way.

Other Thoughts

While, technically, not necessary; as an interesting note I’ve been experimenting with 2 customisations in FNA-XNA for myself.

Something in my logic for eVX is causing my game to get stuck in a loop inside FNA/src/FNAPlatform/SDL2_FNAPlatform.cs in ProgramExit, so to resolve this I adjust it a touch:

			// This _should_ be the last SDL call we make...
			// edg3: put on thread to close - without window stops closing and freezes
			Thread t = new Thread(() =>
			{
				SDL.SDL_Quit();
			});
			t.Start();

I’m unsure of this as it might still leave SDL_Quit running when the window closes completely. I need to double check this, I believe.

Secondly, inside FNA/src/Game.cs I adjust one line:

			// eVX - 120 fps; instead of 166667. Still at 30fps on battery for battery saving which is fine
			TargetElapsedTime = TimeSpan.FromTicks(83333);

This is used as I would love to prefer 120 FPS; though, more importantly, it helped me pick my map render up from around 30 FPS to around 110 FPS. I can still clean and groom my logic to improve it, I think, but it might be my laptop’s hardware limits which I’m aiming at. I’m excited to have 110 FPS while playing and moving around in the test world.

There will be more updates in the future, it just excited me to have a fabulous way to run a Vulkan game the way I want to. Through C#. I will keep doing so, but thank you immensely FNA-XNA. You have helped me get back into game development like I prefer it.

By edg3