FishPlayer

一个喜欢摸鱼的废物

0%

之前我用Unity自带的API去查询一些物体的引用,结果当然是超超超超超级慢,对于那些把项目工程放在辣鸡机械盘上的同事,这个工具根本是没法好好用的。而且因为要用Unity的APi,自然是没办法使用async的。

最近导师拿到QA那边提供的一个类似的工具,改了一下,做了一个超级快的版本。

思路

思路其实是非常简单的。我们知道Unity Asset会有一个唯一的GUID,那我们就可以用这个GUID去查询其引用。
查询的时候我们也不需要使用Unity API去检查引用,而是把我们的各种Asset都当成文本文件直接读取,然后直接在这些文本里去匹配我们需要查询的GUID。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

// guidRegex just contains the asset guid
List<string> GetResult(string[] folders, Regex guidRegex)
{
string[] files = folders.SelectMany(folder => Directory.GetFiles(folder, "*"SearchOption.AllDirectories)).ToArray();
List<string> result = new List<string>(200);
int count = files.Length;
for (int i = 0; i < count; i++)
{
string filePath = files[i];
if (ChecKFile(filePath, guidRegex))
{
result.Add(filePath);
}
}
return result;
}

bool ChecKFile(string filepath, Regex fileContent)
{
string assetText = File.ReadAllText(filepath);
MatchCollection matches = fileContent.Matches(assetText);
return matches.Count > 0;
}

关键代码和Unity API毫无关系,可以避免长时间的卡死,而且我们也能加入取消搜索的功能之类的。如果在乎性能的话可以还可以在读文件检查这边做优化。不过感觉这样已经比之前快了许多了。

之前看了一些关于RectTransform的文章,但是最终记忆都不是很深刻。而且要用的时候重新找还挺麻烦的。
干脆把别人的笔记抄来一些好了,以后也方便就着自己的笔记和UXUI同学解释。

Pivot

中心点,是UI元素旋转/缩放的中心点。使用归一化Vector2表示。

Anchor

其实是由两个点组成的(AnchorMin, AnchorMax)。并使用归一化Vector2来表示。
数值代表了在父类X轴和Y轴方向的百分比。

绝对布局

当anchorMax与anchorMin相等时,Anchor呈现为一个点,称之为锚点
在使用锚点的情况下,anchoredPosition是元素Pivot到Anchor的距离

此时会有4个重要的属性。

  • PosX, posY : 中心点到锚点的参数,实际像素值
  • Width, Height : UI 元素的尺寸

绝对布局的情况下无论分辨率是多少,父物体多大,该UI元素的大小是恒定的。

相对布局

当anchorMax与anchorMin不相等时,Anchor呈现为一个框,称之为锚框
在使用锚框的情况下,anchoredPosition是元素Pivot到锚框中心点的距离

这种情况下UI元素的四个角,距离四个对应的锚点的距离是不变的,在这种情况下RectTransform的属性又变为了Left,Top,Right,Bottom。

  • Left,Top,Right,Bottom : 四个点的数值分别是(Left,Top,Right,Bottom)锚点到实际的rect的这4个位置的点的距离。

SizeDelta

OffsetMin/OffsetMax

min是实际UI原素相对于AnchorMin的偏移,另外一个不言而喻.

sizeDelta就是offsetMax - offsetMin的值。

所以这个属性之所以叫做sizeDelta,是因为在锚点情况下其表征的是size(大小),在锚框的情况下其表征的是Delta(UI元素实际的属性值与锚框的差值)

Rect

rect中的属性,不与UI元素所在的位置有关,只和其自身属性相关。根据rect中提供的width和height可以得到UI元素实际的尺寸大小。

rect.position指的是以Pivot为原点,UI元素左下角的坐标。(right,up 为正方向)

参考资料

https://zhuanlan.zhihu.com/p/194317677
https://blog.csdn.net/jmu201521121014/article/details/105725175

顺便推荐一下在线画图小工具,不方便贴图的时候用这个还挺不错的。
https://asciiflow.com/#/
https://textik.com/

这周赶版本,接到了一个前同事留下来的bug。是说UI上的教程视频一直不播放。
我们的视频资源是在 StreamingAssets 下的,用path进行加载。
播放相关的代码如下。

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

void OnEnable()
{
m_player.prepareCompleted += OnPrepareCompleted;
}

void OnPrepareCompleted(VideoPlayer source)
{
Debug.Log("OnPrepareCompleted");
m_player.Play();
}

