朝日

ZhaoriGame

HEXO使用手册

测试和搭建环境

  • hexo clean清除了你之前生成的东西,也可以不加。
  • hexo new newpage 新建一个文章
  • hexo generate 顾名思义,生成静态文章,可以用 hexo g缩写
  • hexo deploy 部署文章,可以用hexo d缩写
  • hexo new draft newpage 新建一个 newpage.md 文件
  • hexo publish draft newpage 发布post

hexo史上最全搭建教程

设计原则:依赖倒置原则

依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

总结:依赖倒置原则

  • A.高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。
  • B.抽象不应该依赖于具体,具体应该依赖于抽象。

具体例子

当在 Unity 设计底层模块时,上层模块调用的东西其实就只是一些暴露出来的方法,当底层改动的时候上层也需要改动,这就形成了高层次模块对低层次模块的依赖。这时候可以抽象出来这些需要暴露的方法,即使用接口抽象出来,使得底层依赖了抽象的接口,高层也依赖了抽象的接口,从而使得设计更加清爽。

浅谈Unity开发中的分层设计 中也使用了接口抽象的方式,不过是在底层之间的相互依赖中使用了接口,对于上层还是通过暴露公共方法的方式,加了个 模块管理器,来取得具体模块或者具体实现的接口。

设计模式:桥接模式(Bridge Pattern)

将类的功能层次结构和实现层次结构相分离,使二者能够独立地变化,并在两者之间搭建桥梁,实现桥接。它是一种对象结构型模式,又称为柄体(Handle and Body)模式接口(Interfce)模式

意图:在一个软件系统的抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系,从而使两者可以相对独立地变化。

  • 将类的功能层次分离开,父类拥有子类所共有的功能,子类里实现新的功能。
  • 将类的实现层次分离开,父类声明抽象方法,子类来实现。

桥接模式主要包含以下几个角色

  • Abstraction:抽象类,抽象了功能的实现。
  • RefinedAbstraction:扩充抽象类实现了具体的新的功能,构成功能层次结构。
  • Implementor:实现类接口,提供了用于抽象类的接口。
  • ConcreteImplementor:具体实现类,构成实现层次结构。

现要画一个不同颜色不同形状组合的圆,把抽象化和实现分离开来使其能独立地变化

抽象类:

1
2
3
4
5
6
7
8
9
10
11
public abstract class Shape
{
public Color color;

public void SetColor(Color color)
{
this.color = color;
}

public abstract void Draw();
}

扩充抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Circle : Shape
{
public override void Draw()
{
color.BePaint("圆");
}
}


public class Square : Shape
{
public override void Draw()
{
color.BePaint("正方形");
}
}

实现类的接口:

1
2
3
4
public interface Color
{
void BePaint(string shape);
}

具体实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Red : Color
{
public void BePaint(string shape)
{
Console.WriteLine("红色的" + shape);
}
}

public class Black : Color
{
public void BePaint(string shape)
{
Console.WriteLine("黑色的" + shape);
}
}

画出不同颜色不同形状的圆:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Draw
{
public void DoDraw()
{
Color red = new Red();
Color black = new Black();
Shape circle = new Circle();

//画红色的圆
circle.SetColor(red);
circle.Draw();

//画黑色的圆
circle.SetColor(black);
circle.Draw();

}
}

设计模式:享元模式

一、什么是享元模式?

享元模式(Flyweight),运用共享技术有效地支持大量细粒度的对象。UML结构图如下:
享元模式UML图.png

Flyweight是抽象享元角色。它是产品的抽象类,同时定义出对象的外部状态和内部状态的接口或实现;ConcreteFlyweight是具体享元角色,是具体的产品类,实现抽象角色定义的业务;
UnsharedConcreteFlyweight是不可共享的享元角色,一般不会出现在享元工厂中;
FlyweightFactory是享元工厂,它用于构造一个池容器,同时提供从池中获得对象的方法。

二、内部状态与外部状态的区分

