Jump to content
  • Advertisement
Sadbus

Iometric depth sorting (C++, SFML)

Recommended Posts

Hello!

I am creating an isometric RPG with C++ and SFML. Maps are created in Tiled and loaded using JsonCPP. I am having difficulties sorting the player with bigger sprites.

I have a big tileset with textures of multiple sizes. In Tiled the entire tileset is cut into 1x1 tile pieces, which means a bigger texture like a tree (2x2 up to 2x4 tiles) is drawn as multiple sprites. Which creates problems sorting with the player (as you can see in the attached picture). What I am doing now is setting a priority to each sprite corresponding to the cartesian Y coordinate, and sorting all the sprites before drawing them. The green dots are the y coordinates I use for sorting. Middle bottom of sprites, and between the feet of the player.

I can see a couple solutions to this, all involving drawing larger textures as one sprite.

  • Adding size of texture as a custom property to tiled. And just placing one of the tree tiles and having the code draw the whole texture. But this will make map creation difficult as it will not look the same in game, I could place the whole tree and setting all tiles except one as not visible.
  • Split the textures by size into different tilesets. Will this impact performance? How would I know which tile corresponds to which tile set? Preferably dynamic, so I don’t need to change the code when adding another tileset.

Are there better solutions that I have overlooked? Any suggestions be welcome!

 

Thanks,
Sadbus

depth sort.PNG

Share this post


Link to post
Share on other sites
Advertisement

I feel like your current approach should be viable, if you sort using the baseline of the sprites by y co-ordinate (with y increasing from top of the screen to the bottom).

 

Edit: I just reread your post and caught the bit about breaking the sprites up into tiles. That's fine but you're going to need to use the same sort order for all of the tiles which compose each sprite and use the baseline of the bottom tiles as the sort key.

Edited by DerekB

Share this post


Link to post
Share on other sites
23 minutes ago, DerekB said:

I feel like your current approach should be viable, if you sort using the baseline of the sprites by y co-ordinate (with y increasing from top of the screen to the bottom).

 

Edit: I just reread your post and caught the bit about breaking the sprites up into tiles. That's fine but you're going to need to use the same sort order for all of the tiles which compose each sprite and use the baseline of the bottom tiles as the sort key.

How can I achieve that without hardcoding for every gid making up a tree?

Share this post


Link to post
Share on other sites

think in layers, ground layer, player layer, tree layer. I did the following:

/**
 * Order of blitting sprites in a single voxel (earlier in the list is sooner).
 * The number shifted by #CS_LENGTH denotes the order, the lower bits are used to denote the kind of sprite
 * being plotted, for mouse-click detection.
 * @see PixelFinder, Viewport::ComputeCursorPosition
 */
enum SpriteOrder {
	SO_NONE            = 0,                             ///< No drawing.
	SO_FOUNDATION      = (1  << CS_LENGTH),             ///< Draw foundation sprites.
	SO_GROUND          = (2  << CS_LENGTH) | CS_GROUND, ///< Draw ground sprites.
	SO_GROUND_EDGE     = (3  << CS_LENGTH) | CS_GROUND_EDGE, ///< Used for ground edge detection
	SO_FENCE_BACK      = (4  << CS_LENGTH),             ///< Draw fence on the back edges
	SO_SUPPORT         = (5  << CS_LENGTH),             ///< Draw support sprites.
	SO_PLATFORM        = (6  << CS_LENGTH) | CS_RIDE,   ///< Draw platform sprites.
	SO_PATH            = (7  << CS_LENGTH) | CS_PATH,   ///< Draw path sprites.
	SO_PLATFORM_BACK   = (8  << CS_LENGTH) | CS_RIDE,   ///< Background behind the ride (platform background).
	SO_RIDE            = (9  << CS_LENGTH) | CS_RIDE,   ///< Draw ride sprites.
	SO_RIDE_CARS       = (10 << CS_LENGTH) | CS_RIDE,   ///< Cars at a ride.
	SO_RIDE_FRONT      = (11 << CS_LENGTH) | CS_RIDE,   ///< Ride sprite to draw after drawing the cars.
	SO_PLATFORM_FRONT  = (12 << CS_LENGTH) | CS_RIDE,   ///< Front of platform.
	SO_PERSON          = (13 << CS_LENGTH) | CS_PERSON, ///< Draw person sprites.
	SO_FENCE_FRONT     = (14 << CS_LENGTH),             ///< Draw fence on the front edges
	SO_CURSOR          = (15 << CS_LENGTH),             ///< Draw cursor sprites.
};

/**
 * Data temporary needed for ordering sprites and blitting them to the screen.
 * @ingroup viewport_group
 */
