[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下一期哦我们来制作游戏的金钱系统和额外内容的初始搭建。
相关文章:

[Unity Demo]从零开始制作空洞骑士Hollow Knight第二十集:制作专门渲染HUD的相机HUD Camera和画布HUD Canvas
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、制作HUD Camera以及让两个相机同时渲染屏幕二、制作HUD Canvas 1.制作法力条Soul Orb引入库2.制作生命条Health读入数据3.制作吉欧统计数Geo Counter4.制作…...

智能安全配电装置在高校实验室中的应用
摘要:高校实验室是科研人员进行科学研究和实验的场所,通常会涉及到大量的仪器设备和电气设备。电气设备的使用不当或者维护不周可能会引发火灾事故。本文将以一起实验室电气火灾事故为例,对事故原因、危害程度以及防范措施进行分析和总结…...

网络安全等级保护测评机构管理办法(全文)
网络安全等级保护测评机构管理办法(公信安〔2018〕765号) 第一章 总则 第一条 为加强网络安全等级保护测评机构(以下简称“测评机构”)管理,规范测评行为,提高等级测评能力和服务水平,根据《中华人民共和国网络安全法…...

Flutter:shared_preferences数据存储,数据持久化,token等信息存储
官方示例:简单调用 // 初始化示例 final SharedPreferences prefs await SharedPreferences.getInstance(); // 存int await prefs.setInt(counter, 10); // 存bool await prefs.setBool(repeat, true); // 存double await prefs.setDouble(decimal, 1.5); // 存st…...

FileProvider高版本使用,跨进程传输文件
高版本的android对文件权限的管控抓的很严格,理论上两个应用之间的文件传递现在都应该是用FileProvider去实现,这篇博客来一起了解下它的实现原理。 首先我们要明确一点,FileProvider就是一个ContentProvider,所以需要在AndroidManifest.xml里面对它进行声明: <provideran…...

python学习记录18
1 函数的定义 python中的函数指使用某个定义好的名字指代一段完整的代码,在使用名字时可以直接调用整个代码,这个名字叫做函数名。利用函数可以达到编写一次即可多次调用的操作,从而减少代码量。 函数分为内置函数与自定义函数。内置函数例…...

云原生之k8s服务管理
文章目录 服务管理Service服务原理ClusterIP服务 对外发布应用服务类型NodePort服务Ingress安装配置Ingress规则 Dashboard概述 认证和授权ServiceAccount用户概述创建ServiceAccount 权限管理角色与授权 服务管理 Service 服务原理 容器化带来的问题 自动调度:…...

redis工程实战介绍(含面试题)
文章目录 redis单线程VS多线程面试题**redis是多线程还是单线程,为什么是单线程****聊聊redis的多线程特性和IO多路复用****io多路复用模型****redis如此快的原因** BigKey大批量插入数据测试数据key面试题海量数据里查询某一固定前缀的key如果生产上限值keys * ,fl…...

再次讨论下孤注一掷
在孤注一掷中的黑客技术里面,简单介绍了电影孤注一掷中用的一些"黑科技",这里继续讨论下,抛弃这些黑科技,即使在绝对公平的情况下,你也一样赢不了赌场 相对论有一个假设就是光速不变,这里也有个…...

LeetCode46.全排列
LeetCode刷题记录 文章目录 📜题目描述💡解题思路⌨C代码 📜题目描述 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例1 输入:nums [1,2,3] 输出:[[1,2,…...

蓝桥杯-洛谷刷题-day4(C++)
目录 1.高精度乘法 i.P1303 A*B Problem高精度乘法 2.P4924 [1007] 魔法少女小Scarlet i.题目 ii.代码 3.二维数组 i.二维数组的建立 ii.备份 iii.二维数组的转动 4.指令的及时处理 1.高精度乘法 即,将每一位变为数组中的一位,并在数组中以倒序排列&a…...

c++总复习
1. C 中的移动语义及其作用 定义 移动语义是 C 11 引入的一种重要特性,它用于优化对象的资源管理,特别是在涉及对象所有权转移的场景中。传统的 C 语义在对象赋值或传递给函数时,通常会进行拷贝操作,即创建源对象的一个完整副本&…...

设计模式之策略模式-工作实战总结与实现
文章目录 应用场景存在问题解决方案继续延伸 应用场景 假设有这样的业务场景,大数据系统把文件推送过来,根据不同类型采取不同的解析方式。多数的小伙伴就会写出以下的代码: public class Question {public static void main(String[] args…...

E - 11/22 Subsequence题解
文章目录 大致思路代码 大致思路 预处理: 用pos1, pos2, posls 分别记录 1 1 1, 2 2 2 , / / / 在字符串中的『位置』 用cum1 和 cum2 分别存储了 1 1 1 和 2 2 2 的前缀和,这样可以快速获取任意区间内的 1 1 1 和 2 2 2 的『数量』 查询处理: 对于每个查询…...

PyPI 攻击:ChatGPT、Claude 模仿者通过 Python 库传播 JarkaStealer
《Java代码审计》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484219&idx1&sn73564e316a4c9794019f15dd6b3ba9f6&chksmc0e47a67f793f371e9f6a4fbc06e7929cb1480b7320fae34c32563307df3a28aca49d1a4addd&scene21#wechat_redirect 《Web安全》h…...

单片机学习笔记 9. 8×8LED点阵屏
更多单片机学习笔记:单片机学习笔记 1. 点亮一个LED灯单片机学习笔记 2. LED灯闪烁单片机学习笔记 3. LED灯流水灯单片机学习笔记 4. 蜂鸣器滴~滴~滴~单片机学习笔记 5. 数码管静态显示单片机学习笔记 6. 数码管动态显示单片机学习笔记 7. 独立键盘单片机学习笔记 8…...

【大模型-智能体】AutoGen Studio测试和导出工作流程
1. 测试工作流程 AutoGen Studio允许用户针对任务交互式地测试工作流程,并审查由此产生的成果物(如图像、代码和文档)。此外用户还可以查看Agent工作流程在处理任务时的“内心独白”,并查看诸如运行成本(如回合数、令牌…...

【Linux】-学习笔记04
第十二章、磁盘管理 1.查看磁盘空间使用量 1.1df命令 作用: 列出文件系统的磁盘空间占用情况 df,disk free,通过文件系统来快速获取空间大小的信息,当我们删除一个文件的时候,这个文件 不是马上就在文件系统当中消…...

计算机网络:应用层知识点概述及习题
网课资源: 湖科大教书匠 1、概述 习题1 1 在计算机网络体系结构中,应用层的主要功能是 A. 实现进程之间基于网络的通信 B. 通过进程之间的交互来实现特定网络应用 C. 实现分组在多个网络上传输 D. 透明传输比特流 2 以下不属于TCP/IP体系结构应用层范畴…...

如何构建高效的接口自动化测试框架?
🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,涨薪更快 在选择接口测试自动化框架时,需要根据团队的技术栈和项目需求来综合考虑。对于测试团队来说,使用Python相关的测试框架更为便捷。无论选…...

【C++习题】10.反转字符串中的单词 lll
题目: 链接🔗:557.反转字符串中的单词 lll 题目: 代码: class Solution { public:void Reverse(string &s, int start, int end){char tmp;while(start < end){tmp s[start];s[start] s[end];s[end] tmp;…...

undefined symbol: __nvJitLinkComplete_12_4, version libnvJitLink.so.12 问题解决
在部署运行opencompass项目时遇到了如下报错: ImportError: /data/conda/envs/opencompass/lib/python3.10/site-packages/torch/lib/../../nvidia/cusparse/lib/libcusparse.so.12: undefined symbol: __nvJitLinkComplete_12_4, version libnvJitLink.so.12…...

C语言——数组逐元素操作练习
定义一个能容纳10个元素的整形数组a,从键盘读取9个整数存放到前9个数组元素中。 一. 从键盘读取一个整数n和位置p(0<p<8),插入n到数组a中,插入位置:下标p。要求插入点及后续的数组元素都要后移动。 代码如下: …...

HTML的自动定义倒计时,这个配色存一下
<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>自定义倒计时</title><style>* {mar…...

CUDA补充笔记
文章目录 一、不同核函数前缀二、指定kernel要执行的线程数量三、线程需要两个内置坐标变量来唯一标识线程四、不是blocksize越大越好,上限一般是1024个blocksize 一、不同核函数前缀 二、指定kernel要执行的线程数量 总共需要线程数是: 1 * N N个线程…...

C++二级:满足条件的数的累加
现有n个整数,将其中个位数为k的数进行累加求和。 输入 第一行1个整数n。( 0 < n < 1000) 第二行n个非负整数,以空格分隔,每个数不大于100000。 第三行1个整数k。(0 ≤ k ≤ 9) 输出 输出满足题目要求的累加和。…...

【山大909算法题】2014-T1
文章目录 1.原题2.算法思想3.关键代码4.完整代码5.运行结果 1.原题 为带表头的单链表类Chain编写一个成员函数Reverse,该函数对链表进行逆序操作(将链表中的结点按与原序相反的顺序连接),要求逆序操作就地进行,不分配…...

【MySQL实战45讲笔记】基础篇——深入浅出索引(上)
系列文章 基础篇——MySQL 的基础架构 基础篇——redo log 和 binlog 基础篇——事务隔离 目录 系列文章深入浅出索引(上)4.1 索引的常见模型4.2 InnoDB 的索引模型4.3 索引维护4.4 思考:为什么要重建索引以及如何做? 深入浅出索…...

通关C语言自定义类型:联合和枚举
C语言的自定义类型有四个分别是:数组;结构体(struct);联合体(union);枚举(enum)。前面已经讨论过数组和结构体,这期让我们来学习一下联合体和枚举…...

python高阶技巧一
闭包 简单认识一下闭包 以下代码,内层inner函数不仅依赖于自身的参数b,还依赖于外层outer函数的参数a。inner就是一个闭包函数,既能访问外部变量,又保证外部变量不是全局的,不会被篡改掉,确保了外部变量的…...