享元享元,共享细粒度的单元。那么什么是细粒度的单元呢?如果用乐高积木作比喻,那么一个积木人可以称为一个完整的对象。如果我们把积木人拆开,可以进一步得到头、身躯,腿三个部分。而这些部分,相比于完整的积木人而言,它们三个就是细粒度的单元。

那么为什么要把一个完整的对象区分内外部呢?这岂不是增加了代码的复杂度?好,我们暂时搁置,接下来思考这样一个问题,如果我们要创造 10 个积木人,用程序怎么表示呢?

我们先创建一个积木人的类,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class LegoMan
{
public string Head;
public string Torso;
public string Leg;

public LegoMan(string head, string torso, string leg)
{
this.Head = head;
this.Torso = torso;
this.Leg = leg;
}
}

接着我们开始创造积木人,先采用直接 new 的方式:

1
2
3
4
5
6
7
8
9
public class Example {
public static void main(String[] args)
{
LegoMan man1 = new LegoMan("head1", "torso1", "leg1");
LegoMan man2 = new LegoMan("head2", "torso2", "leg2");
...
LegoMan man10 = new LegoMan("head10", "torso10", "leg10");
}
}

现在我们得到了 10 个积木人,接下来我们对积木人作出一些限制,我们现在需要 10 个士兵积木人,由于士兵的制服统一,那么这一百个积木人的下半身是完全一样的,也就是说除了 Head,积木人的 Torso 和 Leg 都是一样的。现在我们继续创建十个士兵积木人。好吧,和上面的例子一样,只是传入的后两个参数均一致。

1
LegoMan manN = new LegoMan("headN", "torsoStandard", "legStandard")

接下来,我们思考这样一个问题,如果需要 1000 个士兵积木人呢?如果采用一般的方式,需要创建 1000 个实例对象,但是这 1000 个对象都有着共同的部分,就是它们的 Torso 和 Leg。那我们可不可以把共同的部分抽取出来呢?当然可以,现在我们把 Torso 和 Leg 整合为一个 Body 类,如下:

1
2
3
4
5
6
7
8
9
10
11
public class Body
{
public string Torso;
public string Leg;

public Body(string torso, string leg)
{
this.Torso = torso;
this.Leg = leg;
}
}

既然 Body 被提取出来了,那么 LegoMan 这个类也要被重写了,如下:

1
2
3
4
5
6
7
8
9
10
11
public class LegoMan
{
public string Head;
public Body BodyIntrinsic;

public LegoMan(string head, Body body)
{
this.Head = head;
this.BodyIntrinsic = body;
}
}

现在我们再来创建 1000 个积木人士兵的话,应该是这样:

1
2
3
4
5
6
7
8
9
10
11
12
public class Example {
public static void main(String[] args)
{
//先创建一个通用的 Body
Body bodyStandard = new Body("torsoStandard", "legStandard");

LegoMan man1 = new LegoMan("head1", bodyStandard);
LegoMan man2 = new LegoMan("head2", bodyStandard);
...
LegoMan man1000 = new LegoMan("head1000", bodyStandard);
}
}

发现了吗?现在虽然也是1000个 LegoMan 的实例,但是却只有一个 Body,也就是说,1000个积木人士兵 的 Head,共享了一个 Body。听起来很疯狂,九头蛇也才九头,一千个头的怪物得多可怕!?哈哈,虽然积木人的玩具不可能这么拼,但是程序里,这种共享机制是可行的。Body 就是享元模式中的内部状态,一个重复度很高的细粒度单元。而 Head 则对应外部状态,会随着需求发生变化。这么分离的好处也很明显,就是大大减少了总数据量。如果不分离内外部,创建 1000 个积木人士兵的成本就是1000个 Head 和1000个 Body,而采用分离策略后,就只需要1000个 Head 外加1个 Body了。当随着创建对象数量级的增大,这种策略带来的好处会越来越明显。

三、完整的享元模式

理解了分离内外部的原因后,下面简单实现一下享元模式

