[Unity Demo]从零开始制作空洞骑士Hollow Knight第二十集:制作专门渲染HUD的相机HUD Camera和画布HUD Canvas

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、制作HUD Camera以及让两个相机同时渲染屏幕
  • 二、制作HUD Canvas
    • 1.制作法力条Soul Orb引入库
    • 2.制作生命条Health读入数据
    • 3.制作吉欧统计数Geo Counter
    • 4.制作其它内容Extra
  • 总结


前言

         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相机设置成hudcameraUIManager.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下一期哦我们来制作游戏的金钱系统和额外内容的初始搭建。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/60764.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python excel接口自动化测试框架!

今天采用Excel继续写一个接口自动化测试框架。 设计流程图 这张图是我的excel接口测试框架的一些设计思路。 首先读取excel文件&#xff0c;得到测试信息&#xff0c;然后通过封装的requests方法&#xff0c;用unittest进行测试。 其中&#xff0c;接口关联的参数通过正则进…

泷羽sec-linux

基础之linux 声明&#xff01; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团…

卷积神经网络学习记录

目录 神经网络基础定义&#xff1a; 基本组成部分 工作流程 卷积层&#xff08;卷积定义&#xff09;【CONV】&#xff1a; 卷积层&#xff08;Convolutional Layer&#xff09; 特征提取&#xff1a;卷积层的主要作用是通过卷积核&#xff08;或滤波器&#xff09;运算提…

计算机网络-GRE(通用路由封装协议)简介

昨天我们学习了VPN的基本概念&#xff0c;虚拟专用网络在当前企业总部与分支间广泛使用。常用的划分方法为基于协议层次有GRE VPN、IPSec VPN、L2TP VPN、PPTP VPN、SSL VPN等。其实我有考虑该怎么讲&#xff0c;因为在IP阶段好像虚拟专用网络讲得不深&#xff0c;在IE的阶段会…

SeggisV1 源码技术指导文档

软件下载地址&#xff1a; 百度网盘&#xff1a;链接:https://pan.baidu.com/s/1ZtwVcLsLypGo5lH6qR9oTw?pwd5856 问题咨询&#xff1a; https://github.com/YangJing524/Seggis

VSCode Terminal无法运行node以及node-gyp等指令

无法使用node指令&#xff0c;使用管理员权限启动VSCode即可&#xff0c;或者右键VSCode属性&#xff0c;修改兼容性中使用管理员权限打开。 运行node-gyp等指令出现因为在此系统上禁止运行脚本。有关详细信息&#xff0c;请参阅 https:/go.microsoft.com/fwlink/?LinkID1351…

前端全栈 === 快速入 门 Redis

目录 简介 通过 docker 的形式来跑&#xff1a; set、get 都挺简单&#xff1a; incr 是用于递增的&#xff1a; keys 来查询有哪些 key: redis insight GUI 工具。 list 类型 left push rpush lpop 和 rpop 自然是从左边和从右边删除数据。​编辑 如果想查看数据…

计算机网络socket编程(2)_UDP网络编程实现网络字典

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 计算机网络socket编程(2)_UDP网络编程实现网络字典 收录于专栏【计算机网络】 本专栏旨在分享学习计算机网络的一点学习笔记&#xff0c;欢迎大家在评论区交流讨…

简单链式表

# 完成双向循环链表的判空、尾插、遍历、尾删 class Node:def __init__(self, value):self.value valueself.next Noneself.prev None class Linklist:def __init__(self,nodeNone):self.head nodeself.size 0def is_empty(self):return self.size 0def add_tail(self,va…

流水线切分策略;通过自适应的重采样和重计算策略来优化计算资源和内存使用

目录 流水线切分策略 1,2,3,4,5指的计算任务(数据切分) 大方块代表GPU计算 黄色代表显存 通过自适应的重采样和重计算策略来优化计算资源和内存使用 一是自适应重计算(Adaptive Recomputation) 二是自适应划分(Adaptive Partitioning) 流水线切分策略 1,2,3,4,5指…

不只是请求和响应:使用Fiddler抓包URL和Method全指南(中)

