找回密码
 立即注册
查看: 403|回复: 0

Unity GGJ48小时心得

[复制链接]
发表于 2022-5-23 20:40 | 显示全部楼层 |阅读模式
上边是我自己在笔记软件上的记录,下边是抄过来的,不过格式可能乱了。大概可以看下内容,如果想仔细看的话还是到上边看格式正常的版本呗0.0
一堆issues


  • FixedUpdate与游戏定时器 FixedUpdate除了用来处理物理逻辑之外并不适合处理其他模块的逻辑,尤其是当大家的潜意识里都笃定它的更新频率是固定的时候。因为这很危险,比如一个需要状态同步的游戏要求按照固定的频率向目标同步状态,如果贸然采用FixedUpdate方法,就会出现上图那样可能在短时间内多次调用的问题。所以最好只在物理逻辑的处理中使用FixedUpdate,而不要滥用。 可变增量时间:每次调用Update之间,经过的时间不一致,所以大部分人使用Time.deltaTime()来计算两次Update之间的时差,磨平可变增量时间的错误。 固定增量时间:因为物理模拟很多微分积分,欧拉法使用都涉及到固定增量时间,因此FixedUpdate应运而生。当然,此时两次间时差并不一定真的是两次调用间的差距,而是因为Unity项目设定时候确定的值。



  • 协程(协同程序)的概括(StartCoroutine 和yield return和StopCoroutine ) public Coroutine StartCoroutine(IEnumerator routine); 协同进程执行中任何地方都可以使用yield语句,yield返回值控制何时恢复程序向下执行,协同程序在对象自有帧执行过程中堪称优秀。协同程序在性能上没有更多的开销。StartCoroutine函数是立刻返回的,但是yield可以延迟结果。直到协同程序执行完毕。 协程函数: 能暂停执行,暂停后立即返回直到中断指令完成后继续执行的函数;类似于一个子线程独立处理问题,性能开销小。 特点: 同一时刻,一个脚本实例下,可以有多个暂停的协成,但只有一个运行着的; 函数体全部完成后,协程结束; 协程可以很好控制跨越一定帧数后执行的行为; 协程在性能上相比一般函数无更多开销 使用方法: 1、通过MonoBehaviour提供的StartCoroutine方法来实现启动协同程序。 StartCoroutine(IEnumerator routine); 比如:StartCoroutine(Example());缺点是无法单独停止此协程,只能等待执行完毕或者StopAllCoroutine()结束所有的。 2、StartCoroutine (methodName:string, value : object = null); 优点,可以传递方法名和一个参数,可以直接通过StopCoroutine(string methodName);停止此协程;缺点:性能开销大,只能传递一个参数。 停止协同程序: 1、StopCoroutine(string methodName); 2、StopAllCoroutine(); 3、设置gameobject的active为false时可以终止协同程序,但是再次设置为true后协程不会再启动。 协同程序执行顺序: 开始协同程序 → 执行协同程序 → 中断协同程序(中断指令)→ 返回上层继续执行→ 中断指令结束后继续执行协同程序剩下的内容 协同程序注意事项: 1、不能在Update或者FixUpdate方法中使用协同程序,否则会报错。 2、关于中断指令:中断指令/YieldInstruction,一个协程收到中断指令后暂停执行,返回上层执行同时等待这个指令达成后继续执行。




  • 定时器与延时函数 单触发定时器:Invoke(string method,int Secondtimes)过Secondtimes 秒后触发method 函数。 重复触发InvokeRepeating(string method,int Secondtimetowake,int Secondtimetonext)每Secondtimetonext触发下一次。  使用延时函数,将想要使用延时的函数定义为IEnumerator类型,然后使用StartCoroutine(IEnumerator routine)调用该函数,注意调用的是声明为IEnumerator类型的函数而不是yield的函数。
  • C#中的IEnumerator 和 yield https://blog.csdn.net/u012138730/article/details/81148086 迭代器模式:允许你访问一个数据项序列,而无需关心序列内部的组织结构IEnumerator叫“迭代器”;IEnumerable叫“可迭代的”。

    • 不带<>的IEnumerator 和 IEnumerable(非泛型): IEnumberator,枚举器(迭代器),字面意思,用来一个一个枚举。 其关键成员有:当前对象current,是否有下一个movenext IEnumerable,可枚举的, 字面意思,继承了我就变成了一个可枚举的对象。 其只有一个成员 GetEnumerator,返回枚举器类型。 二者都是接口类型
      //  枚举器接口 IEnumerator
      public interface IEnumerator
      {
      object Current { get; }
      // 如果是返回 false,就是结束迭代器块
      bool MoveNext();
      void Reset();
      }
      //  可枚举的接口 IEnumerable, 返回枚举器接口
      public interface IEnumerable
      {
      IEnumerator GetEnumerator();
      }
      如果说某个类实现了IEnumerable可枚举的,那么我们说这个类就是可迭代对象。比如dict
      // dict是实现了 IEnumerable 接口的可迭代对象。
      // iterator 是枚举器
      var iterator = dict.GetEnumerator();
      while(iterator.MoveNext())
          {
      item = iterator.Current;
            .....
          }
      // 当然也可以直接更简单的实现迭代循环
      foreach(var item in dict)
    • C#举例如何实现可迭代对象 实现一个IterationSample 类,让它成为是一个为可迭代对象 首先肯定要继承IEnumerable接口,然后再需要实现:  1)一个获得枚举器的函数,GetEnumerator函数  2) 一个枚举器类,IterationSampleIterator,继承IEnumberator
      class IterationSample : IEnumerable
      {
      object[] values;
      int startingPoint;

      public IterationSample(object[] values, int startingPoint)
          {
      this.values = values;
      this.startingPoint = startingPoint;
          }

      public IEnumerator GetEnumerator()
          {
      return new IterationSampleIterator(this);
          }
      // 迭代器实现
      class IterationSampleIterator : IEnumerator
          {
      // 重要数据成员,来获取当前值,下一个值,是否结束。在构造函数中赋值。
      IterationSample parent;
      // 标记当前所迭代的位置。
      int position;
      internal IterationSampleIterator(IterationSample parent)
              {
      this.parent = parent;
      position = -1;
                }

      public bool MoveNext()
              {
      if (position != parent.values.Length)
                  {
      position++;
                  }
      return position < parent.values.Length;
              }

      public object Current
              {
      get
                  {
      if (position == -1 ||
      position == parent.values.Length)
                      {
      throw new InvalidOperationException();
                      }
      int index = (position + parent.startingPoint);
      index = index % parent.values.Length;
      return parent.values[index];
                  }
              }

      public void Reset()
              {
      position = -1;
              }
          }
      }
      这样就能像下面这样进行迭代:
      // sample 是 IterationSample 对象
      var iterator = sample.GetEnumerator();
      while(iterator.MoveNext())
      {
      item = iterator.Current;
        .....
      }
      或者用
      foreach(var item in sample)
      {
          .....
      }
      上述实现迭代器的代码容易理解,但是实现代码繁琐。
    • C#举例实现:使用yield关键字简化 有重复代码,那么可以让编译器给我们实现: 同样实现上述例子中的功能,C#中可以使用如下进行简化: 1)GetEnumerator函数重新实现,包含yield return 语句。 2)IterationSampleIterator 枚举器类,没有实现, 删除不需要了。
      class IterationSample : IEnumerable
      {
      object[] values;
      int startingPoint;

      public IterationSample(object[] values, int startingPoint)
          {
      this.values = values;
      this.startingPoint = startingPoint;
          }

      public IEnumerator GetEnumerator()
          {
      for (int index = 0; index <values.Length; index++)
            {
      yield return values[(index + startingPoint) % values.Length];
            }
          }
      }
      yield关键字是一个语法糖,背后其实生成了一个新的类,肯定是一个枚举器。而枚举器的具体实现MoveNext Current。 就看GetEnumerator中的函数体了。 一个例子了解编译器自动生成的迭代器的 工作原理:
      class IteratorWorkflow
      {
      static readonly string Padding = new string(' ', 30);
      // 也可以返回 IEnumerator<int>,相应的Main里面就不需要先获取 iterable 了。
      static IEnumerable<int> CreateEnumerable()
          {
      Console.WriteLine("{0}Start of CreateEnumerable()", Padding);
      for (int i = 0; i < 3; i++)
              {
      Console.WriteLine("{0}About to yield {1}", Padding, i);
      // !!!遇到yield return 代码就停止执行,再下一次调用MoveNext的时候 继续执行。
      yield return i;
      Console.WriteLine("{0}After yield", Padding);
              }

      Console.WriteLine("{0}Yielding final value", Padding);
      yield return -1;

      Console.WriteLine("{0}End of GetEnumerator()", Padding);
          }

      static void Main()
          {
      // 此处不会真正调用CreateEnumerable()函数
      IEnumerable<int> iterable = CreateEnumerable();
      IEnumerator<int> iterator = iterable.GetEnumerator();

      Console.WriteLine("Starting to iterate");
      // 也可以用foreach来替代。这里展示了foreach的类似过程。
      while (true)
              {
      Console.WriteLine("Calling MoveNext()...");
      //  此处开始真正调用CreateEnumerable()函数
      bool result = iterator.MoveNext();
      Console.WriteLine("... MoveNext result={0}", result);
      if (!result)
                  {
      break;
                  }
      Console.WriteLine("Fetching Current...");
      Console.WriteLine("... Current result={0}", iterator.Current);
              }
          }
      }