1.Flyweight抽象类

通过最上面的 UML 图可以看出,Flyweight 被分为两部分,ConcreteFlyweight(共享的内部)和UnsharedConcreteFlyweight(不可共享的外部)。所以 Flyweight 最好被做成接口,或者抽象类,这里用抽象类实现,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract class Flyweight 
{
//内部状态
public String intrinsic;
//外部状态
public String extrinsic;

//要求享元角色必须接受外部状态
public Flyweight(String extrinsic)
{
this.extrinsic = extrinsic;
}

//定义业务操作
public abstract void Operate(int extrinsic);

}

2. ConcreteFlyweight类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ConcreteFlyweight : Flyweight 
{
//接受外部状态
public ConcreteFlyweight(String extrinsic):base(extrinsic)
{
Debug.Log("共享的 " + extrinsic);
}

//根据外部状态进行逻辑处理
public void Operate(string extrinsic)
{
Debug.Log("处理共享数据 " + extrinsic);
}
}

3. UnsharedConcreteFlyweight类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UnsharedConcreteFlyweight : Flyweight
{
public UnsharedConcreteFlyweight(String extrinsic):base(extrinsic)
{
Debug.Log("非共享的 " + extrinsic);
}

public void Operate(int extrinsic)
{
Debug.Log("处理非共享数据 " + extrinsic);
}

}

4. FlyweightFactory类

既然是处理大量数据,那免不了用一个对象池来进行管理,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class FlyweightFactory 
{
//定义一个池容器
private static List<Flyweight> pool = new List<Flyweight>();

//享元工厂
public static Flyweight GetFlyweight(String extrinsic)
{
var flyweight = pool.Find(obj => obj.extrinsic == extrinsic);

if (flyweight == null)
{
flyweight = new ConcreteFlyweight(extrinsic);
pool.Add(flyweight);
Debug.Log("新创建 " + extrinsic);
}
else
{
Debug.Log("从池中取出 " + extrinsic);
}

return flyweight;
}
}

5.客户端的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Client {

public static void main(String[] args)
{
Flyweight flyweight1 = FlyweightFactory.GetFlyweight("one");
flyweight1.Operate("one");

Flyweight flyweight2 = FlyweightFactory.GetFlyweight("two");
flyweight2.Operate("two");

Flyweight flyweight3 = FlyweightFactory.getFlyweight("one");
flyweight3.Operate("one");

Flyweight unsharedFlyweight = new UnsharedConcreteFlyweight("one");
unsharedFlyweight.operate("one");
}
}

打印结果如下:
新创建 one
处理共享数据 one
新创建 two
处理共享数据 two
从池中取出 one
处理共享数据 one
非共享的 one
处理非共享数据 one

参考博客:
https://www.cnblogs.com/adamjwh/p/9070107.html
https://blog.csdn.net/justloveyou_/article/details/55045638

设计模式:命令模式

命令模式在GoF中的定义是:

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化; 对请求排队或记录请求日志,以及支持可撤销的操作。
命令模式是一种回调的面向对象实现。

游戏设计模式里把它精简为:

命令是具现化的方法调用。

两种术语都意味着将概念变成数据一个对象可以存储在变量中,传给函数。
所以称命令模式为“具现化方法调用”,意思是方法调用被存储在对象中。
类似C#里的回调
把一个对象传递到方法中,让方法内部解析。

下面是一个C#版本的角色控制,传入一个角色,就能调用对应的各种行动。

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

/// <summary>
/// 命令基类
/// </summary>
public abstract class Command
{
public abstract void Execute(BaseCharacter character);
}

/// <summary>
/// 跳的命令
/// </summary>
public class JumpCommand : Command
{
public override void Execute(BaseCharacter character)
{
character.Jump();
}
}

/// <summary>
/// 射击的命令
/// </summary>
public class FireCommand : Command
{
public override void Execute(BaseCharacter character)
{
character.Fire();
}
}

