zoukankan      html  css  js  c++  java
  • 对 Unity 太空射击游戏的实践

    写在前面 #

    本次 Space Shooter 实践通过实现以下功能达到加深对 U3D 游戏开发的认知.

    • 键盘控制飞船移动;
    • 发射子弹设计目标;
    • 随机生成大量障碍物;
    • 计分;
    • 实现游戏对象的生命周期管理;

    同时进一步练习场景元素的编辑, 脚本文件的创建和 GUI 的处理, 以及音频文件的添加等方法.

    最终效果:

    1. 导入模型,贴图和材质 #

    步骤要注意的几点 :

    导入的资源包中有可以正确运行已做好的 Done_Main 场景, 将其删除, 创建一个全新的空场景文件 Main, 实践复原 Done_Main 的功能;

    将 File>>Build Settings>>Player Settings>>Default Is Full Screen 取消勾选, 宽高设置为 400x600;

    飞船模型拖至 Hierarchy 命名为 Player, Reset Transform 组件;

    添加 Rigidbody, 不希望飞船受重力影响而下坠, 取消勾选 Use Gravity 选项;

    添加碰撞体组件 Mesh Collider, 这是一个网格碰撞体, 使飞船能够与随机出现的障碍物发生碰撞, 并在碰撞后触发销毁飞船和障碍物的事件, Mesh Collider 的 Mesh 属性为模型 vehicle_playerShip 的网格, 该网格模型包含许多细小的三角形面片

    为了提高游戏的执行效率, 飞船网格模型不应该过于复杂, 不必进行如此精确的碰撞检测, 应该建立一个简化的模型, 减少不必要的碰撞计算;

    最后还要勾选 Convex 和 Is Trigger 选项框, 将 Mesh Collider 设置为触发器, 如图;

    添加飞船尾部的火焰粒子效果, 要是 Player 的子对象;

    使摄像机正对着飞船, Rotation(90,0,0). 使飞船处于 Viewport 视图窗口的下半部分, Position(0,10,4). 摄像机为正交投影;

    添加背景图片, GameObject>>3D Object>>Quad 创建一个平面命名为 Background, 移除 Mesh Collider, 此时垂直于飞船;(Quad 默认情况下为背向剔除模式, 因此可能需要调整视角才能看到 Quad 平面) Quad 的 Position(90,0,0);

    设置 Background 的纹理图片 Shader 模式为 Unlit/Texture;

    为背景添加粒子效果繁星点点;

    至此动图效果:

    2. 编写脚本代码 #

    2.1 控制飞船移动 ##

    PlayerController.cs 实现方向键控制飞船移动的功能;

     using UnityEngine;
     using System.Collections;
    
     public class PlayerController : MonoBehaviour
     {
     	// 想在 Inspector 视图显示, 就需要为 Boundary 类添加可序列化的属性 [System.Serializable]
     	[System.Serializable]
     	public class Boundary
     	{
     		// 用于管理飞船活动的边界值, XZ 平面
     		public float xMin, xMax, zMin, zMax;
     	}
    
     	// 速度控制变量
     	public float speed;
     	public Boundary boundary;
     	// 飞船倾斜系数
     	public float tilt = 4.0f;
    
     	void FixedUpdate ()
     	{
     		// 得到水平方向输入
     		float moveHorizontal = Input.GetAxis ("Horizontal");
     		// 得到垂直方向输入
     		float moveVertical = Input.GetAxis ("Vertical");
     		// 用上面的水平方向和垂直方向输入创建一个 Vector3 变量, 作为刚体速度, 是一个矢量
     		Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical);
     		Rigidbody rb = GetComponent<Rigidbody> ();
     		if (rb != null) {
     			rb.velocity = movement * speed;
     			// Mathf.Clamp 限定刚体的活动范围
     			rb.position = new Vector3 (
     				Mathf.Clamp (rb.position.x, boundary.xMin, boundary.xMax),
     				0.0f,
     				Mathf.Clamp (rb.position.z, boundary.zMin, boundary.zMax)
     			);
     			// 飞船左右移动时有一定的倾斜效果,
     			// 绕 Z 轴旋转, 往左运动 X 轴上速度为负值, 旋转的角度为逆时针正值, 所以要乘以一个负系数
     			rb.rotation = Quaternion.Euler (0.0f, 0.0f, rb.velocity.x * -tilt);
     		}
    
     	}
     }
    

    至此动图效果为

    2.2 实现射击行为 ##

    步骤需要注意的几点

    新建立一个空的游戏对象 Bolt, 添加 Rigidbody 取消勾选 Use Gravity 选项框.

    为 Bolt 新建一个子对象 Quad 命名为 VFX, Rotation(90,0,0), 移除 Mesh Collider, 添加材质 fx_bolt_orange.

    为 Bolt 添加一个胶囊碰撞体, 勾选 Is Trigger 设为触发器, 设置 Capsule Collider 的 Direction 属性值为 Z-Axis, 设置半径和高度.

    为 Bolt 添加一个脚本 Mover.cs. 此段代码放在 Start() 函数里, 因为在脚本的生命周期中只需要调用一次, 不需要每一帧都调用.

    将 Bolt 拖至 Prefabs 文件夹成为预制体, 预制体做好后将原本的 Bolt 删除.

    using UnityEngine;
    using System.Collections;
    
    public class Mover : MonoBehaviour
    {
    	// 子弹的速度
    	public float speed;
    
    	void Start ()
    	{
    		GetComponent<Rigidbody> ().velocity = transform.forward * speed;
    	}
    
    }
    
    

    脚本控制发射子弹, 为 Player 新建空的子对象 Shot Spawn, Position(0,0,0.7), 在此位置发射子弹

    管理光电子弹的生命周期, 子弹在飞出有效区域之后自行销毁, 为游戏区域添加触发器, 当电光子弹飞出区域时触发事件, 在实践响应函数中调用 Destroy.

    设置 Boundary 为触发器, 由于不需要在场景中显示 Boundary 对象, 移除 Mesh Renderer 组件.

    为 Boundary 添加脚本 DestoryByBoundary.cs

    using UnityEngine;
    using System.Collections;
    
    public class DestoryByBoundary : MonoBehaviour {
    
    	void OnTriggerExit(Collider other){
    		Destroy (other.gameObject);
    	}
    }
    
    

    注意的 :

    • 若要处理游戏对象移出触发器时的事件, 应该重载事件函数 OnTriggerExit;
    • OnTriggerExit 的参数 Collider 表示移出触发器的对象, 这里就是飞出边界的子弹对象上的碰撞体;

    2.3 添加小行星障碍物 ##

    要注意的几点

    小行星随机生成, 随机的角度旋转;

    射击击中小行星时, 小行星爆炸并销毁;

    飞船碰上小行星, 飞船爆炸, 游戏结束;

    新建空对象 Asteroid Position(0,0,9) Rigidbody 取消 Use Gravity 添加 Capsule Collider 勾选 Is Trigger.

    模型 prop_asteroid_01 添加为 Asteroid 的子对象.

    Capsule Collider 属性 Radius = 0.5, Height = 1.6, Direction 为 Z-Axis

    为 Asteroid 添加脚本 RandomRotator.cs;

    using UnityEngine;
    using System.Collections;
    
    public class RandomRotator : MonoBehaviour
    {
    	// tumble 是旋转系数
    	public float tumble;
    	void Start ()
    	{
    		// angularVelocity 表示刚体的角速度;  insideUnitSphere 表示单位长度半径球体内的一个随机点(向量)
    		// 乘积结果描述了在半径长度为 tumble 的球体中的随机点
    		// 由此就可以实现刚体以一个随机的角速度旋转
    		GetComponent<Rigidbody> ().angularVelocity = Random.insideUnitSphere * tumble;
    	}
    }
    
    

    设定 Asteroid 对象的角阻力为0;

    添加控制射击小行星的功能, 为小行星 Asteroid 添加一个脚本来控制碰撞事件 DestroyByContact.cs

    using UnityEngine;
    using System.Collections;
    
    public class DestoryByContact : MonoBehaviour
    {
    	// 小行星爆炸时的粒子对象
    	public GameObject explosion;
    	// 飞船与小行星碰撞飞船爆炸的粒子对象
    	public GameObject playerExplosion;
    
    	void OnTriggerEnter (Collider other)
    	{
    
    		if (other.tag == "Boundary" || other.tag == "Enemy") {
    			return;
    		}
    		if (explosion != null) {
    			// 在小行星销毁的位置生成一个爆炸效果, explosion 是小行星的位置
    			Instantiate (explosion, transform.position, transform.rotation);  
    		}
    
    		if (other.tag == "Player") {
    			// 在玩家飞机销毁的位置生成一个爆炸效果, playerExplosion 是飞船的位置
    			Instantiate (playerExplosion, other.transform.position, other.transform.rotation);  
    		}
    		// 销毁跟小行星碰撞的物体
    		Destroy (other.gameObject);  
    		// 销毁小行星
    		Destroy (this.gameObject);   
    	}
    }
    
    

    Boundary 的 Tag 设为 Boundary; Player 的 Tag 设为 Player

    至此动图效果为

    2.4 控制小行星运动和随机生成 ##

    让小行星以一定的速度飞向飞船, 为 Asteroid 添加脚本 Mover.cs 设置 speed 属性值为 -5; 速度设为负值, 因为小行星与子弹的运动方向相反

    需要先制作 Asteroid 预制体, 创建 Project>>GameController 空游戏对象, Tag 为 GameController, 并为之创建脚本 GameController.cs

    using UnityEngine;
    using System.Collections;
    
    public class GameController : MonoBehaviour
    {
    	// 小行星数组
    	public GameObject[] hazards;
    	// 随机生成小行星的位置
    	public Vector3 spawnValues;
    	// 每一波小行星生成的数量
    	public int hazardCount;
    	// 每次生成小行星对象后延迟的时间, 单位秒
    	public float spawnWait;
    	// 表示开始生成小行星对象前等待的时间
    	public float startWait;
    	// 表示两批小行星阵列间的时间间隔
    	public float waveWait;
    
    	void Start ()
    	{
    		StartCoroutine (SpawnWave ());
    	}
    
    	// 一波一波地生成小行星
    	IEnumerator SpawnWave ()
    	{
    		yield return new WaitForSeconds (startWait);
    
    		while (true) {
    			for (int i = 0; i < hazardCount; i++) {
    				GameObject hazard = hazards [Random.Range (0, hazards.Length)];
    				Vector3 spawnPosition = new Vector3 (Random.Range (-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
    				Instantiate (hazard, spawnPosition, Quaternion.identity);  // 生成随机的小行星
    				yield return new WaitForSeconds (spawnWait);
    			}
    
    			yield return new WaitForSeconds (waveWait);
    		}
    	}
    }
    
    

    有一个要注意的地方, 对数组 Hazards 的内容不能拖成 model ,要是预制体, 否则生成的小行星无效导致不会运动, 如图

    防止小行星数量太多, 距离近以致小行星之间相互碰撞销毁, 需要使用 协程类 WaitForSeconds

    让爆炸后的粒子实例 explosion_asteroid 自动销毁, 建立脚本 DestroyByTime.cs 绑定到 explosion_asteroid 和 explosion_player 上

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class DestroyByTime : MonoBehaviour
    {
    
    	public float lifeTime = 2.0f;
    
    	void Start ()
    	{
    		Destroy (gameObject, lifeTime);
    	}
    }
    
    

    3. 添加音频 #

    将音频文件添加至预制体
    是否勾选 Play On Awake 表明音频文件在唤醒时自动播放;

    4. 添加积分文本 #

    新版 Text 组件的使用方法, GameObject>>UI>>Text 生成 Canvas>>Text 和 EventSystem. 调整 Text 位置, Anchor Presets 选择 top-left.

    积分功能包括以下作用 :

    飞船发射子弹击中小行星后分值增加;
    分值增加后更新 Text 组件的显示;

    在 GameController.cs 脚本添加变量 scoreText 和 score

    // 更新计分 Text 的组件
    public Text scoreText;
    // 保存当前分值
    private int score;
    
    void Start ()
    {
      score = 0;
      UpdateScore ();
      StartCoroutine (SpawnWave ());
    }
    
    void UpdateScore ()
    {
      scoreText.text = "Get Score : " + score;
    }
    
    public void AddScore (int newScoreValue)
    {
      score += newScoreValue;
      UpdateScore ();
    }
    

    脚本 DestoryByContact.cs 可以调用 AddScore 函数.

    // 表示小行星被击中后玩家分值增加的数量
    public int scoreValue;
    // 表示在游戏对象 GameController 上绑定的脚本 GameController.cs
    private GameController gameController;
    
    void Start ()
    {
      GameObject go = GameObject.FindWithTag ("GameController");
      if (go != null) {
        gameController = go.GetComponent<GameController> ();
      } else {
        Debug.Log ("Cannot Find a tag of GameController");
      }
      if (gameController == null) {
        Debug.Log ("Cannot Find the Script of GameController.cs");
      }
    }
    
    if (explosion != null) {
    			// 在小行星销毁的位置生成一个爆炸效果, explosion 是小行星的位置
    			Instantiate (explosion, transform.position, transform.rotation);  
    			gameController.AddScore (scoreValue);
    		}
    

    5. 游戏结束与重新开始 #

    添加游戏结束的 Text 组件

    添加游戏结束的脚本

    GameController 添加变量

    // 更新 Text 组件的显示
    public Text gameOverText;
    // 游戏是否结束
    private bool gameOver;
    
    public void GameOver ()
    {
      gameOver = true;
      gameOverText.text = "游戏结束";
    }
    
    while (true) {
    			if (gameOver) {
    				break;
    			}
      // ... ...
    }
    

    在 DestroyByContact.cs 脚本加入对 GameOver() 函数的调用.

    if (other.tag == "Player") {
    	// 在玩家飞机销毁的位置生成一个爆炸效果, playerExplosion 是飞船的位置
    	Instantiate (playerExplosion, other.transform.position, other.transform.rotation);  
    	gameController.GameOver ();
    }
    

    添加重新开始的 Text 组件, 按[R]键重新开始.

    // 更新添加的 Text 组件
    public Text restartText;
    // 是否重新开始游戏, 只有游戏结束时重新开始
    private bool restart;
    
    void Start ()
    {
      score = 0;
      UpdateScore ();
      gameOverText.text = "";
      gameOver = false;
      restartText.text = "";
      restart = false;
      StartCoroutine (SpawnWave ());
    }
    
    void Update ()
    {
      if (restart) {
        if (Input.GetKeyDown (KeyCode.R)) {
          Application.LoadLevel (Application.loadedLevel);
        }
      }
    }
    

    Application.LoadLevel(Application.loadedLevel) 是 Unity 中重新加载场景的常用方法.

    三个文本

    至此完毕.

    End.

    学习自 Book《Unity 官方案例精讲》

    导出包 →GitHub

  • 相关阅读:
    Android OpenGL ES 2.0 (四) 灯光perfragment lighting
    Android OpenGL ES 2.0 (五) 添加材质
    冒泡排序函数
    javascript object 转换为 json格式 toJSONString
    Liunx CentOS 下载地址
    jquery 图片切换特效 鼠标点击左右按钮焦点图切换滚动
    javascript 解析csv 的function
    mysql Innodb Shutdown completed; log sequence number解决办法
    Centos 添加 yum
    javascript 键值转换
  • 原文地址:https://www.cnblogs.com/isayes/p/6567043.html
Copyright © 2011-2022 走看看