Easy2D - Yet Another 2D Engine

Started by
5 comments, last by ValMan 15 years, 9 months ago
Hello everyone. This is my first post here. I've been working in this little 2D engine project for a while, and I'd like to share it with this community. It is opensource, and my primary objective is to help others make nice games. I know the project is far from professional, after all that's just something I could accomplish in my spare time. Also, I'm no C++ expert, so take it easy on the comments, ok? Here is the link: http://easy2d.sf.net Thanks.
Advertisement
It may be better to create a separate Camera class, so that you could render multiple views of the tilemap at the same time. As is, you would have to change view, render, change view, render, etc.

I didn't look at the code, only at doxy, but it looks like your Sprites are not connected to the map. For a 2D game engine, you would want the sprites to have a position within the map, so they scroll when the map scrolls.

Pretty good and simple design, too. I've been building my own 2d engine for 4 years now, and it's very slowly acquiring roughly the same design shape as yours (I have a game class that does execution flow, a resource map, thinking of making a Graphics class and Audio class, etc. My engine also started out with blitting (but on Win32) and now I converted it to DirectX.
Hi

I'm making a 2D game too. Look at www.codeplex.com/ArcEngine for more details.

- Iliak -
[ ArcEngine: An open source .Net gaming framework ]
[ Dungeon Eye: An open source remake of Eye of the Beholder II ]
ValMan, thanks for the tips.

The idea of creating a separate camera class is indeed nice, it has just been added at the top of my TODO list.

About connecting the sprites to the map, how would it be accomplished? I was thinking on creating a "world" class, where you would have your map, sprites, etc... and scroll them all with a single scroll method, as well as blit them with another single blit method (implementing a rendering pipeline). Is this a good idea?

Thanks.
Well, I can tell you the way I do it. Sorry for long post, but I love talking about my engine :)

First of all, I have a division between "Sprites" and "Actors". A Sprite is a collection of Animations. Each Animation is a collection of Frames and has pointer to Texture that those frames reference. Each frame has texture coordinates and delay in milliseconds. Sprites are treated the same way as textures, meaning they are a "resource", they are not directly used (don't have a Draw() method or a position in space)

Actor is an entity living on the tilemap, represented visually by a Sprite, with the help of Sprite Instance that every actor has. Sprite Instance contains pointer to Sprite, index of current Animation of that sprite that the actor is playing, time when animation started, time when animation was last updated, and animation options (like "paused" which always shows current frame, "stopped" which always shows first frame, "reverse" which plays animation in reverse, etc). When an Actor is drawn, it queries Sprite Instance for the current frame. Sprite Instance calculates current frame index based on time when animation started in relation to current time, looping through current animation's frames and adding up their delays. Then actor uses the current animation's texture, along with current frame's texture coordinates, to draw itself.

Actors have a continuous position within the map, represented in tiles, with floating-point coordinates. So for example an X position of 3.5 would mean a horizontal offset of three-and-a-half tiles from the left edge of map. Because that position is relative to the map, Actors get scrolled when the map view scrolls.
Using a floating point offset instead of pixels also makes it easy to figure out which tiles an Actor is currently over - so it can be added to the world database. World database (using a simple grid for 2D games, or BSP/Octree for 3D games) is a data structure that you can query to get a list of Actors in a specific location. This is useful for visibility detection and collision detection.

For example, when the camera scrolls, some Actors may become visible and others may become invisible. A naive way is to loop through every actor on the map, checking if it is within the camera. If yes, we add it to the list of currently visible actors. But what if you have thousands of actors on the map? Each time you scroll, there would be a slowdown. So what you do instead, is, for every tile, store a linked list of Actors that are currently overlaying that tile. That 2D array of linked lists of actors for every tile is the 2D world database. Now when the camera scrolls, clear the list of currently visible actors, loop through elements in the world database that correspond to tiles visible in camera, get a list of actors for each one, add those actors to the list of currently visible actors, then sort the list by actors' Z coordinate, and remove duplicates (because each actor could overlay more than one tile). This ensures that no matter how many Actors you have on the map, scrolling performance will always be proportional to the number of Actors actually visible, thus reducing the load in average case.

World database is useful for collision detection in the same way. Every time an Actor moves, you have to check for collisions, and do collision response. This means you have to check moving actor against other actors it may collide with. A naive way to do it, once again, is to loop through every single actor on the map. With world database, you can get the extents of the actor to figure out which tiles it overlays, then query world database to get a list of actors over those tiles. Add actors found in this way to "possible collisions" list, remove duplicates, and loop over that list checking for collisions. This way, you only check collisions against actors that are close by, this improving average case performance.

I don't have the concept of tile layers, because that's what Actors are, essentially. Each actor has X, Y and Z coords. X and Y could be set to integers to align actor position to tiles, and Z can be used to control stacking order. So you could logically assign a Z range of 0 to 0.1 to "lowest layer", 0.2 to 0.6 to "middle layer" and 0.7 to 1.0 to "highest layer". Within the middle layer, you could use different Z values based on the vertical position of sprites, to create an illusion of depth. Or any other way you would like - it's more flexible than tile layers, but can be more difficult to manage for the programmer.

Before actual drawing to screen takes place, I have to translate everything into pixels. So I transform all actor positions from world space to camera space (by subtracting camera position from actor position), then multiply the result by tile size. So X position of 3.5 with tile size of 32x32 would give 112 pixels. In addition to this, there is also a camera space to screen space transformation, which makes is possible to draw the camera view to screen with an offset. This could be used to center camera view on the screen for example. Lastly comes drawing the map. When the map is drawn, I first draw visible tiles, then I loop through the list of currently visible actors and draw those. Ideally, the Camera would be managing which tiles are visible and which actors are visible, with a possibility of using multiple cameras.

So that's it, I hope this doesn't sound like gibberish.
No, not at all. Actually, it was very helpful.

I handle the sprites and actors pretty much the same way you explained, i just "name" stuff a bit differently, but the principle is the same. I just didn't have neither a position relative to the map, nor a Z variable, but both of them were in my TODO list already.

Good advice you gave me:

1) Use floating point numbers for sprite (or actor) X,Y,Z positions. So, no matter what the tile size is, you don't need to change anything else in the actors code.