public void SetVideo(string path)
{
m_player.source = VideoSource.Url;
// I guess this process may not be finished now.
m_player.url = path;
m_player.Prepare();
}

查了一下,发现在调用了 SetVideo 之后,OnPrepareCompleted没有被调用,意思是 VideoPlayer的prepareCompleted事件没有触发。
我猜发生这个情况的原因是,当我们使用url来设置视频资源的那一帧,VideoPlayer还没能获取到视频资源的一些属性,或是上一个视频资源还在占用着当前的播放器,所以这个时候如果调用PrePare,其实是没有用的。

所以我使用的Hacky Fix是在设置完资源的URL之后,等一帧再调用Prepare,然后等待 prepareCompleted 事件。

顺便在网上查了一下,也有些人遇到类似的问题,说是赋值URL之后不知道视频资源什么时候才能正确播放。
论坛里有个人给出的解决方法是去检查 VideoPlayer.isPrepared 的这个属性。但赋值URL之后这个值并不会在某一时刻变成true。

Well just ignore dat weird tag. :D

Here is muy notes dat I learn from the Divsion UI talk.

The key is iteration, threading and data.

Iterate your stuff to make it better.

Threading and data driven make the work efficient.

video.

Snow drop UI

  • No canvas
  • Immediate Mode
    • Constant cost (maybe it’s more statble?).
  • Vector Graphics
    • Shapes(scaling friendly, dun rely on resolution) over textures masks.
    • Save GPU load by only rendering the pixels gonna be used.
    • Artist can play with it.

Game Data & Node graph

  • Lots of data getter (get pure data?) (in graph node), used in UI and also game play.

  • Auto conversion to save some performance.

  • custom UI logic components.

  • custom reusable compounds.

Iteration

For coder

Reduce complie time

  • Blob builds(idk what is this).
  • Header file reduction.
  • Keep refactoring code to make it efficient.
  • Remove nolonger used code.

Reduce startup time and Load time

  • Only load needed stuff.
  • UI preview with live data for easy iteration.
  • Well some debug UI will also help :).

(for my unity project)

  • Have a UI preview scene (UI only, we can preview states and transitions).
  • Have simple a gym dat provide gameplay with enough content.

Coder to Artist

  • Coder work with artist (react fast to feedbacks).

  • Resable compounds, arist can directly use.

Drawback of iteration

  • Hard to stop.
  • Set clear goals and deadlines. (do not waste time)
  • Greate for prototyping, risk for delivering. (need a lead to shut down iteration, make the decision)

Building blocks of UI

To let arist and designers to have full(or more) control?

Widget and Graphics

Widget define space, accept input. Graphic improve widget, relative size, also accept input?

5 Basic widgets with a few variations.

Use basic widgets, and use them to build functionalities.

  • Window

    • 2D and 3D (anchoring functionalities, pixels or percentage; prepare some options for different auto scaling and apply to different UI widgets/graphic and situations).
    • Vertical Stack (stack the (children)widgets)
    • Layering
  • Text

    • JIT font generaion (wtf, keep memory down)
    • Fich text formatting lib to build customize text(icon, color …).
    • Auto clip (no idea how to do it)
  • Image

    • Use color/gradient more. (looks good with vector graphic)
    • Texture and loose image(ikd this, no border image?) into sprite sheet.
  • Stack container

    • For sorting elements.
    • Spacing and padding (how to do it with stack)
    • Invert for quick right to left language fixes (wtf)
  • Scroll Box

    • Fixed size(viewport size) with a virtual inner space(content).

7 Graphics (nodes)

  • Text with effects.
  • Images(basic) with more shapes, flat or curved.
  • Lines(strait, curved list?, 3d)
  • Points, single point or large squares(for effects?)
  • Shapes
  • Sector (why? for their art style?)
  • Custom graphics

Simply Ui workflow

  • Complex and powerful (lots of resuable compounds, and threading stuff)
  • Documentation and examples(I guess examples are better and save time)

For coder

  • Simple code; Less code, less bugs
  • Simple Tools; Do not overthink. Simple base, complex behaviour.

For UI artist/designer

  • Communicate to avoid merge conflicts, get ahead of it (cuz yaml merge wont help u all the time).

最近在做红点功能,path是用string挂在UI物体上的。UI同学那边想要一个按钮可以切换path在inspector是可编辑或不可编辑的状态。
最开始毫无思路,因为本身path是一个string,我自然不可以它身上记录这个编辑与否的状态。后来想着能不能把这个状态存在path的protperty drawer上。问了下导师,可行,但是没法直接存。

