21
\$\begingroup\$

I'm using XNA to program a game, and have been experimenting with various ways to achieve a 'selected' effect on my sprites. The trouble I am having is that each clickable that is drawn in the spritebatch is drawn using more than a single sprite (each object can be made up of up to 6 sprites).

I'd appreciate it if someone could advise me on how I could achieve adding an outline to my sprites of X pixels (so the outline width can be various numbers of whole pixels).

Thanks in advance,

  • Greg.
\$\endgroup\$

9 Answers 9

20
\$\begingroup\$

By far the easiest way to do this (so probably the best way, unless you are really strapped for performance) is to have two copies of your sprites.

  • The regular version
  • A"fat", uncoloured version - basically a white version of your sprite X-many pixels "fatter" than the original.

Draw your entire object using the "fat" version, then draw the regular version over the top.

By making the "fat" version white, you can use SpriteBatch's built-in colour tinting to change the selection colour dynamically.

To generate your "fat" verison I recommend writing a Content Pipeline Extension that can automatically take your original sprites, read their alpha channel, create a new alpha channel by sampling the maximum alpha channel in the original image X-many pixels around each pixel, and setting RGB = (1,1,1).

You will have to make sure your sprites all have sufficient transparent border to add the outline (you could check this in the content processor - and even make room if necessary).

If you only have a few sprites, then you could just use a good image editor (GIMP, Photoshop) and do it by hand: Alpha channel to selection, expand selection, selection to alpha, fill colour channels white.

\$\endgroup\$
0
4
\$\begingroup\$

I guess you need to draw all the pieces who each object to a single sprite first. Then I think you'd have to write a shader to detect the edges of the sprite a draw a pixel where-ever it finds an edge. I expect there must be some shaders out there already to do this, which you could either use or port.

\$\endgroup\$
1
  • 11
    \$\begingroup\$ This kind of shader is surprisingly annoying to write, I've heard (we used it in one of our 3D games and kept running into unsightly edge cases). One thing you might want to consider is encoding the outline directly into the sprite texture with a specific color like (0, 255, 255, 0), and just have the shader transform that color when the sprite is selected. Then the shader is a trivial color change, and you have a high level of artist control over the outline and any details you want. \$\endgroup\$
    – user744
    Commented Sep 14, 2010 at 18:24
4
\$\begingroup\$

Depending on requirements, what might also be effective is just creating an outline on-demand for the sprite. I'm assuming your sprites have transparency, and are irregularly shaped rather than just being rectangles (while this would work fine for that, outline rectangles should be Trivial).

when selected:
   outline = new sprite canvas of appropriate size
   for sprite in object:
      # use larger numbers for thicker outlines
      for x in (-1, 0, 1) and y in (-1, 0, 1):
         render sprite mask into canvas at x,y with desired color

Note that you don't need to do this every draw (although I suppose you could), but just create the new outline sprite when switching sprites.

\$\endgroup\$
1
  • \$\begingroup\$ Yes, this is what I wanted to suggest as well. Render the masked sprite 1 pixel to the left, right, top and bottom of the original sprite, under the existing sprite. Many games used this method to outline rendered text, or with only 2 of the 4 positions to create a drop shadow. \$\endgroup\$
    – Kaj
    Commented Sep 14, 2010 at 22:19
1
\$\begingroup\$

The simplest brute force approach is to build two copies of each sprite, a normal and a highlighted one. Then just swap them when highlighted.

If you've got memory to spare there is no need to get more complicated then that. Plus artists have complete control over the look when highlighted so you can do an outline or anything else you'd like.

\$\endgroup\$
2
  • \$\begingroup\$ I think part of the problem is that the OP says each object can be made of multiple sprites. So I guess the outline has to be the outline of the combined object, instead of having a separate outline around each component sprite. \$\endgroup\$
    – Chris Howe
    Commented Sep 14, 2010 at 19:34
  • 1
    \$\begingroup\$ Chris, it does not have to be the outline of the combined object, and it will work fine for an object made out of multiple sprites. Just do exactly what wkerslake said, but make sure you draw the highlighted sprites behind all of the normal sprites for that object. That way no matter how many sprites an object is made of, the highlight will only use slightly more than double the draw time for that object, which should be a lot less than generating the outlines at rendertime with the combined sprites. \$\endgroup\$ Commented Sep 14, 2010 at 19:56
1
\$\begingroup\$

How about for each sprite, also have another sprite that's an outline of the base sprite. When drawing an outlined object, draw the base sprites, then make a mask of the combined rendering, then draw the outline sprites excluding the mask.

