Pages

Friday, December 19, 2014

Hellenica Characters: Anaxagoras

It's been a while since we revealed any new characters here on the devlog, and with Christmas coming up soon, I thought it only appropriate to introduce you to our very own Santa look-alike, Anaxagoras!

Anaxagoras is an inventor who has been at the forefront of the Greek industrial revolution for years. Our main goals in portraying him were to communicate his friendly, helpful nature and to echo the visual aesthetic that we had developed for Nephele, our other character strongly related to steam technology. Our basic approach included:
  • create a friendly facial expression and a non-threatening physique
  • add lots of tools and tool pockets
  • use brass gears and adornments
  • pick colors that match Nephele's outfit to further establish the image of the ancient Greek steam engineer


This first sketch was already quite close, but there were a few small details we wanted to revise. The pocket on the stomach felt impractical and probably uncomfortable. We also felt the device on his wrist was a bit too modern for Hellenica's time frame.


This new version looked great, so we moved on to coloring.

We wanted Anaxagoras to look right at home in the workshop, which meant using browns and oranges that blended in well with the metallic tones found there. We had already achieved a similar look with Nephele, so her palette served as a great starting point.


After that, it was just up to our partners over at YDY-CG to finish up the coloring of the sketch and add in the final details (wristband colors and pouch ornaments). As always, they did a great job executing on our direction, and we're super happy with how he turned out.

Happy holidays everyone!

Friday, December 12, 2014

Saving Hellenica

Saving games is one of the many aspects of game development that seems like it should be much easier than it is. For most games we've worked on, even though the mechanics of storing data are usually simple, the evolving state of the game means you're chasing a shifting goal and constantly at risk of breaking things. We've certainly had this experience with Hellenica as we progressed from just a linear prototype to a multifaceted narrative web. But Hellenica's unique story structure also presents a whole new set of challenges.

To start off, we're not locking the player in a single path. If after exploring the consequences of one choice for leaving a social hub she decides she's more interested in what would have happened on another path, she is free to rewind the game (via the narrative map) to the previous hub and make the other choice. However, a player's story doesn't depend just on the social hub she's in. There's a plethora of choices, both overt and subtle, which will influence the way the story is unfolding. For example, instead of just loading the player into sh4_athens, we also need to remember she came from Sparta and has talked to Alcibiades but not Xenophon and has eavesdropped on a mysterious pirate delivery. To keep track of all this, we create a mini save game for every hub the player visits. So instead of one save game, we have like 30.

A separate save game challenge came from aiming to release Hellenica on tablet platforms, where it's expected that the player can be a jerk and quit your game at ANY TIME without losing her progress. So we need to maintain a sort of perpetual saving process that keeps track of the player's progress throughout a level. And because loading it takes the player halfway through a level (sometimes even halfway through a conversation), this running save file needed to have its own share of custom data beyond the mini-save games that are tied to social hubs.

Going one step further, this is all built on a foundation of profiles, allowing the user to share Hellenica with a friend and not have her progress mucking up your own save map.

So at present a "savegame" in Hellenica consists of.
A profile, containing....
Up to 40 social-hub-specific saves, each containing a story path taken to get there and a plethora of other story variables.
Separate save data related to which combat paths are unlocked.
A running save file, which contains all the minutia of the player's progress through the current level (+extra details if it’s a combat level).
Achievements accrued and various playthrough statistics.
Party customization details concerning which abilities they player's unlocked and which abilities you’re actively using.

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);
}