写在前面
这个系列我删掉了一些东西,所以变成了碎碎念 + 收集癖的记录而已。en... 参考意义不大,建议随便看看...
射箭逻辑
一开始是完全没有用动画,后来加上动画之后出现了很多相应的问题,比如动画没有执行完就执行了后面的代码导致的空指针,比如协程重复被开启,比如目标转换和现在的旋转是一样的话,就会原地转一圈.....通过一次次断点和猜测全部解决之后发现,有动画的效果并没有没有动画的好,于是又把很多地方的动画持续时间设为了0......
整体思路就是:
左手在碰到弓的时候按住 Trigger 可以拿起来,右手直接按 Trigger 可以从空中飞来一只箭握到手中(?牛顿不重要,游戏体验好就 Ok!)
- 搭箭,一开始需要满足箭羽离弓弦很近,并且箭的方向和弓的朝向基本一致,也就是现实中搭箭的逻辑,然后太难了被要求调整,把方向的条件去掉了。
- 箭搭上之后,箭和右手都需要脱离右手柄,层级关系是 弓->箭->右手模型。
- 在搭箭状态下,每一帧更新弓箭的状态
- 弓的拉开程度根据右手柄和原来弓弦的的向量在弓的本地坐标系的 Z 轴的变化值来决定,最后发射的力度也根据这个来确定。弓的朝向与两个手柄的朝向一致,弓的位置要在以头为半径的一个圈的范围内,这样会显得弓在你脸前面,更稳重一点。区别如下图,两张图都是左手伸直了的状态,也就是左手柄的位置其实是一样的。
没有搭箭时弓的远近
搭箭时弓的远近
- 箭的状态,首先是更新位置,箭羽要在拉弦的那个位置,方向要面朝弓把上的一个搭箭点。
- 弓的拉开程度根据右手柄和原来弓弦的的向量在弓的本地坐标系的 Z 轴的变化值来决定,最后发射的力度也根据这个来确定。弓的朝向与两个手柄的朝向一致,弓的位置要在以头为半径的一个圈的范围内,这样会显得弓在你脸前面,更稳重一点。区别如下图,两张图都是左手伸直了的状态,也就是左手柄的位置其实是一样的。
- 这里我觉得比较麻烦的是,不论在任何状态下送了左手柄或者右手柄,或者右手柄离左手柄距离过远,需要解除附着状态,都需要很好的处理,比如在拿箭的过程中送右手柄那箭应该直接消失;在正在附着还没有完全附着的时候也应该直接消失,但弓要回到左手;搭箭完成之后松掉右手柄需要判断这个射箭状态是传送还是正常射箭,来进行后续操作,箭射出之后弓要回到左手,右手模型要回到右手柄......这边我都是用的事件来执行,就导致执行顺序非常不明确。
具体看代码吧
(代码有点长,删掉了)
上面的代码有一些关于抖动的逻辑,原因是一开始是完全按照现实中的射箭来做的,但是非常难瞄准,需要将右手柄放到眼睛后面的位置,让眼睛从箭羽的位置看向箭头才可以瞄准,于是就改成了当右手柄在头附近的时候,箭羽悄悄的到眼睛的位置,这样的话玩家基本不会发现,但问题就是这样射出去的箭也几乎百发百中(QAQ),于是为了增加难度,加一个抖动逻辑,基本思路是:持续记录手柄的位置,比如在 1s 内采样 10 个点,也就是 100ms 采样一次,记录下来,然后在发射的时候根据这 10 个点的偏移(其实应该算类似标准差一样的东西,但是因为抖动非常细微,所以这里计算的是最大值)来算一个 0-1 的抖动值,再根据这个抖动值,来让箭产生一个跟原来轨迹的夹角为 0-5 度,然后方向是 360 随机的一个偏移,效果就是手稍微抖一下,都射不到原来的地方。必须两只手都非常稳。
下面是单独拎出来的抖动逻辑:
// 抖动采样相关
public float SampleJitterTime = 1; // 采样 1s 内的抖动,单位:秒
private const int sampleTimes = 10;
public float MinJitterDistance = 0.002f;
public float MaxJitterDistance = 0.01f;
public Queue<Vector3> controller0HeadJitterDueue = new Queue<Vector3>(sampleTimes); // 左手头抖动,采样 10 次,不能只采样手柄的位置,不然旋转检测不出来,
// 虽然现在也检测不出来 Y 轴方向的抖动,但是一般也不会按照这个方向旋转
public Queue<Vector3> controller0TailJitterDueue = new Queue<Vector3>(sampleTimes);
public Queue<Vector3> controller1HeadJitterDueue = new Queue<Vector3>(sampleTimes);
public Queue<Vector3> controller1TailJitterDueue = new Queue<Vector3>(sampleTimes);
private float lastSampleTime;
// 根据抖动系数让箭稍微偏一点点
CheckJitter(out float controller0JitterK, out float controller1JitterK);
float angle = (controller0JitterK + controller1JitterK) /2 * 7f * Mathf.PI / 180; // 最大偏移 7 度
DebugText.Instance().SetInfo("angle", angle.ToString());
float random_angle = new System.Random().Next(0,360) * Mathf.PI / 180; // 随机向一个方向偏移
Vector3 offset_point = new Vector3(Mathf.Sin(random_angle), 1.0f / Mathf.Tan(angle), Mathf.Cos(random_angle)); // 因为箭的 up 是朝前的,
// 而且 sin(random_angle)平方 + cos(random_angle)平方 = 1
后处理(Bloom 效果)
因为模型有事情,所以自己找的模型搭建了如下场景,增色的就是发光效果,实现如下(这个我是直接 Copy 来着,先记录一下,再仔细研究)
using UnityEngine;
[ExecuteInEditMode]
//屏幕后处理效果主要是针对摄像机进行操作,需要绑定摄像机
[RequireComponent(typeof(Camera))]
public class ScreenEffectBase : MonoBehaviour
{
public Shader shader;
private Material material;
protected Material Material
{
get
{
material = CheckShaderAndCreatMat(shader, material);
return material;
}
}
//用于检查并创建临时材质
private Material CheckShaderAndCreatMat(Shader shader, Material material)
{
Material nullMat = null;
if (shader != null)
{
if (shader.isSupported)
{
if (material && material.shader == shader) { }
else
{
material = new Material(shader) { hideFlags = HideFlags.DontSave };
}
return material;
}
}
return nullMat;
}
}