\$\endgroup\$
4
  • 2
    \$\begingroup\$ Why not just draw the outline sprites first and draw the regular sprites over them? \$\endgroup\$
    – Chris Howe
    Commented Sep 14, 2010 at 19:32
  • \$\begingroup\$ One reason not to do this (or Chris's suggestion) is because it uses twice as much texture memory; another reason is that the artist workflow is crap because you need to update two files every time you change a sprite. \$\endgroup\$
    – user744
    Commented Sep 14, 2010 at 19:34
  • 2
    \$\begingroup\$ Well yeah, you ideally wouldn't have two actual sprite assets. If you're using alpha-testing for your sprites you could put the outline into the sprite and give it an alpha slightly higher than your transparent areas. Then by controlling the alpha test reference value you can control whether the outline is visible or not. All I was saying is that if you have 2 versions of the sprites (whether its 2 assets or 2 states of the same asset) you should draw the outline version first, and then the normal version. This way you don't require any fancy shaders, masks, or whatever. \$\endgroup\$
    – Chris Howe
    Commented Sep 14, 2010 at 19:56
  • 1
    \$\begingroup\$ Chris's idea has merit; in addition having the artist update 2 files (or even the same asset) can be avoided if you produce a tool to generate the alpha for the outline sprites that runs as part of your asset/content pipeline. Texture memory may or may not be a problem, but separate outline sprites should be highly DXT compressible; possibly 1/6th the size of the original texture in video memory and with no fidelity loss. \$\endgroup\$
    – jpaver
    Commented Sep 14, 2010 at 23:57
1
\$\begingroup\$

A few different solutions with different trade offs.

Easiest: Render the object using a flat color multiple times and jitter the position (offset left, up, down, right etc), this will create an outlines version of whatever you render on top of it, but has performance costs and won’t allow for fat borders without a lot of extra renders. A one or two pixel border may be okay with 4x.

Fastest: Preprocess the texture and have a copy that is already bordered, or is just the border, or is a flat greyscale 8-bit mask that you can colorize in a shader. This will likely be fast at the cost of memory.

Best: My opinion, but generating a Signed Distance Field (SDF) representation of your object would likely be the best solution. These textures can be much smaller than the source texture and still capture useful data. Essentially each pixel encodes how far away it is from the object used to generate it. With this data in hand, you can write all kinds of effects from glows to outlines. The border could change in size and color etc, and is still a relatively cheap shader and only one extra draw. Downside is tooling, and preprocessing.

\$\endgroup\$
0
\$\begingroup\$

I'm not sure of effiency, but the easiest way I can see would be to draw a larger version of the sprite in the colour you want selected first. Draw the sprite on top of that. You'll only see the edge of the first sprite, giving the effect of selection.

EDIT: However, as you can see from the comments, this isn't a good idea.

\$\endgroup\$
5
  • 1
    \$\begingroup\$ Just scaling up a sprite doesn't give a true outline \$\endgroup\$
    – Iain
    Commented Sep 14, 2010 at 17:58
  • 2
    \$\begingroup\$ I don't think it would, but it would be easy. \$\endgroup\$ Commented Sep 14, 2010 at 18:30
  • 2
    \$\begingroup\$ Plenty of things are easy but don't solve the problem. For sprites with any kind of interesting silhouette a scale-up will produce absolute garbage. \$\endgroup\$
    – user744
    Commented Sep 14, 2010 at 18:36
  • \$\begingroup\$ Scaling up will only look good for solid square or circular objects. If the shape is really wide, or tall, or has gaps it will look really bad. \$\endgroup\$ Commented Sep 14, 2010 at 18:40
  • 3
    \$\begingroup\$ Scaling up will give better results than doing nothing and would also be a useful step along the path to "perfection." While the graphical results won't be perfect, if this were for an internal tool it may be Good Enough. \$\endgroup\$ Commented Sep 14, 2010 at 21:58
0
\$\begingroup\$

I agree with scaling up the sprite. By Far the easiest route, and you can apply it to selecting ANY sprite without having to create additional sprites especially for this purpose.

  • i.e. spriteOutline.Scale = spriteOriginal.Scale * 0.1f; spriteOutline.Color = new Color(255, 0, 0, 255);
\$\endgroup\$
0
\$\begingroup\$

Replace the color of the original sprite with outline color (or even tint it if you like). Render this flat-shaded or tinted sprite four times with a 1 pixel offset: at x,y =(-1,-1), then (+1,-1), then (-1,+1) then (+1, +1). Repeat for all sprites that make up the object.

After that, render the original sprites in proper order on top at (0,0).

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .