游戏实现流程

游戏实现流程

0.ref unity官方文档

1.PBR/Cook-Torrance BRDF

1.1.ref
[1.]: 由浅入深学习PBR的原理和实现
[2.]: 基于物理的渲染:微平面理论(Cook-Torrance BRDF推导)
[3.]: 图形学 | Shader |用一篇文章理解法线变换、切线空间、法线贴图
[4.]: 自定义着色器基础
[5.]: Unity3D Shader “_WorldSpaceLightPos0”

1. 2.实现效果
![效果](https://img-blog.csdnimg.cn/15a959f0380b453fa8ceb713e0781290.png
有点奇怪, 后面看了论文重新写一遍。还有就是为什么GGX法线使用max对NdotH会出现光圈,而clamp不会.

1.3.code

Shader "Unlit/PBRS"
{
    Properties
    {
        [Space][Header(Texture)][Space]
        _Aldobe ("Aldobe Map", 2D)= "white"{}
        _Normal ("Normal Map", 2D)= "white"{}
        _Metallic ("Metallic Map", 2D)= "white"{}
        _Roughness ("Roughness Map", 2D)= "white"{}
        _AO ("AO Map", 2D)= "ao"{}
        _RoughnessT("RT", Range(0, 1)) = 0 
        _MetallicT("MT", Range(0, 1)) = 0
    }
    SubShader
    {
        
        HLSLINCLUDE
        static const float PI = 3.1415926f;  

        float NDF(float3 normal, float3 h, float a)
        {
            float a2 = a * a;
            float NdotH  = max(0.0, dot(normal, h));

            float nom = a2;
            float dnom = NdotH * NdotH * (a2 - 1.0f) + 1.0f;
            dnom = PI * dnom * dnom;

            return nom / max(dnom, 0.001f);
        }

        float3 Fresnel(float3 h, float3 v, float3 F0)
        {
            return F0 + (1.0f - F0) * pow(1 - dot(h ,v), 5);
        }
        
        float GeometryConut(float3 normal, float3 viewDir, float a)
        {
            float NdotV = max(0.0, dot(normal, viewDir));
            float k = (pow((a + 1.0), 2) / 8);
            return NdotV / (NdotV * (1 - k) + k);
        }

        float Geometry(float3 normal, float3 viewDir, float3 lightDir, float a)
        {
            return GeometryConut(normal, viewDir, a) * GeometryConut(normal, lightDir, a);
        }

        
        float3 PBR(float3 worldPos, float3 eyedPos, float4 lightPos,   
            float3 albedo , float3 normal, float metallic, float Roughness, float AO,
            float3 F0, float3 L0)
        {
            float3 lightDireition;
            if(lightPos.w == 0)
            {
                lightDireition = normalize(lightPos.xyz);
            }
            else
            {
                lightDireition = normalize(lightPos.xyz - worldPos);
            }
            float3 viewDirection = normalize(eyedPos - worldPos);
           
            float3 H = normalize(viewDirection + lightDireition);

            F0 = lerp(F0, albedo, metallic);
            float D = NDF(normal, H, Roughness);
            float3 F = Fresnel(H, viewDirection, F0);
            float G = Geometry(normal, viewDirection, lightDireition, Roughness);

            float NdotV =  max(0.0f, dot(normal, viewDirection));
            float NdotL = max(0.0f, dot(normal, lightDireition));
            float3 ks = F; 
            float3 kd = 1.0f - ks;

            kd *= 1 - metallic;
            float3 fsNom = D * F * G;
            float fsDnom = 4 * NdotV * NdotL;
            float3 fspecular = fsNom / max(fsDnom, 0.001f);

            float3 color = (kd *  albedo / PI + fspecular) * NdotL * L0;

            float3 ambint = float3(0.2f, 0.2f, 0.2f) * albedo * AO;

            return color + ambint;
        }

        ENDHLSL
        pass
        {
            Tags{"RenderType"="Opaque" "LightModel"="ForwardBase"}
            HLSLPROGRAM
            #pragma fragment frag
            #pragma vertex vert
            #include "UnityCG.cginc"
            #include "UnityLightingCommon.cginc"
            #include "AutoLight.cginc"


            struct VertexData
            {
                float4 pos : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
                float2 uv : TEXCOORD0;
            };

            struct VSout
            {
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD1;
                float3 worldPos : TEXCOORD3;
                float2 uv : TEXCOORD2;
                float3 tspace0 : TEXCOORD4;
                float3 tspace1 : TEXCOORD5;
                float3 tspace2 : TEXCOORD6;
                SHADOW_COORDS(1)

            };

            VSout vert(VertexData v)
            {
                VSout o;
                o.pos = UnityObjectToClipPos(v.pos);
                o.worldPos = mul(unity_ObjectToWorld, v.pos);

                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
     
                float3 worldTanget = UnityObjectToWorldDir(v.tangent);
                float tangentSign = unity_WorldTransformParams.w * v.tangent.w;
                float3 bitTangent = cross(o.worldNormal, worldTanget) *  tangentSign;
                o.tspace0 = (worldTanget.x, bitTangent.x, o.worldNormal.x);
                o.tspace1 = (worldTanget.y, bitTangent.y, o.worldNormal.y);
                o.tspace2 = (worldTanget.z, bitTangent.z, o.worldNormal.z);
                o.uv = v.uv;
                TRANSFER_SHADOW(o)
                return o;
            }

            sampler2D  _Aldobe, _Normal, _Roughness, _Metallic, _AO;
            float _RoughnessT, _MetallicT;
            float4 frag(VSout o) : SV_TARGET
            {
                float3 aldobe = tex2D(_Aldobe, o.uv).rgb;
                float3 normal =  UnpackNormal(tex2D(_Normal, o.uv)); 
                float metallic =  tex2D(_Metallic, o.uv).r;
                float roughness = _RoughnessT;// tex2D(_Roughness, o.uv).r;
                float AO = tex2D(_AO, o.uv).r;
                
                float3 worldNormal;
                worldNormal.x = dot(o.tspace0, normal);
                worldNormal.y = dot(o.tspace1, normal);
                worldNormal.z = dot(o.tspace2, normal);
              
               normal = worldNormal;
               
                float3 F0 = 0.4;
                float3 L0 = _LightColor0.rgb;
                float shadow = SHADOW_ATTENUATION(o);
                float3 color = PBR(o.worldPos, _WorldSpaceCameraPos.xyz, _WorldSpaceLightPos0, aldobe, normal, metallic, roughness, AO, F0, L0);
                
                float lookdir = -normalize(UnityWorldSpaceViewDir(o.worldPos));
                float3 reflectdir = reflect(lookdir, o.worldNormal);



                float4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, reflectdir);
                float3 skyColor = DecodeHDR(skyData, unity_SpecCube0_HDR);
                color = color / (color + 1.0f);
                color = pow(color, 1.0 / 2.2);
                return float4(color, 1.0f );
            }
            ENDHLSL
        }

       
        UsePass "Legacy Shaders/VertexLit/SHADOWCASTER"
    }
}

2.NPR

2.1.ref
[1.] : 【游戏开发实战】下载原神模型,PMX转FBX,导入到Unity中,卡通渲染,绑定人形动画
[2.]: 【1,2,3】从零开始的卡通渲染-描边篇
[3.]: Unity卡通渲染总结 NPR Toon Shading
[4.] : NPR中的透明物体bloom

2. 3.code

Shader "Unlit/URP"
{
    Properties
    {
        [Header(Texture)]
         _MainTex("Main Texture", 2D)="white"{}

         [Space(10)] 
         [Header(TColorShadow)]
        _MainColor("Illuminate Color", Color) = (1.0, 1.0, 1.0, 1.0)
        _ShadowColor("Shadow Color", Color) = (0.6, 0.5, 0.4, 1.0)
        _ShadowRange("Shadow Range", Range(0, 1)) = 0.5
        _ShadowSmooth("Shadow Smooth", Range(0, 0.5)) = 0.2
        [Space(10)] 

        [Header(Outline)]
        _outlineWidth("Outline Width", Range(0, 1)) = 0.2
        _outlineColor("outline Color", Color)= (0.0, 0.0, 0.0, 1.0)

        [Space(10)]
        [Header(PostProcess)]
        _RimColor("Rim Color", Color) = (0.8, 0.8, 1.0, 0.5)
        _RimSmooth("Rim Smooth", Range(0.0, 1)) = 0.2
        _RimMin("Rim Min", Range(0.0, 1.0)) = 0.4
        _RimMax("Rim Max", Range(0.0, 1.0)) = 0.7

        [Space(5)]
        _BoomTex("Boom Texture", 2D) = "white"{}
        _Boom("Boom", Range(0, 1)) = 0.3
        _BoomThreshold("Boom Threshold", color) =(0.7, 0.7, 0.7, 1.0)
        _RimBoomColor("Rig Boom Color", Color) = (0.1, 0.1, 0.6, 1.0)
        //_RimBoomAtten("Rim Boom Attenuation", float) = 5
         _RimBoomMin("Rim Boom Min", Range(0.0, 1.0)) = 0.4
        _RimBoomMax("Rim Boom Max", Range(0.0, 1.0)) = 0.7
    }

    SubShader
    {
        Tags{"RenderType"="Opaque"}
        pass
        {
            Name "Light"
            Tags{"Lightmodel"="FowardBase"}
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCg.cginc"
            #include "UnityLightingCommon.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _MainColor;

            float4 _ShadowColor;
            float _ShadowRange;
            float _ShadowSmooth;

            float4 _RimColor;
            float _RimSmooth;
            float _RimMin;
            float _RimMax;

            sampler2D _BoomTex;
            float _Boom;
            float4 _BoomThreshold;

            sampler2D _RimBoomColor;
            float _RimBoomAtten;
            float _RimBoomMax;
            float _RimBoomMin;

            struct VertexData
            {
                float4 modelPosition : POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };

            struct VSOut
            {
                float4 position : SV_POSITION;
                float3 worldPosition : TEXCOORD1;
                float3 worldNormal : TEXCOORD0;
                float2 uv : TEXCOORD2;
            };

            VSOut vert(VertexData v)
            {
                VSOut o;
                o.position = UnityObjectToClipPos(v.modelPosition);
                o.worldPosition = mul(unity_ObjectToWorld, v.modelPosition);
                o.worldNormal = normalize(mul(v.normal,  (float3x3)unity_ObjectToWorld));
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            float4 frag(VSOut i) : SV_TARGET
            {
                float4 baseColor = tex2D(_MainTex, i.uv);
                float3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPosition));
                float3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPosition));
                float shadowAngle = dot(i.worldNormal, lightDir) * 0.5f + 0.5;
                float4 shadow = shadowAngle > _ShadowRange ? _MainColor : _ShadowColor;
                float smooth = smoothstep(0.0f, _ShadowSmooth, shadowAngle - _ShadowRange);
                shadow = lerp(_ShadowColor, _MainColor, smooth);
                float4 diffuse = baseColor * shadow;
                
                //Add: SSS Texture 色调控制
                //Add: ilmTexture 手绘风格控制 Specular

                float NDotV = abs(dot(viewDir, i.worldNormal));
                float NDotL = max(0.0, dot(lightDir, i.worldNormal));

                float ramp = smoothstep(_RimMin, _RimMax, 1 - NDotV);
                float rim = smoothstep(0, _RimSmooth, ramp);
                float4 rimColor = _RimColor * rim * _RimColor.a;
                //float4 rimColor = _RimColor * (1 - NDotV);
                //return float4(i.worldNormal, 1.0f);

                float4 colors = (diffuse + rimColor) * _LightColor0;

                //提亮
                float4 boomColor = max((colors - _BoomThreshold), 0.0f) * _Boom * _LightColor0;
                //float4 rimBoomColor = pow(1 -NDotL, _RimBoomAtten) * _RimColor;
                float4 rimBoomColor =  _RimColor * smoothstep(_RimBoomMin, _RimBoomMax, 1 -NDotL) * 0.06;
                
                return  (boomColor + colors + rimBoomColor );
            }
            ENDHLSL
        }

        pass 
        {
            Name "Outline"

            Tags{"LightModel"="ForwardBase"}

            Cull Front
            ZWrite On
		    ColorMask RGB
		    Blend SrcAlpha OneMinusSrcAlpha
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCg.cginc"

            struct VertexData
            {
                float3 modelPosition : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
                float4 vertColor : COLOR;

            };

            struct VSout
            {
                float4 position : SV_POSITION;
                float3 normal : TEXCOORD0;
                float4 color : COLOR;
            };

            float _outlineWidth;
            float4 _outlineColor;

            VSout vert(VertexData v)
            {
                VSout o;
                UNITY_INITIALIZE_OUTPUT(VSout, o);
                float4 position = UnityObjectToClipPos(v.modelPosition);
                float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.tangent.xyz);
                float3 pnomarl = normalize(TransformViewToProjection(viewNormal)) * position.w;

                float4 UpAndRightNearPoint = mul(unity_CameraInvProjection, float4(1, 1, UNITY_NEAR_CLIP_VALUE, _ProjectionParams.y));
                float aspect = abs(UpAndRightNearPoint.y / UpAndRightNearPoint.x);

                float screenRatio = _ScreenParams.y/_ScreenParams.x;
                pnomarl.x *=  screenRatio;

                position.xy += (_outlineWidth * 0.01f) * pnomarl.xy;
                o.position = position;

                o.color = v.vertColor;
                return o;

            }

            float4 frag(VSout i) : SV_TARGET
            {
                return _outlineColor * i.color;
            }

            ENDHLSL
        }

    }



    Fallback "diffuse"
}

