Adding Sprites to "Game Engine"

Anything related in any way to game development as a whole is welcome here. Tell us about your game, grace us with your project, show us your new YouTube video, etc.

Moderator: PC Supremacists

Post Reply
mattheweston
Chaos Rift Junior
Chaos Rift Junior
Posts: 200
Joined: Mon Feb 22, 2010 12:32 am
Current Project: Breakout clone, Unnamed 2D RPG
Favorite Gaming Platforms: PC, XBOX360
Programming Language of Choice: C#
Location: San Antonio,Texas
Contact:

Adding Sprites to "Game Engine"

Post by mattheweston »

I've been working on a Tile Engine for a game that I'm planning on developing and have a 2D array of Tiles for my TileMap with multiple layers that are derived from an ILayer interface. Each layer is the same size and contains collision information, tiles, warps, etc.

I have been looking at sprites from https://www.spriters-resource.com/; however, the sprites don't match the tiles I am working with and some look like they could fit multiple tiles. After doing some research I believe the best way would be to somehow decouple it from the TileEngine, but I'm at a loss as to how to store the sprite information and keep it sync'd with the Tile Engine in order to write the collision detection code.

Do I need to rearrange the spritesheets to better match my "Engine" or should I focus on making my "engine" work with spritesheets no matter how they are arranged?

Does anyone know a good 2D tutorial that might help me with what I am doing or could provide some advice on how to proceed?
Image
User avatar
dandymcgee
ES Beta Backer
ES Beta Backer
Posts: 4709
Joined: Tue Apr 29, 2008 3:24 pm
Current Project: https://github.com/dbechrd/RicoTech
Favorite Gaming Platforms: NES, Sega Genesis, PS2, PC
Programming Language of Choice: C
Location: San Francisco
Contact:

Re: Adding Sprites to "Game Engine"

Post by dandymcgee »

There are two popular ways to implement player movement in a 2D game: tile-based and pixel-based ("free" movement).

Tile-based movement restricts the player to moving only to the centers of tiles. This makes collision really easy: test the walkability of the destination tile before allowing the player to move.

Pixel-based movement allows the player move freely over the tiles. This makes collision slightly more complicated because the player can now be standing in up to 4 tiles at a time. When the player requests to move, calculate which 4 tiles are at the destiation and test their walkability. For each that has a collider, prevent the player from moving (if solid) or execute the action (e.g. trigger some event).

Rendering is unrelated to the collision problem. The problem is rendering everything in the correct order. The following is one way to achieve this (of many):
render_image(BACKGROUND_BACK);                              // Static image of mountains / sky if your 2D game has this sort of thing (
render_image(BACKGROUND_FRONT);                             // Parallax foreground image(s) or tiles
render_tiles(LAYER_FLOOR,        ALWAYS   , NULL       );   // Tile floor
render_tiles(LAYER_WALL,         LESS_THAN, player.y   );   // First layer above floor (e.g. floor cracks, walls, fences, tall grass, rocks, flowers, etc.)
render_tiles(LAYER_WALL_DETAILS, LESS_THAN, player.y   );   // Additional details on "wall" (e.g. painting on wall, window, cracks on wall/fence, graffiti, etc.)
render_sprite(player);
render_tiles(LAYER_WALL,         GREATER_THAN, player.y);   // Same as above, but in front of player
render_tiles(LAYER_WALL_DETAILS, GREATER_THAN, player.y);   // Same as above, but in front of player
render_tiles(LAYER_CEILING,      ALWAYS   , NULL       );   // Things to render above sprites (e.g. roof overhangs, bridges*, top tile* of lamp post, etc.)
* Bridges: If you want to have multiple walkable elevations, you'll have to repeat the 4 layers above for as many walkable layers as you need (or thing of something else more cunning).

* Top tile: If you want objects to exist in *both* the layer below the player and the layer above the player, you'll need to set different elevations for each tile. Consider a wall that is 3 tiles tall: the bottom tile has to be rendered above the grass, but below the player because you can only stand in front of it (LAYER_WALL), the top tile has to be rendered above the player because you can only stand behind it (LAYER_CEILING). The middle tile has to be rendered before the player if you're standing in front of the wall, and after the player if you're standing behind the wall. You can implement this by sorting everything by its Y-coordinate and rendering it in two passes depending on whether it's greater or less than the player's y-coordinate.

The background layers are whatever you want to show up if no tiles are rendered.

E.g. Terraria:
Image

E.g. Rad Raygun

