FishPlayer

一个喜欢摸鱼的废物

0%

之前看了一些关于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);
}
}

}
}

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

Unity 的 InputSystem 正式发布也好一段时间了。本来这边也想自己做一套输入处理但是看 Unity 这个官方的还挺方便就打算直接用。

InputAction

说下我对这个 InputAction 的简单理解。

InputAction

- Composite
    - Binding
- Binding
  • Interaction
  • Processor

InputAction 是定义的最上层的交互事件,我认为他是直接和 gameplay 层面交互的,提供响应事件和输入设备的数据(比如鼠标的位置,或是线性按钮的数值)

Binding 是具体到输入设备上按键,线性输入的一个数据结构

Composite 是把 Binding 提供的直接输入数据处理成我们想要的数据并提供给 InputAction。

另外两个可以应用于InputAction,Composite 和 Binding 的组件

Interaction 是用来规定当前的 输入/行为 触发的机制。

Processor 是用来对当前的输入数据做追加处理的。

rebind

我在官方的tank example中做的 rebind save

对于简单的 Button Action 里的 Binding, rebind 的时候我们是追加了 OverridePath 给这个 Button Action 下面的 Binding。

Path 是一个字符串, 大概是这样的 “/buttonSouth”。 里面包含了输入设备类型和具体的按钮名。(其实我觉得, 这样的文本可以提供成一个Const string ,会好用很多)

对于简单的 rebind 存储,我们只需要找到所有的 Binding 里的 override path 保存下来,下次启动游戏加载了原本的 Action Map 之后再把 Override Path 给赋值上去。

代码

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

public static class InputBindSaveHelper
{
static readonly string s_keyMappingPreferKey = "KeyMapping";

public static void SaveActionMapRebinding(InputActionMap actionMap)
{
Dictionary<Guid, string> overridedActionPathDict = new Dictionary<Guid, string>();
UnityEngine.InputSystem.Utilities.ReadOnlyArray<InputAction> actions = actionMap.actions;
for (int i = 0; i < actions.Count; i++)
{
InputAction action = actions[i];
UnityEngine.InputSystem.Utilities.ReadOnlyArray<InputBinding> actionBindings = action.bindings;
for (int j = 0; j < actionBindings.Count; j++)
{
InputBinding actionBinding = actionBindings[j];
if (string.IsNullOrEmpty(actionBinding.overridePath))
continue;
overridedActionPathDict.Add(actionBinding.id, actionBinding.overridePath);
}
}

BinaryFormatter binFormatter = new BinaryFormatter();
MemoryStream mStream = new MemoryStream();
binFormatter.Serialize(mStream, overridedActionPathDict);

byte[] byteArr = mStream.ToArray();
// idk why [Encoding.UTF8.GetString()] just doesnt work
string encodingString = Convert.ToBase64String(byteArr, Base64FormattingOptions.InsertLineBreaks);
PlayerPrefs.SetString(s_keyMappingPreferKey, encodingString);
}

public static void LoadActionMapRebinding(InputActionMap actionMap)
{
string encodingString = PlayerPrefs.GetString(s_keyMappingPreferKey);
byte[] byteArr = Convert.FromBase64String(encodingString);

BinaryFormatter binFormatter = new BinaryFormatter();
MemoryStream mStream = new MemoryStream(byteArr);
Dictionary<Guid, string> rebindSavePairDict = binFormatter.Deserialize(mStream) as Dictionary<Guid, string>;

if (rebindSavePairDict != null)
{
UnityEngine.InputSystem.Utilities.ReadOnlyArray<InputAction> actions = actionMap.actions;
for (int i = 0; i < actions.Count; i++)
{
InputAction action = actions[i];
UnityEngine.InputSystem.Utilities.ReadOnlyArray<InputBinding> actionBindings = action.bindings;
for (int j = 0; j < actionBindings.Count; j++)
{
InputBinding actionBinding = actionBindings[j];
// override
if (rebindSavePairDict.ContainsKey(actionBinding.id))
{
actionBinding.overridePath = rebindSavePairDict[actionBinding.id];
action.ApplyBindingOverride(actionBinding);
}
}
}

}
}
}

当前因为项目里的存档相关的功能还没做完,结构还没定下来,所以我只能把 rebind 存在PlayerPrefs里。

总结

我也只是大概看了一下 Unity 的这个 InputSystem,没有很深的去使用。但是初步上手的时候就感觉他的封装和扩展性对于那些操作要求不高的游戏是已经够用的了。
比较麻烦的一个点就是他的 Interaction, Processor 和 Binding 的覆盖全都需要用 string。直接写的 string 会非常不方便,估计使用者得自己总结一些其常用的 const string 写进变量直接调用。
这样才能比较好的维持代码可读性。

最近项目升级了2020.3,Graphic这边多给了一个Raycast Padding的设置。
我们有在用一些完全空白的图片来接受UI的点击响应,同时也有一些用图片接收点击的按钮。

但很坑爹的是,我发现无论怎么设置Raycast Padding的值,场景预览中都没有显示框框,所以我其实是完全不知道这玩意儿到底怎么用的。
搜索一下才知道原来是padding的数值是负数的时候才是往外扩张。

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

