当前位置:   article > 正文

Unity常见面试题型和日常问题总结,附有详解,强烈建议收藏,(持续更新中)_unity面试

unity面试

前情提要:
会收集自己遇到的问题、在网上碰到的问题、大家分享的问题,尽量用不那么程序化的语言去描述,让大家都可以明白的更快(由于工作量有点大,可能更新的有点慢,不过我会尽力去更新完善的)来张洛琪希美图我们就开始啦!

在这里插入图片描述

C#

  1. 什么是面向对象,和面向过程的区别

面向对象:当解决一个问题时,把事物抽象成对象的概念,看这个问题中有哪些对象,给这些对象赋一些属性和方法,让每个对象去执行自己的方法,最终解决问题
面向过程:当解决一个问题时,把事件拆封成一个个函数和数据,然后按照一定的顺序,依次执行完这些方法(过程),等方法执行完毕,事情也解决。
面向过程性能比面向对象要高,但是没有面向对象易于维护、易于复用、易于扩展

  1. 五大基本原则
  • 单一职责原则:一个类的功能要单一,不能太复杂。就像一个人一样,分配的工作不能多,不然虽然很忙碌,但是效率并不高
  • 开放封闭原则:一个模块在扩展性方面应该是开放的,而在更改性方面应该是封闭的。例如一个模块,原先只有服务端功能,但是现在要加入客户端功能,那么在设计之初,就应当把服务端和客户端分开,公用的部分抽象出来,从而在不修改服务端的代码前提下,添加客户端代码。
  • 里式替换原则:子类应当可以替换父类并出现在父类能出现的任何地方。例如参加年会,公司所有的员工应该都要可以参加抽奖,而不是只让一部分员工是参加抽奖。
  • 依赖倒置原则:具体依赖抽象,上层依赖下层。例如B是较A低的模块,但B需要使用A的功能,这时B不能直接使用A中的具体类,而是应当由B来定义一个抽象接口,由A来实现这个抽象接口,B只使用这个抽象接口,从而达到依赖倒置,B也接触了对A的依赖,反过来是A依赖于B定义的抽象接口。
  • 接口分离原则:模块之间要通过抽象接口隔离开,而不是通过具体的类强耦合起来
  1. 面向对象的特征
  • 封装:将数据和行为相结合,通过行为来进行约束代码,从而增加数据的安全性
  • 继承:用来扩展一个类,子类可以继承父类的部分行为和属性,从而便于管理、提高代码的复用
  • 多态:一个对象,在不同情形有不同表现形式;子类对象可以赋值给父类型的变量
  1. 重载和重写
  • 两者定义的方式不同 ;重载方法名相同参数列表不同,重写方法名和参数列表都相同
  • 封装、继承、多态所处的位置不同;重载是在同类中,而重写是在父子类中
  • 多态的时机不同,重载是在编译时多态,而重写是在运行时多态
    通常用在:
  1. 字段和属性的区别

字段: 是一种表示与对象或类相关联的变量的成员,字段的声明用于引入一个或多个给定类型的字段。
属性: 是对字段的封装,将字段和访问字段的方法组合在一起,提供灵活的机制来读取、编写或计算私有字段的值。并且属性有自己的名称,包含Get访问器和set访问器

例如:

