U3D脚本编程与游戏开发

355 阅读6分钟

Lec01 概览

  1. 修改位置
transform.Translate();
transform.position += new Vector3(0, 0, 5* Time.deltaTime);
  1. 输入输出
float v = Input.GetAxis("Vertical");
float h = Input.GetAxis("Horizontal");
  1. 触发器事件
public void OnTriggerEnter(Collider other){}
// 还有 OnTriggerStay, OnTriggerExit
  1. 移动时相机倾斜
Transform c = Camera.main.transform;
Quaternion cur = c.rotation;
Quaternion target = cur * Quaternion.Euler(0, 0, x * 0.1f);
Camera.main.transform.rotation = Quaternion.Slerp(cur, target, 0.5f);

Lec02 基本概念

1. 常用组件

名称功能
Transform提供位置、旋转、缩放、父子关系
Light提供光源
Camera摄像机
Post Processing后期处理,调整画面的色彩、空间、光效、环境光遮蔽等
Mesh Filter网格过滤器,用于指定网格
Mesh Renderer网格渲染器, 渲染网格
Skinned Mesh Renderer皮肤网格渲染器,用于带骨骼的模型 ,网格变形时伸缩面不会断开
Collider各种碰撞体组件
Character Controller内置的角色控制器组件
Rigidbody刚体,物理系统
Particle System粒子系统
Audio Source音源
Audio Listener音频侦听器
Animator动画状态机
Navmesh Agent导航代理
Terrian地形

2. 获取组件

// 通过物体获取
collider = this.GetComponent<SphereCollider>();
collider = GetComponent<SphereCollider>();
// 通过组件获取
collider = transform.GetComponent<SphereCollider>();

物体上任一组件 可以代表物体; transform 字段直接获取 Transform组件

// 获取 多个同名组件
MyScript[] myScripts = GetComponents<MyScript>();

GetComponents 获取组件数组

3. 获取物体

  • GameObject
// 通过名称获取, 未激活的找不到
GameObject player = GameObject.Find("Player");
// 通过标签获取
player = GameObject.FindGameObjectWithTag("Player"); // 查找第一个
GameObject[] players = GameObject.FindGameObjectsWithTag("Player"); // 查找所有
  • transform
方法用途
transform.Find沿着给定路径查找Transform组件,如 transform.Find("../uncle/sister")
transform.GetChild根据序号查找子物体
transform.GetSiblingIndex获得该物体在兄弟节点之间的序号
transform.IsChildOf判断是否是子物体
属性用途
transform.parent获取父物体
transform.root获取根物体
transform.childCount获取子物体总数
  • 直接通过公开变量 引用物体

4. 创建物体

/* 通过 预制体 创建物体 */
Instantiate(prefab, transform); //指定父物体
Instantiate(prefab, null);      // null表示没有父物体,置于场景根节点 
Instantiate(prefab, new Vector3(0,0,0), Quaternion.Indentity);// 指定位置朝向
    
/* 创建 组件 */
myGameObject.AddComponent<Rigidbody>();
    
/* 销毁 */
Destory(myGameObject);        // 调用该方法,会在之后合适时机去销毁
DestoryImmediate();           // 立即销毁 
Destory(myGameObject, 0.8f);  //0.8s后销毁
    
/* Invoke 定时创建 */ 
public GameObject prefab;
public float r = 2.0f;
int c = 0;
void Start()
{
    Invoke("CreatePrefab", 0.5f);
}
void CreatePrefab()
{
    Vector3 pos = new Vector3(r*Mathf.Cos(2*Mathf.PI*c/12), 0.5f, r*Mathf.Sin(2*Mathf.PI*c/12));
    c++;
    Instantiate(prefab, pos, Quaternion.identity);
    if (c < 12)
    {
        Invoke("CreatePrefab", 0.5f);
    }
}

5. 脚本的生命周期
脚本生命周期 MonoBehaviour Lifecycle,
可以理解为脚本从创建到销毁的过程中触发的各种事件。
常用脚本事件:Awake \rightarrow Start \rightarrow FixedUpdate \rightarrow OnCollisionEnter \rightarrow Update \rightarrow 渲染 \rightarrow OnGUI \rightarrow OnDestory

