Another Megablog. 17K.

Posted by Astryl on March 7, 2012, 12:54 a.m.

Woohoo, another massive Mega wall-of-text that is going to be TL;DR'd by the majority of the internet!

The reason I've been offline for a while is twofold, but both sides to the problem lie in the fact that I don't have my own internet connection, I use

my dad's. Independance is great, but requires gainful employment first. And I still haven't got the latter.

So basically, because of an argument/dispute/I'mRightSoShutUp I had a few days before writing this, I was told:

"OK. Sure. You're right, but you can't use the internet now for <x> amount of time." (Where x is an unknown variable that depends on whether my parents

decide I'm being condescending enough to deserve my time on the internet).

Anyway, it doesn't hurt me. Since I do most of my work offline, I got a lot done. I did kinda just disappear from the Minecraft server, which was

annoying (I had just started building a shelter/outhouse when I was 'kicked' by Windows >_>).

Anyway… disclaimer:

This blog may contain traces of Error, a sprinkling of Length, and just a Dash of Awesome…

As the title explains, I'm now working with XNA (Which is made a lot easier now that I understand the ins and outs of the C# language).

So I sat down this evening and opened the VC# Express 2010 IDE, and started working on a platformer. As per tradition, I'm using placeholder art, but

even so, to my dismay I discovered that there is no built in support for animated sprites. So the first thing I had to do? Make a class that can load

and draw animated sprites with all the bells and whistles (Origin, Scaling, Flipping). And I did. The result is as follows:

Source code

class MXSprite
    {
        private Texture2D spriteData;
        private int x;
        private int y;
        private int width;
        private int height;
        private int origin_x;
        private int origin_y;
        private int numframes;
        private double speed;
        private double scale;
        private bool flip_x;
        private bool flip_y;
        private bool isLoaded;

        // Current Frame and Frame Counter
        private int c_frame;
        private double fcount;

        // Status values and properties

        private bool isFrameEnd;
        private bool IsFrameEnd { get { return this.isFrameEnd; } }
        private bool isFrameStart;
        private bool IsFrameStart { get { return this.isFrameStart; } }

        // A sprite that can handle multiple frames in a single image.
        // I'll also make a MXListSprite that uses a container.

        public MXSprite()
        {
            spriteData = null;
            x = y = 0;
            width = height = 0;
            numframes = 0;
            scale = speed = 1.0;
            flip_x = false;
            flip_y = false;
            isLoaded = false;
        }

        public void Load(ContentManager cmanager, string resName)
        {
            if (isLoaded) return;

            spriteData = cmanager.Load<Texture2D>(resName);
      
            isLoaded = true;
        }

        public void Setup(int width, int height, double speed = 1.0, int origin_x = 0, int origin_y = 0)
        {
            this.width = width;
            this.height = height;
            this.speed = speed;
            this.origin_x = origin_x;
            this.origin_y = origin_y;

            this.numframes = spriteData.Width / this.width;

            this.c_frame = 0;
            this.fcount = 0.0;
        }

        public void Step()
        {
            // Perform a frame step. Adjust if necessary
            fcount += speed;
            if (fcount >= 1.0)
            {
                fcount = 0.0; // This could be clipped instead of clamped
		// while(fcount >= 1.0)fcount -= 1.0; // This will work too, and retain the fractional part.
						    // But it's a potential bottleneck if somebody accidentally sets
						    // the Speed property to something stupid like 500.
                c_frame += 1;
            }

            if (c_frame == numframes - 1)
            {
                isFrameEnd = true;
            }
            else isFrameEnd = false;

            if (c_frame > numframes - 1)
            {
                c_frame = 0;
            }
            if (c_frame == 0)
            {
                isFrameStart = true;
            }
            else isFrameStart = false;
        }

        // Methods

        public void Draw(SpriteBatch spriteBatch)
        {
            Rectangle srcRect = new Rectangle(this.c_frame * this.width, 0, this.width, this.height);
            SpriteEffects fx = (flip_x ? SpriteEffects.FlipHorizontally : 0) | (flip_y ? SpriteEffects.FlipVertically : 0);

            spriteBatch.Draw(spriteData, new Vector2(x, y), srcRect, Color.White, (float)0.0, new Vector2(origin_x, origin_y), new Vector2((float)

scale,(float)scale), SpriteEffects.None, (float)0.0);
        }

        // Properties

        public int X { get { return this.x; } set { this.x = value; } }
        public int Y { get { return this.y; } set { this.y = value; } }
        public int Width { get { return this.width; } }
        public int Height { get { return this.height; } }
        public int NumFrames { get { return this.numframes; } }
	public int CurrentFrame { get { return this.c_frame; } set { this.c_frame = (value < this.numframes ? value : numframes-1); } }
        public double Speed { get { return this.speed; } set { this.speed = value; } }
        public double Scale { get { return this.scale; } set { this.scale = value; } }
        public bool FlipX { get { return this.flip_x; } set { this.flip_x = value; } }
        public bool FlipY { get { return this.flip_y; } set { this.flip_y = value; } }
        public bool IsLoaded { get { return this.isLoaded; } }
    }

Feel free to swipe this. It's pretty standard fare, and ought to be included in the XNA package, but

I guess Microsoft feel that they needed to focus their attention more on the 3D aspects of the framework.

Also, all the Properties may annoy you, or seem unnecessary, but I see them

as being pretty useful. For instance, the CurrentFrame set method automatically checks to make sure that the supplied value is less than numframes, or

else it clamps it to numframes-1 (The last frame). This is transparent to you, since you do this: mySprite.CurrentFrame = 40;

And: <3 Ternary expressions.

Unfortunately, you can't see the animation, but I can assure you: It's perfect. Also, that was loaded from a GM PNG Strip, which is extremely useful to

me (Since I still do a lot of spriting in GM).

The second thing I did was switch from Variable Timing (Delta Time) to Fixed Step. Why? Because I'm making a NES style platformer, so I wanted NES style

timing (NTSC, 60FPS).

I'm finding XNA and C# to be extremely easy to use for game development (And tool development), and I actually think I'll be able to finish a game with

it fairly soon… And I have plans. Dark, secret plans.

*Laughs insanely*

I'm probably going to be documenting my efforts to create a game in my blogs, because I can. And because it may be of interest to others.

As usual, watch this space and remember to hit me violently over the

head with a large trout (RC keeps a stock of 'em) if I forget to

blog about it.

Part Two - Because I forgot to post Part One

Self explanatory. Today I wrote another piece of code that provides some pretty standard functionality: Tileset operations. I'm not posting the code

from this one though, because I wrote it at ??:?? AM. It works, but there are bound to be some bugs/missing features (Not to mention it's a mess).

For my next trick, I'm going to make another class that can load and draw a map file imported from TileStudio.

*shot*

Part Three - Levels

Multiple virtual-desktops aren't always a good thing, especially when one was writing ones blog, then forgot about the minimized file on Desktop

3. :S

Well, I wrote a pretty handy level loader. First I made a new TSD (TileStudio Definition, an export plugin basically) to output the data from the maps

in a format I wanted, and then I wrote a simple 'binary compiler' that simply encodes the file in Binary to make it semi hax-proof.

Originally I pulled a Hydra and made a class called MXMap32, which loads a map that is exported via TileStudio's Map->Export Map option (32 bit, 0

based). That worked, but I wanted more. You see, the default export option only exports linear tile data, and only for the currently selected layer.

But TS also has support for other handy things, like map-codes and tile boundaries, so I wrote a module to use with the Code Generation setup.

Here's the table data from a level file:

# of BYTES | DESCRIPTION

————————–

3 | Identifier (MXL)

4 | Length of map label string

? | Map label string (Can be any length)

4 | length of tileset dependancy

? | Tileset dependancy (resource name, like tileset.png)

| It's not stored internally though for easier updating.

4 | Map Width (Max ~4 billion >:3) (In tiles)

4 | Map Height

4 | Number of definitions

————————————-

DEFINITIONS START

————————————-

4 | X location (In tiles, so x = x * tile_width)

4 | Y location (Ditto)

4 | Back layer tile index

4 | Mid layer tile index

4 | Front layer tile index

1 | Bounds byte

1 | Object/Map code byte

————————————-

Repeat n times, then EOF

————————————-

Now, some of you may be wondering why the heck I'm using 32-bit values everywhere. The answer is simple: I want to avoid pulling a Notch.

That is, I don't want to have a reachable 'ceiling' of values, and 4-billion or so is more or less safe.

If I'd gone the path of the average DOS programmer (myself, in other words, or Notch who started off using the same language as I did), I would have

used single bytes (Thus capping the maximum width, height and tile index at 256. Sound familiar?), but I'm not going to be shy about essentially

quadrupling the size of my level files, because:

A) They're miniscule.

B) Who would complain about a 500kb level file when they have

