1.Unity环境搭建

工程文件夹的作用:Asserts必不可缺,其他都可以重新生成

2.Unity界面基础

Project窗口:

层级窗口快捷键:

Scene窗口:

Game窗口:

Scene工具栏功能:

工具栏:

快捷操作大全:

支持的资源格式:

总结:

3.Unity工作原理

一.Unity的工作机制

关于反射:

什么是反射,什么是new
相信学过C#基础和设计模式的小伙伴都听说过和使用过“实例化”类 这个我们做项目必不可少的过程 那么小伙伴们必不可少的实例化方法一定用过这两种 1.使用配置文件+反射的方法
2.直接new的方法
所以我觉得大家可能也会有相同的疑问:这两个到底有什么区别呢?

两者有什么区别

首先说一下我们最先学到的实例化方法 new():
new对象属于静态编译 也就是说当代码生成EXE文件的时候会将所有模块都编译进去,当你启动这个EXE文件的时候,所有模块就会进行一个加载的过程(初始化)这个过程在小程序中不会有什么区别,但是当生成程序是一个比较大的程序的时候,这个加载过程就会比较吃力了,这样你的程序就会对硬件配置产生额外负荷,这是我们需要避免的。
还有就是new对象是不能调用该类里面私有的东西
最重要的是new对象一定要知道类名,当你不知道类名的时候就没办法进行new对象了
举例:spring框架是事先就写好的框架,他内部的处理并不知道用户要写哪些类,应为那是以后由用他的人来定的,这时候你还能在spring内部去new吗?所以用户在用的时候才去配置文件中配置类路径,这是一个典型反射的例子,在很多面试中就问道了这个问题。(关于这点是从网上大牛借鉴来的,当大家学习框架以后就会明白了)
但是如果在已知类名的情况下还是new的效率更高一些
下面是反射方法:
反射属于动态编译,编译过程中并不会将模块编译进去,这样初始化的时候就会减少负荷,只有在运行过程中,需要哪些模块的时候才进行调用
例如你做了一个网页:里边有听歌,看图片,阅读等功能,动态编译是说,当你打开网页的时候,并不会将三个功能全部进行加载,只会在你点击其中一项的时候才会加载选中的模块供用户使用
动态编译相比较静态编译具有速度快,节省系统资源,利于扩展的优点

举例说明

如果用出去旅行来举例的话,new相当于买了一个自行车 走到哪带到哪 而反射相当于在哪里需要车子,就在哪租一个,不需要一直带在身上

脚本基本规则:

脚本创建规则

MonoBehavior基类

不继承MonoBehavior的类

二.生命周期函数

知识点一 了解帧的概念

Unity 底层已经帮助我们做好了死循环
我们需要学习Unity的生命周期函数
利用它做好的规则来执行我们的游戏逻辑就行了

知识点二 生命周期函数的概念

所有继承MonoBehavior的脚本 最终都会挂载到GameObject游戏对象上
生命周期函数 就是该脚本对象依附的GameObject对象从出生到消亡整个生命周期中
会通过反射自动调用的一些特殊函数

Unity帮助我们记录了一个GameObject对象依附了哪些脚本
会自动的得到这些对象,通过反射去执行一些固定名字的函数

知识点三 生命周期函数

注意:
生命周期函数的访问修饰符一般为private和protected
因为不需要再外部自己调用生命周期函数 都是Unity自己帮助我们调用的

当对象(自己这个类对象)被创建时 才会调用该生命周期函数
类似构造函数的存在 我们可以在一个类对象 该创建 进行一些初始化操作

代码实例:

   protected virtual void Awake()
    {
        //在Unity中打印信息的两种方式
        //1. 没有继承MOnoBehavior类的时候
        //Debug.Log("123");
        //Debug.LogError("出错了!!!!!");
        //Debug.LogWarning("警告!!!");
        //2. 继承了MonoBehavior 有一个线程的方法 可以使用
        print("Awake");
    }

    //对于我们来说 想要当一个对象被激活时 进行一些逻辑处理 就可以写在这个函数
    void OnEnable()
    {
        print("OnEnable");
    }

    //主要作用还是用于初始化信息的 但是它相对Awake来说 要晚一点
    //因为它是在对象 进行第一次帧更新之前才会执行的
    void Start()
    {
        print("Start");
    }
    
    //它主要是用于 进行物理更新 
    //它是每一帧的执行的 但是 这里的帧 和游戏帧 有点不同
    //它的时间间隔 是可以在 project setting中的 Time里去设置的
    void FixedUpdate()
    {
        print("FixedUpdate");
    }

    //主要用于处理游戏核心逻辑更新的函数
    void Update()
    {
        print("Update");   
    }

    //一般这个更新是用来处理 摄像机位置更新相关内容的
    //Update和LateUpdate之间 Unity进了一些处理 处理我们动画相关的更新
    void LateUpdate()
    {
        print("LateUpdate");
    }

    //如果我们希望在一个对象失活时做一些处理 就可以在该函数中写逻辑
    void OnDisable()
    {
        print("OnDisable");    
    }

    void OnDestroy()
    {
        print("OnDestroy");    
    }
}

知识点四 生命周期函数支持继承和多态

Lesson1的脚本如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson1 : MonoBehaviour
{
    //把Awake写成一个虚函数
    protected virtual void Awake()
    {
        print("父类的Awake");
    }
}

Lesson1Son的脚本如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson1Son : Lesson1
{
    //可以重写父类Lesson1的虚函数
    protected override void Awake()
    {
        base.Awake();
        print("子类的Awake");
    }
}

运行:可以看到,父类的Awake和子类的Awake都被执行了,所以生命周期函数支持继承和多态

知识点五:补充:关于继承Mono的类的构造函数

要知道,虽然不建议在继承MonoBehavior的类中写构造函数
但是不意味着不能写,当在继承MonoBehavior的类中写无参构造函数时
会发现在编辑模式下或者运行后,只要该脚本挂载在场景中,那么该无参构造函数是会被自动执行
因为Unity的工作原理中提到的反射机制,实际上Unity通过反射帮助我们实例化了该脚本对象
既然要实例化那么肯定是需要new的,只不过Unity中不需要我们自己new继承了MonoBehavior的类,只要挂载后Unity就帮助我们做了这件事

那么为什么不建议继承MonoBehavior的类写构造函数呢?
1.Unity的规则就是,继承MonoBehavior的脚本不能new只能挂载
2.生命周期函数的Awake是类似构造函数的存在,当对象出生就会自动调用
3.写构造函数反而在结构上会破坏Unity设计上的规范
总结:
如果继承MonoBehavior的脚本想要进行初始化相关,可以在Awake或者Start中进行,搞清这两个生命周期函数的执行时机,根据需求选择在哪里进行初始化。
切记!!继承MonoBehavior的脚本不要new,不要new,不要new!!