/// <summary>
/// 移动的命令
/// </summary>
public class MoveCommand : Command
{
public override void Execute(BaseCharacter character)
{

}
}

/// <summary>
/// 对输入的解析
/// </summary>
public class InputHandler
{

private JumpCommand buttonA;
private FireCommand buttonD;
private MoveCommand buttonW;

public Command HandleInputAction()
{
if (Input.GetKeyDown(KeyCode.A)) { return buttonA; }
if (Input.GetKeyDown(KeyCode.D)) { return buttonD; }
if (Input.GetKeyDown(KeyCode.W)) { return buttonW; }

return null;
}



private Player mPlayer;
private Enemy mEnemy;

public void Command()
{
Command command = HandleInputAction();

if (command != null)
{
command.Execute(mPlayer);
command.Execute(mEnemy);
}
}
}

public class Player : BaseCharacter
{
public override void Fire()
{
base.Fire();
}
public override void Jump()
{
base.Jump();
}
}

public class Enemy : BaseCharacter
{
public override void Fire()
{
base.Fire();
}
}
/// <summary>
/// 角色的基类
/// </summary>
public abstract class BaseCharacter
{
public virtual void Jump() { }
public virtual void Fire() { }
}

设计模式:状态模式(有限、分层和下推状态机)

实现了最简单的有限状态机

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
public class FSM : MonoBehaviour
{
public MonoStateMachine monoStateMachine = new MonoStateMachine();

public RunState RunState;
public IdleState IdleState;

private void Start()
{
monoStateMachine.StartState(RunState);


}

private void Update()
{
monoStateMachine.Update();
}
}

public class RunState : MonoState
{
public static RunState instance;

public static RunState Instance()
{
if (instance == null)
{
instance = new RunState();
}
return instance;
}

public override void Enter()
{
Debug.Log("进入跑步");
}

public override void Execute()
{
Debug.Log("开始跑步");
}

public override void Exit()
{
Debug.Log("退出跑步");
}


}

public class IdleState : MonoState
{

public static IdleState instance;

public static IdleState Instance()
{
if (instance == null)
{
instance = new IdleState();
}
return instance;
}

public override void Enter()
{

}

public override void Execute()
{

}

public override void Exit()
{

}

}

public class MonoState
{
public virtual void Enter()
{

}
public virtual void Execute()
{

}

public virtual void Exit()
{

}

}

public class MonoStateMachine
{
// private MonoState mOwner;

private MonoState mCurrentState;
private MonoState mPreviousState;
private MonoState mGlobalState;


public MonoStateMachine()
{
mCurrentState = null;
mPreviousState = null;
mGlobalState = null;
}

/// <summary>
/// 设置初始状态
/// </summary>
/// <param name="state"></param>
public void StartState(MonoState state)
{
mCurrentState = state as MonoState;
mCurrentState.Enter();

}

public void ChangeState(MonoState state)
{
if (state == null)
{
Debug.Log("无法找到此状态");
}

mPreviousState = mCurrentState;
mCurrentState.Exit();
//转换后
mCurrentState = state as MonoState;
mCurrentState.Enter();

}
/// <summary>
/// 还原之前的状态
/// </summary>
public void RevertToPreviouState()
{
ChangeState(mPreviousState);
}

/// <summary>
/// 得到当前状态
/// </summary>
/// <returns></returns>
public MonoState GetCurrentState()
{
return mCurrentState;
}

/// <summary>
/// 得到之前的状态
/// </summary>
/// <returns></returns>
public MonoState GetPreviousState()
{
return mPreviousState;
}

public void Update()
{
if (mCurrentState!=null)
{
mCurrentState.Execute();
}
}
}

单例

  • 单例为最常见的一种设计模式,目前在用的主要有两种方式,一种是基于Unity的,一种是基于C#的
  • 一般来说,unity里的单例分为两种,一种是继承于Monobehaviour的,一种是不继承于它的;
  • 这里给出了两种实现方法