<insert triple-A game here that consumes 10GB of HDD space>?

Anyway, the loader works just fine (Just a few obvious typos I had to fix), and renders the tiles 'smartly'. That is, it contains a few quick-to-process

'exclusion' cases. First of all: If a tile is lower than the view-height, it breaks the render loop (because tiles are processed from an array of data

in a linear fashion. Any tile index greater than the first instance of a tile matching that case is guaranteed to also be out of sight.

Then I do a rectangle intersection test with the view, but instead of breaking out of the loop, I just continue it.

I'm preparing my mind to implement some tile code I used a long time ago that, given an array and a view coordinate, can start processing from the exact

tile it needs to start processing at, and only process the tiles on the current screen (+1 on each side, for scrolling).

The 'psuedocode' for this operation looks something like this:

Hidden for various reasons.

// This array stores the tiles.
Tile[] tiles; // Size varies depending on map size

// This is the absolute view coordinate being 'mapped' to tile-space
// I offset it by (-1,-1) to render the border tiles
int tvx = (view_x / tile_width)-1; 
int tvy = (view_y / tile_height)-1;
// Calculate the width and height of the view in tiles
int tvw = view_width/tile_width;
int tvh = view_height/tile_height;
// Calculate the start index
// map_width is the width of the map in tiles, _not the absolute value_
int start_index = tvy * map_width + tvx;
// Number of tiles to process + 2 extra rows|columns
int p_count = (tvw*tvh) + (map_width*2);

// Clamp values
if(start_index < 0)start_index = 0;
if(start_index > tiles.count()-1)break; // Out of bounds

for(int i = start_index; i < start_index + p_count; i++)
{
  // Check values for validity and break loop if out of bounds
  if(i > tiles.count()-1)break;

  // Otherwise process
  DrawTile(i);
}

Excuse my not so pseudo-codey pseudo-code.

Anyway, I'm going to be using a similar method for checking collisions with the level (Since the bound codes are in an array of the same size as the

tiles. One per tile coordinate in the map).