2.NPR + BPR

3.1.ref

[1.]: 【1,2,3】卡通渲染基本光照模型的实现
[2.]: 【基于物理的渲染(PBR)白皮书】(四)法线分布函数相关总结
[3.]: Shader实验室: smoothstep函数
[4.]: 实时高逼真皮肤渲染01 次表面散射技术发展历史及技术详细解释 (讲的很nb,还没看先放这里

3 3.code

Shader "Unlit/NPR_PBR"
{
    Properties
    {
        [Header(MainColor)]
        _MainTex("Main Texture", 2D) = "white"{}


        [Space(10)]
        [Header(Outline)]
        _OutlineWidth("Outline Width", Range(0, 1)) = 0.2
        _OutlineColor("Outline Color", Color) = (0.0, 0.0, 0.0, 1.0)

        [Space(10)]
        [Header(Shadow)]
        _DividLineIllmuate("Illmute", Range(0, 1)) = 0.6
        _DividLineAmbint("Ambint", Range(0, 1)) = 0.4
        _DividLineShadow("Shadow", Range(0, 1)) = 0.2
        _DividSpecular("Specular", Range(0, 1)) = 0.85
        //_RampSharp("Ramp Sharp", Range(0, 1)) = 0.2
        [Space(10)]
        [Header(Fresnel)]
        _FresnelEffct("Fresnel Effictent", Range(0, 1)) = 0.5
        _FresnelDive("Frensel Divide", Range(0, 1)) = 0.1
        _Roughness("Roughness", Range(0, 1)) = 0.7
        _Metallic("Metallic", Range(0, 1)) = 0.1
    }

    SubShader
    {
        Tags{"LightMode"="ForwardBase"}

        HLSLINCLUDE

        const static float PI = 3.1415926f;

        float LineAddtive(float x)
        {
            return x * 0.5f + 0.5f;
        }

        float normalization(float x)
        {
            return x * 0.5f + 0.5f;
        }

        float NDF(float NdotH, float rougeness)
        {
            float a = rougeness * rougeness;

            float a2 = a * a;
            float nom = a2;

            NdotH = max(NdotH, 0.0);
            float d =  NdotH * NdotH *(a2 - 1) + 1;
            float dnom = PI * d * d;

            return nom / max(dnom, 0.001f);
        }

        float D_GGX( float a2, float NoH )
        {
            float d = ( NoH * a2 - NoH ) * NoH + 1; // 2 mad
            return a2 / ( PI*d*d );         // 4 mul, 1 rcp
        }

        float3 Fresnel(float NdotV, float3 F0)
        {
            return F0 + (1 - F0) * pow(1 - NdotV, 5);
        }  
        
        ENDHLSL

        pass
        {
            Name "Base Color"


            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "UnityLightingCommon.cginc"

            struct VertexData
            {
                float4 modelPosition : POSITION;
                float3 modelNormal : NORMAL;
                float2 uv : TEXCOORD0;
            };

            struct VSOut
            {
                float4 position :SV_POSITION;
                float3 worldPosition : TEXCOORD0;
                float3 worldNormal : TEXCOORD1;
                float2 uv : TEXCOORD2;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            float _DividLineIllmuate;
            float _DividLineAmbint;
            float _DividLineShadow;
            float _DividSpecular;

            float _RampSharp;
            float _Roughness;

            float _FresnelEffct;
            float _FresnelDive;
            float _Metallic;

            VSOut vert(VertexData v)
            {
                VSOut o;
                o.position = UnityObjectToClipPos(v.modelPosition);
                o.worldPosition = mul(unity_ObjectToWorld, v.modelPosition).xyz;
                o.worldNormal = normalize(UnityObjectToWorldNormal(v.modelNormal));
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }
                
            float sigmoid(float x, float center, float sharp) \
            {
                float s;
                s = 1 / (1 + pow(100000, (-3 * sharp * (x - center))));
                return s;
            }
            float4 frag(VSOut i) : SV_TARGET
            {
                float4 baseColor = tex2D(_MainTex, i.uv);

                float3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPosition));
                float3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPosition));
                float H = normalize(lightDir + viewDir);

                float NDotL = dot(i.worldNormal, lightDir);
                float NDotV = dot(i.worldNormal, viewDir);
                float NDoth = dot(i.worldNormal, H);
                float lightRatio = normalization(NDotL);
                // float intensity = 
                //     NDotv > _DividLineIllmuate ? 1.0 
                //     : NDotv > _DividLineAmbint ? 0.8 
                //     : NDotv > _DividLineShadow ? 0.5 
                //     : 0.3;
                _RampSharp = pow(_Roughness - 1, 2);

                float Illmuate = smoothstep(0.0f, _RampSharp, lightRatio - _DividLineIllmuate);
                float Ambint = smoothstep(0.0f, _RampSharp, lightRatio - _DividLineAmbint);
                float Shadow = smoothstep(0.0f, _RampSharp, lightRatio - _DividLineShadow);

                // float intensity = 
                //     Illmuate * 1.0f + 
                //     (Ambint - Illmuate) * 0.8f + 
                //     (Shadow - Ambint) * 0.5 +
                //     (1 - Shadow) * 0.3f;
                 float diffuse = 
                    Illmuate * (1 + LineAddtive(_DividLineIllmuate)) / 2
                    + (Ambint - Illmuate) * (LineAddtive(_DividLineIllmuate) + LineAddtive(_DividLineAmbint)) / 2
                    + (Shadow - Ambint) *(LineAddtive(_DividLineAmbint)+ LineAddtive(_DividLineShadow)) / 2
                    +(1 - Shadow) * LineAddtive(0.3f) / 2;

                float maxNDF = NDF(1, _Roughness);
                float specularDiv = maxNDF * _DividSpecular;
                 half NDF2 = D_GGX(_Roughness * _Roughness, clamp(0, 1, NDoth));

                float N = NDF(clamp(0, 1, NDoth), _Roughness);
                float specularRamp = smoothstep(0.0f, 0.3, N - specularDiv);
                float specular = specularRamp * (maxNDF + specularDiv) / 2;

                float3 F0 = 0.1;
                F0 = lerp(F0, baseColor, F0);
                float3 fresnal = Fresnel(max(NDotL, 0.0), F0);
                float3 fresnalResult = fresnal * _FresnelEffct * smoothstep(0.8, _FresnelDive, 1 - abs(NDotV));

                

                float3 intensity = diffuse.xxx + specular.xxx + fresnalResult;
                return baseColor * float4(intensity, 1.0f) * _LightColor0;
            }

            ENDHLSL
        }

        pass
        {
            Name "Outline"


            Cull Front
            ZWrite On
            Blend SrcAlpha OneMinusSrcAlpha

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            
            struct VertexData
            {
                float4 modelPosition : POSITION;
                float3 modelNormal : NORMAL;
                float4 color : Color;
            };

            struct VSOut
            {
                float4 position :SV_POSITION;
                float4 color : Color;
            };

            float _OutlineWidth;
            float4 _OutlineColor;

            VSOut vert(VertexData v)
            {
                VSOut o;
                float4 position = UnityObjectToClipPos(v.modelPosition);
                
                float3 projectNormal = normalize(mul(v.modelNormal, (float3x3)UNITY_MATRIX_MVP));
                float resloveRatio = _ScreenParams.y / _ScreenParams.x;
                float2 offset = projectNormal * _OutlineWidth * position.w * 0.03f;
                offset.x *= resloveRatio;
                position.xy += offset;

                o.position = position;
                o.color = v.color;
                return o;
            }
            float4 frag(VSOut i) : SV_TARGET
            {
                return _OutlineColor * i.color;
            }

            ENDHLSL
        }

    }

Fallback "Diffuse"

}

4.布料系统/头发

4.1 实现原理

4.1.1 ref
[1.]:布料模拟之基本原理
[2.]:NvCloth 布料模拟浅析
[3.]:图形引擎实战:布料功能在项目的应用
[4.]: 在Unity使用Verlet积分实现逼真的绳索
[5.]: GAMES103-基于物理的计算机动画入门
[6.]:GAMES103笔记 Lecture5 基于物理的布料模拟(Physics-based Cloth Simulation)
[7.]: Unity手游开发札记——布料系统原理浅析和在Unity手游中的应用
[8.]: Unity扩展编辑器
[9.]: 还有很多Unity API的资料
[10.] magica cloth源码

4.1.2.Editor脚本
先编写修改布料的不点和移动点的Editor脚本(ps:模仿magica cloth源码中写的 在这里插入图片描述
4.1.3.Editor code*

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.TerrainTools;
using Unity.VisualScripting;
using WPlayer;

namespace WEditor
{

    [CustomEditor(typeof(ClothMesh))]
    public class MeshPointCotrol : Editor
    {
        [SerializeField]
        private float pointSize = 0.2f;
        [SerializeField]
        Point.PointType selectType = Point.PointType.fixd;
        private Color fixedPointColor = Color.red;
        private Color movePointColor = Color.green;
        private Color invaildPointColor = Color.gray;

        private ClothMesh clothMesh;
        private Matrix4x4 worldMatrix;

        Mesh mesh;
        //方案1:static, 2.SetDirty
        // public static List<Point> points = new List<Point>();
        List<Point> points = null;
        static bool isInit = false;
        static object lastObj;

        private void OnEnable()
        {
            if (!Application.isEditor)
            {
                return;
            }

            if (target == null)
            {
                return;
            }
            clothMesh = target as ClothMesh;

            Mesh mesh;
            MeshFilter meshFilter = clothMesh.GetComponent<MeshFilter>();
            if (meshFilter != null)
            {
                mesh = meshFilter.sharedMesh; 
            }
            else
            {
               mesh = clothMesh.GetComponent<SkinnedMeshRenderer>().sharedMesh;
            }
            
            points = clothMesh.points;

            if (!target.Equals(lastObj))
            {
                isInit = false;
            }

            if (!isInit && points.Count == 0.0f)
            {
                lastObj = target;
                worldMatrix = clothMesh.GetComponent<Transform>().localToWorldMatrix;
                for (int i = 0; i < mesh.vertexCount; i++)
                {
                    Vector4 pos = mesh.vertices[i];
                    pos.w = 1.0f;
                    points.Add(new Point() { position = worldMatrix * pos, type = Point.PointType.move });
                }
                isInit = true;
            }



        }

        public override void OnInspectorGUI()
        {

            base.OnInspectorGUI();


            var em = EditorGUILayout.EnumPopup("Point Type", selectType, GUILayout.ExpandWidth(true));
            selectType = (Point.PointType)em;

            pointSize = EditorGUILayout.FloatField("Point Size", pointSize, GUILayout.ExpandWidth(true));
            // EditorGUI.EnumPopup(new Rect)

        }
        Vector3 startPoint, endPoint;


        private void OnSceneGUI()
        {
            if (!Application.isEditor)
            {
                return;
            }
            Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);

            //Handles.DrawDottedLine(startPoint, endPoint + startPoint, 4.0f);
            startPoint = ray.origin;
            endPoint = ray.origin + 1000.0f * ray.direction;

            points.ForEach((point) =>
            {
                HitTest(startPoint, endPoint, point);
            });

            points.ForEach(point =>
            {
                Handles.color = GetPointColor(point.type);
                Handles.SphereHandleCap(0, point.position, Quaternion.identity, pointSize, EventType.Repaint);
            });

            if (GUI.changed)
            {
                //EditorUtility.SetDirty(target);
            }
        }

        void HitTest(Vector3 start, Vector3 end, Point point)
        {
            float sqrlen = SqrtDistance(start, end, point.position);
            if (sqrlen <= pointSize * pointSize * 0.25f)
            {
                point.type = selectType;
            }
        }

        float SqrtDistance(Vector3 start, Vector3 end, Vector3 position)
        {
            Vector3 ab = end - start;
            Vector3 ac = position - start;
            Vector3 bc = position - end;

            float e = Vector3.Dot(ab, ac);
            float f = Vector3.Dot(ab, ab);
            float g = Vector3.Dot(ac, ac);
            if (e <= 0)
            {
                return g;
            }

            if (e >= f)
            {
                return Vector3.Dot(bc, bc);
            }

            return g - e * e / f;
        }



        Color GetPointColor(Point.PointType type)
        {
            Color color = Color.gray;
            switch (type)
            {
                case Point.PointType.invaild:
                    color = invaildPointColor;
                    break;
                case Point.PointType.fixd:
                    color = fixedPointColor;
                    break;
                case Point.PointType.move:
                    color = movePointColor;
                    break;
                default:
                    break;
            }

            return color;
        }
    }
}

