一、前言
本案例是初级案例,意在帮助想使用unity的初级开发者能较快的入门,体验unity开发的方便性和简易性能。
本次我们将使用团结引擎进行开发,帮助想体验团结引擎的入门开发者进行较快的环境熟悉。
本游戏案例以太空作战为背景,玩家和敌人为不同的飞船形象。敌人分为两种,可发射子弹和不能发射子弹,敌人射中玩家或者碰撞到玩家会消失,玩家生命减少。玩家每消灭一个敌人会获得相应的分数。游戏没有尽头,玩家生命值减少到0,游戏结束。
二、开发环境
操作系统:Windows
Unity 版本:团结引擎1.0.4
团结引擎的安装参考《团结引擎的安装》
三、搭建场景
3.1、 新建项目
打开Tuanjie Hub,点击左上角【create project】按钮,选择【3D】模版,输入项目名称SpaceWar和项目保存路径,点击右下【Create project】按钮完成创建。
3.2、搭建开始场景
团结引擎导入资源方式和Unity完全一样,对Unity导出的资源也完全兼容。下载本文附带的资源,点击【Assets】->【Import Package】->【Custom Package...】,选择下载好的资源,点右下角【Import】,将资源导入到项目。
3.2.1 搭建开始界面
1. 双击打开start场景。我们在场景中创建标题和开始按钮。在Hierarchy面板右键【UI】->【Legacy】-> 【Text】,将Text命名为Title。调整文本大小为宽500,高100,字体大小设置为60, 颜色设置为白色,并是文本居中对齐,位置Y设置为130,如下所示:
2. 接着创建一个开始按钮
在Canvas上右键【UI】->【Legacy】-> 【Button】,将按钮命名为StartBtn。调整按钮大小为宽200,高40,设置按钮颜色为 665441,透明度调整为225(0.8)。设置按钮的文本为“开始游戏”,颜色设置为白色。最后调整按钮Y的位置为 -120, 效果如下:
3. 添加按钮点击事件
在Project区域的Assets下新建文件夹Scripts,用来保存我们脚本。在Scripts文件夹下右键【Create】->【C# Script】,创建一个脚本,命名为StartMenu.cs。双击打开StartMenu.cs脚本,输入以下代码:
using UnityEngine;
using UnityEngine.SceneManagement;public class StartMenu : MonoBehaviour
{public void OnStartBtnClicked(){SceneManager.LoadScene(1);}
}
保存脚本,将脚本挂载到Canvas上。选中StartBtn按钮,在Button组件下的事件部分点击+号添加点击事件,如图:
最后,点击【File】->【Build Settings】,将Assets下的start和level两个场景拖到Build Settings 的 Scenes In Build 下,如图:
一切准备好后,点击运行按钮,看下效果。游戏运行后,点击开始游戏按钮,游戏切换到第二个场景,但第二个场景什么效果也没有,因为我们还没有进行开发,接下来我们就开始开发第二个场景。
3.3、搭建战斗场景
双击打开 level1 场景,资源已经为我们准备好了战斗所需的场景。我们先来看一下场景中的资源。
我们可以看到,在Assets的Prefab文件夹下有许多蓝色的物体,这就是预制体,这些预制体可以重复使用,场景中的蓝色物体就是使用了这些预制体。
可以看到,场景中有三个一样的Enemy,而Prefab文件夹里有个Enemy2,Enemy2是一个飞机的模型预制体。为了充分利用所有的预制体,我们将替换场景中的一个Enemy。场景中的
将Prefab文件夹里的Enermy2直接拖到场景中,将场景中第一个Enermy的坐标复制给Enermy2,然后删除第一个Enermy。如图所示:
我们的游戏是这样的:Enemy 和 Enemy2是两种不同的敌人,Enemy不能发射子弹,但会不断改变方向,Enemy2可以发射子弹,但不能改变方向。无论是Enemy还是Enemy2,当碰撞到玩家的飞机后都会消失,同时玩家也会减少一点生命。
接下来我们就开始开发功能,首先我们要先让玩家动起来。
3.3.1 开发玩家脚本
1. 让玩家动起来
在Scripts文件夹下新建脚本,命名为Player。在Prefab文件夹下选中Player预制体,将Player脚本挂载到Player预制体上,双击打开Player脚本,输入以下代码:
using UnityEngine;public class Player : MonoBehaviour
{public float m_Speed = 10.0f;void Update(){// 通过键盘按键控制玩家移动float h = 0f, v = 0f; // 分别标识在水平和垂直方向上的移动距离if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow)) // 键盘字母A键或者左箭头{h += m_Speed * Time.deltaTime;}if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow)) // 键盘字母D键或者右箭头{h -= m_Speed * Time.deltaTime;}if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow)) // 键盘字母W键或者向上箭头{v -= m_Speed * Time.deltaTime;}if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow)) // 键盘字母S键或者向下箭头{v += m_Speed * Time.deltaTime;}transform.Translate(h, 0, v);}
}
保存脚本,点击运行游戏看下效果。运行游戏后,在Game窗口,我们通过方向键或者ASDW按键就可以控制玩家的移动了,是不是很简单。
2. 发射子弹
我们继续在Player.cs脚本中添加发射子弹的功能。首先我们需要一个子弹的预制体来生成子弹。其次子弹不是每帧都生成,因此需要一个生成子弹的时间间隔。当我们在点击鼠标左键或者按空格键的时候,发射子弹。代码如下:
using UnityEngine;public class Player : MonoBehaviour
{public float m_Speed = 10.0f; // 移动速度public Transform m_RocketTrans; // 子弹预制体public float m_RocketRate = 0f; // 子弹的发射频率void Update(){// 通过键盘按键控制玩家移动...// 发射子弹m_RocketRate -= Time.deltaTime;if (m_RocketRate <= 0){m_RocketRate = 0.1f; // 重置发射时间if (Input.GetKey(KeyCode.Space) || Input.GetMouseButton(0)){Instantiate(m_RocketTrans, transform.position, transform.rotation);}}}
}
选中Player预制体,在 我们把Prefab文件夹下的Rocket预制体赋值给Player脚本的RocketTrans变量。
接着,我们让子弹动起来。当发射子弹后,子弹朝着前方飞去。新建子弹的脚本文件,命名为Rocket.cs。选中Rocket预制体,将脚本挂在到预制体上。双击打开Rocket.cs脚本,输入以下代码:
using UnityEngine;public class Rocket : MonoBehaviour
{public float m_Speed = 10.0f; // 子弹的移动速度public float m_life = 1f; // 子弹的生存时间void Update(){m_life -= Time.deltaTime;if (m_life <= 0) {Destroy(gameObject); // 销毁子弹}transform.Translate(0, 0, -m_Speed * Time.deltaTime); // 子弹移动}
}
保存场景,运行游戏看下效果。运行游戏后,按空格或者鼠标左键,玩家飞机会发射子弹,并且子弹会一直朝前飞行。
3.3.2 开发敌人脚本
1. 让敌人动起来
我们场景中有两种敌人。我们先给形状是飞碟的预制体添加脚本,飞碟不会发射子弹,但在飞行过程中会改变方向。飞碟的预制体为Enermy,因此我们新建一个Enermy.cs脚本,并把他挂载到Enermy预制体上。双击打开Enermy.cs 脚本,输入代码如下:
using UnityEngine;public class Enermy : MonoBehaviour
{public float m_Speed = 1f; // 飞行速度public float m_rotSpeed = 30f; // 旋转速度public float m_changeTimer = 1.5f; // 变向时间间隔void Update(){UpdateMove();}public virtual void UpdateMove(){m_changeTimer -= Time.deltaTime;if (m_changeTimer <= 0){m_changeTimer = 3f;m_rotSpeed = -m_rotSpeed;}// 旋转方向transform.Rotate(Vector3.up, m_rotSpeed * Time.deltaTime, Space.World);// 移动transform.Translate(0, 0, -m_Speed * Time.deltaTime);}
}
同样,我们接着给敌人Enermy2添加脚本。Enermy2是飞机模型,这种敌人可以发射子弹,但不能改变方向。我们新建脚本,命名为SuperEnermy.cs。将脚本挂载到Enermy2预制体上。双击打开SuperEnermy.cs脚本,输入以下代码:
using UnityEngine;public class SuperEnermy : Enermy
{public Transform m_rockey; // 子弹预制体public float m_fireTimer = 2f; // 发射子弹的时间间隔private Transform m_player; // 因为敌人的子弹要朝着玩家发射,因此需要玩家的引用private void Awake(){m_player = GameObject.FindGameObjectWithTag("Player").transform;}public override void UpdateMove(){m_fireTimer -= Time.deltaTime;if (m_fireTimer <= 0){m_fireTimer = 2f;if (m_player != null){Vector3 pos = transform.position - m_player.position; // 玩家相对飞机的方向Instantiate(m_rockey, transform.position, Quaternion.LookRotation(pos));}}transform.Translate(0, 0, -m_Speed * Time.deltaTime);}}
保存脚本,然后把EnermyRocket预制体赋值给SuperEnermy.cs脚本的 m_rocket 字段,如图:
运行游戏,可以看到敌人的飞机每隔2秒就会产生子弹,但子弹还不会移动。下面我们就给敌人的子弹添加脚本。
2. 敌人的子弹
新建EnermyRocket.cs脚本,选中EnermyRocket预制体,将脚本挂载到此预制体上。双击打开EnermyRocket.cs脚本,只需要让本脚本继承Rocket类就可以让子弹动起来了。
3.3.3 添加碰撞事件
我们知道,对于敌人和玩家都是有一定生命的,当敌人生命为0或者碰到玩家时,敌人要进行销毁。但玩家生命为0时,游戏结束。
下面我们就添加一下生命值和碰撞事件。
1. 添加子弹碰撞事件
打开Rocket.cs脚本,这是子弹脚本,我们添加表示子弹威力的字段 m_power,然后添加子弹的碰撞事件,代码如下:
using UnityEngine;public class Rocket : MonoBehaviour
{public float m_Speed = 10.0f; // 子弹的移动速度public float m_life = 1f; // 子弹的生存时间public int m_power = 1; // 子弹威力void Update(){...}private void OnTriggerEnter(Collider other){if (other.tag.CompareTo("Player") == 0) // 对敌人无效,玩家子弹碰撞敌人我们在其他脚本实现return;Destroy(gameObject); // 销毁子弹}
}
2. 添加敌人子弹事件
打开EnemyRocket.cs脚本,添加如下代码:
using UnityEngine;public class EnermyRocket : Rocket
{private void OnTriggerEnter(Collider other){if (other.tag.CompareTo("Enermy") == 0){return;}Destroy(gameObject);}
}
3. 添加敌人事件
打开Enemy.cs脚本,添加如下代码:
using UnityEngine;public class Enermy : MonoBehaviour
{public float m_Speed = 1f; // 飞行速度public float m_rotSpeed = 30f; // 旋转速度public float m_changeTimer = 1.5f; // 变向时间间隔public int m_life = 10;void Update(){UpdateMove();}public virtual void UpdateMove(){...}// 触发器事件private void OnTriggerEnter(Collider other){if (other.tag.CompareTo("PlayerRocket") == 0){Rocket rocket = other.GetComponent<Rocket>();if (rocket != null){m_life -= rocket.m_power; // 根据玩家子弹威力减少生命if (m_life <= 0){Destroy(gameObject); // 生命为0,销毁}}}else if(other.tag.CompareTo("Player") == 0){ m_life = 0;Destroy(gameObject);}}
}
4. 添加玩家事件
打开Player.cs脚本,添加如下代码:
using UnityEngine;public class Player : MonoBehaviour
{public float mSpeed = 10.0f;public Transform m_RocketTrans; // 子弹预制体public float m_RocketRate = 0f; // 子弹的发射频率public int m_life = 10; // 玩家的生命void Update(){...}private void OnTriggerEnter(Collider other){if (other.tag.CompareTo("PlayerRocket") == 0){m_life -= 1;if (m_life <= 0){Destroy(gameObject);}}}
}
保存脚本,运行游戏,可以看到,玩家发射子弹已经可以消灭敌人了。
3.4 生成大量敌人
目前场景中只有三个敌人,接下来我们就开始让游戏源源不断的生成敌人,已增加游戏的趣味性。
1. 添加生成敌人脚本
在Scripts文件夹下新建脚本 EnemySpawn.cs ,双加打开脚本,输入以下代码:
using UnityEngine;public class EnemySpawn : MonoBehaviour
{public Transform m_Enermy; // 敌人的预制体public float m_SpawnTime = 5f; // 生成敌人的时间间隔private void Update(){m_SpawnTime -= Time.deltaTime;if (m_SpawnTime <= 0){ m_SpawnTime = Random.value * 10f; // 随机生成时间if (m_SpawnTime < 5)m_SpawnTime = 5; // 最小生成时间间隔为5sInstantiate(m_Enermy, transform.position, Quaternion.identity); // 生成敌人} }
}
在Prefabs文件夹下,选中Spawn1预制体,将脚本挂载到预制体上,然后把Enemy预制体拖入脚本的Enermy字段。同理给Spawn2挂载脚本,把Enermy2填入Enermy字段,如下:
保存项目,运行游戏,可以看到已经可以源源不断的生成敌人了。至此,我们已经完成了游戏的主要功能开发,接下来我们将对游戏进行完善,如添加分数和游戏结束界面。
四、完善游戏
4.1 搭建UI
4.1.1 添加分数面板
在Hierarchy窗口右键 ->[UI]->[Legacy]->[Text], 新建Text作为我们本局游戏的分数。在创建Text的同时会自动创建一个Canvas。我们在Canvas下新建一个空的物体,命名为ScorePanel,并设置RectTransform 的stretch如图:
将Text放置在 ScorePanel下,并命名为Score。设置颜色为白色,文本内容暂时设置为Score:0,字体大小调整为36,并调整文本位置和大小,如图:
4.1.2 搭建游戏结束界面。
在Hierarchy窗口右键 ->[UI]->[Panel], 新建Panel作为我们游戏结束的背景。在创建Panel的同时会自动创建一个Canvas。我们在Canvas下新建一个空的物体,命名为GameOver,并设置RectTransform 的stretch如图:
将Panel放置在 GameOver下,并命名为Bg。设置颜色为黑色,透明度调整为 130。
在GameOver下新建一个文本组件(【UI】->【Legacy】->【Text】),命名为GameOverText,文本内容为“游戏结束”,设置字体大小为60,对齐方式改为上下居中,左右居中对齐, 颜色为白色,并调整文本组件的大小和位置如图:
在GameOver下新建一个按钮组件(【UI】->【Legacy】->【Button】),命名为AgainBtn,将按钮文本改为“再来一局”,颜色为白色,调整按钮颜色为 665441,透明度设为235,并调整按钮大小和位置,如图:
搭建完成后的最终效果和结构如下:
4.2 添加脚本
4.2.1 添加GameManager脚本
新建GameManager.cs脚本,并将脚本挂载到场景中的GameManager物体上,双击打开脚本,输入以下代码,并给Again按钮添加点击绑定事件OnAgainBtnClicked。
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;public class GameManager : MonoBehaviour
{public static GameManager Instance { get; private set; } // GameManager 单例public Text m_scoreText;public GameObject m_ameOverPanel;public int m_score = 0; // 得分private void Awake(){Instance = this;}void Start(){m_scoreText = GameObject.Find("Score").GetComponent<Text>();m_ameOverPanel = GameObject.Find("GameOver");}public void AddScore(int score){m_score += score;m_scoreText.text = m_score.ToString();}public void GameOver(){m_ameOverPanel.SetActive(true);}public void OnAgainBtnClicked(){ SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);}}
我们在敌人销毁的时候增加分数。打开敌人脚本Enermy.cs,添加增加分数代码:
using UnityEngine;public class Enermy : MonoBehaviour
{......// 触发器事件private void OnTriggerEnter(Collider other){if (other.tag.CompareTo("PlayerRocket") == 0){Rocket rocket = other.GetComponent<Rocket>();if (rocket != null){m_life -= rocket.m_power; // 根据玩家子弹威力减少生命if (m_life <= 0){Destroy(gameObject); // 生命为0,销毁GameManager.Instance.AddScore(m_point); // 增加分数}}}else if(other.tag.CompareTo("Player") == 0){ m_life = 0;Destroy(gameObject);}}
}
当玩家生命值为0的时候,游戏结束,打开游戏结束面。我们打开Player.cs 脚本。
using UnityEngine;public class Player : MonoBehaviour
{......private void OnTriggerEnter(Collider other){if (other.tag.CompareTo("PlayerRocket") == 0){m_life -= 1;if (m_life <= 0){Destroy(gameObject);GameManager.Instance.GameOver();}}}
}
好了,到此为止,我们就可以愉快的玩游戏了,快去试试吧。
五、结束语
好了,本次的案例到这里就结束了,虽然还有很多不足,但作为初学者的练习案例,也用到了许多Unity的基础知识,希望您能够获得一些启发,对您的学习有所帮助。您也可以发挥自己的想想力,完善本案例,比如音效等等,使游戏更加丰富。