木灵鱼儿

木灵鱼儿

阅读:132

最后更新:2022/07/30/ 18:28:16

Cocos Creator 制作仿DNF血条

前言

最近在想boss的血条制作,比较简单的就是单条血条,但是我想弄个想类似dnf那种,血条减完一条还有一条的那种,于是有了本篇文章

预览图

教程

节点结构

由于cocos原生没有这种血条,我们需要手动创建,我先通过两个单色精灵模拟血条的框架,大概就是一个大的节点里面包含一个小的节点,两个节点颜色不一致,里面的节点宽高都小4px,这样就模拟出边框的效果了。

然后就是血条了,我的原理是通过两个血条节点进行来回替换实现的,当血量有两管血的时候,一开始就扣除时控制第一条血条的width,然后这是这个计算非常重要,它需要return出余值,当它存在余值的时候,说明还能扣血, 此时一瞬间将第一条置于第二条的底下,然后继续扣除第二条的width,从而往复循环,达到效果。

血条来来回的切换的时候,需要控制好它的层级结构。

源码

/*
 * @Author: mulingyuer
 * @Date: 2022-07-27 21:00:02
 * @LastEditTime: 2022-07-30 01:04:37
 * @LastEditors: mulingyuer
 * @Description: boss血条
 * @FilePath: \cocos-aircraft-war\assets\script\article_blood\BossArticleBlood.ts
 * 怎么可能会有bug!!!
 */
import { Node, Sprite, math, UITransform } from "cc";

type BloodOptions = {
  twoBlood: Node;
  oneBlood: Node;
};

class BossArticleBlood {
  private colors: Array<string> = ["#AB1A22", "#FA8600", "#92C702", "#007CC8"];
  private oneBlood: Node;
  private twoBlood: Node;
  private totalBlood: number = 0;
  private maxBloodVolume: number = 0;
  //当前激活的属性
  private activeColorIndex: number = 0;
  private activeBloodIndex: number = 0;
  private hp: number = 0; //最大值
  private rate: number = 1; //扣除的比例

  constructor(options: BloodOptions) {
    const { twoBlood, oneBlood } = options;
    this.oneBlood = oneBlood;
    this.twoBlood = twoBlood;

    //获取血条宽度
    const bloodUIData = this.oneBlood.getComponent(UITransform);
    this.maxBloodVolume = bloodUIData.width;

    this.oneBlood["_mySprite"] = this.oneBlood.getComponent(Sprite);
    this.oneBlood["_myUITransform"] = bloodUIData;
    this.twoBlood["_mySprite"] = this.twoBlood.getComponent(Sprite);
    this.twoBlood["_myUITransform"] = this.twoBlood.getComponent(UITransform);

    this.reset();
  }

  /* 重置 */
  private reset() {
    this.activeColorIndex = 0;

    this.oneBlood.active = false;
    this.twoBlood.active = false;
    this.oneBlood["_myUITransform"].width = this.maxBloodVolume;
    this.twoBlood["_myUITransform"].width = this.maxBloodVolume;

    this.oneBlood.setSiblingIndex(1);
    this.twoBlood.setSiblingIndex(0);
  }

  /**
   * @description: 设置血条
   * @param {number} hp
   * @param {number} max
   * @Date: 2022-07-27 22:44:45
   * @Author: mulingyuer
   */
  public setBlood(hp: number, max: number) {
    this.reset();
    this.hp = hp;
    this.rate = this.maxBloodVolume / max;
    //计算血条数量
    this.totalBlood = Math.floor(hp / max);
    this.activeBloodIndex = this.totalBlood;
    //设置血条颜色
    if (this.totalBlood === 1) {
      this.twoBlood.active = false;

      this.oneBlood["_mySprite"].color = this.getColor(0);
      this.oneBlood.active = true;
    } else {
      this.oneBlood["_mySprite"].color = this.getColor(this.getNextIndex());
      this.twoBlood["_mySprite"].color = this.getColor(this.getNextIndex());

      this.oneBlood.setSiblingIndex(1);
      this.twoBlood.setSiblingIndex(0);

      this.oneBlood.active = true;
      this.twoBlood.active = true;
    }
  }

  /**
   * @description: 扣血
   * @param {number} val
   * @Date: 2022-07-27 22:48:10
   * @Author: mulingyuer
   */
  public buckleBlood(val: number): void {
    if (this.hp <= 0) return;

    //一条血
    if (this.totalBlood === 1) {
      this.specBloodBuckle(this.oneBlood, val);
      return;
    }

    //多条血条
    let remeaning = this.specBloodBuckle(this.oneBlood, val);
    while (remeaning > 0) {
      this.activeBloodIndex -= 1;
      const oldBlood = this.oneBlood;
      //更改定义
      this.oneBlood = this.twoBlood;
      this.twoBlood = oldBlood;

      this.oneBlood.setSiblingIndex(1);
      this.twoBlood.setSiblingIndex(0);

      this.twoBlood["_mySprite"].color = this.getColor(this.getNextIndex());
      this.twoBlood["_myUITransform"].width = this.maxBloodVolume;

      if (this.activeBloodIndex <= 1 && this.twoBlood.active) {
        this.twoBlood.active = false;
      }

      //再次扣第一条血
      remeaning = this.specBloodBuckle(this.oneBlood, remeaning, true);
    }
  }

  /* 指定进度条扣血 */
  private specBloodBuckle(blood: Node, val: number, isRateVal = false): number {
    if (!isRateVal) this.hp -= val;
    if (this.hp < 0) this.hp = 0;

    const activeWidth = blood["_myUITransform"].width;
    const realVal = isRateVal ? val : this.getBloodVal(val);

    if (this.hp <= 0) {
      blood["_myUITransform"].width = 0;
      return 0;
    } else if (activeWidth - realVal >= 0) {
      blood["_myUITransform"].width = activeWidth - realVal;
      return 0;
    } else {
      blood["_myUITransform"].width = 0;
      return realVal - activeWidth;
    }
  }

  /* 获取实际血条减的伤害 */
  private getBloodVal(val: number) {
    return Math.ceil(val * this.rate);
  }

  /* 获取下一个下标 */
  private getNextIndex() {
    if (this.activeColorIndex >= this.colors.length) {
      this.activeColorIndex = 0;
    }
    return this.activeColorIndex++;
  }

  /* 获取指定下标的颜色 */
  private getColor(index: number): math.Color {
    return math.color(this.colors[index]);
  }
}

export default BossArticleBlood;

我封装了BossArticleBlood类,接受两个node节点,需要的时候new出来,保存起来实例,用的时候先通过setBlood方法定好血条,然后扣血时通过buckleBlood处理。

这里这是我的一次思路,后续的应用,比如如何知道boss没血了,通过通过接收回调的方式处理,这就自行延展了。

版权申明

本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。

关于作者

站点职位 博主
获得点赞 0
文章被阅读 132

相关文章