4.1.4 Mesh模拟效果
在这里插入图片描述
4.1.5 code

用HashSet存储边,然后筛选添加extren edge
在model空间进行模拟,实现跟随父物体在世界空间进行运动模拟,基础碰撞。

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Animations.Rigging;


//物体太小
//model空间
//PBD
//跟随问题
namespace WPlayer
{

    public class ClothMesh : MonoBehaviour
    {
        [SerializeField] private float mass = 30.0f;
        [SerializeField] private float stiffness = 32.0f;
        [SerializeField] private bool isLockStart = true;
        [SerializeField] private float damping = 0.95f;
        [SerializeField] private float spring_K = 800.0f;
        [SerializeField] private float t;
        [SerializeField] private bool isDrawEdge = false;

        static readonly float g = 9.8f;
        public MeshData meshData;
        public List<Point> points = new List<Point>();
        public bool isinit = false;
        private MeshFilter meshFilter;
        private SkinnedMeshRenderer skinned;
        private Mesh mesh;
        List<Particale> particales = new List<Particale>();
        List<Spring> springs = new List<Spring>();

        HashSet<SortedPair> edgeIdnex = new HashSet<SortedPair>(new IComparer());
        List<SortedPair> rapidEdge = new List<SortedPair>();

        public GameObject colliderSphere;
        Matrix4x4 lastModelToParentWorld;

        //生命周期

        private void Start()
        {
            t = Time.fixedDeltaTime;
            InitMesh();

            if (transform.parent != null)
            {
                lastModelToParentWorld = transform.parent.localToWorldMatrix;
            }
        }

        private void FixedUpdate()
        {
            Simulation();
        }

        private void Update()
        {
            WriteBack();
        }

        private void OnDrawGizmos()
        {
            if (!isDrawEdge)
            {
                return;
            }

            for (int i = 0; i < springs.Count; i++)
            {
                Vector4 posA = springs[i].particaleA.position;
                posA.y += 0.2f;
                posA.w = 1.0f;
                posA = transform.localToWorldMatrix * posA;


                Vector4 posB = springs[i].particaleB.position;
                posB.y += 0.2f;

                posB.w = 1.0f;
                posB = transform.localToWorldMatrix * posB;
                Gizmos.color = Color.yellow;
                if (i > div)
                {
                    Gizmos.color = Color.red;
                }
                Gizmos.DrawLine(posA, posB);
            }
            Gizmos.color = Color.blue;
            
        }

        int div;
        void InitMesh()
        {
                       
            meshFilter = GetComponent<MeshFilter>();

            if (meshFilter != null)
            {
                
                mesh = meshFilter.mesh;
            }
            else
            {
                skinned = GetComponent<SkinnedMeshRenderer>();
                mesh = skinned.sharedMesh;

            }


            var vertices = mesh.vertices;

            for (int i = 0; i < vertices.Length; i++)
            {
                var pos = vertices[i];
                particales.Add(new Particale() { position = pos, oldPostion = pos, isLock = false });
            }

            int[] trigles = mesh.triangles;
            int trigleNumbers = trigles.Length;

            for (int i = 0; i < trigleNumbers; i += 3)
            {
                int triIndex0 = trigles[i + 0];
                int triIndex1 = trigles[i + 1];
                int triIndex2 = trigles[i + 2];


                AddEdge(triIndex0, triIndex1, triIndex2);
                AddEdge(triIndex1, triIndex2, triIndex0);
                AddEdge(triIndex2, triIndex0, triIndex1);
            }

            AddExtrenEdge(trigles);

            for (int i = 0; i < particales.Count; i++)
            {
                var pointType = points[i].type;
                if (pointType == Point.PointType.fixd)
                {
                    particales[i].isLock = true;
                }
            }
        }

        void AddEdge(int index1, int index2, int trg)
        {
            var edge1 = new SortedPair(index1, index2, trg);
            if (!edgeIdnex.Contains(edge1))
            {
                edgeIdnex.Add(edge1);
                springs.Add(new Spring(particales[index1], particales[index2]));
            }
            else
            {
                rapidEdge.Add(edge1);
            }
        }

        void AddExtrenEdge(int[] trigles)
        {
            div = springs.Count;
            rapidEdge.ForEach(edge =>
            {
                SortedPair otherEdge;
                edgeIdnex.TryGetValue(edge, out otherEdge);
                springs.Add(new Spring(particales[edge.triangle], particales[otherEdge.triangle]));
            });

        }

        void Simulation()
        {
            for (int i = 0; i < particales.Count; i++)
            {
                var part = particales[i];
                part.speed *= damping;
                part.position_hat = part.position + t * part.speed;
            }

            for (int i = 0; i < stiffness; i++)
            {
                GetGradient();
                for (int v = 0; v < particales.Count; v++)
                {
                    var part = particales[v];
                    if (!part.isLock)
                    {
                        var sogAppro = 1 / (t * t) * mass + 4 * spring_K;
                        part.position = part.position - part.gradient / sogAppro;
                    }
                }

            }

            for (int i = 0; i < particales.Count; i++)
            {
                var part = particales[i];
                part.speed += (part.position - part.position_hat) / t;
            }

            Collision_Handling();
        }

        void GetGradient()
        {
            Vector3 outForce = OutsideForceCount();
            for (int i = 0; i < particales.Count; i++)
            {
                var p = particales[i];
                p.gradient = mass * (p.position - p.position_hat) / t * t - outForce;
            }

            for (int i = 0; i < springs.Count; i++)
            {
                var spring = springs[i];
                var pA = springs[i].particaleA;
                var pB = springs[i].particaleB;

                var direction = pB.position - pA.position;

                var gs = spring_K * (1 - spring.length / direction.magnitude) * direction;
                pB.gradient += gs;
                pA.gradient -= gs;

            }
        }


        void WriteBack()
        {
            if (transform.parent == null)
            {
                return;
            }

            Matrix4x4 modelToParentWorld = transform.parent.localToWorldMatrix;
            Matrix4x4 baisMatrix = GetBaisMaxtrix(modelToParentWorld);

            var vertice = mesh.vertices;
            for (int i = 0; i < mesh.vertexCount; i++)
            {
                var part = particales[i];
                if (!part.isLock)
                {
                    Vector4 pos = part.position;
                    pos.w = 1.0f;
                    part.position = baisMatrix * pos;
                    vertice[i] = baisMatrix * pos;
                }
                else
                {

                    vertice[i] = part.position;
                }

            }

            mesh.vertices = vertice;
            lastModelToParentWorld = modelToParentWorld;
        }

        Matrix4x4 GetBaisMaxtrix(Matrix4x4 modelToParentWorld)
        {
            var rotation = Quaternion.Inverse(modelToParentWorld.rotation) * lastModelToParentWorld.rotation;
            var move = lastModelToParentWorld.GetColumn(3) - modelToParentWorld.GetColumn(3);
            var scale = modelToParentWorld.lossyScale;

            return Matrix4x4.TRS(move, rotation, scale);
        }

        Vector3 OutsideForceCount()
        {
            return new Vector3(0.0f, -mass * g, 0.0f);
        }

        void Collision_Handling()
        {
            if (colliderSphere != null)
            {
                float radius = colliderSphere.transform.localScale.x / 2;
                Vector3 center = colliderSphere.transform.position;

                for (int i = 0; i < particales.Count; i++)
                {
                    var part = particales[i];
                    Matrix4x4 worldToModel = Matrix4x4.Inverse(transform.localToWorldMatrix);
                    Vector4 modelCenter = center;
                    modelCenter.w = 1.0f;


                    modelCenter = worldToModel * modelCenter;
                    var dis = part.position - (Vector3)modelCenter;
                    if (dis.magnitude <= radius)
                    {
                        part.speed = part.speed + 1 / t * ((Vector3)modelCenter + radius * dis / dis.magnitude - part.position);
                        part.position = (Vector3)modelCenter + radius * dis / dis.magnitude;
                    }
                }

            }
        }

        class Spring
        {
            public Particale particaleA;
            public Particale particaleB;

            public Spring(Particale particaleA, Particale particaleB)
            {
                this.particaleA = particaleA;
                this.particaleB = particaleB;
                this.length = (particaleB.position - particaleA.position).magnitude;
            }

            public float length;
            public float k;
        }
        class Particale
        {
            public Vector3 position;
            public Vector3 oldPostion;
            public Vector3 position_hat;
            public Vector3 gradient;
            public Vector3 speed;
            public bool isLock;
        }



        class IComparer : IEqualityComparer<SortedPair>
        {
            public bool Equals(SortedPair x, SortedPair y)
            {

                return (x.lessValue == y.lessValue && x.value == y.value);

            }

            public int GetHashCode(SortedPair obj)
            {
                return obj.ToString().GetHashCode();
            }
        }

        class SortedPair
        {
            public int lessValue;
            public int value;

            public int triangle { get; private set; }

            public SortedPair(int v1, int v2, int trigale)
            {
                if (v1 > v2)
                {
                    lessValue = v2;
                    value = v1;
                }
                else
                {
                    lessValue = v1;
                    value = v2;
                }

                this.triangle = trigale;
            }
        }
    }

}

4.1.6问题

1.碰撞体太小会穿过
2.跟随父物体算法gpt说加一个形变空间,网上没有太多资料,因为不动点是跟随父物体向前移动的,但在model空间不能直接移动不动点,所以反过来让移动点向后移动,去乘以父物体上两帧中间的localToWorld矩阵中rotation,move差值的inverse构造出的新矩阵;scale保持最新的。
3.模拟效率太低,需要用jobs/burst提高计算效率。
4.没有添加bending edge和没有用PBD实现,后面有时间研究

4.2 Magica Cloth

自己写的显然不能用还是得用magica cloth插件。。

4.2.1.ref
[1.]: Magica Cloth官网
[2.]: 直接分析magica cloth包中给的案例
[3.]: Unity Magica Cloth 介绍
[4.] magica cloth源码
[5.] Magica Cloth 布料模拟使用心得

4.2.2.仿照案例效果
在这里插入图片描述

4.2.3.应用其他模型
优菈模型mesh没有分离,大多数头发和袖子上都没有骨骼。就偷懒随便做一下吧。倒是把参数基本都搞清楚了。

5.IK

5.1 简单实现几种原理