关于yield的一些注意事项:


    • 可以在方法,属性 和 索引器中使用 yield 来实现迭代器。 使用yield,函数返回类型必须是IEnumberable<T>、IEnumberable、IEnumberator<T>和IEnumberator中的一个。 使用yield,函数参数不能是 ref或 out
    • try和 catch语句里面不能出现yield return
    • 不能在匿名方法中用迭代器代码块,也就是不能使用yield。
    • yield break 相当于普通方法中的return



  • 渲染层级以及背景拼接 渲染层级:决定谁在上面谁在下面 通过Sorting Layer和Order in Layer设置渲染层级:


通过点击+添加,并为新得层级添上名字。这里可以拖动某一层级调整顺序,约在下面的层级越后渲染,也就是越显示在前面,Default为默认最小层级。这里添加了两个层级,分别为BackGround和Map用来给背景和地图瓦片。


背景拼接: 背景不够容纳地图,这个时候需要进行一下背景的拼接,只需要复制background,然后拖拽拼接。 如何保证无缝完美的拼接? 需要通过原本图像的像素来计算一下坐标移动的距离。


横向的像素为240,然后我们原本设置的Pixels Per Units为16,那么240/16=15,表铭背景一张图片的横向宽度为15个unity的单位。那么横向背景拼接,复制的backgroud背景只需要改变position X的15个单位。

  • 触发检测和碰撞检测 碰撞:
    void OnCollisionEnter(Collision col)
        {
    Debug.Log("开始碰撞" + col.collider.gameObject.name);
        }
    void OnCollisionStay(Collision col)
        {
    Debug.Log("持续碰撞中" + col.collider.gameObject.name);
        }
    void OnCollisionExit(Collision col)
        {
    Debug.Log("碰撞结束" + col.collider.gameObject.name);
        }

    发生碰撞的条件: 主动方必须有Rigidbody,发生碰撞的两个游戏对象必须有Collider,被动方对于RigidBody可有可无,参数是表示被动方。
    触发:
    void OnTriggerEnter(Collider other)
        {
    Debug.Log("触发器开始出发:" + other.gameObject.name);
        }
    void OnTriggerStay(Collider other)
        {
    Debug.Log("触发器检测中:" + other.gameObject.name);
        }
    void OnTriggerExit(Collider other)
        {
    Debug.Log("触发器结束:" + other.gameObject.name);
        }
    发生触发的条件: 发生碰撞的物体两者其中之一有Rigidbody即可,发生碰撞的两个游戏对象必须有Collider,其中一方勾选IsTrigger即可,参数是表示被动方
  • Instantiate 使用预制体生成物体