知识点六:补充:不同对象的声明周期函数是在同一个线程中执行的吗?

Unity中所有对象上挂载的生命周期函数都是在一个主线程中按先后执行的
Unity会主动把场景上的对象,对象上挂载的脚本都统统记录下来,
在主线程的死循环中,按顺序按时机的通过反射,执行记录的对象身上挂载的脚本的对应生命周期函数

总结

这些生命周期函数 如果你不打算在其中写逻辑 那就不要在这些出生命周期函数
值得一提的是:生命周期函数并不是基类Mono中的成员,Unity会通过函数名反射得到脚本中的生命周期函数,然后在特定时机执行它们

三.Inspector窗口可编辑变量

Inspector显示的可编辑内容就是脚本的成员变量

知识点一 私有和保护无法显示编辑

private int i1;
protected string str1;

知识点二 让私有的和保护的也可以被显示

加上强制序列化字段特性
[SerializeField]
所谓序列化就是把一个对象保存到一个文件或数据库字段中去
[SerializeField]
private int privateInt;
[SerializeField]
protected string protectedStr;

知识点三 公共的可以显示编辑

public int publicInt = 10;
public bool publicBool = false;

知识点四 公共的也不让其显示编辑

在变量前加上特性
[HideInInspector]

[HideInInspector]
public int publicInt2 = 50;

知识点五 大部分类型都能显示编辑

public int[] array;
public List《int》 list;
public E_TestEnum type;
public GameObject gameObj;

字典不能被Inspector窗口显示
public Dictionary<int, string> dic;
自定义类型变量
public MyStruct myStruct;
public MyClass myClass;

知识点六 让自定义类型可以被访问

加上序列化特性
[System.Serializable]
字典怎样都不行

知识点七 一些辅助特性

1.分组说明特性Header
为成员分组
Header特性
  //[Header("分组说明")]
  [Header("基础属性")]
  public int age;
  public bool sex;
  [Header("战斗属性")]
  public int atk;
  public int def;

  //2.悬停注释Tooltip
  //为变量添加说明
  //[Tooltip("说明内容")]
  [Tooltip("闪避")]
  public int miss;

  //3.间隔特性 Space()
  //让两个字段间出现间隔
  //[Space()]
  [Space()]
  public int crit;

  //4.修饰数值的滑条范围Range
  //[Range(最小值, 最大值)]
  [Range(0,10)]
  public float luck;

  //5.多行显示字符串 默认不写参数显示3行
  //写参数就是对应行
  //[Multiline(4)]
  [Multiline(5)]
  public string tips;

  //6.滚动条显示字符串 
  //默认不写参数就是超过3行显示滚动条
  //[TextArea(3, 4)]
  //最少显示3行,最多4行,超过4行就显示滚动条
  [TextArea(3,4)]
  public string myLife;

  //7.为变量添加快捷方法 ContextMenuItem
  //参数1 显示按钮名
  //参数2 方法名 不能有参数
  //[ContextMenuItem("显示按钮名", "方法名")]
  [ContextMenuItem("重置钱", "Test")]
  public int money;
  private void Test()
  {
      money = 99;
  }

  //8.为方法添加特性能够在Inspector中执行
  //[ContextMenu("测试函数")]
  [ContextMenu("哈哈哈哈")]
  private void TestFun()
  {
      print("测试方法");
  }
  #endregion

注意

1.Inspector窗口中的变量关联的就是对象的成员变量,运行时改变他们就是在改变成员变量
public int i = 200;
2.拖曳到GameObject对象后 再改变脚本中变量默认值 界面上不会改变
3.运行中修改的信息不会保存

四.MonoBehavior中的重要内容

知识点一 重要成员

1.获取依附的GameObject
print(this.gameObject.name);
2.获取依附的GameObject的位置信息
得到对象位置信息
print(this.transform.position);//位置
print(this.transform.eulerAngles);//角度
print(this.transform.lossyScale);//缩放大小
这种写法和上面是一样的效果 都是得到依附的对象的位置信息
this.gameObject.transform

3.获取脚本是否激活
this.enabled = false;

获取别的脚本对象 依附的gameobject和 transform位置信息
print(otherLesson3.gameObject.name);
print(otherLesson3.transform.position);

知识点二 重要方法

得到依附对象上挂载的其它脚本

1.得到自己挂载的单个脚本
根据脚本名获取
获取脚本的方法 如果获取失败 就是没有对应的脚本 会默认返回空
Lesson3_Test t = this.GetComponent(“Lesson3_Test”) as Lesson3_Test;
print(t);
根据Type获取
t = this.GetComponent(typeof(Lesson3_Test)) as Lesson3_Test;
print(t);
根据泛型获取 建议使用泛型获取 因为不用二次转换
t = this.GetComponent<Lesson3_Test>();
if( t != null )
{
print(t);
}

只要你能得到场景中别的对象或者对象依附的脚本
那你就可以获取到它的所有信息

2.得到自己挂载的多个脚本
Lesson3[] array = this.GetComponents();
print(array.Length);
List list = new List();
this.GetComponents(list);
print(list.Count);

3.得到子对象挂载的脚本(它默认也会找自己身上是否挂载该脚本(单个优先),而且无论有多少子类都会找)
函数是有一个参数的 默认不传 是false 意思就是 如果子对象失活 是不会去找这个对象上是否有某个脚本的
如果传true 及时 失活 也会找
得子对象 挂载脚本 单个
t = this.GetComponentInChildren<Lesson3_Test>(true);
print(t);
得子对象 挂载脚本 多个

Lesson3_Test[] lts = this.GetComponentsInChildren<Lesson3_Test>(true);
print(lts.Length);

List<Lesson3_Test> list2 = new List<Lesson3_Test>();
this.GetComponentsInChildren<Lesson3_Test>(true, list2);
print(list2.Count);

4.得到父对象挂载的脚本(它默认也会找自己身上是否挂载该脚本(单个优先),而且无论有多少父类都会找)
t = this.GetComponentInParent<Lesson3_Test>();
print(t);
lts = this.GetComponentsInParent<Lesson3_Test>();
print(lts.Length);
它也有list的 省略不写了 和上面是一样的套路

5.尝试获取脚本
Lesson3_Test l3t;
提供了一个更加安全的 获取单个脚本的方法 如果得到了 会返回true
然后在来进行逻辑处理即可
if(this.TryGetComponent<Lesson3_Test>(out l3t))
{
逻辑处理
}

4.Unity重要组件和API

一.最小单位.Gameobject

准备用来克隆的对象
1.直接是场景上的某个对象
2.可以是一个预设体对象
public GameObject myObj;

public GameObject myObj2;

Start is called before the first frame update

知识点一 GameObject中的成员变量

