Unity Documentation全课程视频第15,24章视频

afanihao Unity入门,全课程内容个人学习笔记,简单部分一笔带过,重点内容带

2.3 窗口布局

  • Unity默认窗口布局

    • Hierarchy 层级窗口
    • Scene 场景窗口,3D视图窗口
    • Game 游戏播放窗口
    • Inspector 检查器窗口,属性窗口
    • Project 项目窗口
    • Console 控制台窗口
  • 恢复默认布局 Window | Layouts | Default

  • 调大页面字体 Preference | UI Scaling

3.1 场景

新项目默认创建了 SampleScene 场景 {摄像机,平行光}

3.2 游戏物体

SampleScene 里 {摄像机,平行光} 就是两个游戏物体

添加物体

  • GameObject 下拉菜单
  • Hierarchy 窗口 右键菜单

选中物体(橙色轮廓)(Inspector显示该物体组件属性)

  • Scene 窗口选中
  • Hierarchy 窗口选中 (物体重叠时

重命名、删除物体

  • Hierarchy 窗口选中右键菜单 Rename | Delete

移动物体

  • Move Tool

3.3 3D视图

视图内容

  • Gizmo 导航器 :表示世界坐标方向
  • Grid 栅格 : 表示 XZ 坐标平面(可隐藏、配置)
    • 栅格1格长度代表1个单位,尺寸单位约定为1米
  • Skybox 天空盒(可隐藏)

视图操作

  • 旋转 ALT + LMB
  • 缩放 鼠标滚轮、ALT + RMB(精细)
  • 平移 MMB

导航器操作 Gizmo

  • 恢复y轴方向:SHIFT+点击小方块
  • 顶视图:点击任意轴 (小方块右键菜单)

3.4 世界坐标系

左手坐标系,当x轴向右,y轴向上,z轴向里

3.5 视图中心

视图旋转默认按视图中心点旋转

  • 绕一个物体旋转,需要选中物体后按 F 键(层级窗口双击物体),视图中心设置为该物体坐标原点,然后 ALT + LMB 旋转
  • 添加一个新物体,物体位于视图中心,而不是 {0,0,0}

3.6 透视与正交

  • Perspective 透视视图

    • 物体近大远小
    • 透视畸(ji)变:圆球在角落看起来像椭圆
      • 调小Field of view(广角设定)减少畸变
  • Orthographic 正交视图(Isometric 等距视图)
    • 物体大小与距离无关
    • 常用于物体的布局、对齐操作

4.2 物体操作

可以在 Inspector 窗口拖动 X Y Z

  • Move tool 移动工具(W):沿着坐标轴、坐标平面移动
  • Rotate tool 旋转工具(E)
    • 朝XYZ轴方向旋转,逆时针为正,顺时针为负。反之相反
    • 按住 CTRL 旋转,按 15 度增量旋转(可修改)
  • Scale tool 缩放工具(R):沿着轴向、整体缩放

操作模式

  • Pivot 轴心 | Center 中心点
  • Global 世界坐标系 | Local 局部坐标系

更多操作

  • 多选(层级窗口,视图窗口鼠标拉框)、复制(CTRL + D)
  • 激活 Active :检查器里第一个勾选项

尝试小插件

主要涉及单c#文件插件(切换视图快捷功能)的安装与使用

  • 拖拽进入资源窗口后自动编译
  • AF插件:G 键的视图中心与F键不同,不会放大框显

5.1 网格

Mesh,存储了模型的形状数据

  • 模型形状由若干个小面围合而成,内部都是中空的
  • Mesh 中包含了 面、顶点坐标、面的法向 等数据

Unity中观察模型网格(场景窗口右侧栏,2D按钮左边)

  • shaded 着色模式,显示表面材质
  • wireframe 线框
  • shaded wireframe 线框着色(两个都显示)

高模:面数越多,物体表面越精细,GPU负担也就越重

mesh filter 组件定义网格数据

5.2 材质

Material 定义物体的表面细节(颜色,金属,透明,凹陷,突起)

创建、使用材质

  1. 在资源目录下创建 Material
  2. 修改阿贝多albedo为蓝色(反射率)
  3. 选中物体,把材质拖到物体上

mesh renderer 组件负责渲染,使用材质相当于修改该组件的 Materials 字段,可直接拖动材质到该字段或打开材质浏览器。 (检查器窗口右上角可锁定)

5.3 纹理(贴图)

用一张图定义物体的表面颜色。模型每个面有不同颜色,与贴图映射,在建模软件里完成

将贴图拖动至 albedo,可以看到叠加的效果(反射率改为白色),按 BackSpace 清掉贴图

建模师提供的模型,本身已经带了网格、表面材质、材质贴图

5.5 更多细节

  • Unity 平面(plane)是没有厚度的;正面可见,背面透明;从正方体从内部观察,六个面都是透明的
  • 添加物体默认都是有材质的 Default-Material (引擎内部自带),呈现紫红色说明没材质

5.6 FBX

模型资源

FBX模型一般包含 mesh(定义形状),material(定义光学特性),texture(定义表面像素颜色),有的模型可能定义多个材质。将FBX模型拖动至窗口中生成对象(FBX本身也是一种预制体

贴图文件的路径是约定好的,与fbx相同目录,或者同级 Textures 目录

材质替换(重映射)

  • 在检查器窗口找到材质属性 | Use Embeded Materials | On Demand Remap
  • 使用外部材质:材质属性 | Location | Use External Materials | 修改解压的材质

分解重组

  • FBX里的Mesh单独拖出生成对象,然后给定材质(也可从FBX单独拖出)

6.1 资源文件

复制资源 CTRL + D

  • 模型文件 .fbx
  • 图片文件 .jpg、.png、.psd、.tif
  • 音频文件 .mp3、.wav、.aiff
  • 脚本文件 .cs
  • 材质文件 .mat
  • 场景文件 .unity (记录物体检查器数据)(1个场景等于1个关卡)
  • 描述文件 .meta (每个文件都有)

除此之外,可将选择的文件导出成资源包 .unitypackage ,导出时可把依赖文件一并导出。再通过 .unitypackage 导入 (整个Assets目录也可以导出)

7.1 轴心、几何中心

Pivot 物体操纵基准点,可以在任意位置,轴心点是在建模软件中指定的,可以用空物体当父节点修改原轴心

Center 几何中心点,一个物体绕中心点旋转(炮塔例子)。多个物体则是物体合体之后的中心点

7.2 父子关系

在 Hierarchy 窗口呈现两个物体之间的关系(拖物体B到物体A下)

  • 子物体会随着父物体移动旋转(子物体相对坐标不会变化)
  • 删除父物体,子物体一并删除

相对坐标:子物体坐标相对于父物体(子物体坐标等于相对坐标+父节点坐标)

7.3 空物体

  • Create Empty
  • 场景内不可见(无网格信息),但有transform组件
  • 用于节点的组织、管理(武器站 + 炮塔)(修改轴心);标记位置

7.4 Global、Local

  • Global,世界坐标系:上下、东西、南北
  • Local,本地坐标系:上下、前后、左右 (物体自身为轴)(小车沿车头前进)
  • y 轴 up、z 轴 forward (模型正脸方向与z轴方向一般一致)、x轴 right

8.1 组件

物体节点可绑定多个组件(component ),一个组件代表一个功能

如 Mesh Filter 网格过滤器(加载Mesh);Mesh Renderer 网格渲染器(渲染Mesh)

Transform 所有物体都有、不能被删除(基础组件)

  • 位置(相对位置);旋转(欧拉角);缩放

8.5 摄像机

  • Z轴为拍摄方向
  • 摆放摄像机:选中节点 | GameObject | Align with View 对齐视角(CTRL+SHIFT+F),将摄像机视角变为当前场景窗口视角

9.1 脚本

脚本组件

脚本组件,游戏驱动逻辑,类名和文件名需要一致。编译过程是自动的

只有挂载脚本才能被调用:物体节点添加组件拖动到检查器窗口下面

脚本类继承自 MonoBehaviour

获取物体

  • this 当前脚本组件对象
  • this.gameObject 当前物体
  • this.gameObject.name 当前物体名字(利用获取到的物体对象获取其他属性)
  • this.gameObject.transform 获取 transform 组件(简化为this.transform

GameObject obj = this.gameObject;
string objName = obj.name;
Debug.Log(objName); Transform tr = this.transform; // this.gameObject.transform
Vector3 pos = tr.position;
Debug.Log(pos);

物体坐标

一般常使用 localPosition ,与检查器中的值一致

  • 世界坐标值 this.transform.position
  • 本地坐标值 this.transform.localPosition

修改本地坐标

this.transform.localPosition = new Vector3(0f, 0f, 3.5f);

移动物体

建议先看 10.1 帧更新

不使用 Time.deltaTime 来移动物体是不匀速的(因为时间增量不同)

正确移动方法是 速度 * 时间(每秒走固定米数,每帧移动距离不同)

  void Update()
{
Vector3 pos = this.transform.localPosition;
pos.z += Time.deltaTime * 10f;
this.transform.localPosition = pos;
}

9.4 播放模式

  • 编辑模式
  • 播放(运行)模式:更改不保存,相当于实时调试,实时修改参数并生效
    • 把修改好参数的组件 | Copy Component | 退出播放 | Paste Component Values

10.1帧更新

  • Frame 游戏帧
  • FrameRate 帧率/刷新率
  • FPS(Frames Per Second) 每秒更新多少帧

Update(帧更新):每帧调用一次

  • Time.time 游戏时间(游戏启动后开始计时)
  • Time.deltaTime 距上次帧更新的时间差(时间增量)

Unity 不支持固定帧率,但可以设置一个近似帧率 Application.targetFrameRate = 60;

11.1物体运动

物体移动

使用 transform.Translate(dx,dy,dz) 实现相对运动(参数是坐标增量)

可以添加第四个参数即 transform.Translate(dx,dy,dz,space)

  • Space.World 世界坐标系(默认)
  • Space.Self 本地坐标系(更常用)

物体方向

GameObject.Find("Sphere") 根据名字、路径查找物体

this.transform.LookAt(flag.transform) 将物体 Z 轴转向某一位置,然后每帧沿着 forward 方向按 2m/s 速度前进

  void Start()
{
GameObject flag = GameObject.Find("Sphere");
this.transform.LookAt(flag.transform);
}
void Update()
{
float speed = 2f;
float distance = speed * Time.deltaTime;
this.transform.Translate(0,0,distance,Space.Self);
}

两物体间距

Vector3 的 magnitude 属性表示向量长度

    Vector3 p1 = this.transform.position;
Vector3 p2 = flag.transform.position;
Vector3 p = p2 - p1;
float distance = p.magnitude;

物体移动到另一物体停止移动

 private GameObject flag;
void Start()
{
flag = GameObject.Find("Sphere");
this.transform.LookAt(flag.transform);
}
void Update()
{
Vector3 p1 = this.transform.position;
Vector3 p2 = flag.transform.position;
Vector3 p = p2 - p1;
float distance = p.magnitude;
if (distance > 0.3f)
{
float speed = 2f;
float dis = speed * Time.deltaTime;
this.transform.Translate(0,0,dis,Space.Self);
}
}

摄像机跟随物体

选择物体,Edit | Lock View to Selected (SHIFT + F)

12.1物体旋转

Quaternion 四元组

transform.rotation 不便操作,不建议使用

Euler Angle 欧拉角

  • transform.eulerAngles
  • transform.LocalEulerAngles
this.transform.localEulerAngles = new Vector3(0, 30, 0);

Vector3 angles = this.transform.localEulerAngles;
angles.y += 30 * Time.deltaTime;
this.transform.localEulerAngles = angles;

transform.Rotate(dx,dy,dz,space)Translate 使用方式类似

实现公转:父物体带动子物体旋转

13.1 脚本运行

场景加载过程(框架自动完成)

  1. 创建节点(游戏物体)
  2. 实例化各个节点的组件(包括脚本组件)| 等同 new 类()
  3. 调用各个组件事件函数

13.2 消息函数

属于 MonoBehaviour (统一行为特性类)的消息函数(事件函数、回调函数)

已被禁用的物体 Start / Update 不会被调用,Awake / Start 方法只会被执行一次

  • Awake 第一阶段初始化(总是会被调用)
  • Start 第二阶段初始化(组件被禁用不调用)
  • Update 帧更新
  • OnEnable 当组件启用时调用
  • OnDisable 当组件禁用时调用

Awake 总被调用是根据当前脚本组件的启用、禁用状态来说的,而不是根据整个物体节点的生效、不生效状态,物体节点如果不生效 Awake 不会被调用

另外,如果脚本组件只有一个 Awake 方法,那么脚本组件的启用、禁用也就没有意义,Unity不为它添加勾选框

13.3 脚本执行顺序

  1. 第一阶段初始化(所有脚本)
  2. 第二阶段初始化(所有脚本)
  3. 帧更新(所有脚本)

脚本执行顺序与层级摆放顺序无关系,默认所有脚本的执行优先级为 0

  • 选中脚本,打开 Execution Order 对话框
  • 添加脚本,值越小,优先级越高

13.4 主控脚本

一个空节点挂载游戏全局设置的脚本(高优先级)

14.1参数与特性

公有类成员变量:让开发者自定义参数从而控制脚本组件功能

添加特性,为参数添加编辑器提示

[Tooltip("这个是Y轴的角速度")]

14.2 初始化顺序

检查器参数、Awake、Start 都对某一参数初始化时的调用顺序

  1. 脚本组件实例化(检查器参数)(默认值
  2. Awake 修改了参数
  3. Start 修改了参数(最终的值

个人认为可以在 Awake、Start 对参数进行验证操作

14.3 值类型

基类类型、Vector3、Color 都是结构体值类型

值类型特点

  • 直接赋值
  • 没值,则默认 0
  • 可空值类型可为 null(个人测试,该类型不显示在检查器上)

14.4 引用类型

节点、组件、资源、数组类型

public GameObject flag;

15.1输入

鼠标输入

在帧更新方法添加,前两方法针对一次鼠标事件只会True一次(成对关系),第三个方法多次True

两个鼠标事件是全局的,脚本之间互不影响

  • Input.GetMouseButtonDown(int) 按下事件

    • 0 左键、1 右键、2 中键
  • Input.GetMounseButtonUp(int) 抬起事件
  • Input.GetMouseButton(int) 鼠标状态,表示当前键否正在被按下

屏幕坐标

获取屏幕长宽

private void Start()
{
int width = Screen.width;
int height = Screen.height;
Debug.Log($"{width} , {height}");
}

获取屏幕坐标

Input.mousePosition 仅X、Y有值,屏幕左下角为原点,单位为像素

if (Input.GetMouseButtonDown(0))
{
Vector3 mousePos = Input.mousePosition;
Debug.Log(mousePos);
}

物体世界坐标转屏幕坐标

用于判断物体是否超出屏幕范围(出界是物体轴心点出界,是能看到物体剩余部分的

Camera.main.WorldToScreenPoint(pos)

X,Y是物体在屏幕的哪个位置,Z是物体距离摄像机的距离

Vector3 pos = this.transform.position;
Vector3 screenPos = Camera.main.WorldToScreenPoint(pos);
if (screenPos.x < 0 || screenPos.x > Screen.width) // 左右边
{
Debug.Log("出界了");
}

键盘输入

与鼠标输入类似

  • Input.GetKeyDown(keycode) 按下事件
  • Input.GetKeyUp(keycode) 抬起事件
  • Input.GetKey(keycode) 按键状态
    • KeyCode.A 常量看官方文档

16.1组件调用

代码操控组件

将代码组件与音乐组件放至同节点(顺序无影响) ;this.GetComponent<AudioSource>() 获取AudioSource组件

void Update()
{
if (Input.GetMouseButtonDown(0))
PlayMusic();
} void PlayMusic()
{
AudioSource audio = this.GetComponent<AudioSource>();
if (audio.isPlaying)
audio.Stop();
else audio.Play();
}

组件引用

情景:用主控脚本控制背景音乐空节点音乐组件的播放

不常用方法:在检查器设置节点引用,脚本通过物体节点获得组件

public GameObject bgmNode;
void Update()
{
if (Input.GetMouseButtonDown(0))
PlayMusic();
}
void PlayMusic()
{
AudioSource audio = bgmNode.GetComponent<AudioSource>();
if (audio.isPlaying)
audio.Stop();
else audio.Play();
}

常用方法:在检查器里设置组件引用,脚本直接访问该组件

public AudioSource bgmComponent;
void Update()
{
if (Input.GetMouseButtonDown(0))
PlayMusic();
}
void PlayMusic()
{
AudioSource audio = bgmComponent;
if (audio.isPlaying)
audio.Stop();
else audio.Play();
}
  • **this.GetComponent<T>() **获取当前物体下的组件
  • xxx.GetComponent<T>() 获取其他物体下的组件

个人理解每个组件类都有 GetComponent<T> 泛型实例方法,用于获取当前绑定的节点的各个组件

代码组件引用

情景:用一个脚本组件控制另一个脚本组件公开字段,如修改转速

可以是API获取,通过物体节点再获取脚本组件类型,也可以直接引用,下面是直接引用做法(Unity框架自动完成组件查找过程)

public class InputLogic : MonoBehaviour
{
public FanLogic fan;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
fan.rotateY = 90f;
}
}
} public class FanLogic : MonoBehaviour
{
public float rotateY;
void Update()
{
this.transform.Rotate(0,rotateY * Time.deltaTime,0,Space.Self);
}
}

消息调用

该方法利用反射机制,效率低,不常用,用于调用其他物体中组件的方法

  • 找到目标节点
  • 向目标节点发送“消息"(字符串,函数名字)

执行过程

  1. 找到节点绑定的所有组件
  2. 在所有组件下寻找方法名对应的方法,找到就执行,找不到就报错
public class FanLogic : MonoBehaviour
{
public float rotateY;
void Update()
{
this.transform.Rotate(0,rotateY * Time.deltaTime,0,Space.Self);
} void DoRotate()
{
rotateY = 90f;
}
} public class InputLogic : MonoBehaviour
{
public GameObject fanNode;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
fanNode.SendMessage("DoRotate");
}
}
}

练习、无人机

逻辑很简单,主控节点引用两个脚本组件,然后根据输入修改这两个脚本组件的状态;代码中通过调用各个组件的公开实例方法来修改字段成员

public class RotateLogic : MonoBehaviour
{
float m_rotateSpeed;
void Update() =>
this.transform.Rotate(0, m_rotateSpeed * Time.deltaTime, 0, Space.Self);
public void DoRotate() => m_rotateSpeed = 360*3;
public void DoStop() => m_rotateSpeed = 0;
} public class FlyLogic : MonoBehaviour
{
float m_speed = 0;
void Update()
{
float height = this.transform.position.y;
float dy = m_speed * Time.deltaTime; if( dy > 0 && height < 4 )
this.transform.Translate(0, dy, 0, Space.Self);
else if ( dy < 0 && height > 0)
this.transform.Translate(0, dy, 0, Space.Self);
} public void Fly ()=> m_speed = 1;
public void Land() => m_speed = -1;
} public class MainLogic : MonoBehaviour
{
public RotateLogic rotateLogic;
public FlyLogic flyLogic; void Start()
{
Application.targetFrameRate = 60;
rotateLogic.DoRotate();
} void Update()
{
if(Input.GetKey(KeyCode.W))
flyLogic.Fly();
else if (Input.GetKey(KeyCode.S))
flyLogic.Land();
}
}

17.1节点操作

名称查找节点

效率低,不适应变化;如果有父节点最好指定一下路径;不存在返回null;不常用,通常用公开字段引用对象的方法;查找的是生效节点,不生效节点不纳入查找范围

void Start()
{
GameObject node = GameObject.Find("无人机/旋翼");
RotateLogic rl = node.GetComponent<RotateLogic>();
rl.DoRotate();
}

如果在最前面加/ 表示从根目录开始查找

查找父级

父子级关系由 Transform 维持

  1. 获取父级Transform,
  2. 通过父级Transform获取父级GameObject
  3. 打印父节点名字
void Start()
{
Transform parent = this.transform.parent;
GameObject parentNode = parent.gameObject;
Debug.Log(parentNode.name); // 等同 transform.name
}

查找子级

transform 实现了迭代器接口可以被 foreach 遍历,拿到多个子节点

void Start()
{
foreach (Transform child in transform)
{
Debug.Log(child.name);
}
}

也可通过 getChild(int) 索引获取,下标从 0 开始。下面获取第二个子节点transform,不存在返回null

Debug.Log(transform.GetChild(2).name);
Debug.Log(transform.GetChild(2) is Transform);

名称查找子级

用法与名称查找节点类似;不存在返回null;与名称查找节点不同的是子级节点不生效,transform也能被查找到

void Start()
{
Transform t = transform.Find("旋翼 (1)/旋翼 (2)");
if (t is null) Debug.Log("Nothing found");
else
Debug.Log(t.name);
}

设置父级

transform组件实例方法 SetParent(node) 设置当前transform的父级,如果传入null则无父节点(根目录节点)

void Start()
{
Transform node = transform.Find("/aa");
transform.SetParent(node);
}

设置生效

生效与不生效;也相当于显示与隐藏;个人理解为(启用当前全部组件,禁用当前全部组件)

GameObject 类型实例的实例方法 SetActive;修改自身节点是否生效

void Start()
{
var obj = transform.gameObject;
Debug.Log(obj.activeSelf);
obj.SetActive(false);
Debug.Log(obj.activeSelf);
}

当前节点修改其他节点是否生效

private GameObject obj;

private void Start() =>
obj = GameObject.Find("aa"); void Update()
{
if (Input.GetMouseButtonDown(0))
{
Debug.Log(obj.activeSelf);
obj.SetActive(!obj.activeSelf);
Debug.Log(obj.activeSelf);
}
}

修改子节点是否生效

void Update()
{
if (Input.GetMouseButtonDown(0))
{
GameObject obj = transform.Find("dd").gameObject;
Debug.Log(obj.activeSelf);
obj.SetActive(!obj.activeSelf);
Debug.Log(obj.activeSelf);
}
}

练习、俄罗斯方块

private int index = 0;

private void Start()
{
foreach (Transform child in transform)
child.gameObject.SetActive(false);
transform.GetChild(index).gameObject.SetActive(true);
} private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))ChangeShape();
} private void ChangeShape()
{
transform.GetChild(index).gameObject.SetActive(false);
index = (index + 1) % transform.childCount;
transform.GetChild(index).gameObject.SetActive(true);
}