思路

创建一个object存储这个状态,这个object存入editor的state object里。
在Property drawer进行绘制的时候获取/更新这个object。

代码

代码很简单

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

public class UITagDrawer : PropertyDrawer
{
// the object for string state
class EditingState
{
public bool IsEditing = false;
}

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
/***/

// get state object
EditingState state = GUIUtility.GetStateObject(typeof(EditingState), GUIUtility.GetControlID(FocusType.Passive)) as EditingState;

using (new EditorGUILayout.HorizontalScope(style))
{
Rect editButtonRect = new Rect(position.x, position.y, 16f, 16f);
if (GUI.Button(editButtonRect, "E"))
{
// well since it's a class we dun need to set it back :>
state.IsEditing = !state.IsEditing;
}

if (GUILayout.Button("X", GUILayout.Width(18)))
{
state.IsEditing = !state.IsEditing;
}
if (state.IsEditing)
{
// show text field
tagStr = EditorGUILayout.TextField(tagStr);
}
else
{
// show lable
EditorGUILayout.LabelField(tagStr);
}
}

}
}

以前弄过一个在项目中查找Asset引用的。虽然很泛用,但跑起来非常的慢。
其实我们实际使用的时候,我们会经常做某一种搜索(比如查找脚本在预制体上的引用)。对于这种需要使用的特定条件的查找,单独做一个小工具可能效率会更高。

本来还在烦恼怎么做,导师很快就做好了,跑起来很快。他告诉我,秘诀是 GetComponent。

思路

代码不方便直接贴出来,但还是想分享(记录)一下几个关键的步骤和思路~

选择脚本

我们需要在编辑器上用一个 field 去放我们想要搜寻的脚本。
但是没办法是用在泛型类上。需要指定实际的类型。

Interface也可以查,放心用!

1
2
3
4
5
6
7
8

targetScript = EditorGUILayout.ObjectField(m_targetScript, typeof(MonoScript), false) as MonoScript;

/*
class SomeMono<TData> : MonoBehaviour
doesnt work on this case, the result will be null
*/
Type targetType = m_targetScript.GetClass();

获取预制体

从我们选择的目录下获取所有的prefab,相信Unity,这一步不会很慢的。

1
2
3
4
5
6
7

// we can also set the folderPath to save our time :D

var paths = AssetDatabase.FindAssets($"t: prefab", new[] { folderPath });
GameObject[] toCheck = new GameObject[paths.Length];
for (int i = 0; i < paths.Length; i++)
toCheck[i] = AssetDatabase.LoadAssetAtPath<GameObject>(AssetDatabase.GUIDToAssetPath(paths[i]));

查询引用

1
2
3
4
5

Type targetType;
GameObject prefab;
var components = prefab.GetComponents(targetType);
// if components is not empty, then you get your stuff :D

总结

如果要说学到了什么的话,大概是做工具前要搞明白它的使用情景,不能太脱节Tw.T

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

被封在鼠窝里两个月了,一直都是在家用自己的老爷备用机做工作。但是老(项)爷(目)机的性(优)能(化)实在是太差了。
打开 Unity 需要5分钟,编译代码3分钟,点一下 play 又是2分钟。这段时间来回修各种BUG,切来切去浪费了很多时间。

决定还是用大奶机工作了,但又非常不愿意把公司的间谍全家桶装在电脑上,咋办呢?

上网查了一下,大家说可以把全家桶关在虚拟机里,然后再把网络分享给宿主机。

Hyper-V

最开始试用了 VirtualBox 无论是装32位还是64位的Win10或者 WinServer 都卡到飞起。即使把虚拟机的虚拟盘放在 SSD 上也还是卡。
了解到 Windows 有这个 Hyper-V 虚拟机就决定试用一下。

结果这个玩意儿性能超棒的哦!放在 SSD 上可以说是不怎么卡。而且也能直接读宿主机的硬盘或油盘,非常的方便,虽然读盘的时候硬盘使用率显示是拉满的。

我记得以前如果要要使用 VM 或者 VBox 的话需要关闭 Hyper-V 的功能,但是自从 Win10 20H2 还是某个版本之后就不需要这么做了。

虚拟机启动小坑

“Boot Issue: Start PXE over IPv4”

装好系统以后俺启动虚拟机就报这个错误。
把硬盘移动到第一位就好了。

配置虚拟网卡

参考了网上的通用方案,需要两张虚拟网卡,一张桥接用来上网,另一个接收全家桶的分享。