Falco Girgis wrote:It is imperative that I can broadcast my narcissistic commit strings to the Twitter! Tweet Tweet, bitches! :twisted:
User avatar
YourNerdyJoe
Chaos Rift Cool Newbie
Chaos Rift Cool Newbie
Posts: 79
Joined: Sun Oct 02, 2011 3:28 pm
Current Project: Top secret (not really) Top-Down Shooter for GBA
Favorite Gaming Platforms: GBA, Gamecube, PC, 3DS
Programming Language of Choice: C/C++
Contact:

Re: Adding Sprites to "Game Engine"

Post by YourNerdyJoe »

For storing each sprite's information you can doing something like this (in pseudo c++):

Code: Select all

class Sprite {
 Texture* tex;         //sprite's texture, keep track of what's already loaded somewhere
 int x, y;                 //the sprite's position
 int width, height;        //the size of the sprite
 int frame or texure_coordinates, etc;  //store which portion of the sprite is being drawn
};                  //plus anything else your specific project may need
The important thing to note is that we keep track of the size of each sprite since they vary unlike tiles. Then you can use the size of the sprite, size of the texture, and the current animation frame to calculate the texture coordinates (similar to finding the texture coordinate for a tile based on a tile index but without fixed size tiles).

Also some advice for spriters-resource, you may want to edit some of the sprite sheets to line things up better (align each frame to a grid or something) to make the code a bit simpler.

And adding to dandy's answer on collision, make sure you calculate how many tiles the sprite could be colliding with. So if you're using 16x16 tiles, a 16x16 sprite could cover 2 tile across and 2 tiles down. A 32x32 sprite could cover 3 across and 3 down, etc.
See that?.....
Exactly
https://yournerdyjoe.github.io/
User avatar
dandymcgee
ES Beta Backer
ES Beta Backer
Posts: 4709
Joined: Tue Apr 29, 2008 3:24 pm
Current Project: https://github.com/dbechrd/RicoTech
Favorite Gaming Platforms: NES, Sega Genesis, PS2, PC
Programming Language of Choice: C
Location: San Francisco
Contact:

Re: Adding Sprites to "Game Engine"

Post by dandymcgee »

YourNerdyJoe wrote: And adding to dandy's answer on collision, make sure you calculate how many tiles the sprite could be colliding with. So if you're using 16x16 tiles, a 16x16 sprite could cover 2 tile across and 2 tiles down. A 32x32 sprite could cover 3 across and 3 down, etc.
Yeah, good point. I made some assumptions about the relative size of sprites to tiles.
Falco Girgis wrote:It is imperative that I can broadcast my narcissistic commit strings to the Twitter! Tweet Tweet, bitches! :twisted:
mattheweston
Chaos Rift Junior
Chaos Rift Junior
Posts: 200
Joined: Mon Feb 22, 2010 12:32 am
Current Project: Breakout clone, Unnamed 2D RPG
Favorite Gaming Platforms: PC, XBOX360
Programming Language of Choice: C#
Location: San Antonio,Texas
Contact:

Re: Adding Sprites to "Game Engine"

Post by mattheweston »

This may be better as a new topic, but I'll post here as a continuation of this topic....

Is it better when building an engine or framework for a game to allow for all different kinds of sprite/tile sheets OR should you force the issue and require that those sheets be in a specific format?

From a software engineering standpoint, I would think you'd want your code to be as flexible as possible to allow for reuse, but I'd like to see what others think.
Image
User avatar
YourNerdyJoe
Chaos Rift Cool Newbie
Chaos Rift Cool Newbie
Posts: 79
Joined: Sun Oct 02, 2011 3:28 pm
Current Project: Top secret (not really) Top-Down Shooter for GBA
Favorite Gaming Platforms: GBA, Gamecube, PC, 3DS
Programming Language of Choice: C/C++
Contact:

Re: Adding Sprites to "Game Engine"

Post by YourNerdyJoe »

It all depends on what you're trying to accomplish.
Are you trying to make a flexible engine or framework? Or are you trying to make a specific game that doesn't require the flexibility?

If you're doing the first one, then allowing user defined sizes is probably a good idea.
However, if you're making a game where all the tile sheets will be the same size, sprites and tiles are the same size, etc. then there's no reason to not just code that one case. If the requirements change, you can always add the extra flexibility later when you need it.
See that?.....
Exactly
https://yournerdyjoe.github.io/
User avatar
dandymcgee
ES Beta Backer
ES Beta Backer
Posts: 4709
Joined: Tue Apr 29, 2008 3:24 pm
Current Project: https://github.com/dbechrd/RicoTech
Favorite Gaming Platforms: NES, Sega Genesis, PS2, PC
Programming Language of Choice: C
Location: San Francisco
Contact:

Re: Adding Sprites to "Game Engine"

Post by dandymcgee »

