using UnityEngine;
using UnityEditor;
using System.Reflection;

/// <summary>
/// Unity 自带的 GUIStyle 效果查看器
/// </summary>
public class GUIStyleViewerUtil : EditorWindow
{
    /// <summary>
    /// 滑动条
    /// </summary>
    private Vector2 mScrollView = Vector2.zero;
    /// <summary>
    /// 寻找的样式名称
    /// </summary>
    private string mSearchStyle = "";
    /// <summary>
    /// 样式缓存
    /// </summary>
    private static GUIStyle mBtnStyle;
  [MenuItem("Util/GUIStyle 效果查看器")]
  private static void MenuClicked()
  {
      GetWindow<GUIStyleViewerUtil>("GUIStyle查看器", true);
      // 初始化一个按钮的样式
      mBtnStyle = GetBtnStyle();
  }

  private void OnGUI()
  {
      GUILayout.Space(20);

      // 设置一个查找的功能
      GUILayout.BeginHorizontal("HelpBox");
      GUILayout.Space(20);
      mSearchStyle = EditorGUILayout.TextField("", mSearchStyle, "SearchTextField");
      GUILayout.Label("", "SearchCancelButtonEmpty");
      GUILayout.EndHorizontal();

      // 设置滚动栏
      mScrollView = GUILayout.BeginScrollView(mScrollView);
      // 遍历并显示所有符合条件的 Unity 自带的 GUIStyle
      foreach (GUIStyle style in GUI.skin.customStyles)
      {
          if (style.name.ToLower().Contains(mSearchStyle.ToLower()))
          {
              GUILayout.Space(10);
              // 为了让显示的布局看着更舒服一些,需要以当前 style 为原型新建一个 GUIStyle
              // 因为接下来可能会对文字居中等方面进行调整,
              // 而如果直接更改原来的 style,则会导致系统中的对应 UI 样式和布局也发生改变
              GUIStyle tmp = new GUIStyle(style);
              DrawStyleItem(tmp);
          }
      }
      GUILayout.EndScrollView();
  }

  /// <summary>
  /// 绘制对应的 GUIStyle
  /// </summary>
  /// <param name="style"></param>
  private void DrawStyleItem(GUIStyle style)
  {

      GUILayout.BeginHorizontal("box", GUILayout.Height(style.fixedHeight));
      GUILayout.Space(20);
      // 获取按钮样式
      GUIStyle tmp = mBtnStyle;

      // 设置第一个按钮,点击后可以复制当前 GUIStyle 的名字信息
      if (GUILayout.Button("<color=#000000FF>复制到剪贴板</color>", tmp))
      {
          GUIUtility.systemCopyBuffer = style.name;
      }

      GUILayout.Space(20);

      // 设置第二个按钮,点击后在控制台输出该 GUIStyle 的设置参数
      if (GUILayout.Button("<color=#FFFFFFFF>查看Style参数</color>", tmp))
      {
          ShowGUIStyleMsg(style.name);
      }
      GUILayout.Space(30);

      // 显示当前 GUIStyle 的名字
      EditorGUILayout.SelectableLabel(style.name);

      // 设置左右对称布局,以此为分界线,把前后的布局对称
      GUILayout.FlexibleSpace();

      // 设置文本居中显示
      style.alignment = TextAnchor.MiddleCenter;
      // 显示当前的样式及其名称
      EditorGUILayout.SelectableLabel(style.name, style);

      GUILayout.Space(100);

      // 单独显示样式
      EditorGUILayout.SelectableLabel("", style,
                                      GUILayout.Height(style.fixedHeight),
                                       GUILayout.Width(style.fixedWidth));
      GUILayout.Space(100);

      GUILayout.EndHorizontal();
  }