欢迎浏览高耳机的博客 希望我们彼此都有更好的收获 感谢三连支持! 不只是请求和响应&#xff1a;使用Fiddler抓包HTTP协议全指南(上)-CSDN博客https://blog.csdn.net/Chunfeng6yugan/article/details/144005872?spm1001.2014.3001.5502 &#x1f649;在(上)篇博客中&#xf…

卷积神经网络(CNN)中的批量归一化层(Batch Normalization Layer)

批量归一化层&#xff08;BatchNorm层&#xff09;&#xff0c;或简称为批量归一化&#xff08;Batch Normalization&#xff09;&#xff0c;是深度学习中常用的一种技术&#xff0c;旨在加速神经网络的训练并提高收敛速度。 一、基本思想 为了让数据在训练过程中保持同一分布…

前端速通(CSS)

1.CSS介绍 1.什么是CSS? CSS&#xff08;Cascading Style Sheets&#xff0c;层叠样式表&#xff09;是一种用于控制网页的外观和布局的样式表语言。它与HTML&#xff08;超文本标记语言&#xff09;紧密配合&#xff0c;负责页面元素的样式定义&#xff0c;如字体、颜色、尺…

webkit浏览器内核编译(2024年11月份版本)

webkit浏览器内核编译 本文详细介绍了如何安装和配置Webkit的编译环境和工具的安装&#xff0c;以及在Windows上编译和运行WebKit浏览器引擎的过程&#xff0c;包括安装依赖、设置环境变量、生成解决方案并最终运行附带的MiniBrowser示例。 一、WebKit简介 WebKit 是一个开源的…

C++趣味编程玩转物联网:用树莓派Pico实现一位数码管动态显示

七段数码管是一种经典的电子显示器件&#xff0c;广泛应用于数字时钟、电子仪表等设备。本文将通过树莓派Pico开发板&#xff0c;介绍如何用C代码控制一位七段数码管显示数字。作为一个嵌入式开发项目&#xff0c;这不仅是初学者理解数码管工作原理的好机会&#xff0c;也是C开…

非交换几何与黎曼ζ函数:数学中的一场革命性对话

非交换几何与黎曼ζ函数&#xff1a;数学中的一场革命性对话 非交换几何&#xff08;Noncommutative Geometry, NCG&#xff09;是数学的一个分支领域&#xff0c;它将经典的几何概念扩展到非交换代数的框架中。非交换代数是一种结合代数&#xff0c;其中乘积不是交换性的&…

【CSP CCF记录】201803-1第13次认证 跳一跳

题目 样例输入 1 1 2 2 2 1 1 2 2 0 样例输出 22 思路 没有技术含量的一道题&#xff0c;解题的关键是理解游戏规则。用state标记跳跃状态&#xff0c;以下是对游戏规则的分析&#xff1a; 1. state1&#xff0c;跳到方块上但没跳到中心&#xff0c;得1分 2. state2&#xf…

ALSA(2) ---- DMA实践

DMA实践 本篇文章主要是学习alsa高级音频框架总结而来&#xff0c;ALSA的Platform侧ADMA&#xff0c;学习总结而来&#xff0c;adma驱动来源于telechips产商805x芯片&#xff1b; ADMA物理拓扑图 ADMA物理拓扑图如上&#xff0c;RX和TX ADMA是接收和发送控制器&#xff0c;Ar…

【机器学习】——卷积与循环的交响曲:神经网络模型在现代科技中的协奏

&#x1f3bc;个人主页&#xff1a;【Y小夜】 &#x1f60e;作者简介&#xff1a;一位双非学校的大二学生&#xff0c;编程爱好者&#xff0c; 专注于基础和实战分享&#xff0c;欢迎私信咨询&#xff01; &#x1f386;入门专栏&#xff1a;&#x1f387;【MySQL&#xff0…

lua除法bug

故事背景&#xff0c;新来了一个数值&#xff0c;要改公式。神奇的一幕出现了&#xff0c;公式算出一个非常大的数。排查是lua有一个除法bug,1除以大数得到一个非常大的数。 function div(a, b)return tonumber(string.format("%.2f", a/b)) end print(1/73003) pri…