YourNerdyJoe wrote:If you're doing the first one, then allowing user defined sizes is probably a good idea.
Even in this case, it's probably best to set some sort of limitations unless having something fully dynamic is a total necessity.
YourNerdyJoe wrote:However, if you're making a game where all the tile sheets will be the same size, sprites and tiles are the same size, etc. then there's no reason to not just code that one case. If the requirements change, you can always add the extra flexibility later when you need it.
This. Why waste a ton of time now for something that might never be useful when you can waste far less time later rewriting only the relevant bits to work with only the new requirements when you know what the actual requirements are.

The most important thing to strive for, which has the great side effect of inherently creating resuable code, is modularity or "separation of concerns". Rather than writing a 50 file conglomeration of spaghetti that is all intertwined with use-case specific class hierarchies, global interdependencies (e.g. singletons that shouldn't be singletons), focus on writing bite-size chunks which serve a focused purpose. Your math code should all be in the same place, and only do math. It shouldn't write to files. It shouldn't read from the network. It shouldn't render bounding boxes. It should just do math. If some other piece of code needs to do math, it should call your math module via some coherent API. Same goes for other systems.

I also like the opinion of Mattias Johansson expressed here: https://www.quora.com/Should-you-write- ... e-use-case
I will duplicate the post here for posterity:
Have you seen the movie PI? It's about this mathematician that goes mad in the search for a number, seeing it everywhere.



Generalizing code is a lot like numerology. Once you know about the principle of removing duplication, you start seeing it everywhere. And when you see it, you cannot help yourself, you just have to get rid of that duplication. Nasty, nasty, duplication. Evil. Evil. Evil. Evil.

The problem is that coincidence is a lot more common that the human brain thinks it is. There might not actually be a general case, even if you see it clear as day. It might just be coincidence.

Let's say I'm making two requests to a BananaService. After writing them, the code for the two calls look almost exactly the same. And I'm going to be making tons of different calls to the BananaServer as I code. Duplication! EVIL! So you spend some time refactoring it into a BananaServiceRequest.

Sometimes, this works out for me. The BananaServiceRequest turns out to be super useful and saves me a lot of time and code. Since that makes me happy and feel accomplished, my cognitive bias is to remember those times.

What my brain (because it's a human brain) tends to forget is the much more common cases where that kind of speculative refactoring doesn't work out. When I start writing the third request to the BananaService, it doesn't fit into my neat generalization, so I have to extend it. And when I extend it, it doesn't look so neat anymore. And I don't end up making many calls to BananaService after that either. In fact, when I review the git commit for it all, it actually ends up being more code than the original duplication because of the overhead the generalization added.

Because I'm human, I tend to fall into the above trap a lot. Therefore, I try to stick to two simple rules to prevent that:

Wait for three instances of duplication before generalizing.
This is partly because two cases are not enough to deduce the bigger general case that also works for case number three and four, so you tend to do it wrong. It is also because generalization always adds a bit of overhead, so it's likely that you'll actually end up with more code to maintain after generalizing, defeating the purpose of the generalization. If the third use case that you speculatively anticipate never arises, you've actually made your code base bigger and harder to maintain without adding any functionality!

Intentionally create duplication before generalizing.
I tend to want to find patterns. This makes me see them even when there are none. If two pieces of code kind of look the same, I discipline myself to first change them to make them exactly the same before I try to generalize them. If you don't, you will eventually end up on a wild goose chase trying to create a general case for two pieces of code that aren't really duplication.

If you like my writing, don't miss out on more of it -
follow me on Quora and Twitter (http://twitter.com/mpjme)
More reading if you're bored: http://josdejong.com/blog/2015/01/06/code-reuse/

Also, if you want an excellent example of well-modularized code, I'd recommend taking a look at Eskil's source code for his Quel Solaar engine:
http://www.quelsolaar.com/files/verse_apps.zip

It's a very large project, but even just looking at the directory structure of the files tells you a lot about how it is architected internally.

In summary, reusable code is great if you can do it without adding heaps of useless complexity. Otherwise, just solve the problem you're trying to solve and worry about future problems in the future when you actually understand them rather than pretending you're psychic.
Falco Girgis wrote:It is imperative that I can broadcast my narcissistic commit strings to the Twitter! Tweet Tweet, bitches! :twisted:
mattheweston
Chaos Rift Junior
Chaos Rift Junior
Posts: 200
Joined: Mon Feb 22, 2010 12:32 am
Current Project: Breakout clone, Unnamed 2D RPG
Favorite Gaming Platforms: PC, XBOX360
Programming Language of Choice: C#
Location: San Antonio,Texas
Contact:

Re: Adding Sprites to "Game Engine"

Post by mattheweston »

In other words YAGNI!
Image
Post Reply