王小兵 2023-10-01T23:38:00+08:00 spencer.wong@hotmail.com Open Source a Pixel-Art Game 'PuppyTouch' 2023-03-06T00:00:00+08:00 王小兵 https://peakcoder.com/cocos2d-x/2023/03/06/puppy-touch 开源一个游戏 PuppyTouch 这是我在大学刚毕业时候做的,是一个2d像素风的小游戏,使用cocos2d-x引擎开发的。手写识别使用的是华盛顿大学的 $1 Unistroke Recognizer 算法。

之前在AppStore上架过,长久不更新现已下架。
代码不多,大量的时间都在美术上消耗掉了,年少时的我是真的很爱画像素画 :)

–EOF–

]]>
Unity Editor 添加 Custom Shortcuts 2023-01-31T00:00:00+08:00 王小兵 https://peakcoder.com/unity/2023/01/31/editor-shortcut 1. 在 MenuItem 标签内使用特殊字符
  • % (ctrl on Windows and Linux, cmd on macOS),
  • ^ (ctrl on Windows, Linux, and macOS),
  • # (shift),
  • & (alt).

例如以下代码的快捷键为 CTRL + G

[MenuItem("Test/example %g")]
private static void Test()
{
    Debug.Log("TEST!");
}

2. ShortcutManager(Editor -> Shortcuts) 直接添加全局快捷键

–EOF–

]]>
Non-Convex MeshCollider 设备上不响应碰撞 2023-01-09T00:00:00+08:00 王小兵 https://peakcoder.com/unity/2023/01/09/mesh-collider 非凸多边形的 MeshCollider,在 Editor 下响应碰撞,设备上不响应。
并且会报错 CollisionMeshData couldn’t be created because the mesh has been marked as non-accessible.
需要在 FBX 导入的设置开启 Read/Write

这可能是以下情况导致的:

  • When you read from or write to the Mesh data in your code.
  • When you pass the Mesh to StaticBatchingUtility.Combine() to combine the Mesh at run time.
  • When you pass the mesh to CanvasRenderer.SetMesh.
  • When you use the Mesh to bake a NavMesh using the NavMesh building components at run time.
  • When the Mesh is convex, you use the Mesh with a Mesh Collider, and the Mesh Collider’s Transform has negative scaling (for example, (–1, 1, 1)).
  • When you use the Mesh with a Mesh Collider, and the Mesh Collider’s transform is skewed or sheared (for example, when a rotated Transform has a scaled parent Transform).
  • When you use the Mesh with a Mesh Collider, and the Mesh Collider’s Cooking Options flags are set to any value other than the default.
  • When using a Mesh with a Particle System’s Shape module or Renderer module ·when not using GPU instancing.

–EOF–

]]>
Shader Support VR Single Pass Instanced 2022-07-13T00:00:00+08:00 王小兵 https://peakcoder.com/unity/2022/07/13/single-pass 在 VR 中,有时只有一只眼睛可以看见,另一只眼睛没有渲染
是因为 Shader 没有支持 Single Pass Instanced Render Mode
不用怕性能损失的话,可以 直接使用 Multi-pass Mode 解决

否则需要以下操作让 Shader 在 Single Pass Instanced 模式下让渲染 compatible

1. #include "UnityCG.cginc" - near the top of the CGPROGRAM block

2. UNITY_INSTANCE_ID - should be in the the vert input data struct (ex: appdata)

3. UNITY_SETUP_INSTANCE_ID(v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); - should be in the vert function

4. UNITY_VERTEX_OUTPUT_STEREO - should be in the frag input data struct (ex: v2f)

–EOF–

]]>
Custom UnityAppController 2022-05-10T00:00:00+08:00 王小兵 https://peakcoder.com/unity/2022/05/10/custom-appcontroller 之前项目都是编译后处理脚本里做文件替换 UnityAppController.mm
以下是更优雅的方式:

/path/to/unity/project/Assets/Plugins/iOS/CustomAppController.mm
注意,文件名必须是 ___AppController,前缀可自选,但不能省略;否则在 Build 项目的时候,会被移动到错误的目录中去
使用宏 IMPL_APP_CONTROLLER_SUBCLASS (CustomAppController) 定制启动调用

#import "UnityAppController.h"
#import <UIKit/UIKit.h>
#import <FIRApp.h>
@interface CustomAppController : UnityAppController
@end

IMPL_APP_CONTROLLER_SUBCLASS (CustomAppController)