  /// <summary>
  /// 创建一个按钮样式,用来作为本界面中的按钮基础
  /// </summary>
  /// <returns></returns>
  public static GUIStyle GetBtnStyle()
  {
      // 使用系统自带的 "AC Button" 样式作为原型,因为 "AC Button" 本身就具有点击效果了
      GUIStyle style = new GUIStyle("AC Button");

      // 将样式的点击时的效果图,替换为系统自带的 "flow node 4 on" 样式
      GUIStyle active = new GUIStyle("flow node 4 on");
      style.active.background = active.normal.background;

      // 将平时按钮的效果图,替换为系统自带的 "flow node 4" 样式
      GUIStyle normal = new GUIStyle("flow node 4");
      style.normal.background = normal.normal.background;

      // 参考 "flow node 4" 的内部参数,设置一些图片的参数。
      // 如果不设置的话,则图片会按照原 "AC Button" 的样式进行显示
      // 因为图片不一样,所以会有显示上的问题。
      style.stretchHeight = true;
      style.stretchWidth = true;
      style.border = new RectOffset(11, 11, 11, 15);
      style.margin = new RectOffset(0, 0, 0, 0);
      style.padding = new RectOffset(0, 0, 0, 0);
      style.overflow = new RectOffset(7, 7, 6, 9);

      // 进行文本方面的设置
      style.alignment = TextAnchor.MiddleCenter;
      style.fontSize = 18;
      style.richText = true;

      return style;
  }

  /// <summary>
  /// 展示指定的 GUI 样式的一些数值
  /// </summary>
  /// <param name="style"></param>
  public void ShowGUIStyleMsg(string style)
  {
      // 清空控制台信息,防止混淆
      ClearConsole();

      GUIStyle tmp = new GUIStyle(style);
      Debug.Log("Style Name: " + tmp.name);
      Debug.Log("Style Normal: " + tmp.normal.background);
      Debug.Log("Style Normal Color: " + tmp.normal.textColor);
      Debug.Log("Style Active: " + tmp.active.background);
      Debug.Log("Style Active Color: " + tmp.active.textColor);
      Debug.Log("Style Hover: " + tmp.hover.background);
      Debug.Log("Style Hover Color: " + tmp.hover.textColor);
      Debug.Log("Style Focused: " + tmp.focused.background);
      Debug.Log("Style Focused Color: " + tmp.focused.textColor);
      Debug.Log("Style border: " + tmp.border);
      Debug.Log("Style margin: " + tmp.margin);
      Debug.Log("Style padding: " + tmp.padding);
      Debug.Log("Style overflow: " + tmp.overflow);
      Debug.Log("Style fixedWidth: " + tmp.fixedWidth);
      Debug.Log("Style fixedHeight: " + tmp.fixedHeight);
      Debug.Log("Style stretchWidth: " + tmp.stretchWidth);
      Debug.Log("Style stretchHeight: " + tmp.stretchHeight);
      Debug.Log("Style lineHeight: " + tmp.lineHeight);
      Debug.Log("Style Font: " + tmp.font);
      Debug.Log("Style Font Size: " + tmp.fontSize);
  }

  public void ClearConsole()
  {
      Assembly assembly = Assembly.GetAssembly(typeof(SceneView));

      System.Type type = assembly.GetType("UnityEditor.LogEntries");
      MethodInfo method = type.GetMethod("Clear");
      method.Invoke(new object(), null);

  }
}

其他

在线格式转换器

模之屋

图形相关

Roystan

Shadertoy

Draw Calls

菜鸡都能学会的Unity草地shader

肥皂泡的渲染(薄膜干涉)

从零开始的卡通渲染

编辑器相关

UnityGraphView

Unity的GraphView

HybridCLR

大佬的个人网站

Yremp

Mashiro

Walkingfat

Minionsart

烟雨迷离半世殇

URP-光源与阴影

URP-后处理

Shader实验室 - 知乎 (zhihu.com)

剖析虚幻渲染体系-开篇说明 - 知乎 (zhihu.com)

虚幻4渲染编程专题概述及目录 - 知乎 (zhihu.com)

unity urp shader入门 - 知乎 (zhihu.com)

UnityURPShader讲解-已完结 - 知乎 (zhihu.com)

由浅入深学习PBR的原理和实现 - 0向往0 - 博客园 (cnblogs.com)

查看函数图像网站:https://graphtoy.com/

基础数学运算

  • max(a, b) 返回较大的数
  • min(a, b) 返回较小的数
  • mul(a, b) 两变量相乘,常用于矩阵运算
  • abs(a) 返回a的绝对值
  • round (x) 返回与x最近的整数
  • sqrt (x) 返回x的平方根
  • rsqrt (x) 返回x的平方根的倒数
  • degrees (x) 弧度转角度
  • redians (x) 角度转弧度
  • noise (x) 噪声

