开始时间:2016年7月31日21:37:37
参考链接:http://unity3d.com/learn/tutorials/projects/space-shooter-tutorial
The player GameObject
add Rigidbody Component
To detect collision, the physics engine, through the rigid body, needs to know the volume of our objects.We need to know how much space these objects take up in our game to calculate the collisions. We give this information to the rigid body by using a cage that we wrap around our game objects. This cage defines the volume of that object. The cage is called a Collider.
add CapsuleCollider Component
remove it
add MeshCollider Component, select “Convex”
The Mesh Collider component will not participate properly in physics
collisions and will not be visible in the scene view unless we select
“Convex” on the Mesh Collider Component.
There are some limitations when using the Mesh Collider. Non-convex Mesh Colliders are only supported on GameObjects without a rigidbody. If you want to use a Mesh Collider on a rigidbody, it needs to be marked as Convex.
select “Is Trigger”
We simply need our collision to trigger an action.
打卡:2016年8月1日21:25:18
Camera And Lighting
Set Camera
- Our game needs to feel like an upright arcade game. These did not have any perspective. So we will choose orthographic as our projection mode.
- set Size to 10
- We want the player in the origin, so we move the camera to let player at the bottom at the start.
by default in Unity 5, a Skybox is included in thescene. This skybox will influence the ambient light affecting the shipand will be used as the background image by the Main Camera.
Set Light
main light, a fill light, a rim light
We can organize our hierarchy by using empty Game Objects.
And It is important to reset the transform of this empty game object.
打卡:2016年8月2日14:08:19(昨天看的有点少)
Adding a background
add a quad object, rotate, remove mesh collider component.
What is a matte paint?
Glossy and flat (or matte) are typical extreme levels of glossiness of a finish. Glossy**paints** are shiny and reflect most light in the specular (mirror-like) direction, while on**flat paints** most of the light diffuses in a range of angles. The gloss level of **paint**can also affect its apparent colour.
- Mesh Filter holds the mesh data: Quad.
- The Mesh Renderer renders that mesh using the materials in the Mesh Renderer’s Materials Array.
- The renderer is only able to use a Texture if it’s a part of a Material.
In this case, we did not create a material, we simply dragged the texture on the Quad. It was Unity that created the Material for us. - For our Background, let’s change the Shader. Let’s choose Unlit/Texture for the Shader on the nebula materail. Now our Background is independent of our lighting system and it display the texture exactly as it looks in the original image and it uses no lights at all.
move the background down.
Moving the Player
add Script named PlayerController.cs to Player GameObject
“camelCase” isn’t PascalCase, but “PascalCase” is.
use Input.GetAxis() to 得到运动的分量 来 设置 rb.velocity 从而使飞船运动。
设置飞船运行的限制区域
学习 Class Boundary 的用法
Mathf - A collection of common math functions.
Mathf.Clamp(float value, float min, float max)
将value限制在 min 和 max 之间
设置 飞船的 tilt,让飞船在左右移动的时候有适当的倾斜。
Quaternion Euler(float x, float y, float z)
Returns a rotation that rotates z degrees around the z axis, x degrees around the x axis, and y degrees around the y axis (in that order).
// PlayerController.cs
using UnityEngine;
using System.Collections;
[System.Serializable] // to serialise our new class we need to use this, and therefore visible in the Inspector
public class Boundary // we can use this class in other places in our game.
{
public float xMin, xMax, zMin, zMax;
}
public class PlayerController : MonoBehaviour
{
private Rigidbody rb;
public float speed; // 飞船速度
public float tilt; // 飞船左右运动时的翻转程度
public Boundary boundary;
public GameObject shot;
public Transform shotSpawn;
public float fireRate;
public float nextFire;
void Start()
{
rb = GetComponent<Rigidbody> ();
}
// FixedUpdate will be called automatically by Unity just before each fixed physics step.
// All the code we put inside the FixedUpdate function will be executed once per physics step.
void FixedUpdate()
{
float moveHorizontal = Input.GetAxis ("Horizontal");
float moveVertical = Input.GetAxis ("Vertical");
Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical);
rb.velocity = movement * speed; // the direcion we are travelling and how fast we are travelling
rb.position = new Vector3
(
Mathf.Clamp(rb.position.x, boundary.xMin, boundary.xMax),
0.0f,
Mathf.Clamp(rb.position.z, boundary.zMin, boundary.zMax)
);
rb.rotation = Quaternion.Euler(0.0f, 0.0f, rb.velocity.x * -tilt); // ???为什么要取相反数才能保证翻转方向正确
}
}
Creating shots
Creating shots by assembling artwork, physics components and custom C# code.
- Create a Object named Bolt, this will be the parent object for our shot.
We are going to separate the game logic from our visual effect of the shot. This will allow us to easily make new weapons with different visual effects by reusing the parent game object with the logic and replacing the visual effect layer.
Create a Quad VFX to hold the visual effect image. Add the VFX game object as a child of Bolt.
Create a Material fx_bolt_orange for the Quad VFX. Drag the Material on to the Quad.
Look at Quad VFX game object, We Change the shader on the material fx_bolt_orange to Particles - Additive
With shader particles/additive, black has a value of 0 and will add nothing to the scene. And white has a value of 255 on all channels and will add full white to the scene. All of the other colours will be added on top of the existing background. This will give us a strong, hot laser bolt.
We can also change Shader to mobile/particle/additive.
In general the mobile shader will be more efficient with our game’s resource budget, but in some cases may sacrifice either quality or control. The main control that we will lose by using this mobile shader is the ability to change the tint colour, which we don’t need on our laser bolt.
with our visual effect set up, let’s move on to setting up our logic.也就是说配置 Bolt 对象。
5. Add rigid body component to Bolt game object.
6. Remove collider component of the VFX game object.
7. Add Capsule Collider to Bolt game object. And adjust it’s size and orientation.
8. Click is trigger of this collider to make this collider a trigger collider.
9. Add Mover.cs component to Bolt game object.
10. our player is going to shoot many copies or clones of this shot, so let’s save this game object as a prefab. And we set the speed value in this prefab not the INSTANCE in the scene.
11. delete our working instance of Bolt from the scene.
12. to test the Bolt, as we don’t have any shooting code, we simply drag the copies of the prefab into the hierarchy window while the game is running.
// Mover.cs
using UnityEngine;
using System.Collections;
public class Mover : MonoBehaviour
{
private Rigidbody rb;
public float speed; // control the speed of our laser bolt.
void Start()
{
rb = GetComponent<Rigidbody>();
// We want the laser bolt to travel along the Z axis towards the oncoming hazards
// The local Z axis of an object is also known as it's transform.forward.
rb.velocity = transform.forward * speed;
}
}
Shooting shots
Writing the code and setting up the scene to shoot shots.
What we need to do is instantiate a copy of clone of this Shot prefab when we hit a button or click a mouse during our gameplay.
- create a empty game object named Shot Spawn. We can use this empty game object’s transform as a spawn point in our game. This spawn point should move with our player ship. So let’s drag Shot Spawn on to our player game object and drop it as a child. As it’s a child of the player ship, our Shot Spawn’s position will be relative to the player ship.
- drag the Shot Spawn out along it’s Z axis until it’s in front of the ship.
// PlayerController.cs
using UnityEngine;
using System.Collections;
[System.Serializable] // to serialise our new class we need to use this, and therefore visible in the Inspector
public class Boundary // we can use this class in other places in our game.
{
public float xMin, xMax, zMin, zMax;
}
public class PlayerController : MonoBehaviour
{
private Rigidbody rb;
public float speed; // 飞船速度
public float tilt; // 飞船左右运动时的翻转程度
public Boundary boundary;
public GameObject shot;
public Transform shotSpawn;
public float fireRate;
public float nextFire;
void Start()
{
rb = GetComponent<Rigidbody> ();
}
void Update()
{
if (Input.GetButton("Fire1") && Time.time > nextFire)
{
nextFire = Time.time + fireRate;
//GameObject clone =
Instantiate(shot, shotSpawn.position, shotSpawn.rotation);
}
}
// FixedUpdate will be called automatically by Unity just before each fixed physics step.
// All the code we put inside the FixedUpdate function will be executed once per physics step.
void FixedUpdate()
{
float moveHorizontal = Input.GetAxis ("Horizontal");
float moveVertical = Input.GetAxis ("Vertical");
Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical);
rb.velocity = movement * speed; // the direcion we are travelling and how fast we are travelling
rb.position = new Vector3
(
Mathf.Clamp(rb.position.x, boundary.xMin, boundary.xMax),
0.0f,
Mathf.Clamp(rb.position.z, boundary.zMin, boundary.zMax)
);
rb.rotation = Quaternion.Euler(0.0f, 0.0f, rb.velocity.x * -tilt); // ???为什么要取相反数才能保证翻转方向正确
}
}
打卡:2016年8月3日20:29:59
Boundary
Creating a bounding box to destroy any object that leaves the game area.
We are going to create a box around our game and we will destroy these shots as they leave the box.(所以我们使用 OnTriggerExit(Collider))
注:The number of units from the top of the screen to the bottom is always twice the value of our camera’s orthographic size.
// DestroyByBundary.cs
using UnityEngine;
using System.Collections;
public class DestroyByBoundary : MonoBehaviour
{
void OnTriggerExit(Collider other)
{
// Destroy everything that leaves the trigger
Destroy(other.gameObject);
}
}
Creating hazards
Create an asteroid hazard to challenge the player.
注:We will have a parent game object for the logic and the artwork will be a child.
让小行星随机旋转用到 Random.insideUnitSphere
Returns a random point inside a sphere with radius 1 (Read Only).
Set Angular Drag value to 0. 避免小行星停止自转。
// RandomRotate.cs
using UnityEngine;
using System.Collections;
public class RandomRotater : MonoBehaviour
{
private Rigidbody rb;
public float tumble; // hold our maximum tumble value
void Start()
{
rb = GetComponent<Rigidbody>();
rb.angularVelocity = Random.insideUnitCircle * tumble; // 给小行星一个随机的角速度,让它旋转
}
Write trigger collider code for our 2 colliders to have any effect.
我们发现小行星由于和Boundary接触而消失,为了避免这一效果,tag our boundary.
// DestroyByContact.cs
using UnityEngine;
using System.Collections;
public class DestroyByContact : MonoBehaviour
{
void OnTriggerEnter(Collider other)
{
// Debug.Log(other.name);
if(other.tag == "Boundary") // 当 other 的 tag 是 Boundary 的时候,跳过
{
return;
}
Destroy(other.gameObject); // Destroy the laser bolt when it hits the asteroid.
Destroy(gameObject); // Destroy the asteroid itself.
}
}
打卡:2016年8月5日19:40:31
Explosions
Add explosions to the scene when hazards or the player is destroyed.
- Add explosions to the scene when hazards or the player is destroyed.两个爆炸效果。
- Get the asteroid moving towards the player. Use Mover.cs created before, and set the speed a negative number to set it’s direction right. 很方便的是,当小行星碰撞Boundary的时候也会被销毁。从而不需要再配置另一Destroy程序了。
- 将Asteroid GameObject 保存为Prefab.
// DestroyByContact.cs
using UnityEngine;
using System.Collections;
public class DestroyByContact : MonoBehaviour
{
public GameObject explosion;
public GameObject playerExplosion;
void OnTriggerEnter(Collider other)
{
// Debug.Log(other.name);
if(other.tag == "Boundary") // 当 other 的 tag 是 Boundary 的时候,跳过
{
return;
}
Instantiate(explosion, transform.position, transform.rotation);
if (other.tag == "Player") // 如果是飞船和小行星碰撞的话,要生成飞船的explosion
{
Instantiate(playerExplosion, other.transform.position, other.transform.rotation);
}
Destroy(other.gameObject); // Destroy the laser bolt when it hits the asteroid.
Destroy(gameObject); // Destroy the asteroid itself.
}
}
疑问:爆炸效果 GameObject 并没有被销毁。
Game Controller
Write a controller to spawn our hazards, keep track of and display our score, end the game and allow the game to be restarted.
- Create a new GameObject “GameController” to hold our game controller logic. (This GameObject will not have a physical presence in our game. ) And tag it with GameController.
// GameController.cs
using UnityEngine;
using System.Collections;
public class GameController : MonoBehaviour
{
public GameObject hazard;
public Vector3 spawnValues;
void Start()
{
SpawnWaves();
}
void SpawnWaves()
{
Vector3 spawnPosition = new Vector3(Random.Range(-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
Quaternion spawnRotation = Quaternion.identity;
Instantiate(hazard, spawnPosition, spawnRotation);
}
}
Spawning Waves
Spawn infinite waves of hazards to change our player.
- To have a function that can pause without pausing our entire game, we need to make this function a coroutine.
- 为了Destroy explosion,使用另一种销毁方法,destroy things by time.
using UnityEngine;
using System.Collections;
public class GameController : MonoBehaviour
{
public GameObject hazard;
public Vector3 spawnValues; // 通过Inspector来设置spawnValues,继而在SpawnWaves中设置spawnPosition
public int hazardCount; // 每一波的小行星数量
public float spawnWait; // 相邻两个小行星的生成时间间隔
public float startWait; // a short pause after the game starts for the player to get ready
public float waveWait; // 相邻两波之间的时间间隔
void Start()
{
StartCoroutine(SpawnWaves());
}
IEnumerator SpawnWaves()
{
yield return new WaitForSeconds(startWait);
while(true)
{
for (int i = 0; i < hazardCount; i++)
{
Vector3 spawnPosition = new Vector3(Random.Range(-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
Quaternion spawnRotation = Quaternion.identity;
Instantiate(hazard, spawnPosition, spawnRotation);
yield return new WaitForSeconds(spawnWait);
}
yield return new WaitForSeconds(waveWait);
}
}
}
// DestroyByTime.cs
using UnityEngine;
using System.Collections;
public class DestroyByTime : MonoBehaviour
{
public float lifeTime;
void Start()
{
Destroy(gameObject, lifeTime);
}
}
Part 3 Scoring,Finishing And Building the Game
Audio
Add audio effects for the weapons, explosions and a background music track.
We associate an audio clip with a game object by using an audio source component. The audio source plays an audio clip.
We can add an audio source component to our game objects and then reference an audio clip for that source to play.
But ther is an easier way to do this, If we drag an audio clip on to a game object , Unity will create a new audio source on the game object and automatically reference the dragged audio clip.
- drag audio explosion_asteroid to explosion_asteroid, and click “Play On Awake”.这样当实例化这个Game Object的时候,就会自动播放这个音频。
- 在设置背景音乐的时候 click “Loop”.
- Balancing the audio.调整音量。调低背景音和子弹音量。
using UnityEngine;
using System.Collections;
[System.Serializable] // to serialise our new class we need to use this, and therefore visible in the Inspector
public class Boundary // we can use this class in other places in our game.
{
public float xMin, xMax, zMin, zMax;
}
public class PlayerController : MonoBehaviour
{
private Rigidbody rb;
private AudioSource audioSource;
public float speed; // 飞船速度
public float tilt; // 飞船左右运动时的翻转程度
public Boundary boundary;
public GameObject shot;
public Transform shotSpawn;
public float fireRate;
public float nextFire;
void Start()
{
rb = GetComponent<Rigidbody> ();
audioSource = GetComponent<AudioSource>();
}
void Update()
{
if (Input.GetButton("Fire1") && Time.time > nextFire)
{
nextFire = Time.time + fireRate;
//GameObject clone =
Instantiate(shot, shotSpawn.position, shotSpawn.rotation);
audioSource.Play();
}
}
// FixedUpdate will be called automatically by Unity just before each fixed physics step.
// All the code we put inside the FixedUpdate function will be executed once per physics step.
void FixedUpdate()
{
float moveHorizontal = Input.GetAxis ("Horizontal");
float moveVertical = Input.GetAxis ("Vertical");
Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical);
rb.velocity = movement * speed; // the direcion we are travelling and how fast we are travelling
rb.position = new Vector3
(
Mathf.Clamp(rb.position.x, boundary.xMin, boundary.xMax),
0.0f,
Mathf.Clamp(rb.position.z, boundary.zMin, boundary.zMax)
);
rb.rotation = Quaternion.Euler(0.0f, 0.0f, rb.velocity.x * -tilt); // ???为什么要取相反数才能保证翻转方向正确
}
}
Counting Points and Displaying the Score(NICE)
Counting points and displaying the score.
- Creating a display for the score before we create a score value to feed it with. Adding a GUIText Component.
- Update the GUIText’s transform position to (0.5, 0.5, 0.0)- This will place the GUIText into the middle of the screen.
- Update the GUIText.text with “Score Text”.- This will make the GUIText object easier to see on screen.
注:GUI text is drawn on a layer above our game, not in the game world. We won’t see it in our Scene view. GUI text uses something called Viewport Space, rather than screen space. Screen space is defined in pixels, Viewport Space is defined simply as a single width and height value with (0, 0) in the lower left, and (1, 1) in the upper right.
- Move Score Text to upper left of. Give some pad around it to separate it from the edges of the game screen Use pixel offset
- Feed Score Text a score value.
注:不能再运行前用Hierarchy 中的 GameObject 初始化 Prefab 中的 内容。
// GameController.cs
using UnityEngine;
using System.Collections;
public class GameController : MonoBehaviour
{
public GameObject hazard;
public Vector3 spawnValues; // 通过Inspector来设置spawnValues,继而在SpawnWaves中设置spawnPosition
public int hazardCount; // 每一波的小行星数量
public float spawnWait; // 相邻两个小行星的生成时间间隔
public float startWait; // a short pause after the game starts for the player to get ready
public float waveWait; // 相邻两波之间的时间间隔
public GUIText scoreText;
private int score;
void Start()
{
score = 0; // starting value
UpdateScore();
StartCoroutine(SpawnWaves());
}
IEnumerator SpawnWaves()
{
yield return new WaitForSeconds(startWait);
while(true)
{
for (int i = 0; i < hazardCount; i++)
{
Vector3 spawnPosition = new Vector3(Random.Range(-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
Quaternion spawnRotation = Quaternion.identity;
Instantiate(hazard, spawnPosition, spawnRotation);
yield return new WaitForSeconds(spawnWait);
}
yield return new WaitForSeconds(waveWait);
}
}
public void AddScore(int scoreValue)
{
score += scoreValue;
UpdateScore();
}
void UpdateScore()
{
scoreText.text = "Score: " + score;
}
}
// DestroyByContact.cs
using UnityEngine;
using System.Collections;
public class DestroyByContact : MonoBehaviour
{
public GameObject explosion;
public GameObject playerExplosion;
public int scoreValue; // 每击中一个的分数
private GameController gameController; // 在运行时通过脚本设置,不能直接将 GameController GameObject 拖入 Asteroid Prefab。
void Start()
{
GameObject gameControllerObject = GameObject.FindWithTag("GameController"); // 读取GameObject GameController
if(gameControllerObject != null)
{
gameController = gameControllerObject.GetComponent<GameController>(); // 读取Component GameController(就是那个脚本文件)
}
if(gameController == null)
{
Debug.Log("Cannot find 'GameController' script");
}
}
void OnTriggerEnter(Collider other)
{
// Debug.Log(other.name);
if(other.tag == "Boundary") // 当 other 的 tag 是 Boundary 的时候,跳过
{
return;
}
Instantiate(explosion, transform.position, transform.rotation);
if (other.tag == "Player") // 如果是飞船和小行星碰撞的话,要生成飞船的explosion
{
Instantiate(playerExplosion, other.transform.position, other.transform.rotation);
}
gameController.AddScore(scoreValue);
Destroy(other.gameObject); // Destroy the laser bolt when it hits the asteroid.
Destroy(gameObject); // Destroy the asteroid itself.
}
}
Ending the Game
End the game and show the state of the game with labels for Game Over and Restart.
To keep our hierarchy organized, let’s create an empty parent game object to hold our current score text and two new labels(Restart Text, Game Over Text) we are about to create. Rename the game object Display Text.
Update our code on our gamecontroller script. To capture a keypress, let’s pool for it in update.
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
public class GameController : MonoBehaviour
{
public GameObject hazard;
public Vector3 spawnValues; // 通过Inspector来设置spawnValues,继而在SpawnWaves中设置spawnPosition
public int hazardCount; // 每一波的小行星数量
public float spawnWait; // 相邻两个小行星的生成时间间隔
public float startWait; // a short pause after the game starts for the player to get ready
public float waveWait; // 相邻两波之间的时间间隔
public GUIText scoreText; // We will feed information to these labels as the game progresses.
public GUIText restartText;
public GUIText gameOverText;
private bool gameOver;
private bool restart;
private int score;
void Start()
{
gameOver = false;
restart = false;
gameOverText.text = "";
restartText.text = "";
score = 0; // starting value
UpdateScore();
StartCoroutine(SpawnWaves());
}
void Update()
{
if(restart)
{
if(Input.GetKeyDown(KeyCode.R))
{
// Application.LoadLevel(Application.loadedLevel); // restart the game
SceneManager.LoadScene("_Scenes/main"); // 重新加载这个 Scene Loads the scene by its name or index in Build Settings.
}
}
}
IEnumerator SpawnWaves()
{
yield return new WaitForSeconds(startWait);
while(true)
{
for (int i = 0; i < hazardCount; i++)
{
Vector3 spawnPosition = new Vector3(Random.Range(-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
Quaternion spawnRotation = Quaternion.identity;
Instantiate(hazard, spawnPosition, spawnRotation);
yield return new WaitForSeconds(spawnWait);
}
yield return new WaitForSeconds(waveWait);
if(gameOver) // 游戏结束的时候,询问是否重来
{
restartText.text = "Press 'R' for Restart";
restart = true;
break; // 结束这个 spawnwaves 循环
}
}
}
public void AddScore(int scoreValue)
{
score += scoreValue;
UpdateScore();
}
void UpdateScore()
{
scoreText.text = "Score: " + score;
}
public void GameOver() // 如同 AddScore 一样,此函数可以由别的GameObject调用。
{
gameOverText.text = "Game Over!";
gameOver = true;
}
}
using UnityEngine;
using System.Collections;
public class DestroyByContact : MonoBehaviour
{
public GameObject explosion;
public GameObject playerExplosion;
public int scoreValue; // 每击中一个的分数
private GameController gameController; // 在运行时通过脚本设置,不能直接将 GameController GameObject 拖入 Aeteroid Prefab。
// a variable holding the reference to our game controller instance.
void Start()
{
GameObject gameControllerObject = GameObject.FindWithTag("GameController"); // 读取GameObject GameController
if(gameControllerObject != null)
{
gameController = gameControllerObject.GetComponent<GameController>(); // 读取Component GameController(就是那个脚本文件)
}
if(gameController == null)
{
Debug.Log("Cannot find 'GameController' script");
}
}
void OnTriggerEnter(Collider other)
{
// Debug.Log(other.name);
if(other.tag == "Boundary") // 当 other 的 tag 是 Boundary 的时候,跳过
{
return;
}
Instantiate(explosion, transform.position, transform.rotation);
if (other.tag == "Player") // 如果是飞船和小行星碰撞的话,要生成飞船的explosion
{
Instantiate(playerExplosion, other.transform.position, other.transform.rotation);
gameController.GameOver(); // 告诉 GameController game is over
}
gameController.AddScore(scoreValue);
Destroy(other.gameObject); // Destroy the laser bolt when it hits the asteroid.
Destroy(gameObject); // Destroy the asteroid itself.
}
}
Building the Game
Build the completed game and deploy it to the web.