One of my good friend told me that it was difficult to navigate around on a map of Nibbles, because he didn't know the layout. Naturally I knew it already, so I didn't notice it. (That was when I realized I must use outside testers a lot! Thanks, David!)
I came up with two features to handle this problem:
I came up with two features to handle this problem:
- Adding a zoom function, so you can see more ahead
- Adding 'edge guides' to dynamic elements (collectibles) to the scene
First, let me lay out my idea:
The whole concept was coming from FreeSpace – The Great War, and Skyrim. Let's take a look at FreeSpace first:
The whole concept was coming from FreeSpace – The Great War, and Skyrim. Let's take a look at FreeSpace first:
Can you see the green and white thingies on the top of the screen? They follow around the edge any important out-of-sight object (targeted ship, incoming missiles), marking their direction (you have to face your ship that way to center the object), distance (the number), and also your angle to the target (the wider the guide, the more you have to turn to face the target). Pretty handy.
Next, let's see a screenshot from Skyrim:
Can you see the markers on the top? No, you can't. Let me zoom in for you:
This is a navigation bar. It marks the direction of important locations for you, and works as a compass. (You can see the 'N' for North). The locations are marked with their own symbol, but my favorite thing was that the closer you were to a location, the bigger and less transparent the symbols were! (You can compare the the turkoise and the leftmost grey arrow-thingy.)
Getting ideas from both concepts, I came up with the following design:
- I need a component so an edge guide appears for the given game object
- The edge guide appears on the edge of the screen, but on the inside, so it is visible by the player
- The edge guide is positioned on the line connecting the object and the center of the main camera (to give the exact direction)
- The edge guide is invisible if the object itself is visible
- The edge has two components: and arrow like part, that is always poiniting to the target, and it is closer to the edge; and a symbol part, which is a visual guide of the nature of the object (typically the picture of the object itself)
- The symbol part does not rotate, it is always right-side-up
- The arrow part rotates, and always pointing to the object
- the symbol part is always behind the arrow part, closer to the center of the screen
- the further away the object is, the smaller and more transparent the edge guide is (both arrow and symbol), but only if it's possible to implement (SPOILER ALERT: it is)
Let's see how can we implement this.
First, let's notice, that the whole edge guide is rotating around the tip of the arrow. The symbol part however isn't rotated relative to the screen.
This gives us a structure, where the edge guide consists of two objects (an arrow and a symbol), where the symbol is the child of the arrow in the hierarchy.
So let's say the center of rotation is the red dot, and we rotate the edge guide:
This gives us a structure, where the edge guide consists of two objects (an arrow and a symbol), where the symbol is the child of the arrow in the hierarchy.
So let's say the center of rotation is the red dot, and we rotate the edge guide:
Almost good, but the symbol is not okay, it needs to be rotated to the opposite way:
Perfect. Now let's fire up the Unity editor, (and Visual Studio / VS Express if you prefer), and implement it.
Let's create a new C# script, called 'EdgeGuide'. We will attach the script to our objects of interests.
The first thing we need to do is test the object if it's visible form the main camera, or not. It would seem straightforward to use MonoBehaviour.OnBecameVisible() and MonoBehaviour.OnBecameInvisible(), but it wouldn't work. The reason for that - according to this tutorial - is that these methods will take every camera into account, including the scene editor camera. So your results will be different in the editor, and in the built game.
We have to develop our own solution, which will be a shamless steal from the previously mentioned tutorial.
The first thing we need to do is test the object if it's visible form the main camera, or not. It would seem straightforward to use MonoBehaviour.OnBecameVisible() and MonoBehaviour.OnBecameInvisible(), but it wouldn't work. The reason for that - according to this tutorial - is that these methods will take every camera into account, including the scene editor camera. So your results will be different in the editor, and in the built game.
We have to develop our own solution, which will be a shamless steal from the previously mentioned tutorial.
So every frame we test if the object is visible, hide the the edge guide if it does, or show if it doesn't. Here is our Update(...) method (called every frame):
void Update () {
Plane[] planes = GeometryUtility.CalculateFrustumPlanes(mainCamera);
if (!GeometryUtility.TestPlanesAABB(planes, thisRenderer.bounds)) {
//... show edge guide
} else {
//... hide edge guide
}
}
We will use 'planes' variable later, so we save it for later. These planes are the boundaries of the visible area of the camera. Everything inside is visible, outside is not.
Let's see what happens when we want to show the edge guide.
We need to find the edge point The edge point is where the edge huide should be centered. For this, we create a 'Ray' that origins from the camera position, and points to the object itself. Then we test all the planes for this ray, and save the smallest distance:
We need to find the edge point The edge point is where the edge huide should be centered. For this, we create a 'Ray' that origins from the camera position, and points to the object itself. Then we test all the planes for this ray, and save the smallest distance:
void Update () {
Plane[] planes = GeometryUtility.CalculateFrustumPlanes(mainCamera);
if (!GeometryUtility.TestPlanesAABB(planes, thisRenderer.bounds)) {
Vector3 rayOrigin = mainCamera.transform.position;
rayOrigin.z = transform.position.z;
Ray ray = new Ray(rayOrigin, transform.position - rayOrigin);
float smallestDistance = findEdgePoint(planes, ref ray);
} else {
// ... hide edge guide
}
}
private static float findEdgePoint(Plane[] borderingPlanes, ref Ray rayToTarget) {
float smallestDistance = -1.0f;
foreach (Plane plane in borderingPlanes) {
float rayDistance;
if (plane.Raycast(rayToTarget, out rayDistance)) {
if (smallestDistance < 0.0f || rayDistance < smallestDistance)
smallestDistance = rayDistance;
}
}
return smallestDistance;
}
Negative value is returned if none of the planess intersects the ray, but in our case this is impossible. We take the safe route in our code anyway.
Setting the scale of the edge guide is trivial, but setting the opacity is not. I needed some research in this matter, but in the end it turned out to be really easy. Here is the code:
private void setOpacity(Renderer renderer, float value) {
Color color = renderer.material.color;
color.a = value;
renderer.material.color = color;
}
These are the interesting parts of the edge guide. Oh, one more thing, I've set the center of my arrow sprite to the tip of the arrow in Unity's sprite editor, so it automatically rotates around the right way:
I've uploaded the whole EdgeGuide.cs here, for anyone interested. Study or use it for whatever you like!
Happy coding!







RSS Feed