幂指对函数

  • pow (x, y) x的y次幂
  • exp (x) 返回e为底的指数函数
  • exp2 (value x) 返回以2位底,x为指数幂
  • ldexp (x, exp) 返回与2exp次方的乘积
  • log (x) 返回指定值得以e为底数的对数
  • log10 (x) 求以10为底的对数
  • log2 (x) 求以2为底的对数
  • frexp (x , out exp) 将浮点数分解成尾数和指数,x的返回值是尾数,exp参数返回的值是指数

三角函数和双曲线函数

  • sin(x)、cos(x)、tan(x) 三角函数
  • asin(x)、acos(x)、atan(x) 反三角函数
  • sincos(x,out s,out c) 返回x的正弦和余弦
  • tan(y,x) 返回y/x的正切
  • atan2(y,x) 返回y/x的反正切
  • sinh(x) 返回x的双曲正弦值
  • cosh(x) 返回x的双曲余弦值
  • tanh(x) 返回x的双曲正弦值

处理数值函数

  • ceil(x) 返回>=x的最小整数(向下取整)
  • floor(x) 返回<=x的最大整数(向上取整)
  • step(x,y) x<=y返回1,否则返回0
  • saturate(x) 返回将x钳制到[0,1]范围之间的值
  • clamp(x,min,max) 将x限制在[min,max]范围的值,比min小返回min,比max大返回max
  • fmord(x,y) 返回x对y取余的余数
  • frac(x) 取x的小数部分
  • modf(x,out ip) 将x分为小数和整数部分(输出的ip为整数部分,返回值为小数部分)
  • lerp(x,y,s) 按照s在x到y之间插值,返回
  • smoothstep(min,max,x) 如果x在[min,max]范围内,就返回介于[0,1]之间的平滑Hermite插值,使用smoothstep在两个值创建平滑过渡

条件判断函数

  • all(x) 确定指定量的所有分量是否均为非零,均非零则返回true,否则返回false
  • clip(x) 如果输入值小于零,则丢弃当前像素 常用于判定范围(不仅仅针对0,返回值为void) 常用于Alpha测试,如果每个分量代表到平面的距离,还可以用来模拟剪切平面
  • sign(x) 返回x的正负性 如果x小于零返回-1,如果x等于零返回0,如果x大于零返回1
  • isinf(x) 如果x参数为+ INF或-INF(无穷+无穷仍无穷,0x3f3f3f3f),返回true,否则返回False
  • isfinite(x) 判断x参数是有限,即有界的,与isinf(x)相反
  • isnan(x) 如果x参数为NAN(非数字),返回true,否则返回false

向量和矩阵函数

  • length(v) 返回向量的长度
  • normalize(v) 向量归一化
  • distance(a,b) 返回两个向量之间的距离,不平行的两个向量应该为0,此处表示为根号下各分量之差的平方和
  • dot(a,b) 返回a和b两向量的点积
  • cross(a,b) 返回a和b两向量的叉积,返回值是向量,并且与a,b都垂直
  • determinant(m) 返回指定浮点矩阵的按行列式方式计算的值
  • transpose(m) 返回矩阵m的转置矩阵

光线预算函数

  • reflect(i,n) 以i为入射向量n为法线方向的反射光
  • refract(i,n,ri) 以i为入射向量n为法线方向,ri为折射率的折射光
  • lit(n_dot_l,n_dot_h,m) 输入标量(normal,light,半角向量h,镜面反射系数m) 返回光照向量(环境光,漫反射光,镜面高光反射,1)该计算依据的是BlingPhong光照模型
  • faceforward(n,i,ng) 得到面向视图方向的曲面法向量输入输出为同元向量,返回-n*sign(dot(i,ng))(normal,light,normal)

纹理查找函数

1D纹理查找函数

  • tex1D(s, t) 普通一维纹理查找 返回纹理采样器s在标量t位置的color4
  • tex1D(s,t,ddx,ddy) 使用微分查询一维纹理, t和ddxy均为vector
  • tex1Dlod(s, t) 使用LOD查找纹理s在t.w位置的color4
  • tex1Dbias(s, t) 将t.w决定的某个MIP层偏置后的一维纹理查找
  • tex1Dgrad(s,t,ddx,ddy) 使用微分并指定MIP层的一维纹理查找
  • tex1Dproj(s, t) 把纹理当做一张幻灯片投影到场景中,先使用投影纹理技术需要计算出投影纹理坐标t(坐标t.w除以透视值),然后使用投影纹理坐标进行查询

