added slider and textInput uiElements

This commit is contained in:
Joe Weaver 2020-11-18 12:20:33 -05:00
parent fff1ac4907
commit 3d275ba7f9
13 changed files with 240 additions and 42 deletions

View File

@ -125,6 +125,16 @@ export default class InputReceiver{
}
}
getKeysJustPressed(): Array<string> {
let keys = Array<string>();
this.keyJustPressed.forEach(key => {
if(this.keyJustPressed.get(key)){
keys.push(key);
}
});
return keys;
}
isPressed(key: string): boolean {
if(this.keyPressed.has(key)){
return this.keyPressed.get(key)

View File

@ -1,12 +1,12 @@
import GameNode from "./GameNode";
import Vec2 from "../DataTypes/Vec2";
import { Region } from "../DataTypes/Interfaces/Descriptors";
import { Region, Renderable } from "../DataTypes/Interfaces/Descriptors";
import AABB from "../DataTypes/Shapes/AABB";
/**
* The representation of an object in the game world that can be drawn to the screen
*/
export default abstract class CanvasNode extends GameNode implements Region {
export default abstract class CanvasNode extends GameNode implements Region, Renderable {
private _size: Vec2;
private _scale: Vec2;
private _boundary: AABB;
@ -21,8 +21,6 @@ export default abstract class CanvasNode extends GameNode implements Region {
this._scale.setOnChange(() => this.scaleChanged());
this._boundary = new AABB();
this.updateBoundary();
this.size.set(101, 101);
}
get size(): Vec2 {

View File

@ -4,7 +4,7 @@ import Receiver from "../Events/Receiver";
import Emitter from "../Events/Emitter";
import Scene from "../Scene/Scene";
import Layer from "../Scene/Layer";
import { Physical, Positioned, isRegion, Unique, Updateable, Actor, AI } from "../DataTypes/Interfaces/Descriptors"
import { Physical, Positioned, isRegion, Unique, Updateable, Actor, AI, Debug_Renderable } from "../DataTypes/Interfaces/Descriptors"
import Shape from "../DataTypes/Shapes/Shape";
import Map from "../DataTypes/Map";
import AABB from "../DataTypes/Shapes/AABB";
@ -13,7 +13,7 @@ import NavigationPath from "../Pathfinding/NavigationPath";
/**
* The representation of an object in the game world
*/
export default abstract class GameNode implements Positioned, Unique, Updateable, Physical, Actor {
export default abstract class GameNode implements Positioned, Unique, Updateable, Physical, Actor, Debug_Renderable {
/*---------- POSITIONED ----------*/
private _position: Vec2;
@ -227,4 +227,6 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
};
abstract update(deltaT: number): void;
debug_render = (ctx: CanvasRenderingContext2D): void => {};
}

View File

@ -100,7 +100,6 @@ export default class Label extends UIElement{
// Grab the global alpha so we can adjust it for this render
let previousAlpha = ctx.globalAlpha;
ctx.globalAlpha = this.getLayer().getAlpha();
let origin = this.scene.getViewTranslation(this);
@ -108,23 +107,30 @@ export default class Label extends UIElement{
let offset = this.calculateTextOffset(ctx);
// Stroke and fill a rounded rect and give it text
ctx.globalAlpha = this.backgroundColor.a;
ctx.fillStyle = this.calculateBackgroundColor();
ctx.fillRoundedRect(this.position.x - origin.x - this.size.x/2, this.position.y - origin.y - this.size.y/2,
this.size.x, this.size.y, this.borderRadius);
ctx.strokeStyle = this.calculateBorderColor();
ctx.globalAlpha = this.borderColor.a;
ctx.lineWidth = this.borderWidth;
ctx.strokeRoundedRect(this.position.x - origin.x - this.size.x/2, this.position.y - origin.y - this.size.y/2,
this.size.x, this.size.y, this.borderRadius);
ctx.fillStyle = this.calculateTextColor();
ctx.globalAlpha = this.textColor.a;
ctx.fillText(this.text, this.position.x + offset.x - origin.x - this.size.x/2, this.position.y + offset.y - origin.y - this.size.y/2);
ctx.globalAlpha = previousAlpha;
}
debug_render = (ctx: CanvasRenderingContext2D): void => {
let origin = this.scene.getViewTranslation(this);
ctx.lineWidth = 4;
ctx.strokeStyle = "#00FF00"
let b = this.boundary;
ctx.strokeRect(b.x - b.hw - origin.x, b.y - b.hh - origin.y, b.hw*2, b.hh*2);
}
};
}

View File

@ -0,0 +1,76 @@
import Vec2 from "../../DataTypes/Vec2";
import Color from "../../Utils/Color";
import MathUtils from "../../Utils/MathUtils";
import UIElement from "../UIElement";
export default class Slider extends UIElement {
/** The value of the slider from [0, 1] */
protected value: number;
protected nibColor: Color;
protected sliderColor: Color;
public onValueChange: (value: number) => void;
public onValueChangeEventId: string;
constructor(position: Vec2){
super(position);
this.value = 0;
this.nibColor = Color.RED;
this.sliderColor = Color.BLACK;
this.backgroundColor = Color.TRANSPARENT;
this.borderColor = Color.TRANSPARENT;
// Set a default size
this.size.set(200, 20);
}
protected valueChanged(): void {
if(this.onValueChange){
this.onValueChange(this.value);
}
if(this.onValueChangeEventId){
this.emitter.fireEvent(this.onValueChangeEventId, {target: this, value: this.value});
}
}
update(deltaT: number): void {
super.update(deltaT);
if(this.isClicked){
let val = MathUtils.invLerp(this.position.x - this.size.x/2, this.position.x + this.size.x/2, this.input.getMousePosition().x);
this.value = MathUtils.clamp01(val);
this.valueChanged();
}
}
render(ctx: CanvasRenderingContext2D): void {
// Grab the global alpha so we can adjust it for this render
let previousAlpha = ctx.globalAlpha;
ctx.globalAlpha = this.getLayer().getAlpha();
let origin = this.scene.getViewTranslation(this);
// Calcualate the slider size
let sliderSize = new Vec2(this.size.x, 2);
// Draw the slider
ctx.fillStyle = this.sliderColor.toString();
ctx.fillRoundedRect(this.position.x - origin.x - sliderSize.x/2, this.position.y - origin.y - sliderSize.y/2,
sliderSize.x, sliderSize.y, this.borderRadius);
// Calculate the nib size and position
let nibSize = new Vec2(10, this.size.y);
let x = MathUtils.lerp(this.position.x - this.size.x/2, this.position.x + this.size.x/2, this.value);
let nibPosition = new Vec2(x, this.position.y);
// Draw the nib
ctx.fillStyle = this.nibColor.toString();
ctx.fillRoundedRect(nibPosition.x - origin.x - nibSize.x/2, nibPosition.y - origin.y - nibSize.y/2,
nibSize.x, nibSize.y, this.borderRadius);
// Reset the alpha
ctx.globalAlpha = previousAlpha;
}
}

View File

@ -0,0 +1,81 @@
import Vec2 from "../../DataTypes/Vec2";
import Color from "../../Utils/Color";
import Label from "./Label";
export default class TextInput extends Label {
focused: boolean;
cursorCounter: number;
constructor(position: Vec2){
super(position, "");
this.focused = false;
this.cursorCounter = 0;
// Give a default size to the x only
this.size.set(200, this.fontSize);
this.hAlign = "left";
this.borderColor = Color.BLACK;
this.backgroundColor = Color.WHITE;
}
update(deltaT: number): void {
super.update(deltaT);
if(this.input.isMouseJustPressed()){
let clickPos = this.input.getMousePressPosition();
if(this.contains(clickPos.x, clickPos.y)){
this.focused = true;
this.cursorCounter = 30;
} else {
this.focused = false;
}
}
if(this.focused){
let keys = this.input.getKeysJustPressed();
let nums = "1234567890";
let specialChars = "`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?";
let letters = "qwertyuiopasdfghjklzxcvbnm";
let mask = nums + specialChars + letters;
// THIS ISN'T ACTUALLY AN ERROR, DISREGARD
keys = keys.filter(key => mask.includes(key));
let shiftPressed = this.input.isPressed("shift");
let backspacePressed = this.input.isJustPressed("backspace");
let spacePressed = this.input.isJustPressed("space");
if(backspacePressed){
this.text = this.text.substring(0, this.text.length - 1);
} else if(spacePressed){
this.text += " ";
} else if(keys.length > 0) {
if(shiftPressed){
this.text += keys[0].toUpperCase();
} else {
this.text += keys[0];
}
}
}
}
render(ctx: CanvasRenderingContext2D): void {
// Show a cursor sometimes
if(this.focused && this.cursorCounter % 60 > 30){
this.text += "|";
}
super.render(ctx);
if(this.focused){
if(this.cursorCounter % 60 > 30){
this.text = this.text.substring(0, this.text.length - 1);
}
this.cursorCounter += 1;
if(this.cursorCounter >= 60){
this.cursorCounter = 0;
}
}
}
}

View File

@ -1,4 +1,6 @@
export enum UIElementType {
BUTTON = "BUTTON",
LABEL = "LABEL",
SLIDER = "SLIDER",
TEXT_INPUT = "TEXTINPUT"
}

View File

@ -1,15 +1,15 @@
import Scene from "../Scene";
import UIElement from "../../Nodes/UIElement";
import Layer from "../Layer";
import Graphic from "../../Nodes/Graphic";
import Sprite from "../../Nodes/Sprites/Sprite";
import { GraphicType } from "../../Nodes/Graphics/GraphicTypes";
import { UIElementType } from "../../Nodes/UIElements/UIElementTypes";
import Point from "../../Nodes/Graphics/Point";
import Vec2 from "../../DataTypes/Vec2";
import Shape from "../../DataTypes/Shapes/Shape";
import Button from "../../Nodes/UIElements/Button";
import Label from "../../Nodes/UIElements/Label";
import Slider from "../../Nodes/UIElements/Slider";
import TextInput from "../../Nodes/UIElements/TextInput";
import Rect from "../../Nodes/Graphics/Rect";
export default class CanvasNodeFactory {
@ -38,6 +38,12 @@ export default class CanvasNodeFactory {
case UIElementType.LABEL:
instance = this.buildLabel(options);
break;
case UIElementType.SLIDER:
instance = this.buildSlider(options);
break;
case UIElementType.TEXT_INPUT:
instance = this.buildTextInput(options);
break;
default:
throw `UIElementType '${type}' does not exist, or is registered incorrectly.`
}
@ -126,6 +132,18 @@ export default class CanvasNodeFactory {
return new Label(options.position, options.text)
}
buildSlider(options: Record<string, any>): Slider {
this.checkIfPropExists("Slider", options, "position", Vec2, "Vec2");
return new Slider(options.position);
}
buildTextInput(options: Record<string, any>): TextInput {
this.checkIfPropExists("TextInput", options, "position", Vec2, "Vec2");
return new TextInput(options.position);
}
buildPoint(options?: Record<string, any>): Point {
this.checkIfPropExists("Point", options, "position", Vec2, "Vec2");
@ -142,7 +160,7 @@ export default class CanvasNodeFactory {
/* ---------- ERROR HANDLING ---------- */
checkIfPropExists<T>(objectName: string, options: Record<string, any>, prop: string, type: (new (...args: any) => T) | string, typeName?: string){
if(!options || !options[prop]){
if(!options || options[prop] === undefined){
// Check that the options object has the property
throw `${objectName} object requires argument ${prop} of type ${typeName}, but none was provided.`;
} else {

View File

@ -42,6 +42,10 @@ export default class Layer {
this.depth = 0;
}
getName(): string {
return this.name;
}
setPaused(pauseValue: boolean): void {
this.paused = pauseValue;
}

View File

@ -75,8 +75,8 @@ export default class Level1 extends Scene {
// Add UI
this.addUILayer("UI");
this.coinCountLabel = this.add.uiElement(UIElementType.LABEL, "UI", {position: new Vec2(80, 30), text: "Coins: 0"});
this.livesCountLabel = this.add.uiElement(UIElementType.LABEL, "UI", {position: new Vec2(600, 30), text: "Lives: 3"});
this.coinCountLabel = <Label>this.add.uiElement(UIElementType.LABEL, "UI", {position: new Vec2(80, 30), text: "Coins: 0"});
this.livesCountLabel = <Label>this.add.uiElement(UIElementType.LABEL, "UI", {position: new Vec2(600, 30), text: "Lives: 3"});
}
updateScene(deltaT: number): void {
@ -101,8 +101,8 @@ export default class Level1 extends Scene {
this.coinCountLabel.setText("Coins: " + this.coinCount);
} else if(event.type === MarioEvents.PLAYER_HIT_COIN_BLOCK){
console.log("Hit Coin Block")
console.log(event.data.get("node") === this.player);
this.coinCount += 1;
this.coinCountLabel.setText("Coins: " + this.coinCount);
}
}

View File

@ -1,33 +1,46 @@
import Vec2 from "../../DataTypes/Vec2";
import Debug from "../../Debug/Debug";
import InputHandler from "../../Input/InputHandler";
import InputReceiver from "../../Input/InputReceiver";
import { GraphicType } from "../../Nodes/Graphics/GraphicTypes";
import Button from "../../Nodes/UIElements/Button";
import Label from "../../Nodes/UIElements/Label";
import Slider from "../../Nodes/UIElements/Slider";
import { UIElementType } from "../../Nodes/UIElements/UIElementTypes";
import Scene from "../../Scene/Scene";
import Color from "../../Utils/Color";
import Level1 from "./Level1";
export default class MainMenu extends Scene {
playBtn: Button;
loadScene(): void {
}
startScene(): void {
this.addUILayer("Main");
let size = this.viewport.getHalfSize();
this.viewport.setFocus(size);
this.playBtn = <Button>this.add.uiElement(UIElementType.BUTTON, "Main", {position: new Vec2(size.x, size.y), text: "Play Game"});
this.playBtn.setBackgroundColor(Color.GREEN);
this.playBtn.setPadding(new Vec2(50, 10));
this.playBtn.onClick = () => {
console.log("Play");
let playBtn = <Button>this.add.uiElement(UIElementType.BUTTON, "Main", {position: new Vec2(size.x, size.y), text: "Play Game"});
playBtn.setBackgroundColor(Color.GREEN);
playBtn.setPadding(new Vec2(50, 10));
playBtn.onClick = () => {
let sceneOptions = {
physics: {
physicsLayerNames: ["ground", "player", "enemy", "coin"],
numPhyiscsLayers: 4,
physicsLayerCollisions:
[
[0, 1, 1, 1],
[1, 0, 0, 1],
[1, 0, 0, 1],
[1, 1, 1, 0]
]
}
}
this.sceneManager.changeScene(Level1, sceneOptions);
}
let slider = <Slider>this.add.uiElement(UIElementType.SLIDER, "Main", {position: new Vec2(size.x, size.y*1.5)});
let label = this.add.uiElement(UIElementType.LABEL, "Main", {position: new Vec2(size.x + 150, size.y*1.5), text: ""});
slider.onValueChange = (value) => (<Label>label).setText(value.toString());
this.add.uiElement(UIElementType.TEXT_INPUT, "Main", {position: new Vec2(size.x, size.y*1.7)});
}
updateScene(): void {

View File

@ -2,6 +2,7 @@ import Vec2 from "../../../../DataTypes/Vec2";
import GameEvent from "../../../../Events/GameEvent";
import MathUtils from "../../../../Utils/MathUtils";
import { CustomGameEventType } from "../../../CustomGameEventType";
import Level1, { MarioEvents } from "../../../Mario/Level1";
import { PlayerStates } from "./PlayerController";
import PlayerState from "./PlayerState";
@ -28,6 +29,7 @@ export default class Jump extends PlayerState {
// If coin block, change to empty coin block
if(tile === 4){
this.parent.tilemap.setTileAtRowCol(pos, 12);
this.emitter.fireEvent(MarioEvents.PLAYER_HIT_COIN_BLOCK);
}
}

View File

@ -12,20 +12,6 @@ function main(){
let game = new GameLoop(options);
game.start();
let sceneOptions = {
physics: {
physicsLayerNames: ["ground", "player", "enemy", "coin"],
numPhyiscsLayers: 4,
physicsLayerCollisions:
[
[0, 1, 1, 1],
[1, 0, 0, 1],
[1, 0, 0, 1],
[1, 1, 1, 0]
]
}
}
let sm = game.getSceneManager();
sm.addScene(MainMenu, {});
}