Spot update

As my idea passed through the forge that is implementation, I few problems with the code surfaced. Luckily, I fixed them. Have a messy piece of code

that does the job (And has a few nifty side effects that I decided to keep):

public void Draw(SpriteBatch sb, Rectangle? viewRect = null)
        {

            int w = 32;
            int h = 32;
            Rectangle rr;
            if (viewRect != null)
            {
                rr = (Rectangle)viewRect;
            }
            else rr = new Rectangle(0, 0, 512, 480);

            int tvx = (rr.X / w)-1;
            int tvy = (rr.Y / h)-1;
            int tvw = (rr.Width / w)+2;
            int tvh = (rr.Height / h)+2;

            for (int iy = tvy; iy < tvy + tvh; iy++)
                for (int ix = tvx; ix < tvx + tvw; ix++)
                {
                    int i = iy * width + ix;
                    if (i > numtiles - 1)
                    {
                        i = (i % numtiles);
                        if (i > numtiles - 1) i = numtiles - 1;
                    }
                    if (i < 0)
                    {
                        i = -i;
                        i = numtiles - (i % numtiles);
                    }
                    int x = ix * w;
                    int y = iy * h;
                    x -= rr.X;
                    y -= rr.Y;

                    if (i > numtiles - 1) i = numtiles - 1;

                    if (backlayer[i].t >= 0) myTileset.Draw(sb, backlayer[i].t, x, y, 1.0);
                    if (midlayer[i].t >= 0) myTileset.Draw(sb, midlayer[i].t, x, y, 0.9);
                    if (frontlayer[i].t >= 0) myTileset.Draw(sb, frontlayer[i].t, x, y, 0.1);
                }
	}

Excuse it's messy nature, but it's fast. And fast code never looks pretty.

