import * as Pixi from "pixi.js"; import { PixiPointFrom } from "../../lib/pixi/pixify"; import { Vector2 } from "../../lib/util/geometry/vector2"; import { engageLifecycle, LifecycleHandlerBase } from "./LifecycleHandler"; type Props = { args: {}, updaters: {}, delta: number, tick: number, position: Vector2, efficiencyPercent: number // needs to be between 0 and 100 } type State = {} /** * Reference code for pixi gradient or blur filters: * https://github.com/pixijs/pixi.js/blob/dev/packages/filters/filter-blur/src/generateBlurFragSource.ts * https://github.com/pixijs/pixi.js/blob/dev/packages/filters/filter-blur/src/BlurFilterPass.ts * https://github.com/pixijs/pixi.js/tree/dev/packages/filters/filter-blur/src * https://pixijs.download/dev/docs/PIXI.Filter.html * https://github.com/pixijs/pixi-filters/blob/main/filters/radial-blur/src/RadialBlurFilter.js * https://github.com/pixijs/pixi-filters/blob/main/filters/radial-blur/src/radial-blur.frag * https://filters.pixijs.download/main/docs/index.html * https://www.html5gamedevs.com/topic/8424-how-to-blur-just-an-area/ * https://www.html5gamedevs.com/topic/25539-create-gradient-filter-on-sprite/ * https://filters.pixijs.download/main/docs/index.html * https://pixijs.io/examples/#/textures/gradient-resource.js * */ class EfficiencyBarComponent extends LifecycleHandlerBase<Props, State> { public container: Pixi.Container; public state: State = {} private cornerRadius: number = 10; private boundingBoxWidth: number = 100; public boundingBox: Pixi.Graphics; private innerBarWidth: number = 24; private innerBarHeight: number = 200; private textHeight: number = 24; // observed height of the title text, including padding at the top of the text private paddingBottom: number = 12; public innerBar: Pixi.Graphics; public barBorder: Pixi.Graphics; public filter!: Pixi.filters.BlurFilter; public barFill: Pixi.Graphics; public mask: Pixi.Graphics; // object documentation: https://pixijs.download/dev/docs/PIXI.TextStyle.html public titleText: Pixi.Text; private textStyle: Partial<Pixi.TextStyle> = { fontFamily: 'PixelMix', padding: 4, // https://github.com/pixijs/pixi.js/issues/4500 -- otherwise on first load the text bounding box is calculated to be too small and the tops of the f's get cut off fontSize: 26, // use 26 then scale down 50% results in sharper letters than 13 // align: 'center' }; constructor(props: Props) { super(props); this.container = new Pixi.Container(); this.container.interactive = true; this.container.sortableChildren = true; this.titleText = new Pixi.Text('Efficiency', this.textStyle); this.titleText.scale = PixiPointFrom(new Vector2(0.5, 0.5)); this.titleText.anchor = PixiPointFrom(new Vector2(0.5, 0.0)); // center ourselves, left-right this.titleText.zIndex = 0; this.titleText.x = 50; // full container width is 100, we want to be in the middle this.titleText.y = 4; // bit of padding for the top this.container.addChild(this.titleText); this.boundingBox = new Pixi.Graphics(); this.boundingBox.beginFill(0xDDEEFF); // background color is the blue AACCEE, this is very light bluer than that this.boundingBox.drawRoundedRect( 0, 0, this.boundingBoxWidth, this.textHeight + this.innerBarHeight + this.paddingBottom, this.cornerRadius ); // outerbar = the box containing the efficiency text + bar. 100px is just enough width for the word "Efficiency". 236px height was chosen arbitrarily this.boundingBox.zIndex = -1; this.boundingBox.alpha = .8; // let a bit of the background poke through. TODO: actually blur the background?? cant figure out how to do it this.container.addChild(this.boundingBox); this.innerBar = new Pixi.Graphics(); this.innerBar.beginFill(0xFFFFFF); this.innerBar.drawRoundedRect(0, 0, this.innerBarWidth, this.innerBarHeight, this.cornerRadius); // we want the inner bar (containing the actual efficiency colors) to be 40 wide and 200 tall. round the corners at the same radius as the outer box. // this.innerBar.x = 12; this.innerBar.pivot.x = this.innerBarWidth/2; // this is our width over 2 this.innerBar.x = this.boundingBoxWidth/2; // this is outer bar width / 2 this.innerBar.y = this.textHeight; // this is just enough space below the "Efficiency" text to look nice this.innerBar.zIndex = 3; this.container.addChild(this.innerBar); // rainbow red-green gradient for the contents of the inner bar const barFillContainer = new Pixi.Container(); this.barFill = new Pixi.Graphics(); // source: https://www.schemecolor.com/red-orange-green-gradient.php this.barFill.beginFill(0x69B34C); this.barFill.drawRect(0, 0, this.innerBarWidth, this.innerBarHeight/5); this.barFill.beginFill(0xACB334); this.barFill.drawRect(0, 40, this.innerBarWidth, this.innerBarHeight/5); this.barFill.beginFill(0xFAB733); this.barFill.drawRect(0, 80, this.innerBarWidth, this.innerBarHeight/5); this.barFill.beginFill(0xFF8E15); this.barFill.drawRect(0, 120, this.innerBarWidth, this.innerBarHeight/5); this.barFill.beginFill(0xFF4E11); this.barFill.drawRect(0, 160, this.innerBarWidth, this.innerBarHeight/5); this.barFill.pivot.x = this.innerBarWidth / 2; this.barFill.x = this.boundingBoxWidth / 2; this.barFill.y = this.textHeight; // same positioning as innerBar this.barFill.zIndex = 4; // this.container.addChild(this.barFill); barFillContainer.addChild(this.barFill); barFillContainer.zIndex = 4; this.container.addChild(barFillContainer); // this.barFill.filters = [this.filter]; // mask controls how much of the red-green gradient fillings are visible, depending on how well the player is doing const maskContainer = new Pixi.Container(); // this mask moves up and down this.mask = new Pixi.Graphics(); this.mask.beginFill(0x000000, 1); // color and alpha literally dont matter cuz its a mmask this.mask.drawRoundedRect(0, 0, this.innerBarWidth, this.innerBarHeight, this.cornerRadius); // same dims as the inner bar. note that this doesnt take into account the line style of width 2, so it will cause the filling to leak over into the line style. to fix this barBorder is reapplied over the top to cover the leaks. this.mask.zIndex = 30; // doesnt matter this.mask.pivot.x = this.innerBarWidth/2; // left-right center in ourselves this.mask.x = this.boundingBoxWidth/2; // center in the boundingBox this.mask.y = this.textHeight; // same positioning as inner bar // this mask does not const fixedMask = new Pixi.Graphics(); fixedMask.beginFill(0x000000, 1); // color and alpha literally dont matter cuz its a mmask fixedMask.drawRoundedRect(0, 0, this.innerBarWidth, this.innerBarHeight, this.cornerRadius); // same dims as the inner bar. note that this doesnt take into account the line style of width 2, so it will cause the filling to leak over into the line style. to fix this barBorder is reapplied over the top to cover the leaks. fixedMask.zIndex = 31; // doesnt matter fixedMask.pivot.x = this.innerBarWidth/2; // left-right center in ourselves fixedMask.x = this.boundingBoxWidth/2; // center in the boundingBox fixedMask.y = this.textHeight; // same positioning as inner bar maskContainer.addChild(this.mask); // maskContainer.addChild(fixedMask); this.container.addChild(maskContainer); // this.container.addChild(this.mask); // have to add child here -- not sure why this.barFill.mask = maskContainer; this.container.addChild(fixedMask); barFillContainer.mask = fixedMask; // another copy of innerbar, except this time the fill is transparent; we just need the line style to be redrawn so that // the inner filling mask leakage gets hidden this.barBorder = new Pixi.Graphics(); this.barBorder.lineStyle(2, 0x000000, 1); // this.barBorder.beginFill(0x000000, 0); this.barBorder.drawRoundedRect(0, 0, this.innerBarWidth, this.innerBarHeight, this.cornerRadius); this.barBorder.pivot.x = this.innerBarWidth / 2; this.barBorder.x = this.boundingBoxWidth / 2; this.barBorder.y = this.textHeight; this.barBorder.zIndex = 7; this.container.addChild(this.barBorder); } public renderSelf(props: Props) { this.container.position = PixiPointFrom(props.position); if (props.tick < 60 && props.tick % 10 === 5) { // poll for document webfonts loaded; TODO, substitute for listening to actual fonts ready event this.titleText.updateText(false); // false == force reload text even when text has not changed. needed to get new fonts } this.mask.y = (100 - props.efficiencyPercent) * 2 + 24; } } const wrapped = engageLifecycle(EfficiencyBarComponent); // eslint-disable-next-line type wrapped = EfficiencyBarComponent; export { wrapped as EfficiencyBarComponent };