//名字
     print(this.gameObject.name);
     this.gameObject.name = "Lesson4唐老狮改名";
     print(this.gameObject.name);
     //是否激活
     print(this.gameObject.activeSelf);
     //是否是静态
     print(this.gameObject.isStatic);
     //层级
     print(this.gameObject.layer);
     //标签
     print(this.gameObject.tag);
     //transform
     //this.transform 上一节课讲解的 通过Mono去得到的依附对象的GameObject的位置信息
     //他们得到的信息是一样 都是依附的GameObject的位置信息
     print(this.gameObject.transform.position);
     #endregion

知识点二 GameObject中的静态方法

//创建自带几何体
//只要得到了一个GameObject对象 我就可以得到它身上挂在的任何脚本信息
//通过obj.GetComponent来得去
GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
obj.name = “唐老狮创建的立方体”;

//查找对象相关的知识
//两种找单个对象的共同点:
//1.无法找到失活的对象的
// 只能找到 激活的对象

//2.如果场景中 存在多个满足条件的对象
// 我们无法准确确定找到的是谁

//1查找单个对象
//通过对象名查找
//这个查找效率比较低下 因为他会在场景中的所有对象去查找
//没有找到 就会返回null
GameObject obj2 = GameObject.Find(“唐老狮”);
if( obj2 != null )
{
print(obj2.name);
}
else
{
print(“没有找到对应对象”);
}
//通过tag来查找对象
//GameObject obj3 = GameObject.FindWithTag(“Player”);
//该方法和上面这个方法 效果一样 只是名字不一样而已
GameObject obj3 = GameObject.FindGameObjectWithTag(“Player”);
if (obj3 != null)
{
print(“根据tag找的对象” + obj3.name);
}
else
{
print(“根据tag没有找到对应对象”);
}

//得到某一个单个对象 目前有2种方式了
//1.是public从外部面板拖 进行关联
//2.通过API去找

//2查找多个对象
//找多个对象的API 只能是通过tag去找多个 通过名字 是没有找多个的方法的

//通过tag找到多个对象
//它也是 只能找到 激活对象 无法找到失活对象
GameObject[] objs = GameObject.FindGameObjectsWithTag(“Player”);
print(“找到tag为Player对象的个数” + objs.Length);

//还有几个查找对象相关是用的比较少的方法 是GameObject父类 Object提供的方法
//引出额外知识点 Unity中的Object和C#中的万物之父的区别
//Unity里面的Object 不是指的万物之父object
//Unity里的Object 命名空间在UnityEngine中的 Object类 也是集成万物之父的一个自定义类
//C#中的Object 命名空间是在System中的

//它可以找到场景中挂载的某一个脚本对象
//效率更低 上面的GameObject.Find 和通过FindWithTag找 只是遍历对象
//这个方法 不仅要遍历对象 还要遍历对象上挂载的脚本
Lesson4 o = GameObject.FindObjectOfType();
print(o.gameObject.name);

//实例化对象(克隆对象)的方法
//实例化(克隆)对象 它的作用 是根据一个GameObject对象 创建出一个和它一模一样的对象
GameObject obj5 = GameObject.Instantiate(myObj);
//以后学了更多知识点 就可以在这操作obj5
//如果你继承了 MonoBehavior 其实可以不用写GameObject一样可以使用
//因为 这个方法时Unity里面的 Object基类提供给我们的 所以可以直接用
//Instantiate(myObj);

//删除对象的方法
GameObject.Destroy(myObj2);
//第二个参数 代表延迟几秒钟删除
GameObject.Destroy(obj5, 5);
//Destroy不仅可以删除对象 还可以删除脚本
//GameObject.Destroy(this);

//删除对象有两种作用
//1.是删除指定的一个游戏对象
//2.是删除一个指定的脚本对象
//注意:这个Destroy方法 不会马上移除对象 只是给这个对象加了一个移除标识
// 一般情况下 它会在下一帧时把这个对象移除并从内存中移除

//如果没有特殊需求 就是一定要马上移除一个对象的话
//建议使用上面的 Destroy方法 因为 是异步的 降低卡顿的几率
//下面这个方法 就是立即把对象 从内存中移除了
//GameObject.DestroyImmediate(myObj);

//如果是继承MonoBehavior的类 不用写GameObject
//Destroy(myObj2);
//DestroyImmediate(myObj);

//过场景不移除
//默认情况 在切换场景时 场景中对象都会被自动删除掉
//如果你希望某个对象 过场景不被移除
//下面这句代码 就是不想谁过场景被移除 就传谁
//一般都是传 依附的GameObject对象
//比如下面这句代码的意思 就是自己依附的GameObject对象 过场景不被删除
GameObject.DontDestroyOnLoad(this.gameObject);
//如果继承MOnoBehavior也可以直接写
//DontDestroyOnLoad(this.gameObject);
#endregion

知识点三 GameObject中的成员方法

      //创建空物体
      //new一个GameObject就是在创建一个空物体
      GameObject obj6 = new GameObject();
      GameObject obj7 = new GameObject("唐老狮创建的空物体");
      GameObject obj8 = new GameObject("顺便加脚本的空物体", typeof(Lesson2),typeof(Lesson1));

      //为对象添加脚本
      //继承MOnoBehavior的脚本 是不能够去new 
      //如果想要动态的添加继承MonoBehavior的脚本 在某一个对象上
      //直接使用GameObject提供的方法即可
      Lesson1 les1 = obj6.AddComponent(typeof(Lesson1)) as Lesson1;
      //用泛型更方便
      Lesson2 les2 = obj6.AddComponent<Lesson2>();
      //通过返回值 可以得到加入的脚本信息
      //来进行一些处理

      //得到脚本的成员方 和继承Mono的类得到脚本的方法 一模一样  
      
      //标签比较
      //下面两种比较的方法 是一样的
      if(this.gameObject.CompareTag("Player"))
      {
          print("对象的标签 是 Player");
      }
      if(this.gameObject.tag == "Player")
      {
          print("对象的标签 是 Player");
      }

      //设置激活失活
      //false 失活
      //true 激活
      obj6.SetActive(false);
      obj7.SetActive(false);
      obj8.SetActive(false);

      //次要的成员方法 了解即可 不建议使用
      //强调
      //下面讲的方法 都不建议大家使用 效率比较低
      //通过广播或者发送消息的形式 让自己或者别人 执行某些行为方法

      //通知自己 执行什么行为
      //命令自己 去执行这个TestFun这个函数 会在自己身上挂在的所有脚本去找这个名字的函数
      //它会去找到 自己身上所有的脚本 有这个名字的函数去执行
      this.gameObject.SendMessage("TestFun");
      this.gameObject.SendMessage("TestFun2", 199);

      //广播行为 让自己和自己的子对象执行
      //this.gameObject.BroadcastMessage("函数名");

      //向父对象和自己发送消息 并执行
      //this.gameObject.SendMessageUpwards("函数名");
      #endregion
  }

  void TestFun()
  {
      print("Lesson4的TestFun");
  }

  void TestFun2(int a)
  {
      print("Lesson4的TestFun2" + a);
  }

  // Update is called once per frame
  void Update()
  {
      
  }