pulic class User
{
    private string _name;//_name为字段
    public string Name   //Name为属性,它含有代码块
     {
       get
       {
          return _name;//读取(返回_name值)
        }
       set 
       {
          _name = value;//为_name赋值
        }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

如上,属性以灵活的方式实现了对私有字段的访问,是字段的自然扩展,一个属性总是与某个字段相关联,字段能干的,属性一定能干,属性能干的,字段不一定干的了;为了实现对字段的封装,保证字段的安全性,产生了属性,其本质是方法,暴露在外,可以对私有字段进行读写,以此提供对类中字段的保护,字段中存储数据更安全。

------参考原文链接:字段和属性

  1. 值类型和引用类型
  • 值类型存储在内存栈中,超过作用域自动清理,在作用域结束后会被清理,而引用类型存储在内存堆中,由GC来自动释放,
  • 值类型转换成引用类型(装箱)要经历分配内存和拷贝数据
  • 引用类型转换成值类型(拆箱)要首先找到属于值类型的地址,再把引用对象的值拷贝到内存栈中的值实例中
  • 值类型存取速度快,引用类型存取速度慢
  • 值类型表示实际的数据,引用类型表示存储再内存堆中的数据的指针和引用
  • 值类型的基类是ValueType,引用类型的基类是Object
  • 值类型在内存管理方面有更好的效率,且不支持多态,适合用作存储数据的载体
    引用类型支持多态,适合用于定义应用程序的行为
  • 值类型有:byte、short、int、long、double、decimal、char、bool、struct
    引用类型有:array、class、interface、delegate、Object、string
  1. 接口、抽象类

接口:

  • 契约式设计,是开发的,谁都可以去实现,但是必须要遵守接口的约定;例如所有游戏都必须会有主角给玩家进行操作,这种必须规范
  • 是约束类应该具有的功能集合,从而便于类的扩展和管理
  • 接口不能实例化
  • 接口关注于行为,可以多继承
  • 接口的设计目的,是对类的行为进行约束(提供一种机制,可以强制要求不同的类具有相同的行为,只约束了行为的有无,但是不对如何去实现行为进行约束)
  • 通常用于多人协同

抽象类:

  • 对同类事物相对具体的抽象,例如我会玩游戏,你如果继承了我,你也必须会玩游戏,但是我不管你玩什么类型游戏;
  • 抽象类不能实例化,但是可以间接实例化
  • 抽象类是概念的抽象,只能单继承
  • 抽象类的设计目的,是代码复用(当不同的类具有某些相同的行为,且其中一部分行为的实现方法一致时,可以让这些类都派生于一个抽象类。在这个抽象类中实现了公用的行为,从而可以避免让所有的子类来实现该行为,从而让子类可以自己去实现抽象类中没有的部分)
  • 一个类可以有多个接口,但是只能继承一个类
  1. 委托和事件

委托是一个类,它定义了方法的类型,使得可以将多个方法赋给同一个委托变量,当调用这个变量时,将依次调用其绑定的方法
一般调用方法,我们称为直接调用方法,而委托可以间接调用方法,也就是委托封装了一个或多个方法,方法的类型必须与委托的类型兼容;同时委托也可以当做方法的参数,传递到某个方法中去,当使用这个传进来的委托时,就会间接的去调用委托封装的方法,从而形成动态的调用方法的代码,并且降低了代码的耦合性

using UnityEngine; 
//声明委托,可以在类的外面,也可以在类的内部
public delegate X ADelegate<in T, out X>(T t);//有返回值的委托   对应Func委托 in为输入 out输出
public delegate void BDelegate<in T>(T t);//无返回值的委托    对应Action委托 
 
public class DelegateTest : MonoBehaviour
{
 
    public ADelegate<int, string> MyTest;
 
    [ContextMenu("Exe")]
    private void Start()
    {
        MyTest = IntNumber;
        TestDelegate(MyTest);//输出  返回值:100
        //目标实例(当目方法为静态时,目标实例为null)
        Debug.Log(MyTest.Target);  //输出  DelegateTest
        //实例方法
        Debug.Log(MyTest.Method);  //输出  System.String  IntNumber(Int32)
                                         //(返回值类型)(目标方法)(方法参数)
    }
 
    public string IntNumber(int a) => (a * a).ToString();
 
    public void TestDelegate(ADelegate<int, string> aDelegate) => Debug.LogFormat("返回值:{0}", aDelegate?.Invoke(10));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
using System;
using UnityEngine; 
public class DelegateTest : MonoBehaviour
{ 
    [ContextMenu("Exe")]
    private void Start()
    {
        TestDelegate(Add);
        TestDelegate(Sub);
        //-----------委托与接口类似,委托能实现的,接口也能实现,但又有所不同
        TestInterface(new Add());
        TestInterface(new Sub()); 
        /*
         * 对于这个例子这种情况,更适合使用委托,因为接口而言,要多次实现接口,过于麻烦。
         */
    }
 
    public int Add(int num) => num + num;
    public int Sub(int num) => num - num;
     
    public void TestDelegate(Func<int ,int> _func) => Debug.LogFormat("返回值:{0}", _func?.Invoke(10));
 
    public void TestInterface(ICalcNumber calc) => Debug.LogFormat("返回值:{0}", calc.Calc(10));
}
 
public interface ICalcNumber
{
    public int Calc(int num);
}
 
class Add : ICalcNumber
{
    public int Calc(int num) => num + num;
}
 
class Sub : ICalcNumber
{
    public int Calc(int num) => num - num;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

事件,他可以让一个类或对象去通知其他类、对象,并做出相应的反应;是封装了委托类型的变量,使得:在类的内部,不管是public和protected,总是private的。在类的外部,注册"+=“和注销”-="的访问限制夫和在声明事件时使用的访问符一致。其主要目的就是为了防止订阅者之间相互干扰。

using System;
using UnityEngine;
 
/// <summary>
/// 
/// * Writer:June
/// *
/// * Data:2021.5.12
/// *
/// * Function:事件例子=====>顾客点单
/// *
/// * Remarks:
/// 
/// 
/// 事件模型的五个组成部分
/// 事件的拥有者: 类
/// 事件: event关键字修饰
/// 事件的响应者: 类
/// 事件处理器:  方法-受到约束的方法
/// 事件的订阅关系: +=
/// 
/// </summary>
 
/* 用于事件约束的委托类型,签名应该遵循:事件名+EventHandler
 * (当别人看到这个后缀之后,就知道,这个委托是用来约束事件处理器的,不会用于做别的事!!!)
 */
public delegate void OrderEventHandler(Customer _customer, OrderEventArgs _oe);
 
public class EventEx : MonoBehaviour
{
    //顾客类实例化
    Customer customer = new Customer();
    //服务员类实例化
    Waiter waiter = new Waiter();
 
    private void Start()
    {
        customer.OnOrder += waiter.TakeAction;
        customer.Order();
 
        customer.PayTheBill();
    }
}
 
 
public class Customer 
{
    public float Bill { get; set; }
    public void PayTheBill() => Debug.Log("我应该支付:" + Bill);
    #region 事件的完整声明格式
    //private OrderEventHandler orderEventHandler;
    //public event OrderEventHandler OnOrder
    //{
    //    add { orderEventHandler += value; }//事件添加器
    //    remove { orderEventHandler -= value; }//事件移除器
    //}
    //public void Order()
    //{
    //    if (orderEventHandler != null)
    //    {
    //        OrderEventArgs orderEventArgs = new OrderEventArgs
    //        {
    //            CoffeeName = "摩卡",
    //            CoffeeSize = "小",
    //            CoffeePrice = 28
    //        };
    //        orderEventHandler(this, orderEventArgs);
 
    //        OrderEventArgs orderEventArgs1 = new OrderEventArgs
    //        {
    //            CoffeeName = "摩卡",
    //            CoffeeSize = "小",
    //            CoffeePrice = 28
    //        };
    //        orderEventHandler(this, orderEventArgs1);
    //    }
    //}
    #endregion
 
    #region 事件的简略声明格式
    public event OrderEventHandler OnOrder;
    public void Order()
    {
        //语法糖衣
        if (OnOrder != null)
        {
            OrderEventArgs orderEventArgs = new OrderEventArgs
            {
                CoffeeName = "摩卡",
                CoffeeSize = "小",
                CoffeePrice = 28
            };
            OnOrder(this, orderEventArgs);
 
            OrderEventArgs orderEventArgs1 = new OrderEventArgs
            {
                CoffeeName = "摩卡",
                CoffeeSize = "小",
                CoffeePrice = 28
            };
            OnOrder(this, orderEventArgs1);
        }
    }
    #endregion
}
 
public class OrderEventArgs : EventArgs
{
    public string CoffeeName { get; set; }
    public string CoffeeSize { get; set; }
    public float CoffeePrice { get; set; }
}
 
public class Waiter
{
    //事件处理器
    internal void TakeAction(Customer _customer, OrderEventArgs _oe)
    {
        float finaPrice = 0;
        switch (_oe.CoffeeSize)
        {
            case "小":
                finaPrice = _oe.CoffeePrice;
                break;
            case "中":
                finaPrice = _oe.CoffeePrice + 3;
                break;
            case "大":
                finaPrice = _oe.CoffeePrice + 6;
                break;
            default:
                break;
        }
        _customer.Bill += finaPrice;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136

简而言之,委托是事件的底层基础,事件是委托的上层建筑,类似于字段和属性之间的关系,属性包装着字段,通过一系列的逻辑来保护字段。事件也是如此,起到保护委托类型,以免被外界滥用。他只能通过+=、-=来添加和移除事件处理器,不能直接的外部进行访问和调用

------参考原文链接:事件和委托详解_1
------参考原文链接:事件和委托详解_2

  1. Func和Action
  1. 什么是lambda表达式

一个用来代替委托实例的匿名方法(本质是一个方法,只是没有名字,任何使用委托变量的地方都可以使用匿名方法赋值),从而让代码更加简洁
Lambda表达式引用的外部变量叫做被捕获的变量;捕获了外部变量的Lambda表达式叫做闭包;被捕获的变量是在委托被实际调用的时候才被计算,而不是在捕获的时候

//不使用Lambda表达式时的一个点击事件
public class button : MonoBehaviour 
{
    public Button btn;
    void Start () 
    {
        btn.onClick.AddListener(btnClick); 
    }
    void btnClick()
    {
        Debug.Log("按钮被点击了!");
    }
}
-------------------------------------------------------------------

-------------------------------------------------------------------
//使用Lambda表达式时的一个点击事件
public class button : MonoBehaviour 
{
    public Button btn; 
    void Start () 
    {
        //无参匿名函数
        btn.onClick.AddListener(()=> 
        {
        	Debug.Log("按钮被点击了");
        }
    } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
public class LambdaTest : MonoBehaviour
{
    public delegate int MyDelegate(int t);
 
    [ContextMenu("Exe")]
    private void Start()
    {
        /** 实际上,编译器会通过编写一个私有方法来解析这个lambda表达式
         *  然后把表达式的代码移动到这个方法里
         **/
        MyDelegate my = (x) => { return x * x; };//在只有一个可推测类型的参数时,可以省略小括号
        MyDelegate my1 = (x) => x * x;
        int num = my(2);
        Debug.Log(num);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  1. foreach迭代器循环遍历和for循环遍历的区别

For循环通过下标,对循环中的代码反复执行,功能强大,可以通过Index(索引)去取得元素。
Foreach循环从头到尾,对集合中的对象进行遍历。在遍历的时候,会锁定集合的对象,期间是只读的,不能对其中的元素进行修改。
Foreach相对于For循环,其运行时的效率要低于For循环,内存开销也比For循环要高些。但是,在处理一些不确定循环次数的循环或者循环次数需要计算的情况时,使用Foreach更方便些。

  1. is和as
  2. Async和Await的用法
  3. string和stringBuilder和stringBuffer的区别
  4. 什么是GC垃圾管理器?产生的原因?如何避免?
  5. 反射的实现原理
  6. 什么是泛型
  7. Ref和Out关键字有什么区别?Ref的深层原理是什么?
  8. Using的作用
  9. 引用和指针的区别
  10. 结构体和类的区别
  11. 栈和堆的区别
  12. 什么是虚方法?
  13. 异常捕获

Unity

  1. 函数生命周期

Awake->OnEnable->Start->FixedUpdate->Update->LateUpdate->OnDisable->OnDestory

  • Awake 一般用来实现单例,场景开始时每个对象调用一次,如果对象处于非活动状态,则在激活状态后才会调用Awake
  • OnEnable 在启用对象后立即调用此函数,在场景中有可能会重复调用(反复隐藏和激活)
  • start
  • FixedUpdate通常用来进行物理逻辑的更新,其频率常常超过Update。在里面进行应用运动计算时,无需将值乘以Time.deltaTime.因为FixedUpdate的调用基于可靠的计时器(独立于帧率)
  • Update 每帧调用一次。用于帧更新的主要函数
  • LateUpdate通常用来进行摄像机的位置更新,以确保角色在摄像机跟踪其位置之前已经完全移动
  • OnDisable 在隐藏对象后立即调用此函数,在场景中有可能会重复调用(反复隐藏和激活)
  1. 什么是万向锁?如何避免?

一旦选择±90°作为Picth(倾斜)角,就会导致第一次旋转和第三次旋转等价,从而表示系统被限制在只能绕竖直轴去旋转,丢失了一个表现维度;
解决方法:用四元数可以避免万向锁的产生,缺点是耗费一定的内存,但是目标可以任意旋转,自由度高
请添加图片描述

  1. 什么是协程、线程、进程?区别是什么
  2. 物体发生碰撞的必要条件
  3. 碰撞器和触发器之间的区别
  4. 有几种施加力的方式?
  5. 动画分层
  6. 什么是IK?
  7. 游戏动画有哪几种类型,以及其原理?
  • 关键帧动画: 在动画序列的关键帧中记录各个顶点的原位置及其改变量,然后通过插值运算实现动画效果,角色动画较为真实
  • 骨骼动画: 骨骼按照角色的特点组合成一定的层次结构,有关节相连,可做相对运动,皮肤作为单一的网格蒙在骨骼之外,用来决定角色的外观
    -关节动画: 把角色分为若干部分,每一部分对应一个网格模型,部分的动画连接成一个整体的动画,角色比较灵活
  1. 什么是FSM有限状态机?

这里是引用

  1. 在场景中放置多个摄像机且同时处于活动状态会发生什么?

游戏界面中可以看到很多摄像机的混合

  1. unity中摄像机的Clipping Planes的作用是什么?调整Near、Fare的值时要注意些什么?
  2. UGUI的画布有几种模式?分别是什么?
  3. unity中有一个保存读取数据的类,是什么?其中保存读取整形数据的函数是什么?
  4. 如何检测物体是否被其他对象遮挡?
  5. 有几种光源?分别是什么?

区域光源:在实时光照模式下是无效的,仅在烘培光照模式下有用
点光源:模拟电灯泡的照射效果
平行光源:模拟太阳光效果
聚光灯:模拟聚光灯效果

  1. 什么是LightMap?

在三维软件中打好灯光,然后渲染把场景中各表面的光照输出到贴图上,最后通过引擎贴到场景中,从而使得物体有了光照的效果

  1. 两种阴影的判断方法以及工作原理

本影:
半影:

  1. UnityAction和UnityFunc的区别
  2. unity常用资源路径有哪些?
  3. 向量的点乘、 叉乘以及归一化
  4. 什么是LOD?优缺点是?
  5. 什么是MipMap?优缺点是?
  6. 什么是DrawCall?如何降低DrawCall
  7. 当游戏中需要频繁的创建一个物体对象时,该怎么做去节省内存?

这时可以使用对象池,那什么是对象池呢?

  • 对象池中,存放着需要被反复调用资源的一个空间,例如射击游戏中的子弹,大量的Instantiate()来创建新的对象和Destory()销毁对象,对内存消耗极大。
  • 原理:一开始创建一些物体,并将其隐藏起来,对象池就是这些物体的集合,可以是UI中的无限循环列表元素,也可以是物体,当需要使用时,再将其激活使用,超过使用范围时再将其隐藏起来,通常来说,一个对象池存储的都是一类对象(也可以用字典来创建对象池,这样能指定每个对象池存储对象的类型,同时也可以用tag去访问相应的对象池)
using System.Collections.Generic;
using UnityEngine;
public class BulletsPool : MonoBehaviour
{
    //单例,提供全局访问节点,并且保证只有一个子弹对象池存在
    public static BulletsPool bulletsPoolInstance;
    public GameObject bulletObj;//子弹实例
    public int pooledAmount = 50;//预存储对象池的大小
    public bool lockPoolSize = false;//是否可以动态扩容

    private List<GameObject> pooledObjects;//用一个列表来存储对象,即对象池

    private int currentIndex = 0;//当前的索引

    void Awake()
    {
        bulletsPoolInstance = this;
    }

    void Start()
    {
        pooledObjects = new List<GameObject>();//实例化
        for (int i = 0; i < pooledAmount; ++i)//生成对象池并填充
        {
            GameObject obj = Instantiate(bulletObj);
            obj.transform.SetParent(gameObject.transform);
            obj.SetActive(false);
            pooledObjects.Add(obj);
        }
    }
    public GameObject GetPooledObject()
    {
        for (int i = 0; i < pooledObjects.Count; ++i)
        {
        //这里其实是一个贪心算法,当一个对象被激活时,
        //如要激活下一个对象,索引是从上一个对象开始,而不是从头开始查找
            int temI = (currentIndex + i) % pooledObjects.Count;
            if (!pooledObjects[temI].activeInHierarchy)
            {
                currentIndex = (temI + 1) % pooledObjects.Count;
                return pooledObjects[temI];
            }
        }
        //如果没有固定对象池大小,则往列表中添加预制件
        //(因为列表是可以动态扩容的,所以不用担心会出现越界)
        if (!lockPoolSize)
        {
            GameObject obj = Instantiate(bulletObj);
            pooledObjects.Add(obj);
            return obj;
        }
        return null;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
/*用法也很简单,获取对象池中的预制件,如果不为空,则将其激活,并将其的坐标放到开火点,并发射出去,
当其撞到敌人或者墙时,播放相应的粒子特效,然后将其隐藏起来,用来下次激活(这里没有列出代码,原理和激活相同)*/
GameObject bullet=BulletsPool.bulletsPoolInstance.GetPooledObject();       
        if (bullet != null)
        {           
            bullet.SetActive(true);
            bullet.transform.position = firePoint.position;
            bullet.transform.rotation = Quaternion.Euler(new Vector3(0, 0, Random.Range(-15, 20)));
            bullet.GetComponent<Bullet>().SetDirection(Mathf.Sign(transform.localScale.x));
        }     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  1. 如何优化内存?

这里是引用

  1. MeshRender中Material和SharedMaterial的区别

修改Material会重新生成一个新的Material到内存中,然后修改后的物体就使用这个新的材质,但是,在销毁物体时,需要手动去销毁该Material,不然会一种存在于内存中。

修改SharedMaterial将改变所有使用这个材质的物体的外观,并且也会改变存储在工程中的材质设置

  1. Unity资源加载的方式
  2. 如何知道场景中需要加载的数据?如何动态资源加载?
  3. 无限循环滚动列表如何制作?
  4. 什么是渲染管道?
  5. GPU的工作原理?
  6. 在编辑场景时将GameObject设置为Static有何作用?
  7. 有两个物体,如何让一个物体永远比另一个物体先渲染?
  8. 客户端和服务器交互方式有哪几种?
  9. UnityEditor
  10. unityMon

设计模式

  1. 什么是设计模式

设计模式是一套被反复使用、多数人知晓、经过分类编目、代码设计经验的总结。

  1. 设计模式的七大原则
  • 开闭原则:对扩展开放,对修改
  • 单一职责原则:一个类只负责一个功能领域中的相应职责
  • 里氏转换原则:所有引用基类的地方必须能透明的去使用其子类的对象
  • 依赖倒转原则:依赖于抽象,不能依赖于具体实现
  • 接口依赖原则:类之间的依赖关系应该建立在最小的接口上
  • 合成/聚合复用原则:尽量使用合成/聚成,而不是通过继承来达到复用的目的
  • 最少知识原则(迪米特法则):一个软件应当尽可能少的和其他实体发生相互作用
  1. 单例模式

在整个游戏生命周期中,有很多对象从始至终有且只有一个,而着唯一的实例也只需要生成一次,并提供了一种访问其唯一的对象的方法,可以直接访问,不需要实例化该类的对象,直到游戏结束才会被销毁。所以,单例模式一般应用于管理器类或者是一些需要持久化存在的对象。

public sealed class Singleton
{ 
   private static Singleton instance; 
   public static Singleton Instance()
   {
   		get
   		{
   			if(instance==null)
   				instance=new Singleton();
   			return instance;
   		}
   }
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

------参考原文链接:单例模式的使用

单例模式也分为两种模式

  • 饿汉模式:在类加载时就生成该单例对象
    优点:简单方便;线程安全,调用时反应速度快
    缺点:会降低启动速度;不关程序是否使用,都会创建该单例对象
    应用场景:单例对象功能简单,占用内存小,需要频繁使用的时候
public class HungrySingLeton
    {
        private static HungrySingLeton instance;
        
        private HungrySingLeton()
        {
            instance = new HungrySingLeton();
        }
        
        public static HungrySingLeton Instace
        {
            get{return instance;}
        }
    }    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 懒汉模式:第一次调用时才创建该单例对象
    优点:在需要时创建,利用率高;提高启动速度
    缺点:多线程不安全,可能会创建多个实例
    应用场景:单例对象功能复杂,内存占用大,需要快速启动
 public class LazySingleton
    {
        private static LazySingleton _instance;
        
        public static LazySingleton Instace
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new LazySingleton();
                }
                return _instance;
            }
        }
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

------参考原文链接:引用代码链接

  1. 观察者模式
  1. 工厂模式

计算机网络

  1. 什么是UDP和TCP
  2. 什么是TPC/IP
  3. OSI七层模型
  4. 简述下三次握手和四次挥手
  5. 为什么网络要分层?

数据结构

  1. 常用数据容器
  • 栈:
    先进后出,特殊的线性表,只能在表的一个固定端去进行插入和删除
  • 堆:
    特殊的树形数据结构
  • 队列:
    先进先出,特殊的线性表,只能在一端进行插入,另一端进行删除
  • 数组:
    底层是线性表,使用前需声明长度,不安全,是个存储在连续内存位置的相同类型数据项的集合
  • 列表
    底层是泛型数组,可动态扩容,泛型安全
  • 链表
    数据元素按照链式存储结构进行存储的数据结构,内存位置是不连续的。内部有数据元素为节点,每个节点包含两个字段,分别是数据字段和链接字段,通过一个指针链接到它的相邻节点。
  • 字典
    内部使用哈希表作为存储结构,是包含键和值集合的抽象数据类型,每个键都有一个相关联的值;如果试图找到一个不存在的值,则会抛出异常。
  • 哈希表
    不定长的二进制数据通过哈希函数印射到一个较短的二进制数集

  • 包含一个和多个数据节点的集合,其中一个节点被指定为树的根,其余节点被称为根的子节点

  • 可以把图视为循环树,其中每个节点保持他们之间的任何复杂关系,而不是具有父子关系
  1. 树的类型有哪些?

这里是引用

  1. 循环、路径和回路有什么区别?

这里是引用

  1. 二维数组如何存储在内存中?

这里是引用

  1. 什么是死锁?

这里是引用

  1. 什么是浅拷贝?什么是深拷贝?

这里是引用

  1. 文件结构和存储结构有什么区别?

这里是引用

  1. 深度优先遍历和广度优先遍历

这里是引用

  1. 什么是前、中、后序遍历

这里是引用

常见算法

  1. 常见排序算法
  2. 常见查找算法

Lua

SDK相关

网络通信

渲染和图形学

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/95378
推荐阅读
相关标签
  

闽ICP备14008679号