2D纹理查找函数

  • tex2D(s, t) 普通二维纹理查找 返回纹理采样器s在vector t位置的颜色
  • ex2D(s,t,ddx,ddy) 使用微分查询二维纹理,t和ddxy均为vector
  • tex2Dlod(s, t) 使用LOD查找纹理s在t.w位置的color4
  • tex2Dbias(s, t) 将t.w决定的某个MIP层偏置后的二维纹理查找
  • tex2Dgrad(s,t,ddx,ddy) 使用微分并指定MIP层的二维纹理查找
  • tex2Dproj(s, t) 把纹理当做一张幻灯片投影到场景中,先使用投影纹理技术需要计算出投影纹理坐标t(坐标t.w除以透视值),然后使用投影纹理坐标进行查询

3D纹理查找函数

  • tex3D(s, t) 普通三维纹理查找 返回纹理采样器s在vector t位置的颜色
  • ex3D(s,t,ddx,ddy) 使用微分查询三维纹理,t和ddxy均为vector
  • tex3Dlod(s, t) 使用LOD查找纹理s在t.w位置的color4
  • tex3Dbias(s, t) 将t.w决定的某个MIP层偏置后的三维纹理查找
  • tex3Dgrad(s,t,ddx,ddy) 使用微分并指定MIP层的三维纹理查找
  • tex3Dproj(s, t) 把纹理当做一张幻灯片投影到场景中,先使用投影纹理技术需要计算出投影纹理坐标t(坐标t.w除以透视值),然后使用投影纹理坐标进行查询

立体纹理查找

  • texCUBE(s,t) 返回纹理采样器s在vector t位置的颜色
  • texCUBE(s,t,ddx,ddy) 使用微分查询立方体维纹理 ,t和ddxy均为vector
  • texCUBEDload(s,t) 使用LOD查找纹理s在t.w位置的color4
  • texCUBEbias(s,t) 将t.w决定的某个MIP层偏置后的立方体纹理查找
  • texCUBEgrad(s,t,ddx,ddy) 使用微分并指定MIP层的立方体纹理查找
  • texCUBEproj(s,t) 使用投影方式的立方体纹理查找

偏导函数ddx和ddy

ddx(vector3)、ddy(vector3) ddx 和 ddy 用于求取相邻像素间某属性的差值

img

Core.hlsl

名称 说明
GetVertexPositionInputs(float3 positionOS) 获取输入顶点坐标信息
GetVertexNormalInputs(float3 normalOS) 获取输入顶点法线信息
GetVertexNormalInputs(float3 normalOS, float4 tangentOS) 获取输入顶点法线信息(重载)
GetScaledScreenParams() 获取屏幕缩放参数信息
NormalizeNormalPerVertex(real3 normalWS) 逐顶点法线正交
NormalizeNormalPerPixel(real3 normalWS) 逐像素法线正交
ComputeScreenPos(float4 positionCS) 计算屏幕坐标信息
(real)ComputeFogFactor(float z) 计算雾参数
(real)ComputeFogIntensity(real fogFactor) 计算雾强度
(half3)MixFogColor(real3 fragColor, real3 fogColor, real fogFactor) 混合雾颜色
(half3)MixFog(real3 fragColor, real fogFactor) 混合雾

Lighting.hlsl