18.1资源使用

资源引用

情景:挂一个AudioSource(音频播放)组件,不指定音频AudioClip(音频容器类);要求利用脚本指定播放的资源

注意:在使用 PlayOneShot 实例方法的情况下,音频播放组件的 clip 字段并没有被设定

public AudioClip bgm;

private void Start()
{
var audioSource = GetComponent<AudioSource>();
// audioSource.clip = bgm;
// audioSource.Play();
audioSource.PlayOneShot(bgm);
}

列表引用

情景:挂一个AudioSource组件,不指定音频AudioClip;要求利用脚本随机播放几首歌曲里的一首;

public AudioClip[] bgms;
private void Start()
{
if (bgms.Length == 0)
Debug.Log("我歌呢");
var audioSource = GetComponent<AudioSource>();
// audioSource.PlayOneShot(bgms[Random.Range(0,bgms.Length)]);
audioSource.clip = bgms[Random.Range(0, bgms.Length)];
audioSource.Play();
Debug.Log(audioSource.clip.name);
}

练习、三色球

直接修改颜色也可以直接修改Albedo的颜色(超出入门范畴)

public Material[] ms;
private int index = 0;
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
index = (index + 1) % ms.Length;
Material m = ms[index];
MeshRenderer mr = GetComponent<MeshRenderer>();
mr.material = m;
}
}