总结

//GameObject的常用内容

//基本成员变量
//名字 失活激活状态 标签 层级 等等

//静态方法相关
//创建自带几何体
//查找场景中对象
//实例化对象
//删除对象
//过场景不移除

//成员方法
//为对象 动态添加指定脚本
//设置失活激活的状态
//和MonoBehavior中相同的 得到脚本相关的方法

二.时间相关time

Time相关内容主要用来干啥:
时间相关内容 主要 用于游戏中参与位移、记时、时间暂停等

知识点一 时间缩放比例

时间停止
Time.timeScale = 0;
回复正常
Time.timeScale = 1;
2倍速
Time.timeScale = 2;

知识点二 帧间隔时间

帧间隔时间 主要是用来计算位移
路程 = 时间*速度
根据需求 选择参与计算的间隔时间
如果希望 游戏暂停时就不动的 那就使用deltaTime
如果希望 不受暂停影响 unscaledDeltaTime

帧间隔时间:最近的一帧 用了多长时间(秒)
受scale影响
print(“帧间隔时间” + Time.deltaTime);
不受scale影响的帧间隔时间
print(“不受scale影响的帧间隔时间” + Time.unscaledDeltaTime);

知识点三 游戏开始到现在的时间

它主要用来计时 单机游戏中计时
受scale影响
print(“游戏开始到现在的时间:” + Time.time);
不受scale影响
print(“不受scale影响的游戏开始到现在的时间:” + Time.unscaledTime);

知识点四 物理帧间隔时间 FixedUpdate(默认为0.02,可以去projectsetiing设置)

受scale影响
print(Time.fixedDeltaTime);
不受scale影响
print(Time.fixedUnscaledDeltaTime);

知识点五 帧数

从开始到现在游戏跑了多少帧(次循环)
print(Time.frameCount);

三.必不可少Transform

Transform主要用来干嘛:
游戏对象(GameObject)位移、旋转、缩放、父子关系、坐标转换等相关操作都由它处理
它是Unity提供的极其重要的类

Vector3

知识点一 必备知识点 Vector3基础

Vector3主要是用来表示三维坐标系中的 一个点 或者一个向量
申明
Vector3 v = new Vector3();
v.x = 10;
v.y = 10;
v.z = 10;
只传xy 默认z是0
Vector3 v2 = new Vector3(10, 10);
一步到位
Vector3 v3 = new Vector3(10, 10, 10);

Vector3 v4;
v4.x = 10;
v4.y = 10;
v4.z = 10;

Vector的基本计算
运算符号+ - * / (官方已经进行过重载)
Vector3 v1 = new Vector3(1, 1, 1);
Vector3 v12 = new Vector3(2, 2, 2);
对应用x+或者-x…
print(v1 + v12);
print(v1 - v12);
print(v1 * 10);
print(v12 / 2);

常用
print(Vector3.zero);//000
print(Vector3.right);//100
print(Vector3.left);//-100
print(Vector3.forward);//001
print(Vector3.back);//00-1
print(Vector3.up);//010
print(Vector3.down);//0-10

常用的一个方法
计算两个点之间的距离的方法
print(Vector3.Distance(v1, v12));
#endregion

知识点二 位置

相对世界坐标系
this.gameObject.transform
通过position得到的位置 是相对于 世界坐标系的 原点的位置
可能和面板上显示的 是不一样的
因为如果对象有父子关系 并且父对象位置 不在原点 那么 和面板上肯定就是不一样的
print(this.transform.position);

相对父对象
这两个坐标 对于我们来说 很重要 如果你想以面板坐标为准来进行位置设置
那一定是通过localPosition来进行设置的
print(this.transform.localPosition);

他们两个 可能出现是一样的情况
1.父对象的坐标 就是世界坐标系原点0,0,0
2.对象没有父对象

注意:位置的赋值不能直接改变x,y,z 只能整体改变
不能单独改 x y z某一个值
this.transform.position.x = 10;
this.transform.position = new Vector3(10, 10, 10);
this.transform.localPosition = Vector3.up * 10;

如果只想改一个值x y和z要保持原有坐标一致
1.直接赋值
this.transform.position = new Vector3(19, this.transform.position.y, this.transform.position.z);
2.先取出来 再赋值
虽然不能直接改 transform的 xyz 但是 Vector3是可以直接改 xyz的
所以可以先取出来改Vector3 再重新赋值
Vector3 vPos = this.transform.localPosition;
vPos.x = 10;
this.transform.localPosition = vPos;

如果你想得到对象当前的 一个朝向
那么就是通过 trnasform.出来的
对象当前的各朝向
对象当前的面朝向
print(this.transform.forward);
对象当前的头顶朝向
print(this.transform.up);
对象当前的右手边
print(this.transform.right);

知识点三 位移

理解坐标系下的位移计算公式
路程 = 方向 * 速度 * 时间

方式一 自己计算
想要变化的 就是 position

用当前的位置 + 我要动多长距离 得出最终所在的位置
this.transform.position = this.transform.position + this.transform.up * 1 * Time.deltaTime;

因为我用的是 this.transform.forward 所以它始终会朝向相对于自己的面朝向去动
this.transform.position += this.transform.forward * 1 * Time.deltaTime;

方向非常重要 因为 它决定了你的前进方向
this.transform.position += Vector3.forward * 1 * Time.deltaTime;

方式二 API
参数一:表示位移多少 路程 = 方向 * 速度 * 时间
参数二:表示 相对坐标系 默认 该参数 是相对于自己坐标系的

1相对于世界坐标系的 Z轴 动 始终是朝 世界坐标系 的 Z轴正方向移动
this.transform.Translate(Vector3.forward * 1 * Time.deltaTime, Space.World);

2相对于世界坐标的 自己的面朝向去动 始终朝自己的面朝向移动
this.transform.Translate(this.transform.forward * 1 * Time.deltaTime, Space.World);

3相对于自己的坐标系 下的 自己的面朝向向量移动 (一定不会这样让物体移动) XXXXXXX
this.transform.Translate(this.transform.forward * 1 * Time.deltaTime, Space.Self);

4相对于自己的坐标系 下的 Z轴正方向移动 始终朝自己的面朝向移动
this.transform.Translate(Vector3.forward * 1 * Time.deltaTime, Space.Self);

注意:一般使用API来进行位移

总结