名称 说明
DistanceAttenuation(float distanceSqr, half2 distanceAttenuation) 距离衰减
AngleAttenuation(half3 spotDirection, half3 lightDirection, half2 spotAttenuation) 角度衰减
GetMainLight()/GetMainLight(float4 shadowCoord) 获取主光源
GetPerObjectLightIndex(int index) 获取每个对象灯光Index
GetAdditionalLightsCount() 获取额外灯光数量
ReflectivitySpecular(half3 specular) 高光反射率
OneMinusReflectivityMetallic(half metallic) OneMinus金属反射率
InitializeBRDFData(half3 albedo, half metallic, half3 specular, half smoothness, half alpha, out BRDFData outBRDFData) 初始化BRDF
EnvironmentBRDF(BRDFData brdfData, half3 indirectDiffuse, half3 indirectSpecular, half fresnelTerm) 环境BRDF
DirectBDRF(BRDFData brdfData, half3 normalWS, half3 lightDirectionWS, half3 viewDirectionWS) BRDF
SampleLightmap(float2 lightmapUV, half3 normalWS) 光照贴图
GlossyEnvironmentReflection(half3 reflectVector, half perceptualRoughness, half occlusion) 环境光泽反射
GlobalIllumination(BRDFData brdfData, half3 bakedGI, half occlusion, half3 normalWS, half3 viewDirectionWS) 全局光照
MixRealtimeAndBakedGI(inout Light light, half3 normalWS, inout half3 bakedGI, half4 shadowMask) 实时烘培混合
LightingLambert(half3 lightColor, half3 lightDir, half3 normal) 兰伯特模型
LightingSpecular(half3 lightColor, half3 lightDir, half3 normal, half3 viewDir, half4 specular, half smoothness) 高光
LightingPhysicallyBased(BRDFData brdfData, half3 lightColor, half3 lightDirectionWS, half lightAttenuation, half3 normalWS, half3 viewDirectionWS)/LightingPhysicallyBased(BRDFData brdfData, Light light, half3 normalWS, half3 viewDirectionWS) 基于物理的光照模型
VertexLighting(float3 positionWS, half3 normalWS) 顶点光照颜色
LightweightFragmentPBR(InputData inputData, half3 albedo, half metallic, half3 specular,half smoothness, half occlusion, half3 emission, half alpha) 轻量级片元PBR
LightweightFragmentBlinnPhong(InputData inputData, half3 diffuse, half4 specularGloss, half smoothness, half3 emission, half alpha) 轻量级片元布林·冯

Shadows.hlsl

名称 说明
GetMainLightShadowStrength() 获取主光源阴影强度
GetAdditionalLightShadowStrenth(int lightIndex) 获取额外光源阴影强度
SampleScreenSpaceShadowmap(float4 shadowCoord) 屏幕空间阴影贴图
SampleShadowmap(float4 shadowCoord, TEXTURE2D_SHADOW_PARAM(ShadowMap, sampler_ShadowMap), ShadowSamplingData samplingData, half shadowStrength, bool isPerspectiveProjection = true) 阴影贴图
TransformWorldToShadowCoord(float3 positionWS) 把顶点的世界坐标转换到阴影坐标
MainLightRealtimeShadow(float4 shadowCoord) 主光源实时阴影
AdditionalLightRealtimeShadow(int lightIndex, float3 positionWS) 额外光源实时阴影
GetShadowCoord(VertexPositionInputs vertexInput) 获取阴影坐标信息
ApplyShadowBias(float3 positionWS, float3 normalWS, float3 lightDirection) 应用阴影偏移

SpaceTransforms.hlsl

名称 说明
TransformObjectToWorld(float3 positionOS) 当前模型空间转世界空间矩阵,通常用于把顶点/方向矢量从模型空间转到世界空间
TransformWorldToObject(float3 positionWS) 当前世界空间转模型空间矩阵,通常用于把顶点/方向矢量从世界空间转到模型空间
TransformWorldToView(float3 positionWS) 当前世界空间转相机空间矩阵,通常用于把顶点/方向矢量从世界空间转到相机空间
TransformObjectToHClip(float3 positionOS) 当前模型空间转裁剪空间矩阵,通常用于把顶点/方向矢量从模型空间转到裁剪空间
TransformWorldToHClip(float3 positionWS) 当前世界空间转裁剪空间矩阵,通常用于把顶点/方向矢量从世界空间转到裁剪空间
TransformWViewToHClip(float3 positionVS) 当前相机空间转裁剪空间矩阵,通常用于把顶点/方向矢量从相机空间转到裁剪空间
TransformObjectToWorldDir(real3 dirOS) 把方向矢量从模型空间转换到世界空间中
TransformWorldToObjectDir(real3 dirWS) 把方向矢量从世界空间转换到模型空间中
TransformWorldToViewDir(real3 dirWS) 把方向矢量从世界空间转换到相机空间中
TransformWorldToHClipDir(real3 directionWS) 把方向矢量从世界空间转换到裁剪空间中
TransformObjectToWorldNormal(float3 normalOS) 把法线从模型空间转换到世界空间中
CreateTangentToWorld(real3 normal, real3 tangent, real flipSign) 创建一个切线空间转为世界空间的3x3矩阵
TransformTangentToWorld(real3 dirTS, real3x3 tangentToWorld) 当前切线空间转世界空间矩阵,通常用于把顶点/方向矢量从切线空间转到世界空间
TransformWorldToTangent(real3 dirWS, real3x3 tangentToWorld) 当前世界空间转切线空间矩阵,通常用于把顶点/方向矢量从世界空间转到切线空间
TransformTangentToObject(real3 dirTS, real3x3 tangentToWorld) 当前切线空间转模型空间矩阵,通常用于把顶点/方向矢量从切线空间转到模型空间