19.1定时调用

延迟调用

继承了 MonoBehaviour;利用了反射;delay、interval 是秒不是毫秒

  • Invoke(string func,float delay) 只调用一次
  • InvokeRepeating(string func,float delay,float interval) 首次执行后循环调用
  • IsInvoking(func) 是否在调度队列中
  • CancelInvoke(func) 取消调用、从调度队列中移除

注意:每次 InvokeRepeating,都会添加一个新的调度(加到调度队列),然后大循环每次循环遍历调度队列

private void Start()
{
// Invoke("DoSomething",1);
InvokeRepeating("DoSomething",1,2);
} void DoSomething() =>
Debug.Log("HELLO WORLD " + Time.time);

单线程引擎

Unity 引擎核心是单线程的

private void Start()
{
Debug.Log(Thread.CurrentThread.ManagedThreadId);
InvokeRepeating("DoSomething",1,2);
} private void Update()
{
Debug.Log(Thread.CurrentThread.ManagedThreadId);
} void DoSomething() =>
Debug.Log(Thread.CurrentThread.ManagedThreadId);

终止调度

调用一次 CancelInvoke(string func) 终止了两个同函数的调度;全部都取消不用加参数

private static int COUNT = 1;
private void Start()
{
InvokeRepeating("DoSomething",1,2);
InvokeRepeating("DoSomething",1,2);
} private void Update()
{
if (Time.time > 10)
if (IsInvoking("DoSomething"))
{
CancelInvoke("DoSomething");
Debug.Log("调度被终止了");
}
} void DoSomething()
{
int i = COUNT++;
Debug.Log($"我是任务 {i}");
}

