UI Rework

This week, I’m going to write about the two core components of the game UI. The navigation lights and the child status panel. The navigation lights were added because the feedback from the play testing showed that people were getting lost a lot and needed an easier way to navigate the map. The child status panel was added because the old one was long due for a revamp.

So, to start with, the child status panel. This is what the old UI looked like.

Old UI
Old UI

It was simple, but not very intuitive. Several people complained about it being not obvious what “Lost” stood for and a confusion as to what the icon of the player meant. The new UI replaces this with a child status panel. In it, each of the children and their current status (lost, found, being chased by a hunter or dead) are shown. Here’s a screenshot of the child status panel in action.

New UI
Child Status Panel

The blue light is for found, the black overlay is for lost, the X mark is for dead and the red light is for being chased. The panel was implemented with a bit of a long code. It contains eight images. One each for the children and their status. The script which manages the UI contains four GameObject references to the children and four to the status images (the images for the children remain the same, and hence were not accessed via script). Once this base framework was made, it was just a matter of a few conditional statements to get the right status to display on the right image in the UI. The code for this is not included in the post, as it requires components from some of the other scripts, and is probably not too hard to make.

Next up is the navigation light. The other programmer in the team suggested I use vectors to the children from the player, find the point where it crosses the screen, move the light source there and light it up when using the sonar. I came up with an alternative. I added eight lights around the player, in top, bottom, left, right and the 4 corners of the screen. They are positioned and controlled using a script.

void setPos()
{
navLightTop.transform.position = target.transform.position + new Vector3(0, 3, 0);
navLightBottom.transform.position = target.transform.position + new Vector3(0, -3, 0);
navLightLeft.transform.position = target.transform.position + new Vector3(-6.5f, 0, 0);
navLightRight.transform.position = target.transform.position + new Vector3(6.5f, 0, 0);

navLightTopLeft.transform.position = target.transform.position + new Vector3(-6.5f, 3, 0);
navLightTopRight.transform.position = target.transform.position + new Vector3(6.5f, 3, 0);
navLightBottomLeft.transform.position = target.transform.position + new Vector3(-6.5f, -3, 0);
navLightBottomRight.transform.position = target.transform.position + new Vector3(6.5f, -3, 0);
}
public void lightUp()
{
List<GameObject> children = new List<GameObject>(GameObject.FindGameObjectsWithTag("Child"));
List<GameObject> child = new List<GameObject>();
GameObject nest = GameObject.FindGameObjectWithTag("Nest");

while(children.Count>0)
{
float distance = Vector3.Distance(children[0].transform.position, target.transform.position);
GameObject furthest = children[0];

foreach (GameObject entity in children)
{
if ((Vector3.Distance(entity.transform.position, target.transform.position)) > distance)
{
furthest = entity;
}
}

child.Add(furthest);
children.Remove(furthest);
}

if (child.Count > 0)
{
foreach (GameObject entity in child)
{
if ((Vector3.Distance(entity.transform.position, target.transform.position)) > locatorDistance)
{
activateLight(entity);
}
}
}

if(nest!=null)
{
activateNestLight(nest);
}

}
void activateLight(GameObject child)
{
if (child.transform.position.x > navLightRight.transform.position.x)
{
if (child.transform.position.y > navLightTop.transform.position.y)
{
navLightTopRight.GetComponent<Light>().intensity = 1;
navLightTopRight.GetComponent<Light>().color = child.GetComponent<childMovement>().childColor;
}
else if (child.transform.position.y < navLightBottom.transform.position.y)
{
navLightBottomRight.GetComponent<Light>().intensity = 1;
navLightBottomRight.GetComponent<Light>().color = child.GetComponent<childMovement>().childColor;
}
else
{
navLightRight.GetComponent<Light>().intensity = 1;
navLightRight.GetComponent<Light>().color = child.GetComponent<childMovement>().childColor;
}
}
else if (child.transform.position.x < navLightLeft.transform.position.x) { if (child.transform.position.y > navLightTop.transform.position.y)
{
navLightTopLeft.GetComponent<Light>().intensity = 1;
navLightTopLeft.GetComponent<Light>().color = child.GetComponent<childMovement>().childColor;
}
else if (child.transform.position.y < navLightBottom.transform.position.y)
{
navLightBottomLeft.GetComponent<Light>().intensity = 1;
navLightBottomLeft.GetComponent<Light>().color = child.GetComponent<childMovement>().childColor;
}
else
{
navLightLeft.GetComponent<Light>().intensity = 1;
navLightLeft.GetComponent<Light>().color = child.GetComponent<childMovement>().childColor;
}
}
else
{
if (child.transform.position.y > navLightTop.transform.position.y)
{
navLightTop.GetComponent<Light>().intensity = 1;
navLightTop.GetComponent<Light>().color = child.GetComponent<childMovement>().childColor;
}
else if (child.transform.position.y < navLightBottom.transform.position.y)
{
navLightBottom.GetComponent<Light>().intensity = 1;
navLightBottom.GetComponent<Light>().color = child.GetComponent<childMovement>().childColor;
}
}
}