记录一些常用的 Build-In 到 HLSL 的函数

光照模式
Bulid - In URP
ForwardBase UniversalForward
ForwardAdd 开启关键字 _ADDTIONAL_LIGHTS
ShadowCaster ShadowCaster

HEXO使用手册

测试和搭建环境

  • hexo clean清除了你之前生成的东西,也可以不加。
  • hexo new newpage 新建一个文章
  • hexo generate 顾名思义,生成静态文章,可以用 hexo g缩写
  • hexo deploy 部署文章,可以用hexo d缩写
  • hexo new draft newpage 新建一个 newpage.md 文件
  • hexo publish draft newpage发布post

hexo史上最全搭建教程

技能系统简述

技能系统使用组件式开发,由一个SKillEntity管理所有的Component,Compoent负责实现模块的具体功能,技能分为主动技能和被动技能。

主动技能(SkillEntity)

主动技能有根据释放类型不同分为普通技能、开关技能、蓄力技能、循环技能等…,根据外部系统分为普通攻击、普通技能和大招等…有些特殊的系统会加入其他类型的技能比如QTE之类的。

SkillEntity

  • 释放条件 SkillConditionComponent
  • 技能释放 SkillBasicComponent
  • 技能配置 SkillData

SkillData

技能配置数据由三个部分和一个参数组成

  • ConfData(Excel配置,主要是一些外部系统配置如:技能描述、图标、名字等…,以及一些数值相关的配置如:技能伤害计算相关联属性)
  • EditorData(技能编辑器配置,包含技能目标选择条件、技能范围、释放条件在什么条件下释放什么效果,在什么时间释放什么技能效果播什么动作和音效,技能效果(伤害,位移,发射子物体,子弹时间、时间减速等…))
  • SkillBlock列表(一个技能分为多个SkillBlock,相当于释放技能时根据条件的不同选择释放不同的SkillBlock,释放的Effect是由SkillBlock来管理释放的。如:目标身上有id为2333的buff时释放瞬移到目标位置,没有时释放一个向前的范围伤害,这两个不同的需求算作一个Skill,两个SkillBlock,每个SkillBlock下的Effect不同)
  • EffectData列表 (技能包含的所有Effect)
  • SkillAction列表(技能动作)

img

上图配置是在范围内筛选出一个目标作为技能目标,在范围内可以释放技能,否则朝向目标移动。

SkillBlock

  • **SkillBlockData** Data
  • **List<EffectData>** effectDatas;

img

SkillBlock封装技能具体实现配置,一个完整的技能由多个SkillBlock数据组成,每个SkillBlock都是一个完整的技能表现。

  • 第一部分SkillBlock是技能中的索引配置,当前执行完之后跳转的索引。
  • 第二部分是SkillBlock执行的条件,填Condition找对应的Condition配置检测条件通过后执行。
  • 第三部分是技能打断条件,分三部分时间和打断行为类型。
  • 第四部分是技能的动作配置,动作融合。
  • 第五部分是技能效果Effect,技能产生的效果,如:造成伤害,位移,击飞,子弹时间,屏幕效果等。

Effect

技能效果,是技能直接作用于游戏逻辑的,如伤害会对目标造成伤害同时对方播受击动画特效等;位移会强制改变目标位置;击飞会对权重低目标产生击飞的效果,子弹时间会改变除目标外其他角色的时间增量;时间减速会改变TimeScale等。

  • Id,Name,Type,开始时间,持续时间,释放概率等一些基础功能。
  • 控制技能效果生效的SkillConditon。
  • 播放音效。
  • 播放特效。
  • 目标类型范围,用于筛选在范围内可被作为目标的角色。