练习、红绿灯

public Material[] colors;
private int index = 0; private void Start()
{
Invoke("ChangeColor",0);
} void ChangeColor()
{
GetComponent<MeshRenderer>().material = colors[index];
index = (index + 1) % colors.Length;
if (index == 1)
Invoke("ChangeColor", 3);
else if(index == 2)
Invoke("ChangeColor", 0.5f);
else
Invoke("ChangeColor", 3);
}

练习、加速减速

public float speedY = 0f;
private int control = 1; private void Start()
{
InvokeRepeating("SpeedControl",0,0.1f);
} void Update()
{
transform.Rotate(0,speedY * Time.deltaTime,0,Space.Self);
if (Input.GetMouseButtonDown(0))
control = -control;
} void SpeedControl()
{
speedY = speedY + 10f * control;
speedY = speedY > 180 ? 180 : speedY;
speedY = speedY < 0 ? 0 : speedY;
}

20.1Vector3

特性

Vector3 是结构体,里面有三个字段 x,y,z,向量是有方向的量,有长度

  • v.magnitude 长度(模长)
  • v.normalized 单位向量标准化(长度为1的向量是单位向量)
    • 向量的每个分量都除以向量的模长
  • 常用静态常量有很多
    • Vector3.zero (0,0,0)
    • Vector3.up (0,1,0)
    • Vector3.right (1,0,0)
    • Vector3.forward (0,0,1)
  • 向量有加减运算(初中知识)


  • 向量有乘法运算

    • 标量乘法:放大倍数
    • 点积:Vector3.Dot(a,b)
    • 叉积:Vector3.Cross(a,b)
  • 不可空值类型不能被设置为 null,可以留空不写,即默认值 0,0,0

