[Unity Demo]从零开始制作空洞骑士Hollow Knight第二十集:制作专门渲染HUD的相机HUD Camera和画布HUD Canvas
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
hello大家好久没见,之所以隔了这么久才更新并不是因为我又放弃了这个项目,而是接下来要制作的工作太忙碌了,每次我都花了很长的时间解决完一个部分,然后就没力气打开CSDN写文章就直接睡觉去了,现在终于有时间整理下我这半个月都做了什么内容。
那么,这个HUD是什么意思呢?百度翻译就是平行显示系统,一般用于车辆上面的某个系统,但它也可以作为了一个专业的游戏术语,显示与摄像机平行的东西,如果你还没理解的话,我就直接上图吧:就是这些不会随着摄像机移动而相对移动的物品,这些看似是UI来实现的也可以使用摄像机的功能来实现:
另外,我的Github已经更新了,想要查看最新的内容话请到我的Github主页下载工程吧:
GitHub - ForestDango/Hollow-Knight-Demo: A new Hollow Knight Demo after 2 years!
一、制作HUD Camera以及让两个相机同时渲染屏幕
OK我们到_GameCameras中创建一个新的相机名字就叫HudCamera,并按照如下设置好camera的各个参数。
作为playmakerFSM的公共变量,我们还需要初始化这个公共变量:
创建一个同名脚本HUDCamera.cs,目前只用处理在HUD Camra激活以后将menu的camera移动到hud camera上:由于我们还没做到暂停界面所以可以完全不管。
using System;
using UnityEngine;
public class HUDCamera : MonoBehaviour
{
private GameCameras gc;
private InputHandler ih;
private bool shouldEnablePause;
private void OnEnable()
{
if (!gc)
{
gc = GameCameras.instance;
}
if (!ih)
{
ih = GameManager.instance.inputHandler;
}
if (ih.pauseAllowed)
{
shouldEnablePause = true;
ih.PreventPause();
}
else
{
shouldEnablePause = false;
}
Invoke("MoveMenuToHUDCamera", 0.5f);
}
private void MoveMenuToHUDCamera()
{
gc.MoveMenuToHUDCamera();
if (shouldEnablePause)
{
ih.AllowPause();
shouldEnablePause = false;
}
}
}
我们还需要创建一个类似于UI画布的子对象名字就叫HUD Canvas,它负责我们第二节讲到的四个部分的位置,控制它们的大小之类的。
同样它也是一个公共变量:(另一个playmakerFSM我们下一期再讲。)
创建一个相机不难,但是我们都知道一个相机会渲染一个屏幕上的所有东西,那么怎么让两个摄像机渲染各自需要渲染的东西呢?很简单,就是找到每个摄像机的Culling Mask,选择想要渲染的东西,比如HudCamera就渲染UI层即可:
主摄像机就渲染其它的层级:
但这些还远远不够,其实做到这里我也遇到瓶颈了,上网搜索解决方法都是毫不相关的内容:
但是功夫不负有心人。我居然在CSDN找到了解决方法:内容的原文如下:
【Unity】双摄像机叠加渲染_unity两个摄像机画面叠加-CSDN博客
主要是分为四个部分:
第一部分就是我上面设置的Culling Mask,
第二部分,设置好两个摄像机的Clear Flags:渲染UI的就用Depth Only,
主摄像机用SkYBOX
第三部分设置好两个深度,保证HUD Camera的深度大于主摄像机:
然后它们两个摄像机的target display都保证是display 1就没问题了,还有就是记得把UI的游戏对象的layer都设置成UI,这样HUD Camera才能渲染到:
为了保证HUD Camera和主摄像机的culling mask没有问题,来到脚本GameCameras.cs中,添加上刚刚创建好的camera参数:
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityStandardAssets.ImageEffects;
public class GameCameras : MonoBehaviour
{
private static GameCameras _instance;
public static GameCameras instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType<GameCameras>();
if (_instance == null)
{
Debug.LogError("Couldn't find GameCameras, make sure one exists in the scene.");
}
DontDestroyOnLoad(_instance.gameObject);
}
return _instance;
}
}
[Header("Cameras")]
public Camera hudCamera;
public Camera mainCamera;
[Header("Controllers")]
public CameraController cameraController;
public CameraTarget cameraTarget;
[Header("FSMs")]
public PlayMakerFSM cameraShakeFSM;
public PlayMakerFSM cameraFadeFSM;
public PlayMakerFSM soulOrbFSM;
public PlayMakerFSM soulVesselFSM;
[Header("Other")]
public tk2dCamera tk2dCam;
public GameObject hudCanvas;
public Transform cameraParent;
public GeoCounter geoCounter;
private bool init;
private GameManager gm;
private void Awake()
{
if(_instance == null)
{
_instance = this;
Debug.LogFormat("GameCameras DontDestroyOnLoad");
DontDestroyOnLoad(this);
return;
}
if(this != _instance)
{
DestroyImmediate(gameObject);
return;
}
}
private void OnDestroy()
{
DestroyImmediate(sceneParticles);
}
public void SceneInit()
{
if(this == _instance)
{
StartScene();
}
}
private void StartScene()
{
if (!init)
{
SetupGameRefs();
}
if(gm.IsGameplayScene() || gm.ShouldKeepHUDCameraActive())
{
MoveMenuToHUDCamera();
if (!hudCamera.gameObject.activeSelf)
{
hudCamera.gameObject.SetActive(true);
}
}
else
{
DisableHUDCamIfAllowed();
}
if (gm.IsMenuScene())
{
cameraController.transform.SetPosition2D(14.6f, 8.5f);
}
cameraController.SceneInit();
cameraTarget.SceneInit();
sceneParticles.SceneInit();
}
public void MoveMenuToHUDCamera()
{
int cullingMask = mainCamera.cullingMask;
int cullingMask2 = hudCamera.cullingMask;
UIManager.instance.UICanvas.worldCamera = hudCamera; //让uimanager的canvas相机设置成hudcamera
UIManager.instance.UICanvas.renderMode = RenderMode.ScreenSpaceCamera;
mainCamera.cullingMask = (cullingMask ^ 134217728);
hudCamera.cullingMask = (cullingMask2 | 134217728);
}
public void DisableHUDCamIfAllowed()
{
if (gm.IsNonGameplayScene() && !gm.ShouldKeepHUDCameraActive())
{
hudCamera.gameObject.SetActive(false);
}
}
private void SetupGameRefs()
{
gm = GameManager.instance;
if(cameraController != null)
{
cameraController.GameInit();
}
else
{
Debug.LogError("CameraController not set in inspector.");
}
if (cameraTarget != null)
{
cameraTarget.GameInit();
}
else
{
Debug.LogError("CameraTarget not set in inspector.");
}
init = true;
}
}
回到编辑器都添加上去参数:没有的参数都是后面要讲的。
二、制作HUD Canvas
这一节的内容会非常繁多,因为毕竟是一个顶级2D类银河恶魔城游戏,只能说樱桃工作室是会极致的打磨产品的,所以我做起来也非常费时,你们能看多少就看多少吧。首先我们从法力条开始讲起。
1.制作法力条Soul Orb
我们先来讲子对象再来聊聊这个playmakerFSM
首先是提醒玩家可以治疗的粒子系统:
白光,White Flash:它有一个playmakerFSM用来控制什么时候发光
Orb Full:这是当法力条满了的时候的sprite,它的子对象Pulse Sprite是加强白光的效果
Eyes:眼睛,当玩家的法力条达到三分之二的时候,这个眼睛就会显示出来,感觉就像外星人一样
然后它需要一个playmakerFSM来控制显示的动画:
这里本来如果没有spell法术的话就会进入Off状态,但我觉得没必要所以就不连Off了:
不得不说这个iTween确实好用,选择合适的Ease Type就可以很流畅的改变你想要改变的值,比如我们的这个colour,还有眼睛的大小scale,让它看起来像播放了一个睁开的动画一样
这些OVER , UNDER事件都是由Soul Orb的playmakerFSM来发送的。
HUD Frame:也就是法力条的头部,他需要动画和一个playmakerFSM控制动画的播放:
一个是Idle动画,另一个是Appear动画
创建一个名字为Load Animation的playmakerFSM:
对于HUD Frame的动画,我们由四款不一样的需要选择,所以我们使用FsmString记录需要使用哪一款的HUD Frame,这四款分别是正常的,破碎的,钢魂的,寻神者的,所以我们才需要判断使用哪一款
这个就是寻神者情况下的:
这里需要判断模式以及是否是第一次进入教学关卡enteredTutorialFirstTime:
在等待个1.75秒后,我们还需要判断是否是破碎的HUD Frame:
等到发送事件SOUL LIMITER DOWN以后就回到最原始的款式,
这个也是同理:
然后就是每当场景发生变换的时候就调用一次。
Burst Anim:当满了以后播放这个动画,提示一下玩家该使用法力条了
Soul Orb Break Effect:也就是法力条破碎后播放的粒子效果
SoulOrb_fill:这个就是涉及到shader的mask相关了:
第二个子对象是一个mask,其实就是透明的shader作用在上面
第一个Liquid则是有些tk2dAnimator动画,它的动画看起来非常奇怪,你可能会好奇这些动画是怎么实现上面的效果的:
所以这就是shader牛逼的地方了,可以看到,随着移动它的mask区域也发生了改变,这个liquid仿佛成为了一个mask附着在mask上面了,当它的位置发生改变时里面的mask的范围也会发生改变,
这个shader其实是Unity资源商店的一个商品名字叫做:Alpha Masking,以下是该作者给出的使用教程注意这段话:If an object uses the Default Sprite or Unlit/Transparent shader, the mask will be applied to it,所以这就是为什么我们需要一个mask使用Unlit/Transparent。这样它就会附着在这个mask上面了,而这个mask的大小要和HUD_Frame的大小一样,这样看起来好像SoulOrb_fill附着HUD_Frame一样。我们就使用它的shader:Sprites-Alpha-Mask-WorldCoords即可实现这个很牛的功能。
HOW TO USE:
- To apply the mask to objects, attach those objects to the same parent (may have more than one level of hierarchy) and put the mask directly under that parent, too.
- Clicking "Apply Mask to Siblings in Hierarchy" will detect all siblings (including their children) and attempt to apply the mask to all them. If an object uses the Default Sprite or Unlit/Transparent shader, the mask will be applied to it.
- The mask can be moved, scaled and rotated freely in the Editor, but it can only be rotated over a chosen axis (depending on what mapping axis is selected).
MASK PARAMETERS:
- Mask Mapping World Axis: defines, over which axis the mask should be applied. This is usually the axis, which corresponds with the camera direction.
- Invert Axis: in case you need to map the mask over an inverted axis.
- Clamp Alpha Horizontally: if the texture isn't clamped by Unity (in import settings), then you can choose to clamp it horizontally only (it will be repeated vertically, unless chosen otherwise).
- Clamp Alpha Vertically: if the texture isn't clamped by Unity (in import settings), then you can choose to clamp it vertically only (it will be repeated horizontally, unless chosen otherwise).
- Clamping Border: if one of the two bove settings are enabled, you can use this variable to tweak the "edge" with of clamping. Depending on the alpha texture size and its usage, you might run into texture clamping issues. In that case, try increasing (or lowering) the Clamping Border value.
- Use Mask A Channel (not RGB): the mask uses the texture RGB channels by default. Toggle "Use Mask A Channel (not RGB)" to use the Alpha channel of the texture instead.
- Display Mask: toggle this setting to enable or disable the visibility of the mask. This setting is only available in the Editor (and while not running the player).
THING TO HAVE IN MIND:
- You can either create your own materials for masked Sprites/3D objects manually, or the Mask will create its own.
- When using prefabs of masked objects, it's better to manually create materials.
接下来是这四个容器,但我们目前还用不多所以我先不讲了,暂时贴出来给大伙看看吧:
经常打四锁五门的朋友都知道,这个就是禁止使用容器的法力条的meshrenderer
这是限制法力条后的burst anim:
这两个是在寻神者模式的,懂的都懂:
(写到这里的时候这网页居然崩溃了,真的给我整无语了,没办法只能收拾心情再写一遍了)
回到Soul Orb我们来写一个playmakerFSMSoul Orb Control:
初始阶段清除MP,HeroController的方法ClearMP,取消激活寻神者里的binding cap,设置孩子的引用,设置好liquid Y轴方向的位置,通过GetPlayerDataInt来获得当前MP判断是否为0,以及设置好LiquidY初始的位置加上Mp*每消耗一次Mp Liquid的Y轴偏移量。
如果开始时MP是满的就激活orb full
设置可以播放Can Focus Anim.
Check Can Heal判断能否使用回复术:就是在玩家的playmakerFSM叫Spell Control,通过里面的FSMBool变量Focusing存储在自身的Hero Focusing变量上,如果正在focusing就取消,反之则通过MPCharge和focusMP_amount判断够不够法力值来回血
如果不能够的情况下,就告诉Liquid的事件:CANT HEAL
如果能够治疗的话,就发送Liquid事件CAN HEAL,播放动画以及音效,设置好eFFect范围,同时在玩家的SpriteFlash中调用方法:flashFocusGet()
private void flashFocusGet()
{
Start();
flashColour = new Color(1f, 1f, 1f);
amount = 0.5f;
timeUp = 0.1f;
stayTime = 0.01f;
timeDown = 0.35f;
block.Clear();
block.SetColor("_FlashColor", flashColour);
flashingState = 1;
flashTimer = 0f;
repeatFlash = false;
}
然后通过MP的值是否大于50来判断是否显示眼睛
然后再Idle状态等待外面物体发送这些事件:
如果发送的是MP LOSE事件,首先判断是否是寻神者模式锁住法力条
再检查一遍MP 有没有小于50判断是否显示眼睛:
MP LOSE阶段:Liquid播放动画Shrink,设置Orb Full为非激活,设置好Liquid的Y轴方向的位置
然后就是用我们最爱的ITween Move To改变Liquid的位置
Check MP:检查我们的MP的值是否小于1,小于1则Mp Is Zero事件发送
在MP <= 1阶段,我们得让Liquid的Y轴离远点保证不会再mask的上面
让我们回到Idle状态,这个MP SET老实说我也没想到怎么用,可以先放在一边:
然后是事件MP GAIN SPA在温泉中获得MP的事件,这个我也没做到也先放着。
当发送获得MP事件MP GAIN之后,通过MPCharge和maxMP判断MP是否满了,满的话就给子对象Get Flash发送FLASH事件闪一下提示玩家。
这个promptFocus是提示玩家使用回复术的,这也是下一期的内容:
在MP Gain阶段,也是获得当前Mp和满Mp的值,然后设置好Liquid的Y轴方向,
老实说我还没想要当mp为一半的时候到底该怎么样,所以先放着这个HALF FULL。
如果Mp满了的话执行和我上面说的差不多的操作
这个寻神者模式下满法力条的情况:
最后是法力条被吸收消耗的时候状态MP DRAIN:设置orb full为非激活,判断Mp是否已经为0,设置好Liquid的Y轴的位置
那么讲了这么多有关Mp的事件,会在哪里调用呢?
首先是在HeroController.cs函数当中:
public void AddMPCharge(int amount)
{
int mpreserve = playerData.MPReserve;
playerData.AddMPCharge(amount);
GameCameras.instance.soulOrbFSM.SendEvent("MP GAIN");
if (playerData.MPReserve != mpreserve && gm)
{
gm.soulVessel_fsm.SendEvent("MP RESERVE UP");
}
}
public void SoulGain()
{
int num;
if(playerData.MPCharge < playerData.maxMP)
{
num = 11;
}
else
{
num = 6;
}
int mpreverse = playerData.MPReserve;
playerData.AddMPCharge(num);
GameCameras.instance.soulOrbFSM.SendEvent("MP GAIN");
if(playerData.MPReserve != mpreverse)
{
gm.soulVessel_fsm.SendEvent("MP RESERVE UP");
}
}
public void AddMPChargeSpa(int amount)
{
TryAddMPChargeSpa(amount);
}
public bool TryAddMPChargeSpa(int amount)
{
int mpreserve = playerData.MPReserve;
bool result = playerData.AddMPCharge(amount);
gm.soulOrb_fsm.SendEvent("MP GAIN SPA");
if(playerData.MPReserve != mpreserve)
{
gm.soulVessel_fsm.SendEvent("MP RESERVE UP");
}
return result;
}
然后就是在hero的playmakerFSM当中我们会调用herocontroller.cs的TakeMp和TakeMpQuick两个函数:
public void TakeMp(int amount)
{
Debug.LogFormat("Take MP");
if(playerData.MPCharge > 0)
{
playerData.TakeMP(amount);
if(amount > 1)
{
GameCameras.instance.soulOrbFSM.SendEvent("MP LOSE");
}
}
}
public void TakeMPQuick(int amount)
{
if (playerData.MPCharge > 0)
{
playerData.TakeMP(amount);
if (amount > 1)
{
GameCameras.instance.soulOrbFSM.SendEvent("MP DRAIN");
}
}
}
终于整完第一部分了,接下来该讲讲生命条Health了。
2.制作生命条Health
其实我做了11个生命条,不知道你数了没有:
还是来一个一个介绍下它们的功能吧,首先是只有一滴血的时候的视觉效果们:Low Health Vignette
创建一个名字为Vignette Control的playmakerFSM:
很简单,通过UP DOWN两个事件改编它的scale,还有淡入淡出的手段改变依赖他的spriteRenderers,TextMeshPro,InvAnimateUpAndDown们
这里需要一个新的脚本FadeGroup.cs:
using System;
using TMPro;
using UnityEngine;
public class FadeGroup : MonoBehaviour
{
public SpriteRenderer[] spriteRenderers;
public TextMeshPro[] texts;
public InvAnimateUpAndDown[] animators;
public float fadeInTime = 0.2f;
public float fadeOutTime = 0.2f;
public float fadeOutTimeFast = 0.2f;
public float fullAlpha = 1f;
public float downAlpha;
public bool activateTexts;
private int state;
private float timer;
private Color currentColour;
private Color fadeOutColour = new Color(1f, 1f, 1f, 0f);
private Color fadeInColour = new Color(1f, 1f, 1f, 1f);
private float currentAlpha;
public bool disableRenderersOnEnable;
private void OnEnable()
{
if (disableRenderersOnEnable)
{
DisableRenderers();
}
}
private void Update()
{
if (state != 0)
{
float t = 0f;
if (state == 1) //将所有spriteRenderers和texts的alpha设置为upalpha
{
timer += Time.deltaTime;
if (timer > fadeInTime)
{
timer = fadeInTime;
state = 0;
for (int i = 0; i < spriteRenderers.Length; i++)
{
if (spriteRenderers[i] != null)
{
Color color = spriteRenderers[i].color;
color.a = fullAlpha;
spriteRenderers[i].color = color;
}
}
for (int j = 0; j < texts.Length; j++)
{
if (texts[j] != null)
{
Color color2 = texts[j].color;
color2.a = fullAlpha;
texts[j].color = color2;
}
}
}
t = timer / fadeInTime;
}
else if (state == 2) //将所有spriteRenderers和texts的alpha设置为downalpha
{
timer -= Time.deltaTime;
if (timer < 0f)
{
timer = 0f;
state = 0;
if (downAlpha > 0f)
{
for (int k = 0; k < spriteRenderers.Length; k++)
{
if (spriteRenderers[k] != null)
{
Color color3 = spriteRenderers[k].color;
color3.a = downAlpha;
spriteRenderers[k].color = color3;
}
}
for (int l = 0; l < texts.Length; l++)
{
if (texts[l] != null)
{
Color color4 = texts[l].color;
color4.a = downAlpha;
texts[l].color = color4;
}
}
}
else
{
DisableRenderers();
}
}
t = timer / fadeOutTime;
}
if (state != 0)
{
currentAlpha = Mathf.Lerp(downAlpha, fullAlpha, t);
for (int m = 0; m < spriteRenderers.Length; m++)
{
if (spriteRenderers[m] != null)
{
Color color5 = spriteRenderers[m].color;
color5.a = currentAlpha;
spriteRenderers[m].color = color5;
}
}
for (int n = 0; n < texts.Length; n++)
{
if (texts[n] != null)
{
Color color6 = texts[n].color;
color6.a = currentAlpha;
texts[n].color = color6;
}
}
}
}
}
/// <summary>
/// 将所有的spriterender和text都设置为透明alpha = 0
/// </summary>
public void FadeUp()
{
timer = 0f;
state = 1;
for (int i = 0; i < spriteRenderers.Length; i++)
{
if (spriteRenderers[i] != null)
{
Color color = spriteRenderers[i].color;
color.a = 0f;
spriteRenderers[i].color = color;
spriteRenderers[i].enabled = true;
}
}
for (int j = 0; j < texts.Length; j++)
{
if (texts[j] != null)
{
Color color2 = texts[j].color;
color2.a = 0f;
texts[j].color = color2;
texts[j].gameObject.GetComponent<MeshRenderer>().SetActiveWithChildren(true);
}
}
for (int k = 0; k < animators.Length; k++)
{
if (animators[k] != null)
{
animators[k].AnimateUp();
}
}
}
public void FadeDown()
{
timer = fadeOutTime;
state = 2;
for (int i = 0; i < animators.Length; i++)
{
if (animators[i] != null)
{
animators[i].AnimateDown();
}
}
}
public void FadeDownFast()
{
timer = fadeOutTimeFast;
state = 2;
for (int i = 0; i < animators.Length; i++)
{
if (animators[i] != null)
{
animators[i].AnimateDown();
}
}
}
private void DisableRenderers()
{
for (int i = 0; i < spriteRenderers.Length; i++)
{
if (spriteRenderers[i] != null)
{
spriteRenderers[i].enabled = false;
}
}
for (int j = 0; j < texts.Length; j++)
{
if (texts[j] != null)
{
Color color = texts[j].color;
color.a = 0f;
texts[j].color = color;
//texts[j].gameObject.GetComponent<MeshRenderer>().SetActiveWithChildren(false);
}
}
}
}
通过playmakerFSM调用里面的方法FadeUp和FadeDown就可以轻松改变所依赖对象的值。
using System;
using HutongGames.PlayMaker;
using UnityEngine;
[ActionCategory("Hollow Knight")]
public class FadeGroupUp : FsmStateAction
{
[UIHint(UIHint.Variable)]
public FsmOwnerDefault target;
public override void Reset()
{
target = new FsmOwnerDefault();
}
public override void OnEnter()
{
GameObject gameObject = (target.OwnerOption == OwnerDefaultOption.UseOwner) ? Owner : target.GameObject.Value;
if (gameObject != null)
{
FadeGroup component = gameObject.GetComponent<FadeGroup>();
if (component != null)
{
component.FadeUp();
}
}
base.Finish();
}
}
using System;
using HutongGames.PlayMaker;
using UnityEngine;
[ActionCategory("Hollow Knight")]
public class FadeGroupDown : FsmStateAction
{
[UIHint(UIHint.Variable)]
public FsmOwnerDefault target;
public FsmBool fast;
public override void Reset()
{
target = new FsmOwnerDefault();
}
public override void OnEnter()
{
GameObject gameObject = (target.OwnerOption == OwnerDefaultOption.UseOwner) ? Owner : target.GameObject.Value;
if(gameObject != null)
{
FadeGroup component = gameObject.GetComponent<FadeGroup>();
if (component != null)
{
if (fast.Value)
{
component.FadeDownFast();
}
else
{
component.FadeDown();
}
}
}
base.Finish();
}
}
Low Health Particles
Low Health Light灯:也是外面发送事件UP 和DOWN接收后做出行为
然后就是第一条血了:
除了常规的health_display的playmakerFSM以外,它还有一个复仇之魂效果的playmaker叫Fury Effects,不过这也不是我们关心的,同时它需要订阅事件HERO DAMAGED。
它的子对象第一个:Idle状态下的生命:
第二个是Max Up,回血到这个血条的时候播放的粒子系统:
这个就是护符复仇之魂播放的粒子系统:
这个是使用蜂巢回血的时候的替换的生命:
回到playmaker : health_display当中,我们先创建好事件以及变量:
这个Health Number很关键,你得说明这是第几个生命条,比如我这是第一个就写1.
初始化阶段,找到孩子的引用,关闭idle sprite
这个equippedCharm_29就是蜂巢之血,检查玩家是否装配该护符。
普通情况就是普通情况的动画名:
这是在蜂巢之血情况下的,我们先去把动画制作了吧:
我们先去把他们的动画给做了:记住名字一定要和set string value里面的名字一样:
制作完成动画后让我们回到playmaker,下一个状态是检查当前的血条是否是满血的血条:就是获取gammanager当中的maxHealth的int类型变量并比较
如果超过了满血的话,我们就取消显示它的meshrenderer,
这里是获取新生命值后的再一次检查:
获取新生命值我们就播放这个血条的动画,
在Idle状态下,我们只需要显示它的子对象Idle的meshrenderer即可:
当玩家收到伤害以后会发送HERO DAMAGED事件,
这个是判断是否要销毁这个血条的meshrenderer:
如果是的话就播放动画Empty
那么这时候在治疗状态下呢?就是小骑士的playmaker:“Spell Control”发送了HERO HEALED事件呢,有可能在受到伤害的同时治疗回血了,这也是我们要考虑的,所以Heal和Break时互通的两个状态
当然还有回到满血的状态时,我们还是有一些动画和粒子系统要处理的:
剩下的这些事很后面的内容了,你们还记得寻神者模式的四锁五门吗,我们这里先做个大概的,反正不会执行到这个状态:首先是检查是否寻神者锁生命值状态
using System;
using HutongGames.PlayMaker;
[ActionCategory("Hollow Knight/GG")]
public class GGCheckBoundHeart : FSMUtility.CheckFsmStateAction
{
public FsmInt healthNumber;
public CheckSource checkSource;
public override void Reset()
{
healthNumber = null;
checkSource = CheckSource.Regular;
base.Reset();
}
public override bool IsTrue
{
get
{
int num = -1;
CheckSource checkSource = this.checkSource;
if (checkSource != CheckSource.Regular)
{
if (checkSource == CheckSource.Joni)
{
num = (int)(healthNumber.Value * 0.71428573f) + 1;
}
}
else
{
num = healthNumber.Value;
}
//TODO:
return false;
}
}
public enum CheckSource
{
Regular,
Joni
}
}
如果是,则播放动画锁生命:当然这里不会执行到 的
如果不是,就判断是否要播放动画:
首先是是否是第一次游玩该游戏,这里会有个延迟显示的动画,就是依次延迟的显示第1,2,...,n个血条
如果是第一次玩的话,就等一会:播放动画Anim Appear,
设置完成初始化,如果该血条是最后一个满血条,那么就广播事件LAST HP ADDED,我忘记给谁接收了先接着写吧。
回到Load Animation?状态,当发送事件FULL到达,Check if Full检查当前血条是否是满的:
最后一个是复活状态:我们获取的gamemanager的属性RespawningHero来判断当前是否在复活玩家。
using System;
using HutongGames.PlayMaker;
[ActionCategory("Hollow Knight")]
public class GetRespawningHero : FsmStateAction
{
[RequiredField]
[UIHint(UIHint.Variable)]
public FsmBool variable;
public override void Reset()
{
variable = new FsmBool();
}
public override void OnEnter()
{
if (GameManager.instance)
{
variable.Value = GameManager.instance.RespawningHero;
}
base.Finish();
}
}
如果是真正正在复活玩家的话,就等1秒:
设置初始化为真,
再检查一遍是否装备了蜂巢之血:
当然这第一个血条还有特殊的地方,大家肯定还记得亡者之怒吧,就是当你只有一滴血的时候伤害翻倍,这其中当然有一些视觉效果和听觉效果要完成,但我还没做到护符,先随便写写吧:
做完了第一个血条剩下的不是随随便便,需要注意更改的是,其它血条没有亡者之怒fury particle的效果和playmaker,但是它们有寻神者的绑定血条的子对象Idle Bound:(记得关掉它的meshrenderer,我展示给你们看看的)
其它要改的是它们的playmakerFSM,你是第几个血条就输入Health Number为几
OK复制粘贴我们就做了11个血条,还有它们的完整行为:
你们肯定注意到我有个子对象Joni Health没讲,其实这个是生命水来的,但我还没做到,先搭个对象剩下的以后来做:
还有两个内容我打算到下一期再讲,这期先做到这里吧。
总结
OK我们来展示一下Soul Orb和Health的效果:游戏刚开始,系统判断玩家第一次游玩游戏,所以会延迟显示
由于我们在playerdata设置的满血为5,所以他就显示5个血条:
当你的法力值soulorb满了以后,播放粒子效果,显示眼睛eye,你这时候再打法力值也不会接着增加了。
然后最厉害的回血的时候,法力条犹如一个容器里液体的涌动,
当法力条低于一般的时候,眼睛就会消失,
OK下一期哦我们来制作游戏的金钱系统和额外内容的初始搭建。
原文地址:https://blog.csdn.net/dangoxiba/article/details/143529974
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!