using UnityEngine;
public class BloomCtrl : ScreenEffectBase
{
private const string _LuminanceThreshold = "_LuminanceThreshold";
private const string _BlurSize = "_BlurSize";
private const string _Bloom = "_Bloom";
[Range(0, 4)]
public int iterations = 3;
[Range(0.2f, 3.0f)]
public float blurSize = 0.6f;
[Range(1, 8)]
public int dowmSample = 2;
[Range(0.0f, 10.0f)]
public float luminanceThreshold = 0.6f;//控制Bloom效果的亮度阈值,因为亮度值大多数时不大于1,故该值超过1时一般无效果,但开启HDR后图像的亮度取值范围将扩大
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (Material != null)
{
Material.SetFloat(_LuminanceThreshold, luminanceThreshold);
int rth = source.height / dowmSample;
int rtw = source.width / dowmSample;
RenderTexture buffer0 = RenderTexture.GetTemporary(rtw, rth, 0);
buffer0.filterMode = FilterMode.Bilinear;
//第1个Pass中提取纹理亮部,存到buffer0中,以便后面进行高斯模糊处理
Graphics.Blit(source, buffer0, Material, 0);
for (int i = 0; i < iterations; i++)
{
Material.SetFloat(_BlurSize, blurSize * i + 1.0f);
//第2,3个Pass中对亮部分别进行纵向和横向的渲染处理(高斯模糊)
RenderTexture buffer1 = RenderTexture.GetTemporary(rtw, rth, 0);
Graphics.Blit(buffer0, buffer1, Material, 1);
RenderTexture.ReleaseTemporary(buffer0);//临时创建的渲染纹理不能直接释放 x: buffer0.Release();
buffer0 = RenderTexture.GetTemporary(rtw, rth, 0);
Graphics.Blit(buffer1, buffer0, Material, 2);
RenderTexture.ReleaseTemporary(buffer1);
}
//第4个Pass将buffer0高斯模糊后的结果传给_Bloom以进行最后的混合
Material.SetTexture(_Bloom, buffer0);
Graphics.Blit(source, destination, Material, 3);//注意这里用原始纹理作为源纹理而不是buffer0,因为buffer0已经作为另一个参数进行了传递,而这里还需要原始的纹理以进行混合
RenderTexture.ReleaseTemporary(buffer0);
}
else
Graphics.Blit(source, destination);
}
}
Shader "MyUnlit/Bloom"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Bloom("Bloom",2D)="black"{}
_LuminanceThreshold("Luminance Threshold",Float)=0.5
_BlurSize("Blur Size",Float)=1.0
}
SubShader
{
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
half4 _MainTex_TexelSize;
sampler2D _Bloom;
float _LuminanceThreshold;
float _BlurSize;
struct v2f
{
half2 uv : TEXCOORD0;
float4 pos : SV_POSITION;
};
struct v2fBloom
{
//half4是因为这里还要存储_Bloom纹理
half4 uv:TEXCOORD0;
float4 pos:SV_POSITION;
};
v2f vert(appdata_img v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.uv=v.texcoord;
return o;
}
v2fBloom vertBloom(appdata_img v)
{
v2fBloom o;
o.pos=UnityObjectToClipPos(v.vertex);
//xy存储主纹理,zw存储_Bloom纹理,这样不必再申请额外空间
o.uv.xy=v.texcoord;
o.uv.zw=v.texcoord;
//纹理坐标平台差异化判断,主要针对DirectX,因为DirectX与OpenGL纹理坐标原点不同(分别在左上和左下)
//同时Unity平台对于主纹理已经进行过内部处理,因此这里只需要对_Bloom纹理进行平台检测和翻转
//主要表现为进行y轴方向的翻转(因为y轴方向相反),对于_Bloom纹理来说也就是w
#if UNITY_UV_STARTS_AT_TOP
if(_MainTex_TexelSize.y<0){
o.uv.w=1.0-o.uv.w;
}
#endif
return o;
}
//提取超过亮度阈值的图像
fixed4 fragExtractBright(v2f i):SV_Target
{
fixed4 col=tex2D(_MainTex,i.uv);
fixed val=clamp(Luminance(col)-_LuminanceThreshold,0.0,1.0);
return col*val;
}
//对xy和zw对应的纹理采样进行混合
fixed4 fragBloom(v2fBloom i):SV_Target
{
return tex2D(_MainTex,i.uv.xy)+tex2D(_Bloom,i.uv.zw);
}
ENDCG
ZTest Always
Cull Off
ZWrite Off
//Pass 1:提亮部
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment fragExtractBright
ENDCG
}
//Pass 2,3:高斯模糊,这里直接调用以前写的Pass
UsePass "MyUnlit/GaussianBlur/GAUSSIANBLUR_V"
UsePass "MyUnlit/GaussianBlur/GAUSSIANBLUR_H"
//Pass 4:混合原图和模糊后亮部
Pass
{
CGPROGRAM
#pragma vertex vertBloom
#pragma fragment fragBloom
ENDCG
}
}
Fallback Off
}
网友评论