C#扩展方法

扩展方法
C#在3.0版本中引入了“扩展方法”,既有静态方法的优点,又使调用它们的代码的可读性得到了提高。在使用扩展方法时,可以像调用实例方法那样调用静态方法。

扩展方法声明
必须在一个非嵌套的、非泛型的静态类中(所以必须是一个静态方法)。
至少有一个参数。
第一个参数必须附加this关键字做前缀。
第一个参数不能有其他任何修饰符(如ref或out)。
第一个参数的类型不能是指针类型。
如何使用
扩展方法参数可以给该参数的类型增加一个方法,简单点说就是在A类中写一个方法,B类中也会拥有这个方法。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static class A
{
public static void Foo(this B s)
{
}
}

class B
{
}

class MainClass
{
static void Main()
{
B b = new B();
// 使用B类的对象调用A类中定义的Foo方法
b.Foo();
}
}

代码中对象b可以直接调用Foo方法,因为这个方法已经被扩展到B类中了。

举个例子
我想做一个用于辅助Transform类的工具类(TransformHelper),其中有一个方法是要递归查找子对象中的某个变换组件,这时候就可以通过 **this参数(扩展方法)**来给Transform类添加此方法,代码如下:

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
/// <summary>
///变换组件助手类
/// </summary>
public static class TransformHelper
{
/// <summary>
/// 递归查找变换组件
/// </summary>
/// <param name="cuurentTF">当前对象</param>
/// <param name="childName">要查找的子节点名称</param>
/// <returns></returns>
public static Transform FindChildByName(this Transform cuurentTF, string childName)
{
Transform child = cuurentTF.Find(childName);

if (child != null) return child;

for (int i = 0; i < cuurentTF.childCount; i++)
{
child = FindChildByName(cuurentTF.GetChild(i), childName);
if (child != null) return child;
}

return null;
}
}

/// <summary>
/// 游戏主窗口
/// </summary>
public class UIMainWindow : MonoBehaviour
{
private void Start()
{
// 用静态类的方式调用方法
TransformHelper.FindChildByName(transform, "ButtonGameStart").GetComponent<Button>().onClick.AddListener(OnGameStartButtonClick);

// 直接使用变化组件调用方法,注意,此时不用要再传第一个参数了,因为第一个参数已经成为默认的this了。
transform.FindChildByName("ButtonGameStart").GetComponent<Button>().onClick.AddListener(OnGameStartButtonClick);
}
}

基本原则
C#只支持扩展方法,不支持扩展属性、扩展事件、扩展操作符等。
扩展方法(第一个参数前面是this的方法)必须在非泛型的静态类中声明,扩展方法必须有一个参数,而且只有第一个参数使用this标记。
C#编译器查找静态类中的扩展方法时,要求这些静态类本身必须具有文件作用域。
C#编译要求“导入”扩展方法。(静态方法可以任意命名,C#编译器在寻找方法时,需要花费时间进行查找,需要检查文件作用域中的所有的静态类,并扫描它们的所有静态方法来查找一个匹配)。
多个静态类可以定义相同的扩展方法。
用一个扩展方法扩展一个类型时,同时也扩展了派生类型。
调用方法时无需传递第一个参数,默认指定调用方法的对象(this)为第一个参数。
扩展方法的优劣分析
刚接触到扩展方法时,内心总觉得这是不合理的,因为扩展方法可能导致每个类都可能有新的、隐藏的、未知的方法,首先从内存的角度考虑就是不合理的(问题1),其次在代码的可维护性和易用性两方面考虑也是颇具困难的(问题2)。

比如我将方法扩展到泛型中,代码如下:

1
2
3
4
public static string FindKey<T>(this T obj, FindHandler<T> handler)
{
return null;
}

按照正常的理解,会以为所有类型,或者说所有引入这个类的类型都会加入FindKey方法,但实际上并不是这样的,或者说并不完全是这样的。虽然这些方法可以使用对象直接调用,但实际上被调用的还是最初的那个静态方法,而不是重新写入到泛型T类中的方法。

为什么这么说呢?

因为在扩展方法中会使用ExtensionAttribute这个Attribute。一旦使用this关键字标记了某个静态方法的第一个参数,编译器就会在内部向该方法应用一个定制的attribute,这个attribute会在最终生成的文件的元数据中持久性的存储下来,此属性在System.Core dll程序集中。

任何静态类只要包含了至少一个扩展方法,它的元数据中也会应用这个attribute,任何一个程序集包含了至少一个符合上述特点的静态类,它的元数据也会应用这个attribute。如果代码用了一个不存在的实例方法,编译器会快速的扫描引用的所有程序集,判断它们哪些包含了扩展方法,然后,在这个程序集中,可以扫描包含了扩展方法的静态类。

如果同一个命名空间中的两个类含有扩展类型相同的方法,就没有办法做到只用其中一个类中的扩展方法。为了通过类型的简单名称(没有命名空间前缀)来使用类型,可以导入该类型所有在的命名空间,但这样做的时候,你没有办法阻止那个命名空间中的扩展方法也被导入进来。

回到最初提出的问题:

问题1:是不是每个对象都加入了这个扩展方法?
这个问题其实并未发生,因为C#使用的方式不是给每个对象加一个方法,而是另外提供了一个扩展方法的列表,在使用时通过列表找到被扩展的静态方法然后调用,也就是说方法还是只有那一个方法,并没有大范围的占据方法区。

问题2:代码的可维护性和易用性上是否受到了影响?
这个问题其实是存在的,如果开发团队不能有效的控制扩展方法的定义,将会出现局部代码无法溯源或者扩展功能影响主场景功能的问题,导致代码可读性差,语义不明确等问题。但如果开发团队能够有效的控制扩展方法的创建,并提供专门的扩展方法维护及使用方案,开发效率也会有些许提升。

总结
扩展方法可以说是一把双刃剑,用好了锋利无比,用不好也有可能会自伤,但总得来说还是功大于过,特别适合具有经验的团队使用。

三消游戏的一些算法总结

•	"如何表示消除游戏的棋盘?"
•	"怎样检测相邻的相同元素?"

•	"如何实现消除后自动补全棋盘效果?
•	"如何实现消除后自动生成新元素效果?"

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

// 消除游戏棋盘表示
int[,] board = new int[8, 8]; // 0表示空位,1-6表示不同颜色

// 检测相邻相同元素
bool HasMatch(int[,] grid, int x, int y) {
int color = grid[x, y];
// 检查水平方向
if (x > 0 && grid[x-1, y] == color && x < grid.GetLength(0)-1 && grid[x+1, y] == color)
return true;
// 检查垂直方向
if (y > 0 && grid[x, y-1] == color && y < grid.GetLength(1)-1 && grid[x, y+1] == color)
return true;
return false;
}
• "如何实现三消游戏中的特殊连锁反应效果?"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

// 连锁反应节点
public class ChainNode {
public Vector2Int Position;
public ChainNode Next;
}

// 处理连锁反应
void ProcessChain(ChainNode head) {
while (head != null) {
RemoveTile(head.Position);
ApplySpecialEffect(head.Position);
head = head.Next;
yield return new WaitForSeconds(0.3f); // 动画间隔
}
}

“如何检测棋盘上的所有可消除组合?”

“怎样实现特殊消除效果(如L型/T型消除)?”
// 检测所有匹配项
List FindAllMatches(int[,] grid) {
var matches = new List();
int width = grid.GetLength(0);
int height = grid.GetLength(1);

// 水平检测
for (int y = 0; y < height; y++) {
    for (int x = 0; x < width - 2; x++) {
        if (grid[x, y] > 0 && 
            grid[x, y] == grid[x+1, y] && 
            grid[x, y] == grid[x+2, y]) {
            matches.Add(new Vector2Int(x, y));
            matches.Add(new Vector2Int(x+1, y));
            matches.Add(new Vector2Int(x+2, y));
        }
    }
}

// 垂直检测(类似逻辑)
// ...

return matches.Distinct().ToList();

}

问题:”如何实现随着玩家水平变化的难度系统?”

public class DifficultyAdjuster {
private float playerSuccessRate;
private int gamesPlayed;

public int GetAdjustedLevel(int baseLevel) {
    float difficultyFactor = Mathf.Clamp(1.5f - playerSuccessRate, 0.5f, 2f);
    return Mathf.RoundToInt(baseLevel * difficultyFactor);
}

public void UpdateSuccess(bool levelPassed) {
    gamesPlayed++;
    playerSuccessRate = (playerSuccessRate * (gamesPlayed - 1) + (levelPassed ? 1 : 0)) / gamesPlayed;
}

}

洗牌算法

原理:遍历所有牌(52+大小王),每张牌随机一个小于54的数,交换当前遍历的index和随机数对应数组下标的值。random 方法为伪随机,并非等概率随机。
c#实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  private static void Shuffle ()  
{
// 扑克牌初始化
List<string> allCard = new List<string>();
for (int i = 0; i < 54; i++) {
allCard.Add(i);
}

// 定义随机数
Random random = new Random ();
// 从数组的最后一个数开始递减
for (int i = allCard.Count - 1; i > 0; i--) {
// 随机下标
int index = random.Next (0, i);
// 随机出来的数与最后位置的数交换
string temp = allCard [i];
allCard [i] = allCard [index];
allCard [index] = temp;
}
foreach (string item in allCard) {
Console.WriteLine (item);
}
}

获取牌类型:数组值除以13是否大于4,是则是大小王,否则为普通牌
c#实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  for (int i = 0; i < allCard.Count; i++)
{
if (allCard [i] / 13 > 4)
{
if (allCard [i] % 4 == 1)
{
//小王
}
else
{
/大王
}
}
else
{
//cardType为0时为黑桃,以次类推,cardNum代表A到K,可以以cardType_cardNum作为牌图片索引。
int cardType = allCard [i] / 13;
int cardNum = allCard [i] % 13;
}
  }

unity 性能优化之CPU

影响性能的因素:对于一个游戏来说,有两种主要的计算资源:CPU和GPU,它们会互相合作,来让我们的游戏可以在预期的帧率和分辨率下工作。CPU负责其中的帧率,GPU主要负责分辨率相关的一些东西。本篇会介绍CPU的优化技巧~

