Source: PlayScene-classes/UIManager.js

import FlappyState from "./FlappyState";
import Utils from "../helper-classes/Utils";
import eventsCenter from "./EventsCenter";
import {Difficulty} from "../enums/States";

const States = {
	IDLE: 0,
	RUNNING: 1,
	PAUSED: 2
}

/**
 * @classdesc This class is a usuful tool to manipulate, create, and hide the user interface display.
 * It controls the score, highscore, and all menus in the main game.
 * @author Christian P. Auman
 * @memberOf module:PlayScene
 * @class
 */
class UIManager extends FlappyState {
	constructor(scene, difficulty) {
		super(States.IDLE)
		this.scene = scene
		this.difficulty = difficulty
		this.score = 0
		this.highScore = 0
		// this.scoreText = null
		// this.highScoreText = null
		this.startPos = null
		this.depth = 2000
		
		this.buttonSize = {
			width: 195,
			height: 61
		}
		this.menuSize = {
			width: 348,
			height: 540
		}
		
		this.controlsDialogue = null
		this.tweens = {
			controlsDialogueTween: null,
			scoreTextTween: null,
			highScoreTextTween: null,
		}
	}
	
	/**
	 * preload() is a method that is called at the start which allows for loading images and assets that
	 * are needed in the game before use.
	 */
	preload() {
		window.addEventListener('checkpoint', () => {
			this.addPoint()
		})
		this.scene.load.image('star-particle', '/assets/star-particle.png')
		this.scene.load.image('death-menu', '/assets/death-menu.png')
		this.scene.load.spritesheet('restart-button', 'assets/restart-button.png', {
			startFrame: 0,
			endFrame: 1,
			frameHeight: this.buttonSize.height,
			frameWidth: this.buttonSize.width
		})
		this.scene.load.spritesheet('menu-button', 'assets/menu-button.png', {
			startFrame: 0,
			endFrame: 1,
			frameHeight: this.buttonSize.height,
			frameWidth: this.buttonSize.width
		})
	}
	
	/**
	 * creates and initializes the score, highscore, scoretext, and highscoretext gameobjects
	 */
	setupScore() {
		// setting the starting position for the ui to base off of
		this.startPos = {
			x: this.scene.game.canvas.width / 2,
			y: this.scene.game.canvas.height / 4
		}
		// creating the score tet ui
		this.scoreText = this.scene.add.text(this.startPos.x, this.startPos.y, 'a', {
			fontFamily: 'FlappyFont',
			fontSize: '100px',
			strokeThickness: 5,
			stroke: 'black'
		})
		// setting initial values for the score text
		this.scoreText.depth = this.depth
		this.scoreText.setOrigin(0.5, 0.5)
		// updating the value and position of the score text
		this.updateScoreText()
		// creating the highscore text ui
		this.highScoreText = this.scene.add.text(this.startPos.x, this.startPos.y, 'a', {
			fontFamily: 'FlappyFont',
			fontSize: '50px',
			strokeThickness: 5,
			stroke: 'black'
		})
		// setting up the initial values for the highscore text ui
		this.highScoreText.depth = this.depth
		this.highScoreText.setOrigin(0.5, 0.5)
		// initial size for the score text
		this.scoreSize = {
			width: this.scoreText.displayWidth,
			height: this.scoreText.displayHeight
		}
		// updating the value and position of the highscore text
		this.updateHighScoreText()
	}
	
	setupControlsDialogue() {
		this.controlsDialogue = this.scene.add.text(this.scene.game.canvas.width / 2, this.scene.game.canvas.height / 2, 'PRESS SPACE TO JUMP...', {
			fontFamily: 'FlappyFont',
			fontSize: '50px'
		})
		this.controlsDialogue.depth = this.depth
		this.controlsDialogue.setOrigin(0.5, 0.5)
		this.showControlsDialogue(150)
	}
	
	setupDeathMenu() {
		const BUTTON_OFFSET = 10
		const BUTTON_GROUP_BOTTOM_PADDING = (this.menuSize.width - this.buttonSize.width) / 2
		this.deathMenu = this.scene.add.image(this.scene.game.canvas.width / 2, this.scene.game.canvas.height / 2, 'death-menu')
			.setOrigin(0.5, 0.5)
			.setScale(0)
			.setAlpha(0)
			.setDepth(this.depth)
		this.menuButton = this.scene.add.sprite(this.deathMenu.x, this.deathMenu.y + this.menuSize.height / 2 - this.buttonSize.height  - BUTTON_GROUP_BOTTOM_PADDING, 'menu-button')
			.setOrigin(0.5, 0)
			.setScale(0)
			.setAlpha(0)
			.setInteractive({cursor: 'pointer'})
			.setDepth(this.depth)
		this.restartButton = this.scene.add.sprite(this.menuButton.x, this.menuButton.y - this.buttonSize.height - BUTTON_OFFSET, 'restart-button')
			.setOrigin(0.5, 0)
			.setScale(0)
			.setAlpha(0)
			.setInteractive({cursor: 'pointer'})
			.setDepth(this.depth)
		this.menuScoreText = this.scene.add.text(this.deathMenu.x, this.deathMenu.y - this.menuSize.height / 4, '', {
			fontFamily: 'FlappyFont',
			fontSize: '50px',
			strokeThickness: 5,
			stroke: 'black'
		})
			.setOrigin(0.5, 1)
			.setDepth(this.depth + 3)
		this.menuScoreText.setPosition(this.menuScoreText.x, this.menuScoreText.y + this.menuScoreText.displayHeight / 2)
			.setAlpha(0)
			.setScale(0)
		this.menuHighScoreText = this.scene.add.text(this.menuScoreText.x, this.menuScoreText.y + 100, '', {
			fontFamily: 'FlappyFont',
			fontSize: '50px',
			strokeThickness: 5,
			stroke: 'black'
		})
			.setOrigin(0.5, 1)
			.setDepth(this.depth + 3)
			.setAlpha(0)
			.setScale(0)
	}
	