struct DrawData {
	/**
	 * Setter method to initialize the other fields.
	 * @param level Slice of this sprite (vertical row).
	 * @param z_height Height of the voxel being drawn.
	 * @param order Selection when to draw this sprite (sorts sprites within a voxel). @see SpriteOrder
	 * @param sprite Mouse cursor to draw.
	 * @param base Base coordinate of the image, relative to top-left of the window.
	 * @param recolour Recolouring of the sprite.
	 * @param highlight Highlight the sprite.
	 */
	inline void Set(int32 level, uint16 z_height, SpriteOrder order, const ImageData *sprite, const Point32 &base, const Recolouring *recolour = nullptr, bool highlight = false)
	{
		this->level = level;
		this->z_height = z_height;
		this->order = order;
		this->sprite = sprite;
		this->base = base;
		this->recolour = recolour;
		this->highlight = highlight;
	}

	int32 level;                 ///< Slice of this sprite (vertical row).
	uint16 z_height;             ///< Height of the voxel being drawn.
	SpriteOrder order;           ///< Selection when to draw this sprite (sorts sprites within a voxel). @see SpriteOrder
	const ImageData *sprite;     ///< Mouse cursor to draw.
	Point32 base;                ///< Base coordinate of the image, relative to top-left of the window.
	const Recolouring *recolour; ///< Recolouring of the sprite.
	bool highlight;              ///< Highlight the sprite (semi-transparent white).
};

/**
 * Sort predicate of the draw data.
 * @param dd1 First value to compare.
 * @param dd2 Second value to compare.
 * @return \c true if \a dd1 should be drawn before \a dd2.
 */
inline bool operator<(const DrawData &dd1, const DrawData &dd2)
{
	if (dd1.level != dd2.level) return dd1.level < dd2.level; // Order on slice first.
	if (dd1.z_height != dd2.z_height) return dd1.z_height < dd2.z_height; // Lower in the same slice first.
	if (dd1.order != dd2.order) return dd1.order < dd2.order; // Type of sprite.
	return dd1.base.y < dd2.base.y;
}

/**
 * Collection of sprites to render to the screen.
 * @ingroup viewport_group
 */
typedef std::multiset<DrawData> DrawImages;
  
DrawImages draw_images;
draw_images.clear();

draw_images.insert(..); // For all the sprites that must be drawn.

// drawing  
for (const auto &iter : collector.draw_images) {
	const DrawData &dd = iter;
	const Recolouring &rec = (dd.recolour == nullptr) ? recolour : *dd.recolour;
	_video.BlitImage(dd.base, dd.sprite, rec, dd.highlight ? GS_SEMI_TRANSPARENT : gs);
}

The DrawImages multi-set sorts all sprites in what you set as < relation. Rendering is just adding all sprites, and then iterate over them, drawing each sprite.

The 'slice level' here is what you see in a horizontal row from left to right, ie rectangles connected corner to corner rather than edge to edge, like a checker board standing on one of its corners at 45 degrees, where its fields then run horizontally from left to right. I used that so all tiles at equal distance (equal height at the screen) are drawn together, from back to front.

Within a tile I have a fixed order for each type of sprite from back to front, eg back-fence, player, front-fence, and so on.

 

Share this post


Link to post
Share on other sites
3 hours ago, Alberth said:

How can I achieve that without hardcoding for every gid making up a tree?

I would assign a sorting ID to each tile in the map and at preprocessing or loading determine the map space sorting coordinate for each sorting ID, then when you want to render a set of tiles you can just sort on sorting coordinate.

The sorting coordinate would be the map space Y co-ordinate of the tile which corresponds to the lowest screen space Y co-ordinate when rendering (visually that's the bottom edge of the tile on screen).

Background tiles could all get a special sentinel ID and sorting co-ordinate which always sorts to the end of the list, multi-tile sprites like your trees would have the same ID for each tile so the whole lot sort together. Dynamic sprites like the player sort correctly based on their current sorting co-ordinate.

The assignment of sorting IDs to tiles would best be done automatically, given the information of which tiles are background tiles,  which tiles are members of sets like your carved up trees, etc, it should be possible to develop an algorithm that assigns appropriate sorting IDs.

 

3 hours ago, Alberth said:

think in layers, ground layer, player layer, tree layer.

I think the trouble here is that OPs situation involves an unbounded number of tree layers and no fixed ordering for the player layer.

Edited by DerekB

Share this post


Link to post
Share on other sites

Thanks for all help!

I solved it a while ago now, but I figured I would leave my solution here for others to find. Im gonna rewrite it to use use a different entity system, but the parsing will stay the same.

In Tiled I added a height multiplier property to sprites consisting of multiple tiles. Tiles below the top one are set to zero.

The code that parses the tiled json file checks if the tile has a height property, if not it loads it as a normal sized tile. If the value is set to 0 it continues to the next iteration of the for loop. If the value is set, it multiples the tile height by the height value.

if (properties[std::to_string(gid)].isMember("Height"))
{
    if (properties[std::to_string(gid)]["Height"].asInt() == 0)
        continue;
    else
        tileSize.y *= properties[std::to_string(gid)]["Height"].asInt();
}

I also had to mess about with some offsets, because different sized tiles got placed in different locations.

All the sprites are contained within its own object, containg a priority (basically the y coordinate). The sprite objects are added to a list and sorted like this.

objects.sort([](Object *f, const Object *s) { return f->priority < s->priority; });

 

Share this post


Link to post
Share on other sites

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!