CPU:

作用:计算。主要是在蒙皮骨骼计算,布料模拟,顶点动画,粒子模拟等,还有在各种顶点变换、光照、贴图混合等。在每次绘图前,我们都需要先准备好顶点数据(位置、法线、颜色、纹理坐标等),然后调用一系列API把它们放到GPU可以访问到的指定位置,最后,我们需要调用_glDraw命令,来告诉GPU进行渲染。而调用_glDraw命令的时候,就是一次Draw Call。影响CPU效率的因素主要有DrawCall、物理组件、GC、代码,CPU过高会影响帧率,卡顿、发热严重,游戏性能就会下降。

性能开销:引擎模块性能开销和自身代码性能开销。其中,引擎模块中又可细致划分为渲染模块、动画模块、物理模块、UI模块、粒子系统、加载模块和GC调用等等。其中渲染模块、UI模块和加载模块,往往占据了游戏CPU性能开销的Top3。

优化:
DrawCalls:Draw Call是渲染模块优化方面的重中之重,一般来说,Draw Call越高,则渲染模块的CPU开销越大。
Drawcall batching:Unity在运行时可以将一些物体进行合并,从而用一个批次调用来渲染他们。对于使用同一个材质的物体,它们之间的不同仅仅在于顶点数据的差别,即使用的网格不同而已。我们可以把这些顶点数据合并在一起,再一起发送给GPU,就可以完成一次批处理。Unity中有两种批处理方式:一种是动态批处理,一种是静态批处理。但不论静态批次还是动态批次都要求对象的材质是共享的,即不同材质的对象是无法进行批次的。而且要注意的一点:如果在脚本中调用材质时,使用Renderer.material会造成材质的拷贝,而使用Renderer.sharedMaterial来调用则不会拷贝材质。对于动态批处理来说,好消息是一切处理都是自动的,不需要我们自己做任何操作,而且物体是可以移动的,但坏消息是,限制很多,可能一不小心我们就会破坏了这种机制,导致Unity无法批处理一些使用了相同材质的物体。对于静态批处理来说,好消息是自由度很高,限制很少,坏消息是可能会占用更多的内存,而且经过静态批处理后的所有物体都不可以再移动了。

Unity进行动态批处理对模型的要求很多:
1.动态批处理仅支持小于900顶点的网格物体,如果你的着色器使用顶点位置,法线和UV值三种属性,那么你只能批处理300顶点以下的物体;如果你的着色器需要使用顶点位置,法线,UV0,UV1和切向量,那你只能批处理180顶点以下的物体。而且未来顶点值有可能会变,不要依赖这个数据。
2.一般来说,所有对象都必须需要使用同一个缩放尺度(可以是(1, 1, 1)、(1, 2, 3)、(1.5, 1.4, 1.3)等等,但必须都一样)。但如果是非统一缩放(即每个维度的缩放尺度不一样,例如(1, 2, 1)),那么如果所有的物体都使用不同的非统一缩放也是可以批处理的。
3.拥有lightmap的物体含有额外(隐藏)的材质属性,使用lightmap的物体不会批处理。多通道的shader会妨碍批处理操作。接受实时阴影的物体也不会批处理。
4.时刻警惕透明物体,透明对象要得到正确的渲染效果,就必须从后往前渲染(这里不讨论使用深度的方法),这意味着,透明物体几乎一定会造成overdraws。尽可能把多张小纹理合并到一张大纹理(Atlas图集)中是一个好主意。“Generate Mip Maps”会为同一张纹理创建出很多不同大小的小纹理,构成一个纹理金字塔。而在游戏中可以根据距离物体的远近,来动态选择使用哪一个纹理。这是因为,在距离物体很远的时候,就算我们使用了非常精细的纹理,但肉眼也是分辨不出来的,这种时候完全可以使用更小、更模糊的纹理来代替,而这大量可以节省访问的像素的数目。但它的缺点是,由于需要为每一个纹理建立一个图像金字塔,因此它会需要占用更多的内存。“Max Size”决定了纹理的长宽值,如果我们使用的纹理本身超过了这个最大值,Unity会对其进行缩小来满足这个条件。这里再重复一点,所有纹理的长宽比最好是正方形,而且长度值最好是2的整数幂。这是因为有很多优化策略只有在这种时候才可以发挥最大效用。

物理组件:从性能优化的角度考虑,物理组件能少用还是少用为好。1.设置一个合适的Fixed Timestep(物理计算频率)。2.就是不要使用网格碰撞器(mesh collider)。

GC优化:GC能释放内存,但会加重CPU的负担,因此对于GC的优化目标就是尽量少的触发GC。GC不是用来处理引擎的assets(纹理啦,音效啦等等)的内存释放的,GC也主要是针对Mono的对象来说的,而它管理的也是Mono的托管堆。引用类型,比如类的实例,字符串,数组等会被分配到托管堆。GC触发:首先是堆的内存不足时,会自动调用GC。其次,编程人员手动的调用GC。

UI模块:

1.使用图集:合理拆分UI图集,区分公共图集(常驻)和非公共图集。太大容易造成冗余加载,容易导致内存占用过大,导致内存显存交换开销。太小有容易导致显存碎片影响效率。

2.使用九宫格降低图片大小。使用镜像图片,镜像片图片使用链接:https://zhuanlan.zhihu.com/p/25995971

3.layout group, canvas group组件,任何子节点变了父节点都会用getcompent找到laygroup

4.不需要交互的UI的Raycast target关了。

5.尽量不用UI特效。

加载模块:场景切换时的主要性能开销主要体现在两个方面,前一场景的场景卸载和下一场景的场景加载。

1.场景卸载:destory:引擎在切换场景时会收集未标识成“DontDestoryOnLoad”的GameObject及其Component,然后进行Destroy。同时,代码中的OnDestory被触发执行,这里的性能开销主要取决于OnDestroy回调函数中的代码逻辑.Resources.UnloadUnusedAssets:一般情况下,场景切换过程中,该API会被调用两次,一次为引擎在切换场景时自动调用,另一次则为用户手动调用(一般出现在场景加载后,用户调用它来确保上一场景的资源被卸载干净)。其耗时开销主要取决于场景中Asset和Object的数量,数量越多,则耗时越慢。

2.场景加载:资源加载:其加载效率主要取决于资源的加载方式(Resource.Load或AssetBundle加载)、加载量(纹理、网格、材质等资源数据的大小)和资源格式(纹理格式、音频格式等)等等。Instantiate实例化:在Instantiate实例化时,引擎底层会查看其相关的资源是否已经被加载,如果没有,则会先加载其相关资源,再进行实例化,这其实是大家遇到的大多数“Instantiate耗时问题”的根本原因,这也是为什么我们在之前的AssetBundle文章中所提倡的资源依赖关系打包并进行预加载,从而来缓解Instantiate实例化时的压力。场景加载尽量使用使用加载的方式。

代码优化:
1.字符串连接的处理。因为将两个字符串连接的过程,其实是生成一个新的字符串的过程。而之前的旧的字符串自然而然就成为了垃圾。而作为引用类型的字符串,其空间是在堆上分配的,被弃置的旧的字符串的空间会被GC当做垃圾回收。字符串的链接使用StringBuilder进行链接。
2.尽量不要使用foreach,而是使用for。foreach其实会涉及到迭代器的使用,而据传说每一次循环所产生的迭代器会带来24 Bytes的垃圾。那么循环10次就是240Bytes。
3.不要直接访问gameobject的tag属性。比如if (go.tag == “human”)最好换成if (go.CompareTag (“human”))。因为访问物体的tag属性会在堆上额外的分配空间。如果在循环中这么处理,留下的垃圾就可想而知了。
4.使用对象“池”,以实现空间的重复利用。
5.最好不用LINQ的命令,因为它们会分配临时的空间,同样也是GC收集的目标。而且我很讨厌LINQ的一点就是它有可能在某些情况下无法很好的进行AOT编译。比如“OrderBy”会生成内部的泛型类“OrderedEnumerable”。这在AOT编译时是无法进行的,因为它只是在OrderBy的方法中才使用。所以如果你使用了OrderBy,那么在IOS平台上也许会报错。
6.最好不要频繁使用GetComponent,尤其是在循环中(频繁的调用GetComponent方法会造成CPU的开销,但是对GC几乎没有影响。GetComponent只会在EDITOR模式返回NULL时会造成额外的堆内存分配)。
7.善于使用OnBecameVisible()(当renderer(渲染器)在任何相机上可见时调用OnBecameVisible)和OnBecameInVisible(),来控制物体的update()函数的执行以减少开销。
8.使用内建的数组,比如用Vector3.zero而不是new Vector(0, 0, 0);
9.对于方法的参数的优化:善于使用ref关键字。值类型的参数,是通过将实参的值复制到形参,来实现按值传递到方法,也就是我们通常说的按值传递。复制嘛,总会让人感觉很笨重。比如Matrix4x4这样比较复杂的值类型,如果直接复制一份新的,反而不如将值类型的引用传递给方法作为参数。

10.如果可以避免使用浮点型(float),尽量使用整形(int),尽量少用复杂的数学函数比如 Sin 和 Cos 等等。

Unity 性能优化之GPU

GPU:负责整个渲染流水线。它会从处理CPU传递过来的模型数据开始,进行Vertex Shader、Fragment Shader等一系列工作,最后输出屏幕上的每个像素。因此它的性能瓶颈包括顶点、像素、显存等因素有关。
GPU的瓶颈主要存在在如下的方面:
1.像素分辨率,可以简单的理解为图形处理单元每秒渲染的像素数量,可以在unity的Player Settings中设置降低分辨率。
2.像素的复杂度,比如动态阴影,光照,复杂的shader等等
3.几何体的复杂度(顶点数量)
4.GPU的显存带宽:存储带宽是指GPU在其特定的内存上的读写速率