One of the 'features' is the fact that index values wrap. Meaning that the level wraps, which could be used to make an interesting layout. Only one

problem: I seem to have an error in my wrapping code that shifts the level up by one tile each time it wraps horizontally.

Now, by the calculations I've done and after much thought on the matter, I've determined that this game of mine can load a theoretically massive sized

level (Because it's a 32-bit implementation, that's 4GB), but still render it at the same speed as a single-screen map. Of course, this isn't really the

case because of the extra data that needs to loaded (Sprites, Tiles, Music, Sounds, etc).

It might surprise you that this is a method that was handed down from the days of DOS, though usually it was implemented in assembly (Or at least

optimized afterwards). Similar techniques were also used on the old consoles (NES, SNES, GBA, etc), because they didn't have enough RAM for the

programmers to practice the deplorable (Read: Lazy) methods we use. :P

So far, I'm doing great with this project, and the only major snag I've run into is that my coffee is about to run out. D:

Here. Have a screenshot of progress:

My next task is to fix my buggy view code. It doesn't work the way you'd expect it to (All in reverse, and not working for Entities). I'm first going to

see what options XNA offers, then make something myself if I have to.

Not too shabby, for three days of on-and-off work with lots of Minecraft in between (I'm designing an interesting mod).

Well, it looks like I've broken my own long-blog record. 2523 words. Not bad at all! Of course, you're all going to TL;DR this. Shame on you.

TL;DR Section

Icodedstuffwannaseeitlookimmamakingagaeminxnasinceyoucantbebotheredtoreadthefullblogyougetthisunreadablepieceofcrapinsteadlol.

Comments

LAR Games 12 years, 8 months ago

The TL:DR section was harder to read than the blog. lol

Astryl 12 years, 8 months ago

Which is precisely why I made it. :3

Rob 12 years, 8 months ago

What a coincidence… I'm trying to make levels too for some school project (SFML/C++). I recalled you mentioning TileStudio a few months ago, but since I'm stupid stubborn awesome I didn't use it and instead am making my really shitty, basic level editor. Hurray! Except I'm only storing the index/x/y for each tile AND THAT'S IT. (I'm not going for limitations on a fixed tile size, so the x/y are not in tile coordinates, but in pixels.)

Astryl 12 years, 8 months ago

My tiles aren't limited in size, the tile dimensions are stored in the file along with everything else, meaning I can quickly swap between 16x16, 32x32 or even 16x24 if I wanted to.

Rob 12 years, 8 months ago

Quote:
4 | X location (In tiles, so x = x * tile_width)

I was talking about this. What if your tile_width is 32 and you want it at position 16 of 48? Or is that a float…? I just assumed it would be an integer…

Astryl 12 years, 8 months ago

Ah, I see what you mean. I do have a layer offset that's absolute, but… yeah. I could add in another value for an offset for each tile, which might be handy. But personally, I don't see myself using it. Also, using the tile-coordinate system means that my collision detection code for the tile bounds is extremely quick.

=P

Rob 12 years, 8 months ago

Is there that much of a difference between checking different values for variables if they're the same size? (eg 4 vs 128, etc) They'd be ints either way. I always thought it was the same or negligible… But I'm not exactly an EXPERT PROGRAMMER

Astryl 12 years, 8 months ago

I could store the value as a float, yes, and it is the same size as a float, but: TileStudio doesn't support anything but grid-values, so what's the point? :P

Also, I never use offset tiles. Objects, sure, but tiles? No.

Rob 12 years, 8 months ago

…what? I was asking about the speed difference for using, say, ints ranging in the 100s vs in the 1000s. (like if you did it based on the tile grid compared to the raw pixel. like using 1 instead of 16, or 50 instead of 800)

Astryl 12 years, 8 months ago

Ah. Misread. Minecraft does that to me. Eh… No. There's no difference in using lower valued ints or higher valued ints, unless I was iterating through an array of length <x> where x is an int that is thousands of elements long. Technically, reading in the int values as 32-bit ints can take slightly longer, but… no. No difference. Except that, in collision detection code, I want to perhaps check the entire width of the object, which might be 16, which might mean that to get an accurate collision, I'd have to potentially iterate 16 times (I'd actually do three checks: min, mid and max.