/* 跟随的相机 */
public class FollowCam : MonoBehaviour
{
    public Transform followTarget;
    Vector3 offset;
    void Start()
    {
        offset = transform.position - followTarget.position;
    }
    void LateUpdate()  // 确保被跟随物体移动后,再移动相机
    {
        transform.position = followTarget.position + offset;
    }
}

6. 协程

// 协程实现 定时创建
public GameObject prefab;
public float r = 2.0f;
void Start()
{
    StartCoroutine(CreatePrefabCor());
} 
IEnumerator CreatePrefabCor()
{
    for (int i = 0; i < 12; i++)
    {
        Vector3 pos = new Vector3(r*Mathf.Cos(2*Mathf.PI*i/12),0.5f,r*Mathf.Sin(2*Mathf.PI*i/12));
        Instantiate(prefab, pos, Quaternion.identity);
        yield return new WaitForSeconds(0.5f);
    }       
}

Lec03 物理系统

1. 基本概念

  • 刚体
    Rigidbody组件以及Rigidbody2D组件。已经挂载该组件的物体,不宜通过脚本直接修改位置或朝向,而是通过施加力、修改速度、修改角速度来实现。运动学(Kinematic)则是挂载该组件,但运动不受物理控制。运动学刚体在需要碰撞检测时依旧被系统处理,带来一些性能开销。
  • 休眠
    当一个刚体“停止”一段时间,直到再次受到力的影响之前,物理引擎都不再反复计算其运动,刚体进入休眠状态。刚体的休眠和唤醒一般都是自动进行。
  • 碰撞体
    碰撞体定义物理外形,一个物体可以挂载多个碰撞体组件,也可以通过子物体挂载实现。
    • 分为 静态碰撞体,刚体碰撞体, 动力学刚体碰撞体
    • 两个碰撞体发生碰撞,至少有一个刚体碰撞体。
    • 不需要移动的障碍物做成静态碰撞体,需要移动的做成动力学刚体碰撞体。
    • 两个障碍物之间碰撞,不受外力的做成动力学刚体,受外力的做成普通刚体。
  • 触发器
    碰撞体组件勾选IsTrigger属性。作为触发器的物体不再是物理上的固体,允许其他物体从中穿过。
  • 物理材质
/* Physics Material 可调参数:*/
   Dynamic Friction //动态摩擦系数
   Static Friction  //静态摩擦系数
   Bounciness       //弹性系数
   Friction Combine //与其他物体接触时的摩擦力系数算法
   Bounce Combine   //... 的弹性系数算法
  • 层,物理关节,射线检测,角色控制器

2. 脚本编程

  • 刚体运动
// 修改刚体位置
if (Input.GetKeyDown(KeyCode.Space))
    {   // 实现多段跳跃,跳跃时跳跃方向速度置零,效果更好。
        rigid.velocity = new Vector3(rigid.velocity.x, 0, rigid.velocity.z);
        rigid.AddForce(new Vector3(0, jumpForce, 0));
    }    
  • 射线检测
// Physics.Raycast() 常用重载
bool Raycast(Vector3 origin, Vector3 direction, float maxDistance, int layerMask);
Ray ray = new Ray(Vector3 origin, Vector3 direction);
bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance, int layerMask);
    
// RaycastHit 射线碰撞信息 
Vector3 point = hitInfo.point;        // 碰撞点
Collider coli = hitInfo.collider;     // 碰撞体组件
Transform trans = hitInfo.transform;  // 获取transform组件
Vector3 normal = hitInfo.normal;      // 碰撞点的法线向量
    
// 穿过多个物体的射线
Physics.RaycastAll();
    
// 区域覆盖性射线 Physics.Overlap...
Collider[] OverlapSphere(Vector3 position, float radius, int layerMask);
    