优化:
1减少绘制的数目:尽可能减少模型中三角形的数目,尽可能重用顶点,对于GPU来说,它本质上只关心有多少个顶点。因此,尽可能减少顶点的数目其实才是我们真正对需要关心的事情。移除不必要的Hard Edge以及纹理衔接,即避免Smoothing splits和UV splits。遮挡剔除是用来消除躲在其他物件后面看不到的物件,这代表资源不会浪费在计算那些看不到的顶点上,进而提升性能。控制绘制顺序,主要原因是为了最大限度的避免overdraws,也就是同一个位置的像素可以需要被绘制多变。在Unity中,那些Shader中被设置为“Geometry” 队列的对象总是从前往后绘制的,而其他固定队列(如“Transparent”“Overla”等)的物体,则都是从后往前绘制的。这意味这,我们可以尽量把物体的队列设置为“Geometry” 。减少实时光照,使用Lightmaps,使用God Rays,使用mobile版的shader。。 LOD技术有点类似于Mipmap技术,不同的是,LOD是对模型建立了一个模型金字塔,根据摄像机距离对象的远近,选择使用不同精度的模型。它的好处是可以在适当的时候大量减少需要绘制的顶点数目。它的缺点同样是需要占用更多的内存,而且如果没有调整好距离的话,可能会造成模拟的突变。

2.LOD:LOD(level of detail)技术可以用来减少较远网格的复杂度,通过减少需要渲染的顶点数量同时不影响游戏的表现效果。菜单Component-Rendering-LODGroup。

3.优化显存带宽:主要途径是减少贴图的内存占用来提高游戏的性能,贴图压缩 贴图压缩可以极大的减少贴图在磁盘和内存中的占用率。如果游戏的内存是我们的限制因素,则压缩贴图可以提高游戏的性能。使用mipmap,MipMap会占用内存,但能提高图片渲染的质量(比压缩要好)。

4.游戏中的图像特效会极大的造成像素问题,特别是有多个图像特效。如果游戏受到像素的影响同时又采用了图像特效,则最好对图像特效进行优化,比如用优化后的爆炸特效来替代优化前的爆炸特效。特别是在同一个相机上有多个图像特效,这会造成多个shader pass,这种情况下最好将多个图像特效合并在一个shader pass中。如果这样设置都不能解决问题,则最好考虑是否能不用图像特效,特别是在低端机上。

5.如果游戏中的模型网格没有使用法线贴图,则可以在导入设置的时候剔除切线顶点来减少顶点数量。

6.警惕透明物体。GUI大多数被设置成半透明,尽量减少窗口中GUI所占的面积。如果实在无能为力,可以把GUI绘制和三维场景的绘制交给不同的摄像机,而其中负责三维场景的摄像机的视角范围尽量不要和GUI重叠。

7.减少实时光照。使用光照贴图Lightmaps;使用雾和体积光God Rays;使用mobile版的shader。

8.遮挡剔除:顾名思义就是被遮挡看不见的地方不渲染,例如墙后的物体。遮挡剔除可以CPU计算也可以GPU计算。Unity自带了OcclusiongCulling

9.降低渲染的分辨率:缩小Framebuff分辨率,减少ps开销和内存显存,但是会模糊,王者荣耀等很多主流游戏在Android上都降了分辨率。

见识 读书笔记


作者:吴军
作者简介:吴军毕业于清华大学计算机系(本科)和清华大学电子工程系(硕士),
于1993-1996年在清华任讲师,于1996年起在美国约翰霍普金斯大学攻读博士,
设计了谷歌中、日、韩搜索算法以及谷歌的自然语音分析器。
2011-2012年担任腾讯负责搜索和搜索广告等业务的副总裁,
后回到谷歌负责计算机自动问答项目。著有《数学之美》、《浪潮之巅》和《文明之光》。

10月20日
这本书总结一句话就是:你最终能走多远取决于你的见识
命运是什么?
作者对命运提出了自己的见解。他认为要想命好,需要信命和认命。
信命是知道自己有所不能,认命则似孔子所云,“不逾矩”。
他认为命取决于两个因素:环境的因素和我们对于未来走向规定的方向(独特的看问题的方法和行事方式);
决定命运的还包括我们的习惯、行为和背后的思维方式。
让我想起来另一伟人的名言(撒切尔夫人,还是伊丽莎白女皇?):
注意你的想法,因为它决定你的言辞和行为。
注意你的言辞和行动,因为它能主导你的行为。
注意你的行为,因为它能改变你的习惯。
注意你的习惯,因为它能塑造你的性格。
注意你的性格,因为它能决定你的命运。

10月26日
幸福是什么?
幸福是目的,成功是手段
作者认为幸福的本源有俩个,基因的传承和影响力

人生就像一条河,每个人都希望自己的这条河更宽更深更长,要做到更深,只能靠自己的休闲和对世界的理解,
要做到更宽,则要和一些志同道合的人在一起共同做一些事情。

世界观是什么?
作者认为,世界观是人类的本能,是人们认识世界的方法论,是人们认识世界的基础。
我们必须承认,任何社会都是分层次的,所谓好一点的社会不过是有一个上下层之间的通道,
让人员可以流动而已,没有一个国家,一个社会会过两年就把现在的金字塔打碎,然后再随机建造一个,
所以逆袭者的目标无非是在金字塔上往上走几层。虽然我们抱怨社会阶层固化,但往下的通道永远非常宽,
只要稍微不努力或者多抱怨几句,就能外下走几层,相反,往上的通道即便再宽,往上走也是一件辛苦的事,
有时需要一辈子或者几代人的努力才能完成。

婚姻是什么?
只有聪明的人才会欣赏聪明的人,从优生学来说,母亲的智力水平对孩子的智力水平影响力比父亲更大
找一个好的配偶,对自己的投资和职业都有帮助
除了迷人,有气质,聪明,一个女生自己对恋爱和未来家庭的看法也很重要。
完美的爱情并不意味着完美的婚姻,世界上没有什么老实和不老实的男人之分,只有对你好和不好之分。维持长久的婚姻靠对方老实是没有用的。
精英家庭:父母成熟,首先自己要不断进步,大气,开朗,不斤斤计较,不倚老卖老,学习的愿望很强烈,愿意尝试新的东西,接受新鲜事物。

11月26日 天气阴
Study as if you were to live forerver,live as if you were to die tomorrow.
生也有崖,知也无涯,提醒自己少做无用的事,做好有用事情,找到无关紧要的事情,然后下决心把那些事情放弃掉。

首先要能够跳出思维定式,换一个角度来判断一件事情的重要性,其次,敢于舍弃。

我们需要记住幸福生活才是根本,其他都不过是达成这个目的的手段而已,如果我们能够每天出门的时候想到
‘责任’,‘荣誉’,‘从容’,‘镇定’,就能比18世纪的贵族过的更好。

任何时候都不要屈服于恐惧和压力,只要你们充满自信,任何时候你们都是强大的,是不可战胜的。

10月28日 天气阴
见识是什么?
见识需要突破自我设限的那堵墙,有时是狭隘的自我认识抑或是膨胀的自我认识。

教育观/管理观
故善者因之,其次利导之,其次教诲之,其次整齐之,最下者与之争。
很多时候,成败与否取决于见识的高低,而不是自己简单的努力,见识的高低,取决于我们的环境。

父母的见识其实才是起跑线,人生的学习何时都不晚。

善于把过去看似没有什么大用的经历,变成日后成功的财富。
如果我们有足够的耐心,有好的办法,有持之以恒的努力,或许运气会降临到我们头上的。如果努力了,运气没有来,
我们要宽慰自己:问心无愧是我们唯一稳得的报酬,我能能做的不过是尽人事听天命而已。

11月1日 天气阴
关于企业经营
1、把产品做成牙刷培养用户每天使用习惯。可靠和稳定;
2、从本质种寻找商业模式,提供有用的内容,内容不一定是自己生产的。
3、薪火相传,公司的基因,把成熟的果实交个他人看管自己负责最需要支持,最需要资源的新业务。

11月2日 天气阴
关于投资,
不做自己看不懂的事情
不要做空股票
永远不要用杠杆投资,人的一生不需要俩次富有
关于销售
销售的本质:把钱收回来
持续的生意是让顾客买的东西用光,做生意必须照顾人的面子
如果我们能够照顾别人的面子,生意就能做好,事情就能做好;
如果我们为别人提供价值,而不是一味的推销,我们的产品,甚至我们自己,就会受欢迎。

11月3日 天气阴
拒绝伪工作者,效率的高低不取决于开始了多少工作,而在于完成了多少工作。
怎样防止员工成为伪工作者,首先管理者要让员工站在做什么事情能够让公司最大获益的高度去工作,其次管理者要让员工明白,他们积极工作,最大的受益方是自己。
伪工作特征
既不能为公司带来较大收益,也不能给用户带来价值的改进和升级。
明明可以通过学习一种新技能更有效的工作,切偏偏要守着过去的旧工具
做事情前不认真思考,做事时通过简单的试错方法盲目找答案
做产品不讲究质量,不认真测试,上线后不停的修补,总是在花费很多的时间和精力找漏洞和打补丁。
不注重用有限的资源解决95%的问题,而是大部分时间和精力用于纠结不重要的5%的问题
每次开会找大量不必要的人旁听,或者总是去参加那些不必要的会议。

11月4日 天气阴
10000小时的定律误区:
1、简单重复
2、习惯性失败
3、在自己的世界里精进,对外界越排斥
4、狗熊掰棒子,没有积累效应,第二次的努力时要最大限度的用第一次努力的结果。

确立 ‘愿景 – 目标(战略) –道路(战术)‘
即使不中听的话,也要试着找到其中合理之处。
凡事做记录,这样可以避免狗熊掰棒子
OKR–目标关键结果
做好最后的1%,不要狗尾续貂

11月26日 天气晴
工作时大家谋生的手段,职业时我们一辈子都要从事的事业,对待自己的职业,需要专业的态度,
一切以工作目标的达成为目的,永远要明确工作不是为了公司或者他人,而是为了自己的职业发展。

把自己仅仅当成一个单位的过客,而不是主人,心态的变化,造成不求上进,浪费时间,注重长期收益,
把一件事情放到两三年的时间周期来规划和对待,这时我们对他的态度就不会不同。
被语言暴力激怒后就乱了章法。
疏于沟通,大部分时间,提前打招呼总是一个良好的,职业的做事方式。
第一份工作应该让自己快速成长,养成良好的职业习惯,最短的时间内了解全行业。