public Vector3 speed; // = null;  EXCEPTION

测距

物体之间的距离,确切的说是轴心点之间的距离

  • 向量相减然后求距离
  • 或直接用 Vector3.Distance(Vector3 a,Vector3 b)
public GameObject a;
public GameObject b;
private void Start()
{
Vector3 apos = a.transform.position,bpos = b.transform.position;
float disc = Vector3.Distance(apos, bpos);
Debug.Log(disc);
Debug.Log((apos-bpos).magnitude);
}

物体运动方向

让一个物体沿着某一方向运动(Translate 有多个函数重载方法)

public Vector3 speed;
private void Update()
{
transform.Translate(speed * Time.deltaTime,Space.Self);
}

21.1预制体

创建

预先制作好的物体节点(模板),*.prefab

样本节点做好后,拖到资源目录下,会生成预制体文件。原始样本节点可以删除

prefab 文件只记录了节点的信息;不包含材质、贴图数据,仅包含引用(导出时会将依赖一并导出)

实例

  • 预制体生成的节点实例在层级窗口是蓝色
  • 右键菜单有预制体相选项 | 检查器窗口上面有预制体相关选项
  • 预制体生成的节点实例可以Unpack断开与预制体的链接,后续预制体的修改不会影响该节点

编辑

  • 单独编辑

    • 双击预制体 | 点击 Scenes 或返回箭头退出
  • 原位编辑
    • 选择预制体实例节点,点击层级管理器右侧箭头或检查器上的Open,此时仅选中的物体被编辑,其余物体是陪衬 | 点击返回箭头退出
    • 有 normal/gray/hidden 三种显示模式
  • 覆盖编辑
    • 修改预制体实例节点后,点击检查器上的 Overrides | 这个操作也可以撤销节点修改