2) Have a list of actors above the tiles, so you don't need to loop through all actors to get those who will be drawn, and improve collision detection performance.

3) Detach the camera (or viewport) from the map, so you can have more than just one camera at a time.

I think you only missed the part when an actor has to be drawn behind a wall (I mean, behind a part of the tilemap), so I think you should assign a Z value to the map layers as well.

I am still, like, "getting the hang" of game programming, and I'm coding this engine as the need arises for creating my demos, that's why it lacks some good patterns. Those advice of yours were very valuable, real real thanks !! =)
I recommended you to do detach camera from the map because it helps with performance in cases where you need to render two different parts of the map simultaneously on each frame. For example, when you have a portal through which you can see another part of the map - a cool eye candy. Also this would be useful when you want to have computer screens within the game world, showing other parts of the world as if they were a filmed by security cameras - another cool eye candy. This functionality would be too much trouble if you can afford to render different parts of the map linearly instead of in parallel - for example, in a cut-scene, where you transition or cut from one camera position to another, or when you simply move the camera to stay behind the player character.

Also, I decided to take your advice on implementing tile layers. As I started to sketch out the new functionality, I found that having a Camera for a map with multiple layers all of which could be scrolled independently is a little problematic (my original Camera idea was for a map with only one layer). Multiple layers are indeed good because not only could you use them to create over-hangs, but you could also scroll them with various speeds to create an old-school parallax effect like in Metroid or Duke Nukem 2D. I suppose you could even create maps of buildings with multiple floors. So I decided I definitely should implement layers.

From here on, I decided there are two ways to handle the multi-layer camera problem: first, make Camera class contain one size (the size of visible area), and an array of scroll positions, one for each layer. An array (std vector) of Camera objects would be owned by the Map so when you want to create another Camera, you would go Map->CreateCamera(), therefore the Camera can have a reference to the Map so it knows how many tile layers the Map has, and can allocate the same number of scroll positions, one for each layer. To scroll the layers, you would call Camera->SetPosition(layerindex, x, y) and to change size of visible area you would call Camera->SetSize(cx, cy). The map will also have an index of the "current" camera, so you would do something like Map->SetCamera(0) to set to first camera, then Map->Render(), then Map->SetCamera(1) and Map->Render(), etc. That way, for simple games, user can do Map->Render() without worrying about cameras, because a "default" camera would be created and selected as current camera when the map is created.

The second way of handling cameras is to have Camera only expose one position along with one size (but still internally containing a scroll position for each layer). Then each layer on the map would have a "parallax" floating point property. Camera would always store the previous scrolling position it scrolled to, and each time you do Camera->SetPosition(x,y), old position will be subtracted from new position to figure out the delta between the two. Then, for each layer, the delta would be multiplied by that layer's parallax property and added to current scroll position for that layer. Thus you scroll the whole map with only one set of coordinates and all of the layers respond at the same time, yet individually. This makes parallax possible, but if you want to scroll one of the layers in response to something other than camera changes - in response to time, for example - you would be out of luck.

In other news, I realized that I could save a lot of memory if instead of allocating a tile structure for each tile (each tile in my engine takes around 16 bytes because there is a chance of it being animated), I instead allocate a 2D array that points into a 1D array of tiles. For each tile which has not been given a texture (meaning, it's just a blank spot), the corresponding pointer in the 2D array would be NULL. Each time a tile is edited in the map editor and given a texture, I would first check the 1D tile array to make sure there is not a tile with the same attributes that already exists (in which case I would set pointer to that tile). If the edited tile is unique, then I add it to the 1D tile array, and set corresponding 2D array pointer to the newly added tile. In addition, when the map is saved, I would call an Optimize() method, which will re-arrange the 1D tile array to appear in row-major order for better cache coherency. Optimize() method could also handle the checking for tiles with same attributes and re-routing the pointers, because doing it while the map is being edited could prove to be too slow.

This topic is closed to new replies.

Advertisement