本练习项目主要用来了解Unity的基本使用方法,包括创建游戏体、键盘、鼠标操作,基本的物理碰撞、UI显示和逻辑处理等知识。
1. 导入资源并创建背景
- 可通过直接复制粘贴的方式导入,或者在Project窗口中单击鼠标右键,选择【Import New Asset】进行导入。还可以在文件管理系统中,直接将资源拖入Assets文件夹中。
- 直接创建【Plane】的背景,可将材质球拖到【Inspector】中Materials下的Element 0,来作为背景的材质。(注意将材质球的Rendering Mode设为Cutout,这样才能显示出透明效果)
- 在【Window】->【Rendering】->【Lighting Settings】中更改Skebox(天空盒)为纯色;然后在场景中心添加点光源,调节灯光强度到合适。
2. 创建主角和子弹
- 将飞船模型拖入场景,创建主角的脚本,添加响应鼠标或键盘按下的逻辑处理。
- 将子弹模型拖入场景,并创建脚本,按一定速度往飞船面向的方向移动,其中实现OnBecameInvisible函数(当可渲染的物体离开可视范围,此函数会被自动触发),该函数作用为在子弹超出屏幕时,进行销毁。
- 将子弹作为Prefab(预制体),通过飞船脚本进行子弹的创建。(通过Instantiate函数进行子弹的实例化)
3. 创建敌人和敌人生成器
- 将敌人模型拖入场景,并创建脚本,定义敌人蛇皮走位,不过整体方向得向着飞船移动,其中还可以定时创建子弹(通过Instantiate函数),需要注意的是,子弹要向着主角方向发射。(Quaternion.LookRotation(主角位置-自身位置))
通过协程的方式进行创建敌人,代码如下:
[AddComponentMenu("GameScripts/EnemySpawn")]
public class EnemySpawn: MonoBehavior {
public Transform m_enemyPrefab;
void Start() {
StartCoroutine(SpawnEnemy()); // 执行协程函数
}
IEnumerator SpawnEnemy() {
while () { // 循环创建敌人
yield return new WaitForSeconds(Random.Range(5, 15)); // 随机等待5-15秒
Instantiate(m_enemyPrefab, transform.position, Quaternion.identity); // 生成敌人实例
}
}
}
4. 物理碰撞
- 给主角、敌人、子弹添加碰撞体:在菜单栏中选择【Component】->【Physics】->【Box Collider】,并选中【Is Trigger】复选框;
- 给主角、敌人、子弹添加碰撞体:在菜单栏中选择【Component】->【Physics】->【Rigidbody】,取消选择【Use Gravity】复选框来避免受到重力影响,选中【Is Kinematic】使游戏体的运动不受物理模拟影响。
对于所设置的非预制体的游戏体,可在Inspector窗口的右下角单击【Apply】按钮,则原始游戏体的Prefab和使用该Prefab的游戏体会自动更新设置。
5. 声音与特效
- 给主角(或敌人)添加Audio Source组件:【Component】->【Audio】->【Audio Source】;
- 选择爆炸特效的Prefab,为其添加Audio Source组件,然后将爆炸音效文件指定给Audio Source的Audio Clip中。由于默认【Play On Awake】处于选中状态,故当爆炸特效被实例化后,会自动播放爆炸音效。
- Audio Source中【Spatial Blend】的值默认为0,表示音效为2D音效,不会受到空间环境影响,若设为1,音效则变为3D音效。
通过以下代码进行播放:
public AudioClip m_shootClip; // 声音文件
public Transform m_explosionFX; // 爆炸特效
void Update() {
// 执行其他逻辑
// 发射子弹时,播放一次射击声音
AudioSource m_audio = this.GetComponent<AudioSource>();
m_audio.PlayOneShot(m_shootClip);
// 主角(或敌人)死亡后,进行销毁(或隐藏),在销毁(或隐藏)之前,实例化爆炸特效(爆炸特效是一个Prefab)
if (m_life <= 0) {
Instantiate(m_explosionFX, this.transform.position, Quaternion,identity);
Destroy(this.gameObject);
}
}
6. 游戏UI及战斗管理
对于游戏UI,需要注意的是,将Canvas的渲染模式改成:摄像机空间。
创建GameManager脚本,其中有个静态实例Instance,在Start(或Awake)函数中指向自身,以便于在其他类的对象中引用GameManager实例。
其中,GameManager控制UI界面的显示逻辑,并循环播放背景音乐(将Audio Source的loop属性设为true),还通过SceneManager.LoadScene来读取关卡。
在读取下一关卡时,当前关卡的游戏体都会被销毁,若希望保留一些游戏变量的值,可将这类变量设置为static。
最后,创建空游戏,并为其指定GameManager脚本组件即可。
7. 关卡跳转
在菜单栏中选中【File】->【Build Settings】,添加关卡。
8. 用鼠标控制主角
- 选择【GameObject】->【3D Object】->【Quad】,创建一个平面物体,设置其位置和尺寸来铺满整个屏幕。然后取消物理组件【Mesh Collider】中【Mesh Render】的选中,从而隐藏平面物体的显示。
- 新建一个层,指定给平面物体的Layer,而后面的鼠标操作产生的射线,只与此层中的平面物体碰撞;
在主角脚本中新增两个属性,然后更新Start,并在Update中调用新增的新增MoveTo函数:
protected Vector3 m_targetPos; // 主角所要移动的目标位置
public LayerMask m_inputMask; // 鼠标射线碰撞层(将上面新增的层指定到该属性)
void Start() {
// 其他代码
// 初始化目标位置
m_targetPos = this.transform.position;
}
void MoveTo() {
if (Input.GetMouseButton(0)) {
Vector3 ms = Input.mousePosition; // 获得鼠标屏幕位置
Ray ray = Camera.main.ScreenPointToRay(ms); // 将屏幕位置转为射线
RaycastHit hitinfo; // 用来记录射线碰撞信息
bool iscast = Physics.Raycast(ray, out hitinfo, 1000, m_inputMask);
if (iscast) {
m_targetPos = hitinfo.point; // 如果射中目标,记录射线碰撞点
}
// 使用MoveTowards函数,获得朝目标移动的位置
Vector3 pos = Vector3.MoveTowards(this.transform.position, m_targetPos, m_speed*Time.deltaTime);
// 更新当前位置
this.transform.position = pos;
}
}
9. 发布游戏
- 【Edit】->【Project Settings】->【Player】,设置游戏名称、公司名和图标;
- 不同平台有不同的构建版本选项。其中,在PC平台上,【Resolution】用于设置游戏窗口大小,【Run In Background】可使游戏窗口失去焦点后在后台继续允许,设置【Display Resolution Dialog】为【Enabled】,游戏在每次启动时会显示出一个用于设置显示分辨率的窗口。
- 默认启动Unity游戏会看到Unity商标,若想去掉,则取消选中【Show Splash Screen】。不过该功能只能在Unity的专业版中使用!