使用Unity里的方法

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
public abstract class ScriptSingleton<T>  : MonoBehaviour where T : ScriptSingleton<T>
{
protected static T _instance;
public static T Instance
{
get
{
if (_instance == null)
{
//从场景中找T脚本的对象
_instance = FindObjectOfType<T>();
if (FindObjectsOfType<T>().Length > 1)
{
Debug.LogError("场景中的单例脚本数量 > 1:" + _instance.GetType().ToString());
return _instance;
}
//场景中找不到的情况
if (_instance == null)
{
string instanceName = typeof(T).Name;
GameObject instanceGO = GameObject.Find(instanceName);
if (instanceGO == null)
{
instanceGO = new GameObject(instanceName);
DontDestroyOnLoad(instanceGO);
_instance = instanceGO.AddComponent<T>();
DontDestroyOnLoad(_instance);
}
else
{
//场景中已存在同名游戏物体时就打印提示
Debug.LogError("场景中已存在单例:" + instanceGO.name);
}
}
}
return _instance;
}
}

void OnDestroy()
{
_instance = null;
}
}

通过反射实现的单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public abstract class Singleton<T> where T : Singleton<T> 
{
protected static T mInstance = null;
protected Singleton() { }
public static T Instance
{
get
{
if (mInstance == null)
{
var ctors = typeof(T).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);
var ctor = Array.Find(ctors, c => c.GetParameters().Length == 0);
if (ctor == null)
{
throw new Exception("Non-public ctor() not found!");
}
mInstance = ctor.Invoke(null) as T; }

return mInstance;
}
}
}
}

模板测试(Stencil Test)相关

介绍

模板测试一般发生在深度测试前,在片段着色器处理完一个片段后执行。和深度测试一样也会丢弃片元。模板测试是根据一个缓冲来进行的,它叫做模板缓冲(Stencil Buffer)

一个模板缓冲中,(通常)每个模板值(Stencil Value)是8位的。所以每个像素/片段一共能有256种不同的模板值。我们可以将这些模板值设置为我们想要的值,然后当某一个片段有某一个模板值的时候,我们就可以选择丢弃或是保留这个片段了。

使用方法

模板缓冲操作允许我们在渲染片段时将模板缓冲设定为一个特定的值。通过在渲染时修改模板缓冲的内容,我们写入了模板缓冲。在同一个(或者接下来的)渲染迭代中,我们可以读取这些值,来决定丢弃还是保留某个片段。使用模板缓冲的时候你可以尽情发挥,但大体的步骤如下:

  • 启用模板缓冲的写入。
  • 渲染物体,更新模板缓冲的内容。
  • 禁用模板缓冲的写入。
  • 渲染(其它)物体,这次根据模板缓冲的内容丢弃特定的片段。

所以,通过使用模板缓冲,我们可以根据场景中已绘制的其它物体的片段,来决定是否丢弃特定的片段。

具体案例

一般来说,stencil完整的语法如下

1
2
3
4
5
6
7
8
9
10
11
stencil
{
Ref referenceValue
ReadMask readMask
WriteMask writeMask
Comp comparisonFunction
Pass stencilOperation
Fail stencilOperation
ZFail stencilOperation
}

我们可以用模板缓冲来实现一个物体轮廓,步骤如下:

  1. 在绘制(需要添加轮廓的)物体之前,将模板函数设置为GL_ALWAYS,每当物体的片段被渲染时,将模板缓冲更新为1。
  2. 渲染物体。
  3. 禁用模板写入以及深度测试。
  4. 将每个物体缩放一点点。
  5. 使用一个不同的片段着色器,输出一个单独的(边框)颜色。
  6. 再次绘制物体,但只在它们片段的模板值不等于1时才绘制。
  7. 再次启用模板写入和深度测试。

这个过程将每个物体的片段的模板缓冲设置为1,当我们想要绘制边框的时候,我们主要绘制放大版本的物体中模板测试通过的部分,也就是物体的边框的位置。我们主要使用模板缓冲丢弃了放大版本中属于原物体片段的部分。

0%