Vector3
如何申明 提供的 常用静态属性 和一个 计算距离的方法
位置
相对于世界坐标系 和 相对于父对象 这两个坐标的区别
不能够 单独修改 xyz 只能一起统一改
位移
自己如何计算来进行位移
API是哪个方法 来进行位移 使用时 要注意

Rotation

知识点一 角度相关

相对世界坐标角度
print(this.transform.eulerAngles);

相对父对象角度
print(this.transform.localEulerAngles);

注意:设置角度和设置位置一样 不能单独设置xyz 要一起设置
如果我们希望改变的 角度 是面板上显示的内容 那一点是改变 相对父对象的角度
this.transform.localEulerAngles = new Vector3(10, 10, 10);
this.transform.eulerAngles = new Vector3(10, 10, 10);
print(this.transform.localEulerAngles);

知识点二 旋转相关

自己计算(省略不讲了 和位置一样 不停改变角度即可)

API计算
自转
每个轴 具体转多少度
第一个参数 相当于 是旋转的角度 每一帧
第二个参数 默认不填 就是相对于自己坐标系 进行的旋转
this.transform.Rotate(new Vector3(0, 10, 0) * Time.deltaTime);
this.transform.Rotate(new Vector3(0, 10, 0) * Time.deltaTime, Space.World);

相对于某个轴 转多少度
参数一:是相对哪个轴进行转动
参数二:是转动的 角度 是多少
参数三:默认不填 就是相对于自己的坐标系 进行旋转
如果填 可以填写相对于 世界坐标系进行旋转
this.transform.Rotate(Vector3.right, 10 * Time.deltaTime);
this.transform.Rotate(Vector3.right, 10 * Time.deltaTime, Space.World);

相对于某一个点转
参数一:相当于哪一个点 转圈圈
参数二:相对于那一个点的 哪一个轴转圈圈
参数三:转的度数 旋转速度 * 时间
this.transform.RotateAround(Vector3.zero, Vector3.right, 10 * Time.deltaTime);

总结

角度相关和位置相关 差不多
如何得到角度
通过transform 可以得到相对于世界坐标系和相对于父对象的
如何自转和绕着别人赚
Rotate
RotateAround

其中 欧拉角有一个坑,就是代码获得的值只有0-360!!!

缩放和看向

知识点一 缩放

相对世界坐标系
print(this.transform.lossyScale);
相对本地坐标系(父对象)
print(this.transform.localScale);

注意:
1.同样缩放不能只改xyz 只能一起改(相对于世界坐标系的缩放大小只能得 不能改)
所以 我们一般要修改缩放大小 都是改的 相对于父对象的 缩放大小 localScale
this.transform.localScale = new Vector3(3, 3, 3);

2.Unity没有提供关于缩放的API
之前的 旋转 位移 都提供了 对应的 API 但是 缩放并没有
如果你想要 让 缩放 发生变化 只能自己去写(自己算)

2.Unity没有提供关于缩放的API
之前的 旋转 位移 都提供了 对应的 API 但是 缩放并没有
如果你想要 让 缩放 发生变化 只能自己去写(自己算)
this.transform.localScale += Vector3.one * Time.deltaTime;

知识点二 看向

让一个对象的面朝向 可以一直看向某一个点或者某一个对象
看向一个点 相对于世界坐标系的
this.transform.LookAt(Vector3.zero);
看向一个对象 就传入一个对象的 Transform信息
this.transform.LookAt(lookAtObj);

总结:

缩放相关
相对于 世界坐标系的缩放 只能得 不能改
只能去修改相对于本地坐标系的缩放(相对于父对象)
没有提供对应的API来 缩放变化 只能自己算
看向
LookAt 看向一个点 或者一个对象
一定记住 是写在Update里面 才会不停变化

父子关系

知识点一 获取和设置父对象

获取父对象
print(this.transform.parent.name);
设置父对象 断绝父子关系
this.transform.parent = null;
设置父对象 认爸爸
this.transform.parent = GameObject.Find(“Father2”).transform;

通过API来进行父子关系的设置
this.transform.SetParent(null);//断绝父子关系
this.transform.SetParent(GameObject.Find(“Father2”).transform);//认爸爸

参数一:我的父亲
参数二:是否保留世界坐标的 位置 角度 缩放 信息
true 会保留 世界坐标下的状态 和 父对象 进行计算 得到本地坐标系的信息
false 不会保留 会直接把世界坐标系下的 位置角度缩放 直接赋值到 本地坐标系下
this.transform.SetParent(GameObject.Find(“Father3”).transform, false);

知识点二 抛妻弃子

就是和自己的所有儿子 断绝关系 没有父子关系了
this.transform.DetachChildren();

知识点三 获取子对象

按名字查找儿子
找到儿子的 transform信息
Find方法 是能够找到 失活的对象的 !!!!! GameObject相关的 查找 是不能找到失活对象的
print(this.transform.Find(“Cube (1)”).name);
他只能找到自己的儿子 找不到自己的孙子 !!!!!!
print(this.transform.Find(“GameObject”).name);
虽然它的效率 比GameObject.Find相关 要高一些 但是 前提是你必须知道父亲是谁 才能找

遍历儿子
如何得到有多少个儿子
1.失活的儿子也会算数量
2.找不到孙子 所以孙子不会算数量
print(this.transform.childCount);
通过索引号 去得到自己对应的儿子
如果编号 超出了儿子数量的范围 那会直接报错的
返回值 是 transform 可以得到对应儿子的 位置相关信息
this.transform.GetChild(0);

for (int i = 0; i 《 this.transform.childCount; i++)
{
print(“儿子的名字:” + this.transform.GetChild(i).name);
}

知识点四 儿子的操作

判断自己的爸爸是谁
一个对象 判断自己是不是另一个对象的儿子
if(son.IsChildOf(this.transform))
{
print(“是我的儿子”);
}
//得到自己作为儿子的编号
print(son.GetSiblingIndex());
//把自己设置为第一个儿子
son.SetAsFirstSibling();
//把自己设置为最后一个儿子
son.SetAsLastSibling();
//把自己设置为指定个儿子
//就算你填的数量 超出了范围(负数或者更大的数) 不会报错 会直接设置成最后一个编号
son.SetSiblingIndex(1);

总结

设置父对象相关的内容
获取子对象

抛弃妻子
儿子的操作

坐标转换

知识点一 世界坐标转本地坐标

print(Vector3.forward);

世界坐标系 转本地坐标系 可以帮助我们大概判断一个相对位置

世界坐标系的点 转换 为相对本地坐标系的点
受到缩放影响
print("转换后的点 " + this.transform.InverseTransformPoint(Vector3.forward));

世界坐标系的方向 转换 为相对本地坐标系的方向
不受缩放影响
print(“转换后的方向” + this.transform.InverseTransformDirection(Vector3.forward));
受缩放影响
print(“转换后的方向(受缩放影响)” + this.transform.InverseTransformVector(Vector3.forward));