五级工程师的职业发展
1、初级工程师:能够独立解决问题,完成工程工作
2、中级工程师:能够指导和带领其他人一同完成更有影响力的工作
3、高级工程师:能够独立并设计和完成产品,并且在市场上获得成功
4、资深工程师:能够设计和实现别人不能做出的产品,别人无法完成的工作
5、首席工程师:能够开创一个产业

基层员工要抬起头,管理层要弯下腰。
善于沟通的人会理解对方的提问和目的,然后提供有用的信息
关注细节

技术不是万能的,凡事做到50分靠常识,50-90分靠技术,任何事情做到90分后单纯靠技术就很难提高了,要靠艺术。

11月27日 天气晴
商业的本质
商业的本质就是让人多花钱而不是省钱。
经营和管理的秘诀—不给选择
第三眼美女新产品在市场哈桑成功的三阶段
第一眼,科技(产品)敏感型但是有瑕疵
第二眼,高端消费型
第三眼,推广型

11月28日 天气晴
理性的投资观
1、钱时上帝存在你那里的,不是给你的,回头还要还给他
钱只有花出去的才是你的,钱的本质就是对各种资源(自然资源和社会资源)的所有权和使用权的量化度量。
让钱发挥最大的效能,最好的办法就是利用他把今天过好。
钱和任何东西,都是为了让你生活的更好,而不是给你带来麻烦
钱时挣出来的,不是省出来的,而挣钱的效率取决于一个人的气度
给钱花不光,但是可以迅速投光

风险意识
生活还总给你任何事情都是有风险,都要有风险的意识,远防垃圾人
任何好的投资,都要建立在控制风险意识的基础上

投资的行为要围绕目的进行,针对时保值,养老等不同目的设置投资方式,还要制定可行的目标。
选择投资对象的考量原则
回报和风险
流动性:存款的流动性最好,股票次之,债券再次,房市最差
准入成本,股票买卖要手续费,买卖房子要交手续费和税费。

投资总的误区,贵重的金属是好的投资,只有经济危机和战乱时才会升值。
专业的人士理财一定比我们自己做的好,管理基金的人和投资的人有利益冲突。
在股市花时间研究的人越多,回报就越高。
只要有人买股票就会不断涨。
哪怕我买的股票亏了钱,只要我不卖,就没有损失。

围绕投资目的进行资产配置,
如果投资为了长期稳定性的增值,那么投资的效果主要取决于资产的配置,而非那一只股票的选择,或哪一次投资机会的把握。

11月1日 天气晴
好好说话
说话做事是要达到目的,怎么说服人,事实比口才更重要,不要证明比对方高明,要让大家去正视事实。

一个人成功与否不仅仅取决于个人能力,还要靠他的调动资源的能力,聪明的人总是善于借力的。
如何做好演讲,能够把一件事情讲透,3-5哥要点提到即可。
我们是谁,过去几年做了什么。
我们能提供什么核心价值,核心竞争力。
我们的投资哲学,方法论。
我们对要做的事业的看法,对行业的了解。
我们对所谓的趋势的看法,趋势来源于事实的积累。

Unity 性能优化之UGUI

一、界面制作
1.在UI界面中,因为一个Canvas下的所有UI元素都是合在一个Mesh中的,过大的Mesh在更新时开销很大,所以一般建议每个较复杂的UI界面,都自成一个Canvas(可以是子Canvas),在UI界面很复杂时,甚至要划分更多的子Canvas。同时还要注意动态元素和静态元素的分离,因为动态元素会导致Canvas的mesh的更新。最后,Canvas又不能细分的太多,因为会导致Draw Call的上升。

2.UWA性能检测报告中的Shared UI Mesh:
Shared UI Mesh作为静态全局变量,由底层直接维护,其大小与当前场景中所有激活的UI元素所生成的网格数相关。一般来说当界面上UI元素较多,或者文字较多时该值都会较高,在使用UI/Effect/shadow和UI/Effect/Outline时需要注意该值,因为这两个Effect会明显增加文字所带来的网格数。

3.打包图集:设计UI时要考虑重用性,如一些边框、按钮等,这些作为共享资源,放在13张大图集中,称为重用图集;其它非重用UI按照功能模块进行划分,每个模块使用12张图集,为功能图集;对于一些UI,如果同时用到功能图集与重用图集,但是其功能图集剩下的“空位”较多,则可以考虑将用到的重用图集中的元素单独拎出来,合入功能图集中,从而做到让UI只依赖于功能图集。也就是通过一定的冗余,来达到性能的提升。

4.把Packing Tag相同的源纹理文件,打到同一个AssetBundle中(设置一样的AssetBundle Name),从而避免Atlas的冗余。同时这样打包可以让依赖它的Canvas的打包更加自由,即不需要把依赖它的Canvas都打在一个AssetBundle中,在更新时直接更新Atlas所在的AssetBundle即可。

5.ScrollRect在滚动的时候,会产生Canvas.SendwillRenderCanvases:
ScrollRect在滚动时,会产生OnTransformChanged的开销,这是UI元素在移动时触发的,但通常这不会触发Canvas.SendWillRenderCanvases。
如果观察到Canvas.SendWillRenderCanvases耗时较高,可以检查下ScrollRect所在的Canvas是否开启了Pixel Perfect的选项,该选项的开启会导致UI元素在发生位移时,其长宽会被进行微调(为了对其像素),而ScrollRect中通常有较多的UI元素,从而产生较高的Canvas.SendWillRenderCanvases开销。因此可以尝试关闭Pixel Perfect看效果是否可以接受,或者尝试在滚动过程中暂时关闭Pixel Perfect等方式来消除其开销。

6.在整体游戏内存压力不大的情况下,常用界面可以预先在加载在场景(Scene)中,对加载完成的界面进行缓存,确保下一次打开时不会再卡顿。

7.少用mask:Mask对于uGUI性能来说是噩梦一般的存在,因为很可能因为这个东西,导致Drawcall数量成倍增长。Mask实现的具体原理是一个Drawcall来创建Stencil mask(来做像素剔除),然后画所有子UI,再在最后一个Drawcall移掉Stencil mask。这头尾两个Drawcall无法跟其他UI操作进行Batch,所以表面上看加个Mask就会多2个Drawcall,而且Mask中的UI元素无法与其他batch,所以很多原本可以合并的UI就无法合并了,从而增加DrawCall

8.关注相邻对象的合批问题,通过重新排列可绘制对象的顺序、调整对象的位置以消除不可见的重叠空间等方式减少DC

9.合理利用子Canvas处理一个界面过大、动静分离等问题,但要注意不同Canvas不会合批

10.关闭不需要的Raycast Target,仅在必须接收指针事件的UI组件上启用“ Raycast Target”设置。Graphic Raycaster会检测将“ Raycast Target”设置为true的所有Graphic组件。对于每个Raycast Target,Raycaster都会执行一组测试。

二、网格
1.如果修改的是Image组件上的Color属性,其原理是修改顶点色,因此是会引起网格的Rebuild的(即Canvas.BuildBatch操作,同时也会有Canvas.SendWillRenderCanvases的开销)。而通过修改顶点色来实现UI元素变色的好处在于,修改顶点色可以保证其材质不变,因此不会产生额外的Draw Call。

2.在UI的默认Shader中存在一个Tint Color的变量,正常情况下,该值为常数(1,1,1),且并不会被修改。如果是用脚本访问Image的Material,并修改其上的Tint Color属性时,对UI元素产生的网格信息并没有影响,因此就不会引起网格的Rebuild。但这样做因为修改了材质,所以会增加一个Draw Call。

3.在 UGUI 中,Batch是以Canvas为单位的,即在同一个Canvas下的UI元素最终都会被Batch到同一个Mesh中。而在Batch前,UGUI会根据这些UI元素的材质(通常就是Atlas)以及渲染顺序进行重排,在不改变渲染结果的前提下,尽可能将相同材质的UI元素合并在同一个SubMesh中,从而把DrawCall降到最低。而Batch的操作只会在UI元素发生变化时才进行,且合成的Mesh越大,操作的耗时也就越大。
因此,尽可能把频繁变化(位置,颜色,长宽等)的UI元素从复杂的Canvas中分离出来,从而避免复杂的Canvas频繁重建。在UGUI中,网格的更新或重建(为了尽可能合并UI部分的DrawCall)是以Canvas为单位的,且只在其中的UI元素发生变动(位置、颜色等)时才会进行。因此,将动态UI元素与静态UI元素分离后,可以将动态UI元素的变化所引起的网格更新或重建所涉及到的范围变小,从而降低一定的开销。而静态UI元素所在的Canvas则不会出现网格更新和重建的开销。

4.多人同屏的时候,人物移动会使得头顶上的名字Mesh重组,从而导致较为严重的卡顿:
如果是用UGUI开发的,当头顶文字数量较多时,确实很容易引起性能问题,可以考虑从以下几点入手进行优化:
尽可能避免使用UI/Effect,特别是Outline,会使得文本的Mesh增加4倍,导致UI重建开销明显增大;
拆分Canvas,将屏幕中所有的头顶文字进行分组,放在不同的Canvas下,一方面可以降低更新的频率(如果分组中没有文字移动,该组就不会重建),另一方面可以减小重建时涉及到的Mesh大小(重建是以Canvas为单位进行的);
降低移动中的文字的更新频率,可以考虑在文字移动的距离超过一个阈值时才真正进行位移,从而可以从概率上降低Canvas更新的频率。

三、界面切换:
1.把被覆盖的界面 SetActive(False),但发现后续 SetActive(True) 的时候会有 GC.Alloc 产生。这种情况下,希望既降低 Batches 又降低 GC Alloc 的话
可以尝试通过添加一个 Layer 如 OutUI, 且在 Camera 的 Culling Mask 中将其取消勾选(即不渲染该 Layer)。从而在 UI 界面切换时,直接通过修改 Canvas 的 Layer 来实现“隐藏”。但需要注意事件的屏蔽,禁用动态的 UI 元素等等。这种做法的优点在于切换时基本没有开销,也不会产生多余的 Draw Call,但缺点在于“隐藏时”依然还会有一定的持续开销(通常不太大),而其对应的 Mesh 也会始终存在于内存中(通常也不太大)。
2.GC Alloc 并不是由Instantiate 直接引起的,而是因为被实例化出来的组件会进行 OnEnable 操作,而在 OnEnable 操作中产生了 GC,
因此,我们不建议通过 Instantiate/Destroy 来处理切换频繁的 UI 界面,而是通过 SetActive(true/false),甚至是直接移动 UI 的方式,以避免反复地造成堆内存开销。