5.1.1.ref
[1.]: 【游戏开发】逆向运动学(IK)详解
[2.]: GAMES105-计算机角色动画基础
[3.]: 对雅可比矩阵的理解
[4.]: 利用雅可比矩阵来实现IK
[5.]: 角色动画研究 —— IK的三种结算方法 (写完了才发现的好文章

下面实现除了解析法在语义上基本存在问题,因为在一帧中进行迭代物体的位置不会实时更新,所以多次循环只是将每次的步长放大了。不过也能收敛。

5.2.1 解析法code

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class IKResolve : MonoBehaviour
{
    [SerializeField]
    public List<Transform> bonesTransforms = new List<Transform>();
    [SerializeField]
    public Transform target;
    Vector3 lastTargetPosition;
    private List<Bone> bones = new List<Bone>();

    float totalLenth = 0.0f;

    private void Start()
    {
        GetBones();

        lastTargetPosition = target.position;
    }

    private void FixedUpdate()
    {
        if ((target.position - lastTargetPosition).sqrMagnitude >= 0.1f)
        {
            TowBoneResolve_slover();

            lastTargetPosition = target.position;
        }
    }

    private void LateUpdate()
    {
        WriteBack();
    }

    private void OnDrawGizmos()
    {

        Gizmos.color = Color.yellow;
        for (int i = 0; i < bones.Count - 1; i++)
        {
            Gizmos.DrawLine(bones[i].position, bones[i + 1].position);
        }
        Gizmos.color = Color.red;
        if (bones.Count >= 1)
        {
            Gizmos.DrawLine(bones[0].position, target.position);
        }
    }
    void GetBones()
    {
        if (bonesTransforms.Count <= 1)
        {
            return;
        }

        for (int i = 0; i < bonesTransforms.Count; i++)
        {
            bones.Add(new Bone(bonesTransforms[i]));
        }

        //lentgh
        for (int i = 0; i < bones.Count - 1; i++)
        {
            totalLenth += (bones[i].position - bones[i + 1].position).magnitude;
        }
    }

    void TowBoneResolve_slover()
    {
        Debug.Assert(bones.Count == 3);
        if (!CanArriveTarget())
        {
            return;
        }

        var root = bones[0];
        var joint = bones[1];
        var end = bones[2];

        end.position = target.position;

        float sqrRootToEnd = (end.position - root.position).sqrMagnitude;
        float sqrJointToRoot = (joint.position - root.position).sqrMagnitude;
        float sqrJointToEnd = (end.position - joint.position).sqrMagnitude;

        float cosTheata =
            (sqrJointToRoot + sqrRootToEnd - sqrJointToEnd) /
            (2 * Mathf.Sqrt(sqrJointToRoot) * Mathf.Sqrt(sqrRootToEnd));

        Vector3 JointBlendDir = Vector3.Cross((end.position - root.position), joint.position - root.position);
        JointBlendDir = Vector3.Cross(JointBlendDir, end.position - root.position).normalized;

        float theata = Mathf.Acos(cosTheata);
        float f = Mathf.Sqrt(sqrJointToRoot) * Mathf.Sin(theata);
        Debug.Log(Mathf.Sin(theata));
        float dist = Mathf.Sqrt(sqrJointToRoot - f * f);

        Vector3 offsetX = (end.position - root.position).normalized * dist;
        Vector3 offsetY = JointBlendDir * f;

        joint.position = root.position + offsetX + offsetY;

    }



    void WriteBack()
    {
        for (int i = 0; i < bonesTransforms.Count; i++)
        {
            bonesTransforms[i].position = bones[i].position;
        }
    }

    void Jcobi_slover()
    {

    }

    bool CanArriveTarget()
    {

        if (bones.Count <= 1)
        {
            return false;
        }

        //Fixed: right jugement


        if ((bones[0].position - target.position).sqrMagnitude <= totalLenth * totalLenth)
        {
            return true;
        }
        return false;
    }

    class Bone
    {
        public Bone(Transform parent)
        {
            position = parent.position;
            quaternion = parent.rotation;
        }

        public Vector3 position;
        public Quaternion quaternion;
        public float maxRotationAngle = 180.0f;
    }
}

5.1.2.2 数值法(雅可比矩阵)

using System.Collections.Generic;
using UnityEngine;

public class IkJacobi : MonoBehaviour
{
    [SerializeField]
    public List<Transform> bonesTransforms = new List<Transform>();
    [SerializeField]
    public Transform target;
    [SerializeField]
    float presource = 0.1f;
    [SerializeField]
    public int limits = 10;
    [SerializeField]
    float step = 0.01f;
    Vector3 lastTargetPosition;
    private List<Bone> bones = new List<Bone>();

    List<float> jacbiMaxtrix = null;
    Vector3[] jacbiMaxtrixInserve = null;
    List<float> jointRotation = null;
    List<Vector3> jointRotationAsix = null;

    float totalLenth = 0.0f;

    private void Start()
    {
        GetBones();
        InitData();

        lastTargetPosition = target.position;
    }

    private void FixedUpdate()
    {
        if ((target.position - lastTargetPosition).sqrMagnitude >= 0.1f)
        {
            lastTargetPosition = target.position;

        }
            Jacbi_slover();
    }

    private void LateUpdate()
    {
        //WriteBack();
    }

    private void OnDrawGizmos()
    {

        Gizmos.color = Color.yellow;
        for (int i = 0; i < bones.Count - 1; i++)
        {
            Gizmos.DrawLine(bones[i].position, bones[i + 1].position);
        }
        Gizmos.color = Color.red;
        if (bones.Count >= 1)
        {
            Gizmos.DrawLine(bones[0].position, target.position);
        }
    }

    void GetBones()
    {
        if (bonesTransforms.Count <= 1)
        {
            return;
        }

        for (int i = 0; i < bonesTransforms.Count; i++)
        {
            bones.Add(new Bone(bonesTransforms[i]));
        }

        //lentgh
        for (int i = 0; i < bones.Count - 1; i++)
        {
            totalLenth += (bones[i].position - bones[i + 1].position).magnitude;
        }
    }

    void InitData()
    {
        int bonesCount = bones.Count;
        jacbiMaxtrixInserve = new Vector3[bonesCount];
        jacbiMaxtrix = new List<float>(bonesCount * 3 * 1);
        jointRotationAsix = new List<Vector3>(bonesCount);
        jointRotation = new List<float>(bonesCount);

        for (int i = 0; i < bonesCount; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                jacbiMaxtrix.Add(0);
            }
            jointRotationAsix.Add(new Vector3(0.0f, 0.0f, 0.0f));
            jointRotation.Add(0);
        }
    }

    void Jacbi_slover()
    {
        if (!CanArriveTarget())
        {
            return;
        }

        Debug.Assert(bones.Count >= 1);
        int limit = 0;
        float sqrDist = presource + 1.0f;
        //没必要的循环
        while (sqrDist > presource * presource && limit++ <= limits)
        {
            Vector3 endPos = bonesTransforms[bones.Count - 1].position;
            Vector3 targetPos = target.position;
        
            for (int i = 0; i < bones.Count; i++)
            {
                Vector3 jointToEnd = endPos - bonesTransforms[i].position;
                Vector3 jointToTarget = targetPos - bonesTransforms[i].position;

                Vector3 rotationVector = Vector3.Cross(jointToEnd, jointToTarget).normalized;
                Vector3 jointFoward = Vector3.Cross(rotationVector, jointToEnd);
                jointRotationAsix[i] = rotationVector;

                jacbiMaxtrix[i] = jointFoward.x;
                jacbiMaxtrix[i + bones.Count] = jointFoward.y;
                jacbiMaxtrix[i + bones.Count * 2] = jointFoward.z;
            }

            Vector3 e = endPos - targetPos;
            sqrDist = e.sqrMagnitude;
            //inserve

            int row = jacbiMaxtrix.Count / 3;
            for (int i = 0; i < row; i++)
            {
                jacbiMaxtrixInserve[i].x = jacbiMaxtrix[i];
                jacbiMaxtrixInserve[i].y = jacbiMaxtrix[i + bones.Count];
                jacbiMaxtrixInserve[i].z = jacbiMaxtrix[i + bones.Count * 2];
            }

            for (int i = 0; i < bones.Count; i++)
            {
                jointRotation[i] = 0.0f;
            }

            for (int i = 0; i < bones.Count; i++)
            {
                jointRotation[i] += Vector3.Dot(jacbiMaxtrixInserve[i], e);
            }

            for (int i = 0; i < bones.Count; i++)
            {
                var quaternion = Quaternion.AngleAxis(jointRotation[i] * step, -jointRotationAsix[i]);
                quaternion.Normalize();
                bonesTransforms[i].rotation = quaternion * bonesTransforms[i].rotation;
            }

            //WriteBack();
        }
    }


    void WriteBack()
    {
        for (int i = bonesTransforms.Count - 1; i >= 0; i--)
        {
            //bonesTransforms[i].rotation = bones[i].rotation;

        }
        for (int i = 0; i < bonesTransforms.Count; i++)
        {
            bonesTransforms[i].rotation = bones[i].rotation;
        }
    }


    bool CanArriveTarget()
    {

        if (bones.Count <= 1)
        {
            return false;
        }

        //Fixed: right jugement


        if ((bones[0].position - target.position).sqrMagnitude <= totalLenth * totalLenth)
        {
            return true;
        }
        return false;
    }

    class Bone
    {
        public Bone(Transform parent)
        {
            position = parent.position;
            rotation = parent.rotation;
        }

        public Vector3 position;
        public Quaternion rotation;
        public float maxRotationAngle = 180.0f;
    }
}


5.2.1.3 Cyclic coordinate descent(循环坐标推演)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class IKCCD : MonoBehaviour
{
    [SerializeField]
    List<Transform> boneTransforms = new List<Transform>();
    public Transform target;

    private int boneCount;
    private Transform end;
    int index;

    private void Start()
    {

        boneCount = boneTransforms.Count;
        Debug.Assert(boneCount >= 1);
        end = boneTransforms[boneCount - 1];
        index = boneCount - 2;
    }
    void Update()
    {
        if (target != null)
        {
            CCD_Slover();
            //CCDSingle_Slover();
        }
    }

    void CCDSingle_Slover()
    {
        if (boneCount <= 1)
        {
            return;
        }

        var joint = boneTransforms[index];
        Quaternion q = AcculateAngle(joint);
        joint.rotation = q * joint.rotation;

        index = index - 1;
        if (index == 0)
        {
            index = boneCount - 2;
        }

    }


    void CCD_Slover()
    {
        if (boneCount <= 1)
        {
            return;
        }

        for (int i = boneCount - 2; i >= 0; i--)
        {
            var joint = boneTransforms[i];
            Quaternion q = AcculateAngle(joint);
            joint.rotation = q * joint.rotation;
        }
    }

    Quaternion AcculateAngle(Transform joint)
    {
        var endToJoint = end.position - joint.position;
        var targetToJoint = target.position - joint.position;

        return Quaternion.FromToRotation(endToJoint, targetToJoint);
    }
}

5.1.2.4 Forward and backward reaching inverse kinematics(前向和后向到达IK)
和CCD基本一致思想

5.1.2.5 PBD
后面有时间倒回来研究

5.2 实现Foot IK,Hand IK等

5.2.1.ref
[1.]: 【Unity3d FootIK】写一个最简单的IK(1)
[2.]: Unity Mecanim动画系统 之 IK(Inverse Kinematics即反向动力学)的相关说明和简单使用
[3.]: 站稳了没?——Unity IK的简单应用
[4]: 【UNITY FOOT IK】实现FOOT IK SYSTEM以适应不同地形

5.2.2.1 控制类

为了实现一个功能比较自由,扩展性稍微好点的ik需要定义几个类

1.IKSetting总控制IK
统一提供修改IK的状态的接口,并记录角色处于的状态以便同一个ik进行不同的行为

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.PlayerSettings;

[RequireComponent(typeof(Animator))]
public class IkSetting : MonoBehaviour
{
   [Header("Control")]
   [SerializeField] public bool m_IsActive = true;
   [SerializeField] public PlayerAction action = PlayerAction.HoldWeapon;
   [Header("Animation Curve")]
   [SerializeField] private string runningLeft = "LeftFoot";
   [SerializeField] private string runningRight = "RightFoot";
   [SerializeField] private string jump = "Jump";
   private Animator m_Animator;

   public enum PlayerAction
   {
       Idel,
       HoldWeapon,
       Climb
   }

   public enum IKGoal
   {
       Body = -1,
       RightFoot = AvatarIKGoal.RightFoot,
       LeftFoot = AvatarIKGoal.LeftFoot,
       RightHand = AvatarIKGoal.RightHand,
       LeftHand = AvatarIKGoal.LeftHand
   }

   private void Awake()
   {
       m_Animator = GetComponent<Animator>();
       Debug.Assert(m_Animator != null);
   }
   public  Transform GetBonesTransform(HumanBodyBones bones)
   {
       return m_Animator.GetBoneTransform(bones);
   }

   public Vector3 GetPosition(IKGoal goal)
   {
       if (goal == IKGoal.Body)
       {
           return m_Animator.bodyPosition;
       }
       return m_Animator.GetIKPosition((AvatarIKGoal)goal);
   }



   public Quaternion GetRotation(IKGoal goal)
   {
       if (goal == IKGoal.Body)
       {
           return m_Animator.bodyRotation;
       }
       return m_Animator.GetIKRotation((AvatarIKGoal)goal);
   }

   public void SetPosition(IKGoal goal, Vector3 pos)
   {
       if (!m_IsActive)
       {
           return;
       }

       if (goal == IKGoal.Body)
       {
           m_Animator.bodyPosition = pos;
       }
       else
       {
           m_Animator.SetIKPosition((AvatarIKGoal)goal, pos);
       }
   }