桥接

我这边选用的是有线网的接口

内部网络

ipconfig 结果

宿主机配置

接下来就是配置宿主机,这又有一个不一样的情况出现了。

在 Hyper-V 那边添加了1个虚拟交换机以后, 宿主机这边会出现2个适配器。不知道是不是我这边特有的情况。
把内部网络适配器的IP地址设置到 192.168.137.0 网段,我这边修改的是这个名字相同的。

然后宿主机添加转发就可以了,不过速度不够快,全家桶直接装宿主机上还是比较快的,

1
route add x.y.0.0 mask 255.255.0.0 192.168.137.1

参考资料

https://yidianyidi.fun/VirtualBox/yidianyidi-1708121150.html

https://www.youtube.com/watch?v=9Hc-5EOtaJs

经过了一次用户测试之后,拿到的反馈说我们很多页面的UX设计都不行,只能各种重做,不论是我程序这边还是UXUI组那边,我们都需要重新拼好一些 prefab。

然后还有一些旧的 prefab 我们需要换皮改尺寸之类的,这次就来分享一个简单的小工具,用来做一些简单的尺寸适配。

RectTransform 上 anchor 的 Min, Max 都是已经做了归一化的值。依靠调整 anchor 的值就可以设置当前元素在父元素中的相对位置和面积占比。
具体参考下面这篇文章
https://blog.csdn.net/ChinarCSDN/article/details/88983587

代码

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

[InitializeOnLoad]
public static class RectTransformExtendedEditor
{
struct EditContext
{
public RectTransform Transform;
}

private static EditContext m_context;
private static bool m_showTool;

static RectTransformExtendedEditor()
{
Selection.selectionChanged += OnSelectionChanged;
}

private static void OnSelectionChanged()
{
SceneView.duringSceneGui -= OnSceneGUI;
if (Selection.activeTransform is RectTransform rectTransform)
{
m_context = new EditContext()
{
Transform = rectTransform
};

SceneView.duringSceneGui += OnSceneGUI;
}
}

private static void OnSceneGUI(SceneView sceneView)
{
Handles.BeginGUI();
if (GUI.Button(new Rect(0, 0, 20, 20), "T"))
{
m_showTool = !m_showTool;
}
if (m_showTool)
{
GUILayout.BeginArea(new Rect(0, 20, 120, 320));
if (GUILayout.Button("Set Anchors"))
{
RectTransform rectTransform = m_context.Transform;
RectTransform parent = rectTransform.parent as RectTransform;

Undo.RecordObject(rectTransform, "Set Anchors");

if (parent != null)
{
Vector2 anchorMin = new Vector2(
rectTransform.anchorMin.x + rectTransform.offsetMin.x / parent.rect.width,
rectTransform.anchorMin.y + rectTransform.offsetMin.y / parent.rect.height);

Vector2 anchorMax = new Vector2(
rectTransform.anchorMax.x + rectTransform.offsetMax.x / parent.rect.width,
rectTransform.anchorMax.y + rectTransform.offsetMax.y / parent.rect.height);

rectTransform.anchorMin = anchorMin;
rectTransform.anchorMax = anchorMax;
rectTransform.offsetMin = rectTransform.offsetMax = Vector2.zero;
}
}
GUILayout.EndArea();
}

Handles.EndGUI();
}
}

End

有了这个 acnhor 适配真的方便不少。有时候我们做好一个 list element,但在不同的地方用到的时候大小可能不一样。
用这个适配调整以后可以防止各种奇妙出格,而且就点两下按钮,对于 UXUI 团队来说也是非常容易上手的。

为了方便策划在 scriptable object 上配置我们的任务以及事件相关的文本,我做了一个简单的 struct 。里面包含了 I2 的 Lockey 和一个参数数组。
既然要方便策划配置,那肯定要做一个简单的预览,这样策划就能知道自己配置的文本参数是否正确。

既然要做预览,那肯定得得自己复写一下他的 property drawer了。对一个字段的编辑器绘制重写有两种方式,一种是继承 OdinValueDrawer class, 还有一种是 CustomValueDrawer Attribute.

https://odininspector.com/tutorials/how-to-create-custom-drawers-using-odin/how-to-create-a-custom-value-drawer
https://odininspector.com/attributes/custom-value-drawer-attribute

踩坑

我定义的结构是这样的。