public class RaycastTarget : Graphic
{
// Additionally, If you need a Raycast target that is not a rectangle, you can implement bool Raycast(Vector2 sp, Camera eventCamera) method from Graphic.
public override void SetMaterialDirty() { return; }
public override void SetVerticesDirty() { return; }
/// Probably not necessary since the chain of calls `Rebuild()`->`UpdateGeometry()`->`DoMeshGeneration()`->`OnPopulateMesh()` won't happen; so here really just as a fail-safe.
protected override void OnPopulateMesh(VertexHelper vh)
{
vh.Clear();
return;
}

#if UNITY_EDITOR

private void OnDrawGizmos()
{
if (!this.enabled)
return
RectTransform rectTransform = this.transform as RectTransform;
Color wireColor = Color.yellow;
if (this.isActiveAndEnabled)
wireColor.a *= 0.7f
// Padding to be applied to the masking
// X = Left, Y = Bottom, Z = Right, W = Top
// if you wanna make it bigger, then the all value shouble be negative
Vector4 padding = this.raycastPadding * -1.0f;
Matrix4x4 localToWorld = rectTransform.localToWorldMatrix
Vector3 topLeft = SomeUtils.GetOffsetLocalPosition(rectTransform, SomeUtils.UIOffsetType.TopLeft);
Vector3 topRight = SomeUtils.GetOffsetLocalPosition(rectTransform, SomeUtils.UIOffsetType.TopRight);
Vector3 bottomLeft = SomeUtils.GetOffsetLocalPosition(rectTransform, SomeUtils.UIOffsetType.BottomLeft);
Vector3 bottomRight = SomeUtils.GetOffsetLocalPosition(rectTransform, SomeUtils.UIOffsetType.BottomRight)
topLeft = localToWorld.MultiplyPoint(topLeft + (Vector3.left * padding.x) + (Vector3.up * padding.w));
topRight = localToWorld.MultiplyPoint(topRight + (Vector3.right * padding.z) + (Vector3.up * padding.w));
bottomLeft = localToWorld.MultiplyPoint(bottomLeft + (Vector3.left * padding.x) + (Vector3.down * padding.y));
bottomRight = localToWorld.MultiplyPoint(bottomRight + (Vector3.right * padding.z) + (Vector3.down * padding.y))
Color tempColor = Gizmos.color;
Gizmos.color = wireColor
Gizmos.DrawLine(topLeft, topRight);
Gizmos.DrawLine(topLeft, bottomLeft);
Gizmos.DrawLine(bottomRight, topRight);
Gizmos.DrawLine(bottomRight, bottomLeft);
Gizmos.color = tempColor
}

#endif

}

空白raycast target的可以在Gizmo里画,但是Image本身的不是那么好画,因为无法直接覆写原本的Editor。
一个笨办法就是做一个自己的Iamge然后也在Gizmo里画。
但其实这个是Graphic的property 不知道怎么做通用解决方案。

问题出现

终于改来改去,俺做的这个循环滚动列表成功运用到了项目里的仓库UI上。但是还是被一个BUG给击垮了。

UX给到的设计稿是当玩家鼠标在其中一个物品上时,要有一个小弹窗显示这个物品的一些讯息。
BUG的表现是当我已经’选中’一个物品后,用鼠标滚轮滚到下一行,小弹窗显示的还是上一个物品的讯息。

BUG原因

我在UI脚本中写了关于鼠标移动进入和离开的处理,但其实当列表滚动时候,鼠标下面的物体还是上一个物体,但是物体内的数据已经改变,却没有刷新。

解决方案

其实也还算有个凑合的解决方案。导师之前重写了InputModule,所以我们决定在这边做改动。
当发生滚动时,我们需要重新做raycast。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void OnScrollWheel(InputAction.CallbackContext context)
{
if (IsActive())
{
PointerEventData eventData = GetPointerData();
eventData.button = PointerEventData.InputButton.Middle;
eventData.Reset();
eventData.scrollDelta = context.ReadValue<Vector2>()
GameObject testScrollObject = ExecuteEventExecuteHierarchy(eventData.pointerCurrentRaycasgameObject, eventData, ExecuteEvents.scrollHandler)
if (testScrollObject != null)
{
// to re-do the reycast cuz we may scroll a recyclscrollrect
HandlePointerExitAndEnter(eventData, null);
m_rayCastDelayFrame = SCROLL_RAYCAST_DELAY;
// SCROLL_RAYCAST_DELAY =3 or 5, depends....
}
}
}

我们先清楚了事件数据所认为的鼠标悬停的物体,然后过几帧再重新发射射线进行检测。
因为我的循环滚动列表依赖于Unity自带的滚动视图,并且其位置也是在LateUpdate中更新的,所以我们最好在下一帧或者接下来的几帧后再做射线检测。。

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

// will call in update
public override void Process()
{
/*
.....
*/
CheckAndDoDelayRaycast();
}

void CheckAndDoDelayRaycast()
{
if (m_rayCastDelayFrame < 0)
{
return;

if (--m_rayCastDelayFrame == 0)
{
var eventData = GetPointerData();
eventSystem.RaycastAll(eventData, m_RaycastResultCache);
var firstRaycast = FindFirstRaycast(m_RaycastResultCache)
eventData.pointerCurrentRaycast = firstRaycast;
m_RaycastResultCache.Clear()
var temp = eventData.pointerCurrentRaycast.gameObject;
HandlePointerExitAndEnter(eventData, temp);
m_rayCastDelayFrame = -1;
}
}

最近在拼UI,需要建立几个临时的场景在本地。自然不能上传,但是又想手贱 git add . 还是决定在本地手动设置忽略一些文件。

设置Git exclude

首先需要找到这个文件。

exclude 文件在 如图的目录中

1
\.git\info\

找到以后以文本文件的形式打开,用法和 gitignore 差不多,我还么有深入研究,但我差不多这样用。

把我需要忽略的本地目录填上去就可以了。
顺便一说,若使用这个方式忽略了某个目录,当把已经进行版本控制的文件丢进这个目录里时,Git中会生成一个delete的改动。