   public void SetRotation(IKGoal goal, Quaternion q)
   {
       if (!m_IsActive)
       {
           return;
       }

       if (goal == IKGoal.Body)
       {
           m_Animator.bodyRotation = q;
       }
       else
       {
           m_Animator.SetIKRotation((AvatarIKGoal)goal, q);
       }
   }



   private void OnAnimatorIK(int layerIndex)
   {
       if (m_Animator == null) { return; }

       if (m_IsActive)
       {
           float runningRightCurve =  m_Animator.GetFloat(runningRight);
           float runningLeftCurve =  m_Animator.GetFloat(runningLeft);
           m_Animator.SetIKPositionWeight(AvatarIKGoal.LeftFoot, 1.0f);
           m_Animator.SetIKRotationWeight(AvatarIKGoal.LeftFoot, 1.0f);

           m_Animator.SetIKPositionWeight(AvatarIKGoal.RightFoot, 1.0f);
           m_Animator.SetIKRotationWeight(AvatarIKGoal.RightFoot, 1.0f);

           m_Animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1.0f);
           m_Animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1.0f);

           m_Animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 0.1f);
           m_Animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 0.1f);
       }
       else
       {
           m_Animator.SetIKPositionWeight(AvatarIKGoal.LeftFoot, 0.0f);
           m_Animator.SetIKRotationWeight(AvatarIKGoal.LeftFoot, 0.0f);

           m_Animator.SetIKPositionWeight(AvatarIKGoal.RightFoot, 0.0f);
           m_Animator.SetIKRotationWeight(AvatarIKGoal.RightFoot, 0.0f);

           m_Animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 0.0f);
           m_Animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 0.0f);

           m_Animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 0.0f);
           m_Animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 0.0f);
       }
   }

   public void SetIKPoint(IkSetting.IKGoal goal, Transform foot, Vector3 position, Quaternion rotation)
   {
       if (position != Vector3.zero)
       {
           SetPosition(goal, position);
           foot.position = position;
       }


       if (rotation != Quaternion.identity)
       {
           SetRotation(goal, rotation);
           foot.rotation = rotation;
       }

   }


}

2.IKControlBase
提取IK相同的特征和方法便于控制和编写。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


[RequireComponent(typeof(IkSetting))]
public class IkControlBase : MonoBehaviour
{
    [Header("Debug")]
    [SerializeField] protected bool isDebug = true;
    [SerializeField] protected float hitPointRaduis = 0.02f;

    [Header("Ray")]
    [SerializeField] protected float fowardLength = 0.1f;
    [SerializeField] protected float backLength = 0.05f;
    [SerializeField] protected float maxDistance = 0.15f;
    [SerializeField] protected float raduis = 0.1f;
    [SerializeField] protected float deviateDistance = 0.02f;
    [SerializeField] protected LayerMask layerMasks;

    [Header("Control")]
    [Range(0, 10)]
    [SerializeField] protected bool isIK = true;
    [SerializeField] protected float footOffset = 0.01f;
    [SerializeField] protected float iKSpeed = 1.0f;
    [SerializeField] protected float ikMoveSpeed = 0.5f;
    [SerializeField] protected float ikRotationVelocity = 0.5f;
    [SerializeField] protected Transform leftHand;
    [SerializeField] protected Transform rightHand;


    protected IkSetting ik;
    RaycastHit rayHit;

   

    public void InitData(HumanBodyBones bone, ref Transform foot)
    {
        foot.SetPositionAndRotation(
            ik.GetBonesTransform(bone).position,
            ik.GetBonesTransform(bone).rotation);
    }
    public void IKRayCast(IkSetting.IKGoal goal, HumanBodyBones bone, Transform foot, ref Vector3 position, ref Quaternion rotation)
    {
        foot.position = ik.GetBonesTransform(bone).position;
        foot.position = ik.GetPosition(goal);
        foot.rotation = ik.GetRotation(goal);

        Vector3 offset = foot.up * deviateDistance;
        Vector3 startPositon = foot.position + offset;


        if (Physics.Raycast(
            startPositon,
            -foot.up, out rayHit, maxDistance,
            layerMasks))
        {
            var hitNormal = rayHit.normal;

            var quaternion = Quaternion.FromToRotation(foot.up, hitNormal);
            position = rayHit.point + foot.up * footOffset;
            rotation = quaternion * foot.rotation;
        }
        else
        {
            position = Vector3.zero;
            rotation = Quaternion.identity;
        }



        if (isDebug)
        {
            Debug.DrawLine(foot.position, -foot.up * maxDistance + foot.position, Color.red);
        }
    }


    void FootCapsuleRayCast(IkSetting.IKGoal goal, HumanBodyBones bone, Transform foot, ref Vector3 position, ref Quaternion rotation)
    {
        foot.position = ik.GetBonesTransform(bone).position;
        foot.rotation = ik.GetRotation(goal);

        Vector3 offset = foot.up * deviateDistance;
        Vector3 capsuleStart = foot.position - foot.forward * fowardLength + offset;
        Vector3 capsuleEnd = foot.position + foot.forward * backLength + offset;

        if (Physics.CapsuleCast(
            capsuleStart, capsuleEnd, raduis,
            -transform.up, out rayHit, maxDistance,
            layerMasks))
        {
            var hitNormal = rayHit.normal;

            var quaternion = Quaternion.FromToRotation(foot.up, hitNormal);
            position = rayHit.point + foot.up * footOffset;
            rotation = quaternion * foot.rotation;

            if (isDebug)
            {
                Debug.Log("Hit");
                Debug.Log(ik.GetBonesTransform(HumanBodyBones.RightFoot).up);
            }
        }
        else
        {
            position = Vector3.zero;
            rotation = Quaternion.identity;
        }



        if (isDebug)
        {
            Debug.DrawLine(capsuleStart, -foot.up * maxDistance + capsuleStart, Color.red);
            Debug.DrawLine(capsuleEnd, -foot.up * maxDistance + capsuleEnd, Color.red);
        }
    }


   
}

5.2.2.2 Foot IK
觉得获得bone位置效果好一点;还有就是用曲线控制ik,感觉事件应该好一些,后面换成事件控制ik
1.效果
在这里插入图片描述

2.code

using UnityEngine;



public class FootIK : IkControlBase
{
   
    [SerializeField] private Transform leftFoot;
    [SerializeField] private Transform rightFoot;

    private Vector3 leftFootPosition, rightFootPositon;
    private Quaternion leftFootRotation, rightFootRotation;

   


    private void Start()
    {
        ik = GetComponent<IkSetting>();
        Debug.Assert(ik != null);

        InitData(HumanBodyBones.RightFoot, ref rightFoot);
        InitData(HumanBodyBones.LeftFoot, ref leftFoot);
    }
 

    private void Update()
    {
        if (ik != null && ik.m_IsActive && isIK)
        {
            IKRayCast(IkSetting.IKGoal.LeftFoot, HumanBodyBones.LeftFoot, leftFoot, ref leftFootPosition, ref leftFootRotation);
            IKRayCast(IkSetting.IKGoal.RightFoot, HumanBodyBones.RightFoot, rightFoot, ref rightFootPositon, ref rightFootRotation);
        }
    }

    private void OnAnimatorIK(int layerIndex)
    {

        if (!(ik != null && ik.m_IsActive && isIK))
            return;

        //MoveBody();

        ik.SetIKPoint(IkSetting.IKGoal.LeftFoot, leftFoot, leftFootPosition, leftFootRotation);
        ik.SetIKPoint(IkSetting.IKGoal.RightFoot, rightFoot, rightFootPositon, rightFootRotation);

    }

    private void OnDrawGizmos()
    {
        if (isDebug)
        {
            if (leftFoot == null || rightFoot == null)
            {
                return;
            }
            Gizmos.color = Color.yellow;
            Gizmos.DrawSphere(leftFootPosition, hitPointRaduis);
            Gizmos.DrawSphere(rightFootPositon, hitPointRaduis);
        }
    }
}

    


5.2.2.3 Hand IK
1.效果
climb ik

2.code

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HandIK : IkControlBase
{
    public Transform leftHandHold;
    public Transform rightHandHold;

    private Vector3 leftHandPosition, rightHandPositon;
    private Quaternion leftHandRotation, rightHandRotation;


    private void Start()
    {
        ik = GetComponent<IkSetting>();
        Debug.Assert(ik != null);

        InitData(HumanBodyBones.RightHand, ref rightHand);
        InitData(HumanBodyBones.LeftHand, ref leftHand);
    }


    private void Update()
    {
        ChangeAnction();
        IkAction();
    }

    
    void ChangeAnction()
    {
        if (ik.action == IkSetting.PlayerAction.HoldWeapon && leftHandHold == null && rightHandHold == null)
        {
            ik.action = IkSetting.PlayerAction.Idel;
        }
    }
    void IkAction()
    {
        switch (ik.action)
        {
            case IkSetting.PlayerAction.Idel:
                break;
            case IkSetting.PlayerAction.HoldWeapon:
                WeaponAction();
                break;
            case IkSetting.PlayerAction.Climb:
                ClampAction();
                break;
            default:
                break;
        }
    }


    void WeaponAction()
    {
        
        if (ik != null && ik.m_IsActive && isIK)
        {
            HandPointSet(leftHandHold, ref leftHandPosition, ref leftHandRotation);
            HandPointSet(rightHandHold, ref rightHandPositon, ref rightHandRotation);
        }
    }

    void HandPointSet(Transform hold, ref Vector3 position, ref Quaternion rotation)
    {
        if (hold == null)
        {
            position = Vector3.zero;
            rotation = Quaternion.identity;
            return;
        }

        position = hold.position;
        rotation = hold.rotation;
    }

    void ClampAction()
    {
        if (ik != null && ik.m_IsActive && isIK)
        {
            IKRayCast(IkSetting.IKGoal.LeftHand, HumanBodyBones.LeftHand, leftHand, ref leftHandPosition, ref leftHandRotation);
            IKRayCast(IkSetting.IKGoal.RightHand, HumanBodyBones.RightHand, rightHand, ref rightHandPositon, ref rightHandRotation);
        }
    }

    private void OnAnimatorIK(int layerIndex)
    {

        if (!(ik != null && ik.m_IsActive && isIK))
            return;

        //MoveBody();

        ik.SetIKPoint(IkSetting.IKGoal.LeftHand, leftHand, leftHandPosition, leftHandRotation);
        ik.SetIKPoint(IkSetting.IKGoal.RightHand, rightHand, rightHandPositon, rightHandRotation);

    }

    private void OnDrawGizmos()
    {
        if (isDebug)
        {
            if (leftHand == null || rightHand == null)
            {
                return;
            }
            Gizmos.color = Color.yellow;
            Gizmos.DrawSphere(leftHandPosition, hitPointRaduis);
            Gizmos.DrawSphere(rightHandPositon, hitPointRaduis);
        }
    }

    
}

6. 人物相机移动

实现基础的移动,攀爬,滑铲等动作,以及动画和不同相机的切换

6.1 输入控制类
便于配置不同的按键和管理人物动作

using UnityEngine;

[RequireComponent(typeof(ThridPersonControl))]
public class ThirdPerson : MonoBehaviour
{
    [Header("Control KeyCode")]
    [SerializeField] private KeyCode JumpKey = KeyCode.Space;
    [SerializeField] private KeyCode SprintingKey = KeyCode.LeftShift;
    [SerializeField] private KeyCode CrouchKey = KeyCode.LeftControl;
    [SerializeField] private KeyCode ClampUpkey = KeyCode.Q;
    
    private Vector3 moveDirection;
    private float rotationX = 0.0f;
    private float rotationY = 0.0f;
    private ThridPersonControl personControl;
    private WallRunning wallRunning;
    private bool isJump;
    private bool isSprinting;
    private bool isCrouch;
    private bool isClampUp;
    
    private void Start()
    {
      
        personControl = GetComponent<ThridPersonControl>();
        wallRunning = GetComponent<WallRunning>();
    }

    private void FixedUpdate()
    {

        personControl.Move(moveDirection, isJump, isSprinting, isCrouch);

        if (wallRunning != null)
        {
            wallRunning.RunningMovement(moveDirection, isClampUp);
        }
    }

    private void Update()
    {
        MyInput();
        personControl.Rotate(rotationX, rotationY);
    }

    void MyInput()
    {
        isJump =  Input.GetKey(JumpKey);
        isSprinting = Input.GetKey(SprintingKey);
        isCrouch = Input.GetKey(CrouchKey);
        isClampUp = Input.GetKey(ClampUpkey);

        moveDirection.x = Input.GetAxisRaw("Horizontal");
        moveDirection.z = Input.GetAxisRaw("Vertical");

        rotationX = Input.GetAxisRaw("Mouse X");
        rotationY = Input.GetAxisRaw("Mouse Y");
    }
}