Instantiate函数实例化是将original对象的所有子物体和子组件完全复制,成为一个新的对象。这个新的对象拥有与源对象完全一样的东西,包括坐标值等。  instantiateWorldSpace这个值为TRUE,表示实例化对象相对于世界坐标系的位置不变,相对于父对象的坐标值变了;为false,表示实例化对象相对于父对象的坐标值不变,但在世界坐标系中的位置变了。即看当前物体到底要生成到何处,对应于世界坐标不变还是对应父物体不变。

  • 获取当前物体的所有子物体 1、使用GetComponentsInChildren()  Transform[] father = GetComponentsInChildren<Transform>();  不过此法会获取所有子物体及孙物体  如下方法可以仅获得子物体  C#         Transform father;         for (int i = 0; i < father.childCount; i++)           {             var child = father.GetChild(i).gameObject;             Debug.Log([child.name](http://child.name));           }   2、GameObject.FindGameObjectsWithTag()来找到物体  此法只用于已有tag的物体  GameObject[] name3 = GameObject.FindGameObjectsWithTag("erzi");  若只找一个则是  GameObject[] name2 = GameObject.FindGameObjectWithTag("erzi"); 3、查找当前场景下所有带脚本的游戏物体  MonoBehaviour[] scripts = ``Object.FindObjectsOfType<MonoBehaviour>();  然后scripts.gameObject就可以获得游戏物体咯。
  • position和localposition position是根据世界原点为中心 localPosition是根据父节点为中心,如果没有父节点,localpositon和position是没有区别的 选中一个物体左上角Global和Local切换看物体世界坐标轴和本地坐标轴下的坐标
  • 物体间距离计算 static function ``*Distance*`` (a: Vector3, b: Vector3) : float 等同于(a-b).magnitude
  • 物体移动的方法 第一个方法: 可以通过this.transform.Translate( (directionOfTravel.x * speed * Time.deltaTime), (directionOfTravel.y * speed * Time.deltaTime),(directionOfTravel.z * speed * Time.deltaTime), Space.World); 也可以如直接计算此时的position直接赋值 第二个方法: 挂载Rigidbody后,在FixedUpdate中获得要去的地方的位置,计算此时速度方向,然后赋值给rb.velocity ,2D中速度为二维向量。
  • C#的基类、派生类、重写与重载
    class <派生类> : <基类>
    {
    ...
    }
    C# 不支持多重继承。但是,可以使用接口来实现多重继承。
    class Shape
       {
       ...
       }
    public interface PaintCost
       {
          int getCost(int area);

       }
    class Rectangle : Shape, PaintCost
       {
       ...
       }
    继承时构造方法的调用顺序为:子类静态、父类静态、父类构造、子类构造 子类重写父类方法:

    • 使用Virtual关键字Override从写
      public virtual void Clear()
      {
          UpdateView();
      }

      // 不能修改public 为其它权限
      public override void Clear()
      {
          base.Clear();

      }
    • 使用new覆盖
      public  void Clear()
      {
          UpdateView();
      }
      // 可以覆盖,但是父类调用时候不会调用到子类实现的新方法
      private new void Clear()
      {
          base.Clear();

      }