知识点二 本地坐标转世界坐标

本地坐标系的点 转换 为相对世界坐标系的点 受到缩放影响
print(“本地 转 世界 点” + this.transform.TransformPoint(Vector3.forward));

本地坐标系的方向 转换 为相对世界坐标系的方向
不受缩放影响
print(“本地 转 世界 方向” + this.transform.TransformDirection(Vector3.forward));
受缩放影响
print(“本地 转 世界 方向” + this.transform.TransformVector(Vector3.forward));

总结

其中最重要的 就是 本地坐标系的点 转世界坐标系的点
比如 现在玩家要在自己面前的n个单位前 放一团火 这个时候 我不用关心世界坐标系
通过 相对于本地坐标系的位置 转换为 世界坐标系的点 进行 特效的创建 或者 攻击范围的判断

四.input和Screen

输出相关input

注意:输入相关内容肯定是写在Update中的

知识点一 鼠标在屏幕位置

屏幕坐标的原点 是在 屏幕的左下角 往右是X轴正方向 往上时Y轴正方向
返回值时Vector3 但是只有 x和y有值 z一直是0 是因为屏幕本来就是2D的 不存在Z轴
print(Input.mousePosition);

知识点二 检测鼠标输入

鼠标按下相关检测 对于我们来说
比如: 1.可以做 发射子弹
2.可以控制摄像机 转动

鼠标按下一瞬间 进入
0左键 1右键 2中键
只要按下的这一瞬间 进入一次
if( Input.GetMouseButtonDown(0) )
{
print(“鼠标左键按下了”);
}

鼠标抬起一瞬间 进入
if( Input.GetMouseButtonUp(0) )
{
print(“鼠标左键抬起了”);
}

鼠标长按按下抬起都会进入
就是 当按住按键不放时 会一直进入 这个判断
if( Input.GetMouseButton(1))
{
print(“右键按下”);
}

中键滚动
返回值的 y -1往下滚 0没有滚 1往上滚
它的返回值 是Vector的值 我们鼠标中键滚动 会改变其中的Y值
print(Input.mouseScrollDelta);

知识点三 检测键盘输入

比如说 按一个键释放一个技能或者切换武器 等等的操作

键盘按下
if( Input.GetKeyDown(KeyCode.W) )
{
print(“W键按下”);
}

传入字符串的重载
这里传入的 字符串 不能是大写的 不然会报错
只能传入小写字符串
if( Input.GetKeyDown(“q”) )
{
print(“q按下”);
}

键盘抬起
if( Input.GetKeyUp(KeyCode.W) )
{
print(“W键抬起”);
}

键盘长按
if( Input.GetKey(KeyCode.W) )
{
print(“W键长按”);
}

知识点四 检测默认轴输入

我们学习鼠标 键盘输入 主要是用来
控制玩家 比如 旋转 位移等等
所以Unity提供了 更方便的方法 来帮助我们控制 对象的 位移和旋转

键盘AD按下时 返回 -1到1之间的变换
相当于 得到得这个值 就是我们的 左右方向 我们可以通过它来控制 对象左右移动 或者左右旋转
float h = Input.GetAxis(“Horizontal”);
print(h);

键盘SW按下时 返回 -1到1之间的变换
得到得这个值 就是我们的 上下方向 我们可以通过它来控制 对象上下移动 或者上下旋转
print(Input.GetAxis(“Vertical”));

鼠标横向移动时 -1 到 1 左 右
print(Input.GetAxis(“Mouse X”));

鼠标竖向移动时 -1 到 1 下 上
print(Input.GetAxis(“Mouse Y”));

我们默认的 GetAxis方法 是有渐变的 会总 -101之间 渐变 会出现小数

GetAxisRaw方法 和 GetAxis使用方式相同
只不过 它的返回值 只会是 -1 0 1 不会有中间值

其它

是否有任意键或鼠标长按
if(Input.anyKey)
{
print(“有一个键长按”);
}
是否有任意键或鼠标按下
if(Input.anyKeyDown)
{
print(“有一个键 按下”);
//这一帧的键盘输入
print(Input.inputString);
}

手柄输入相关
得到连接的手柄的所有按钮名字
string[] strs = Input.GetJoystickNames();

某一个手柄键按下
if( Input.GetButtonDown(“Jump”) )
{

}

某一个手柄键抬起
if (Input.GetButtonUp(“Jump”))
{
}

某一个手柄键长按
if (Input.GetButton(“Jump”))
{

}

移动设备触摸相关
if(Input.touchCount > 0)
{
Touch t1 = Input.touches[0];
位置
print(t1.position);
相对上次位置的变化
print(t1.deltaPosition);
}

是否启用多点触控
Input.multiTouchEnabled = false;

陀螺仪(重力感应)
是否开启陀螺仪 必须开启 才能正常使用
Input.gyro.enabled = true;
重力加速度向量
print(Input.gyro.gravity);
旋转速度
print(Input.gyro.rotationRate);
陀螺仪 当前的旋转四元数
比如 用这个角度信息 来控制 场景上的一个3D物体受到重力影响
手机怎么动 它怎么动
print(Input.gyro.attitude);
}

总结

Input类 提供大部分和输入相关的内容
鼠标、键盘、触屏、手柄、重力感应
对于我们目前来说
鼠标、键盘 是必须掌握的核心知识
今天必须记住 鼠标键盘输入相关的API
GetAxis(大部分主角的起步需要用到它)

屏幕相关Screen

知识点一 静态属性

常用
当前屏幕分辨率
Resolution r = Screen.currentResolution;
print(“当前屏幕分辨率的宽” + r.width + “高” + r.height);

屏幕窗口当前宽高
这得到的 是当前 窗口的 宽高 不是设备分辨率的宽高
一般写代码 要用窗口宽高 做计算时 就用他们
print(Screen.width);
print(Screen.height);

屏幕休眠模式
Screen.sleepTimeout = SleepTimeout.NeverSleep;

不常用
运行时是否全屏模式
Screen.fullScreen = true;
窗口模式
独占全屏FullScreenMode.ExclusiveFullScreen
全屏窗口FullScreenMode.FullScreenWindow
最大化窗口FullScreenMode.MaximizedWindow
窗口模式FullScreenMode.Windowed
Screen.fullScreenMode = FullScreenMode.Windowed;

移动设备屏幕转向相关
允许自动旋转为左横向 Home键在左
Screen.autorotateToLandscapeLeft = true;
允许自动旋转为右横向 Home键在右
Screen.autorotateToLandscapeRight = true;
允许自动旋转到纵向 Home键在下
Screen.autorotateToPortrait = true;
允许自动旋转到纵向倒着看 Home键在上
Screen.autorotateToPortraitUpsideDown = true;

