Pages

Showing posts with label unity. Show all posts
Showing posts with label unity. Show all posts

Friday, April 24, 2015

Sprite palette swapping with shaders in Unity

As any game developer knows, asset reuse is your friend. Whenever the design calls for a new character, I often turn to our existing assets to see if I can create what we need more quickly and cheaply than contracting out another character sprite sheet.

One method for reusing assets is to swap out the colors. RPGs have used this trick for years, and it works just as well in Hellenica.


Every sprite in Hellenica uses a relatively limited color palette. To make a new variation, we just choose a few colors in the palette and assign new colors. Here's what our friendly pirate's color palette looks like:


What I ended up doing is creating a custom shader that takes a sprite and a small palette texture (like the one above) and swaps the colors on the fly.

-- Start of the technical bit! --

Creating the palette texture is pretty straightforward, but I thought it would be interesting to explain how the shader knows which color to use when it's drawing a pixel.

One way to do this is to embed the palette indices straight into the original sprite. But that presents a tricky problem! How can I change the data stored in the image without changing how the image looks? It turns out that it's doable if you don't change too much.

Before diving in to how this works, it's important that we're on the same page regarding digital color. For the purposes of this post, all you need to know is that every displayable color can be represented as a combination of three colors: red, green, and blue, or RGB for short. If you've used a paint program, you've probably seen this before:


If the color depth is sufficiently high, changing these RGB values by very small amounts doesn't change the final color much. In our case, we're using 32-bit color depth, which means each RGB value can range from 0 to 255.

This image contains 64 different colors, but to my eye it's all the same color. You can open it up in a paint program and poke around with the eyedropper to see what I mean.


Starting with a bright red color, RGB(252, 0, 0), I generated all possible color combinations that can be made by adding 0 - 3 to each of the color channels (red, green, and blue). That's 4 * 4 * 4 = 64 combinations.

This is the approach I'm using to embed the palette index into the original sprite. Since all of our character sprites in Hellenica use fewer than 64 colors, I can safely store the palette index inside the sprite pixels without modifying the end result visually.

Here's what it looks like when I modified our pirate friend:


The pirate on the left is the original source sprite, but the pirate on the right is the sprite that I modified. Can you tell the difference?

Here's a visual breakdown of the process for storing the indices:


We support at most 64 unique colors, so that means we only need 6 bits to represent the color index. First, I split this index into three separate 2-bit values. Then I hide these in the two least significant bits of the three color channels (red, green, and blue). As you saw before, since I'm only changing the two least significant bits, the visual impact is negligible.

I put together a little editor script to generate a color palette texture from a sprite and embed the indices using this process. Then I made a shader that can pick out the index and use it to look up the actual color in the palette texture.

Putting all of this together, creating a new variation now takes about as much time as it takes to swap out some colors in Paint.

-- End of the technical bit! --

Here are some variations on our pirate friend and the associated palette textures:


Aside from simpler color swapping, this technique is also amenable to some more imaginative uses!


I'm really happy with how things turned out, and I'm sure I'll come up with more uses as the project progresses. Let me know what you think in the comments!

Thursday, December 4, 2014

A little combat art clean-up

 As we finish up more and more of the combat art, some of our old techniques for controlling sprites from the Age of Temp Art have started to bother me. This afternoon I tackled one such issue dealing with the facing of our actor sprites.

When we first started out, we plugged in some representative fantasy character sprites as place holders for our characters so that we could prototype combat mechanics without waiting for an artist. (There are a lot of great resources on the web for this purpose. Check out OpenGameArt.org or the Spriters' Resource as a start.) Here are some examples:


While this was great for communicating character strengths or attack potential, it didn't communicate character facing! The side-on view in these pictures made it difficult to determine which of the 4 possible directions a character was facing in our 3-D world.

To address this at the time, I fudged the sprite's rotation in the world to hint at the direction it was facing. Here's an example using our latest sprites:


Unfortunately, rotating the sprite relative to the camera guarantees that some of the pixels will be blended together to make the final image that makes it to your screen. This means less visual fidelity for our otherwise crispy pixel art.

What I want instead is to have the character sprites always orient themselves in such a way that the plane of their sprite is parallel to the camera's view plane (effectively, the player's screen).