22.1动态创建实例

UnityEngine.Object.Instantiate(Object perfab,Transform parent) 有多个重载版本

创建预制体实例后,应做初始化

  • parent 父节点(方便管理控制)
  • position 、localPosition 位置
  • eulerAngles / localEulerAngles 旋转
  • Script 自带的脚本组件

public GameObject bulletPrefeb;
public GameObject bulletFolder;
public GameObject Canno;
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
GameObject obj = Instantiate(bulletPrefeb, null);
obj.transform.SetParent(bulletFolder.transform);
obj.transform.position = transform.position;
obj.transform.eulerAngles = Canno.transform.eulerAngles;
// obj.transform.rotation = Canno.transform.rotation;
obj.GetComponent<BulletLogic>().speed = 0.5f;
}
}

22.3销毁实例

比如 22.1 的子弹案例

  • 飞出屏幕,销毁
  • 按射程、飞行时间销毁
  • 击中目标,销毁

UnityEngine.Object.Destroy(GameObject obj)

  • 不要写成 Destroy(this) ,这相当于删除当前组件
  • Destroy不会立即执;即创建出来实例的Start方法在Update执行完后才会执行

public class BulletLogic : MonoBehaviour
{
public float speed;
public float maxDistance;
void Start()
{
Debug.Log("Start Start");
float lifetime = 1;
if (speed > 0) lifetime = maxDistance / speed;
Destroy(gameObject,lifetime);
Debug.Log("Start Finish");
} void Update() =>
this.transform.Translate(0, 0, speed, Space.Self);
} public class SimpleLogic : MonoBehaviour
{
public GameObject bulletPrefeb;
public GameObject bulletFolder;
public GameObject Canno;
public float speed = 0.5f;
public float flyTime = 2f;
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
GameObject obj = Instantiate(bulletPrefeb, null);
Debug.Log("Instantiate Start");
obj.transform.SetParent(bulletFolder.transform);
obj.transform.position = transform.position;
obj.transform.eulerAngles = Canno.transform.eulerAngles;
// obj.transform.rotation = Canno.transform.rotation;
obj.GetComponent<BulletLogic>().speed = speed;
obj.GetComponent<BulletLogic>().maxDistance = speed * flyTime;
Debug.Log("Instantiate Finish");
}
}

练习炮口旋转

官方建议不要获取对象欧拉角再覆盖欧拉角,涉及转换的一些问题

而是固定用一个Vector3当做对象的欧拉角