指定屏幕显示方向
Screen.orientation = ScreenOrientation.Landscape;

知识点二 静态方法

设置分辨率 一般移动设备不使用
Screen.SetResolution(1920, 1080, false);

五.必不可少Camera

Camera可编辑参数

Camera代码相关

知识点一 重要静态成员

1.获取摄像机
如果用之前的知识 来获取摄像机
主摄像机的获取
如果想通过这种方式 快速获取摄像机 那么场景上必须有一个 tag为MainCamera的摄像机
print(Camera.main.name);
获取摄像机的数量
print(Camera.allCamerasCount);
得到所有摄像机
Camera[] allCamera = Camera.allCameras;
print(allCamera.Length);

2.渲染相关委托
摄像机剔除前处理的委托函数
Camera.onPreCull += © =>
{

};
摄像机 渲染前处理的委托
Camera.onPreRender += © =>
{

};
摄像机 渲染后 处理的委托
Camera.onPostRender += © =>
{

};

知识点二 重要成员

1.界面上的参数 都可以在Camera中获取到
比如 下面这句代码 就是得到主摄像机对象 上的深度 进行设置
Camera.main.depth = 10;

2.世界坐标转屏幕坐标
转换过后 x和y对应的就是屏幕坐标 z对应的 是 这个3D物体 里我们的摄像机有多远
我们会用这个来做的功能 最多的 就是头顶血条相关的功能
Vector3 v = Camera.main.WorldToScreenPoint(this.transform.position);
print(v);
3.屏幕坐标转世界坐标

3.屏幕坐标转世界坐标
只所以改变Z轴 是因为 如果不改 Z默认为0
转换过去的世界坐标系的点 永远都是一个点 可以理解为 视口 相交的焦点
如果改变了Z 那么转换过去的 世界坐标的点 就是相对于 摄像机前方多少的单位的横截面上的世界坐标点
Vector3 v = Input.mousePosition;
v.z = 5;
obj.position = Camera.main.ScreenToWorldPoint(v);
print(Camera.main.ScreenToWorldPoint(v));

六.核心系统

光源系统继承

光源组件

光相关面板

物理系统之碰撞系统

刚体

碰撞器

物理材质参数

碰撞检测函数

知识点回顾

1.如何让两个游戏物体之间产生碰撞(至少1个刚体 和 两个碰撞器)
2.如何让两个物体之间碰撞时表现出不同效果(物理材质)
3.触发器的作用是什么(让两个物体碰撞没有物理效果,只进行碰撞处理)

注意:碰撞和触发响应函数 属于 特殊的生命周期函数 也是通过反射调用

知识点一 物理碰撞检测响应函数
碰撞触发接触时会 自动执行这个函数
  private void OnCollisionEnter(Collision collision)
  {
      //Collision类型的 参数 包含了 碰到自己的对象的相关信息

      //关键参数
      //1.碰撞到的对象碰撞器的信息
      //collision.collider

      //2.碰撞对象的依附对象(GameObject)
      //collision.gameObject

      //3.碰撞对象的依附对象的位置信息
      //collision.transform

      //4.触碰点数相关
      //collision.contactCount
      //接触点 具体的坐标
      //ContactPoint[] pos = collision.contacts;

      //只要得到了 碰撞到的对象的 任意一个信息 就可以得到它的所有信息

      print(this.name + "被" + collision.gameObject.name + "撞到了");
  }

  //碰撞结束分离时  会自动执行的函数
  private void OnCollisionExit(Collision collision)
  {
      print(this.name + "被" + collision.gameObject.name + "结束碰撞了");
  }

  //两个物体相互接触摩擦时 会不停的调用该函数
  private void OnCollisionStay(Collision collision)
  {
      print(this.name + "一直在和" + collision.gameObject.name + "接触");
  }


知识点二 触发器检测响应函数
    //触发开始的函数 当第一次接触时 会自动调用
    protected virtual void OnTriggerEnter(Collider other)
    {
        print(this.name + "被" + other.gameObject.name + "触发了");
    }

    //触发结束的函数 当水乳相融的状态结束时 会调用一次
    private void OnTriggerExit(Collider other)
    {
        print(this.name + "被" + other.gameObject.name + "结束水乳相融的状态了");
    }

    //当两个对象 水乳相融的时候 会不停调用
    private void OnTriggerStay(Collider other)
    {
        print(this.name + "和" + other.gameObject.name + "正在水乳相融");
    }
知识点三 要明确什么时候会响应函数

1.只要挂载的对象 能和别的物体产生碰撞或者触发 那么对应的这6个函数 就能够被响应
2.6个函数不是说 我都得写 我们一般是根据需求来进行选择书写
3.如果是一个异形物体,刚体在父对象上,如果你想通过子对象上挂脚本检测碰撞是不行的 必须挂载到这个刚体父对象上才行
4.要明确 物理碰撞和触发器响应的区别

知识点四 碰撞和触发器函数都可以写成虚函数 在子类去重写逻辑

一般会把想要重写的 碰撞和触发函数 写成保护类型的 没有必要写成public 因为不会自己手动调用 都是Unity
通过反射帮助我们自动调用的

刚体加力

知识点一 刚体自带添加力的方法

给刚体加力的目标就是
让其有一个速度 朝向某一个方向移动

1.首先应该获取刚体组件
rigidBody = this.GetComponent();

2.添加力
相对世界坐标
世界坐标系 Z轴正方向加了一个里
加力过后 对象是否停止移动 是由阻力决定的
如果阻力为0 那给了一个力过后 始终 是不会停止运动
rigidBody.AddForce(Vector3.forward * 10);
如果想要在 世界坐标系方法中 让对象 相对于自己的面朝向动
rigidBody.AddForce(this.transform.forward * 10);

相对本地坐标
rigidBody.AddRelativeForce(Vector3.forward * 10);

3.添加扭矩力,让其旋转
相对世界坐标
rigidBody.AddTorque(Vector3.up * 10);
相对本地坐标
rigidBody.AddRelativeTorque(Vector3.up * 10);

4.直接改变速度
这个速度方向 是相对于 世界坐标系的
如果要直接通过改变速度 来让其移动 一定要注意这一点
rigidBody.velocity = Vector3.forward * 5;

5.模拟爆炸效果
模拟爆炸的力 一定是 所有希望产生爆炸效果影响的对象
都需要得到他们的刚体 来执行这个方法 才能都有效果
rigidBody.AddExplosionForce(100, Vector3.zero, 10);

知识点二 力的几种模式

第二个参数 力的模式 主要的作用 就是 计算方式不同而已
由于4中计算方式的不同 最终的移动速度就会不同
rigidBody.AddForce(Vector3.forward * 10, ForceMode.Acceleration);