3.全屏界面下的3d对象,可以通过隐藏3d对象父节点或隐藏3d相机减少消耗。对于能看到一小部分3d对象的界面,可以通过屏幕截图的方式保存一个备份并显示在界面底部,从而释放底下的3d资源。

4.合并图片(例如道具框和道具底图),透明度为0的对象任然会消耗渲染资源,这种对象尽量隐藏掉。

四、加载相关
加载UI预制的时候,如果把特效放到预制里,会导致加载非常耗时:
UI和特效(粒子系统)的加载开销在多数项目中都占据较高的CPU耗时。UI界面的实例化和加载耗时主要由以下几个方面构成:

纹理资源加载耗时,UI界面加载的主要耗时开销,因为在其资源加载过程中,时常伴有大量较大分辨率的Atlas纹理加载,我们在之前的Unity加载模块深度分析之纹理篇有详细讲解。对此,我们建议研发团队在美术质量允许的情况下,尽可能对UI纹理进行简化,从而加快UI界面的加载效率。

UI网格重建耗时,UI界面在实例化或Active时,往往会造成Canvas(UGUI)或Panel(NGUI)中UIDrawCall的变化,进而触发网格重建操作。当Canvas或Panel中网格量较大时,其重建开销也会随之较大。
UI相关构造函数和初始化操作开销,这部分是指UI底层类在实例化时的ctor开销,以及OnEnable和OnDisable的自身开销。
上述2和3主要为引擎或插件的自身逻辑开销,因此,我们应该尽可能避免或降低这两个操作的发生频率。我们的建议如下:
在内存允许的情况下,对于UI界面进行缓存。尽可能减少UI界面相关资源的重复加载以及相关类的重复初始化;
根据UI界面的使用频率,使用更为合适的切换方式。比如移进移出或使用Culling Layer来实现UI界面的切换效果等,从而降低UI界面的加载耗时,提升切换的流畅度。
对于特效(特别是粒子特效)来说,我们暂时并没有发现将UI界面和特效耦合在一起,其加载耗时会大于二者分别加载的耗时总和。因此,我们仅从优化粒子系统加载效率的角度来回答这个问题。粒子系统的加载开销,就目前来看,主要和其本身组件的反序列化耗时和加载数量相关。对于反序列化耗时而言,这是Unity引擎负责粒子系统的自身加载开销,开发者可以控制的空间并不大。对于加载数量,则是开发者需要密切关注的,因为在我们目前看到的项目中,不少都存在大量的粒子系统加载,有些项目的数量甚至超过1000个,如下图所示。因此,建议研发团队密切关注自身项目中粒子系统的数量使用情况。一般来说,建议我们建议粒子系统使用数量的峰值控制在400以下。

注:原文这里应有一张粒子系统数量统计示意图,但当前仓库未保留对应图片。

unload(false)卸载AssetBundle并不会销毁其加载的资源 ,是必须调用 Resources.UnloadUnusedAssets才行。
五、字体
Font.CacheFontForText主要是指生成动态字体Font Texture的开销, 一次性打开UI界面中的文字越多,其开销越大。如果该项占用时间超过2s,那么确实是挺大的,这个消耗也与已经生成的Font Texture有关系。简单来说,它主要是看目前Font Texture中是否有地方可以容下接下来的文字,如果容不下才会进行一步扩大Font Texture,从而造成了性能开销。

Unity AssetBundle 官方文档整理

一、为什么要使用AssetBundle

AssetBundle是Unity推荐的资源管理方式,热更新必须使用此方式。

二、AssetBundle是什么?

1
2
3
4
5
6
7
First is the actual file on disk. This we call the AssetBundle archive, or just archive for short in this document. 
The archive can be thought of as a container, like a folder, that holds additional files inside of it.
These additional files consist of two types; the serialized file and resource files. The serialized file contains your assets broken out into their individual objects and written out to this single file.
The resource files are just chunks of binary data stored separately for certain assets (textures and audio) to allow us to load them from disk on another thread efficiently.

Second is the actual AssetBundle object you interact with via code to load assets from a specific archive.
This object contains a map of all the file paths of the assets you added to this archive to the objects that belong to that asset that need to be loaded when you ask for it.

1.如上,第一部分是我们在unity里看到的,生成出来的ab包文件,你可以理解成一种特殊的文件夹。我们将chariot打成ab包”animation_2d_chariot.unity3d“,然后用UnityStudio解压”animation_2d_chariot.unity3d“后的结构大概是下面这样子的

他所包含的资源列表如下:

你也可以通过.mainfest文件查看

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
ManifestFileVersion: 0
CRC: 2785811640
Hashes:
AssetFileHash:
serializedVersion: 2
Hash: 05555bf8d49a3c8fc690e4913454de28
TypeTreeHash:
serializedVersion: 2
Hash: 0317c6c2e1c00c8e914e7d09d8b3e9b0
HashAppended: 0
ClassTypes:
- Class: 1
Script: {instanceID: 0}
- Class: 4
Script: {instanceID: 0}
- Class: 21
Script: {instanceID: 0}
- Class: 28
Script: {instanceID: 0}
- Class: 48
Script: {instanceID: 0}
- Class: 114
Script: {fileID: 11500000, guid: 24fd26203f8ea48f1b25f24fc3663d1c, type: 3}
- Class: 114
Script: {fileID: 11500000, guid: c93168c4c5e9f49bfa80fc75bd465a40, type: 3}
- Class: 114
Script: {fileID: 11500000, guid: a6791178c999f426a8618ef42eac4275, type: 3}
- Class: 115
Script: {instanceID: 0}
- Class: 212
Script: {instanceID: 0}
- Class: 213
Script: {instanceID: 0}
Assets:
- Assets/Data/animation/2d/chariot/chariot_5.prefab
- Assets/Data/animation/2d/chariot/chariot_6.prefab
Dependencies:
- F:/ALClient/Assets/Temp/data/shader.unity3d

2.第二部分是脚本中使用的,例如通过unity api AssetBundle.LoadFromFile可以从指定路径加载一个AssetBundle 对象,这个要加载的对象就是上面我们说的unity里面的Asset(你可以理解成AssetBundle是一种特殊的资源,如prefabs),通过这个脚本的AssetBundle,我们可以加载出unity的AssetBundle所包含的文件

三、如何生成AssetBundle文件?

生成AssetBundle文件分两步,第一步标记你要生成AssetBundle的文件,你可以在unity面板直接指定AB包名字,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
To assign a given Asset to an AssetBundle, follow these steps:

Select the asset you want to assign to a bundle from your Project View
Examine the object in the inspector
At the bottom of the inspector
, you should see a section to assign AssetBundles and Variants:
The left-hand drop down assigns the AssetBundle while the right-hand drop down assigns the variant
Click the left-hand drop down where it says “None” to reveal the currently registered AssetBundle names
Click “New…” to create a new AssetBundle
Type in the desired AssetBundle name. Note that AssetBundle names do support a type of folder structure depending on what you type. To add sub folders, separate folder names by a “/”. For example: AssetBundle name “environment/forest” will create a bundle named forest under an environment sub folder
Once you’ve selected or created an AssetBundle name, you can repeat this process for the right hand drop down to assign or create a Variant name, if you desire. Variant names are not required to build the AssetBundles

1.选中该Project中要导出ab包的文件。2在Inspect底部设置AssetBundle

但是一般不推荐这种手撸的方法(大项目动辄几万个文件。。。),一般使用脚本动态进行设置,如下,主要就是针对你要导出的文件、文件夹调用AssetImporter.SetAssetBundleNameAndVariant方法动态设置ab包,你可以进一步封装,例如文件夹xxx目录下的所有文件都设置成单独的ab包或者只有子目录设置ab包等等,这种方法会比手撸效率高很多,只需要设置一次需要导出abbb包的文件,然后在每次打包都调用指定的方法进行ab包设置就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
// 设置单个文件(或目录)的ABName
private static void ImportSingleFile(string Path, string abName)
{
AssetImporter importer = AssetImporter.GetAtPath(Path);

if (importer == null)
{
Debugger.LogError("[路径错误] path:{0}", Path);
return;
}
abName = abName.Replace ('\\', '_').Replace ('/','_');
importer.SetAssetBundleNameAndVariant(abName, BaseDef.AB_SUFFIX);
}

第二步,调用unity的生成ab包的接口(依赖第一步设置完的ab包名字和属性)

BuildPipeline.BuildAssetBundles(string outputPath, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform);

outputPath:输出路径
assetBundleOptions:压缩模式等,unity提供三种压缩模式,官方说明:https://docs.unity3d.com/Manual/AssetBundles-Building.html

BuildAssetBundleOptions.None:LZMA格式压缩,使用的时候要整包解压到内存,最大的压缩比(意味着压缩后的文件最小),但第一次的加载需要更长的时间,unity会将解压后的LZMA重新压缩成LZ4格式并保存在硬盘里,意味着第二次加载将会拥有和LZ4压缩相同的加速度,unity推荐在下载时使用(如热更、高清资源),这样可以节省用户流量,加快下载速度

BuildAssetBundleOptions.UncompressedAssetBundle:不压缩,最大的文件,最快的加载速度
BuildAssetBundleOptions.ChunkBasedCompression:LZ4压缩,使用的时候不需要整包解压,即只解压当前需要的块,这是unity推荐的压缩方式(母包)

Using ChunkBasedCompression has comparable loading times to uncompressed bundles with the added benefit of reduced size on disk.
如果觉得lz4的压缩格式导致包体过大,可以将一部分ab包在压缩成lzma(将要压缩的ab包放在文件夹Temp,再将Temp压缩成LZMA格式,只在用户初次进入游戏时整个解压就好了),相当于压缩了两次

targetPlatform:目标平台android/ios等