private Vector3 _eulerAngles;
public float rotateSpeed = 30f;
public GameObject Canno;
void Update()
{
float delta = rotateSpeed * Time.deltaTime;
if (Input.GetKey(KeyCode.W))
if (_eulerAngles.x > -60)
_eulerAngles.x -= delta;
if (Input.GetKey(KeyCode.S))
if (_eulerAngles.x < 30)
_eulerAngles.x += delta;
if (Input.GetKey(KeyCode.A))
_eulerAngles.y -= delta;
if (Input.GetKey(KeyCode.D))
_eulerAngles.y += delta;
Canno.transform.localEulerAngles = _eulerAngles;
}

23.1简单物理

刚体与碰撞体

刚体 RigidBody

使物体具有物理学特性。添加刚体组件后由物理引擎负责刚体的运动

碰撞体组件 Collider

设置物体的碰撞体积范围。也由物理引擎负责

默认添加的碰撞体一般情况下会根据网格自动设置尺寸,可以另外编辑

反弹与摩擦

通过物理材质,设置一些参数后将该物理材质赋给碰撞体组件的物理材质引用

运动学刚体

RigidBody 组件参数 Is Kinematic 打勾,此时为运动学刚体

零质量,不会受重力影响,但可以设置速度来移动;这种运动刚体完全由脚本控制

碰撞检测

需满足以下两个条件

  • 物体是运动刚体
  • 碰撞体开启了 Is Trigger
  • 物理引擎只负责探测(Trigger),不会阻止物体或者反弹
  • 物理引擎计算的是 Collider 之间的碰撞,和物体自身形状无关
  • 当检测到碰撞时,调用当前节点多个事件消息函数,如 OnTriggerEnter
public Vector3 speed;
void Update() =>
transform.Translate(speed * Time.deltaTime,Space.Self); private void OnTriggerEnter(Collider other)
{
Debug.Log("发生碰撞");
Debug.Log(other.name);
}

练习、子弹销毁物体

给子弹添加如下代码

private void OnTriggerEnter(Collider other)
{
Debug.Log(other.name);
Destroy(other.gameObject);
Destroy(gameObject);
}


25.1射击游戏

天空盒

Window | Rendering | Lighting (CTRL + 9)

子弹

public class BulletLogic : MonoBehaviour
{
public float speed = 1f; void Update()
{
transform.Translate(0,0,speed * Time.deltaTime,Space.Self);
}
private void OnTriggerEnter(Collider other)
{
if (!other.name.StartsWith("怪兽")) return;
Destroy(other.gameObject);
Destroy(gameObject);
}
}

发射与移动

public class PlayerLogic : MonoBehaviour
{
public GameObject bulletPrefeb;
public GameObject bulletFolder;
public Transform firePos;
public Transform fireEulerAngles;
public float speed = 15f;
public float lifeTime = 3f;
public float interval = 2f;
private float _interval = 2f;
public float moveSpeed = 15f;
private void Update()
{
_interval += Time.deltaTime;
if (Input.GetMouseButtonDown(0) && _interval > interval)
{
_interval = 0f;
GameObject obj = Instantiate(bulletPrefeb, null);
obj.transform.SetParent(bulletFolder.transform);
obj.transform.position = firePos.position;
obj.transform.eulerAngles = fireEulerAngles.eulerAngles;
obj.GetComponent<BulletLogic>().speed = speed;
obj.GetComponent<BulletLogic>().lifeTime = lifeTime;
}
if(Input.GetKey(KeyCode.A))
transform.Translate(-Time.deltaTime * moveSpeed,0,0,Space.Self);
if(Input.GetKey(KeyCode.D))
transform.Translate(Time.deltaTime * moveSpeed,0,0,Space.Self);
}
}

怪兽生成器

public class CreatorLogic : MonoBehaviour
{
public GameObject enemyPrefeb;
void Start()
{
InvokeRepeating("CreateEnemy",1f,1f);
} void CreateEnemy()
{
GameObject obj = Instantiate(enemyPrefeb,transform);
var pos = transform.position;
pos.x += Random.Range(-30, 30);
obj.transform.position = pos;
obj.transform.eulerAngles = new Vector3(0, 180, 0);
}
}

添加爆炸特效

public GameObject explosionPrefeb;
...
private void OnTriggerEnter(Collider other)
{
if (!other.name.StartsWith("怪兽")) return;
Destroy(other.gameObject);
Destroy(gameObject);
GameObject obj = Instantiate(explosionPrefeb, null); // 不要挂载子弹节点下面
// 粒子特效播放完会自毁
obj.transform.position = transform.position;
}