在默认情况下他已经能在 inspector上画出我的 Lockey 和我的参数数组。
因为考虑到给整个结构做 OdinValueDrawer 需要自己画数组有点麻烦,所以我选择用 CustomValueDrawer 只重新画 LocalizedString。

LocalizedString 的重画我直接照搬了同事的,但是我发现了一个非常奇怪的问题。

我创建了一个下拉选框,然后在用户选中一个新的 lockey 的时候我对结构体中的 lockey 进行更新。但我发现在下拉选框的选中事件触发后进行赋值,绘制完成再返回新的结构,得到的是没有被更改的结果。
但是同样的绘制代码,在 OdinValueDrawer 中却能得到正确的结果!!!CustomValueDrawer 和 OdinValueDrawer 的原理是不一样的。

OdinValueDrawer 就是相当于一个更先进的 Property Drawer, 在这个绘制器里是可以获取到原本的实际的字段值的。

CustomValueDrawer 则像是一个函数,里面包含了绘制的方法,但是不能够直接操作原本的字段,需要最后把你更新到的值返回出去,这才能够赋值给实际的那个字段。

代码给出,有问题的地方使用注释标明了。

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

[Serializable]
public struct LocalizationStringParmPack
{
[CustomValueDrawer("DrawPreviewText")]
public LocalizedString TextLoc;
public LocalizationParamsManager.ParamValue[] ParmsArray;
public override string ToString()
{
return this.GetRawEnglishString();
}

#if UNITY_EDITOR

static GUIStyle m_previewStyle;
static GUIStyle PreviewStyle
{
get
{
if (m_previewStyle == null)
{
m_previewStyle = new GUIStyle(EditorStyles.textArea);
m_previewStyle.wordWrap = true;
}
return m_previewStyle;
}
}

// use hacky stuff to get selected new lockey
static string s_wtfKey = string.Empty;
static bool s_hasChanged = false;
LocalizedString DrawPreviewText(LocalizedString value, GUIContent label)
{
using (new EditorGUILayout.VerticalScope(UnityEditor.EditorStyles.helpBox))
{
// draw lockey
using (new EditorGUILayout.HorizontalScope())
{
if (label != null)
{
EditorGUILayout.LabelField(label, GUILayout.Width(EditorGUIUtility.labelWidth));
}

// weird stuff happin in dropdown
if (EditorGUILayout.DropdownButton(new GUIContent(value.mTerm), FocusType.Keyboard))
{
var terms = LocalizationManager.GetTermsList();
var selector = new GenericSelector<string>("Select Term", terms);
selector.EnableSingleClickToSelect();
selector.SelectionConfirmed += s =>
{
if (s.Any())
{
s_wtfKey = s.First();
// WEIRD STUFF HERE
/* if I do
LocalizedString next = new LocalizedString(s.First());
and return 'next' and the end of method, it just doesnt work!!!

cuz the GenericSelector is actually a editor window, and
*/
s_hasChanged = true;
}
};
selector.ShowInPopup();
}

int mask = (value.mRTL_IgnoreArabicFix ? 0 : 1) +
(value.mRTL_ConvertNumbers ? 0 : 2) +
(value.m_DontLocalizeParameters ? 0 : 4);

using (var changed = new EditorGUI.ChangeCheckScope())
{
int newMask = EditorGUILayout.MaskField(mask, new string[] { "Arabic Fix", "Ignore Numbers in RTL", "Localize Parameters" }, GUILayout.Width(30));
if (newMask != mask)
{
value.mRTL_IgnoreArabicFix = (newMask & 1) == 0;
value.mRTL_ConvertNumbers = (newMask & 2) == 0;
value.m_DontLocalizeParameters = (newMask & 4) == 0;
}
}

if (GUILayout.Button($"Clear"))
{
/* this will also work
LocalizedString next = new LocalizedString("[NULL]");
*/
s_wtfKey = "[NULL]";
}

}

LocalizedString next = s_hasChanged ? new LocalizedString(s_wtfKey) : value;
s_hasChanged = false;
/*
draw preview
draw preview
draw preview
*/
return next;
}
}

string GetParamValue(string key)
{
for (int i = 0; i < ParmsArray.Length; i++)
{
if (ParmsArray[i].Name == key)
{
return string.IsNullOrEmpty(ParmsArray[i].Value) ? key : ParmsArray[i].Value;
}
}
return key;
}

#endif
}

又踩坑

因为这边使用了静态字段,所以如果需要同时绘制这个字段两次,那就会出问题。所以最后我还是换回OdinValueDrawer。
静态字段最好还是别在这种情况下使用吧。