四、AssetBundle如何分组?

AssetBundle 数量太少:
  会增加运行时内存使用,因为可能加载了当前功能不需要使用的资源
  会增加加载时间,虽然lz4压缩格式不需要整包解压,但还是会把文件头加载进来的
  需要下载大量数据,包体太大,导致细分度不够,可能其中一个对象更新了会导致其他对象也更新,对热更不友好。

有太多的 AssetBundle:
  会增加构建的时间
  会加大开发的复杂性
  会增加总的加载时间:一个大文件的解压时间和多个小文件的解压时间 ,文件总大小一致的话,肯定是大文件快

官方说明:https://docs.unity3d.com/Manual/AssetBundles-Preparing.html

1.按逻辑实体(功能)分组,例如英雄界面相关的预知体一个包,副本界面相关的预制体打一个包,对热更支持最高

2.按类型分组,例如音效、shader、本地化文件等都单独打1-n个包,对热更版本不友好,因为包体相对会被比较大

3.不相干(concurrent)内容分组,将需要同时加载和使用内容分组到同一个 AssetBundle 的策略。这种策略最常用在强本地相关属性的内容上,也就是说内容很少或者基本不可能在应用特定的位置或者时间之外出现,例如某个副本关卡用到的独特的资源、模型等

官方的分组建议如下:

1
2
3
4
5
6
7
8
9
10
Regardless of the strategy you follow, here are some additional tips that are good to keep in mind across the board:

1.Split frequently updated objects into AssetBundles separate from objects that rarely change
2.Group objects that are likely to be loaded simultaneously. Such as a model, its textures, and its animations
3.If you notice multiple objects across multiple AssetBundles are dependant on a single asset from a completely different AssetBundle, move the dependency to a separate AssetBundle.
If several AssetBundles are referencing the same group of assets in other AssetBundles, it may be worth pulling those dependencies into a shared AssetBundle to reduce duplication.
4.If two sets of objects are unlikely to ever be loaded at the same time, such as Standard and High Definition assets, be sure they are in their own AssetBundles.
6.Consider splitting apart an AssetBundle if less that 50% of that bundle is ever frequently loaded at the same time
7.Consider combining AssetBundles that are small (less that 5 to 10 assets) but whose content is frequently loaded simultaneously
8.If a group of objects are simply different versions of the same object, consider AssetBundle Variants

1.把经常更新的资源放在一个单独的包里面,跟不经常更新的包分离
2.把需要同时加载的资源放在一个包里面,如同一个功能模块。如果两个对象不太可能同时加载,比如一个纹理的高清和标清版本,可以将他们分配到不同的 AssetBundle 中
3.可以把其他包共享的资源放在一个单独的包里面,例如UI界面里面会有很多按钮、弹窗,而这些资源一般是所有界面通用的,那就可以把它们打1-3个图集
4.控制ab包体的大小,太大了,热更的话,要更新很大的文件,太小的话,io次数会很高,对性能不好

五、如何加载AssetBundle?

1.从包含AssetBundle数据的bytes 里读取

2.本地加载最快的接口,如果是lzma的压缩格式,会先解压到内存里(占用内存)

3.从网络加载(也可以从本地加载)

1
2
3
4
5
6
7
8
9
10
11
12
IEnumerator InstantiateObject()

{
string uri = "file:///" + Application.dataPath + "/AssetBundles/" + assetBundleName;
     UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.GetAssetBundle(uri, 0);
yield return request.Send();
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
GameObject cube = bundle.LoadAsset<GameObject>("Cube");
GameObject sprite = bundle.LoadAsset<GameObject>("Sprite");
Instantiate(cube);
Instantiate(sprite);
}

六、如何从AssetBundle中加载文件?

同步加载单个对象:T objectFromBundle = bundleObject.LoadAsset(assetName);

异步加载单个对象:

AssetBundleRequest request = loadedAssetBundleObject.LoadAssetAsync(assetName);
yield return request;
var loadedAsset = request.asset;
同步加载ab包里的所有对象:Unity.Object[] objectArray = loadedAssetBundle.LoadAllAssets();

异步加载ab包里的所有对象:

AssetBundleRequest request = loadedAssetBundle.LoadAllAssetsAsync();
yield return request;
var loadedAssets = request.allAssets;
七、如何使用AssetBundleManifest,什么叫做依赖包?

1
2
3
4
5
6
7
AssetBundles can become dependent on other AssetBundles if one or more of the UnityEngine.Objects contains a reference to a UnityEngine.Object located in another bundle. A dependency does not occur if the UnityEngine.Object contains a reference to a UnityEngine.Object that is not contained in any AssetBundle. In this case, a copy of the object that the bundle would be dependent on is copied into the bundle when you build the AssetBundles. If multiple objects in multiple bundles contain a reference to the same object that isn’t assigned to a bundle, every bundle that would have a dependency on that object will make its own copy of the object and package it into the built AssetBundle.

Should an AssetBundle contain a dependency, it is important that the bundles that contain those dependencies are loaded before the object you’re attempting to instantiate is loaded. Unity will not attempt to automatically load dependencies.

Consider the following example, a Material in Bundle 1 references a Texture in Bundle 2:

In this example, before loading the Material from Bundle 1, you would need to load Bundle 2 into memory. It does not matter which order you load Bundle 1 and Bundle 2, the important takeaway is that Bundle 2 is loaded before loading the Material from Bundle 1. In the next section, we’ll discuss how you can use the AssetBundleManifest objects we touched on in the previous section to determine, and load, dependencies at runtime.

ab包依赖情况有以下两种:

1.ab包引用到另一个ab包里面的资源,即ab包依赖了另一个ab包,例如Bundle1材质A引用了Bundle2的贴图B,那么Bundle1就是依赖Bundle2的,在加载材质A前,你必须先加载Bundle2到内存,unity不会自动加载依赖项。也就是说在你使用某个ab包时,必须先加载他依赖的ab包。

2.ab包引用到另一个不再任何ab包里的资源,例如Bundle1材质A引用了贴图B,而贴图B没有打进任何ab包里,那么最终打ab包时,unity会拷贝一份贴图B到Bundle1,如果有n个Bundle都引用了贴图B,那么这n个Bundle里都会有贴图的拷贝,会造成资源冗余。

AssetBundleManifest文件包含了所有ab包的依赖关系,在使用ab包前,你需要先加载AssetBundleManifest文件,在通过AssetBundleManifest获取ab包的依赖ab包,AssetBundleManifest的加载:

AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset(“AssetBundleManifest”);
获取依赖包:

1
2
3
4
5
6
7
AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
string[] dependencies = manifest.GetAllDependencies("assetBundle"); //Pass the name of the bundle you want the dependencies for.
foreach(string dependency in dependencies)
{
AssetBundle.LoadFromFile(Path.Combine(assetBundlePath, dependency));
}

七、如何判断AssetBundle是否还被引用?

ab包的引用主要作用有两点:1.缓存ab包,卸载无用的ab包,避免内存泄漏 2.判断ab包的依赖包是否已经被加载

核心点是引用技术,每个AssetBundle都会维护一个引用计数,当该ab包被加载、被依赖时引用计数加1,当依赖包被卸载、加载的资源被卸载时,引用计数减1,当引用计数为0超过一段时间(一般为几分钟)时,认为该ab包已经无用了,卸载该ab包。

以下是一个简单的示例(不涉及到从ab包里加载资源),简单说明引用计数的用法,这个例子分两部分,一个是缓存的ab包实体类AssetBundleCache,该类维护一个自己的引用计数,一个是缓存的控制类ABCachePool,该类用于维护ab包的引用、卸载。

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
using UnityEngine;

