记录Unity和C#遇到的坑(持续更新)

UI图是完整的, 但导入Unity后UI图残缺了

在这里插入图片描述
把MeshType改成Full Rect

美术只给了左边一半UI, 用2张图在Unity里拼, 然后发现中间有缝

在这里插入图片描述
把 Filter Mode 改成 Point(no filter)
MeshType 也要改成 Full Rect

改特效的startLifetime

特效是沿直线发射粒子, 需求是用startLifetime控制特效的长度

这样, 长度不是瞬间达到目标长度, 而是会缓慢变化到目标长度
eff.main.startLifetime = 10

所以要将特效的时间前进1, 使其立即突变为目标长度
eff:Simulate(1)
eff:Play()

关闭多点触控

Input.multiTouchEnabled = false;

关闭之后Input.touches[n]就不好用了

Editor扩展UI面板

[CustomEditor(typeof(Transform), true)] --[CustomEditor(typeof(Text), true)] -- 不行

解决方法:
自己写个脚本MyText继承Text, 然后再扩展

using UnityEditor;
using UnityEngine;
using UnityEngine.UI;

[CustomEditor(typeof(MyText), true)]
[CanEditMultipleObjects]
public class AdaptiveTextEditor : UnityEditor.UI.TextEditor
{
    Text txt;

    protected override void OnEnable()
    {
        base.OnEnable();
        txt = target as Text;
    }

    public override void OnInspectorGUI()
    {
        serializedObject.Update();
        base.OnInspectorGUI();
        serializedObject.ApplyModifiedProperties();

        GUILayout.BeginHorizontal();
        if (GUILayout.Button("test"))
        {
            txt.text = "逼傻是念着倒";
            txt.alignment = TextAnchor.MiddleCenter;
            txt.raycastTarget = false;
        }
        GUILayout.EndHorizontal();
    }
}

ref 和 out 和 in

in传入参数, out传出参数, ref传入传出参数

ref与out 都是为了让函数有多个返回值

ref 必须在方法外赋值
out 必须在方法内赋值
in 必须在方法外赋值, 在方法内不可写入

手机游戏画面轻微抖动

开发安卓游戏, 用小米10做测试机, 拖拽大地图的时候, 画面轻微抖动
查了半天代码, 没找到原因
然后换了台小米8测试, 不抖了
之所以为什么会这样的原因竟然是因为屏幕刷新率的问题!
小米10的屏幕刷新率是90赫兹, 改为60赫兹, 就不抖了

数据结构: 跳表

构建:
把原始链表每间隔1个数据拿出来, 形成第一级索引表
把第一级索引表间隔1个数据拿出来, 形成第二级索引表
依次形成多级索引表, 最后形成树状结构

优点:
空间换时间, 二分查找, 速度快, 时间复杂度为O(logn)
在这里插入图片描述

遍历一个枚举

用Enum.GetValues()

using System;

foreach (panelsDef panel in Enum.GetValues(typeof(panelsDef)))
{
	DoSomething(panel);
}

SVN提交原则: 1次只提交1个功能!

一旦你同时提了多个功能, 然后又想分离出来, 会超级麻烦
所以
要分别改, 分开提, 不要同时提

设计模式的六大原则:

开闭原则:实现热插拔,提高扩展性。
里氏代换原则:实现抽象的规范,实现子父类互相替换;
依赖倒转原则:针对接口编程,实现开闭原则的基础;
接口隔离原则:降低耦合度,接口单独设计,互相隔离;
迪米特法则,又称不知道原则:功能模块尽量独立;
合成复用原则:尽量使用聚合,组合,而不是继承;
摘自: https://www.runoob.com/design-pattern/design-pattern-intro.html

一些C#效率

if elseif会执行多次判断
switch只判断一次,效率较高

foreach写起来比for方便
foreach是只读的, 不能增删改
foreach效率比for要高 (因为for要检查index是否有效)
但是foreach会产生更多GC (每遍历过一个元素就释放它)

ToUpper()和ToLower()会创建一个新的字符串
bool.Parse()和Compare(), 是忽略大小写的比较

手机语言

众所周知: zh是中文, en是英文

下面这些你可能不知道:
zh-Hans 全宇宙通用简体中文
zh-Hans-CN 大陆简体中文
zh-Hans-TW 台湾简体中文

zh-Hant 全宇宙通用繁體中文
zh-Hant-CN 大陸繁體中文
zh-Hant-TW 臺灣繁體中文

为了区分是简体还是繁体, 会用到Hans或Hant

空字符判断 的效率

str.Length == 0; 速度最快, 但是要求str必不能为null
str == String.Empty; String.Empty的内部也是个""
str == ""; 速度最慢, 因为要新创建一个"", 再做对比