继承是派生类(子类)去实现(重写<override>、重构<new>)基类(父类)的方法或属性。从而获取在派生类中要实现的功能。 重写<override>只是对方法里面的功能实现重新做的了编写,并没有对方法的参数进行添加、改变、删除 ;重载则是对方法参数的个数、位置、参数类型进行了改变。 重写的父类方法时,必须指明被重写的方法是虚方法(virtual关键字)。在子类重写父类方法时必须有重写关键字(override)这样才能重写父类的方法


  • 音乐控件的操作 AudioSource获取音乐源,AudioClips数组存放可能用到的所有音效。记得要把物体上的音乐源拖拽到脚本上才能使用。audio.play()是播放音乐,audio.clip赋值当前要播放的音乐。audio.loop是循环播放的标志位,audio.stop()是停止,都可以在程序中使用来播放音效。 audioSource.isPlaying表示是否在播放音乐;audioSource.pitch表示音乐的播放速度 https://blog.csdn.net/qq_41967240/article/details/102619099
  • 四元数与旋转相关 四元数本质上是一种高阶复数,是一个四维空间,它的虚部包含了三个虚数单位,i、j、k,即一个四元数可以表示为x = a + bi + cj + dk。 三种旋转方式: 矩阵旋转 优点: 旋转轴可以是任意向量; 缺点: 旋转其实只需要知道一个向量+一个角度,一共4个值的信息,但矩阵法却使用了16个元素; 而且在做乘法操作时也会增加计算量,造成了空间和时间上的一些浪费; 欧拉旋转 优点: 很容易理解,形象直观; 表示更方便,只需要3个值(分别对应x、y、z轴的旋转角度);但按我的理解,它还是转换到了3个3*3的矩阵做变换,效率不如四元数; 缺点: 之前提到过这种方法是要按照一个固定的坐标轴的顺序旋转的,因此不同的顺序会造成不同的结果; 会造成万向节锁(Gimbal Lock)的现象。这种现象的发生就是由于上述固定坐标轴旋转顺序造成的。理论上,欧拉旋转可以靠这种顺序让一个物体指到任何一个想要的方向,但如果在旋转中不幸让某些坐标轴重合了就会发生万向节锁,这时就会丢失一个方向上的旋转能力,也就是说在这种状态下我们无论怎么旋转(当然还是要原先的顺序)都不可能得到某些想要的旋转效果,除非我们打破原先的旋转顺序或者同时旋转3个坐标轴。这里有个视频可以直观的理解下; 由于万向节锁的存在,欧拉旋转无法实现球面平滑插值; 四元数旋转 优点: 可以避免万向节锁现象; 只需要一个4维的四元数就可以执行绕任意过原点的向量的旋转,方便快捷,在某些实现下比旋转矩阵效率更高; 可以提供平滑插值; 缺点: 比欧拉旋转稍微复杂了一点点,因为多了一个维度; 理解更困难,不直观; 使用一个四元数q=((x,y,z)sinθ2, cosθ2) 来执行一个旋转。具体来说,如果我们想要把空间的一个点P绕着单位向量轴u = (x, y, z)表示的旋转轴旋转θ角度,我们首先把点P扩展到四元数空间,即四元数p = (P, 0)。那么,旋转后新的点对应的四元数(当然这个计算而得的四元数的实部为0,虚部系数就是新的坐标)为:
    $$
    p^{'}= q p q^{-1}
    $$

    其中,q=(cosθ2, (x,y,z)sinθ2) ,由于u是单位向量,因此 N(q)=1,即q1=q。右边表达式包含了四元数乘法。 表示:q = ((x, y, z),w) = (v, w),其中v是向量,w是实数,这样的式子来表示一个四元数
    $$
    四元数乘法:q1q2=(\vec{v1}×\vec{v2}+w1\vec{v2}+w2\vec{v1},w1w2\vec{v1}\vec{v2})
    $$

    $$
    共轭四元数: q^* = (- \vec{v2},w)
    $$

    $$
    四元数的模:$N(q) = √(x^2 + y^2 + z^2 +w^2)$,即四元数到原点的距离  
    $$

    $$
    四元数的逆:$q^{1}=\frac{q^*}{N(q)}$
    $$

    最简单的例子:把点P(1, 0, 1)绕旋转轴u = (0, 1, 0)旋转90°,求旋转后的顶点坐标。首先将P扩充到四元数,即p = (P, 0)。而q = (usin45°, cos45°)。求p′=qpq1的值。建议大家一定要在纸上计算一边,这样才能加深印象,连笔都懒得动的人还是不要往下看了。最后的结果p` = ((1, 0, -1), 0),即旋转后的顶点位置是(1, 0, -1)。 注意: 用于旋转的四元数,每个分量的范围都在(-1,1); 每一次旋转实际上需要两个四元数的参与,即q和q; 所有用于旋转的四元数都是单位四元数,即它们的模是1;
    Unity中可以使用Quaternion.Euler和Quaternion.eulerAngles  和其他类型转换: 轴角到四元数:单位长度的旋转轴(x, y, z)和一个角度θ,对应的四元数为:q=((x,y,z)sinθ/2, cosθ/2) 欧拉角到四元数:欧拉旋转(X, Y, Z),其对应四元数为:  x = sin(Y/2)sin(Z/2)cos(X/2)+cos(Y/2)cos(Z/2)sin(X/2)   y = sin(Y/2)cos(Z/2)cos(X/2)+cos(Y/2)sin(Z/2)sin(X/2)   z = cos(Y/2)sin(Z/2)cos(X/2)-sin(Y/2)cos(Z/2)sin(X/2)   w = cos(Y/2)cos(Z/2)cos(X/2)-sin(Y/2)sin(Z/2)sin(X/2)   q = ((x, y, z), w) Unity中使用: Quaternion.AngleAxis(float angle, Vector3 axis),它可以返回一个绕轴线axis旋转angle角度的四元数变换 使用Quaternion.eulerAngles来得到欧拉角度,计算完成后更新Quaternion.eulerAngles;或者使用Quaternion.Euler(yourAngles)来创建一个新的四元数。 Quaternion.LookRotation<br />Quaternion.Angle<br />Quaternion.Euler<br />Quaternion.Slerp<br />Quaternion.FromToRotation<br />Quaternion.identity
    https://blog.csdn.net/qq_15020543/article/details/82834885 https://www.jianshu.com/p/9c3f11eeda1f https://www.cnblogs.com/xiaowangba/p/6314663.html
  • 发送消息和接收消息(脚本函数调用) 为了方便Unity物体之间的通信,Unity推出了SendMessge方法 推送机制可以非常方便我们的脚本开发,它实现的是一种伪监听者模式,利用的是反射机制

    • SendMessage 函数原型:public void SendMessage(string methodName, object value = null, SendMessageOptions options = SendMessageOptions.RequireReceiver); 调用一个对象的methodName函数,这个函数可以是公有的也可以是私有的,后面跟一个可选参数(此参数作为传入参数)
    • SendMessageUpwards 原型:public void SendMessageUpwards(string methodName, object value = null, SendMessageOptions options = SendMessageOptions.RequireReceiver); 作用和SendMessage类似,只不过它不仅会向当前对象推送一个消息,也会向这个对象的父对象推送这个消息(会遍历所有父对象)
    • BroadcastMessage 原型:public void BroadcastMessage(string methodName, object parameter = null, SendMessageOptions options = SendMessageOptions.RequireReceiver); 作用和SendMessageUpwards的作用正好相反,它不是推送消息给父对象,而是推送消息给所有的子对象(遍历子对象)

当然,在碰撞检测获取到其他物体的collider时候可以通过other.GameObject获取其物体,然后通过GetComponent<脚本名>.FunctionName();来实现对其的方法调用,不过仅支持public的方法。 再其次,被调用脚本函数为static类型,调用时直接用  脚本名.函数名()即可。

  • 获取输入事件 有两种获取输入的系统,一种为新的InputSystem,另一种为原本的InputManager。 旧有的输入系统大概都是:
    // 按住a键
    Input.GetKey("a")
    //这两种一样,KeyCode枚举类型,down是按下瞬间,up为抬起瞬间
    Input.GetKeyDown("a")
    Input.GetKeyDown(KeyCode.A)
    Input.GetKeyUp("a")
    //鼠标输入,左键0 右键1 中键2
    Input.GetMouseButton(0)
    Input.GetMouseButtonDown(0)
    Input.GetMouseButtonUp(0)
    和一些GetAxis类的函数,输入管理器可以在项目管理中设置轴名称,降低程序耦合,另一方面可以让游戏玩家自定义游戏输入按键。


而新的InputSystem则是需要在创建一个InputActions文件,勾选generate C# Class,然后打开InputActions文件,对按键以及对于的调用函数进行设置, Action Maps(最左相当于一个文件夹) ;  Action中的InputUp是我用来监听所有能代表Up输入  ; InputUp子目录中的可以通过最右中的Listen来监听输入;


最后使用要把Player Input挂载在玩家的游戏物体上。 编写玩家逻辑代码: 两套实现方法: 第一套不适用于本地多人游戏,因为两个函数关联,每次按下必执行另一个函数,会广播到所有挂载此脚本的物体上
controls.PlayerMove.upAction.performed += ctx => PlayerMove(getDir(GameController.blockType.Up));
controls.PlayerMove.downAction.performed += ctx => PlayerMove(getDir(GameController.blockType.Down));
controls.PlayerMove.leftAction.performed += ctx => PlayerMove(getDir(GameController.blockType.Left));
controls.PlayerMove.rightAction.performed += ctx => PlayerMove(getDir(GameController.blockType.Right));

第二套方法适合本地多人游戏,
    private void OnUpAction(InputValue value)
    {
        PlayerMove(getDir(GameController.blockType.Up));
    }
    private void OnDownAction(InputValue value)
    {
        PlayerMove(getDir(GameController.blockType.Down));
    }
    private void OnLeftAction(InputValue value)
    {
        PlayerMove(getDir(GameController.blockType.Left));
    }
    private void OnRightAction(InputValue value)
    {
        PlayerMove(getDir(GameController.blockType.Right));
    }

https://blog.csdn.net/weixin_46045344/article/details/115445372 https://www.bilibili.com/video/BV1cf4y1F7zY?from=search&seid=12301447195505609892&spm_id_from=333.337.0.0

  • 正交与透视 正交摄像机没有透视效果,一般可以用于 2D 游戏开发或者是 3D 游戏的 UI 开发。在正交相机中唯一与显示范围相关的属性只有一个,那就是 Size,单位为 Unity 单位。这个属性的值代表了摄像机在纵向上一半的显示范围。举例来说,如果把 Size 设置为5,那就意味着这个摄像机在纵向可以显示10个 Uinty 单位。 像素和 Unity 单位之间的转换关系是怎么的呢?准确的说,他们两者之间的关系是不固定的,这取决与在导入素材时"Pixels To Units"这一属性的值
  • 四种坐标系详细介绍 World Space(世界坐标):我们在场景中添加物体(如:Cube),他们都是以世界坐标显示在场景中的。transform.position可以获得该位置坐标。  Screen Space(屏幕坐标):以像素来定义的,以屏幕的左下角为(0,0)点,右上角为(Screen.width,Screen.height),Z的位置是以相机的世界单位来衡量的。注:鼠标位置坐标属于屏幕坐标,Input.mousePosition可以获得该位置坐标,手指触摸屏幕也为屏幕坐标,Input.GetTouch(0).position可以获得单个手指触摸屏幕坐标。 ViewPort Space(视口坐标):视口坐标是标准的和相对于相机的。相机的左下角为(0,0)点,右上角为(1,1)点,Z的位置是以相机的世界单位来衡量的。(用的不多,反正我暂时没有用到~呵呵~) 绘制GUI界面的坐标系:这个坐标系与屏幕坐标系相似,不同的是该坐标系以屏幕的左上角为(0,0)点,右下角为(Screen.width,Screen.height)。 世界坐标→屏幕坐标:camera.WorldToScreenPoint(transform.position);这样可以将世界坐标转换为屏幕坐标。其中camera为场景中的camera对象。 屏幕坐标→视口坐标:camera.ScreenToViewportPoint(Input.GetTouch(0).position);这样可以将屏幕坐标转换为视口坐标。其中camera为场景中的camera对象。 视口坐标→屏幕坐标:camera.ViewportToScreenPoint(); 视口坐标→世界坐标:camera.ViewportToWorldPoint(); 通过这些方法可以将坐标进行替换计算。 https://blog.csdn.net/e295166319/article/details/52734585
  • Vector3类详解 向量的运算:   加减:各个分量分别相加减。  标量:只有大小,没有方向  数乘:向量与标量的乘数 向量运算: 点乘(余弦,结果为数字) 叉乘(正弦,结果为垂直于两向量的新向量) Vector3的常用方法: 静态成员变量 right(右):代表坐标轴(1,0,0) left (左):代表坐标轴(-1,0,0) up(上) :代表坐标轴(0,1,0) down (下): 代表坐标轴(0,-1,0) forward (前):代表坐标轴(0,0,1) back(后):代表坐标轴(0,0,-1) zero(零):代表坐标轴(0,0,0) one(一):代表坐标轴(1,1,1) 实例成员变量 magnitude:(返回向量的长度,向量的长度是(xx+yy+z*z)的平方根)(只读) normalized:返回一个长度为一的新向量,原向量长度不变 sqrMagnitude:返回向量的长度的平方(只读) 方法 Cross:向量叉乘 Dot:向量点乘 Lerp(v(from),v(to),t):两个向量之间的线性插值。按照数字t在from到to之间插值。 MoveTowards(v,v,speed);当前的地点移向目标。 RotateTowards(v,v,r,m);当前的向量转向目标。 Max(v,v),Min(v,v); 向量的最大值,最小值 Angle:返回两个向量之间的夹角 Distance:返回两个向量之间的距离 还有许多,在下边博客中
    https://blog.csdn.net/Barrett_/article/details/111354110?ops_request_misc=%7B%22request%5Fid%22%3A%22164326202916780264039287%22%2C%22scm%22%3A%2220140713.130102334.pc%5Fblog.%22%7D&request_id=164326202916780264039287&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~times_rank-4-111354110.nonecase&utm_term=Vector3&spm=1018.2226.3001.4450 https://blog.csdn.net/Windgs_YF/article/details/87882774

一堆感觉可能会用到的参考

https://www.jianshu.com/p/3d90a5bfad83
https://www.xuanyusong.com/archives/561
https://www.cnblogs.com/driftingclouds/p/6442847.html
https://www.cnblogs.com/xiaoahui/p/10454278.html
https://blog.csdn.net/qq_38721111/article/details/86742317
https://blog.csdn.net/m0_43396324/article/details/121600078
https://www.cnblogs.com/mttnor/p/9647850.html
https://blog.csdn.net/nanggong/article/details/79832456

mask一下可能有用的要看的东西

https://www.gameres.com/847380.html
https://learn.unity.com/tutorial/she-zhi-unity-bian-ji-qi?uv=2020.3&projectId=5facf921edbc2a2003a58d3a
https://itch.io/jams
https://blog.csdn.net/hanjilun/article/details/106041574
https://blog.csdn.net/yindongfang1/article/details/102827602
https://blog.csdn.net/ymiku/article/details/45956969
http://www.sikiedu.com/course/94/tasks

Unity官方的一些教程

https://learn.u3d.cn/tutorial/MiniGameDev#
https://learn.unity.com/tutorial/get-ready-for-unity-essentials?language=en&pathwayId=5f7bcab4edbc2a0023e9c38f&missionId=5f77cc6bedbc2a4a1dbddc46&projectId=612f9602edbc2a1b588a3af3
https://learn.unity.com/project/ping-tai-you-xi-microgame?uv=2019.3

结尾

当然是我们两天内最后做完的垃圾小游戏啦
视频在这里:
https://www.bilibili.com/video/BV1DF411p78x/
活动项目在这里:
https://globalgamejam.org/2022/games/light-and-dark光与暗-1
源码会在这里:
https://gitee.com/gongweixi/LightAndDark
不过由于项目还会在完美世界的制作人培养计划中作为作业,所以暂时先不放出来啦。
四元数乘法:q1q2=(\vec{v1}×\vec{v2}+w1\vec{v2}+w2\vec{v1},w1w2\vec{v1}\vec{v2})

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-16 08:57 , Processed in 0.104652 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表