Did you ever want to control the universe? In this tutorial, I’m going to show you how you can make your desk the center of the universe. You are going to set up a solar system and place the sun, earth and moon in it. Also, we are going to let them circle around each other like they do in reality.
Things you need
- Unity 3D
- A new Unity3D project
- ARToolKit Unity3D plugin
- These NFT Marker
- Camera connected to your computer
Set up the basics
You should have opened a new project with a new scene and already imported the ARUnity package. If you need support doing this please see Getting started with ARToolKit for Unity.
We now set up an AR scene as in the Getting started with ARToolKit for Unity tutorial with the difference that this scene will contain three ARMarkers and three ARTrackedObjects.
- Create a new GameObject called ARToolKit
- Drag the following scripts onto it
- ARController
- 3x ARMarker
- The Type of all ARMarker is NFT
- Change the Marker Tag of the first ARMarker script to sun and set the NFT dataset name to Sun_poster_150ppi
- Change the Marker Tag of the second ARMarker script to earth and set the NFT dataset name to Earth_150ppi
- Change the Marker Tag of the third ARMarker script to moon and set the NFT dataset name to Moon_South_Pole_150ppi
- Create another GameObject called ARSceneRoot and drag AROrigin script to it
- Set Layer to AR foreground
- As child to Scene root create three GameObjects and rename them to Sun, Earth, Moon
- Drag ARTrackedObject script to those three objects
- Inside the ARTrackedObject script in the Sun, Earth and Moon GameObject set the Marker tag to sun, earth, and moon accordingly. (This is how you establish the link between the ARMarker and the GameOjects that will be placed upon this marker)
- Verify that Got marker is true for all three
- As child to ARSceneRoot create a Camera object
- Set culling mask to AR foreground
- Drag ARCamera to this camera object
If you need help during these steps please consult Getting started with ARToolKit for Unity as the steps above are all described there in detail.
Add the planets
Now we are going to add and style the actual planets.
For the planets add one sphere as a child to the Sun, Earth and Moon GameObject each.
- Add a new material to the sun sphere and make it a yellow color.
- Select Project/Favorites/All Materials
- Right click in the middle area Create/Material
- Select a yellowish color for Albedo and Emission
- For the earth, I got the material from the Unity Store: https://www.assetstore.unity3d.com/en/#!/content/23399 (it is free)
- To style the moon I took the earth material from the Unity Store and replaced the texture with this MoonMap
There are better models to make the solar system more realistic in the Unity Store. But they cost some money. Decide for yourself if you want to use them:
- https://www.assetstore.unity3d.com/en/#!/content/13783
- https://www.assetstore.unity3d.com/en/#!/content/4436
- https://www.assetstore.unity3d.com/en/#!/content/56766
The sun also needs to emit some light so let us do that.
- Add a light as child object of the sun sphere
- Make the type a point light
- Place the light in the center of the sphere
- Also, add a Halo to the sphere:
- Select SunSphere and check the Halo checkbox
- Enter an orange/yellowish color
- Set the size of the halo, I used 0.32
Setting the size brings me to the fact that we need to change the default sizes of our spheres: (x/y/z)
- Sun Scale: 0.3/0.3/0.3
- Earth Scale: 0.12/0.1085/0.12
- Moon Scale: 0.03/0.03/0.03
Also, we need to set the position of the planets. If we leave them to default they will be placed with their center in the middle of the marker and that is not what we want.
- Sun Position: 0/0/ -0.175
- Earth Position: 0/0/ -0.175
- Moon Position: 0/0/ -0.175
Get the planets into the correct distance
Now we are going to measure the distance between the planets (well between the markers that are linked to the planets). This is done in a similar way as in the stretchy line tutorial.
1. Create a new script called distanceLine
2. Override the functions: Start, OnMarkerFound, OnMarkerLost, OnMarkerTracked
3. In Start() we look-up our line object and store a reference to it.
4. In OnMarkerFound() and OnMarkerLost() we lookup if the found/lost marker is the one we are looking for and store/release references to this marker.
void Start () { line = GameObject.FindGameObjectWithTag (lineTag).GetComponent<LineRenderer>(); line.SetWidth(0.0f, 0.0f); } void OnMarkerFound(ARMarker marker) { if (marker.Tag.Equals(marker1Tag)) { marker1 = marker; } else { marker2 = marker; } } void OnMarkerLost(ARMarker marker){ if (marker.Tag.Equals(marker1Tag)) { marker1 = null; } else { marker2 = null; } }
5. To let the script know which markers we are looking for we define two static string members which Unity will show as text entry fields for this function. We will use this text fields and enter earth, sun, moon tags accordingly later.
public string marker1Tag; public string marker2Tag;
6. In OnMarkerTracked() we call a private function to measure the distance and draw a line between the two markers. We use the line as an indicator if the markers have the correct distance between each other.
void OnMarkerTracked(ARMarker marker){ drawLine (marker); }
7. drawLine() implementation:
- First, make a sanity check if we have two markers and if the two markers are the ones we are looking for
- Then get the position of the first marker and use it as the start position. Then get the position of the second marker and use as the target position
- To draw the line, we use etPosition(0,startPosition) and setPosition(1,targetPosition). 0 marks the start and 1 the target coordinate of the line.
- To measure the distance, we use the Unity function Vector.Distance()
private void drawLine(ARMarker marker){ //Make sure that the tracked marker is one of the markers we are interested in and that we have the other one already loaded if (marker1 != null && marker2 != null && (marker.Tag.Equals (marker1Tag) || marker.Tag.Equals (marker2Tag))) { Vector3 startPosition = marker1.TrackedObject.transform.position; line.SetPosition (0, startPosition); Vector3 targetPosition = marker2.TrackedObject.transform.position; line.SetPosition (1, targetPosition); line.SetWidth (0.01f, 0.01f); float distance = Vector3.Distance (targetPosition, startPosition); line.SetWidth (0.01f, 0.01f); } }
8. We also want the line to disappear if only one of the two markers is visible and reappear if all two are visible.
- Add line.setWidth(0f,0f) as last call in the OnMarkerLost() function
- Add line.setWidth(0.01f,0.01f) at the end of drawLine()
Start the rotation when the distance is right
We are going to set-up another component that keeps track of the distance changes and kicks off the rotation of the planets. Let’s do this by creating our own component as a C# script.
1. Create another component and call it rotateAroundObject
2. Let us call rotateAroundObject every time we notice that the distance changes:
1. Add an event broadcast to the distanceLine component at the end of drawLine() function
private void drawLine(ARMarker marker){ float distance = Vector3.Distance (targetPosition, startPosition); eventReceiver.BroadcastMessage ("OnLineChange", distance, SendMessageOptions.DontRequireReceiver);
2. Implement the event broadcast receiver method in the rotateAroundObject component
void OnLineChange(float dist){}
3. Additionally, we pass the measured distance to this event
3. To know which object to circle around define a public member that is used to enter the tag of the GameObject that we want to circle around. While you are at this, define two more members to store the valid min and max distance.
public string rotationObject; public float validDistanceMin; public float validDistanceMax;
4. Alright, in OnLineChange() we now look up the object to circle around, check if our distance is correct and call startRotation()
void OnLineChange(float dist){ this.distance = dist; rotateObject = GameObject.FindWithTag(rotationObject); if (rotateObject != null && distance > validDistanceMin && distance < validDistanceMax) { startRotation(); }
We can optimize this a bit if we do not look up the rotateObject every time the line changes but in the OnStart() function. So let’s do that.
- Override the OnStart() function
- Copy the lookup of the rotateObject over to to the OnStart function
- Also, we implement that the position of the planet that circles is moved onto <<Is this needed?>>
Now let’s implement startRotation()
This is only three lines of code:
- Call the Unity3D function transform.rotateAround()
- Pass to it the position of the object that we are rotating around
- The rotation mask. This defines around which axis the rotation is done
- And the rotation speed
At this state, the planet is already circling around the other planet. To make it a bit more realistic you can also add the feature to circle in an elliptic way. You can see how that is done in code snippet.
private void startRotation(){ transform.RotateAround (rotateObject.transform.position, rotationMask, rotationSpeed * Time.deltaTime); Vector3 desiredPosition = (transform.position - rotateObject.transform.position).normalized * radius + rotateObject.transform.position; transform.position = Vector3.MoveTowards(transform.position, desiredPosition, Time.deltaTime * 1); }
Wire everything together
Now we are going to put everything in place and make the solar system work:
- Add two Unity3D Line Renderer as children of ARSceneRoot and name them DistanceLine and DistanceLineMoonEarth:
- Create an empty GameObject
- Add the unity component Line Renderer to it
- Tag the DistanceLine with the name distanceLine and add the Distance Line script to it.
- Tag the DistanceLineMoonEarth with the name earthMoonDistance and add the Distance Line script to it.
- Configure the DistanceLine script:
- Event Receiver: EarthSphere
- Line Tag: distanceLine
- Marker 1 Tag: sun
- Marker 2 Tag: earth
- Configure the DistanceLineMoonEarth script:
- Event Receiver: MoonSphere
- Line Tag: earthMoonDistance
- Marker 1 Tag: earth
- Marker 2 Tag: moon
- Add the RotateAroundObject component to the EarthSphere and MoonSphere
- Configure the RotateAroundObject at EarthSphere:
- Rotation Speed: 7
- Rotation Object: sun
- Valid Distance Min: 0.3
- Valid Distance Max: 0.4
You can play around with this values. Just the Rotation Object needs to be the name of the object we are rotating around.
- Configure the RotateAroundObject at MoonSphere:
- Rotation Speed: 100
- Rotation Object: earth
- Valid Distance Min: 0.1
- Valid Distance Max: 0.13′
That’s it now you have everything in place and the solar system is ready to run. Let me know if you encounter any issues, have any question or general feedback.
Thanks for reading I hope to see you around soon.
Download to the Solar system unity export. (This also includes the function to fade out the lines once the correct distance is reached, which I didn’t cover in this tutorial.)
You sir are awesome :))
Hi 🙂 Great tutorial 🙂 But I have a problem with tracking nft objects. From some reason it track and generate only one nft marker at the time.. it can not track and generate objects of multiple nft markers at the same time. Any idea where can be a problem?
Thanks
Matus
[…] have created this amazing SolarSystem example using ARToolKit5. It is a great way to show how to bring Augmented Reality to life. With […]
Can I get code in C# file?
Sorry, don’t understand your question.