Like a lot of people I grew up with video games. But these were quit different from what we have today. Space invaders, Lunar lander, Missile Command and Asteroids look like cave drawings when compared to what is available today. I have experimented with tools like LightWave and Maya but their costs are prohibitive and they are not really suited for amateur game developers. Unity 3D, on the other hand, is ideally suited for those just getting started with game development. In addition, it can easily support more complex professional games. Their recent announcement for free support for mobile applications means its time for me to make the leap.
A modern game typically requires a lot of people, mainly artists, to create scenes and characters. I can use tools such as Blender but I am not nearly proficient enough to build the images as well as create the game. I need a game where I can leverage existing art work and just focus on the mechanics of the game and learning Unity.
What I need is a 2D side scrolling space game. I decided on trying to replicate the Lunar Lander game.
It won’t be an exact match but instead more updated and something that fits with the Unity model. Look around in the Apple and Google app stores and you can find a number of these games. Some are 2D while others are 3D and much more realistic. I am not trying to be the next “Flappy Bird” so I don’t expect to compete with other games. Its all about the learning.
Unity 3D
A lot can bee done with Unity right out of the box. Anything that requires reacting to a user(player) in going to bring up the need to add custom coding. There are two choices for doing this, C# and Javascript. A lot of the tutorials and examples are in Javascript so I’ll stick with this.
The Game
The point of game is to land the ship on the surface before you run out fuel and crash. In the earlier games the ship would rotate as well as translate. Correcting the rotation makes the game much more difficult to play. For this version I’ll stick with simple translation left, right, up and down. Of course there needs to be a surface to land on. A simple flat surface is boring. Adding some sort of obstacles will make it a bit more challenging.
Things to consider:
- The ship
- Obstacles
- Landing
- Movement
- Gravity
- Fuel
- Crashing
- Player controls
- Scoring
- Sound
The Ship
Unity can import models from many tools such as Blender and Max 3D. For a mobile game the model can not be too complex. The more detailed the poorer the game performance will be. I found a reasonably sized lunar lander model from NASA that is free to use.
Obstacles
In the original game the surface changed from flat to mountains. I decided to add rocks to a flat surface. In order to make things a bit more complex I added the rocks at random locations and sizes.
Landing
The rocks provide obstacles to avoid but there needs to be a ‘safe’ landing place. These are marked ‘green’ so the player can be seen. Since the rocks are randomly placed the landing places need to be adjusted as well. The process is to place a landing spot and then place the rocks. The code has to make sure the rocks are not covering the landing place and that there is enough room for the lander.
Startup code to build the scene:
Declare the rocks and landing pads
var rocks: Transform[]; var landingPads: Transform[];
Find the game object tagged GUI so that we can determine the player’s level. The landing pads are adjusted differently once the player is beyond level one.
Create the landing pads by varying the “x” value.
GUI = GameObject.FindGameObjectWithTag("GUI").GetComponent(InGameGUI); if(GUI.playerLevel > 1) startx = (GUI.playerLevel * 1.1)* 4895.0; else startx = 4895.0; currentXoffset =startx + 1200*Random.Range(3,10); for(i =1; i < numberOfLandingPads; i++) { lp = Instantiate(landingPads[0], Vector3 (currentXoffset,-69.0, 514.6719), Quaternion.identity); lp.transform.localScale.x = 160; lp.transform.localScale.y = 1.1; lp.transform.localScale.z = 160; lp_locations[lp_locations_index,0] = currentXoffset; lp_locations[lp_locations_index,1] = (lp.transform.localScale.x*5); currentXoffset += (lp.GetComponent.().bounds.size.x*Random.Range(3,6)); lp_locations_index++; }
Create a 1000 rocks. Each rock is generated in a random x location. The height of each rock is also random( y direction). The game is 2D but I am using Unity in 3D mode. For creating the rocks I am creating a 3D field. At some point I may change the game to be more 3D. Each rock is check to make sure that it doesn’t overlap with a landing pad. I didn’t want the code to get stuck in the overlap process so after 10 tries I give up.
for (var x = 0; x < 1000; x++) { var breakOut=0; do { var index = Random.Range(0,4); var locX = Random.Range(-50000,50000); var locZ = Random.Range(-3000,2000); var scaleX = 200;//Random.Range(Random.Range(5,50),Random.Range(150,200)); var scaleY = Random.Range(Random.Range(5,50),Random.Range(70,500)); if(GUI.playerLevel >2) { scaleY = Random.Range(Random.Range(5,50),Random.Range(70,GUI.playerLevel*500)); } var scaleZ = 400; //Random.Range(Random.Range(5,50),Random.Range(50,100)); // Debug.Log( " Creating rocks locX "+locX + " locZ " +locZ +" scaleX " +scaleX+ " scaleY " +scaleY); breakOut++; if(breakOut > 10) { // Debug.Log("==============breakOut++++++++++++"); return; } } while (checkOverlap(locX) ); rock = Instantiate(rocks[index], Vector3 (locX, 0, locZ), Quaternion.identity); rock.transform.localScale.x = scaleX; rock.transform.localScale.y = scaleY; rock.transform.localScale.z = scaleZ; rock.tag = "rock"; }
A lot of values are hardcoded simply for expedience. Good software practice would be to use variables or contestants
Movement
Since the game has more than one or two controls it requires the addition of buttons. Keyboard controls are not an options and multi-touch is complicated. I need to control the main engine(up), left and right thrusters and a pause button.
A ParticleEmitter is used to indicate engine or thruster action.
var engineThruster : ParticleEmitter; var LeftThruster : ParticleEmitter; var RightThruster : ParticleEmitter;
An audio file is played when the engine is on. While the engine button is pressed the emitter is set too true
// if the Emitter is not running then fire it // and play the sound // then move ship up if(engineThruster.emit == false) { GetComponent.().PlayOneShot(engineSound); engineThruster.emit = true; } moveShip_up();
function moveShip_up(){ var dir:Vector3; // if we are out of fuel then do not move the ship if(fuelMeterCurentValue == 0) return; // update the fuel status updateFuel(); // get the local pos pos = Camera.main.WorldToScreenPoint(transform.position); // if the ship is higher than the screen // set the velocity to 0 if( pos.y >= Screen.height) { transform.GetComponent.().velocity.y=0.0; } else { // yMovement is either 1 or 0 depending on the button pushed // it limits movement to X or Y movement only // adjust the upward velocity the further away from the ground. // the value '200' should be replaced with ratio of the screen // height if( (pos.y < ceiling) && (pos.y > Screen.height-200)) { dir = Vector3(0,yMovement*upwardThrust/2.0,0); } else { if( (pos.y < Screen.height-200) && (pos.y > Screen.height/2)) { dir = Vector3(0,yMovement*upwardThrust/1.5,0); } else { dir = Vector3(0,yMovement*upwardThrust,0); } } // add force to the ship GetComponent.().AddForce(dir); } }
Gravity
The assumption is that the planet has gravity. I have left the gravity setting standard as Unity sets it.
Fuel
Fuel usage is adjusted when ever the engine is running. In the FixedUpdate() Unity function the fuel is adjusted:
fuelMeterCurentValue -=fuelLossRate*Time.deltaTime;
The term Time.deltaTime increments the fuel usage according to the FixedUpdate() rate. It is standard in Unity to do this when doing something in the fixed update call.
Crashing
There are two ways to fail a landing. One is to land on rocks. The other is to land too fast. A vertical velocity indicator turns red when the ship is landing too fast. When the ship touches the landing pad the velocity is checked. The function OnCollisionEnter() is called when two objects touch. In this case it will be the ship and either a landing pad or a rock. setting Time.timeScale to zero stops the game play. the GUI.guiMode is set to either win or lose. This will cause the correct screen to be displayed and the score to be adjusted.
if( theCollision.gameObject.tag == "landingpad" ) { if( (theCollision.relativeVelocity.magnitude > 50.0) ) { Explode(); GUI.guiMode ="Lose"; Time.timeScale = 0; } else { Time.timeScale = 0; GUI.guiMode ="Win"; } }
PlayerControls
Since this is a mobile game there needs to be buttons for the player. A single touch would work if it was to run the lander engine. Left and right translations are harder. Touch to the left of the lander could go left and the same for right. Since the lander moves it could move under the touch point and cause the movement to change. Buttons just seem easier.
Unfortunately Unity’s UI is not straight forward.The placement and operation of a button is pretty simple. Buttons are GUITexture components. Getting the position and sizing correct for different size devices is a challenge. There is talk that future versions of Unity will have better UI tools.
In the FixedUpDate function I test each button.
for (var touch : Touch in Input.touches) { if (engineButton.HitTest (touch.position)) { // handle engine event } if (leftThrusterButton.HitTest (touch.position)) { // handle left thruster event } . . . . }
Scoring
Scoring is pretty straight forward. Land successfully and you get a point and proceed to the next level. Crash and you have to repeat the level. At each level the landing spots get harder to find. As the level increases I need to increase the fuel(or lower the rate at which its is used).
Sound
Sound is handled from an AudioSource component.
GetComponent.().PlayOneShot(engineSound);
This plays the sound once. As long as the button is held down the sound will be played over and over. Playing the sound in a loop is possible for something like background music. For sounds like the engine or thrusters I need short burst of sound.
Screen Shots
The ship approaching a landing pad. The vertical velocity is in white and positive. This indicates that the ship is moving up at rate within the range for landing.
Since the landing pads are randomly placed I found it hard to locate them and no run out of fuel. I added a overhead view in the upper right corner to guide the player towards a landing pad.
The left corner shows the fuel and velocity levels.
The ship over the rocks. The vertical velocity is in red and negative. This indicates that the ship is moving down at rate too large to land.
Goggle Play
I decided to put the game on Goggle Play just to see how this process works.
Update: I see one person has complained that at a high level you just crash into the rocks. It could be that this is a fuel issue. The landing pads are too far away for the fuel usage rate.
https://play.google.com/store/apps/details?id=com.punkinsoft&hl=en
Once I get the iOS version to work I’ll put it on the Apple Store as well.