Unity 游戏开发、01 基础篇 | 阿发入门篇全课程学习笔记的更多相关文章

  1. Unity游戏开发面试基础知识

    面试第一次知识总结: 一.Unity基本操作 1.unity提供哪几种光源? 点光源.平行光.聚光灯.区域光. 2.物体发生碰撞的必要条件什么? 两个物体必须有碰撞体Collider组件,一个物体上必 ...

  2. opencv图像处理基础 (《OpenCV编程入门--毛星云》学习笔记一---五章)

    #include <QCoreApplication> #include <opencv2/core/core.hpp> #include <opencv2/highgu ...

  3. C# Unity游戏开发——Excel中的数据是如何到游戏中的 (四)2018.4.3更新

    本帖是延续的:C# Unity游戏开发--Excel中的数据是如何到游戏中的 (三) 最近项目不算太忙,终于有时间更新博客了.关于数据处理这个主题前面的(一)(二)(三)基本上算是一个完整的静态数据处 ...

  4. 2017年Unity游戏开发视频教程(入门到精通)

    本文是我发布的一个Unity游戏开发的学习目录,以后我会持续发布一系列的游戏开发教程,都会更新在这个页面上,适合人群有下面的几种: 想要做独立游戏的人 想要找游戏开发相关工作的人 对游戏开发感兴趣的人 ...

  5. 【Unity游戏开发】浅谈Lua和C#中的闭包

    一.前言 目前在Unity游戏开发中,比较流行的两种语言就是Lua和C#.通常的做法是:C#做些核心的功能和接口供Lua调用,Lua主要做些UI模块和一些业务逻辑.这样既能在保持一定的游戏运行效率的同 ...

  6. 喵的Unity游戏开发之路 - 轨道摄像机

    前言        很多童鞋没有系统的Unity3D游戏开发基础,也不知道从何开始学.为此我们精选了一套国外优秀的Unity3D游戏开发教程,翻译整理后放送给大家,教您从零开始一步一步掌握Unity3 ...

  7. 喵的Unity游戏开发之路 - 在球体上行走

    很多童鞋没有系统的Unity3D游戏开发基础,也不知道从何开始学.为此我们精选了一套国外优秀的Unity3D游戏开发教程,翻译整理后放送给大家,教您从零开始一步一步掌握Unity3D游戏开发. 本文不 ...

  8. 喵的Unity游戏开发之路 - 游泳

    原文: https://mp.weixin.qq.com/s/-ERFNB1GRZ6UAkHOhP9UQw 很多童鞋没有系统的Unity3D游戏开发基础,也不知道从何开始学.为此我们精选了一套国外优秀 ...

  9. 喵的Unity游戏开发之路 - 多场景:场景加载

    如果丢失格式.图片或视频,请查看原文:https://mp.weixin.qq.com/s/RDVMg6l41uc2IHBsscc0cQ 很多童鞋没有系统的Unity3D游戏开发基础,也不知道从何开始 ...

  10. C# Unity游戏开发——Excel中的数据是如何到游戏中的 (二)

    本帖是延续的:C# Unity游戏开发——Excel中的数据是如何到游戏中的 (一) 上个帖子主要是讲了如何读取Excel,本帖主要是讲述读取的Excel数据是如何序列化成二进制的,考虑到现在在手游中 ...

随机推荐

  1. 在树莓派上实现numpy的conv2d卷积神经网络做图像分类,加载pytorch的模型参数,推理mnist手写数字识别,并使用多进程加速

    这几天又在玩树莓派,先是搞了个物联网,又在尝试在树莓派上搞一些简单的神经网络,这次搞得是卷积识别mnist手写数字识别 训练代码在电脑上,cpu就能训练,很快的: import torch impor ...

  2. Simple Date Format类到底为啥不是线程安全的?

    摘要:我们就一起看下在高并发下Simple Date Format类为何会出现安全问题,以及如何解决Simple Date Format类的安全问题. 本文分享自华为云社区<[高并发]Simpl ...

  3. TIM-BLDC六步换相-串口中断模拟检测霍尔信号换相-软件COM事件解析

    TIM-BLDC六步换相-串口中断模拟检测霍尔信号换相-软件COM事件解析 一.COM事件解析 COM事件简介:COM事件即换相事件只用于高级定时器当中,其主要目的是用在BLDC方波的控制中,用于同时 ...

  4. 数据库系统架构:从HBase到InfluxDB的变革

    目录 数据库系统架构:从 HBase 到 InfluxDB 的变革 2. 技术原理及概念 2.1 基本概念解释 2.2 技术原理介绍 2.3 相关技术比较 3. 实现步骤与流程 3.1 准备工作:环境 ...

  5. java调用WebService(未完成)记录篇

    背景: 因工作需要和一个Sap相关系统以WebService的方式进行接口联调,之前仅听过这种技术,但并没有实操过,所以将本次开发相关的踩坑进行记录 通过一个实例来认识webservice 服务端 首 ...

  6. 学习C++这一篇就够了(提升篇)

    C++中除了面向对象的编程思想外,还有另一种就是泛型编程 主要用到的技术就是模板 模板机制的分类: 函数模板 类模板 函数模板 作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体定制,用虚拟 ...

  7. spring-boot-plus2.7.12版本重磅发布,三年磨一剑,兄弟们等久了,感谢你们的陪伴

    Everyone can develop projects independently, quickly and efficiently! spring-boot-plus是一套集成spring bo ...

  8. 【EF Core】主从实体关系与常见实体关系的区别

    上次老周扯了有关主.从实体的话题,本篇咱们再挖一下,主.从实体之间建立的关系,跟咱们常用的一对一.一对多这些关系之间有什么不同. 先看看咱们从学习数据库开始就特熟悉的常用关系--多对多.一对一.一对多 ...

  9. 【原创】从Ubuntu-base构建ubuntu rootfs系统(以x86_64和arm为例)

    版权声明:本文为本文为博主原创文章,转载请注明出处,博客地址:https://www.cnblogs.com/wsg1100/.如有错误,欢迎指正. 目录 1.介绍 2.目的 2.准备宿主系统 2.1 ...

  10. Pandas: 将dataframe转换为dict

    背景 将Dataframe的每一列数据转换成字典并保存.也就是字段名变为key, 数值变为value. 方案 以下是效果图 参考链接 https://blog.csdn.net/hanyunkaka/ ...