安卓Input.GetMouseButton(0)

在安卓平台上也可以使用Input.GetMouseButton(0)来接收手指的点击事件
不过只能接收一个点击
如果你用多个手指点击的话, 会取多指之间的中点作为Input.mousePosition

C#列表深拷贝

列表的深拷贝可以用:
1.反射
2.先ToArray(), 再把这个array作为参数传入一个new List
3.ForEach()一个一个赋值到新列表

将Vector3.right 饶Z轴 旋转angle度

 Vector3 dir = Quaternion.Euler(new Vector3(0, 0, angle)) * Vector3.right;

帧数限制

计算量很少, 渲染量也很少
但是, 帧数很低, WaitForTargetFPS开销很大
原因: 看你是不是设置了帧数: Application.targetFrameRate

拖尾Trail Renderer改颜色

Trail Renderer 在unity编辑器里可以动态修改材质球的颜色
但打包成安卓就不行了
解决方法:
写个可变色的shader, 改shader的颜色

一些优化

不透明的东西, 不要用带透明通道的着色器
不需要写入的时候, 关闭纹理的"可写"选项
2D游戏里, 用不到mipmap, 3D游戏里大部分也用不到, mipmap和LOD一样是用内存换渲染

小音频用Decompress On Load, 在内存中不压缩, 快, 但是占内存多
大音频用Compressed In Memory, 在内存中压缩, 占内存小, 但是慢(因为需要一个解压的过程)
Streaming是I/O操作, 不占内存, 如果你项目中的音频占很多内存, 就用这个

关于接口的坑

先定义个接口

public interface ITest
{
    void AFun();
}

如果, 既要静态方法又想要动态方法, 像这样↓写是不行的

public partial class TestInterface : ITest
{
    public void AFun()
    {
        Debug.Log("动态 A");
    }
    public static void AFun()
    {
        Debug.Log("静态 A");
    }
}

↑这样写的话, 以下2种调用方式都会报错:
调用不明确(你有2个AFun()方法, 我不知道调用哪一个)

    void Start()
    {
        TestInterface t = new TestInterface();
        t.AFun();
        
        TestInterface.AFun();
    }

所以应该怎么写呢? 嗯, ↓要这样写才行

public partial class TestInterface : ITest
{
    //注意这里不能写public, 因为接口本来就是public
    void ITest.AFun()
    {
        Debug.Log("动态 A");
    }
    public static void AFun()
    {
        Debug.Log("静态 A");
    }
}

调用的时候这样调↓

    void Start()
    {
        TestInterface t = new TestInterface();
        //这里需要强转
        ((ITest)t).AFun();
        
        TestInterface.AFun();
    }

构造函数的特殊用法

public class Structure
{
    public Structure(int num) : this(new int[] { num })
    { }

    public Structure(int[] nums)
    {
        Debug.Log("不管你传参是int还是int[], 都走这个方法, 参数为: " + nums[0]);
    }
}

垃圾回收的3种方式

  1. 计数法, 添加引用+1, 销毁引用-1, 完事把标记为0的内存释放掉, 缺点: 互相引用无法被释放, 计数的储存
  2. 标记法, 遍历内存, 有引用的就做标记, 完事把没有标记释放掉, 缺点: 遍历开销, 需要暂停, 释放内存造成不连续
  3. 复制法, 只遍历在用的内存, 把这些内存复制到新的区域, 内存连续, 缺点: 复制开销, 需要保留区域用来复制

where

where字句后面有new()约束的话,T类型必须有public且没有参数的构造函数。

长度和占位

bit: 位, 即0或1
byte: 字节, 1个字节是8位, 即2^8, 即[0-255], 因为要分正负号, 就得再除以2, 即[-128, 127]
bool: 占1位
char: 占1个字节
short: 占2个字节, 即16位
int: 占4个字节, 即32位, 即2^32, 即[-2147483648, 2147483647]