Effect作为所有技能效果的基类,所有的技能效果都继承自Effect

img

上图是改变相机镜头缩放的技能效果。

除了Effect的基础功能外,Zoom效果实现了对相机FOV和,GroupFramingSize的改变,同时加入了淡入淡出和曲线的功能,实现了镜头缩放的效果。

SkillEntity实现细节

SkillBasicComponent

  • **SkillBlock** playingSkillBlock;// 当前正在执行的SkillBlock

技能逻辑处理组件,处理SkillBlock逻辑,当SkillEnity创建时就开始Tick增加计算技能的时间轴,当前一个SkillBlock时间完成后跳转到一下个SkillBlock。

  • **ISkillCaster** caster; // 施法者
  • **ISkillReceiver** receiver; // 被施法者

技能需要有施法者和被施法者,技能效果所有的逻辑处理都基于施法者和被施法者,ISkillCaster和ISkillReceiver中规范了处理技能相关用到的一些方法,角色属性数据如:(生命值,攻击力,防御力等…),Transform信息,角色系别阵营如:(比如金木研属于青铜树,董香属于古董),战斗阵营信息(有方角色&敌方角色等..)

计时相关

  • **FixedCounter** EffectiveTimer;// 当前的Effect
  • **FixedCounter** CoolTimer;// 技能冷却时间

FixedCounter是封装的计时器,设置最大值,在Tick中驱动,用于处理时间相关功能。常用参数有当前值,最大值,百分比值,设置增长值,是否完成计时等。

EffectiveTimer用作当前Effect的计时器

CoolTimer用作于技能冷却时间

Tick

  • TickEffective() // 技能效果
  • TickCool()// 冷却时间
  • TickAccumlateSkill()// 蓄力技
  • TickOnOffSkill()// 开关技
  • 扩展其他类型的技能…

  • 普通攻击Tick的deltaTime需要计算攻击速度

  • 蓄力技需要处理释放逻辑,具体看技能是怎么实现的,如果说蓄力是由配置来决定跳转的SkillBlock那么只需处理释放就行了,蓄满后多长时间自动释放。设计成SkillBlock跳转,就是每个SkillBlock设置一定时长,蓄力时间达到这个SkillBlock跳转下一个蓄力SkillBlock,如果在当前SkillBlock释放的话直接跳转对应技能效果的SkillBlock。如果是需要记录蓄力时长那么久需要记录当前蓄力时间,设计的时候需要定制每个SkillBlock的蓄力时长,如果达到指定蓄力时长就选择对应SkillBlock。
  • 开关技记录一下技能开关状态做不同的逻辑处理

InterruptSkill

打断技能,具体可配SkillBlock在技能的某时间段可被某种行为打断,做一个技能的衔接,一个角色的技能是否流程打断技能配置很关键。

img

上图配置中TotalTime当前技能的持续时间,xxTime表示三段打断时间,xxInterrupt 表示打断技能的行为类型

移动打断,释放大招打断,普通攻击打断,使用技能打断等几种打断方式。

SkillConditionComponent

SkillCondition

SkillCondition条件基类封装一些条件检测通用的字段方法,如:条件类型,检测区域类型,检测范围半径,目标类型等字段,返回符合条件的目标,是否达成条件等方法,编辑器用的配置需要有读写xml的接口。所有扩展的条件都继承自SkillCondition

SkillConditionComponent主要职责是管理技能配置中所有SkillCondition,检测条件,对外提供检测结果的接口。传入条件id找到对应条件检测条件并返回结果。

img

上图为检测角色身上被动技的条件。

img

使用条件输入ConditionId,同时也实现了通过表达式来同时处理多个条件。

与、或、非(& | !)