动量定理
Ft = mv
v = Ft/m;
F:力
t:时间
m:质量
v:速度

1.Acceleration
给物体增加一个持续的加速度,忽略其质量
v = Ft/m
F:(0,0,10)
t:0.02s
m:默认为1
v = 100.02/ 1 = 0.2m/s
每物理帧移动0.2m/s
0.02 = 0.004m

2.Force
给物体添加一个持续的力,与物体的质量有关
v = Ft/m
F:(0,0,10)
t:0.02s
m:2kg
v = 100.02/ 2 = 0.1m/s
每物理帧移动0.1m/s
0.02 = 0.002m

3.Impulse
给物体添加一个瞬间的力,与物体的质量有关,忽略时间 默认为1
v = Ft/m
F:(0,0,10)
t:默认为1
m:2kg
v = 101/ 2 = 5m/s
每物理帧移动5m/s
0.02 = 0.1m

4.VelocityChange
给物体添加一个瞬时速度,忽略质量,忽略时间
v = Ft/m
F:(0,0,10)
t:默认为1
m:默认为1
v = 101/ 1 = 10m/s
每物理帧移动10m/s
0.02 = 0.2m

知识点三 力场脚本

Unity拥有一个可以直接挂载在物体上的力组件,可以持续不断的为一个gameobject施加力,常见于各种特性上。

总结

如果你希望即使有阻力 也希望对象一直动 那你就一直“推”就行了
rigidBody.AddForce(Vector3.forward * 10);

七.音效系统

音频文件导入

音频源和音频监听脚本

音效源参数

代码控制音频源
知识点一 代码控制播放停止

if( Input.GetKeyDown(KeyCode.P) )
{
//播放音效
audioSource.Play();
//延迟播放 填写的是秒数
//audioSource.PlayDelayed(5);
}
if (Input.GetKeyDown(KeyCode.S))
{
//停止音效
audioSource.Stop();
}
if( Input.GetKeyDown(KeyCode.Space) )
{
//暂停
audioSource.Pause();
}

if( Input.GetKeyDown(KeyCode.X) )
{
//停止暂停 和暂停后 Play效果是一样的 都会继续播放现在的音效
audioSource.UnPause();
}

知识点二 如何检测音效播放完毕

//如果你希望某一个音效播放完毕后 想要做什么事情
//那就可以在Update生命周期函数中 不停的去检测 它的 该属性
//如果是false就代表播放完毕了
if(audioSource.isPlaying)
{
print(“播放中”);
}
else
{
print(“播放结束”);
}

知识点三 如何动态控制音效播放

1.直接在要播放音效的对象上挂载脚本 控制播放

2.实例化挂载了音效源脚本的对象
这种方法 其实用的比较少
Instantiate(obj);

3.用一个AudioSource来控制播放不同的音效
AudioSource aus = this.gameObject.AddComponent();
aus.clip = clip;
aus.Play();

潜在知识点

一个GameObject可以挂载多个 音效源脚本AudioSource
使用时要注意 如果要挂载多个 那一定要自己管理他们 控制他们的播放 停止 不然 我们没有办法准确的获取
谁是谁

麦克风输入相关
知识点一 获取设备麦克风信息

string[] strs = Microphone.devices;
for (int i = 0; i 《 strs.Length; i++)
{
print(strs[i]);
}

知识点二 开始录制

参数一:设备名 传空使用默认设备
参数二:超过录制长度后 是否重头录制
参数三:录制时长
参数四:采样率
if( Input.GetKeyDown(KeyCode.Space) )
{
clip = Microphone.Start(null, false, 10, 44100);
}

知识点三 结束录制

if( Input.GetKeyUp(KeyCode.Space) )
{
Microphone.End(null);
第一次去获取 没有才添加
AudioSource s = this.GetComponent();
if (s == null)
s = this.gameObject.AddComponent();
s.clip = clip;
s.Play();

知识点四 获取音频数据用于存储或者传输

规则 用于存储数组数据的长度 是用 声道数 * 剪辑长度
float[] f = new float[clip.channels * clip.samples];
clip.GetData(f, 0);
print(f.Length);
}

最终:综合小项目

一.前置知识

场景切换和退出游戏
知识点一 场景切换

if( Input.GetKeyDown(KeyCode.Space) )
{
//切换到场景2
//直接 写代码 切换场景 可能会报错
//原因是没有把该场景加载到场景列表当中
SceneManager.LoadScene(“Test2”);

//用它不会报错 只会有警告 一样可以切换场景
//SceneManager
//Application.LoadLevel(“Test2”);
}

知识点二 退出游戏

if( Input.GetKeyDown(KeyCode.Escape) )
{
//执行这句代码 就会退出游戏
//但是 在编辑模式下没有作用
//一定是发布游戏过后 才有用
Application.Quit();
}
#endregion

##### 鼠标隐藏锁定相关
知识点一 隐藏鼠标

//Cursor.visible = true;
#endregion

知识点二 锁定鼠标

//None 就是 不锁定
//Locked 锁定 鼠标会被限制在 屏幕的中心点 不仅会被锁定 还会被隐藏 可以通过ESC键 摆脱编辑模式下的锁定
//Confined 限制在窗口范围内
Cursor.lockState = CursorLockMode.Confined;

#endregion

知识点三 设置鼠标图片

//参数一:光标图片
//参数二:偏移位置 相对图片左上角
//参数三:平台支持的光标模式(硬件或软件)
Cursor.SetCursor(tex, Vector2.zero, CursorMode.Auto);

Unity自带随机数和委托(含与C#的对比)
知识点一 随机数

//Unity中的随机数
//Unity当中 的Random类 此Random(Unity)非彼Random(C#)
//使用随机数 int重载 规则是 左包含 右不包含
//0~99之间的数
int randomNum = Random.Range(0, 100);
print(randomNum);
//float重载 规则是 左右都包含
float randomNumF = Random.Range(1.1f, 99.9f);

//C#中的随机数
//System.Random r = new System.Random();
//r.Next(0, 100);
#endregion

知识点二 委托

//C#的自带委托
System.Action ac = () =>
{
print(“123”);
};

System.Action<int, float> ac2 = (i, f) =>
{

};

System.Func fun1 = () =>
{
return 1;
};

System.Func<int,string> fun2 = (i) =>
{
return “123”;
};
//Unity的自带委托
UnityAction uac = () =>
{

};

UnityAction uac1 = (s) =>
{

};

模型文件

项目实战–坦克大战

感悟

重点是掌握面向对象的思想,该工程里面的所有obj几乎都秉承着自己的事情自己做的原则——UI自带showme和hideme,player的fire方法一直被传递到武器身上。这样比起我曾经的项目,极大的提高了后期的维护容易度,也让代码更加的模块化。

打包