王小兵 2022-05-28T00:25:23+08:00 spencer.wong@hotmail.com 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 语言

]]>
UE4入门 Roadmap 2021-06-13T00:00:00+08:00 王小兵 https://peakcoder.com/ue4/2021/06/13/ue4-roadmap 1.UE4介绍

UE4介绍 UE4和Unity对比

2.模板介绍和工程创建

安装UE4 创建工程 模块选择, 编辑器介绍

3.Actor操作

Actor介绍(放置,变换,分组,合并) 常见放置Actor 静态网格物体 BSP画刷模式 Landscape地形系统 草地工具

4.蓝图

蓝图介绍 蓝图快速入门指南(Launchpad) 案例-横版游戏 案例-第一人称控制

5.UMG基础+案例

UI创建和显示 UI组件事件绑定 UMG UI设计快速入门指南

6.物理系统和射线

碰撞事件和触发事件 射线检测系统 APEX可破坏网格 第一人称射击游戏

7.动画系统

时间轴 Matinee Sequencer Composure

8.行为树

行为树介绍 行为树节点 行为树快速入门指南

9.材质系统+案例

材质编辑器 基本材质概念 特殊效果(法线贴图,菲涅尔,自发光,彩色半透明阴影 等) 自定义材质函数

10.Paper2D系统

创建Flipbooks 2D物理和动画 2D图集

11.C++脚本基础

C++与蓝图 标签介绍 案例:第一人称射击教程

–EOF–

]]>
URP Tilt-Shift Effect 2021-05-24T00:00:00+08:00 王小兵 https://peakcoder.com/unity3d/2021/05/24/tilt-shift 使用URP + RenderFeature 实现的 Tilt-Shift 移轴摄影效果。github

以上.

–EOF–

]]>
SSD 移动硬盘格式的坑 2021-04-13T00:00:00+08:00 王小兵 https://peakcoder.com/ssd/2021/04/13/ssd 因为穷,个人 MBP 的硬盘只有256G,故狗东买了一块闪迪 1TSSD 移动硬盘作为辅助存储.

公司用PC,回家 MacBook,初衷是两种OS可以读写. 以下是我在选择硬盘格式时候遇到的一些坑.

1] 一开始使用的 exFAT 格式

因为这格式倆 OS 都可直接读写.但在拷贝一个Unity工程到硬盘的时候,发现在 NTFS 60G 到移动硬盘里变 400 多G了,靠北!我可只有1个T而已啊. 一番google之后发现:32G 以上的 exFAT 格式硬盘默认簇大小128K,即使大小不到1K的文件也要占128K. 又Unity工程文件全是细碎小文件,故膨胀至此.

2] 使用 NTFS

NTFS簇只有4K,应该就不会浪费空间了.在使用Mounty啊之类的软件之后Mac也能写硬盘.似乎..完美了. 但当老夫发现无法在硬盘打开创建Unity工程!提示The project is on case sensitive file system.Case sensitive file systems are not supported at the moment.Please move the project folder to a case insensitive file system. 又一番google,好吧,NTFS是大小写敏感的,只是Win默认关闭了. 在Mac上大小写敏感,而且还关不掉,汗,只能放弃这格式了.

3] 再使用的 exFAT 格式

算了还是使用exFAT吧,因为之前检索发现 exFAT 直接使用4K簇大小,只要容量不超过16T也是可以的.但默认格式化的时候最小簇只能设 64K 微软太恶意了! 能不能把簇改小呢?又双叒叕一番google发现对于系统自带的format命令行格式化程序则没有对exFAT文件系统施加任何人为限制,那么解决方案自然就是:先以管理员身份运行CMD命令提示符,注意空格输入:Format X: /FS:exFAT /Q /A:4096 /Y 即可将任意类型的盘快速格式化为exFAT-4K簇,其中X:为要格式化卷的盘符,/A:参数为自行指定一个簇大小.或者直接使用DiskGenius等第三方软件来格式化为exFAT也是可以的.

似乎又完美了,都可读写,速度也没有太影响.

但天不遂人愿,又有新的问题,因为只能在 NTFS 格式硬盘上创建软连接,所以在 exFAT 格式的盘上,Unity2019package 全都会拷一份到项目内. 这也还好了,因为用Mac APFS 也一直如此,但只会拷贝一次,第二次打开就很快了.但是 WinexFAT 的硬盘,Unity每次都会拷贝!很慢啊,这绝对 Unity bug,太恶心了,但也只能 file a bugUnity 解决了,没啥办法,哎.

以上.

–EOF–

]]>
新应用「晴书」发布 2020-12-24T00:00:00+08:00 王小兵 https://peakcoder.com/%E7%8B%AC%E7%AB%8B%E5%BC%80%E5%8F%91/2020/12/24/new-app

虽说苹果爸爸圣诞放假暂停审核,但还是在平安夜approve了,ready for sale. 欢迎大家下载。

PS: 开发此App的动机之一是为了泡姑娘,估计追不到了…

–EOF–

]]>