// 射线调试 
Debug.DrawLine(Vector3 start, Vector3 end);
Debug.DrawLine(Vector3 start, Vector3 end, Color color, float duration);
Debug.DrawRay(Vector3 start, Vector3 dir);
Debug.DrawRay(Vector3 start, Vector3 dir, Color color, float duration);
  • FixedUpdate
    与Update不同的是,物理更新的时间间隔默认0.02秒。刚体的运动属于物理更新,因此追随刚体运动的相机应当在FixedUpdate中实现。
  • 更多施加力的方式
// 施加角速度, 刚体存在angularDrag角阻尼,会慢慢停下
rigid.angularVelocity = new Vector3(0,10,0); 
    
// 修改质心, 使用本地坐标系
rigid.centerOfMass = new Vector3(0, -10, 0);
    
// 在给定位置施加力
void AddForceAtPosition(Vector3 force, Vector3 position, ForceMode mode);
// 其中 力的模式,是一个枚举类型,修改让力的作用方式发生变化。
    
// 刚体约束, 在Inspector直接修改,或:
rigid.constraints = RigidbodyConstraints.FreezePositionX | RigidbodyConstraints.FreezeRotationY;

Lec04 数学基础

1. 坐标系

transform.position       //世界坐标系
transform.localPosition  //局部坐标系

Camera.main.ScreenToViewportPoint(Input.mousePosition);  // 鼠标指针在屏幕上的位置

transform.TransformPoint();         // 局部 -> 世界
transform.InverseTransformPoint();  // 世界 -> 局部
transform.Translate(Vector3.forward);  // 物体沿自身前方前行
{
Vector3 worldToLocal = transform.InverseTransformDirection(Vector3.forward);
transform.Translate(worldToLocal);     // 物体沿世界前方前行 ,即z轴
}

原因:Translate方法 默认以局部坐标系 为基准,前者将Vector3.forward默认为自身前方; 后者先将世界前方转换为局部坐标系,再移动。

2. 向量

/* 方法 */
Cross()    // 叉乘
Dot()      // 点乘
Project()  // 投影
Angle()    // 角度
Distance() // 距离

/* 属性 */
x、y、z       // 分量
normalized    // 标准化向量
magnitude     // 长度
sqrMagnitude  // 长度平方

3. 四元数

  • 万向节锁定问题
    欧拉角的3个轴存在嵌套结构,以y\rightarrowx\rightarrowz为例,当中间的轴转动带动最内层的轴,使得内层轴和外层轴重合,从而缺失了一个方向的转动,发生万向节锁定。
  • 四元数
/* 方法 */ 
*       // 相乘表示依次旋转
Euler() // 由欧拉角得到一个四元数
Slerp() // 球面插值
Lerp()  // 插值
LookRotation() // 由向量得到四元数

/* 属性 */
x、y、z、w   // 分量
eulerAngles  // 获得对应的欧拉角
identity     // 获得无旋转的四元数
  • 四元数乘法不满足交换律:
    旋转 * 旋转: 按照旋转的坐标系坐标轴 依次旋转
    旋转 * 朝向 or 朝向 * 旋转: 得到新的朝向
    旋转 * 向量: 得到新的向量
  • Slerp球面插值
static Quaternion Slerp(Quaternion a, Quaternion b, float t);
  • 朝向
/* 物体朝向鼠标拖拽的方向 */
 if (Input.GetKey(KeyCode.Mouse0))
{
    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    RaycastHit hitInfo;
    if (Physics.Raycast(ray, out hitInfo))
    {
        Vector3 target = hitInfo.point - transform.position;
        target.y = transform.position.y;   // 实现 平视
        transform.forward = target;        // 赋值时 自动标准化
    }
}
  • FPS角色控制
// 隐藏鼠标指针
Cursor.visible = false;
// 锁定鼠标到屏幕中央
Cursor.lockState = CursorLockMode.Locked;
__________________________________
// 鼠标控制转向
float mx = Input.GetAxis("Mouse X");
float my = - Input.GetAxis("Mouse Y");         // 左手坐标系
Quaternion qx = Quaternion.Euler(0, mx, 0);
Quaternion qy = Quaternion.Euler(my, 0, 0);
transform.rotation = qx * transform.rotation;  // 绕y 世界坐标系
transform.rotation = transform.rotation * qy;  // 绕x 局部坐标系