Unity's Transform class provides a handy function called LookAt that gets us most of the way there. This function rotates the object's transform so that its forward vector points down the LookAt vector you provide, with an optional up vector hint. In our case, we tell each sprite to LookAt the camera's position. Here's the modified version:


If you compare this image with the previous one, you should notice that the sprites actually appear slightly larger, especially those near the center of the screen. The sprites are already much clearer, but there's still a slight problem.

Look closely at the three bandits at the top of that last image. Do they look a little different to you?

A naive application of that LookAt function I mentioned doesn't quite give us what we want. This is because our camera uses an orthographic projection instead of a normal perspective projection. (I won't go into this here, see this for the gory details.) As a sprite approaches an edge of the screen, it rotates more and more to face the camera's position. Basically, sprites look skinnier as they approach the left or right side of the screen and shorter as they approach the top or bottom of the screen. Not good!

To fix this, we tell the sprite to LookAt different points on the camera's view plane such that the sprite is perfectly parallel to the screen. Here's the end result:


All three bandits are now the same size!

(Some of you will note that there are slight differences between the three sprites. Our combat rendering is not pixel perfect, so at times a sprite pixel will land on a screen pixel boundary. I may address this in a future post if I get time.)

I can then flip the sprite to give an indication of its facing, which won't affect the rotation of the sprite at all.


For those that are interested, here's a quick code snippet from the component responsible for orienting the sprites. Let me know if you have any questions or suggestions!

private void LateUpdate()
{
// Project the targets position onto the camera's xy-plane
//  to get a look-at vector that is orthogonal to
//  to the camera's xy-plane. This will ensure that each
//  sprite is rotated at a similar angle, regardless of
//  its position relative to the camera

// world position of the sprite
Vector3 target_position = m_cached_transform.position;
// world position of the camera
Vector3 camera_position = m_camera_transform.position;

// direction vector from the camera to the target
Vector3 camera_to_target = target_position - camera_position;

// project the camera_to_target vector onto the camera's up/down vectors
float target_up = Vector3.Dot(camera_to_target, m_camera_transform.up);
float target_right = Vector3.Dot(camera_to_target, m_camera_transform.right);

// determine the position on the camera's xy-plane such that
//  (target_position - new_position).normalized == camera.forward
//  the new look-at vector is orthogonal to the camera's xy-plane
Vector3 look_at_point = camera_position + target_up * m_camera_transform.up + target_right * m_camera_transform.right;

// finally rotate the target to face the newly computed look-at point
m_cached_transform.LookAt(look_at_point, m_camera_transform.up);
}

Wednesday, October 22, 2014

This changes everything

Every once in a while we implement a change in the project that makes me wonder how I ever enjoyed any of the previous versions. The last couple weeks I've been using some cycles to get our characters animating, and now that Diona is running and jumping around our little worlds, I just can't go back to our older builds. It's SO good to see our characters come to life on the screen!

Here's a quick video of some of her animations in place.


This is the animation graph I'm using to control her:


As far as animation graphs go, it's fairly simple. You can think of each rectangle as a single animation (idling in place, running, or shooting her bow), while the white arrows between the rectangles represent possible transitions between those animations.

So, for example, the arrows connecting diona_run to diona_hop allow Diona to immediately perform a hop while she is running. But, since there are no arrows between diona_run and diona_attack_bow, she has to stop moving and return to the diona_idle state before she can attack.

The nature of our turn-based combat means we can get away with a fairly simple set of transitions, as opposed to say, a real-time shooter game. Here's one part of an animation tree (a specialized kind of graph) from a recent Battlefield game:


It's not unusual for a large AAA studio to have a couple of programmers and a dedicated animation team spend all of their time refining the animation tree of the protagonist character. Maybe for our next game. ;)

I also put together some quick footage I took running around one of our test levels. Take a look!


Now that everything is in place, it's fairly simple to plug in the movement animations for our other characters, and that means our game is quickly coming to life!

Stay tuned for more!

Wednesday, April 9, 2014

An experiment in dealing with occlusion

One of the fun ("fun") problems you run into when developing an isometric tactics game is that of obscured visibility or occlusion.

With terrain that can take on arbitrary heights, it is very easy to build levels that will allow the player to place their characters right up next to one or more walls, blocking them from view. Any level with moderate complexity will likely afford this situation.