	create() {
		this.setupScore()
		this.setupControlsDialogue()
		this.setupDeathMenu()
		this.particleManager = this.scene.add.particles('star-particle')
		this.starEmmiter = this.createStars(this.scoreText, {})
		this.menuHighScoreText.setOrigin(0.5, 0.5)
			.setScale(1)
		this.menuStarEmmiter = this.createStars(this.menuHighScoreText, {frequency: 150, scale: {min: 0.05, max: 0.3}})
		this.menuHighScoreText
			.setScale(0)
		this.particleManager.depth = 10001
		this.tweens.scoreTextTween = this.fadeScaleAnim(this.scoreText, {x: this.scene.game.canvas.width / 2 - this.scoreText.displayWidth / 4, delay: 0})
		this.tweens.highScoreTextTween = this.fadeScaleAnim(this.highScoreText, {x: this.scene.game.canvas.width / 2 - this.highScoreText.displayWidth / 4, delay: 100})
	}
	
	createStars(text, {y=0, frequency=100, scale={min: 0.1, max: 0.5}}) {
		return this.particleManager.createEmitter({
			speed: 20,
			lifespan: 500,
			alpha: {start: 1, end: 0},
			scale: scale,
			on: false,
			y: y,
			frequency: frequency,
			quantity: 3,
			blendMode: 'ADD',
			emitZone: {type: 'random', source: this.getSource(text)}
		})
	}
	
	getSource(text) {
		return {getRandomPoint: function(vec) {
			let x = 0
			let y = 0
			let alpha = 0
			do {
				x = Phaser.Math.RND.between(0, text.displayWidth - 10)
				y = Phaser.Math.RND.between(0, text.displayHeight - 10)
				alpha = text.texture.getPixel(x, y).alpha
			} while(alpha <= 0.9)
			return vec.setTo(x + text.x - text.displayWidth / 2, y + text.y - text.displayHeight / 2)
		}}
	}
	
	setScore(score) {
		this.score = score
		this.updateScoreText()
	}
	
	updateScoreText() {
		this.scoreText.setText(this.score + '')
		// this.scoreText.x = this.startPos.x
	}
	
	setHighScore(score) {
		this.highScore = score
		this.updateHighScoreText()
	}
	
	updateHighScoreText() {
		this.highScoreText.setText(this.highScore + "")
		this.highScoreText.x = this.startPos.x
		this.highScoreText.y = this.startPos.y + this.scoreSize.height * .9
	}
	
	startG() {
		super.start()
		if (this.tweens.controlsDialogueTween)
			this.tweens.controlsDialogueTween.remove()
		this.tweens.controlsDialogueTween = this.fadeScaleAnim(this.controlsDialogue, {
			duration: 1000,
			x: this.scene.game.canvas.width / 2 - this.controlsDialogue.displayWidth / 2,
			from: {
				alpha: 1,
				scale: 1,
			},
			to: {
				alpha: 0,
				scale: 0,
				direction: '+',
				
			}
		})
		this.tweens.controlsDialogueTween.on('complete', () => {
			this.controlsDialogue.visible = false
			this.tweens.controlsDialogueTween.remove()
		})
	}
	
