一些动作游戏的通用代码和架构. based on the game Valda: Before the Dawn
程序层面,有效提升打击感的方法:
可以很方便地用来做子弹时间,别人很慢而自己很快,塞尔达里那种时快时慢的子弹时间也可以. 扩展以后还可以支持时间回溯 (参考Braid),需要用到帧同步或者状态同步.
public interface ITimeZone {
float relativeTimeScale { get; set; }
float timeScale {get; set;}
float deltaTime {get;}
float fixedDeltaTime {get;}
// we need to handle multiple sources of timeScale change.
void ChangeTimeScale(float newTimeScale, int priority);
// Sometimes we need to tween timeScale, such as bullet time.
// Same as above, we need to handle multiple sources by priority.
void TweenTimeScale(ITweenInfo tweenInfo, int priority);
}
void DoUpdate(){
hero.animator.speed = hero.localTimeScale;
}
通用方案下,每个粒子系统的TimeScale需要单独调整
IEnumerator PlayEffectCrt(float duration){
if(hero.localTimeScale == 1)
yield break;
float timer;
while(timer < duration){
particleSystem.Simulate(hero.localDeltaTime);
timer += hero.localDeltaTime;
yield return null;
}
}
注意: Simulate()方法会产生大量GC.
particleSystem.main.simulationSpeed = hero.localTimeScale;
某些特效可能不受TimeScale影响,始终按照正常速度播放
if(effect.playInUnscaledTime)
particleSystem.main.simulationSpeed = 1;
IEnumerator WaitForSecondsLocal(float duration) {
float timer = 0;
while(timer <= duration) {
timer += LocalDeltaTime;
yield return null;
}
}
// Example
IEnumerator DoSkill(Skill skill){
SkillOn(skill);
yield return hero.WaitForSecondsLocal(skill.duration);
SkillEnd();
}
区分轻重硬直和动画,打击感才会丰富,后面会详细讲. 硬直动画用状态机来实现
主要是摄像机晃动的效果,表现地面震动
推荐插件: https://github.com/ewersp/CameraShake
音效和特效都要卡在合适的动画帧播放
API参考:
http://gad.qq.com/article/detail/38140
最好制作一个方便预览的场景,但很难满足特效和策划的要求。设计代码的时候就要考虑预览问题.
轻量状态机,带调试功能:
https://github.com/ImYellowFish/Unity3d-Finite-State-Machine
如果可以构成连续技,实际触发的Skill可能并不是Input中指定的Skill 比如普攻第二段是上挑,第三段下劈, 等等.
Input传入攻击的时候,如果该技能不能立刻释放(正在放其他技能,或者硬直),则等到玩家就绪 的时候释放.
Input传入攻击的时候,如果玩家就绪,但是距离要求不满足,则走进范围后再释放. 也是一个中间处理器. 以上三个处理器的耦合需要小心处理
比如:A是否能取消B前摇,A能否取消B激活,A能否取消B后摇
判断下是否已经在释放技能,如果是,则判断取消规则.
具体用什么规则看战斗系统的设计 比如每个技能都有int优先级,优先级高的可以取消优先级低的前摇.
不要让策划拍脑子想,程序制定的规则肯定比策划靠谱
动作游戏的技能经常会用到. 某些技能会带有特殊属性,比如破防,强制取消,带格挡,等等. 在读表系统中加入对Flag Enum类型的支持,可以减少很多列
Flag规则可以参考2D格斗游戏:
http://wiki.shoryuken.com/Skullgirls/Glossary
https://github.com/ImYellowFish/UnityUtility/tree/master/ModedStandardV2017
// file: UnityStandardCore.cginc
half4 fragForwardBaseInternal (VertexOutputForwardBase i)
{
FRAG_SETUP(i);
// Unity standard PBS
half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
//-----------custom functions (forward base)-------------
// apply custom Emission
c.rgb += Emission(i.tex.xy) * _EmissionMultiplier;
#ifdef _ZGAME_HSBC
// apply HSBC effect
c.rgb = HSBC(c.rgb, _HsbcParam, i.tex.xy);
#endif
#ifdef _ZGAME_RIM
// apply rim color
c.rgb += Rim(s);
#endif
// apply extra light
#ifdef _ZGAME_FORWARD_BASE_MULTI_LIGHT
c.rgb += ExtraLight(s, _ExtraLightDir_1, _ExtraLightColor_1, ExtraLightAtten(s.posWorld, _ExtraLightPos_1, _ExtraLightDir_1));
c.rgb += ExtraLight(s, _ExtraLightDir_2, _ExtraLightColor_2, ExtraLightAtten(s.posWorld, _ExtraLightPos_2, _ExtraLightDir_2));
#endif
#ifdef _ZGAME_SUBSURFACE_SCATTERING
// apply subsurface scattering
c.rgb = c.rgb + SSS(mainLight.dir, s.normalWorld, -s.eyeVec, mainLight.color, i.tex.xy);
#endif
#ifdef _ZGAME_UV_FLOW
// apply uv flow
c.rgb += UV_Flow(i.tex);
#endif
//------------end custom functions --------
UNITY_APPLY_FOG(i.fogCoord, c.rgb);
return OutputForward (c, s.alpha);
}
#ifdef _ZGAME_FORWARD_BASE_MULTI_LIGHT
half4 _ExtraLightDir_1;
fixed4 _ExtraLightColor_1;
fixed4 _ExtraLightPos_1;
half4 _ExtraLightDir_2;
fixed4 _ExtraLightColor_2;
fixed4 _ExtraLightPos_2;
inline fixed3 ExtraLight(FragmentCommonData s, half4 lightDir, fixed4 lightColor, fixed atten)
{
UnityLight l;
l.color = lightColor.rgb * lightColor.a * 2 * atten;
l.dir = lightDir.xyz;
UnityIndirect noIndirect = ZeroIndirect();
return UNITY_BRDF_PBS(s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, l, noIndirect);
}
inline half ExtraLightAtten(half3 worldPos, half4 lightPos, half4 lightDir) {
half dist_proj = dot(worldPos - lightPos.xyz, lightDir.xyz);
half dist2 = dist_proj * dist_proj;
return saturate((lightPos.w + 1) / (dist2 + 0.01) - lightPos.w);
}
#endif
#ifdef _ZGAME_SUBSURFACE_SCATTERING
half _SSS_Sigma;
half _SSS_Power;
fixed4 _SSS_Color;
sampler2D _SSS_Ramp;
sampler2D _SSS_Mask;
// subsurface scattering
inline fixed3 SSS(half3 lightDir, half3 normal, half3 viewDir, fixed3 lightColor, half2 uv) {
// half f = _SSS_Sigma * max(dot(-normal, lightDir), 0) + (1 - _SSS_Sigma) * (dot(-viewDir, lightDir) * 0.5 + 0.5);
// fixed3 c = pow(f, _SSS_Power) * _SSS_Color.rgb * 2;
half nl = dot(normal, lightDir);
fixed3 c = pow(tex2D(_SSS_Ramp, half2(nl / 2 + 0.5, 0.5)), _SSS_Power) * _SSS_Color.rgb * lightColor;
// apply mask
c = c * tex2D(_SSS_Mask, uv).r;
return c;
}
#endif
边缘光, 用来制作受击的闪光效果, 比描边算法要省一点.
#ifdef _ZGAME_RIM
fixed3 _RimColor;
half _RimPower;
// Add rim color to this character
inline fixed3 Rim(FragmentCommonData s) {
half rimDot = 1 - saturate(dot(s.normalWorld, -s.eyeVec));
return _RimColor * pow(rimDot, _RimPower);
}
#endif
改变色调,饱和度,亮度和对比度, 用来制作变身后的色调变化
https://forum.unity3d.com/threads/hue-saturation-brightness-contrast-shader.260649/
根据美术需求做一些Mask blending.