Lec05 UI系统

1. Rect Transform 矩阵变换组件
Anchor Presets : 调整控件的对齐方式,选择Stretch拉伸模式,偏移为零时将铺满整个父控件。

2. Image 图片组件
Soure Image : 图片源
Color : 修改图片叠加颜色
Material : 修改图片材质,一般为空
Raycast Target : 射线检测目标
Image Type : 图片类型,Simple、Sliced、Tiled、Filled
Set Native Size : 重置整个图片为原始像素大小
Preserve Aspect : 保留长宽比,图片类型为简单和填充时,勾选该选项在拉伸图片时长宽比例不变

3. Text 文本组件
Horizontal Overflow : 横向超出,分Wrap折行、Overflow超出
Vertical Overflow : 纵向超出,分Truncate丢弃、Overflow超出

4. Button 按钮组件
Interactable : 是否可交互
Transition : 外观状态切换方式
Navigation : 导航顺序
OnClick() 事件

5. Toggle 单选框组件
Is On : 勾选
Group : 单选框组,同一组的单选框只有1个可勾选

6. Slider 滑动条组件
Interactable : 是否可交互
Fill Rect : 填充区域
Handle Rect : 手柄区域
Direction : 滑动条方向
Whole Numbers : 勾选后数值都取整
Value : 滑动条数值

7. Input Field 输入框组件
Charater Limit : 字符数量限制,限制用户输入的最大字符数量
Content Type : 输入格式,Standard、Password、Integer Number、Email Adderss
Line Type : 指定单行或多行
Place Holder : 占位符,提示用的字符
光标选项: Caret Blink Rate闪烁频率、Caret Width、Custom Caret Color
Selection Color : 选中颜色

8. Scroll Rect 组件
Horizontal : 是否可以横向滚动
Vertiacl : 是否可以纵向滚动
Movement Type : 移动方式,有Elastic、Clamped、Unrestricted
Elasticity : 弹性
Inertia : 惯性,开启后指定 Decelaration Rate 减速率
Scroll Sensitivity : 滚动灵敏度
指定滚动条的Visibility : Permanet 始终显示、Auto Hide、Auto Hide And Expand Vierport

9. Canvas 画布组件
Render Mode : 渲染模式

  • Screen Space -Overlay : 画布与场景无关,直接渲染在游戏的最上层。
  • Screen Space -Camera : 整个画布会像普通物体一样被摄像机渲染,而且正好和摄像机视野一样大,贴合画面且具有一定的z轴深度。
  • World Spcae : 画布彻底变成一层普通物体,UI画布整体可以旋转、缩放、移动,适合UI于游戏场景交互的互动式游戏。

10. Canvas Scaler 画布缩放器组件
Scale Match Mode : 屏幕适配模式

  • Match Width or Height : 自行选择优先匹配宽度或高度
  • Expand : 将内容适当放大到满足画布的长或宽的任意一边。
  • Shrink : 将内容一直放大到同时满足长和宽。

9. Event System
17种输入事件。事件的参数有BaseEventData、PointerEventData、AxisEventData,前者是后两者的基类。
通过Event Trigger事件触发器触发输入事件,参数是BaseEventData,可以将其转为派生类:

PointerEventData pevt = bevt as PointerEventData;

为按钮组件添加响应函数:

myBtn = GetComponent<Button>();
myBtn.onClick.AddListener(() => { Debug.Log("click!"); });
接口名称说明
IPointerEnterHandler鼠标进入
IPointerExitHandler鼠标离开
IPointerDownHandler鼠标按下
IPointerUpHandler鼠标抬起
IPointerClickHandler鼠标点击一次
IInitializePotentialDragHandler发现可拖拽物体
IBeginDragHandler开始拖拽
IDragHandler拖拽中
IEndDragHandler停止拖拽
IDropHandler拖拽释放
IScrollHandler鼠标滚轮
IUpdateSelectedHandler选中物体时反复触发
ISelectHandler物体被选择
IDeselectHandler物体被取消选择
IMoveHandler物体移动
ISubmitHandler提交按钮按下
ICancelHandler提交按钮取消

Lec06 动画系统