	stopG() {
		if (this.currentState != States.PAUSED) {
			super.stop()
			if (this.score > this.highScore) {
				this.setHighScore(this.score)
				if (!this.menuStarEmmiter.on) {
					this.menuStarEmmiter.visible = true
					this.menuStarEmmiter.start()
				}
				this.setRainbowTint(this.menuHighScoreText)
			} else {
				this.resetColor(this.menuHighScoreText)
			}
			this.starEmmiter.stop()
			this.starEmmiter.visible = false
			this.menuHighScoreText.setText(this.highScore)
			this.menuScoreText.setText(this.score)
			Utils.scaleOut(this.scene, {duration: 1000}, this.scoreText)
			Utils.scaleOut(this.scene, {duration: 1000}, this.highScoreText)
			Utils.scaleIn(this.scene, {delay: 200}, null, this.deathMenu, this.restartButton, this.menuButton, this.menuScoreText)
			Utils.scaleIn(this.scene, {delay: 200, scale: this.score > this.highScore?1.5:1}, null, this.menuHighScoreText)
			this.scene.input.once('gameobjectdown', (pointer, objectHit) => {
				if (objectHit === this.restartButton) {
					this.restartButton.setFrame(1)
				} else if (objectHit === this.menuButton) {
					this.menuButton.setFrame(1)
				}
			})
			this.scene.input.once('gameobjectup', (pointer, objectHit) => {
				if (objectHit === this.restartButton) {
					this.restartButton.setFrame(0)
					setTimeout(() => {
						window.dispatchEvent(new Event('reset'))
					}, 200)
				} else if (objectHit === this.menuButton) {
					this.menuButton.setFrame(0)
					eventsCenter.emit('loadscene', {sceneToLoad: 'MenuScene', currentScene: this.scene.scene})
				}
				const fadeOut = Utils.scaleOut(this.scene, {duration: 1000}, this.deathMenu, this.restartButton, this.menuButton, this.menuScoreText, this.menuHighScoreText)
			})
		}
	}
	
	resetG() {
		super.reset()
		
		this.menuStarEmmiter.stop()
		this.menuStarEmmiter.visible = false
		this.deathMenu.scale = 0
		this.menuScoreText.scale = 0
		this.menuHighScoreText.scale = 0
		this.menuButton.scale = 0
		this.restartButton.scale = 0
		
		this.starEmmiter.stop()
		this.starEmmiter.visible = false
		this.resetColor(this.scoreText)
		this.setScore(0)
		this.showControlsDialogue()
		this.updateScoreText()
		this.updateHighScoreText()
		
		Utils.scaleIn(this.scene, {}, null, this.highScoreText, this.scoreText)
	}
	
	fadeScaleAnim(text, {delay = 0, duration = 1000, x = 0, easeParams = [4, 3], from={alpha:0, scale: 0.5}, to={alpha: 1, scale: 1, direction: '-'}}) {
		text.alpha = from.alpha
		text.scale = from.scale
		text.visible = true
		const tween =  this.scene.tweens.add({
			targets: [text],
			alpha: to.alpha,
			scale: to.scale,
			delay: delay,
			duration: duration,
			ease: 'Elastic.out',
			easeParams: easeParams
		})
		return tween
	}
	
	bounceAnim(text, {duration=50, delay=0, from={scale: 1}, to={scale: 1.1}}) {
		text.scale = from.scale
		const tween = this.scene.tweens.add({
			targets: [text],
			scale: to.scale,
			duration: duration,
			delay: delay,
			ease: 'Bounce.out',
			yoyo: true
		})
		tween.on('complete', () => {
			tween.remove()
		})
	}
	
	showControlsDialogue(delay=0) {
		this.tweens.controlsDialogueTween = this.fadeScaleAnim(this.controlsDialogue, {
			duration: 1000,
			delay: delay,
		})
	}
	
	addPoint() {
		this.setScore(this.score + 1)
		if (this.score > this.highScore) {
			this.setRainbowTint(this.scoreText)
			if (!this.starEmmiter.on) {
				this.starEmmiter.visible = true
				this.starEmmiter.start()
			}
		} else if (this.score === this.highScore) {
			this.scoreText.setTint(0xf0ffea, 0x2fff00, 0x27491a, 0x98ff72)
		} else {
			const ratio = this.score / this.highScore
			if (ratio < 0.2) {
				this.scoreText.setTint(0x52b788, 0xb7e4c7, 0xb7e4c7, 0x1b4332)
			} else if (ratio < 0.5) {
				this.scoreText.setTint(0x98ff72, 0x98ff72, 0x27491a, 0x27491a)
			} else if (ratio < 0.8) {
				this.scoreText.setTint(0x38b000,0x004b23, 0x9ef01a,  0x007200)
			} else if (ratio < 0.9) {
				this.scoreText.setTint(0x38b000,0x007200, 0x007200,  0x007200)
			}
		}
		this.bounceAnim(this.scoreText, {})
	}
	
	setRainbowTint(text) {
		if (this.difficulty === Difficulty.INSANE) {
			text.setTint(0x2d00f7, 0xf72585, 0x480ca8, 0xff6d00)
		} else if (this.difficulty === Difficulty.HARD) {
			text.setTint(0xff758f, 0xe01e37, 0xff758f, 0xbd1f36)
		} else if (this.difficulty === Difficulty.MEDIUM) {
			text.setTint(0xfcec5d, 0xff5a00, 0xff9500, 0xffe15c)
		} else {
			text.setTint(0xff00ff, 0xffff00, 0x00ff00, 0xff0000)
		}
	}
	
	resetColor(text) {
		text.setTint(0xFFFFFF)
	}
}

export default UIManager