6.2 控制人物相机基础类
实现基础动作和其他动作的管理和速度控制,以及不同人称相机相对人物的位移。

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class ThridPersonControl : MonoBehaviour
{
    [Header("Move")]
    public PlayerMoveState moveState;
    public float moveSpeed;
    [SerializeField] private float walkSpeed = 5.0f;
    [SerializeField] private float sprintingSpeed = 8.0f;
    [SerializeField] private float crouchSpeed = 3.0f;
    [SerializeField] private float rotateSpeed = 5.0f;
    [SerializeField] private float playerRotationMinAngle = -40.0f;
    [SerializeField] private float playerRotationMaxAngle = 30.0f;
    [SerializeField] private float diffenrentSpeed = 5.0f;
    private float desireSpeed = 0.0f;
    private float playerRotationX;
    private float playerRotationY;
    private Vector3 nowSpeed;

    public enum PlayerMoveState
    {
        Walk,
        sprinting,
        air,
        crouch,
        sliding,
        wallRunning
    }

    public enum CameraType
    {
        Fisrt,
        Second,
        Third
    }


    [Header("Jump")]
    [SerializeField] private float playerHighten = 0.2f;
    [SerializeField] private float jumpForce = 5.0f;
    [SerializeField] private float jumpInvoke = 1.0f;
    [SerializeField] private float airDrag = 0.0f;
    [SerializeField] private float mulAirForce = 1.0f;

    [Header("Ground")]
    [SerializeField] private float groundDrag = 2.0f;
    [SerializeField] private LayerMask groundMask;

    private bool isInCanjump = true;
    public bool isInGrounded;
    [Header("Slope")]
    [SerializeField] private float maxSlopeAngle = 40.0f;
    float slopeAngle = 0.0f;
    private RaycastHit hitSlope;
    public bool isInSlope = false;
    private bool isExistingSlope;

    [Header("Sliding")]
    [SerializeField] private float sliderMaxSpeed = 25.0f;
    [SerializeField] private float sliderSpeed = 9;
    private Sliding sliding;
    public bool isContriasSlidingSpeed = true;
    public bool isSliding = false;
    public bool isCanSliding = true;

    [Header("Wall Running")]
    public float wallRunningSpeed = 5.0f;
    public bool isWallRunning = false;

    [Space(10)]
    [Header("Camera Orientation")]
    public Transform cameraPosition;
    public Transform cameraOrientation;
    [SerializeField] private float SensX = 400.0f;
    [SerializeField] private float SensY = 400.0f;
    [Header("Camera Follow")]
    [SerializeField] private CameraType cameraType = CameraType.Fisrt;
    [SerializeField] private float right = 0.0f;
    [SerializeField] private float distanse = 1.5f;
    [SerializeField] private float highten = 1.8f;
    [SerializeField] private float lookAngle = 10.0f;
    [SerializeField] private float cameraMinAngle = -40.0f;
    [SerializeField] private float cameraMaxAngle = 30.0f;


    private new Rigidbody rigidbody;
    private Transform transform;

    private float cameraRotationAngelX = 0.0f;
    private float cameraRotationAngelY = 0.0f;

    private Quaternion towrdsQuaternion;
    // Start is called before the first frame update
    void Start()
    {
        if (TryGetComponent<Transform>(out transform))
        {
            towrdsQuaternion = transform.localRotation;
        }

        if (TryGetComponent<Rigidbody>(out rigidbody))
        {
            rigidbody.freezeRotation = true;
        }

        sliding = GetComponent<Sliding>();

    }

    private void MoveStateChange(bool isJump = false, bool isSprinting = false, bool isCrouch = false)
    {
        rigidbody.drag = (isInGrounded ? groundDrag : airDrag);

        rigidbody.useGravity = !isInSlope;
        if (isInGrounded)
        {
            if (isWallRunning)
            {
                desireSpeed = wallRunningSpeed;
                moveState = PlayerMoveState.wallRunning;
            }
            if (isSprinting)
            {
                desireSpeed = sprintingSpeed;
                moveState = PlayerMoveState.sprinting;
            }

            if (isJump && isInCanjump)
            {
                //moveSpeed = sprintingSpeed;
                moveState = PlayerMoveState.air;
            }

            if (isCrouch)
            {
                if (rigidbody.velocity.magnitude >= sprintingSpeed && isCanSliding)
                {
                    desireSpeed = sliderSpeed;
                    moveState = PlayerMoveState.sliding;
                }
                else
                {
                    desireSpeed = crouchSpeed;
                    moveState = PlayerMoveState.crouch;
                }
            }
            if (!isSprinting && !isCrouch && !isWallRunning)
            {
                desireSpeed = walkSpeed;
                moveState = PlayerMoveState.Walk;
            }

            if (Mathf.Abs(moveSpeed - desireSpeed) >= diffenrentSpeed)
            {
                StopAllCoroutines();
                StartCoroutine(SmoothMoveSpeed());

            }
            else
            {
                moveSpeed = desireSpeed;
            }

        }

    }

    IEnumerator SmoothMoveSpeed()
    {

        float diff = Math.Abs(desireSpeed - moveSpeed);
        float timer = 0.0f;
        float startSpeed = moveSpeed;

        while (diff >= timer)
        {
            moveSpeed = Mathf.Lerp(startSpeed, desireSpeed, timer / diff);
            timer += Time.deltaTime;
            yield return null;
        }
    }
    public void Move(Vector3 moveAmount, bool isJump = false, bool isSprinting = false, bool isCrouch = false)
    {
        CheckInGround();
        MoveStateChange(isJump, isSprinting, isCrouch);
        MovePlayerAndCamera(moveAmount);

        isContriasSlidingSpeed = !sliding.CheckSlidingDown(moveAmount, hitSlope);
        SlidingMove(moveAmount, isCrouch);

        if (isJump)
        {
            sliding.StopSliding();
            Jump();
        }
    }




    public void Rotate(float RotationX, float RotationY = 0.0f)
    {
        playerRotationX += -RotationY * SensY * Time.deltaTime;
        playerRotationY += RotationX * SensX * Time.deltaTime;
        playerRotationX = Mathf.Clamp(playerRotationX, playerRotationMinAngle, playerRotationMaxAngle);
        if (transform != null)
        {
            transform.localRotation = Quaternion.Euler(0.0f, playerRotationY, 0.0f);
        }

        cameraRotationAngelY = playerRotationY;
        cameraRotationAngelX = playerRotationX;
        if (cameraOrientation != null)
        {
            cameraOrientation.localRotation = Quaternion.Euler(cameraRotationAngelX, cameraRotationAngelY, 0.0f);
        }

        if (cameraOrientation != null)
        {
            cameraOrientation.Rotate(lookAngle, 0.0f, 0.0f);
        }

        //camera
        /*cameraRotationAngelX = -RotationY * SensY * Time.deltaTime;
        cameraRotationAngelY = RotationX * SensX * Time.deltaTime;

        cameraRotationAngelX = Mathf.Clamp(cameraRotationAngelX, cameraMinAngle, cameraMaxAngle);
*/


    }


    private void SlidingMove(Vector3 moveAmount, bool isCrouch)
    {
        if (sliding != null)
        {
            if (isCrouch && rigidbody.velocity.magnitude >= sprintingSpeed && isCanSliding)
            {
                sliding.StartSliding();
            }
            else
            {
                sliding.StopSliding();
            }

            if (isSliding)
            {
                sliding.SlidingMove(moveAmount, hitSlope);
            }
        }
    }
    private void MovePlayerAndCamera(Vector3 moveAmount)
    {
        if (transform != null && rigidbody != null)
        {
            moveAmount = transform.TransformDirection(moveAmount);


            if (isInSlope)
            {
                //bad 
                //rigidbody.AddForce(-Physics.gravity , ForceMode.Force);
            }

            if (isInGrounded)
            {

                rigidbody.AddForce(GetProjectDiretion(moveAmount).normalized * moveSpeed * 10.0f, ForceMode.Force);
            }
            else if (!isInGrounded)
            {
                rigidbody.AddForce(moveAmount.normalized * moveSpeed * mulAirForce * 10.0f, ForceMode.Force);
            }



            //contrais speed do not over move speeds

            ControlSpeed();


        }



        if (cameraPosition != null)
        {
            var v = cameraPosition.localPosition;
            switch (cameraType)
            {
                case CameraType.Fisrt:
                    v.Set(0.0f, highten / 1.5f, distanse / 4);
                    break;
                case CameraType.Second:
                    break;
                case CameraType.Third:
                    v.Set(right, highten, -distanse);
                    break;
            }
            cameraPosition.localPosition = v;
        }
    }

    public Vector3 GetProjectDiretion(Vector3 direction)
    {
        if (isInGrounded)
        {
            direction = Vector3.ProjectOnPlane(direction, hitSlope.normal);
        }

        return direction;
    }
    void ControlSpeed()
    {
        nowSpeed = rigidbody.velocity;

        float contriasSpeed = moveSpeed;


        if (isSliding && !isContriasSlidingSpeed)
        {
            contriasSpeed = sliderMaxSpeed;
        }

        if (nowSpeed.sqrMagnitude >= contriasSpeed * contriasSpeed)
        {
            if (isInSlope && isExistingSlope)
            {
                nowSpeed.Normalize();
                nowSpeed *= moveSpeed;
            }
            else
            {
                nowSpeed.y = 0.0f;
                nowSpeed.Normalize();
                nowSpeed *= moveSpeed;
                nowSpeed.y = rigidbody.velocity.y;
            }
            rigidbody.velocity = nowSpeed;

        }
    }

    void Jump()
    {

        if ((isInGrounded || isWallRunning) && isInCanjump)
        {
            isExistingSlope = false;
            isInCanjump = false;
            nowSpeed = rigidbody.velocity;
            nowSpeed.y = 0.0f;
            rigidbody.velocity = nowSpeed;
            rigidbody.AddForce(transform.up * jumpForce, ForceMode.Impulse);
            Invoke(nameof(ResetJump), jumpInvoke);
        }
    }

    private void ResetJump()
    {
        isInCanjump = true;

        isExistingSlope = true;
    }
    private void CheckInGround()
    {
        isInGrounded = Physics.Raycast(transform.position, Vector3.down, out hitSlope, playerHighten, groundMask);

        slopeAngle = Vector3.Angle(Vector3.up, hitSlope.normal);
        isInSlope = (slopeAngle < maxSlopeAngle && slopeAngle != 0.0f);

    }

    private void OnDrawGizmos()
    {
        if (transform != null)
        {
            Gizmos.color = Color.yellow;
            Gizmos.DrawLine(transform.position, (transform.position - transform.up.normalized * playerHighten));

        }
    }

    private void OnGUI()
    {
        GUI.Box(new Rect(10, 10, 100, 50), new GUIContent("Speed: " + rigidbody.velocity.magnitude));
    }
}