// AssetBundle缓存
public class AssetBundleCache
{
string m_name; // AssetBundle name
int m_referencedCount; // 引用计数
float m_unloadTime; // 释放时间

public AssetBundleCache(string name, AssetBundle ab, int refCount)
{
m_name = name;
Bundle = ab;
ReferencedCount = refCount;
}

// AssetBundle
public AssetBundle Bundle
{
get;
private set;
}

// 是否常驻,通用资源的ab包不卸载
public bool Persistent
{
get;
set;
}

public string BundleName
{
get
{
return m_name;
}
}

// 引用计数
public int ReferencedCount
{
get
{
return m_referencedCount;
}

set
{
m_referencedCount = value;
if (m_referencedCount <= 0)
{
m_unloadTime = Time.realtimeSinceStartup;
}
else
{
m_unloadTime = 0;
}
if (m_referencedCount < 0)
{
Debug.LogWarningFormat("AssetBundleCache reference count < 0, name:{0}, referencecount:{1}", m_name, m_referencedCount);
}
}
}

// 是否可以删除
public bool IsCanRemove
{
get
{
// 常驻资源
if (Persistent) return false;

// 非常驻,并且引用计数为0
if (!Persistent && ReferencedCount <= 0)
{
return true;
}

return false;
}
}

// 缓存时间到
public bool IsTimeOut
{
get
{
return Time.realtimeSinceStartup - m_unloadTime >= Config.Instance.AssetCacheTime;//这个时间自己定义
}
}

//卸载ab包
public void Unload()
{
if (Bundle != null)
{
Bundle.Unload(false);
Bundle = 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
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
137
138
139
140
141
142
143
144
145
146
147
public class ABCachePool
{
#region Instance
private static ABCachePool m_Instance;
public static ABCachePool Instance
{
get { return m_Instance ?? (m_Instance = new ABCachePool()); }
}
#endregion
Dictionary<string, AssetBundleCache> m_AssetBundleCaches = new Dictionary<string, AssetBundleCache>(); // 缓存队列
HashSet<string> m_persistentABs = new HashSet<string>();

public Dictionary<string, AssetBundleCache> AssetBundleCaches
{
get
{
return m_AssetBundleCaches;
}
}

  //只有在退出游戏时会调用这个接口
public void ClearAllCache()
{
foreach(KeyValuePair<string, AssetBundleCache> keyval in m_AssetBundleCaches)
{
keyval.Value.Unload();
}
m_AssetBundleCaches.Clear();
}

  //是否存在ab包缓存
public bool IsExistCache(string abName)
{
return m_AssetBundleCaches.ContainsKey (abName);
}

// 引用这个bundle
public AssetBundleCache ReferenceCacheByName(string abName)
{
AssetBundleCache cache = null;
m_AssetBundleCaches.TryGetValue (abName, out cache);
if(cache!=null)
{
++cache.ReferencedCount;
}
return cache;
}

// 获取ABCache 不增加引用
public AssetBundleCache GetABCacheByName(string abName)
{
AssetBundleCache cache = null;
m_AssetBundleCaches.TryGetValue (abName, out cache);
return cache;
}

public AssetBundleCache AddCache(string abName, AssetBundle bundle, int refCount)
{
if(m_AssetBundleCaches.ContainsKey (abName))
{
Debugger.LogWarning ("AssetBundleCache already contains key:{0}, it will be cover by new value.", abName);
}

AssetBundleCache cache = new AssetBundleCache (abName, bundle, refCount);
m_AssetBundleCaches [abName] = cache;

if(m_persistentABs.Contains (abName))
{
cache.Persistent = true;
}

return cache;
}

// immediate 只有场景是立刻卸载
public AssetBundleCache UnReferenceCache(string abName, bool immediate = false)
{
AssetBundleCache cache = null;
if (!m_AssetBundleCaches.TryGetValue(abName, out cache))
{
return null;
}

if(cache.Persistent)
{
return null;
}

--cache.ReferencedCount;

if (immediate && cache.IsCanRemove)
{
RemoveCache (abName);
}

return cache;
}

public void RemoveCache(string abName)
{
AssetBundleCache cache = m_AssetBundleCaches [abName];
cache.Unload ();
m_AssetBundleCaches.Remove(abName);
}

private List<string> m_lstRm = new List<string>();
// 清除无引用的AssetBundle缓存
public void ClearNoneRefCache(bool mustTimeout)
{
foreach(KeyValuePair<string, AssetBundleCache> keyval in m_AssetBundleCaches)
{
AssetBundleCache item = keyval.Value;
// 只清除引用计数为0的
if (item.IsCanRemove && (!mustTimeout || item.IsTimeOut))
{
m_lstRm.Add(keyval.Key);
}
}

for(int i=0; i<m_lstRm.Count; i++)
{
RemoveCache(m_lstRm[i]);
}
m_lstRm.Clear();
}

/// <summary>
/// 常驻ab包设置
/// </summary>
/// <param name="arrAB"></param>
public void SetPersistentABs(string[] arrAB)
{
m_persistentABs.Clear();
for (int i = 0; i < arrAB.Length; i++)
{
string strAB = FileHelper.GenBundlePath (arrAB[i]);
m_persistentABs.Add(strAB);

AssetBundleCache abCache;
m_AssetBundleCaches.TryGetValue(strAB, out abCache);
if (abCache!=null)
{
abCache.Persistent = true;
}
}
}
}

八、如何卸载AssetBundle?

1
2
3
4
5
6
7
8
9
10
11
Most projects should use AssetBundle.Unload(true) and adopt a method to ensure that Objects are not duplicated. Two common methods are:

Having well-defined points during the application’s lifetime at which transient AssetBundles are unloaded, such as between levels or during a loading screen.

Maintaining reference-counts for individual Objects and unload AssetBundles only when all of their constituent Objects are unused. This permits an application to unload & reload individual Objects without duplicating memory.

If an application must use AssetBundle.Unload(false), then individual Objects can only be unloaded in two ways:

Eliminate all references to an unwanted Object, both in the scene and in code. After this is done, call Resources.UnloadUnusedAssets.

Load a scene non-additively. This will destroy all Objects in the current scene and invoke Resources.UnloadUnusedAssets automatically.

AssetBundle.Unload可以卸载一个AssetBundle,下面会说到这个方法

Resources.UnloadUnusedAssets会卸载所有不被引用的资源,具体如下图所示

注:原文这里应有一张 Resources.UnloadUnusedAssets 卸载关系示意图,但当前仓库未保留对应图片。

九、注意事项

1.AssetBundle.Unload(bool unloadAllLoadedObjects)

unloadAllLoadedObjects为true时会卸载所有从这个ab包里加载的对象(不包括instantiation对象),例如材质M加载自Bundle1,当Bundle1调用Unload(true)时,材质M也会被删除,对象会在场景中显示红色(缺失)

unloadAllLoadedObjects为false时不会卸载从这个ab包里加载的对象,但会断开和这个对象的联系,例如材质M加载自Bundle1,当Bundle1调用Unload(false)时,材质M不会被删除,当用户再次加载Bundle1的时候不会重新建立和材质M的联系,而是会重新创建一份引用,造成材质M的冗余,如下图,内存里会存在两个材质M,冗余了一份

注:原文这里应有一张材质冗余的内存示意图,但当前仓库未保留对应图片。

关于true跟false,unity官方时间以使用true的,这样不会造成冗余,但是你必须清楚的知道什么时候可以卸载这个ab包

1
2
3
4
5
6
7
8
9
10
11
Most projects should use AssetBundle.Unload(true) and adopt a method to ensure that Objects are not duplicated. Two common methods are:

Having well-defined points during the application’s lifetime at which transient AssetBundles are unloaded, such as between levels or during a loading screen.

Maintaining reference-counts for individual Objects and unload AssetBundles only when all of their constituent Objects are unused. This permits an application to unload & reload individual Objects without duplicating memory.

If an application must use AssetBundle.Unload(false), then individual Objects can only be unloaded in two ways:

Eliminate all references to an unwanted Object, both in the scene and in code. After this is done, call Resources.UnloadUnusedAssets.

Load a scene non-additively. This will destroy all Objects in the current scene and invoke Resources.UnloadUnusedAssets automatically.

2.一个没有被分配到任何ab包中的资源A,任何引用资源A的ab包都会产生一份资源A的拷贝,这会导致游戏的ab包大小变大(资源A冗余了),如果这两个ab包都被加载到内存,那么还会导致内存里存在两份完全一样资源A。

1
2
3
4
5
Any Object that is not explicitly assigned in an AssetBundle will be included in all AssetBundles that contain 1 or more Objects that reference the untagged Object.

If two different Objects are assigned to two different AssetBundles, but both have references to a common dependency Object, then that dependency Object will be copied into both AssetBundles.
The duplicated dependency will also be instanced, meaning that the two copies of the dependency Object will be considered different Objects with a different identifiers.
This will increase the total size of the application’s AssetBundles. This will also cause two different copies of the Object to be loaded into memory if the application loads both of its parents.

解决这个问题的最优解是:把所有ab包引用的资源都打到ab包里,即ab包不引用任何不再ab包里的资源,但是这样做需要程序在加载ab包时,确认该ab包的所有依赖包都已经加载完成了(ab包缓存、ab依赖包加载)

3.图集:首先我们需要大概知道图集在AssetBundle里是以什么形式存在的。如下图所示,一个图集的ab包里包含了这个图集的图片资源以及图集的信息(下图的SpriteAtlasTexture-ui_atlas_elf-1024x1024-fmt12)

注:原文这里应有一张图集资源在 AssetBundle 中的结构示意图,但当前仓库未保留对应图片。

需要注意的是:

1.如果一个图集包含的Sprite资源被包含在多个AssetBundle里,那么所有包含该图集的Sprite的ab包都会有一份图集信息(SpriteAtlasTexture-ui_atlas_elf-1024x1024-fmt12),从上图我们看到,这个文件还是很大的

2.如果一个图集包含的Sprite资源不再任何ab包里,那么图集信息(SpriteAtlasTexture-ui_atlas_elf-1024x1024-fmt12)也不会在任何的ab包里

综上,如果图集分散到多个ab包,会造成资源冗余,会增大包体大小,运行时也会浪费内存,如果不分配到ab包里又无法热更,那么唯一的做法就是一个图集打一个ab包(把相同图集的Sprite放在同一个文件夹,这个文件夹只包含该图集的sprite,在把这个文件夹打成ab包)

4.减少同时加载的AB数量(这个是纯逻辑控制),使用AssetBundle.LoadFromFile接口。使用WWW加载会生成一个新的线程,在移动平台线程多了会导致游戏崩溃,尽量使用UnityWebRequest

常用的Bash 命令 整理

常用的Bash 命令 整理

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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#!/bin/bash
current_time = $(date "+%Y-%m-%d %H:%M:%S")

function replace_str_in_file(){
sed -i "s/$1/$2/g" $3
}

replace_str_in_file 'result.displayType= "YourName"' 'result.displayType="${DISPLAY_NAME}"' applocalInfo.lua

wget 下载整站
wget -r -p -np -k https://raw.githubusercontent.com/

通过ssh备份
tar zcvf - back/ | ssh root@www.jb51.net tar xzf - -C /root/back/

kill
pstree -ap 10277 |grep -oP '[0-9]{4,6}'|xargs kill -9

导出最后一列非空数据
awk -f "|" '{if($NF!="")print $NF}'

行前或行后插入
sed 'p;s/^.*$/----/' file
awk '{print $0;print "----"}' file

sed 's/^/new/g' file
sed 's/$/new/g' file

#生成sshkey 并上传服务器
ssh-keygen -y -f ~/.ssh/id_rsa && cat ~/.ssh/id_rsa.pub | ssh root@host "cat - >> ~/.ssh/authorized_keys"

简单列出当前目录文件
python -m SimpleHTTPServer
python3 -m http.server

查看内存使用情况
ps aux --sort=rss |sort -k 6 -rn

查看ip
ifconfig |awk -F"[ ]+|[:]" 'NR==2 {print $4}'

查找并杀进程
pgrep nginx|xargs kill
pidof nginx|xargs kill

删除注释
sed -i 's/#.*$//g' filename

查看代码,除掉注释和空行
egrep -v "^#|^$"  filename
sed '/#.*$/d; /^ *$/d'

按大小列出文件
ls|xargs du -h|sort -rn
#不递归下级目录使用du -sh

在somefile.sh 文件里加上set+x set-x
1. 用 && || 简化if else
判断是否为空文件
if [[ -s $file ]]; then

    echo "not empty"

fi
获取文件大小
stat -c %s $file

stat --printf='%s\n' $file

wc -c $file

rsync 备份
rsync -r -t -v /source_folder /destination_folder

rsync -r -t -v /source_folder [user@host:/destination_folder

为所有txt文件加上.bak
rename '.txt' '.txt.bak' *.txt
去掉所有的bak
rename '*.bak' '' *.bak

空格替换成下划线
find path -type f -exec rename 's/ /_/g' {} \;
文件名改成大写
find path -type f -exec rename 'y/a-z/A-Z/' {} \;
文件名改成小写
find path -type f -exec rename 'y/A-Z/a-z/' {} \;

# for while 循环


for ((i=0; i < 10; i++)); do echo $i; done

for line in $(cat a.txt); do echo $line; done

for f in *.txt; do echo $f; done

while read line ; do echo $line; done < a.txt

cat a.txt | while read line; do echo $line; done


删除空行


cat a.txt | sed -e '/^$/d'

(echo "abc"; echo ""; echo "ddd";) | awk '{if (0 != NF) print $0;}'

字典结构
hput() {

        eval "hkey_$1"="$2"

}

hget() {

        eval echo '${'"hkey_$1"'}'

        }

$ hput k1 aaa

$ hget k1

aaa
遍历数组
array=( one two three )

for i in ${array[@]}

        do

echo $i

done
获取路径和文件名


$ dirname ‘/home/lalor/a.txt'

/home/lalor

$ basename ‘/home/lalor/a.txt'

a.txt

获取文件名和扩展
var=hack.fun.book.txt

echo ${var%.*}

hack.fun.book

echo ${var%%.*}

hack

echo ${var#.*}

fun.book.txt

echo ${var##.*}

txt


清理僵尸进程
ps -eal | awk '{ if ($2 == "Z"){ print $4}}' | kill -9

创建函数库

将函数定一个在另一个文件,然后通过source 命令加载到当前文件

在命令行使用函数

将函数定义在~/.bashrc 中即可

掌控谈话读书笔记

总结

说话技巧 : 压低声音,放慢语速,用深夜电台DJ的方式和对方谈话
每句话末尾音调下行,而不要抬高上行,给人一种不容分辨的潜在压力

重复对方的话,尤其是最后的词语
在使用DJ语调的前提下,给对方的负面情绪贴牌
用‘看起来’,‘听上去’,‘感觉上’对方有负面情绪这样的套路,来打卡话匣子,从而获取对谈判有利的信息
谈判前罗列对方可能提到的所有负面指责,根据上述策略演练具体应对方式
小心说 yes,掌控说 NO,说NO给人潜在掌控局面的感觉
yes 有三种含义,敷衍,确认事实,承诺行动,只有最后一种才是谈判中需要的,前俩种都是没用的,反倒有副作用

当你让对方相信你真的理解他的梦想和感受时,才有可能发生精神和行为的改变,才能给突破奠定基础
在你说服他们看一看你要完成的目标之前,有必须说一些让他们能回答‘你说的对’的事情
有效的停顿,在增强的时候使用停顿
最低限度的鼓励,用简单的事,好的,了解了,等有效传递信息,表明我正全身心的关注你说的话
重复,避开争吵的话题,而是倾听,把对方的话重复回去
标注,让对方感受到他的感受被辨认出来
释义,用自己的语言复述对方的话,这样足以显示你真的理解他的关心的事
总结,需要倾听和重复对方的话,完整的总结对方的说法,只有这样才能得到’你说的对‘
你说的对,给出的信号时,谈判可以从僵局重启,打消了前进路上的障碍,使我们和对手之间产生了认同点

你说的是正确的,这只是为了让他们不要再打断你

扭转现实 抗衡的手段总事存在的,谈判从来不是一个线性方程,我们有很多不理性的盲点,隐藏的需求尚未发现的信息
永远不要提出一个折中分歧的方案
人们在逼近时限时,往往会不自觉的加快速度,没有耐心,情感会使人忽视做决定时的风险

公平,‘我们只想得到公平’,对方的防御行动,‘我们已经给了你公平’,重复对方的公平
‘我希望你能感觉自己被公平对待,如果你觉得我不够公平,可随时打断我,我们一起来解决问题’
为公平的名誉而奋斗,名誉高于自己,让名誉为你铺平道路
对方抛出公平,让他们解释你是怎么不公平的对待他们的。

扭转对方对现实的认知,确定效应,人会倾向于确定的事物而非其他可能性
损失规避,人们会愿意冒更大的风险去避免损失而非争议的受益,仅仅向对方表明你能把他们的需求传递出去是不够的,
要让对方相信,如果协议不成,他们会失去一些实质性的东西
标定情绪:指控审查来确定对方的恐惧,触发对方的损失规避的心理,预先说明你的建议有多糟糕
对方先出牌,用一个特定的数字来秒定范围,预设和调整
你的名誉走在你的前面,不要过于凶狠吞掉一个没有经验的对手
划定一个范围(鼓励性的范围)
操纵非价格条款,对对方不太重要,但对自己而言是有价值的,非金钱因素
特定数字,表现出严肃和持久,坚定
无关的惊喜礼物,出乎意料的安抚姿态,引发回报

愉快的坚持,非工资条款
确定成功的工作标准,从而度量下一次涨薪
激发对方对你成功的兴趣,获得非正式的导师,
让对方相信,你会誓死捍卫对方的重要利益
需要付出什么才能在这里取得成功
如果有人给你指导,他们会观察你是否遵循他们的建议
这是公平的,我是想你请求涨薪,而不是董事会,我所要的一切就是你的同意

制造幻觉,用校准问题来消除对抗,赢得合作
谈判是哄骗而非征服,是说服对方而非战胜对方,甚至控制对方产生幻觉,主动为你工作
不要在开火时谈判,不要对真实世界的落差视而不见,不是所有的事发生都有剧本
让对方产生控制局面的幻觉,一个开放性问题,How?我如何知道你说的话事真的?承认冲突并给对方提供虚幻的控制权
我该怎么办呢?将冲突转变为可合作性的问题

开放性的校准问题,也许,可能,我想,情绪保持冷静,避免威胁控诉
why,为什么选择我,希望对方看到改变的情况并建立起支持这种改变的防线
why u do this?是什么让你这么做呢?
这里什么东西对你而言更重要?
我如何词啊能让事情对我们更有利的方向发展呢
你希望我如何推进?
是什么导致我们走到今天这个境地?
我们如何才能解决这个问题?
目标是什么?我们在这里想要完成/达成什么目标?
我该如何做?

不要被情绪带偏,咬住自己的舌头,谈判配翻译(给自己组织语言的计划)
被语言攻击时,不要用语言反击,用校准问题来消除对手武装
不要逼迫对方,‘你时对的’

确保执行,如何发现撒谎者,确保所有人贯彻执行
成功不是口头承诺,需要后续执行
没有怎么做,同意将时一文不值
反复提问,‘我怎么知道xx还活着,?我怎么凑到这么多钱,?我不知道xx的情况,怎么才能付你钱?
足够的提问可以塑造谈判环境,前提是清楚知道期望的谈判方向
影响谈判背后的因素,这对你和团队的其他人造成怎样的影响?
意识到谈判桌之外的参与者,一个不安分的参与者就可以毁了协议

关注说谎者的语调,校验语调与语言是否相符,需要标柱来找出不一致的根源

三次原则,1、第一次给你承诺,2、标注,总结对方的话,你说的对 3、校准问题:问什么是成功的必备因素

识破谎言,谎话比真话用词更多,用更多的第三人称代词来拉远自己和谎言的距离,说谎者更倾向于复杂的句子

谈判者的地位越重要,更要避免使用第一人称

互利原则,极端预设点

了解自己的谈判风格
适应型:花时间建立关系,喜欢双赢
善交际,求和平,乐观,不专心,无时间观念
沉默是愤怒
应对:对过度聊天保持清醒

主张型:时间就是金钱,喜欢胜利超越一切
直言不讳,希望被倾听,更多是教你做事,而不是询问
应对:沉默,倾听,重复,校准问题, 软化音调,变得有亲和力,使自己更容易接近

分析师型:喜欢数据分析,系统性,工作勤奋,讨厌意外
怀疑自然人性,做好充足准备,一数据支持论点,避免即兴演讲,防止意外方式
沉默是思考时间
应对:微笑

明白三种类型的不同,并接受,开放的心态,不要用己所不欲的方法威胁对方,而要用适合他们的方法去威胁他们

用开诚布公的方法来躲开攻击,我如何接受这个条件
我们先把价格放一边,讨论一下什么能促进这个协议?
你还能提供什么,以便让我觉得这个价格对我有利?
如果对方硬要你出价,说一个比尔可能会提出的令人吃惊的高价
我看不出这个提议有什么可行的地方(战略性生气)
对不起这个方案对我而言行不通(威胁不是通过愤怒表达,而是靠镇定的姿态)
用为什么提问:为什么和我做生意,为什么离开之前的供应商,他们做的更好
提出主张大不要习以为常,将注意力集中于自身,清楚坚定的表达自己的观点

不要过于渴求,有准备离开的意识, 清楚自己的底线,不惧离开
强硬的友爱,永远不要把对手当做敌人,不要进行人身攻击,就事论事
不要给自己制造敌人

出价–反出价

设定你的目标价
第一次出价的目标价的65%
准备三个可能增加到的价格,85%, 100%, 95%
提价前,说不,抵抗对方
最终数字使用更精确的非整数,增加信服力
抛出一个非金钱的条件,显示你已经达到底线了

找到黑天鹅,通过未知的位置信息来取得突破
有些时候,关键的决定性信息会隐藏在不易被人察觉的未知之处,这需要你去了解对方的信仰,背景,习惯,经历等等信息,才有可能敏锐地发现它。
提供三种类型的杠杆
正面:提供对方想要的东西
负面:让对方感到痛苦,威胁,避免损失,注意谨慎使用威胁,有人宁死不屈
中性:利用别人的正常表现和标准达到预计有利的结果
想要得到杠杆,必须要对方相信交易失败会有哪些实质性的损失
相似性原则,对相似的人更容易信任
观察不警觉的时候,晚餐,休息时间
和对手面对面,盯着对方的眼睛

准备很重要,提前准备一页纸的谈判清单