// 如果使用 modules import,则不能用.mm混编 只能用.m
@import GoogleMobileAds;

@implementation CustomAppController

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
    [super application:application didFinishLaunchingWithOptions:launchOptions];
    [FIRApp configure];
    [[GADMobileAds sharedInstance] startWithCompletionHandler:nil];
    return YES;
}

@end

–EOF–

]]>
Find and remove missing components 2022-03-09T00:00:00+08:00 王小兵 https://peakcoder.com/unity/2022/03/09/remove-missing
public static void RemoveInGo(GameObject g)
{
    var components = g.GetComponents<Component>();
    
    var r = 0;
    
    for (var i = 0; i < components.Length; ++i)
    {
        if (components[i] != null) 
            continue;
        
        var s = g.name;
        var t = g.transform;
        while (t.parent != null) 
        {
            s = t.parent.name +"/"+s;
            t = t.parent;
        }
        
        Debug.Log ($"{s} has a missing script at {i}", g);
        
        var serializedObject = new SerializedObject(g);
        
        var prop = serializedObject.FindProperty("m_Component");
        
        prop.DeleteArrayElementAtIndex(i-r);
        r++;
    
        serializedObject.ApplyModifiedProperties();
    }
    
    foreach (Transform childT in g.transform)
    {
        RemoveInGo(childT.gameObject);
    }
}

官方解决方案可以使用
GameObjectUtility.RemoveMonoBehavioursWithMissingScript

–EOF–

]]>
Unity Mecanim Animation System 2022-02-28T00:00:00+08:00 王小兵 https://peakcoder.com/unity/2022/02/28/mecanim 1. Mecanim 动画系统

基于状态机的动画控制系统,是一个面向动画应用的动画系统 动画的制作编辑(Animation)功能相对有限 重在如何从资源中提取动画,配置融合动画,应用动画,复用动画 以及为实际开发提供可视化的GUI编辑和监视功能

2. 角色模型动画的创建与应用工作流程

  1. 建模
  2. 制作动画,导出资源
  3. Unity 提取人物模型,提取动画切片 Clip
  4. 通过 Mecanim 动画系统进行编辑:创建动画状态,动画的融合、过渡调整,设定过渡条件,结合脚本控制

3. AnimtionClip 动画片段

Unity 中的动画片段分为两种 通过剪辑 FBX 中的整段动画生成的动画切片(Clip) 以及由 Animation 窗口创建出的动画片段(Clip)

4. AnimationEvent 动画事件

通过AnimatorEvent动画事件 我们可以在动画播放到某个特定位置时,触发脚本中的特定方法(发射子弹,生成特效,受击信息)

5. Animator 动画状态机

5.1 脚本说明

Controller: 状态机的配置资源文件,整合Clip资源,创建状态机状态(State),设置动画过渡与融合

Avatar: 动画节点导引替身,与动画复用(尤其是人形动画复用)有关,通过配置和应用Avatar,可以实现不同FBX模型之间的动画复用

Apply Root Motion: 是否将动画中的根节点位移,植入到Unity中的物体位移上

Update Mode: 状态机播放动画的时间模式

5.2 动画参数的类型有 4 种

  1. Float:就是 C# 种的单精度浮点性
  2. Int:C# 中的整形
  3. Bool:C# 中的布尔型
  4. Trigger:一种特殊的布尔性参数,它作为触发条件,触发过渡后会被自动置为 false
    Trigger 的触发必须对当前状态机的运行状态进行判断
    一个Trigger,只能对应触发一个过渡,而一个布尔可以触发不同动画分层中的多个过渡

5.3 过渡线配置

选中一条过渡线 Gizmo,我们可以在右侧 Inspector 窗口中配置这个过渡的相关属性
Interruption Source:
Current State 或 NextState指该过渡,允许被其它从 CurrentState 或 NextState 出发的过渡打断
CurrentStateThenNextState,指从 CurrentState 或 NextState 出发的过渡均可打断
但 CurrentState 出发的过渡打断优先于 NextState 出发的过渡

5.4 State 配置