6.3 滑铲类
独立基础类实现的滑铲类

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class ThridPersonControl : MonoBehaviour
{
    [Header("Move")]
    public PlayerMoveState moveState;
    public float moveSpeed;
    [SerializeField] private float walkSpeed = 5.0f;
    [SerializeField] private float sprintingSpeed = 8.0f;
    [SerializeField] private float crouchSpeed = 3.0f;
    [SerializeField] private float rotateSpeed = 5.0f;
    [SerializeField] private float playerRotationMinAngle = -40.0f;
    [SerializeField] private float playerRotationMaxAngle = 30.0f;
    [SerializeField] private float diffenrentSpeed = 5.0f;
    private float desireSpeed = 0.0f;
    private float playerRotationX;
    private float playerRotationY;
    private Vector3 nowSpeed;

    public enum PlayerMoveState
    {
        Walk,
        sprinting,
        air,
        crouch,
        sliding,
        wallRunning
    }

    public enum CameraType
    {
        Fisrt,
        Second,
        Third
    }


    [Header("Jump")]
    [SerializeField] private float playerHighten = 0.2f;
    [SerializeField] private float jumpForce = 5.0f;
    [SerializeField] private float jumpInvoke = 1.0f;
    [SerializeField] private float airDrag = 0.0f;
    [SerializeField] private float mulAirForce = 1.0f;

    [Header("Ground")]
    [SerializeField] private float groundDrag = 2.0f;
    [SerializeField] private LayerMask groundMask;

    private bool isInCanjump = true;
    public bool isInGrounded;
    [Header("Slope")]
    [SerializeField] private float maxSlopeAngle = 40.0f;
    float slopeAngle = 0.0f;
    private RaycastHit hitSlope;
    public bool isInSlope = false;
    private bool isExistingSlope;

    [Header("Sliding")]
    [SerializeField] private float sliderMaxSpeed = 25.0f;
    [SerializeField] private float sliderSpeed = 9;
    private Sliding sliding;
    public bool isContriasSlidingSpeed = true;
    public bool isSliding = false;
    public bool isCanSliding = true;

    [Header("Wall Running")]
    public float wallRunningSpeed = 5.0f;
    public bool isWallRunning = false;

    [Space(10)]
    [Header("Camera Orientation")]
    public Transform cameraPosition;
    public Transform cameraOrientation;
    [SerializeField] private float SensX = 400.0f;
    [SerializeField] private float SensY = 400.0f;
    [Header("Camera Follow")]
    [SerializeField] private CameraType cameraType = CameraType.Fisrt;
    [SerializeField] private float right = 0.0f;
    [SerializeField] private float distanse = 1.5f;
    [SerializeField] private float highten = 1.8f;
    [SerializeField] private float lookAngle = 10.0f;
    [SerializeField] private float cameraMinAngle = -40.0f;
    [SerializeField] private float cameraMaxAngle = 30.0f;


    private new Rigidbody rigidbody;
    private Transform transform;

    private float cameraRotationAngelX = 0.0f;
    private float cameraRotationAngelY = 0.0f;

    private Quaternion towrdsQuaternion;
    // Start is called before the first frame update
    void Start()
    {
        if (TryGetComponent<Transform>(out transform))
        {
            towrdsQuaternion = transform.localRotation;
        }

        if (TryGetComponent<Rigidbody>(out rigidbody))
        {
            rigidbody.freezeRotation = true;
        }

        sliding = GetComponent<Sliding>();

    }

    private void MoveStateChange(bool isJump = false, bool isSprinting = false, bool isCrouch = false)
    {
        rigidbody.drag = (isInGrounded ? groundDrag : airDrag);

        rigidbody.useGravity = !isInSlope;
        if (isInGrounded)
        {
            if (isWallRunning)
            {
                desireSpeed = wallRunningSpeed;
                moveState = PlayerMoveState.wallRunning;
            }
            if (isSprinting)
            {
                desireSpeed = sprintingSpeed;
                moveState = PlayerMoveState.sprinting;
            }

            if (isJump && isInCanjump)
            {
                //moveSpeed = sprintingSpeed;
                moveState = PlayerMoveState.air;
            }

            if (isCrouch)
            {
                if (rigidbody.velocity.magnitude >= sprintingSpeed && isCanSliding)
                {
                    desireSpeed = sliderSpeed;
                    moveState = PlayerMoveState.sliding;
                }
                else
                {
                    desireSpeed = crouchSpeed;
                    moveState = PlayerMoveState.crouch;
                }
            }
            if (!isSprinting && !isCrouch && !isWallRunning)
            {
                desireSpeed = walkSpeed;
                moveState = PlayerMoveState.Walk;
            }

            if (Mathf.Abs(moveSpeed - desireSpeed) >= diffenrentSpeed)
            {
                StopAllCoroutines();
                StartCoroutine(SmoothMoveSpeed());

            }
            else
            {
                moveSpeed = desireSpeed;
            }

        }

    }

    IEnumerator SmoothMoveSpeed()
    {

        float diff = Math.Abs(desireSpeed - moveSpeed);
        float timer = 0.0f;
        float startSpeed = moveSpeed;

        while (diff >= timer)
        {
            moveSpeed = Mathf.Lerp(startSpeed, desireSpeed, timer / diff);
            timer += Time.deltaTime;
            yield return null;
        }
    }
    public void Move(Vector3 moveAmount, bool isJump = false, bool isSprinting = false, bool isCrouch = false)
    {
        CheckInGround();
        MoveStateChange(isJump, isSprinting, isCrouch);
        MovePlayerAndCamera(moveAmount);

        isContriasSlidingSpeed = !sliding.CheckSlidingDown(moveAmount, hitSlope);
        SlidingMove(moveAmount, isCrouch);

        if (isJump)
        {
            sliding.StopSliding();
            Jump();
        }
    }




    public void Rotate(float RotationX, float RotationY = 0.0f)
    {
        playerRotationX += -RotationY * SensY * Time.deltaTime;
        playerRotationY += RotationX * SensX * Time.deltaTime;
        playerRotationX = Mathf.Clamp(playerRotationX, playerRotationMinAngle, playerRotationMaxAngle);
        if (transform != null)
        {
            transform.localRotation = Quaternion.Euler(0.0f, playerRotationY, 0.0f);
        }

        cameraRotationAngelY = playerRotationY;
        cameraRotationAngelX = playerRotationX;
        if (cameraOrientation != null)
        {
            cameraOrientation.localRotation = Quaternion.Euler(cameraRotationAngelX, cameraRotationAngelY, 0.0f);
        }

        if (cameraOrientation != null)
        {
            cameraOrientation.Rotate(lookAngle, 0.0f, 0.0f);
        }

        //camera
        /*cameraRotationAngelX = -RotationY * SensY * Time.deltaTime;
        cameraRotationAngelY = RotationX * SensX * Time.deltaTime;

        cameraRotationAngelX = Mathf.Clamp(cameraRotationAngelX, cameraMinAngle, cameraMaxAngle);
*/


    }


    private void SlidingMove(Vector3 moveAmount, bool isCrouch)
    {
        if (sliding != null)
        {
            if (isCrouch && rigidbody.velocity.magnitude >= sprintingSpeed && isCanSliding)
            {
                sliding.StartSliding();
            }
            else
            {
                sliding.StopSliding();
            }

            if (isSliding)
            {
                sliding.SlidingMove(moveAmount, hitSlope);
            }
        }
    }
    private void MovePlayerAndCamera(Vector3 moveAmount)
    {
        if (transform != null && rigidbody != null)
        {
            moveAmount = transform.TransformDirection(moveAmount);


            if (isInSlope)
            {
                //bad 
                //rigidbody.AddForce(-Physics.gravity , ForceMode.Force);
            }

            if (isInGrounded)
            {

                rigidbody.AddForce(GetProjectDiretion(moveAmount).normalized * moveSpeed * 10.0f, ForceMode.Force);
            }
            else if (!isInGrounded)
            {
                rigidbody.AddForce(moveAmount.normalized * moveSpeed * mulAirForce * 10.0f, ForceMode.Force);
            }



            //contrais speed do not over move speeds

            ControlSpeed();


        }



        if (cameraPosition != null)
        {
            var v = cameraPosition.localPosition;
            switch (cameraType)
            {
                case CameraType.Fisrt:
                    v.Set(0.0f, highten / 1.5f, distanse / 4);
                    break;
                case CameraType.Second:
                    break;
                case CameraType.Third:
                    v.Set(right, highten, -distanse);
                    break;
            }
            cameraPosition.localPosition = v;
        }
    }

    public Vector3 GetProjectDiretion(Vector3 direction)
    {
        if (isInGrounded)
        {
            direction = Vector3.ProjectOnPlane(direction, hitSlope.normal);
        }

        return direction;
    }
    void ControlSpeed()
    {
        nowSpeed = rigidbody.velocity;

        float contriasSpeed = moveSpeed;


        if (isSliding && !isContriasSlidingSpeed)
        {
            contriasSpeed = sliderMaxSpeed;
        }

        if (nowSpeed.sqrMagnitude >= contriasSpeed * contriasSpeed)
        {
            if (isInSlope && isExistingSlope)
            {
                nowSpeed.Normalize();
                nowSpeed *= moveSpeed;
            }
            else
            {
                nowSpeed.y = 0.0f;
                nowSpeed.Normalize();
                nowSpeed *= moveSpeed;
                nowSpeed.y = rigidbody.velocity.y;
            }
            rigidbody.velocity = nowSpeed;

        }
    }

    void Jump()
    {

        if ((isInGrounded || isWallRunning) && isInCanjump)
        {
            isExistingSlope = false;
            isInCanjump = false;
            nowSpeed = rigidbody.velocity;
            nowSpeed.y = 0.0f;
            rigidbody.velocity = nowSpeed;
            rigidbody.AddForce(transform.up * jumpForce, ForceMode.Impulse);
            Invoke(nameof(ResetJump), jumpInvoke);
        }
    }

    private void ResetJump()
    {
        isInCanjump = true;

        isExistingSlope = true;
    }
    private void CheckInGround()
    {
        isInGrounded = Physics.Raycast(transform.position, Vector3.down, out hitSlope, playerHighten, groundMask);

        slopeAngle = Vector3.Angle(Vector3.up, hitSlope.normal);
        isInSlope = (slopeAngle < maxSlopeAngle && slopeAngle != 0.0f);

    }

    private void OnDrawGizmos()
    {
        if (transform != null)
        {
            Gizmos.color = Color.yellow;
            Gizmos.DrawLine(transform.position, (transform.position - transform.up.normalized * playerHighten));

        }
    }

    private void OnGUI()
    {
        GUI.Box(new Rect(10, 10, 100, 50), new GUIContent("Speed: " + rigidbody.velocity.magnitude));
    }
}

6.4 其他动作等
太多了就不放了

7.动画

