游戏实现流程
游戏实现流程
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.实现效果
有点奇怪, 后面看了论文重新写一遍。还有就是为什么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.效果
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正式发布了