Speed:动画片段的播放速度(置为-1可倒放动画)
Multiplier:Speed的一个倍率因子,需要勾选Parameter可选定一个浮点动画参数来动态控制这个因子
Mirror:是否进行左右翻转
Cycle Offset:偏移量(和之前Clip中的偏移相同),0到1之间的浮点值,可勾选Parameter选定一个浮点动画参数来动态控制
Foot IK:脚部的IK反向力学修正,针对人物上坡,上楼梯时的踏空而行进行修正
Write Default: 主要是针对人物骨骼之外的一些节点,例如武器,跟随物,这些节点只在部分互动动作之中被用到,那些没有使用该节点的动作执行时,是要让节点处于初始状态,还是上一次互动动作末尾时的状态

5.5 Animator Override Controller

是一种Controller状态机配置资源文件,它可以继承一种指定的Animator Controller,并在此基础上进行修改,从而在复用Controller的同时,做出针对性的修改

OverrideController 只能继承自 Controller,不能形成继承树

5.6 Apply Root Motion

RootMotion 总共有三种状态,勾选,不勾选 Handled by Script
Hadndled by Script 状态,是由于我们在 Animator 所在物体的脚本中, 定义方法 OnAnimatorMove()

5.7 Avatar的作用与配置

Avatar替身(人物节点导引),主要与动画的复用,尤其是人物动画的复用有关

5.8 动画系统脚本 API

// 这里的名称要与Animator窗口中,动画参数的名称对应
int runHash=Animator.StringToHash("Run");

// 通常对于调用频繁的动画参数我们使用哈希值进行快速访问
// 下面设置/获取动画参数均有使用String参数名称进行映射的重载和使用哈希值进行映射的重载

// 获取设置Float类型参数,通常结合Input轴线
animator.GetFloat(blendHash);animator.SetFloat(blendHash, Input.GetAxis("Horizontal"));

// 获取设置Int类型参数
animator.GetInteger(intHash);animator.SetInteger(intHash,Number);

// 获取设置Bool类型参数
animator.GetBool(boolHash);animator.SetBool(boolHash, true / false);

// 触发,取消触发Trigger的方法
animator.SetTrigger(jumpHash);animator.ResetTrigger(jumpHash);

// 我们先预设状态的哈希值
int idleHash = Animator.StringToHash("Idle");

int layerID = animator.GetLayerIndex("Base Layer");
animatorStateInfo = animator.GetCurrentAnimatorStateInfo(layerID);

// 判定当前状态是否是Idle状态
if (animatorStateInfo.shortNameHash == idleHash)
{
    Debug.Log("OnState Idle");
}

// 过渡状态的 nameHash userNameHash
AnimatorTransitionInfo transitionInfo;
transitionInfo = animator.GetAnimatorTransitionInfo(layerID);
Debug.Log(transitionInfo.nameHash);
Debug.Log(transitionInfo.userNameHash);

5.9 动画分层

动画分层可以让我们使用多层动画来对人物/角色不同组分分别配置动画,再使用一定的混合模式,从而实现一些复杂的效果

5.10 IK 反向动力学

5.11 BlendTree 混合树

BlendTree的作用是将多个动画状态混合成为一个动画状态,并通过动画参数来影响状态的输出结果。
使用 BlendTree 的目的是应对状态机的高耦合导致的低扩展缺点,尤其是在人物运动相关的动作(前后左右的行走奔跑)这类繁多的动作
在状态过渡时 BlendTree 就相当于一个普通的 State,可以创建指向和指出 BlendTree 的过渡。BlendTree 的 State 配置也和普通 State 一致

–EOF–

]]>
Reduce Mesh Triangles Within Unity 2022-02-23T00:00:00+08:00 王小兵 https://peakcoder.com/unity/2022/02/23/simplify-mesh 使用 Unity Mesh Simplifier 在 Unity 编辑器内降低模型面数
不需要使用 Max 或者 Blender Decimate 3D 软件
减面之后,再使用 FBX Exporter 导出减面的 FBX 就可以了

写了个编辑器脚本调用 mesh simplier api 方便使用

using UnityEditor;
using UnityEngine;
using UnityMeshSimplifier;

public class MeshSimplifyTool 
{
    [MenuItem ("GameObject/Simplify Mesh", false, 30)]
    static void OnContextItem (MenuCommand command)
    {
        if (Selection.objects.Length <= 0) 
        {
            DisplayNoSelectionDialog ();
            return;
        }
        SimplifyMesh ();
    }