7.1 原理(未实现

后面再研究其中的原理,暂时用不到

7.2 动画切换类

管理动画的状态机,简单的使用了BlendTree,给人物做动画切换(但动作就找几个基础的

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


[RequireComponent(typeof(ThridPersonControl))]
public class AnimationChange : MonoBehaviour
{
    [Header("Param Name")]
    public string move = "IsMove";
    public string speed = "Speed";
    public string jump = "IsJump";
    [Header("Control")]
    [SerializeField] private float dampTime = 0.2f;

    private Animator animator;
    private ThridPersonControl personControl;



    private int moveKey;
    private int speedKey;
    private int jumpKey;

    private void Start()
    {
        personControl = GetComponent<ThridPersonControl>();
        animator = GetComponentInChildren<Animator>();

        InitAnimtorParam();
    }

    private void Update()
    {
        if (personControl == null || animator == null)
        {
            return;
        }

        if (animator)
        {
            AnimationStateChange();
        }

    }

    void InitAnimtorParam()
    {
        moveKey = Animator.StringToHash(move);
        speedKey = Animator.StringToHash(speed);
        jumpKey = Animator.StringToHash(jump);
    }

    void AnimationStateChange()
    {
        animator.SetFloat(speed, personControl.moveSpeed, dampTime, Time.deltaTime);

        animator.SetBool(jumpKey, false);
        animator.SetBool(moveKey, false);

        switch (personControl.moveState)
        {
            case ThridPersonControl.PlayerMoveState.Idel:
                break;
            case ThridPersonControl.PlayerMoveState.Walk:
            case ThridPersonControl.PlayerMoveState.sprinting:
                break;
            case ThridPersonControl.PlayerMoveState.air:
                animator.SetBool(jumpKey, true);
                break;
            case ThridPersonControl.PlayerMoveState.crouch:
                break;
            case ThridPersonControl.PlayerMoveState.sliding:
                break;
            case ThridPersonControl.PlayerMoveState.wallRunning:
                break;
            default:
                break;
        }


    }

}

7.UI

7.1 UI框架

管理每个场景中的UI元素,自动加载和销毁

7.1.1.UIType
简单存储文件信息

public struct UIInfo
{
    public string name;
    public string path;

    public UIInfo(string name, string path)
    {
        this.name = name;
        this.path = path;
    }
}

7.1.2 UIBasePanel
对UI示例化


using UnityEngine;

namespace wManager
{
    public abstract class UIBase
    {
        public abstract void OnStart();
        public abstract void OnEnble();

        public abstract void OnDisable();

        public abstract void OnDestroy();
    }

    public class UIBasePanel : UIBase
    {
        public UIInfo info;

        public GameObject activeObject;
        public UIBasePanel(UIInfo info)
        {
            this.info = info;
        }

        public UIBasePanel(string name, string path)
        {
            info.name = name;
            info.path = path;
        }

        public void EnableInteractable(bool enable)
        {
            if (activeObject == null)
            {
                var cavans = activeObject.GetComponent<CanvasGroup>();
                if (cavans == null)
                {
                    cavans = activeObject.AddComponent<CanvasGroup>();
                }

                cavans.interactable = enable;
            }
        }
        public override void OnDestroy()
        {
            if (activeObject != null)
            {
                Object.Destroy(activeObject);
            }
        }

        public override void OnDisable()
        {
            if (activeObject != null)
            {
                activeObject.SetActive(false);
            }

        }

        public override void OnEnble()
        {
            if (activeObject != null)
            {
                activeObject.SetActive(true);
            }
        }

        public override void OnStart()
        {
            if (activeObject != null)
            {
                activeObject.SetActive(true);
            }
        }
    }
}

7.1.3 UIManager
和SenceManager一起对场景中UI进行管理

using System.Collections.Generic;
using UnityEngine;
using Unity;
using UnityEngine.UI;
using System.Xml.Serialization;
using Unity.VisualScripting;

namespace wManager
{
    public class UIManager
    {
        private Canvas canvas;
        private Stack<UIBasePanel> panels = null;
        private Dictionary<int, UIBasePanel> panelMap;
        public UIManager()
        {
            panels = new Stack<UIBasePanel>();
            panelMap = new Dictionary<int, UIBasePanel>();
        }

        public void SetCanvas(Canvas canvas)
        {
            this.canvas = canvas;
        }

        public void AddPanel(UIBasePanel uIBase)
        {
            if (panels.Count == 0)
            {

            }
            else if (panels.Peek().info.name == uIBase.info.name)
            {
                return;
            }
            else
            {
                panels.Peek().OnDisable();
            }

            int key = uIBase.info.name.GetHashCode();
            if (!panelMap.ContainsKey(key))
            {
                uIBase.activeObject = GetInstantiate(uIBase.info);
                panelMap.Add(key, uIBase);
            }


            panels.Push(panelMap[key]);
            panelMap[key].OnStart();

        }

        public void DestroyAllPanel()
        {
            while (panels.Count > 0)
            {
                panels.Peek().OnDestroy();
                panels.Pop();
            }

            GameObject.Destroy(canvas);
        }


        //not really destroy
        public void PopPanel()
        {
            if (panels.Count == 0)
            {
                return;
            }

            panels.Peek().OnDisable();
            panels.Pop();

            if (panels.Count != 0)
            {
                panels.Peek().OnEnble();
            }

        }

        public void PopAllPanel()
        {
            while (panels.Count > 0)
            {
                panels.Peek().OnDisable();
                panels.Pop();
            }
        }
        private GameObject GetInstantiate(UIInfo info)
        {
            GetCanves();

            var gameObject = LoadResouce<GameObject>(info.path);
            if (gameObject == null)
            {
                return null;
            }

            return Object.Instantiate(gameObject, canvas.transform);
        }

        private void GetCanves()
        {
            if (canvas != null)
            {
                return;
            }

            canvas = Object.FindObjectOfType<Canvas>();

            if (canvas == null)
            {
                var gm = new GameObject();
                gm.name = "Canvas";
                gm.AddComponent<Canvas>();
                canvas = gm.GetComponent<Canvas>();
                canvas.renderMode = RenderMode.ScreenSpaceOverlay;
            }

        }
        //fixed AB package
        private static T LoadResouce<T>(string path) where T : Object
        {
            return (T)Resources.Load<T>(path);
        }

    }
}

8.Input框架

为不同的场景提供不同的controller,而不会互相影响

8.1.playerAction
提供角色枚举,后面改从为从配置文件中加载


public enum PalyerAction
{
    //motion
    MoveForward,
    MoveBackward,
    Fire
    
}

public enum StoreAction
{
    //store
    SelectUp,
    SelectDwon
}

8.2.InputType
抽象不同的输入,如Axis和key等

using Unity.VisualScripting;
using UnityEngine;

namespace wManager
{
    interface IKeyInput
    {
        int key { get; set; }
        public bool IsKeyUp();
        public bool IsKeyDown();
        public bool IsKeyPress();
    }

    public class KeyCodeInput : IKeyInput
    {
        KeyCode keyCode;
        public KeyCodeInput(UnityEngine.KeyCode code)
        {
            keyCode = code;
        }

        public int key { get => (int)keyCode; set => keyCode = (UnityEngine.KeyCode)value; }

        public bool IsKeyDown()
        {
            return Input.GetKeyDown(keyCode);
        }

        public bool IsKeyPress()
        {
            return Input.GetKey(keyCode);
        }

        public bool IsKeyUp()
        {
            return Input.GetKeyUp(keyCode);
        }
    }

    public class MouseKeyInput : IKeyInput
    {
        MouseButton mouseButton;
        public MouseKeyInput(MouseButton code)
        {
            mouseButton = code;
        }

        public int key { get => (int)mouseButton; set => mouseButton = (MouseButton)value; }

        public bool IsKeyDown()
        {
            return Input.GetMouseButtonDown(key);
        }

        public bool IsKeyPress()
        {
            return Input.GetMouseButton(key);
        }

        public bool IsKeyUp()
        {
            return Input.GetMouseButtonUp(key);
        }
    }

    interface IAxisInput
    {
        public string axisName { get; set; }
        public float delta();
    }

    public class AxisInputRaw : IAxisInput
    {
        string name;
        public AxisInputRaw(string s)
        {
            name = s;
        }

        public string axisName { get => name; set => name = value; }

        public float delta()
        {
            return Input.GetAxisRaw(axisName);
        }
    }

    public class MousePosition
    { 
        public Vector2 mousePostion { get => Input.mousePosition; }
    }


}

8.3.ControllerBase
角色所有的输入

using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

namespace wManager
{

    interface IInputMothed<T>
    {
        public float Delta(T state);
        public Vector2 Positon(T state);
        public bool IsKeyUp(T state);
        public bool IsKeyDown(T state);
        public bool IsKeyPress(T state);
    }

    public class ControllerBase<T> : IInputMothed<T> where T : System.Enum
    {
        Dictionary<T, IKeyInput> keyInputs = new Dictionary<T, IKeyInput>();
        Dictionary<T, IAxisInput> axisInputs = new Dictionary<T, IAxisInput>();
        Dictionary<T, MousePosition> positonInputs = new Dictionary<T, MousePosition>();



        public void AddInput(T state, KeyCode key)
        {
            if (keyInputs.ContainsKey(state))
            {
                if (keyInputs[state].key != (int)key)
                {
                    Debug.LogWarning("Mult Decalre Key Code For: " + state.ToString());

                    keyInputs[state].key = (int)key;
                }
            }
            else
            {
                keyInputs.Add(state, new KeyCodeInput(key));
            }
        }
        public void AddInput(T state, MouseButton key)
        {
            if (keyInputs.ContainsKey(state))
            {
                if (keyInputs[state].key != (int)key)
                {
                    Debug.LogWarning("Mult Decalre Key Code For: " + state.ToString());

                    keyInputs[state].key = (int)key;
                }
            }
            else
            {
                keyInputs.Add(state, new MouseKeyInput(key));
            }

        }
        public void AddInput(T state, string key)
        {
            if (axisInputs.ContainsKey(state))
            {
                if (axisInputs[state].axisName != key)
                {
                    Debug.LogWarning("Mult Decalre Key Code For: " + state.ToString());

                    axisInputs[state].axisName = key;
                }
            }
            else
            {
                axisInputs.Add(state, new AxisInputRaw(key));
            }
        }

        public void AddInput(T state)
        {
            if (axisInputs.ContainsKey(state))
            {
                return;
            }
            else
            {
                positonInputs.Add(state, new MousePosition());
            }
        }

        public float Delta(T state)
        {
            if (!axisInputs.ContainsKey(state))
            {
                return 0.0f;
            }

            return axisInputs[state].delta();
        }

        public Vector2 Positon(T state)
        {
            if (!positonInputs.ContainsKey(state))
            {
                return Vector2.zero;
            }

            return positonInputs[state].mousePostion;
        }

        public bool IsKeyDown(T state)
        {
            if (!keyInputs.ContainsKey(state))
            {
                return false;
            }

            return keyInputs[state].IsKeyDown();
        }

        public bool IsKeyPress(T state)
        {
            if (!keyInputs.ContainsKey(state))
            {
                return false;
            }

            return keyInputs[state].IsKeyPress();
        }

        public bool IsKeyUp(T state)
        {
            if (!keyInputs.ContainsKey(state))
            {
                return false;
            }

            return keyInputs[state].IsKeyUp();
        }


    }

   


}

8.4.ControllerBlock
阻断角色的某一行为的输入

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace wManager
{
    public class ControllerBlock<T> where T : System.Enum
    {
        ControllerBase<T> inputBase = null;

        public Func<T, bool> IsKeyPress;
        public Func<T, bool> IsKeyDown;
        public Func<T, bool> IsKeyUp;
        public Func<T, float> Delta;
        public Func<T, Vector2> Position;

        public ControllerBlock(ControllerBase<T> controller)
        {
            inputBase = controller;
        }

        public ControllerBlock()
        {
        }

        bool BlockIsKePress(T s) => false;
        bool BlockIsKeyDown(T s) => false;
        bool BlockIsKeyUp(T s) => false;
        float BlockDelta(T s) => 0.0f;
        Vector2 BlockPosition(T s) => Vector2.zero;

        public void SetEnanle(bool isEnable)
        {
            if (isEnable)
            {
                IsKeyPress = inputBase.IsKeyPress; 
                IsKeyDown = inputBase.IsKeyDown; 
                IsKeyUp = inputBase.IsKeyUp;
                Delta = inputBase.Delta;
                Position = inputBase.Positon;
            }
            else 
            {
                IsKeyPress = BlockIsKePress;
                IsKeyDown = BlockIsKeyDown;
                IsKeyUp = BlockIsKeyUp;
                Delta = BlockDelta;
                Position = BlockPosition;
            }
        }
    }
}

8.5.Controller
角色不同场景的controller

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace wManager
{
    //可以单一枚举,也可以多个
    using PlayerState = PalyerAction;
    using StoreState = StoreAction;

    public class Controller<T> where T : System.Enum
    {
        ControllerBlock<T> block;

        public Controller(ControllerBlock<T> block)
        {
            this.block = block;
            this.block.SetEnanle(false);
        }

        public void SetEnable(bool enable) => block.SetEnanle(enable);

        public bool IsKeyPress(T state) => block.IsKeyPress(state);
        public bool IsKeyDown(T state) => block.IsKeyDown(state);
        public bool IsKeyUp(T state) => block.IsKeyUp(state);
        public float Delta(T state) => block.Delta(state);
        public Vector2 Position(T state) => block.Position(state);

    }

    public class PlayerController : Controller<PlayerState>
    {
        public PlayerController(ControllerBase<PlayerState> input) : base(new ControllerBlock<PlayerState>(input))
        {
        }

        //如果单一枚举提供接口

    }

    public class StoreController : Controller<StoreState>
    {
        public StoreController(ControllerBase<StoreState> input) : base(new ControllerBlock<StoreState>(input))
        {
        }
    }
}

8.6.ControllerManager
单例管理和向外提供所有controller

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace wManager
{

    using PlayerState = PalyerAction;
    using StoreState = StoreAction;
    public class ControllerManager
    {
        ControllerBase<PlayerState> playerInput;
        ControllerBase<StoreState> storeInput;
        //init tow controller
        public PlayerController playerController;
        public StoreController storeController;
        public ControllerManager()
        {
            playerInput = new ControllerBase<PlayerState>();
            playerInput.AddInput(PlayerState.MoveForward, KeyCode.W);
            playerInput.AddInput(PlayerState.MoveBackward, KeyCode.S);
            playerInput.AddInput(PlayerState.Fire);
            playerController = new PlayerController(playerInput);


            storeInput = new ControllerBase<StoreState>();
            storeInput.AddInput(StoreState.SelectUp, KeyCode.W);
            storeInput.AddInput(StoreState.SelectDwon, KeyCode.S);
            storeController = new StoreController(storeInput);
        }

       
    }
}

9. AI行为树

9.1 ref
[1.] AI 行为树的工作原理
[2.] 游戏AI - 行为树Part1:简介
[3.] 行为树的基本概念及进阶
[4.] 游戏AI与行为树——游戏关卡、战斗实现逻辑知识点整理(1)
[5.]【AI】行为树学习
[6.] 游戏AI - 行为树框架
[7.] 行为树(Behavior Tree)实践
[8.] Unity+Lua搭建一个简易的基于事件驱动(Event-Driven)的AI行为树 Part1:事件驱动行为树简述

9.2 code

10.Custom SPR

11. AB包管理调

Utility

一些工具类

1.单例


namespace wManager
{
    public class Singleton<T> where T : new()
    {
        private static readonly T instance = new T();

        public static T Instance
        {
            get { return instance; }
        }

    }
}

ECS Jobs Burst

4.13【代码生成器出问题了】
6.06 unity ecs正式发布了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值