The script sets the light intensity of all eight light sources to zero at start, and re-positions them around the player in each update. Also, if the intensity is greater than zero, it is decreased each update. The script also contains a function (which is not called from this script directly) which lights up correct navigation lights with the correct color when called. The light up function is called from the script for the sonar, each time the sonar is used. It’s working is as follows. The whole game area is divided into 9 parts as illustrated (area 9 is the camera view).

split
Screen Split-up

If a child is in the 9th section, no lights are lit up. But if it is in any other section, the light on the corresponding part of the screen is lit up. If there are two children in the same section, then the light will show the color of the closer one. An extra functionality was added later on, to show a light towards the nest (where you should get to, to finish the game) once all children are found and it is active. Anyway, here’s a screenshot of the navigation lights in action.

nav lights
Navigation Lights

Of sprite sheets and animations

This week, I’m going to write about my work with sprite sheets and animations. Even though this is more in the line of an artist’s job, I decided to do it because in the end, it turned out to be a numbers game. Plus, we wanted to implement multiple animations on the same sprite, so it required coding.

So why was it a ‘numbers game’. And why the multiple animations. We had three animated objects in the game. The player itself, the hunter enemy (see my post about hunter targeting for details) and a navigation flower. The first trouble we ran into was that Unity does not support animated gifs. Only sprite sheets. So, the gifs made by the artists were unusable. Directly at least. Unity, requires sprite sheets, multiple frames of the gif on the same file. Such as this one.


It is then possible to slice this sheet into multiple individual images in Unity, and turn them into an animation. Here’s when the problems started creeping up. I added a sprite sheet on the hunter and flew there, only to die mysteriously. On close examination, I discovered that I was attacked by a very tiny hunter! The reason? Sprite sheets and idle sprites were of different sizes and the collider was adjusted for the larger idle sprite (and not re-adjusted for the smaller animated one). And Unity has no apparent (if anyone knows, please let me know) ways to re-size the sprites or the sprite sheets. So, easy fix. Change the idle sprite to one of the frames from the animation sprite sheet and adjust the size of both the hunter object and the collider accordingly. Then I added the second animation on the hunter, an aggro animation. In the hunter animator, I added a trigger which changes the animation depending on the value of a boolean variable. The boolean is then changed in the hunter’s code to true or false depending on it’s state. So, I have a hunter with two animations in it. I flew in to test it out, and notice that the hunter shrinks when it starts chasing! Reason? Sprite sheets of the two animations have different pixel counts. This is why it’s a numbers game. So a note to the artists, when making sprites or sprite sheets, BE CONSISTENT. It wasn’t too much work from the animated gifs though. I scaled the image to the same width and a height equal to the height of an individual frame multiplied by the number of frames (so I can fit them all in a single column). Then a small bit of re-arranging in GIMP (GNU Image Manipulation Program, a free software with similar functionality as Photoshop) and I have a sprite sheet! Adding the more precisely sized sheets, I got the hunter to not shrink when it starts chasing (which might be an interesting idea in some cases, but not so in this context).

The player sprite sheet was a lot simpler. There was only one animation. But, due to the weird way it was coded, it has to be facing a particular direction in the sprite for it to fly properly. Also, the center of the sprite has to be properly aligned with the pivot of the moth (did not know how to edit pivots in Unity at that point) for it to rotate correctly. Another two easy fixes. Rotate the frames and add padding on each side to align the pivot and the image’s center.

Finally, the flower. Now this is where things get a bit tricky. I wanted to implement it so that the flower animation only starts playing when the player gets close to it. And, I wanted the animation to not loop. The second one was easy, just a matter of turning the “Loop Time” option off. For the first one, I did a bit of code work. I disabled the animator and added this code segment.

if (Vector3.Distance(target.transform.position, transform.position) < distance)
{
anim.enabled = true;
particles.SetActive(true);
}

So when the player flies close to the flower, both the animation and the particles are enabled. The flower blooms and remains that way (because the loop option is off).

For some polishing, I used the colorize option in GIMP to get multi-colored children and flower sprite sheets. Here’s a sample of the flowers.

flowers
Flowers