Here's an example from Final Fantasy Tactics. Our hero looks woefully outnumbered, but have no fear. Four of his allies are just on the other side of that building! Let's hope they've got an alchemist able to use Phoenix Downs amongst their ranks..

Information is the key resource in these kinds of games, so when a player can't see the board properly, they will make it very clear to you, the developer, and anyone else they can get a hold of.
  "I hate this annoying camera!"
  "Why can't I just rotate the camera around here?"
  "This game is terrible!"
And all the while the poor designer in charge of the game camera is just trying to line up some quality shots of the action. I feel you, buddy.

I think this tendency to victimize the camera is a learned one for most gamers. Many games give the player some level of control over the camera, so they are accustomed to a certain level of agency when it comes to problem-solving visibility issues. Unfortunately, in most games it is quite difficult to design a flexible camera that will thoroughly empower the player while always creating a compelling composition on screen. Kudos to you if you're making a first-person or top-down game!

So yes, we have implemented the standard Tactics rotate and tilt controls in Hellenica. But recently I've been experimenting with a different approach as well.

From within our editor, I can mark up sectors of the tile map to assign tiles to specific visual groups. Here's what that looks like on one of the levels I showed off last week:


At run-time, the game splits up the environment into separate meshes based on the visual group assignments. This allows me to selectively modify material properties on individual visual groups, properties such as alpha, for instance.

So, what's the point of all of this?

Basically, I can evaluate the camera's position and direction and decide what I want the player to be able to see at any given moment. This is usually a set of characters that should be in view and any highlighted, interactive tiles. If any of these things are obscured by tile geometry, I can pick out the guilty party, and make it temporarily transparent.

Here's a before and after shot:


This is just a first pass at this technique, but I'm pretty optimistic given the results. I have some changes coming down the pipe to clean up the tile geometry a bit which I think will really make this approach work well. Obviously, each additional visual group has a distinct performance cost. That is still something to explore in more depth, but I am not too concerned.

I'm really hoping that this solution will be a win for usability and player happiness as well as visual fidelity. I know I can count on the players to let me know if that's the case in the end.

Wednesday, February 5, 2014

A simple logger class for Unity projects


Today I thought I'd share a simple utility that you can drop into your own projects to help manage your debug (or release!) logs at run-time.

Unity's included Debug class has some great functionality for logging, raising warnings, and raising errors. Over the course of a project, however, regular use of these functions can lead to a very noisy log, which can make debugging tedious.

With that in mind, I set out to create a custom logger with the following requirements:
  • quick to drop into a new project
  • easy for a person to use for the first time
  • easily filterable at run-time
  • zero overhead outside of DEBUG builds

To address the first two constraints, I made the Logger class a static singleton that creates itself whenever any of the public logging functions are invoked. This also means that a new user simply needs to register their logging channel and start logging to start using the Logger in their project. Here's a simple example:

  Logger.register_channel(Log_Channel.Combat, Logger.E_Level.Verbose);

  Logger.log(Log_Channel.Combat, "Really super important combat event!");

By using Unity's Object.DontDestroyOnLoad() function on the Logger object, I guarantee that it will stick around for the lifetime of the game, even across level loads, unless you explicitly destroy it. This keeps lifetime management simple as well.

To be able to modify the filters at run-time, I set up a simple inspector (Logger_Inspector.cs) for the Logger class that generates drop-down lists for each channel that has been registered with the logger.  It looks a little something like this:


From here you can set the logging level of any channel directly allowing you to filter out exactly what you want, when you want it.

Finally, to make sure that none of our logging statements have any impact on our final release builds, every publicly accessible member function of the Logger class makes use of the System.Diagnostics.Conditional attribute.  Here's an example of what this looks like:

  [System.Diagnostics.Conditional("DEBUG")]
  public static void log(string channel, string message)
  {
    ...
  }

When the DEBUG flag is unset during compilation, none of the public Logger functions will be compiled and calls to these functions will become empty statements (or no-ops). This translates to zero memory and processing overhead in non-DEBUG builds, which makes programmers everywhere happy. Hats off to the .NET and Mono guys!

So that's about it. There's still a good bit of room for extension here: adding different log output streams, adding hierarchical filters, or anything else your heart may desire. You can find all the code and a containing .unityPackage file here in our public BitBucket repo.

I hope this code helps you out in your projects. Let us know what you think!