(为什么负数比正数多一位呢, 因为多一个负0
正0的2进制为: 0000 0000 0000 0000
负0的2进制为: 1000 0000 0000 0000, 就用来表示-2147483648了


float是32位, int也是32位, 但float只能保证6-7位有效数字, 为什么:

1bit(符号位) 8bits(指数位) 23bits(尾数位)
精度是由尾数的位数来决定的
2^23 = 8388608,一共七位
这意味着最多能有7位有效数字,第7位是8, 保证不了, 所以绝对能保证的为6位
所以float的精度为6~7位有效数字;

C#里的long和Int64一样

分辨率

我们说的视频1080p, 是指视频长宽1920×1080 = 2073600个像素, 也就是我们常说的: 手机拍照200万像素
2k 是 2560x1440
4k 是 3840x2160
注意: 以上, 表示在16:9的宽高比时的数值

bps和Bps

bps: bits per second, 传的是bit, 就是位, 就是0101010111
注意: 在计算传输速率时是以1000进的, 即1M bps = 1000K bps

Bps: Byte per second, 字节, 1M Bps = 1024K Bps
2个G的葫芦娃动画片, 就是2G Byte

你家的网速是10M
10M就是说10 000 000位每秒, 而1个字节是8位, 10 000 000 ÷ 8 = 1 250 000字节
所以实际下载速度仅仅是每秒1.25M

那个10M是唬你的, 是10101000111000的传输速度
你真正关心的是2个多G的葫芦娃要下载多久, 这里的每秒1.25M才是你需要的

K M G T …

1KB (Kilobyte 千字节)=1024B,
1MB (Megabyte 兆字节 简称“兆”)=1024KB,
1GB (Gigabyte 吉字节 又称“千兆”)=1024MB,
1TB (Trillionbyte 万亿字节 太字节)=1024GB,其中1024=2^10 ( 2 的10次方),
1PB(Petabyte 千万亿字节 拍字节)=1024TB,
1EB(Exabyte 百亿亿字节 艾字节)=1024PB,
1ZB (Zettabyte 十万亿亿字节 泽字节)= 1024 EB,
1YB (Jottabyte 一亿亿亿字节 尧字节)= 1024 ZB,
1BB (Brontobyte 一千亿亿亿字节)= 1024 YB.
抄自: https://xuexi.zqnf.com/844056.html

const 和 readonly的区别

readonly可以用构造函数赋值

拼url

string str = “戴拿”;
string.Format(“{0}”, str);
那如果我想拼一个"{“进去呢, 会报错怎么办?
那就得打上”{{", 就表示{

空格表示为%20, 双引号表示为%22
https://blog.csdn.net/superit401/article/details/78052965

IList和List的区别:

List, 她里面有Sort, CopyTo, FindIndex, Reverse, Contains, ForEach, Add等等一堆方法

public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable
{
	//(方法太多就不贴了, 想看? 在List身上按F12进去看)
}

而IList, 她只有以下功能

public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable
{
    T this[int index] { get; set; }
    int IndexOf(T item);
    void Insert(int index, T item);
    void RemoveAt(int index);
}

人家List会的本领: 排序, 翻转, 复制, 判断包含等, IList全都不会, 她只能用来存放数组, 只是一个可怜的容器
既然IList远不如List强大, 那什么情况下用IList而不用List呢?
答: 需要节省的时候

极端的UI优化

用全屏UI时, 禁用世界相机
非全屏UI时, 用世界相机截屏, 把截图铺在UI底下做背景, 然后禁用世界相机
(我记得生化危机5里, 一章结束后, 画面会定格, 然后出结算界面, 估计就是这样弄的)

不需要进行点击交互的组件,取消勾选Raycast Target

CullTransparentMesh: 新版本勾上这个, 图片的Alpha为0时不会Overdraw (比如做"点击屏幕任意位置"的时候)

Static的列表不用的时候记得Clear()

一直占内存
你下次用的时候, 里面存的东西还在里面躺着

Json的读取和保存

方法非常多
1.用JsonUtility
注意!
要用[System.Serializable]特性, 序列化才能转成Json;
因为是轻量型, 不能读存多维数组
用法: JsonUtility.FromJson();

2.用Newtonsoft.Json;
要下载dll
可解析多维数组, 形如List<List<int>>

	using Newtonsoft.Json;
	//类存为Json
	public static void SaveJson(MapData mapData)
    {
        if (!File.Exists(JsonPath()))
        {
            File.Create(JsonPath());
        }

        string json = JsonConvert.SerializeObject(mapData);
        File.WriteAllText(JsonPath(), json);
        //Debug.Log("保存成功");
    }
    
	//读取Json
    public static MapData ReadJson()
    {
        if (!File.Exists(JsonPath()))
        {
            Debug.LogError("文件不存在");
            return null;
        }

        string json = File.ReadAllText(JsonPath());
        MapData mapData = JsonConvert.DeserializeObject<MapData>(json);

        return mapData;
    }

改变2D Sprite的颜色

block.GetComponent<SpriteRenderer>().color = Color.red;

物体从相机中消失了

看相机的Layer有没有取消勾选
看相机有没有勾选OcclusionCulling(遮挡剔除)

自定义yield

很好的文章:
https://blog.csdn.net/qq_31967569/article/details/83305415

一个方法0个引用, 但是在执行, 有4种情况

0 是生命周期方法
1 用了Invoke(“方法名”, 1f); 这时候Ctrl+F搜一下方法名就行了
2 直接把方法挂在物体上了, 找吧
3 方法挂在了动画的Event上, 今天遇到这个坑, 这时候Animation就变成了ReadOnly, 但是Event仍然可以在模型上找到那个动画的Event来改

代码调整画质

//设置画质
int qualityLevel = 0;//即Very Low
QualitySettings.SetQualityLevel(qualityLevel, true);
//Get画质
QualitySettings.GetQualityLevel();

在Hierarchy面板中, 给一个List添加多个GameObject

public List<GameObject> equips;

Hierarchy面板中就会显示这个list

你的做法: 在Size输入10, 然后把10个物体一个一个依次拖进来, 对吧?
简单方法: 先锁住这个Hierarchy, 直接选中多个物体, 一次性拖进list中

Switch 我赌很多人不知道这个

		int a = 0;
        switch (a)
        {
            case 0:
            	//这里不写break;
            case 1:
                //0和1都会执行Do();
                Do();
                break;
            case 2:
                break;
            default:
                break;
        }

Application.OpenURL( );

↓打开浏览器并打开百度页, 这个↓"http://"加不加都行

Application.OpenURL("http://www.baidu.com");

↓打开E盘

Application.OpenURL(@"file://E:\");

↓打开手机微信

Application.OpenURL("weixin://");

实现相机拍照的几种思路

鼠标点击, 射线检测
OnVisible(); 判断是否在视野中
Vector3.Diatance(); 判断距离
Vector3.Angle(); 判断角度
Physics.BoxCastAll(); 发一个方形射线, 在打到的数组里遍历查找

一个方法里打了2个断点, 结果第1个走了, 第2个竟然没走

你第2个断点打到 if 里去了, 而它走的 else

有个办法可以让你敲代码的速度提升2%

扣掉键盘上的"Caps Lock"和"Insert"键

struct是值类型

由于是存在栈中, 所以比class快
小体积的玩意就用struct
比如Vector3就是个struct

static 和 const

static: 静态变量, 永久占内存, 存的数据可改
const: 常亮, 不可改

继承mono

只有继承mono, 才能挂在场景里的物体上;
继承mono, 就不能用构造函数了;
为了性能, 能不继承就不继承;
继承mono的类, 类名 和 文件名 要同名
(比如, 你有一个Mgr.cs文件, 里面必须要写有 class Mgr : MonoBehaviour{})

什么时候需要继承:
需要用到Awake Update OnEnable OnTriggerEnter等方法时;

录制包含子物体的动画

如果想录的动画包括子物体的移动旋转等, 要确保这个子物体身上没有Animator

正则

把所有 “不是数字的字符” 都换成 “.”

string str = "1,2,的撒发三份122,提问2,4,为前提";
string result = Regex.Replace(str, @"[^0-9]+",".");
print(result);
//输出1.2.122.2.4

我竟然不知道这些方法???

transform.SetPositionAndRotation(Vector3, Quaternion);
dotween.SetDelay(float);
transform.GetChild(string);
Undo.RecordObject(object, string);//Editor
list.IndexOf(T item)
Quaternion.Euler(Vector3);//传一个欧拉角进去
Physics2D.queriesStartInColliders = false;//2D射线不检测自身
//还有, 2D射线不需要Ray
dotween.SetUpdate(true);//使DOTween不受TimeScale的影响
dotween.OnUpdate( ()=>DoSomething() );//tween过程中每帧执行DoSomething();
CustomYieldInstruction //继承此类能实现自定义的yield
list.TrueForAll(x => x < 10);//返回bool

类型转化

↓类型一致的转化, 比如数值 float, int, double, enum等, 也就是"强制转换"

float f = 1.2f;
int i = (int)f;//截取整数部分

↓类型不同的转化, 得用方法

string s = f.ToString();
int a = Convert.ToInt32(s);//基本上什么类型都能转成int
int b = int.Parse(s);//只能把string转成int

int 除 int 怎么得到一个 float

//↓像这样, 把其中一个数字强转成float就行啦
(float)1 / 2
1 / (float)2

//↓没必要2个数字都转
(float)1 / (float)2

注意: 1f和1.0f都是float, 而1.0是double, 不是float,

Array.ConvertAll, 数组批量操作

比for简洁

↓把string数组转化为int数组

string[] strings = { "1", "   2", "3" };
int[] ints = Array.ConvertAll(strings, int.Parse);

↓所有string, Trim();一下

string[] strings = { "1", "   2", "3" };
strings = Array.ConvertAll(strings, p => p.Trim());

纹理图片勾选Generate Mip Maps

会有类似LOD的效果, 离远了就看不到纹理了
内存会增加约1/3
为什么是1/3呢, 看这个视频, 第47分钟:
现代计算机图形学入门

不同宽度的Grid子物体

项目要求是ABCD(可能还有EF)答题选项, 动态加载
↓要是都是这样的题型就简单了

口 A.我是选项A
口 B.我是选项B
口 C.我是选项C
口 D.我是选项D

先制作宽度一行的Toggle预制体

口 A.我是选项A

再弄个ScrollView在Content上挂上ContentSizeFitter以适应ABCD(EF), 再挂上VerticalLayoutGroup均匀排布就行了

↓但是题型有这样的

口 A.我是选项A
口 B.我是选项B, 我就是要比别的选项长, 别的选项只占一行, 而我就是要占好几行,, 我就是特立独行, 我就是格外一个样, 你的VerticalLayoutGroup失效了,
你能拿我怎么办, 我就是我是颜色不一样的烟火
口 C.我是选项C
口 D.我是选项D

众所周知, LayoutGroup的子物体挂上ContentSizeFitter是不生效的
所以权宜之计是将Toggle预制体宽度调宽

口 A.我是选项A


效果如下, 很不好看

口 A.我是选项A


口 B.我是选项B, 我就是要比别的选项长, 别的选项只占一行, 而我就是要占好几行,, 我就是特立独行, 我就是格外一个样, 你的VerticalLayoutGroup失效了,
你能拿我怎么办, 我就是我是颜色不一样的烟火
口 C.我是选项C


口 D.我是选项D


解决方法:
把Toggle预制体挂在一个Text下, 然后把这个Text作为预制体, Text上挂ContentSizeFitter是不会失效的
这个Text呢, 就用来取代Toggle中Label, 但是Label不要删, 只把字删掉就好
要是删了的话, 点击Toggle的文字部分就无效了, 只有点它前面的小方框才有效

GetComponentsInChildren(True);

子物体必须是活着的, 不然get不到
除非, 在参数里面加上 (true)

GetComponentsInChildren<T>(true);

JSON数组

最后一个元素后面 不 ! 加 ! 逗 ! 号 !

总是不小心点到场景的一棵树上, 点出一大长串的Hierarchy?

把这个脚本, 挂在场景的最高父物体上
不论是点到了窗子还是桌子, 选中的都是场景的最高父物体

using UnityEngine;
[SelectionBase]
public class IAmAncestor : MonoBehaviour
{

}

↑上面的方法, 不行, 下面的方法, 行↓
在这里插入图片描述
给所有不想点的东西(树啊房子啊)的layer设置成XXX
然后点开图中这个Layers
再点XXX后面的小锁
这样就再也点不到场景中的树和房子了

手性翻转(镜像)模型

改变模型的Scale X Y Z的其中一个为负数就行了

协程非正常停止

带有协程的脚本.enabled = false; 协程会照常运行
带有协程脚本的gameObject.SetActive(false); 协程全部停止,即使再激活, 协程也不会继续执行

C#方法传入多个参数

用params关键字

public void Speak(params int[] s)
{
    for (int i = 0; i < s.Length; i++)
    {
		persons[i].DoAnim("Speak");
	}
}
//用的时候:
Speak(0,1);
Speak(4,5,6,7,8,9);

判断空Action 及其简化

↓这样写, 如果你没传参数进来, 会报空

void DontCheckNull(Action action = null)
{
	action();
}

↓所以需要判断一下空

void DoCheckNull(Action action = null)
{
	if (action != null)
	{
		action();
	}
}

可以简化为↓ (C#6可以, C#4不行)

void DoCheckNull(Action action = null)
{
	action?.Invoke();
}

后来我发现几乎所有的判空都能这样写: 加个问号

数值型不能为空

因为它在栈上, 怎么会空呢, 除非…
在声明的时候这样写:

int? a;

int? 表示可空类型,就是一种特殊的值类型,它的值可以为null
用于给变量设初值得时候,给变量(int类型)赋值为null,而不是0
int?? 用于判断并赋值,先判断当前变量是否为null,如果是就可以赋个新值,否则跳过

public int? a = null;
public int b()
{
	return a ?? 0;
}

原文地址: https://www.cnblogs.com/firstcsharp/archive/2011/12/11/2283797.html

鼠标点在UI上还是3D物体上

用EventSystem.current.IsPointerOverGameObject()

if (Input.GetMouseButtonDown(0) && !EventSystem.current.IsPointerOverGameObject())
{
	//点在物体上了
}
else if(Input.GetMouseButtonDown(0) && EventSystem.current.IsPointerOverGameObject())
{
	//点在UI上了
}

给按钮添加一次性委托

需求:
一个小弹窗, 它的固有点击事件为: 自身窗体消失, 还可能有别的点击事件, 且这些点击事件仅生效一次

开搞:
↓一开始我是这样写的, 很蠢, 不管有没有委托, 都全部移除, 再添加上消失事件和自定义的委托

public void ShowTip(string tipText, Action action = null)
{
    textTip.text = tipText;
    panelTip.SetActive(true);

    btnTip.onClick.RemoveAllListeners();
    btnTip.onClick.AddListener(() => panelTip.SetActive(false));
    if (action != null)
    {
        btnTip.onClick.AddListener(() =>
        {
            action();
        });
    }
}

↓改进了一下, 用了EventTrigger, 但是还是很蠢

public void ShowTip(string tipText, Action action = null)
{
    textTip.text = tipText;
    panelTip.SetActive(true);

    if (action != null)
    {
        btn.MyBtnClick += action;
        btn.MyBtnClick += ()=> { btn.MyBtnClick -= action; };
    }
}

↓其实明明可以更简单的

public void ShowTip(string tipText, Action action = null)
{
    textTip.text = tipText;
    panelTip.SetActive(true);

    btn.MyBtnClick = action;
}

判断物体是否在视野内

注意1 : 在Game面板中可见, 或在Scene面板中可见, 都会触发本方法
注意2 : 即使把Mash Fitter设置为None, 使之不可见, 仍会触发本方法

bool isVisiable;
private void OnBecameVisible()
{
    isVisiable = true;
}

相机穿模

方法1: 调小相机的Near Clip Plane
方法2: 如果是自己手里的枪穿模, 那就另开一个相机, 只看枪, 相机的Clear Flags改为Depth only
方法3: 在相机上添加碰撞体

获取当前场景的名字

string sceneName = SceneManager.GetActiveScene().name;

生命周期

刚才犯了一个很傻逼的问题:
在Start里声明, 在OnEnable里调用, 结果报空了;
生命周期啊生命周期, 先OnEnable再Start;

注意:
在同一个类里, 是先OnEnable再Start;
但是在不同类里, 顺序是不确定的, 举个例子:
触发顺序可能为:
A类的OnEnable, B类的OnEnable, A类的Start, B类的Start
也可能为:
A类的OnEnable, A类的Start, B类的OnEnable, B类的Start
(A的Start可能会排在B的OnEnable前面)

Canvas - Space Camera

实现拖拽功能, 让ret跟着鼠标走, 遇到的坑:

ret.position = Input.mousePosition;

↑这样不行, ret的移动幅度特别大.

ret.parent = mother;//mother是一个点,锚点在左下角
ret.localPosition = Input.mousePosition;

↑这样才行
960 540(适配分辨率), 能完美运行
切换成1920 1080的时候, 鼠标在左下角, ret也在左下角, 此时没错
但当鼠标移动到屏幕中间时, ret已经跑到右上角去了, ret的移动距离是鼠标的2倍
解决方法:

ret.localPosition = Input.mousePosition * (540f / Screen.height);

乘以(除以)屏幕的放大(缩小)比例, 就行了

映射

项目要求是15个模块, 不同的场景, 不同的人物, 不同的剧情, 相同点都是对话和播动画. 我的思路是做3个类

1.Data类存数据;
2.SceneModel存放场景里的人物和物品和基本方法;
3.SceneControl主控制器, 控制事件流程; 然后分15个子类

15个子类, 分别是Scene0Control, Scene1Control, Scene2Control…Scene14Control; 然后用映射方法, 通过不同的类名, 挂到不同的场景中

public int State = 0;
SceneControl sceneControl = gameObject.AddComponent<SceneControl>();
string ctrlClassName = "Scene" + State.ToString() + "Control";
sceneControl = Activator.CreateInstance(Type.GetType(ctrlClassName)) as SceneControl;

呃, 失败了…还是 Switch 吧
有空再研究…


我研究回来了
因为脚本是挂在物体上的, 不需要用Activator.CreateInstance, 只要拿到Type就行了

public static ProcessMgr CreateByName(string module)
{
	Type type = Type.GetType("ProcessMgr" + module, true);
    return new GameObject("ProcessMgr").AddComponent(type) as ProcessMgr;
}

Editor文件夹

不需要打包的脚本放入名字是Editor的文件夹下, Editor文件夹放哪都行, 有很多叫做Editor的文件夹也行

foreach

只能读, 不能改, 而且会产生垃圾, (理论上应该尽可能的少用)

Unity的输入框打不了字

有可能是它不能输入全角字符
也有可能是发布的WebGL版, 是打不了中文的
还有WebGL版要把字体全部改为中文字体, 不然字体不显示

Text我不想换行但它自己换行了

因为你打半角空格的时候, Unity以为你打的是英语单词, 为了不让单词从中间切开, 它就自动换行了;
改成全角空格就好了;

OnMouseEnter被挡住

项目需求是"光线"照射到"物体A"的时候, “物体A"显示出来, 然后再对"物体A"进行操作;
所以我用了OnTriggerEnter和OnTriggerExit;
以上都正常, 然后我在物体A上用OnMouseEnter, 方法没有用, 因为"物体A"被"光线"遮住了;
解决方法, 把"光线"的Layer改成"IngoneRayCast”;

字符串比较(忽略大小写)

s1.Equals(s2, StringComparison.OrdinalIgnoreCase)

单例

不要在一个单例的Awake里使用另一个单例.
比如:
在A单例的Awake中使用B单例. 但是B还没Awake, 此时B单例为null, 报空;
解决方法:

  1. B在Awake时初始化. A在Start里使用B.instance;
  2. 单例管理, 注意Awake的顺序;
  3. 不滥用单例, 单例是全局的, 使用过多的单例不利于解耦

在for里AddListener

for (int i = 0; i < 10; i++)
{
	btns[i].onClick.AddListener(() => 
	{
		print(i);
	});
}

↑这样btns里的每个btn打印出来都是"10";

for (int i = 0; i < 10; i++)
{
	int temp = i;
	btns[i].onClick.AddListener(() => 
	{
		print(temp);
	});
}

↑这样打印出来才是想要的0,1,2,3,4,5,6,7,8,9


额, 这里有个很奇怪的地方我一直想不通, 哪位胸大的来分析一下:
↓在C#里这样写输出3 3 3

using System;
using System.Collections.Generic;

public class Test
{
	public static void Main()
	{
	    List<Action> actions = new List<Action>();
	    
	    for(int i = 0; i < 3; i++)
	    {
	        actions.Add(()=>{Console.WriteLine(i);});
	    }
	    
		for(int i = 0; i < actions.Count; i++)
	    {
	        actions[i].Invoke();
	    }
	}
}

↓而在Lua里同样写法, 输出的却是1 2 3

actions = {}

for i = 1, 3 do
    actions[i] = function() print(i) end
end

for i = 1, #actions do
   actions[i]()
end

妈呀, 没想到, 1年之后, 我被↑这个问题坑到了
我是这样写的↓, 以为会输出123, 没想到输出333

actions = {}

for i = 1, 3 do
    a = i
    actions[i] = function() print(a) end
end

for i = 1, #actions do
   actions[i]()
end

VS的中文string输出成乱码���

工具 > 自定义 > 命令 > 添加命令 > 文件 > 高级保存选项;
高级保存选项
然后关闭, 再点击高级保存选项, 把编码改成UTF-8(无签名)
UTF-8

txt中用的空格

不间断空格\u00A0 让一个单词在结尾处不会换行显示
半角空格(英文符号)\u0020
全角空格(中文符号)\u3000

.mp3格式打包WebGL不能播放

不知道为什么, 有的mp3能播, 有的mp3不能播, 可能是mp3的问题(怀疑是假mp3格式)
方法就是转换成wav格式
正确方法是要在Unity里调mp3的WebGl设置

CameraSpaceUI

记得给物体用特定的layer
相机只看这个layer, 别的不看

录音

audioClip = Microphone.Start(null, false, RECORD_TIME, RECORD_RATE);

RECORD_TIME: 录音时长
RECORD_RATE: 录音的采样率, 填8000 - 44100

常用string方法

//取前 i个字符
str.Substring(0, i);
str.Remove(i, str.Length - i);
//去除所有的空格
str.Replace(" ", "");
//去除头尾的空格和类空格字符
str.Trim();
//去除回车
str.Replace("\r", "");

↓正则表达式↓ 不用记忆这些东西, 肯定忘, 用的时候再看就行
https://www.runoob.com/csharp/csharp-regular-expressions.html

加特性[]

↓给本物体添加AudioSource组件, 省的你忘了挂上

[RequireComponent(typeof (AudioSource))]

↓提示本方法已过时, 但方法依然可以用

[Obsolete("已过时, 请使用NewMethod")]

↓true表示已弃用, 方法不能用了

[Obsolete("已弃用, 请使用NewMethod", true)]

↓这样一来这个方法就不跑了, 除非在最上面(using XXX上面)写上:#define IsTest;

[Conditional("IsTest")]

↓只能挂一个本脚本, 不允许挂多个

[DisallowMultipleComponent]

↓会出现在菜单栏的Component > Test

[AddComponentMenu("Test")]

Unity打包路径里面不要有中文!

否则报错

时间格式

正确格式: yyyy-MM-dd HH-mm-ss-ffff

System.DateTime.Now.ToString("yyyy-MM-ddHH-mm-ss-ffff");

Y按星期算年, 会不准, 原因我忘了
M是月, m是分钟, 这个没有大小写的转换
d是当月的天数(2月1日), D是一年中的天数(32天)
H是24小时制, h是12小时制
s是秒, S是毫秒

另外的还有w之类的, 和星期相关的, 对咱们用处不大, 估计对老外有用, 比如母亲节是:五月的第二个星期天

导入的FBX人物动画双脚乱动

因为unity把动画压缩了
解决方法:Animation:Anim.Compression改成off (不压缩动画)
或者下面3个error改小

所有的UI按钮失灵

不要慌, 可能只是因为你的场景里没有EventSystem.
遇到好几次了, 我怎么就记不住呢?
还有别的可能:
1.被另一个透明panel覆盖住了
2.你Canvas上没挂Graphic Raycaster(毕竟Unity的UI本来就用的是射线检测)
3.你用射线检测写的按钮按下, 但按钮上没挂Collider
4.你用的Space World模式的Canvas, 摄像机的Tag层不是Main Camera

更换UI的图片

Sprite sp = Resources.Load("Die",typeof (Sprite)) as Sprite;
img.sprite=sp;

图片的载入一定要加上typeof (Sprite), 不然没有用

OnDisable()

不要像下面这样, 在OnDisable()中给子物体换爸妈, 会报错

private void OnDisable()
    {
        for (int i = 0; i < transform.childCount; i++)
        {
            transform.GetChild(i).SetParent(null);
        }
    }

拿到一个UGUI的Height 或者Width

这样是没用的:

float x = joy.localScale.x;

这样才行:(仅锚点不分开的时候)

RectTransform rtf = joy.GetComponent<RectTransform>();
float x = rtf.sizeDelta.x;

这样也行:(锚点分开也行)

float x = rtf.rect.width;
float y = rtf.rect.height;

设置UI的宽:

rtf.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal,100);

得到中心点:

Vector2 pivot = rtf.pivot;

UI中用到的富文本

要勾选Text组件的"Rich Text"选项:

//将"奥特曼"粗体显示
我是<b>奥特曼</b>
//斜体
我是<i>奥特曼</i>
//字体大小
我是<size=14>奥特曼</size>
//字体颜色
我是<color=red>奥特曼</color>

DOTween的坑

tw.PlayBackwards(); 并不会触发tw.OnComplete();

UI.DOMove()
项目需求: UI移动到指定位置, 而且需要能够切换窗口和全屏模式:
常用的UI.DOMove(), 移动到new Vector3(1,1,1)就不行了, 因为屏幕分辨率变了之后, Vector3(1,1,1)并不会去自适应屏幕位置;
移动到Scene.height+UI自身长度也不行, 因为屏幕一变, 自身长度也是变的;
所以我放了一些空点在UI里, 这些点是有屏幕适配的, 直接DOMove移动到该点的坐标就行了
这里的坑是: 点的pivot要和被移动的UI的pivot一致才行, 锚点定位是没关系的, 上下左右定哪都行不影响, 但pivot一定要一毛一样;

比较2个List

内容一样, 顺序也一样:

using System.Linq;
if(list1.SequenceEqual(list2))

内容一样, 顺序可以不一样:

using System.Linq;
if(list1.Count == list2.Count && list1.Count(t => !list2.Contains(t)) == 0)

Unity读取.txt文件

.txt文件要另存为utf-8格式;

// 将txt中的内容加载进TextAsset中
TextAsset txt = Resources.Load("txt") as TextAsset;
// 以换行符作为分割点,保存数组, 最后记得Trim()一下, 去掉多余的空格换行符什么的
string[] strs = txt.text.Split('\n').Trim();

枚举

//正确的枚举是这样是
public enum Character
{
    A,
    B,
    C,
}
//而不是下面这样, 再用个类把枚举包起来
public class CharacterEnum 
{
    public enum Character
    {
      A,
      B,
      C,
    }
}

枚举是值类型

异步加载

IEnumerator LoadAsyncScene(string sceneName)
{
	AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
    //只要没加载完, 就一直返回空
    while (!asyncLoad.isDone)
    {
    	yield return null;
    }
    //加载完了
    DoSomething();
}

↓你还可以把下面这个玩意↓设为false, 等你的进度条加完了, 再设为true;

asyncLoad.allowSceneActivation;

↓带进度条的异步加载↓以及适配WebGL
https://blog.csdn.net/weixin_43994445/article/details/106231953

有几个插件找不到?

Cinemachine, ADS等Unity内置插件在商店是找不到的, 他们在这:
Windows → Package Manager

TimeLine在:
Windows → Sequencing

2018里好像没有
也可能是下架了, 没了
也可能是暂时下架了, 过几天就有了

  • 14
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值