    private static void SimplifyMesh()
    {
        var selectedGOs = Selection.GetFiltered<GameObject> (SelectionMode.TopLevel);
        if (selectedGOs.Length > 0)
        {
            var go = selectedGOs[0];
            var mf = go.GetComponent<MeshFilter>();
            if (mf == null)
            {
                DisplayNoMeshDialog();
                return;
            }

            var mesh = mf.sharedMesh;
            var ms = new MeshSimplifier();
            ms.Initialize(mesh);
            ms.SimplifyMesh(0.5f);
            var destMesh = ms.ToMesh();
            mf.sharedMesh = destMesh;
        }
        else
        {
            DisplayNoSelectionDialog();
        }
    }
    
    private static void DisplayNoSelectionDialog()
    {
        EditorUtility.DisplayDialog (
            "Mesh Simplify Warning", 
            "No GameObjects selected.", 
            "Ok");
    }
    
    private static void DisplayNoMeshDialog()
    {
        EditorUtility.DisplayDialog (
            "Mesh Simplify Warning", 
            "GameObject has no mesh attach.", 
            "Ok");
    }
}

使用步骤

  1. 在 Hierarchy 选中 GameObject 右键点击 Simplify Mesh
  2. 每次减面 50%,可反复点击
  3. 右键选中 使用 FBX Exporter 导出