[转载自][https://www.acgloby.com/技能系统程序设计思路-主动技能/]

被动技能(PassiveSkillEntity)& 光环技能(HaloSkillEntity)

被动技能有两种,一种是被动触发的技能,一种是光环技能在范围内才会生效的技能,所有被动技能继承自光环技能。

被动技算是一种特殊的光环技,没有范围的光环技。被动技能一般用来增加属性,增加buff之类的,也可以用作标记,只有在被动技触发时会释放效果列表。

光环技能有一个固定的范围(也不一定,可能会有技能效果会改变光环大小的效果ヾ(✿゚▽゚)ノ),进入光环时触发的效果,离开光环时的触发效果。

被动技能触发器(事件系统)

被动技能选择触发条件,在Tick中检测触发器,满足时触发此被动技释放当被动技能触发的技能效果列表。

触发条件注册对应的事件(PassiveSkillEventType)在对应的系统上发送事件来触发被动技,如:当释放技能时触发被动,参数有技能id或者技能类型, 在CastSkill()释放技能时Push该事件,被动技能实时监测收到的事件满足条件触发对应的技能效果。

触发条件有生效次数、冷却时间来限制事件的触发。

img

img

触发事件后释放被动技释放时的效果列表,如果没有添加事件对应加上该被动技就会触发该效果列表。

光环技能

光环技能与被动技不用在于光环技能会检测在光环范围内的目标,进入光环的时候释放效果,离开光环的时候释放效果,被动技能主要用在触发时释放效果。

img

img

如:光环目标类型是自己和伙伴,检测范围内的所有目标,筛选出符合目标的角色,放到一个列表内,进入光时添加一个无限时长buff,对应的离开光环时把这个buff驱散。

未完待续…

[转载自][https://www.acgloby.com/技能系统程序设计思路-被动技能/]

矩形范围检测

img

计算底边中点center,宽为2*range,长为length,朝向为q的矩形,是否与圆心为pos,半径为radius的圆有交点

1
2
3
4
5
6
7
8
9
public bool IntersectsRectangle(Vector3 center, float length, float range, Quaternion q, Vector3 pos, float radius)
{
Vector3 dis = pos - center;
Vector3 forward = q * Vector3.forward;
Vector3 right = q * Vector3.right;
var vd = Vector3.Dot(dis, forward.normalized);
var vl = Vector3.Dot(dis, right.normalized);
return vd > -radius && vd < length + radius && vl > -radius - range && vl < radius + range;
}

计算中心点为center,高为height1的圆(柱),是否与底边中点为dst,高为height2的矩形(体)相交–只考虑高度

1
2
3
4
5
6
7
8
9
public bool IntersectsHeight(Vector3 center, float radius, Vector3 dst, float height1, float height2)
{
var h = center.y - dst.y;
if(h >= 1e-6f)
{
return h <= height1 + height2;
}
return Mathf.Abs(h) < height1;
}

圆形范围检测

img

判断两个圆形中心点距离是否小于两个圆形半径和

1
2
3
4
5
6
7
public bool IntersectsCircle(Vector3 center, Vector3 pos, float r1, float r2)
{
var radius = r1 + r2;
Vector3 d = pos - center;
Vector2 v = new Vector2(d.x, d.z);
return v.sqrMagnitude <= radius * radius;
}

环形范围检测

img

判断外圆形和目标中心点距离是否小于两个圆形半径和,并且大于内圆和目标距离半径和

扇形范围检测

img

判断圆形和扇形是否相交在圆形检测的基础上计算施法者与目标之间的夹角是否在扇形二分之一角度内

1
2
3
4
5
6
public bool IntersectsSector(Transform caster, Transform receiver, float angle, float radius)
{
Vector3 deltaA = caster.position - receiver.position;
float tmpAngle = Mathf.Acos(Vector3.Dot(deltaA.normalized, caster.forward)) * Mathf.Rad2Deg;
return tmpAngle < angle * 0.5f && deltaA.magnitude < radius;
}

转载自,侵删

设计原则:依赖倒置原则

依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

总结:依赖倒置原则

  • A.高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。
  • B.抽象不应该依赖于具体,具体应该依赖于抽象。

具体例子

当在 Unity 设计底层模块时,上层模块调用的东西其实就只是一些暴露出来的方法,当底层改动的时候上层也需要改动,这就形成了高层次模块对低层次模块的依赖。这时候可以抽象出来这些需要暴露的方法,即使用接口抽象出来,使得底层依赖了抽象的接口,高层也依赖了抽象的接口,从而使得设计更加清爽。

浅谈Unity开发中的分层设计 中也使用了接口抽象的方式,不过是在底层之间的相互依赖中使用了接口,对于上层还是通过暴露公共方法的方式,加了个 模块管理器,来取得具体模块或者具体实现的接口。