FishPlayer

一个喜欢摸鱼的废物

0%

这周赶版本,接到了一个前同事留下来的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 写进变量直接调用。
这样才能比较好的维持代码可读性。

之前做了一个自带简单 GridLayout 的循环滚动列表。但是基于继承的思路做的。导师指出了其不便使用的一些情况,而且Unity其实是比较提倡大家使用组合模式的,所以我决定修改一下。

循环滚动

思路

因为交互侧没有无限滚动的需求,所以我打算直接用简单明确的思路做一个简单凑合能跑的版本。

首先根据视口大小计算出实际需要使用到的 GridElement 数量,通过比较灵活的接口去申请/释放 GridElement 物体(老实说我感觉我没有特别好的方法去计算一个准确的值。所以就按着 (row + 2) (column 2) 来计算了,虽然有一点浪费内存,但在我当前设置的滚动速度下是够用的。)。

再着,这个做法之所以很简单,是因为我无需做滚动相关的输入处理。这个方案需要与 UGUI ScrollRect 一同使用。
我会根据数据量计算并应用content的大小,这样就能够让 ScrollRect 照常处理滚动。

当每一次 ScrollRect 的滚动位置处理完毕后,先用 Viewport 的测试出能显示的 GridElement 物体,然后才根据 Content 位置来拜访 GridElement,并发出通知告知 GridElement 对应的数据序列号变更.

这个做法非常的简单易懂,但也有个明显的缺点,就是当位置变更时我需要根据数据量去频繁测试每个数据对应 GridElement 的可见性,感觉优一点的做法应该是缓存了当前可见的数据范围,再根据每次移动的距离来决定是做少量测试或是大量测试。

配合实际显示组件使用

我定义了一个简单的接口用于描述什么样的列表逻辑便于使用这个滚动列表。
但其实直接继承滚动列表也是一种可行的做法。

1
2
3
4
5
6
7
8
9
10

public interface IListView
{
int DataElementCount { get; }
RectTransform AddElement(RectTransform parent);
void RemoveElement(RectTransform element);
void InitElement(RectTransform element, int index);
void UnInitElement(RectTransform element);
void OnElementIndexChanged(RectTransform element, int prevIndex, int nextIndex);
}

总结

其实这个功能丢尽项目里后用的地方不是特别多,所以都是隔几个月修一下,每次都要看自己写的垃圾代码好久,属实头痛了。
总算是用起来比以前方便不少了。接下来就把一些小问题修一修好了。

回头再看已经和自己做的那个初版完全不一样了,个人认为思路比以前那个好多了。

完整代码

最近项目升级了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的改动。