–EOF–

]]>
Writing Shader Code for the Universal RP 2021-12-09T00:00:00+08:00 王小兵 https://peakcoder.com/unity/2021/12/09/urp-shader URP中,RenderType可能不太重要了,在内置管线中,是用来做 Replacement Shaders 用的
但是在 URP 中不支持Replacement Shader,尽管有个 ForwardRenderer 的 overrideMaterail
每个Pass标签都需要标记特定的 LightMode,URP使用 single-pass forward renderer
所有只有一个 “UniversalFoward” 的 Pass,也不能同时渲染多个对象
也可以不加Tag,但是会破坏 SRP Batcher
建议在单独的 MeshRender 上使用独立的 Shader 或者材质
或者使用 Forward Renderer 上 overrideMaterial 的 Render Objects 特性

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Unity想弃用CG,推荐使用 HLSL(High level shading language)
不再有 fixed 类型,只有half 和 float
Cg 和 HLSL 被视为相同的语言
如果在 URP 中使用 CG 的标签,将与 URPShaderLibrary 冲突
因为变量和函数会被重复定义
现在请使用 HLSLPROGRAM, HLSLINCLUDE, ENDHLSL
不推荐用CGPROGRAM, ENDCG, CGINCLUDE

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
URP LightMode Tags:
Tags{“LightMode” = “XXX”}
UniversalForward:前向渲染物件之用
ShadowCaster: 投射阴影之用
DepthOnly:只用来产生深度图
Mata:来用烘焙光照图之用
Universal2D :做2D游戏用的,用来替代前向渲染
UniversalGBuffer : 貌似与延迟渲染相关

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Pass 的 Name,应该全大写
可以用 UsePass 来引用
例如:UsePass “Custom/UnlitShaderExample/MyShader”
为了与 SPRBatcher 兼容,所有传递必须共享相同的 UnityPerMaterial CBUFFER
如果不匹配,则出错

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
HLSL 数据类型
bool - true / false.
float - 32位浮点数,用在比如世界坐标,纹理坐标,复杂的函数计算
half - 16位浮点数,用于短向量、方向、颜色,模型空间位置
double - 64位浮点数,不能用于输入输出,要使用double,得声明为一对unit再用asuint把double打包到uint对中,再用asdouble函数解包
fixed - 只能用于内建管线,URP不支持,用half替代
real - 好像只用于URP,如果平台指定了用half(#define PREFER_HALF 0),否则就是float类型
int - 32位有符号整形
uint - 32位无符号整形(GLES2不支持,会用int替代)

vector - 类型可以直接在基础数据后添加维度
例如:float4, half3, int2
访问可以用xyzw或者rgba访问

HLSL数据类型3 – 矩阵
matrix类型可以直接在基础数据后添加 维度 x 维度
例如:float4x4, int4x3, half2x1
即表达 4行4列的float,4行3列的int,2行1列的half
float3x3 m = { 0, 1, 2, 3, 4, 5, 6, 7, 8};
float3 row0 = m[0]; // 0, 1, 2
float r1c2 = m[1][2]; // 5
乘法,使用mul进行
mul(m, row0)
第1个列数必须与第2个行数相同

HLSL数据类型4 – 数组
ShaderLab 材质属性面板 Properties 不支持数组,只能从C#中设置
必须在 Shader 中指定数组的大小,例如
float array[10];
float4x4 array2[10];

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
纹理和采样
定义:
TEXTURE2D(textureName);
SAMPLER(sampler_textureName);
缓存区:
从C#中使用material.SetBuffer 或者 Shader.SetGlobalBuffer
例如:StructuredBuffer buffer;

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
宏(macro)
define MUL2(x,y) ((x)(y))
可以做一些语法糖,例如:
define TRANSFORM_TEX(tex, name) (tex.xy
name##_ST.xy + name##_ST.zw)
o.uv = TRANSFORM_TEX(in.uv, _MainTex) =>
o.uv = (in.uv.xy * _MainTex_ST.xy + _MainTex.ST_zw)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
每个 Pass, UnityPerMaterial CBUFFER 都是相同的
CBUFFER需要包含所有公用的属性(即与ShaderLab中的Properties相同)
它不能包含其它未公开的属性以及纹理采样器
虽然不需要通过C# material.SetColor/SetFloat等
但多 Material 实例具有不同的值,这会产生问题,SRP Batcher会将他们一起批处理
1、如果您有未公开的变量,请始终使用Shader.SetGlobalColor / Float
以使它们在所有材质实例中保持不变。
2、如果每个材料都不同,则通过Shaderlab属性块将它们公开,然后将它们添加到 CBUFFER 中

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
HLSLINCLUDE
#include “Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl”
CBUFFER_START(UnityPerMaterial) float4 _BaseMap_ST; float4 _BaseColor; CBUFFER_END
ENDHLSL
Core.hlsl文件是URP的内置核心文件
比如如果要使用灯光,则添加 Lighting.hlsl文件

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Vertex Shader 阶段
在内置 Shader 中,使用 UnityObjectToClipPos 将模型空间转换到裁剪空间
URP 中使用 TransformObjectToHClip 函数(在 SpaceTransforms.hlsl 中有定义)
URP 中也可以使用 GetVertexPositionInputs 函数(在Core.hlsh中有定义)
得到的 VertexPositionInputs 结构体包含以下内容
positionWS = positionWorldSpace
positionVS = positionViewSpace
positionCS = positionClipSpace
positionNDC = position in Normalised Device Coordinates
我们可以直接使用上述代码,即便含有未使用到的变量
因为 Shader 编译器会自动对上述代码进行优化
法线 & 切线
VertexNormalInputs normalInputs = GetVertexNormalInputs(IN.normalOS, IN.tangentOS);
GetVertexNormalInputs 将模型空间法线和切线转换为世界空间
包含 normalWS, tangentWS, bitangetWS

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Fragment Shader 阶段
可以使用 SAMPLE_TEXTURE2D 宏进行采样
URP 不支持 Surface Shader
URP 的一些光照文件在 Lighting.hlsl 中
可以用来处理例如 UniversalFragmentPBR

以下代码展示了阴影相关的宏
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _SHADOWS_SOFT
#include “Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl”

原文链接

]]>
Unity 构建 Android Custom Gradle 2021-08-02T00:00:00+08:00 王小兵 https://peakcoder.com/unity/2021/08/02/unity-gradle Unity 2019.3之后的 Gradle Project 包含两个module
1] UnityLibrary
包含Unity runtime 和工程数据,是个 Library moudle,可以嵌入到其他android工程
2] Launcher
包含应用名称icon,是application moudle 启动Unity

Unity Custom 配置
1) baseProjectTemplate.gradle => root/build.gradle
顶层的 build.gradle 用于定义适用于项目中所有模块的构建配置

2) launcherTemplate.gradle => root/launcher/build.gradle
模块级build文件,包含有关如何构建 Android 应用程序(bundling、签名、分包)的说明,依赖于 unityLibrary 项目,输出.apk或者.aab

3) mainTemplate.gradle => root/unityLibrary/build.gradle
模块级build文件,输出.aar文件,一般性操作覆盖这个文件就好

4) gradleTemplate.properties => gradle.properties
gradle 属性文件. 可以在其中配置项目全局 Gradle 设置,如 Gradle 守护程序的最大堆大小

5) proguard-user.txt => proguard-unity.txt
构建系统会应用一组适当的规则以使用其内置的缩减工具(如 R8)

6) LauncherManifest.xml => root/launcher/src/main/AndroidManifest.xml

ant maven gradle 都是 java 的构建工具
ant maven 使用 xml
ant 无法管理依赖,maven 使用xml不够简洁
gradle 使用 groovy 或者 kotlin 语言

]]>