Followed this official tutorial, my own version on GitHub, and you can see it in action here (WSAD to move, mouse click to shoot).
- Create a new 2D project, download and import the assets from https://www.assetstore.unity3d.com/en/#!/content/13866
- Save a scene in _Scenes folder and name it
Main
- File -> Build settings -> Web player -> Switch platform
- Player Settings… – define the size
- Reorder the layout the way you want but remember to save it by clicking the Layout button in the top right corner
- Drag
Vehicle_play
from the Assets->Model folder to the Hierarchy
- Rename to
player
, reset transform origin - Add component -> Physics -> Rigidbody and deselect Use Gravity
- Add component -> Physics -> Capsule collider, change direction to Z-Axis and adjust position of X, and check Is Trigger
- Click on the y gizmo of the camera and adjust again
- In this example, the Capsule collider would suffice, but we can add Mesh collider and this is very detailed but we can also drag a predefined Mesh collider from Models folder to Mesh collider -> Mesh variable which is less detailed but sufficient and better than the Capsule collider
- To see the mesh collider, turn off the Mesh renderer
- Add Prefabs -> VFX -> Engines ->
engines_player
to the player
object
- Reset camera transform origin
- Set transform Y to 10, Z to 5 (so that the ship starts at the bottom)
- Set rotation X to 90
- Set Projection to ortographic
- Set Size to 10
- Set Clear flags to Solid Color
- Background black
- Edit -> Render settings – property Ambient light set to Black (0,0,0,255) to effectively turn it off
- Lights
- Main light
- Hierarchy -> Create -> Directional light (based on rotation!, not position) and rename accordingly
- Reset origin position
- Set Rotation X = 20, Y = – 115
- Intensity = 0.75
- Fill light
- Duplicate the Main light and rename accordingly
- Rename to Fill light
- Reset its rotation only
- Set Rotation Y = 115
- Set Color to some shade of blue
- Rim light
- Duplicate the Fill light and rename accordingly
- Reset origin position
- Color White
- Rotation Y = 65, X = -15
- Intensity = 0.25
- Object organization
- Create new empty object (ctrl + shift + n)
- Rename to
Lights
- Rest transform origins
- Move the whole
Lights
object out of the way by setting the position Y = 100
- Background
- Hierarchy -> Create -> 3D object -> Quad; rename accordingly
- Reset transform origin
- Set rotation X = 90
- Remove the Mesh Collider component
- Drag Assets -> Textures ->
tile_nebula_green
to the Quad
object - Scale X = 15, Y = 30
- Shaders; difuse – mat, specular – glossy, unlit -> texture is the one we will use since this makes it independent of the lighting system (it displays the image originally as it looks)
- Position Y = -10 because it otherwise overlaps with 0,0,0 player object position
- Moving the player ship
- In the Assets folder, create folder Scripts
- Click on the player and Add Component -> Script -> name it
PlayerController
- Drag the created script file to the Scripts folder
- You have
Update
and FixedUpdate
we will use since we are dealing with physics.
#pragma strict
public var speed = 12;
function FixedUpdate () {
var moveHorizontal = Input.GetAxis("Horizontal") ;
var moveVertical = Input.GetAxis("Vertical");
rigidbody.velocity = Vector3(moveHorizontal, 0, moveVertical) * speed;
}
Input.GetAxis
returns a number from 0 to 1 that’s why you have to multiply - At this point, the player ship can go out of the play area, to fix this, use the
Mathf.Clamp
function:
#pragma strict
public class Boundary extends System.Object {
public var zMin : float;
public var zMax : float;
public var xMin : float;
public var xMax : float;
}
public var speed = 10;
public var boundary : Boundary;
function FixedUpdate () {
var moveHorizontal = Input.GetAxis("Horizontal") ;
var moveVertical = Input.GetAxis("Vertical");
rigidbody.velocity = Vector3(moveHorizontal, 0, moveVertical) * speed;
rigidbody.position = Vector3(
Mathf.Clamp(rigidbody.position.x, boundary.xMin, boundary.xMax),
0,
Mathf.Clamp(rigidbody.position.x, boundary.zMin, boundary.zMax)
);
}
extends System.Object
is used to Serialize the object to be able to be used within other object and visible in the inspector - To add tilting when moving:
#pragma strict
public class Boundary extends System.Object {
public var zMin : float;
public var zMax : float;
public var xMin : float;
public var xMax : float;
}
public var speed = 10;
public var boundary : Boundary;
public var tilt = 4;
function FixedUpdate () {
var moveHorizontal = Input.GetAxis("Horizontal") ;
var moveVertical = Input.GetAxis("Vertical");
rigidbody.velocity = Vector3(moveHorizontal, 0, moveVertical) * speed;
rigidbody.position = Vector3(
Mathf.Clamp(rigidbody.position.x, boundary.xMin, boundary.xMax),
0,
Mathf.Clamp(rigidbody.position.z, boundary.zMin, boundary.zMax)
);
rigidbody.rotation = Quaternion.Euler(0, 0, rigidbody.velocity.x * - tilt);
}
- Bullets
- Create a new empty object, rename to
Bolt
- Reset transform
- Create Quad, reset transform, rename VFX
- Add VFX as Child of the Bolt
- Rotate X = 90
- Drag Textures ->
fx_lazer_orange_dff
to it - Shader -> Particles -> Additive (you can also use Mobile version)
- Add component -> Physics -> Rigidbody, deselect Use gravity
- Remove the VFX Mesh collider
- Select Bolt and add Capsule colider
- Change radius and Direction to Z-axis
- Check the
Is Trigger
checkbox - Add script to
Bolt
object, name it Mover
- Make it a Prefab
- Delete it from Hierarchy
- Firing Bullets
- PlayerController.js:
#pragma strict
public class Boundary extends System.Object {
public var zMin : float;
public var zMax : float;
public var xMin : float;
public var xMax : float;
}
public var bullet : GameObject;
public var speed = 10;
public var boundary : Boundary;
public var tilt = 4;
public var waitPeriod = 0.05;
private var nextFire : float;
function FixedUpdate () {
var moveHorizontal = Input.GetAxis("Horizontal") ;
var moveVertical = Input.GetAxis("Vertical");
rigidbody.velocity = Vector3(moveHorizontal, 0, moveVertical) * speed;
rigidbody.position = Vector3(
Mathf.Clamp(rigidbody.position.x, boundary.xMin, boundary.xMax),
0,
Mathf.Clamp(rigidbody.position.z, boundary.zMin, boundary.zMax)
);
rigidbody.rotation = Quaternion.Euler(0, 0, rigidbody.velocity.x * - tilt);
}
function Update(){
if (Input.GetButton("Fire1") && Time.time > nextFire){
nextFire = Time.time + waitPeriod;
Instantiate(bullet, Vector3(rigidbody.position.x, 0, rigidbody.position.z),
Quaternion.identity);
}
}
- Drag the bullet prefab to the bullet variable on the player script
- Cleaning up
- Create Cube, rename Boundary
- Reset transform origin and place it around the whole game
- Turn off Mesh Renderer
- Is Trigger on Box collider
- Remove the renderer
- Add script
Destroy
:
function OnTriggerExit(other : Collider)
{
Destroy(other.gameObject);
}
- Enemies
- Create an empty game object –
Asteroids
- Reset transform origin, move a bit away along the Z axis
- Drag the
Asteroid
model from the Models folder and place it as a child of the Asteroids
empty game object – RTO (reset transform origin from now on) - Click on the EGO (empty game object) and add Physics ->
Rigidbody
- Deselect Use Gravity
- Add -> Physics -> Capsule collider
- Add Script:
public var tumble : float;
function Start () {
rigidbody.angularVelocity = Random.insideUnitSphere * tumble;
}
AngularVelocity
– how fast the object is rotating - Remove the
AngularDrag
which eventually slows the asteroid to a halt (drag – trenje for all you Croatian readers) - Add script
DestroyByContact
:
#pragma strict
function OnTriggerEnter (other : Collider) {
if (other.tag == "Boundary"){
return;
}
Destroy(other.gameObject);
Destroy(gameObject);
}
- Add Tag Boundary to
Boundary
- Explosions
- Adjust the
DestroyByContact
script:
#pragma strict
public var asteroidExplosion : GameObject;
public var playerExplosion : GameObject;
function OnTriggerEnter (other : Collider) {
if (other.tag == "Boundary"){
return;
}
Instantiate(asteroidExplosion, transform.position, transform.rotation);
if (other.tag == "Player"){
Instantiate(playerExplosion, other.transform.position, other.transform.rotation);
}
Destroy(other.gameObject);
Destroy(gameObject);
}
- Drag the needed explosions from the VFX folder
- Drag the Mover script to the Asteroid and set speed to
-5
- Drag Asteroid to a Prefab folder
- Delete it from Hierarchy
- Game Controller
- Create EGO and rename accordingly
- Set tag to
GameController
- Add script:
#pragma strict
public var enemy : GameObject;
public var spawnValues : Vector3;
function Start () {
SpawnWaves();
}
function SpawnWaves(){
var spawnPosition = Vector3(Random.Range
(-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
var spawnRotation = Quaternion.identity;
Instantiate(enemy, spawnPosition, spawnRotation);
}
- Outside set values for
spawnValues
to 6, 0, 18
- Add wave of enemies:
#pragma strict
public var enemy : GameObject;
public var spawnValues : Vector3;
public var enemyCount : int;
public var waitForEnemy : float;
public var waitForPlayer : float;
public var waveWait : float;
function Start () {
SpawnWaves();
}
function SpawnWaves(){
yield WaitForSeconds(waitForPlayer);
while (true){
for (var i=0; i<enemyCount; i++){
var spawnPosition = Vector3(Random.Range(-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
var spawnRotation = Quaternion.identity;
Instantiate(enemy, spawnPosition, spawnRotation);
yield WaitForSeconds(waitForEnemy);
}
}
yield WaitForSeconds(waveWait);
}
- Destroying explosion animations
- Add new script
DestroyByTime
#pragma strict
public var aliveTime : float;
function Start () {
Destroy(gameObject, aliveTime);
}
- Set this script on all of the explosion prefabs
- Set
AliveTime
to 2
- Audio
- Drag the Audio files to appropriate Explosion Prefabs
- For a
weapon_player
– drag it to a Player
object in Hierarchy and remove the Play on Awake - Update the
PlayerController
script to:
function Update(){
if (Input.GetButton("Fire1") && Time.time > nextFire){
nextFire = Time.time + waitPeriod;
Instantiate(bullet, Vector3(rigidbody.position.x, 0, rigidbody.position.z), Quaternion.identity);
audio.Play();
}
}
- Drag
music_background
to GameController
- Select Loop and Play on Awake
- Adjust the volumes:
- Player 0.5
- Game Controller 0.5
- Displaying score
- Create -> UI -> Text, rename to
ScoreText
- Set correct Rect Transform
- In the
GameController
script, add:
#pragma strict
public var enemy : GameObject;
public var spawnValues : Vector3;
public var enemyCount : int;
public var waitForEnemy : float;
public var waitForPlayer : float;
public var waveWait : float;
public var scoreText : UnityEngine.UI.Text;
private var score : int;
function Start () {
score = 0;
updateScore();
SpawnWaves();
}
function SpawnWaves(){
yield WaitForSeconds(waitForPlayer);
while (true){
for (var i=0; i<enemyCount; i++){
var spawnPosition = Vector3(Random.Range
(-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
var spawnRotation = Quaternion.identity;
Instantiate(enemy, spawnPosition, spawnRotation);
yield WaitForSeconds(waitForEnemy);
}
}
yield WaitForSeconds(waveWait);
}
function updateScore(){
scoreText.text = "Score: " + score;
}
function IncreaseCount(){
score ++;
updateScore();
}
- In the
DestroyByContact
:
#pragma strict
public var asteroidExplosion : GameObject;
public var playerExplosion : GameObject;
private var gameController : GameController;
function Start(){
var gameControllerObject : GameObject = GameObject.FindWithTag("GameController");
if (gameControllerObject != null){
gameController = gameControllerObject.GetComponent(GameController);
}
else{
Debug.Log("oops");
}
}
function OnTriggerEnter (other : Collider) {
if (other.tag == "Boundary"){
return;
}
Instantiate(asteroidExplosion, transform.position, transform.rotation);
if (other.tag == "Player"){
Instantiate(playerExplosion, other.transform.position, other.transform.rotation);
}
gameController.IncreaseCount();
Destroy(other.gameObject);
Destroy(gameObject);
}
- Drag the
ScoreText
object to form the reference to it in the GameController
.
- Ending the game
- Create an empty game object and rename it to
DisplayText
- Rest transform origin
- Add
ScoreText
to it (drag the Canvas
containing it) - Create new UI -> Text (rename to
RestartText
), and place it in the upper right corner - Create new UI -> Text (rename to
GameOverText
) - Update the script to:
#pragma strict
public var enemy : GameObject;
public var spawnValues : Vector3;
public var enemyCount : int;
public var waitForEnemy : float;
public var waitForPlayer : float;
public var waveWait : float;
public var scoreText : UnityEngine.UI.Text;
public var restartText : UnityEngine.UI.Text;
public var gameOverText : UnityEngine.UI.Text;
private var restart : boolean;
private var gameOver : boolean;
private var score : int;
function Start () {
restart = false;
gameOver = false;
restartText.text = "";
gameOverText.text = "";
score = 0;
updateScore();
SpawnWaves();
}
function Update(){
if (restart){
if (Input.GetKeyDown(KeyCode.R)){
Application.LoadLevel(Application.loadedLevel);
}
}
}
function SpawnWaves(){
yield WaitForSeconds(waitForPlayer);
while (true){
for (var i=0; i<enemyCount; i++){
var spawnPosition = Vector3(Random.Range
(-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
var spawnRotation = Quaternion.identity;
Instantiate(enemy, spawnPosition, spawnRotation);
yield WaitForSeconds(waitForEnemy);
}
if (gameOver){
restartText.text = "R to restart";
restart = true;
break;
}
}
yield WaitForSeconds(waveWait);
}
function updateScore(){
scoreText.text = "Score: " + score;
}
function IncreaseCount(){
score ++;
updateScore();
}
function GameOver(){
gameOver = true;
gameOverText.text = "Game over!";
}
- Drag the text object to form a reference in
GameController
object. - Update the
DestroyByContact
with:
if (other.tag == "Player"){
Instantiate(playerExplosion, other.transform.position,
other.transform.rotation);
gameController.GameOver();
}
- Deploy the game:
- File -> Build settings
- Web Player
- Build