这是我参与[第五届青训营]笔记创作活动的第十八天
本课堂重点内容:
- 3D 实体搭建
- 相机,光照,天空盒
- 控制与碰撞
- 玩法逻辑与 UI
详细知识点介绍:
3D 实体搭建:
3D 实体
3D游戏是由一个个具有形状的实体组成的。每个实体在空间中存在于特定的位置,有特定的姿态(旋转角度)
3D 实体的位姿态:
- 位置 Position(x,y,z),是一个三维向量坐标
- 旋转 Rotation(x,y,z),是一个三维向量坐标
- 缩放 Scale(x,y,z),是一个三维向量坐标
在 Unity 中,绝大部分情况下,是先缩放,后旋转,最后平移
3D 实体的创建:
- 通过加载 3D 模型创建,如 fbx,obj,gltf
- 通过组合参数化的基本几何体创建
3D 实体的绘制:
- 材质
- 颜色
- 纹理
预制体:
- 将游戏对象保存在工程中,在需要的时候创建出来,这就是预制体
- 预制体存储着一个游戏对象,包括游戏对象的所有组件以及其下的所有子游戏对象
相机,光照,天空盒
相机
- Clear Flag
- 背景颜色
- Culling Mask
- 投影(透视、正交)
光照
- 常用类型:点光源、平行光
- 颜色
- 强度
- 阴影类型
天空盒
- 相机的清除标志设置为“天空盒”
- 窗口 - 渲染 - 照明设置
- 环境 - 天空盒材质
控制与碰撞
为主角飞船添加控制逻辑
- 添加刚体组件
- Add Component > Physics > Rigidbody
- Use Gravity 设置为 false,忽略重力影响
- isKinematic 设置为 true,飞船通过脚本而非力影响运动属性
- 设置 Constraints, 冻结Z轴位移以及 X、Y、Z 轴旋转
- 添加自定义脚本
- Add Component > New Script
MonoBehaviour
MonoBehaviour 是一个基类,所有的 Unity 脚本都派生自该类
Start()在首次调用任何 Update 方法之前在帧上调用 StartUpdate()每帧调用 UpdateFixedUpdate()用于物理计算且独立于帧率。具有物理系统的帧率;每个固定帧率帧调用该函数LateUpdate()在每一次调用 Update 函数后调用OnGUI()系统调用 OnGUI 来渲染和处理 GUI 事件。它实现可以每帧调用多次(每个事件调用一次)OnDisable()该函数在对象被禁用时调用。对象销毁时也会调用该函数OnEnable()该函数在对象变为启用和激活状态时调用
Input.GetAxis() 和输入管理器(InputManager)
- InputManager 是 Unity 设置输入响应方式的管理列表,位置在 Edit > Project Setting > Input 中
添加敌机
- 为每架敌机预制体添加一个刚体
- 建立敌机的脚本 Enemey.cs
- 为每架敌机预制体均添加脚本 Enemey.cs
随机生成敌机
新建一个名为 Main的 C#脚本,绑定到 Main Camera 上
设置标签、图层和物理规则
- 主角(Hero):会与敌机、敌机炮弹、升级道具碰撞,但不会与主角自身的炮弹碰撞
- 主角炮弹(ProjectileHero):与敌机、敌机炮弹碰撞
- 敌机(Enemy):与主角和主角炮弹碰撞
- 敌机炮弹(ProjectileEnemy):只与主角飞船碰撞
- 升级道具(PowerUp):只与主角飞船碰撞
实践练习例子:
主角飞船 Hero.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Hero : MonoBehaviour
{
static public Hero S; // 单例对象
[Header("Set in Inspector")]
public float speed = 30;
// 以下控制飞船运动
public float rollMult = -45;
public float pitchMult = 30;
[Header("Set Dynamically")]
public float shieldLevel = 1;
// Start is called before the first frame update
void Start()
{
if(S==null){
S = this; // 设置单例对象
} else {
Debug.LogError("尝试重复设置 Hero 实例")
}
}
// Update is called once per frame
void Update()
{
// 从 input(用户输入)类中获取信息
float xAxis = Input.GetAxis("Horizontal");
float yAxis = Input.GetAxis("Vertical");
// 基于获取的水平轴和竖直轴信息,修改 transofm.position
Vector3 pos = transofm.position;
pos.x += xAxis * speed * Time.deltaTime;
pos.y += yAxis * speed * Time.deltaTime;
transofm.position = pos;
// 位置变化时让飞船旋转一个角度,让飞船更有动感
transofm.rotation = Quaternion.Euler(yAxis*pitchMult, xAxis*rollMult,0);
}
}
敌机 Enemey.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Hero : MonoBehaviour
{
[Header("Set in Inspector: Enemy")]
public float speed = 10f; // 运动速度 单位为 m/s
public float fireRate = 0.3f; // 发射频率
public float health = 10;
public float score = 100; // 击毁敌机获得的分数
// pos 是一个属性,即行为表现与字段相似的方法
public Vector3 pos {
get {
return(this.transofm.position);
}
set {
this.transofm.position = value;
}
}
// Update is called once per frame
Void Update()
{
Move();
}
public virtual void Move() {
Vector3 tempPos = pos;
tempPos.y -= speed * Time.deltaTime;
pos = tempPos;
}
}
随机生成 Main
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement; // 用于加载和重载场景
public class Hero : MonoBehaviour
{
private float camWidth; // 游戏界面呈现的相机宽度
private float camHeight; // 游戏界面呈现的相机高度
static public Main S; // Main 单例
[Header("Set in Inspector")]
public GameObject[] prefabEnemies; // Enemy 预设数组
public float enemySpawnPerSecond = 0.5f; // 每秒产生的敌机数量
public float enemySpawnPadding = 1.5f; // 填充敌机距离地图左右边界的位置
// Start is called before the first frame update
void Start()
{
S = this;
camHeight = Camera.main.orthographicSize; // 只有正交投影下有效
camWidth = camHeight * Camera.main.aspect;
// 调用一次 SpawnEnemy() 默认值是每两秒生成一个
Invoke("SpawnEnemy", 1f/enemySpawnPerSecond);
}
public void SpawnEnemy() {
// 随机选取一架敌机预设,实例化
int ndx = Random.Range(0, prefabEnemies.Length);
GameObject go =
Instantiate<GameObject> (prefabEnemies[ndx]);
// 使用随机生成的 x 坐标,将敌机置于屏幕上方
Vector3 pos = Vector3.zero;
float xMax = camWidth - enemySpawnPadding;
float xMin = -camWidth + enemySpawnPadding;
pos.x = Random.Range(xMin, xMax);
pos.y = camHeight + enemySpawnPadding;
go.transform.position = pos;
// 再次调用 SpawnEnemy()
Invoke("SpawnEnemy", 1f/enemySpawnPerSecond);
}
个人课后总结:
- 理解实时游戏 update 的时序控制
- 通过Input.GetAxis()与InputManager 监听玩家输入
- Instantiate() 动态生成游戏实体实例
- Camera.main 获得相机参数,设置场景中的物体在画面中的位置
- 利用标签、图层管理器区分实体种类并设置物理规则
- 通过 Collider 组件与 OnTrigger 函数添加碰撞事件
- 通过图形用户界面管理游戏界面