仿星露谷物语开发总结VIP(Unity高级编程知识)
1、生命周期函数
(1)有哪些生命周期函数
Awake/OnEnable/Start/Update/FixedUpdate/LateUpdate
(2)生命周期函数的时间点/次数/顺序
函数名 | 触发时间点 | 顺序 |
Awake | 脚本实例被加载时,1次 | 最早 |
OnEnable | 对象被激活时,多次 | Awake之后 |
Start | Update第一次调用前,1次 | OnEnable之后 |
Update | 每帧调用一次,多次 | Start之后 |
FixedUpdate | 固定时间间隔 | |
LateUpdate | Update之后调用,多次 | Update之后 |
(3)生命周期函数中可以返回IEnumerator类型的是
Start函数,定义为:
private IEnumerator Start(){}
2、物体GameObject相关
(1)当前对象
gameObject或transform.gameObject
(2)实例化物体
Instantiate(GameObject物体, 世界坐标的Vector3位置,旋转方向, 父对象Transform)
后面2个参数可以省略
(3)销毁物体
Destroy(GameObject物体)
(4)查找对象的常用方法
1)通过名称找别的对象:GameObject.Find(“name”)
2)通过标签找别的对象:GameObject.FindWithTag(“Player”)
3)查找一级子节点对象:transform.Find(“name”).gameObject
(5)返回实例ID
GameObject实例.GetInstanceID()
(6)判断自身的激活状态
activeSelf属性
3、transform组件
(1)获取对象的世界坐标位置
transform.position
(2)获取对象的父对象
transform.parent.gameObject
(3)查看对象下的子对象的Transform
transform.Find(名称) 返回Transform类型
(4)物体移动
transfrom.Translate(dir * speed * Time.deltaTime)
4、外部输入
(1)鼠标左键点击一下
Input.GetMouseButtonDown(0)
(2)鼠标左键持续点击
Input.GetMouseButton(0)
(3)按一次键盘A
Input.GetKeyDown(KeyCode.A)
(4)持续按键盘A
Input.GetKey(KeyCode.A)
(5)获取水平方向轴值
float horizontal = Input.GetAxis("Horizontal");
(6)获取垂直方向轴值
float vertical = Input.GetAxis("Vertical");
(7)鼠标的位置
Input.mousePosition
(8)鼠标拖拽的方法
类实现IBeginDragHandler、IDragHandler、IEndDragHandler接口,
然后在OnBeginDrag、OnDrag、OnEndDrag函数中实现功能
(9)鼠标滑过的方法
类实现IPointerEnterHandler, IPointerExitHandler接口,
然后在OnPointerEnter、OnPointerExit函数中实现功能
(10)鼠标点击的方法
类实现IPointerClickHandler接口
然后在OnPointerClick函数中实现功能
5、声音
(1)播放简单流程
首先,有一个Audio Listener组件,一般挂在Main Camera下面。
然后,给对象放Audio Source组件,然后配置声音Clip
接着,通过AudioSource player = GetComponent<AudioSource>()获取组件对象
最后,播放音乐
- 背景音乐:player.clip = xx; player.Play()
- 音效:player.PlayOneShot(yy)
6、碰撞
(1)两个物体碰撞的前提条件
2个条件:
- 两个物体都有碰撞器,比如BoxCollider2D组件
- 其中一个物体有RigidBody组件
(2)获取碰撞物体
private void OnCollisionEnter(Collision collision){
collision.gameObject
}
(3)设置碰撞体不碰撞
在Project Settings中设置碰撞矩阵,设置Layer之间不碰撞
7、标签
(1)自定义新标签方法
使用PropertyAttribute定义新的标签属性
使用PropertyDrawer告诉Unity如果在Inspector中渲染这些属性
(2)[SerializeField]
序列化私有属性
(3)[System.Serializable]
序列化自定义对象
(4)[HideInInspector]
Inspector中隐藏字段,减少视觉混乱
(5)[RequireComponent(typeof(<T>))]
当前脚本执行的前置组件,如果没有会自动添加
(6)[ExecuteAlways]
在编辑模式和播放模式下都能够运行
(7)[ContextMenu]
编辑器模式下工作,右键出现在菜单中,点击会执行对应函数。
8、组件
(1)类和组件的关系
类继承了MonoBehaviour时就变成了组件。
(2)查找组件的常用方法
1)当前对象的组件:GetComponent<T>()
2)当前对象及子对象的组件:GetComponentInChildren<T>()
3)对象父对象链的组件:GetComponentInParent<T>()
4)整个场景中按照类型找组件:FindObjectOfType<T>()
(3)Image组件
(3.1)Type参数:Simple/Sliced/Tiled/Filled
1)Simple:普通图片显示
2)Sliced:切片,将图片分为核心和边缘两部分,核心可以缩放,边缘不进行缩放
3)Tiled:图片正常大小显示,然后调整w/h值会出现剪切、堆叠的效果
4)Filled:适合做特效的填充模式
(3.2)Preserve Aspect属性
图像将保持其原始宽高比进行缩放
(4)Sorting Group组件作用
优化2D游戏中的渲染顺序和性能。
将一组Sprite Renderer作为整体一起绘制从而减少绘制次数,提高渲染效率。
比如角色由身体、衣服等多个部分组成,每个部分都有自己的Sprite Renderer,那么给这个角色这个角色添加一个Sorting Group可以帮助优化这些部分的渲染。
(5)UI布局组件
组件名称 | 功能 | 用途 |
AspectRatioFitter(纵横比适配器) | 保持对象的固定宽高比,影响单个元素 | 图片显示时防止拉伸变形 |
Canvas Scaler | 调整UI元素的缩放比例,影响整个画布 | 根据不同屏幕尺寸自动调整UI元素的大小 |
Vertical Layout Group(垂直布局组) | 将子元素按照垂直方向布局,并设置间距、对齐方式 | 自动排列按钮、文本等UI控件 |
ContentSizeFitter(内容尺寸适配器) | 根据子元素的内容调整父容器的大小 | 子元素数量或大小变化时自动调整父容器的大小 |
(6)TextMeshPro组件
比Text组件更优秀的文本组件
(7)渲染模式screen space-overlay/screen space-camera/world space
Overlay:覆盖在所有内容上,不随摄像机变化,比如菜单栏
Camera:随着摄像机移动,但是保持一定距离不变,比如跟随角色的生命值条
World space:作为游戏对象,比如商店的招牌
(8)RigidBody组件作用
模拟重力、碰撞、力的作用等物理效果
(9)RigidBody的Body Type选项Dynamic/Kinematic/Static
Dynamic:完全受物理引擎控制,受重力、力和碰撞影响。可与其他物体发生物理交互。一般用于角色、敌人、可移动的道具。
Kinematic:不受重力和力影响,位置由Transform或脚本控制(rigidbody2D.MovePosition执行移动)。仍可参与碰撞检测,但不会被物理力推动。一般用于平台(如移动的传送带)、NPC路径移动
Static:完全静止,不受物理影响。仅作为碰撞体存在,不会移动或旋转。一般用于地形、墙壁、固定障碍物。
9、时间Time
(1)一帧的时间
Time.deltaTime
(2)游戏暂停和恢复
Time.timeScale=0f;
Time.timeScale=1f;
(3)时间的数据类型
float
10、事件Event
(1)超过16个参数的事件处理流程
1)定义委托:public delegate void delegateFunc(参数)
2)定义EventHandler类/Event/触发函数
public static class EventHandler{ // 定义类
public static event delegateFunc xxEvent; // 定义事件
public static void callXXEvent(参数){ // 定义触发函数
if(xxEvent != null){
xxEvent(参数)
}
}
}
3)订阅事件:EventHandler.xxEvent += func(参数)
4)发布事件:EventHandler.callXXEvent(参数)
(2)小于16个参数的事件处理流程
没有定义委托这一步
将定义事件换成public static event Action<参数> xxEvent;
其他的和上一个处理流程一致。
(3)OnDisable未取消订阅事件的影响
首先,会导致内存泄漏。若订阅事件的对象被销毁,而事件仍保留对该对象方法的引用,这个对象就无法被垃圾回收。长此以往,内存占用会不断增加。
其次,会造成空引用异常。当订阅事件的对象已被销毁,但事件触发时,仍会尝试调用该对象的方法,这就会引发NullReferenceException,导致游戏崩溃。
然后,会造成方法重复调用。若对象反复启用和禁用,每次启用时都会进行事件订阅。要是没有在禁用时取消订阅,同一方法就会被多次订阅,事件触发时该方法会被多次调用。
11、动画
(1)Animator.StringToHash方法
将字符串转换为哈希值,用于AnimatorController中引用的参数、触发器,减少字符串比较提高性能。
使用方法:
Animator animator = GetComponent<Animator>();
int jumpTrigger = Animator.StringToHash(“Jump”);
animator.SetTrigger(jumpTrigger);
(2)使用触发器触发动画的常见方法
Animator animator = GetComponent<Animator>();
animator.SetTrigger(“xx”);
(3)Animator组件中的Apply Root Motion属性的作用
动画中的位移能够直接应用到角色。
如果不勾选该选项,动画中向前跑则只能原地跑了。
(4)获取当前动画信息
animator.GetCurrentAnimatorStateInfo
可以获取动画名称、持续时间 、当前进度、是否循环等信息
(5)Has Exit Time属性
是动画状态机中两个状态之间过渡的属性。启用该选项,当前动画必须播放到一定比例或时间后才能触发过渡。反选该选项,过渡可以立即生效,无需等待当前动画完成。
12、Tilemap地图系统
(1)创建Tilemap流程的4步
1)在Hierarchy下创建Tilemap,此时会出现Grid和Tilemap对象,可创建多个Tilemap对象进行分层
2)打开Tile Palette面板,把图片拖进去后生成对应的palette
3)使用Bush把palette绘制到Tilemap地图上
4)给Tilemap对象添加Tilemap Collider组件,同时使用Composite Collider优化碰撞体
(2)Grid类的含义
1)它是地图系统的“容器”
2)控制了所有Tilemap子对象的对齐和布局规则
3)一个场景中通常是一个Grid对象
(3)Tilemap类的含义
1)用于绘制瓦片的地图层
2)每个Tilemap是一个网格化的地图层,用于放置Tile(瓦片)
3)一个Grid下可以有多个Tilemap子对象(比如背景层、碰撞层)
(4)世界坐标和网格坐标互转的方法
世界 -> 网格:Grid实例(或Tilemap实例).WorldToCell( worldPosition);
网格 -> 世界:Grid实例(或Tilemap实例).CellToWorld( gridPosition);
(5)Tilemap函数
(5.1)清空所有瓦片
Tilemap实例.ClearAllTiles()
(5.2)设置瓦片
Tilemap实例.setTile(gridPosition, tile实例);
(6)获取网格的世界坐标位置
Grid类. GetCellCenterWorld(gridposX, gridPosY, gridPosZ);
13、Camera
(1)角色在边界显示一半黑屏的解决方案
1)角色由Cinemachine负责跟随显示,需要给该对象添加Cinemachine confiner组件。
2)在该组件中配置Bounding Shape的碰撞体
3)然后在Cinemachine组件中指定extension为cinemachine confiner组件。
14、数据存储
(1)ScriptableObject的功能和文件形式
SO是数据存储的容器。
文件为.asset的格式
(2)ScriptableObject的使用流程
1)创建类继承自ScriptableObject类,类中定义好属性信息
2)在CreateAssetMenu标签中指定fileName和menuName信息
3)在Asset下右击根据menuName和fileName选中选项进行创建
4)在创建的资源中定义好属性信息
5)在别的类的SO字段中拖入该ScriptableObject进行数据读取
(3)自定义类型数据写入文件的类型转换
自定义类型转为可序列化的,
比如Vector3类型不能使用,需要创建自定义的Vector3Serializable,该类型需要通过[System.Serializable]进行标记
15、坐标空间
(1)对比世界坐标/视口坐标/屏幕坐标/网格坐标的英文和作用
坐标空间 | 作用 |
世界坐标World Space | 游戏世界中物体真实位置 |
视口坐标Viewport Space | 相对于摄像机的比例坐标,左下角是(0,0),右上角为(1,1) |
屏幕坐标Screen Space | 像素坐标,比如分辨率为1920*1080,左下角是(0,0),右上角为(1920,1080) |
网格坐标GridPosition | 基于Tilemap的地图系统,左上角是(0,0) |
(2)世界坐标转视口坐标、屏幕坐标的方法
转视口坐标:Camera.main.WorldToViewportPoint(worldPosition)
转屏幕坐标:Camera.main.WorldToScreenPoint(worldPosition)
(3)屏幕转世界坐标方法
Camera.main.ScreenToWorldPoint(screenPosition)
(4)鼠标操作屏幕在哪个坐标空间
屏幕坐标
16、系统函数
(1)判断当前是否在播放模式
Application.IsPlaying(gameObject)返回bool类型
(2)退出游戏及执行环境
Application.Quit();
运行模式下执行,编辑器模式下不成功
17、编辑器函数
(1)代码中变更SO数据通知编辑器变更
EditorUtility.SetDirty(SO对象);
(2)定义编辑器模式下执行的代码
#if UNITY_EDITOR
#endif
18、UI
(1)全透明颜色
Color.clear
(2)Pixels Per Unit
每个单位像素数量
在U3D空间中,一个单位通常被设计为一米。
(3)2D纹理类
Texture2D
(4)获取纹理中的像素信息的使用方法
Texture2D实例.GetPixels(),返回Color[]类型
(5)设置纹理中的像素信息的使用方法
Texture2D类型.SetPixels(Color[]类型数据);
Texture2D类型.Apply(); // 使之生效
(6)纹理(Texture)和材质(Material)的区别
纹理 是粒子的 “皮肤”,定义外观细节。
材质 是粒子的 “渲染规则”,决定如何显示纹理。
(7)Texture类的filterMode属性
使用方法:对纹理在缩放时的采样方式进行控制。
Point(最近邻):纹理在缩放过程中,会保留清晰的像素边缘,比较适合像素艺术风格的游戏
Bilinear(双线性):通过对相邻像素进行平均处理,使纹理边缘看起来更加平滑
Trilinear(三线性):和双线性类似,但在处理Mipmap层级之间的过渡时效果更好
19、粒子系统
(1)怎么产生粒子特效
Hierarchy下创建一个物体,该物体添加Particle System组件,配置相关参数
该物体放到哪里,哪里就会有相应的粒子特效。
(2)重要参数
参数名 | 作用 |
Duration | 整个粒子系统的存在时间 |
Start Lifetime | 单个粒子的存活时间 |
Gravity Modifier | 重力对粒子的影响程度,正值向下,负值向上 |
Simulation Space | 粒子系统的坐标空间, Local:局部空间,跟着发射器一起动 World:世界空间,粒子一旦生成就脱离发射器的影响 |
Emission-Rate Over Time | 每秒发射的粒子数 |
Emission-Bursts | 一次性发射粒子的数量 |
Shape – Shape | 粒子发射器的形状 |
Texture Sheet Animation | 将一个纹理图集应用到粒子系统上,一般是选图片 |
Renderer | 渲染方式,一般选图片方式,并且设置Sorting Layer |
20、协程
(1)协程用法最简单写法
构造协程方法:
IEnumator routerFunc(){
// do something
yield return new WaitForSeconds(2f);
}
使用方法:
StartCoroutine(routerFunc());
(2)yield return null的含义
协程暂停执行直到下一帧
100、难点解读
(1)GetAxis和GetAxisRaw的异同
相同:都是获取输入轴上的值
不同:
特性 | GetAxis | getAxisRaw |
返回值范围 | [-1,1]浮点数 | 只有-1,0,1 |
是否平滑 | 是(有加速/减速效果) | 无,立即切换 |
输入延迟 | 有 | 无 |
常用场景 | 平滑移动、模拟摇杆控制 | 精确控制 |
(2)角色移动的方法差异
组件 | 方法 | 优点 | 缺点 |
Transform组件 | 修改transform.position | 简单 | 没有物理特性 |
Transform组件 | transform.Translate(Vector3.forward * speed * Time.deltaTime); | ||
Rigidbody组件 | Rigidbody对象.velocity=dir * speed | 直接控制速度 | 干扰正常物理模拟 |
Rigidbody组件 | Rigidbody对象.AddForce(dir*力量) | 模拟真实推力 | |
Rigidbody组件 | rb.MovePosition(pos) | 保持物理特性的平滑 | |
CharacterController组件 | characterController.Move(moveDirection * speed * Time.deltaTime); | 简单碰撞检测及爬坡 | 物理特性功能有限 |
(3)gameObject和GameObject的区别
1)gameObject
MonoBehaviour类自动提供的一个属性,指向当前脚本的GameObject实例。
比如gameObject.name可以获取实例名称
比如gameObject.AddComponent<XX>()可以添加组件
2)GameObject:
Unity引擎的一个类,用于创建和操作游戏对象
比如GameObject.Find来查找对象
(4)onXxDrag、OnPointerXx、OnPointerClick函数是否出现在自身脚本的区别
如果对象脚本中包含这两个函数,则可以在函数中获取对象的属性。
如果要获取其他对象的属性,则需要使用PointerEventData的pointerCurrentRaycast.gameobject获取对方物体的信息
(5)2D场景中检测碰撞方法对比
方法名称 | 功能 | 是否分配内存 | 使用建议 |
Physics2D.OverlapBoxAll(...) | 检测矩形区域内所有Collider2D | 分配新数组 | 简单易用,适合不频繁调用 |
Physics2D.OverlapBoxNonAlloc(...) | 检测矩形区域内所有Collider2D | 不分配新内存 | 高频调用时推荐,避免GC |
Physics2D.OverlapPointAll(...) | 检测某一点上的所有Collider2D | 分配新数组 | 点击检测、鼠标拾取等 |
(6)AnimationClip和AnimationControl的区别
AnimationClip是一个具体的动画数据文件,存储了对象在时间线上的属性变化(比如位置、旋转、缩放、材质参数等)。
AnimationControl是一个状态机工具,用于管理多个AnimationClip之间的逻辑和过渡(如切换条件、混合、层级)。
101、C#知识
(1)Invoke方法
延迟执行某个不带参数的方法
Invoke("MyMethod", 2f); // 2秒后调用 MyMethod 方法
(2)委托函数
定义:一种类型安全的函数指针。 声明方式:public delegate 返回类型 委托名(参数类型 参数名, ...); // 就是在返回类型之前增加了delegate关键字。
定义:委托对象使用+运算符合并相同类型的委托。
作用:创建一个委托要调用方法的列表,然后依次执行。
委托机制允许委托类型方法的参数作为参数传递给其他方法。
(3)为什么不用Delegate构造事件
Delegate对象在订阅事件时直接用=而不是+=,则会影响其他订阅的服务。
Event是一种特殊的delegate,限制订阅者只能通过+=进行订阅,否则编译报错。
(4)Event触发时为什么要判空
如果没有订阅者,则Event实例没有初始化,此时触发会报未引用的异常。
(5)virtual和abstract函数的区别
Virtual:虚拟的模板,提供了一个默认的实现,允许派生类根据需要进行重写(override),也可以不重写。
Abstract:抽象的概念,仅声明了方法,没有实现,强制非抽象派生类必须提供实现。
(6)Dictionary获取值的方法
Dictionary实例.TryGetValue(key, out value)
(7)获取类型的默认值
default()函数
(8)类型转换函数
as
(9)Mathf. Approximately方法
Mathf.Approximately(float a, float b)
作用: 用于比较两个浮点数是否“近似相等”。
由于浮点数在计算机中的精度问题(如 0.1f + 0.2f != 0.3f),直接使用 == 比较两个浮点数可能会导致误差。Mathf.Approximately 提供了一种更安全的方式来判断两个浮点数是否足够接近,可以认为是“相等”。
(10)Mathf.MoveTowards方法
Mathf.MoveTowards(float current, float target, float maxDelta)
作用: 将一个值从当前值向目标值移动,但不会超过指定的最大步长。 这个方法常用于平滑地改变某个值(比如位置、角度、速度等),避免一次性跳变到目标值。
参数:
current: 当前值。
target: 目标值。
maxDelta: 每次调用允许移动的最大步长(正值)。
102、架构设计
(1)角色移动的动画设计
1)角色分多个身体部位,每个身体部位一个Animator,动画入参保持一致。
2)在Update中计算移动速度及身体部位的状态值,同时触发动画事件实现走路/跑步。
3)在FixedUpdate中通过Rigidbody组件的MovePosition方法实现移动。
(2)实现树木淡入淡出的设计
1)给树木添加Box Collider组件,并设置为trigger
2)添加自定义的Fader脚本组件,在该脚本中提供淡入淡出的函数,通过SpriteRenderer组件逐步改变透明度实现淡入淡出。
3)给角色添加另一个TriggerFade自定义组件,在OnTriggerEnter函数中触发每个树木的淡入淡出函数
(3)从库存栏中选中Item拖到游戏世界中的思路
1)创建拖拽用的item预制体
2)开始拖Item时,创建预制体实例,把该Item的信息赋值到预制体实例上
3)拖的过程中,让预制体实例的position等于Input.mousePosition
3)拖结束时,销毁预制体实例,并在游戏世界item们的父transform中创建item实例
(4)在库存栏中交换两个item的思路
1)开始拖Item时获取该slotNumber索引值
2)结束拖时获取停止时的toSlotNumber索引值z
3)在库存列表中交换两个索引值对应的信息
4)触发更新库存信息事件
(5)idle状态动画arm部位替换为carry模式的思路
1)获取所有的Animator组件,找到所有arm部位的动画clip,全部映射到carry模式的动画clip。
2)通过动画覆盖控制器让映射生效,此时Animator状态机中都是carry模式的动画了
3)通过isCarry的值调整动作Event的参数,使得动画发生变更
(6)保存游戏状态的思路
提供状态保存的接口,包括类型ID、数据结构、注册/注销接口、保存/恢复接口。
每个类型的状态需要继承该接口,
编写SaveLoadManager类,接收注册的类的数据,统一操作每个类型的保存和恢复,
在合适的位置(比如切换场景)执行SaveLoadManager的保存和恢复。
(7)保存地面属性的思路
在Hierarchy中增加GridProperties对象,其下是各个地面属性Tilemap对象。
生成一个脚本,当该脚本Disable时,会扫描tilemap组件并保存到gridProperties的SO实例中。
然后GridProperties对象下每个地面属性tilemap对象都挂载该脚本,并在脚本组件中指定地面属性信息为自己的属性。
(8)Dug时地面图形变化思路
根据当前Dug的grid的上下左右计算当前grid的图片,
然后再调整上下左右4个grid的图片
(9)对象池的设计思路
对象池用于快速产生预制体对象,提高性能。
设计Dictionary<int, Queue<GameObject>> poolDictionary用于存储对象池,int是预制体的id,Queue<GameObject>是预制体实例化之后的队列。
每次获取对象时,从队首获取实例,同时队尾再增加一个实例。
同时将获取的实例进行position,rotation等设置。
(10)农作物生长的思路
创建一个CropDetails类,记录农作物每个阶段的时间、图片等信息。
然后创建SO配置所有农作物的Details信息。
农作物接收天数变化的事件,然后根据每个阶段的时间更换对应的图片。
(11)A*算法构建路径的思路
G为起点到当前点的路径值,H为当前点到终点的预测值,F=G+H
每次取节点四周最小的F值的点放入openPathList,走过的点放入closedPathList中
然后每次取openPathList中最小F的点计算四周的F值,直到走到终点
可以在节点中标记obstacle属性以及isPath属性,对于非isPath的属性可以在F中加入惩罚系数。
(12)NPC移动的思路
计算每秒钟需要达到的位置,然后每秒钟接收到事件后移动到对应位置。
(13)NPC跨场景移动的思路
提前准备好场景间移动的起/终点数据,每个场景的起/终点一条路径,多条路径组成一个场景间的数据,多个场景间的数据汇总配置到SO容器中。
路径中的起/终点取NPC的位置数据,则值特别大,否则会固定的数据。
在每个场景中分别进行NPC移动。
(14)基于对象池播放声音的方法
常见Sound组件放到对象池中。
Sound组件需要输入音乐名称以配置AudioClip
在OnEnable中播放音乐,在OnDisable中停止音乐(协程方法,一段时间后停止)
外部播放音乐只需要启用/关闭Sound组件即可。
103、Unity按钮操作
(1)Tilemap左右翻转/旋转
左右翻转:Shift + [
旋转:]
(2)同时打开2个场景并激活第2个场景的方法
第2个场景以附加形式打开,右击第2个场景选择”Open Scene Additive”。
第2个场景右击选择“Set Active Scene”。
(3)Inspector中选中Anchor的方法
按住Shift + Alt进行选取
(4)Screen界面中聚焦某个物体的方法
在Hierarchy中选中某个物体按F键
(5)预制体创建变体
预制体右键 Create -> Prefab Variant
(6)Animator界面移动界面
按住Alt键后左键移动
200、常用代码片段
(1)鼠标控制移动的方法
void Update()
{
if (Input.GetMouseButtonDown(0))
{
// 按下鼠标左键发射射线
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// 声明一个碰撞信息类
RaycastHit hitInfo;
// 碰撞检测
bool res = Physics.Raycast(ray, out hitInfo);
// 如果碰撞到的情况下,hitInfo就有内容了
if (res)
{
transform.position = hitInfo.point;
}
}
}
(2)单例模式类
public abstract class SingletonMonobehaviour<T> : MonoBehaviour where T: MonoBehaviour
{
private static T instance;
public static T Instance { get { return instance; } }
protected virtual void Awake()
{
if (instance == null)
{
instance = this as T;
}
else
{
Destroy(gameObject);
}
}
}
(3)AnimatorOverrideController动画重写控制器的2种使用方法
1)一般使用方法
定义变量:AnimatorOverrideController animatorOverrideController;
初始化变量:animatorOverrideController = new AnimatorOverrideController(animator.runtimeAnimatorController);
animator.runtimeAnimatorController = animatorOverrideController;
通过上面的赋值,后续对animatorOverrideController的修改将直接作用于animator.runtimeAnimatorController(运行态的动画控制器)。
运行中更新动画:animatorOverrideController["shot"] = weaponAnimationClip[weaponIndex];
2)每帧更新多个动画剪辑
使用AnimatorOverrideController.ApplyOverrides方法,它能够将动画器剪辑绑定重新分配的数量减少到每次调用只有一个。
定义新老动画的映射:List<KeyValuePair<AnimationClip, AnimationClip>> clipOverrides;
初始化:animatorOverrideController.GetOverrides(clipOverrides);
运行中更新动画:animatorOverrideController.ApplyOverrides(clipOverrides);
其中ApplyOverrides函数:对该动画器重写控制器应用写列表
GetOverrides函数:获取该动画器重写控制器中当前定义的动画剪辑重写的列表。
(4)计算一秒钟的方法
private void GameTick()
{
gameTick += Time.deltaTime;
if(gameTick >= 0.012f)
{
gameTick -= 0.012f; // 减完重新开始
UpdateGameSecond();
}
}
(5)附加方式加载Scene并设置Active的方法
SceneManager.LoadSceneAsync(xx, LoadSceneMode.Additive);
SceneManager.SetActiveScene(xxScene);
(6)获取GUID的方法
string _gUID = System.Guid.NewGuid().ToString();
(7)调整给定点位置以适应屏幕像素密度的方法
RectTransformUtility.PixelAdjustPoint(Vector2 point, Transform elementTransform, Canvas canvas);
- point:需要调整的点的屏幕坐标位置
- elementTransform:与point点关联的Transform或RectTransform。这个参数用于确定如何根据其父级对象和画布来调整点的位置
- canvas:用于获取当前画布的缩放信息和其他相关属性。Canvas决定了UI元素如何被渲染以及它们相对于屏幕的比例关系。
(8)在指定2D矩形区域查找所有包含特定组件的游戏对象
public static bool GetComponentsAtBoxLocation<T>(out List<T> listComponentsAtBoxPosition, Vector2 point, Vector2 size, float angle)
{
bool found = false;
List<T> componentList = new List<T>();
Collider2D[] collider2DArray = Physics2D.OverlapBoxAll(point, size, angle);
// Loop through all colliders to get an object of type T
for(int i = 0; i < collider2DArray.Length; i++)
{
T tComponent = collider2DArray[i].gameObject.GetComponentInParent<T>();
if(tComponent != null)
{
found = true;
componentList.Add(tComponent);
}
else
{
tComponent = collider2DArray[i].gameObject.GetComponentInChildren<T>();
if(tComponent != null)
{
found = true;
componentList.Add(tComponent);
}
}
}
listComponentsAtBoxPosition = componentList;
return found;
}
(9)保存/加载文件
【保存到文件】
public void SaveDataToFile()
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(Application.persistentDataPath + "/WildHopeCreek.data" , FileMode.Create);
bf.Serialize(file, <数据内容>);
file.Close();
}
【加载文件】
public void LoadDataFromFile()
{
BinaryFormatter bf = new BinaryFormatter();
if(File.Exists(Application.persistentDataPath + "/WildHopeCreek.data"))
{
FileStream file = File.Open(Application.persistentDataPath + "/WildHopeCreek.data", FileMode.Open);
gameSave = (GameSave)bf.Deserialize(file); // 转换数据类型
file.Close();
}
}
(10)AnimationClip类使用示例
using UnityEngine;
using UnityEditor; // 仅在编辑器环境使用
public class AnimationClipExample : MonoBehaviour
{
[ContextMenu("Create Animation")]
public void CreateSimpleAnimation()
{
// 创建新的动画剪辑
AnimationClip clip = new AnimationClip();
clip.name = "SimpleFloat";
clip.frameRate = 60;
// 设置循环模式
AnimationClipSettings settings = AnimationUtility.GetAnimationClipSettings(clip);
settings.loopTime = true;
AnimationUtility.SetAnimationClipSettings(clip, settings);
// 创建位置Y轴的动画曲线
AnimationCurve curve = new AnimationCurve();
curve.AddKey(0f, 0f); // 时间0,Y=0
curve.AddKey(1f, 1f); // 时间1,Y=1
curve.AddKey(2f, 0f); // 时间2,Y=0
curve.postWrapMode = WrapMode.Loop; // 设置循环模式
// 将曲线应用到Y轴位置
clip.SetCurve("", typeof(Transform), "localPosition.y", curve);
// 保存动画文件到项目
AssetDatabase.CreateAsset(clip, "Assets/SimpleFloat.anim");
AssetDatabase.SaveAssets();
Debug.Log("动画创建成功!路径:Assets/SimpleFloat.anim");
}
}
【使用步骤】
1)将此脚本挂载到任意GameObject上
2)在Inspector面板右键点击脚本组件
3)选择“Create Animation”菜单项
4)项目中会自动生成"SimpleFloat.anim"动画文件
【查看效果】
1)创建Animator Controller
2)将生成的动画文件拖入控制器
3)给物体添加Animator组件并关联控制器
4)运行游戏即可看到物体上下浮动效果