improved webGL shader, added label shader

This commit is contained in:
Joe Weaver 2021-02-19 15:35:57 -05:00
parent 4214ef7fd4
commit 924469a2cd
43 changed files with 574 additions and 650 deletions

77
dist/builtin/shaders/label.fshader vendored Normal file
View File

@ -0,0 +1,77 @@
precision mediump float;
uniform vec4 u_BackgroundColor;
uniform vec4 u_BorderColor;
uniform float u_BorderWidth;
uniform float u_BorderRadius;
uniform vec2 u_MaxSize;
varying vec4 v_Position;
void main(){
vec2 adj_MaxSize = u_MaxSize - u_BorderWidth;
vec2 rad_MaxSize = u_MaxSize - u_BorderRadius;
vec2 rad2_MaxSize = u_MaxSize - 2.0*u_BorderRadius;
bool inX = (v_Position.x < adj_MaxSize.x) && (v_Position.x > -adj_MaxSize.x);
bool inY = (v_Position.y < adj_MaxSize.y) && (v_Position.y > -adj_MaxSize.y);
bool inRadiusRangeX = (v_Position.x < rad_MaxSize.x) && (v_Position.x > -rad_MaxSize.x);
bool inRadiusRangeY = (v_Position.y < rad_MaxSize.y) && (v_Position.y > -rad_MaxSize.y);
bool inRadius2RangeX = (v_Position.x < rad2_MaxSize.x) && (v_Position.x > -rad2_MaxSize.x);
bool inRadius2RangeY = (v_Position.y < rad2_MaxSize.y) && (v_Position.y > -rad2_MaxSize.y);
if(inX && inY){
// Inside bounds, draw background color
gl_FragColor = u_BackgroundColor;
} else {
// In boundary, draw border color
gl_FragColor = u_BorderColor;
}
// This isn't working well right now
/*
if(inRadius2RangeX || inRadius2RangeY){
// Draw normally
if(inX && inY){
// Inside bounds, draw background color
gl_FragColor = u_BackgroundColor;
} else {
// In boundary, draw border color
gl_FragColor = u_BorderColor;
}
} else if(inRadiusRangeX || inRadiusRangeY){
// Draw a rounded boundary for the inner part
float x = v_Position.x - sign(v_Position.x)*rad2_MaxSize.x;
float y = v_Position.y - sign(v_Position.y)*rad2_MaxSize.y;
float radSq = x*x + y*y;
float bRadSq = u_BorderRadius*u_BorderRadius;
if(radSq > bRadSq){
// Outside of radius - draw as transparent
gl_FragColor = u_BorderColor;
} else {
gl_FragColor = u_BackgroundColor;
}
} else {
// Both coordinates are in the circular section
float x = v_Position.x - sign(v_Position.x)*rad_MaxSize.x;
float y = v_Position.y - sign(v_Position.y)*rad_MaxSize.y;
float radSq = x*x + y*y;
float bRadSq = u_BorderRadius*u_BorderRadius;
if(radSq > bRadSq){
// Outside of radius - draw as transparent
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
} else if(sqrt(bRadSq) - sqrt(radSq) < u_BorderWidth) {
// In border
gl_FragColor = u_BorderColor;
} else {
gl_FragColor = u_BackgroundColor;
}
}
*/
}

12
dist/builtin/shaders/label.vshader vendored Normal file
View File

@ -0,0 +1,12 @@
attribute vec4 a_Position;
uniform mat4 u_Transform;
varying vec4 v_Position;
void main(){
gl_Position = u_Transform * a_Position;
// Pass position to the fragment shader
v_Position = a_Position;
}

View File

@ -1,24 +0,0 @@
precision mediump float;
uniform vec4 u_Color;
varying vec4 v_Position;
void main(){
// Default alpha is 0
float alpha = 0.0;
// Radius is 0.5, since the diameter of our quad is 1
float radius = 0.5;
// Get the distance squared of from (0, 0)
float dist_sq = v_Position.x*v_Position.x + v_Position.y*v_Position.y;
if(dist_sq < radius*radius){
// Multiply by 4, since distance squared is at most 0.25
alpha = 4.0*dist_sq;
}
// Use the alpha value in our color
gl_FragColor = vec4(u_Color.rgb, alpha);
}

View File

@ -1,11 +0,0 @@
attribute vec4 a_Position;
uniform mat4 u_Transform;
varying vec4 v_Position;
void main(){
gl_Position = u_Transform * a_Position;
v_Position = a_Position;
}

View File

@ -1,145 +0,0 @@
{
"name": "player_spaceship",
"spriteSheetImage": "player_spaceship.png",
"spriteWidth": 256,
"spriteHeight": 256,
"leftBuffer": 0,
"rightBuffer": 0,
"topBuffer": 0,
"bottomBuffer": 0,
"columns": 5,
"rows": 5,
"animations": [
{
"name": "idle",
"repeat": true,
"frames": [
{
"index": 0,
"duration": 10
},
{
"index": 1,
"duration": 10
},
{
"index": 2,
"duration": 10
}
]
},
{
"name": "boost",
"repeat": true,
"frames": [
{
"index": 3,
"duration": 10
},
{
"index": 4,
"duration": 10
},
{
"index": 5,
"duration": 10
}
]
},
{
"name": "shield",
"repeat": false,
"frames": [
{
"index": 6,
"duration": 10
},
{
"index": 7,
"duration": 10
},
{
"index": 8,
"duration": 10
},
{
"index": 9,
"duration": 10
},
{
"index": 10,
"duration": 10
},
{
"index": 11,
"duration": 10
},
{
"index": 12,
"duration": 10
}
]
},
{
"name": "explode",
"repeat": false,
"frames": [
{
"index": 13,
"duration": 10
},
{
"index": 14,
"duration": 10
},
{
"index": 15,
"duration": 10
},
{
"index": 16,
"duration": 10
},
{
"index": 17,
"duration": 10
},
{
"index": 18,
"duration": 10
},
{
"index": 19,
"duration": 10
},
{
"index": 20,
"duration": 10
},
{
"index": 21,
"duration": 10
},
{
"index": 22,
"duration": 10
},
{
"index": 23,
"duration": 10
}
]
},
{
"name": "explode",
"repeat": false,
"onEnd": "dead",
"frames": [
{
"index": 24,
"duration": 1
}
]
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

View File

@ -1,48 +0,0 @@
import Vec2 from "./Wolfie2D/DataTypes/Vec2";
import { GraphicType } from "./Wolfie2D/Nodes/Graphics/GraphicTypes";
import Point from "./Wolfie2D/Nodes/Graphics/Point";
import Rect from "./Wolfie2D/Nodes/Graphics/Rect";
import AnimatedSprite from "./Wolfie2D/Nodes/Sprites/AnimatedSprite";
import Sprite from "./Wolfie2D/Nodes/Sprites/Sprite";
import Scene from "./Wolfie2D/Scene/Scene";
import Color from "./Wolfie2D/Utils/Color";
export default class WebGLScene extends Scene {
private point: Point;
private rect: Rect;
private player: AnimatedSprite;
private t: number = 0;
loadScene() {
this.load.spritesheet("player", "hw1_assets/spritesheets/player_spaceship.json");
}
startScene() {
this.addLayer("primary");
this.point = this.add.graphic(GraphicType.POINT, "primary", {position: new Vec2(100, 100), size: new Vec2(10, 10)})
this.point.color = Color.CYAN;
console.log(this.point.color.toStringRGBA());
this.rect = <Rect>this.add.graphic(GraphicType.RECT, "primary", {position: new Vec2(300, 100), size: new Vec2(100, 50)});
this.rect.color = Color.ORANGE;
this.player = this.add.animatedSprite("player", "primary");
this.player.position.set(800, 500);
this.player.scale.set(0.5, 0.5);
this.player.animation.play("idle");
}
updateScene(deltaT: number) {
this.t += deltaT;
let s = Math.sin(this.t);
let c = Math.cos(this.t);
this.point.position.x = 100 + 100*c;
this.point.position.y = 100 + 100*s;
this.rect.rotation = this.t;
}
}

View File

@ -1,18 +0,0 @@
import AI from "../DataTypes/Interfaces/AI";
import GameEvent from "../Events/GameEvent";
import GameNode from "../Nodes/GameNode";
/**
* A very basic AI class that just runs a function every update
*/
export default class ControllerAI implements AI {
protected owner: GameNode;
initializeAI(owner: GameNode, options: Record<string, any>): void {
this.owner = owner;
}
handleEvent(event: GameEvent): void {}
update(deltaT: number): void {}
}

View File

@ -12,4 +12,6 @@ export default class StateMachineAI extends StateMachine implements AI {
// @implemented // @implemented
initializeAI(owner: GameNode, config: Record<string, any>): void {} initializeAI(owner: GameNode, config: Record<string, any>): void {}
activate(options: Record<string, any>): void {}
} }

View File

@ -10,6 +10,9 @@ export default interface AI extends Updateable {
/** Initializes the AI with the actor and any additional config */ /** Initializes the AI with the actor and any additional config */
initializeAI(owner: GameNode, options: Record<string, any>): void; initializeAI(owner: GameNode, options: Record<string, any>): void;
/** Activates this AI from a stopped state and allows variables to be passed in */
activate(options: Record<string, any>): void;
/** Handles events from the Actor */ /** Handles events from the Actor */
handleEvent(event: GameEvent): void; handleEvent(event: GameEvent): void;
} }

View File

@ -30,6 +30,7 @@ export default interface Actor {
/** /**
* Sets the AI to start/stop for this Actor. * Sets the AI to start/stop for this Actor.
* @param active The new active status of the AI. * @param active The new active status of the AI.
* @param options An object that allows options to be pased to the activated AI
*/ */
setAIActive(active: boolean): void; setAIActive(active: boolean, options: Record<string, any>): void;
} }

View File

@ -23,6 +23,26 @@ export default class AABB extends Shape {
this.halfSize = halfSize ? halfSize : new Vec2(0, 0); this.halfSize = halfSize ? halfSize : new Vec2(0, 0);
} }
/** Returns a point representing the top left corner of the AABB */
get topLeft(): Vec2 {
return new Vec2(this.left, this.top)
}
/** Returns a point representing the top right corner of the AABB */
get topRight(): Vec2 {
return new Vec2(this.right, this.top)
}
/** Returns a point representing the bottom left corner of the AABB */
get bottomLeft(): Vec2 {
return new Vec2(this.left, this.bottom)
}
/** Returns a point representing the bottom right corner of the AABB */
get bottomRight(): Vec2 {
return new Vec2(this.right, this.bottom)
}
// @override // @override
getBoundingRect(): AABB { getBoundingRect(): AABB {
return this.clone(); return this.clone();

View File

@ -7,7 +7,7 @@ import Shape from "./Shape";
*/ */
export default class Circle extends Shape { export default class Circle extends Shape {
private _center: Vec2; private _center: Vec2;
private radius: number; radius: number;
/** /**
* Creates a new Circle * Creates a new Circle
@ -32,6 +32,24 @@ export default class Circle extends Shape {
return new Vec2(this.radius, this.radius); return new Vec2(this.radius, this.radius);
} }
get r(): number {
return this.radius;
}
set r(radius: number) {
this.radius = radius;
}
// @override
/**
* A simple boolean check of whether this AABB contains a point
* @param point The point to check
* @returns A boolean representing whether this AABB contains the specified point
*/
containsPoint(point: Vec2): boolean {
return this.center.distanceSqTo(point) <= this.radius*this.radius;
}
// @override // @override
getBoundingRect(): AABB { getBoundingRect(): AABB {
return new AABB(this._center.clone(), new Vec2(this.radius, this.radius)); return new AABB(this._center.clone(), new Vec2(this.radius, this.radius));
@ -51,4 +69,8 @@ export default class Circle extends Shape {
clone(): Circle { clone(): Circle {
return new Circle(this._center.clone(), this.radius); return new Circle(this._center.clone(), this.radius);
} }
toString(): string {
return "(center: " + this.center.toString() + ", radius: " + this.radius + ")";
}
} }

View File

@ -71,6 +71,13 @@ export default abstract class Shape {
*/ */
abstract overlaps(other: Shape): boolean; abstract overlaps(other: Shape): boolean;
/**
* A simple boolean check of whether this Shape contains a point
* @param point The point to check
* @returns A boolean representing whether this Shape contains the specified point
*/
abstract containsPoint(point: Vec2): boolean;
static getTimeOfCollision(A: Shape, velA: Vec2, B: Shape, velB: Vec2): [Vec2, Vec2, boolean, boolean] { static getTimeOfCollision(A: Shape, velA: Vec2, B: Shape, velB: Vec2): [Vec2, Vec2, boolean, boolean] {
if(A instanceof AABB && B instanceof AABB){ if(A instanceof AABB && B instanceof AABB){
return Shape.getTimeOfCollision_AABB_AABB(A, velA, B, velB); return Shape.getTimeOfCollision_AABB_AABB(A, velA, B, velB);

View File

@ -269,6 +269,17 @@ export default class Vec2 {
return this; return this;
} }
/**
* Does an element wise remainder operation on this vector. this.x %= other.x and this.y %= other.y
* @param other The other vector
* @returns this vector
*/
remainder(other: Vec2): Vec2 {
this.x = this.x % other.x;
this.y = this.y % other.y;
return this;
}
/** /**
* Returns the squared distance between this vector and another vector * Returns the squared distance between this vector and another vector
* @param other The vector to compute distance squared to * @param other The vector to compute distance squared to

View File

@ -78,6 +78,36 @@ export default class Debug {
this.debugRenderingContext.globalAlpha = alpha; this.debugRenderingContext.globalAlpha = alpha;
} }
/**
* Draws a circle at the specified position
* @param center The center of the circle
* @param radius The dimensions of the box
* @param filled A boolean for whether or not the circle is filled
* @param color The color of the circle
*/
static drawCircle(center: Vec2, radius: number, filled: boolean, color: Color): void {
let alpha = this.debugRenderingContext.globalAlpha;
this.debugRenderingContext.globalAlpha = color.a;
if(filled){
this.debugRenderingContext.fillStyle = color.toString();
this.debugRenderingContext.beginPath();
this.debugRenderingContext.arc(center.x, center.y, radius, 0, 2 * Math.PI);
this.debugRenderingContext.closePath();
this.debugRenderingContext.fill();
} else {
let lineWidth = 2;
this.debugRenderingContext.lineWidth = lineWidth;
this.debugRenderingContext.strokeStyle = color.toString();
this.debugRenderingContext.beginPath();
this.debugRenderingContext.arc(center.x, center.y, radius, 0, 2 * Math.PI);
this.debugRenderingContext.closePath();
this.debugRenderingContext.stroke();
}
this.debugRenderingContext.globalAlpha = alpha;
}
/** /**
* Draws a ray at the specified position * Draws a ray at the specified position
* @param from The starting position of the ray * @param from The starting position of the ray

View File

@ -18,6 +18,7 @@ import EnvironmentInitializer from "./EnvironmentInitializer";
import Vec2 from "../DataTypes/Vec2"; import Vec2 from "../DataTypes/Vec2";
import Registry from "../Registry/Registry"; import Registry from "../Registry/Registry";
import WebGLRenderer from "../Rendering/WebGLRenderer"; import WebGLRenderer from "../Rendering/WebGLRenderer";
import Scene from "../Scene/Scene";
/** /**
* The main loop of the game engine. * The main loop of the game engine.
@ -130,7 +131,7 @@ export default class Game {
/** /**
* Starts the game * Starts the game
*/ */
start(): void { start(InitialScene: new (...args: any) => Scene, options: Record<string, any>): void {
// Set the update function of the loop // Set the update function of the loop
this.loop.doUpdate = (deltaT: number) => this.update(deltaT); this.loop.doUpdate = (deltaT: number) => this.update(deltaT);
@ -142,7 +143,9 @@ export default class Game {
// Load the items with the resource manager // Load the items with the resource manager
this.resourceManager.loadResourcesFromQueue(() => { this.resourceManager.loadResourcesFromQueue(() => {
// When we're dont loading, start the loop // When we're done loading, start the loop
console.log("Finished Preload - loading first scene");
this.sceneManager.addScene(InitialScene, options);
this.loop.start(); this.loop.start();
}); });
} }
@ -178,14 +181,7 @@ export default class Game {
// Clear the canvases // Clear the canvases
Debug.clearCanvas(); Debug.clearCanvas();
if(this.gameOptions.useWebGL){ this.renderingManager.clear(this.clearColor);
(<WebGLRenderingContext>this.ctx).clearColor(this.clearColor.r, this.clearColor.g, this.clearColor.b, this.clearColor.a);
(<WebGLRenderingContext>this.ctx).clear((<WebGLRenderingContext>this.ctx).COLOR_BUFFER_BIT | (<WebGLRenderingContext>this.ctx).DEPTH_BUFFER_BIT);
} else {
(<CanvasRenderingContext2D>this.ctx).clearRect(0, 0, this.WIDTH, this.HEIGHT);
(<CanvasRenderingContext2D>this.ctx).fillStyle = this.clearColor.toString();
(<CanvasRenderingContext2D>this.ctx).fillRect(0, 0, this.WIDTH, this.HEIGHT);
}
this.sceneManager.render(); this.sceneManager.render();

View File

@ -18,6 +18,7 @@ import NavigationPath from "../Pathfinding/NavigationPath";
import TweenManager from "../Rendering/Animations/TweenManager"; import TweenManager from "../Rendering/Animations/TweenManager";
import Debug from "../Debug/Debug"; import Debug from "../Debug/Debug";
import Color from "../Utils/Color"; import Color from "../Utils/Color";
import Circle from "../DataTypes/Shapes/Circle";
/** /**
* The representation of an object in the game world. * The representation of an object in the game world.
@ -198,6 +199,15 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
this.scene.getPhysicsManager().registerObject(this); this.scene.getPhysicsManager().registerObject(this);
} }
/**
* Sets the collider for this GameNode
* @param collider The new collider to use
*/
setCollisionShape(collider: Shape): void {
this.collisionShape = collider;
this.collisionShape.center.copy(this.position);
}
// @implemented // @implemented
/** /**
* @param group The name of the group that will activate the trigger * @param group The name of the group that will activate the trigger
@ -254,8 +264,9 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
} }
// @implemented // @implemented
setAIActive(active: boolean): void { setAIActive(active: boolean, options: Record<string, any>): void {
this.aiActive = active; this.aiActive = active;
this.ai.activate(options);
} }
/*---------- TWEENABLE PROPERTIES ----------*/ /*---------- TWEENABLE PROPERTIES ----------*/
@ -306,8 +317,13 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
/** Called if the position vector is modified or replaced */ /** Called if the position vector is modified or replaced */
protected positionChanged(): void { protected positionChanged(): void {
if(this.hasPhysics){ if(this.collisionShape){
if(this.colliderOffset){
this.collisionShape.center = this.position.clone().add(this.colliderOffset); this.collisionShape.center = this.position.clone().add(this.colliderOffset);
} else {
this.collisionShape.center = this.position.clone();
}
} }
}; };
@ -336,15 +352,20 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
} }
// If this has a collider, draw it // If this has a collider, draw it
if(this.hasPhysics && this.collisionShape){ if(this.collisionShape){
let color = this.isColliding ? Color.RED : Color.GREEN; let color = this.isColliding ? Color.RED : Color.GREEN;
if(this.isTrigger){ if(this.isTrigger){
color = Color.PURPLE; color = Color.MAGENTA;
} }
color.a = 0.2; color.a = 0.2;
if(this.collisionShape instanceof AABB){
Debug.drawBox(this.inRelativeCoordinates(this.collisionShape.center), this.collisionShape.halfSize.scaled(this.scene.getViewScale()), true, color); Debug.drawBox(this.inRelativeCoordinates(this.collisionShape.center), this.collisionShape.halfSize.scaled(this.scene.getViewScale()), true, color);
} else if(this.collisionShape instanceof Circle){
Debug.drawCircle(this.inRelativeCoordinates(this.collisionShape.center), this.collisionShape.hw*this.scene.getViewScale(), true, color);
}
} }
} }
} }

View File

@ -82,7 +82,7 @@ export default abstract class UIElement extends CanvasNode {
// See of this object was just clicked // See of this object was just clicked
if(Input.isMouseJustPressed()){ if(Input.isMouseJustPressed()){
let clickPos = Input.getMousePressPosition(); let clickPos = Input.getMousePressPosition();
if(this.contains(clickPos.x, clickPos.y)){ if(this.contains(clickPos.x, clickPos.y) && this.visible && !this.layer.isHidden()){
this.isClicked = true; this.isClicked = true;
if(this.onClick !== null){ if(this.onClick !== null){
@ -136,15 +136,15 @@ export default abstract class UIElement extends CanvasNode {
* Overridable method for calculating background color - useful for elements that want to be colored on different after certain events * Overridable method for calculating background color - useful for elements that want to be colored on different after certain events
* @returns The background color of the UIElement * @returns The background color of the UIElement
*/ */
calculateBackgroundColor(): string { calculateBackgroundColor(): Color {
return this.backgroundColor.toStringRGBA(); return this.backgroundColor;
} }
/** /**
* Overridable method for calculating border color - useful for elements that want to be colored on different after certain events * Overridable method for calculating border color - useful for elements that want to be colored on different after certain events
* @returns The border color of the UIElement * @returns The border color of the UIElement
*/ */
calculateBorderColor(): string { calculateBorderColor(): Color {
return this.borderColor.toStringRGBA(); return this.borderColor;
} }
} }

View File

@ -14,14 +14,14 @@ export default class Button extends Label {
} }
// @override // @override
calculateBackgroundColor(): string { calculateBackgroundColor(): Color {
// Change the background color if clicked or hovered // Change the background color if clicked or hovered
if(this.isEntered && !this.isClicked){ if(this.isEntered && !this.isClicked){
return this.backgroundColor.lighten().toStringRGBA(); return this.backgroundColor.lighten();
} else if(this.isClicked){ } else if(this.isClicked){
return this.backgroundColor.darken().toStringRGBA(); return this.backgroundColor.darken();
} else { } else {
return this.backgroundColor.toStringRGBA(); return this.backgroundColor;
} }
} }
} }

View File

@ -68,6 +68,10 @@ export default class Label extends UIElement{
return ctx.measureText(this.text).width; return ctx.measureText(this.text).width;
} }
setHAlign(align: string): void {
this.hAlign = align;
}
/** /**
* Calculate the offset of the text - this is used for rendering text with different alignments * Calculate the offset of the text - this is used for rendering text with different alignments
* @param ctx The rendering context * @param ctx The rendering context

View File

@ -1,5 +1,6 @@
import Map from "../../DataTypes/Map"; import Map from "../../DataTypes/Map";
import ShaderType from "../../Rendering/WebGLRendering/ShaderType"; import ShaderType from "../../Rendering/WebGLRendering/ShaderType";
import LabelShaderType from "../../Rendering/WebGLRendering/ShaderTypes/LabelShaderType";
import PointShaderType from "../../Rendering/WebGLRendering/ShaderTypes/PointShaderType"; import PointShaderType from "../../Rendering/WebGLRendering/ShaderTypes/PointShaderType";
import RectShaderType from "../../Rendering/WebGLRendering/ShaderTypes/RectShaderType"; import RectShaderType from "../../Rendering/WebGLRendering/ShaderTypes/RectShaderType";
import SpriteShaderType from "../../Rendering/WebGLRendering/ShaderTypes/SpriteShaderType"; import SpriteShaderType from "../../Rendering/WebGLRendering/ShaderTypes/SpriteShaderType";
@ -14,6 +15,7 @@ export default class ShaderRegistry extends Map<ShaderType> {
public static POINT_SHADER = "point"; public static POINT_SHADER = "point";
public static RECT_SHADER = "rect"; public static RECT_SHADER = "rect";
public static SPRITE_SHADER = "sprite"; public static SPRITE_SHADER = "sprite";
public static LABEL_SHADER = "label";
private registryItems: Array<ShaderRegistryItem> = new Array(); private registryItems: Array<ShaderRegistryItem> = new Array();
@ -21,8 +23,6 @@ export default class ShaderRegistry extends Map<ShaderType> {
* Preloads all built-in shaders * Preloads all built-in shaders
*/ */
public preload(){ public preload(){
console.log("Preloading");
// Get the resourceManager and queue all built-in shaders for preloading // Get the resourceManager and queue all built-in shaders for preloading
const rm = ResourceManager.getInstance(); const rm = ResourceManager.getInstance();
@ -35,17 +35,17 @@ export default class ShaderRegistry extends Map<ShaderType> {
// Queue a load for the sprite shader // Queue a load for the sprite shader
this.registerAndPreloadItem(ShaderRegistry.SPRITE_SHADER, SpriteShaderType, "builtin/shaders/sprite.vshader", "builtin/shaders/sprite.fshader"); this.registerAndPreloadItem(ShaderRegistry.SPRITE_SHADER, SpriteShaderType, "builtin/shaders/sprite.vshader", "builtin/shaders/sprite.fshader");
// Queue a load for the label shader
this.registerAndPreloadItem(ShaderRegistry.LABEL_SHADER, LabelShaderType, "builtin/shaders/label.vshader", "builtin/shaders/label.fshader");
// Queue a load for any preloaded items // Queue a load for any preloaded items
for(let item of this.registryItems){ for(let item of this.registryItems){
const shader = new item.constr(item.key); const shader = new item.constr(item.key);
shader.initBufferObject(); shader.initBufferObject();
this.add(item.key, shader); this.add(item.key, shader);
console.log("Added", item.key);
// Load if desired // Load if desired
if(item.preload !== undefined){ if(item.preload !== undefined){
console.log("Preloading", item.key);
rm.shader(item.key, item.preload.vshaderLocation, item.preload.fshaderLocation); rm.shader(item.key, item.preload.vshaderLocation, item.preload.fshaderLocation);
} }
} }

View File

@ -19,6 +19,7 @@ import Slider from "../Nodes/UIElements/Slider";
import TextInput from "../Nodes/UIElements/TextInput"; import TextInput from "../Nodes/UIElements/TextInput";
import AnimatedSprite from "../Nodes/Sprites/AnimatedSprite"; import AnimatedSprite from "../Nodes/Sprites/AnimatedSprite";
import Vec2 from "../DataTypes/Vec2"; import Vec2 from "../DataTypes/Vec2";
import Color from "../Utils/Color";
/** /**
* An implementation of the RenderingManager class using CanvasRenderingContext2D. * An implementation of the RenderingManager class using CanvasRenderingContext2D.
@ -32,6 +33,8 @@ export default class CanvasRenderer extends RenderingManager {
protected origin: Vec2; protected origin: Vec2;
protected zoom: number; protected zoom: number;
protected worldSize: Vec2;
constructor(){ constructor(){
super(); super();
} }
@ -49,6 +52,8 @@ export default class CanvasRenderer extends RenderingManager {
canvas.width = width; canvas.width = width;
canvas.height = height; canvas.height = height;
this.worldSize = new Vec2(width, height);
this.ctx = canvas.getContext("2d"); this.ctx = canvas.getContext("2d");
this.graphicRenderer = new GraphicRenderer(this.ctx); this.graphicRenderer = new GraphicRenderer(this.ctx);
@ -107,7 +112,10 @@ export default class CanvasRenderer extends RenderingManager {
} }
// Render the uiLayers on top of everything else // Render the uiLayers on top of everything else
uiLayers.forEach(key => uiLayers.get(key).getItems().forEach(node => this.renderNode(<CanvasNode>node))); uiLayers.forEach(key => {
if(!uiLayers.get(key).isHidden())
uiLayers.get(key).getItems().forEach(node => this.renderNode(<CanvasNode>node))
});
} }
/** /**
@ -221,4 +229,10 @@ export default class CanvasRenderer extends RenderingManager {
this.uiElementRenderer.renderTextInput(uiElement); this.uiElementRenderer.renderTextInput(uiElement);
} }
} }
clear(clearColor: Color): void {
this.ctx.clearRect(0, 0, this.worldSize.x, this.worldSize.y);
this.ctx.fillStyle = clearColor.toString();
this.ctx.fillRect(0, 0, this.worldSize.x, this.worldSize.y);
}
} }

View File

@ -45,11 +45,11 @@ export default class UIElementRenderer {
// Stroke and fill a rounded rect and give it text // Stroke and fill a rounded rect and give it text
this.ctx.globalAlpha = label.backgroundColor.a; this.ctx.globalAlpha = label.backgroundColor.a;
this.ctx.fillStyle = label.calculateBackgroundColor(); this.ctx.fillStyle = label.calculateBackgroundColor().toStringRGBA();
this.ctx.fillRoundedRect(-label.size.x/2, -label.size.y/2, this.ctx.fillRoundedRect(-label.size.x/2, -label.size.y/2,
label.size.x, label.size.y, label.borderRadius); label.size.x, label.size.y, label.borderRadius);
this.ctx.strokeStyle = label.calculateBorderColor(); this.ctx.strokeStyle = label.calculateBorderColor().toStringRGBA();
this.ctx.globalAlpha = label.borderColor.a; this.ctx.globalAlpha = label.borderColor.a;
this.ctx.lineWidth = label.borderWidth; this.ctx.lineWidth = label.borderWidth;
this.ctx.strokeRoundedRect(-label.size.x/2, -label.size.y/2, this.ctx.strokeRoundedRect(-label.size.x/2, -label.size.y/2,

View File

@ -8,6 +8,7 @@ import UIElement from "../Nodes/UIElement";
import ResourceManager from "../ResourceManager/ResourceManager"; import ResourceManager from "../ResourceManager/ResourceManager";
import UILayer from "../Scene/Layers/UILayer"; import UILayer from "../Scene/Layers/UILayer";
import Scene from "../Scene/Scene"; import Scene from "../Scene/Scene";
import Color from "../Utils/Color";
/** /**
* An abstract framework to put all rendering in once place in the application * An abstract framework to put all rendering in once place in the application
@ -48,6 +49,9 @@ export default abstract class RenderingManager {
*/ */
abstract render(visibleSet: Array<CanvasNode>, tilemaps: Array<Tilemap>, uiLayers: Map<UILayer>): void; abstract render(visibleSet: Array<CanvasNode>, tilemaps: Array<Tilemap>, uiLayers: Map<UILayer>): void;
/** Clears the canvas */
abstract clear(color: Color): void;
/** /**
* Renders a sprite * Renders a sprite
* @param sprite The sprite to render * @param sprite The sprite to render

View File

@ -1,6 +1,7 @@
import Graph from "../DataTypes/Graphs/Graph"; import Graph from "../DataTypes/Graphs/Graph";
import Map from "../DataTypes/Map"; import Map from "../DataTypes/Map";
import Vec2 from "../DataTypes/Vec2"; import Vec2 from "../DataTypes/Vec2";
import Debug from "../Debug/Debug";
import CanvasNode from "../Nodes/CanvasNode"; import CanvasNode from "../Nodes/CanvasNode";
import Graphic from "../Nodes/Graphic"; import Graphic from "../Nodes/Graphic";
import { GraphicType } from "../Nodes/Graphics/GraphicTypes"; import { GraphicType } from "../Nodes/Graphics/GraphicTypes";
@ -10,10 +11,13 @@ import AnimatedSprite from "../Nodes/Sprites/AnimatedSprite";
import Sprite from "../Nodes/Sprites/Sprite"; import Sprite from "../Nodes/Sprites/Sprite";
import Tilemap from "../Nodes/Tilemap"; import Tilemap from "../Nodes/Tilemap";
import UIElement from "../Nodes/UIElement"; import UIElement from "../Nodes/UIElement";
import Label from "../Nodes/UIElements/Label";
import ShaderRegistry from "../Registry/Registries/ShaderRegistry"; import ShaderRegistry from "../Registry/Registries/ShaderRegistry";
import Registry from "../Registry/Registry"; import Registry from "../Registry/Registry";
import ResourceManager from "../ResourceManager/ResourceManager"; import ResourceManager from "../ResourceManager/ResourceManager";
import ParallaxLayer from "../Scene/Layers/ParallaxLayer";
import UILayer from "../Scene/Layers/UILayer"; import UILayer from "../Scene/Layers/UILayer";
import Color from "../Utils/Color";
import RenderingUtils from "../Utils/RenderingUtils"; import RenderingUtils from "../Utils/RenderingUtils";
import RenderingManager from "./RenderingManager"; import RenderingManager from "./RenderingManager";
import ShaderType from "./WebGLRendering/ShaderType"; import ShaderType from "./WebGLRendering/ShaderType";
@ -25,6 +29,7 @@ export default class WebGLRenderer extends RenderingManager {
protected worldSize: Vec2; protected worldSize: Vec2;
protected gl: WebGLRenderingContext; protected gl: WebGLRenderingContext;
protected textCtx: CanvasRenderingContext2D;
initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): WebGLRenderingContext { initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): WebGLRenderingContext {
canvas.width = width; canvas.width = width;
@ -47,6 +52,15 @@ export default class WebGLRenderer extends RenderingManager {
// Tell the resource manager we're using WebGL // Tell the resource manager we're using WebGL
ResourceManager.getInstance().useWebGL(true, this.gl); ResourceManager.getInstance().useWebGL(true, this.gl);
// Show the text canvas and get its context
let textCanvas = <HTMLCanvasElement>document.getElementById("text-canvas");
textCanvas.hidden = false;
this.textCtx = textCanvas.getContext("2d");
// Size the text canvas to be the same as the game canvas
textCanvas.height = height;
textCanvas.width = width;
return this.gl; return this.gl;
} }
@ -54,6 +68,18 @@ export default class WebGLRenderer extends RenderingManager {
for(let node of visibleSet){ for(let node of visibleSet){
this.renderNode(node); this.renderNode(node);
} }
uiLayers.forEach(key => {
if(!uiLayers.get(key).isHidden())
uiLayers.get(key).getItems().forEach(node => this.renderNode(<CanvasNode>node))
});
}
clear(color: Color): void {
this.gl.clearColor(color.r, color.g, color.b, color.a);
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
this.textCtx.clearRect(0, 0, this.worldSize.x, this.worldSize.y);
} }
protected renderNode(node: CanvasNode): void { protected renderNode(node: CanvasNode): void {
@ -74,44 +100,32 @@ export default class WebGLRenderer extends RenderingManager {
} else { } else {
this.renderSprite(node); this.renderSprite(node);
} }
} else if(node instanceof UIElement){
this.renderUIElement(node);
} }
} }
protected renderSprite(sprite: Sprite): void { protected renderSprite(sprite: Sprite): void {
let shader = Registry.shaders.get(ShaderRegistry.SPRITE_SHADER); let shader = Registry.shaders.get(ShaderRegistry.SPRITE_SHADER);
let options = this.addOptions(shader.getOptions(sprite), sprite);
let options = shader.getOptions(sprite);
options.worldSize = this.worldSize;
options.origin = this.origin;
shader.render(this.gl, options); shader.render(this.gl, options);
} }
protected renderAnimatedSprite(sprite: AnimatedSprite): void { protected renderAnimatedSprite(sprite: AnimatedSprite): void {
let shader = Registry.shaders.get(ShaderRegistry.SPRITE_SHADER); let shader = Registry.shaders.get(ShaderRegistry.SPRITE_SHADER);
let options = this.addOptions(shader.getOptions(sprite), sprite);
let options = shader.getOptions(sprite); shader.render(this.gl, options);
options.worldSize = this.worldSize;
options.origin = this.origin;
Registry.shaders.get(ShaderRegistry.SPRITE_SHADER).render(this.gl, options);
} }
protected renderGraphic(graphic: Graphic): void { protected renderGraphic(graphic: Graphic): void {
if(graphic instanceof Point){ if(graphic instanceof Point){
let shader = Registry.shaders.get(ShaderRegistry.POINT_SHADER); let shader = Registry.shaders.get(ShaderRegistry.POINT_SHADER);
let options = shader.getOptions(graphic); let options = this.addOptions(shader.getOptions(graphic), graphic);
options.worldSize = this.worldSize;
options.origin = this.origin;
shader.render(this.gl, options); shader.render(this.gl, options);
} else if(graphic instanceof Rect) { } else if(graphic instanceof Rect) {
let shader = Registry.shaders.get(ShaderRegistry.RECT_SHADER); let shader = Registry.shaders.get(ShaderRegistry.RECT_SHADER);
let options = shader.getOptions(graphic); let options = this.addOptions(shader.getOptions(graphic), graphic);
options.worldSize = this.worldSize;
options.origin = this.origin;
shader.render(this.gl, options); shader.render(this.gl, options);
} }
} }
@ -121,16 +135,48 @@ export default class WebGLRenderer extends RenderingManager {
} }
protected renderUIElement(uiElement: UIElement): void { protected renderUIElement(uiElement: UIElement): void {
throw new Error("Method not implemented."); if(uiElement instanceof Label){
let shader = Registry.shaders.get(ShaderRegistry.LABEL_SHADER);
let options = this.addOptions(shader.getOptions(uiElement), uiElement);
shader.render(this.gl, options);
this.textCtx.setTransform(1, 0, 0, 1, (uiElement.position.x - this.origin.x)*this.zoom, (uiElement.position.y - this.origin.y)*this.zoom);
this.textCtx.rotate(-uiElement.rotation);
let globalAlpha = this.textCtx.globalAlpha;
this.textCtx.globalAlpha = uiElement.alpha;
// Render text
this.textCtx.font = uiElement.getFontString();
let offset = uiElement.calculateTextOffset(this.textCtx);
this.textCtx.fillStyle = uiElement.calculateTextColor();
this.textCtx.globalAlpha = uiElement.textColor.a;
this.textCtx.fillText(uiElement.text, offset.x - uiElement.size.x/2, offset.y - uiElement.size.y/2);
this.textCtx.globalAlpha = globalAlpha;
this.textCtx.setTransform(1, 0, 0, 1, 0, 0);
}
} }
protected renderCustom(node: CanvasNode): void { protected renderCustom(node: CanvasNode): void {
let shader = Registry.shaders.get(node.customShaderKey); let shader = Registry.shaders.get(node.customShaderKey);
let options = shader.getOptions(node); let options = this.addOptions(shader.getOptions(node), node);
options.worldSize = this.worldSize;
options.origin = this.origin;
shader.render(this.gl, options); shader.render(this.gl, options);
} }
protected addOptions(options: Record<string, any>, node: CanvasNode): Record<string, any> {
// Give the shader access to the world size
options.worldSize = this.worldSize;
// Adjust the origin position to the parallax
let layer = node.getLayer();
let parallax = new Vec2(1, 1);
if(layer instanceof ParallaxLayer){
parallax = (<ParallaxLayer>layer).parallax;
}
options.origin = this.origin.clone().mult(parallax);
return options;
}
} }

View File

@ -0,0 +1,122 @@
import Mat4x4 from "../../../DataTypes/Mat4x4";
import Vec2 from "../../../DataTypes/Vec2";
import Debug from "../../../Debug/Debug";
import Rect from "../../../Nodes/Graphics/Rect";
import Label from "../../../Nodes/UIElements/Label";
import ResourceManager from "../../../ResourceManager/ResourceManager";
import QuadShaderType from "./QuadShaderType";
export default class LabelShaderType extends QuadShaderType {
constructor(programKey: string){
super(programKey);
this.resourceManager = ResourceManager.getInstance();
}
initBufferObject(): void {
this.bufferObjectKey = "label";
this.resourceManager.createBuffer(this.bufferObjectKey);
}
render(gl: WebGLRenderingContext, options: Record<string, any>): void {
const backgroundColor = options.backgroundColor.toWebGL();
const borderColor = options.borderColor.toWebGL();
const program = this.resourceManager.getShaderProgram(this.programKey);
const buffer = this.resourceManager.getBuffer(this.bufferObjectKey);
gl.useProgram(program);
const vertexData = this.getVertices(options.size.x, options.size.y);
const FSIZE = vertexData.BYTES_PER_ELEMENT;
// Bind the buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);
// Attributes
const a_Position = gl.getAttribLocation(program, "a_Position");
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 2 * FSIZE, 0 * FSIZE);
gl.enableVertexAttribArray(a_Position);
// Uniforms
const u_BackgroundColor = gl.getUniformLocation(program, "u_BackgroundColor");
gl.uniform4fv(u_BackgroundColor, backgroundColor);
const u_BorderColor = gl.getUniformLocation(program, "u_BorderColor");
gl.uniform4fv(u_BorderColor, borderColor);
const u_MaxSize = gl.getUniformLocation(program, "u_MaxSize");
gl.uniform2f(u_MaxSize, -vertexData[0], vertexData[1]);
// Get transformation matrix
// We want a square for our rendering space, so get the maximum dimension of our quad
let maxDimension = Math.max(options.size.x, options.size.y);
const u_BorderWidth = gl.getUniformLocation(program, "u_BorderWidth");
gl.uniform1f(u_BorderWidth, options.borderWidth/maxDimension);
const u_BorderRadius = gl.getUniformLocation(program, "u_BorderRadius");
gl.uniform1f(u_BorderRadius, options.borderRadius/maxDimension);
// The size of the rendering space will be a square with this maximum dimension
let size = new Vec2(maxDimension, maxDimension).scale(2/options.worldSize.x, 2/options.worldSize.y);
// Center our translations around (0, 0)
const translateX = (options.position.x - options.origin.x - options.worldSize.x/2)/maxDimension;
const translateY = -(options.position.y - options.origin.y - options.worldSize.y/2)/maxDimension;
// Create our transformation matrix
this.translation.translate(new Float32Array([translateX, translateY]));
this.scale.scale(size);
this.rotation.rotate(options.rotation);
let transformation = Mat4x4.MULT(this.translation, this.scale, this.rotation);
// Pass the translation matrix to our shader
const u_Transform = gl.getUniformLocation(program, "u_Transform");
gl.uniformMatrix4fv(u_Transform, false, transformation.toArray());
// Draw the quad
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
/**
* The rendering space always has to be a square, so make sure its square w.r.t to the largest dimension
* @param w The width of the quad in pixels
* @param h The height of the quad in pixels
* @returns An array of the vertices of the quad
*/
getVertices(w: number, h: number): Float32Array {
let x, y;
if(h > w){
y = 0.5;
x = w/(2*h);
} else {
x = 0.5;
y = h/(2*w);
}
return new Float32Array([
-x, y,
-x, -y,
x, y,
x, -y
]);
}
getOptions(rect: Label): Record<string, any> {
let options: Record<string, any> = {
position: rect.position,
backgroundColor: rect.calculateBackgroundColor(),
borderColor: rect.calculateBorderColor(),
borderWidth: rect.borderWidth,
borderRadius: rect.borderRadius,
size: rect.size,
rotation: rect.rotation
}
return options;
}
}

View File

@ -22,24 +22,9 @@ export default class SpriteShaderType extends QuadShaderType {
const program = this.resourceManager.getShaderProgram(this.programKey); const program = this.resourceManager.getShaderProgram(this.programKey);
const buffer = this.resourceManager.getBuffer(this.bufferObjectKey); const buffer = this.resourceManager.getBuffer(this.bufferObjectKey);
const texture = this.resourceManager.getTexture(options.imageKey); const texture = this.resourceManager.getTexture(options.imageKey);
const image = this.resourceManager.getImage(options.imageKey);
gl.useProgram(program); gl.useProgram(program);
// Enable texture0
gl.activeTexture(gl.TEXTURE0);
// Bind our texture to texture 0
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set the texture parameters
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// Set the texture image
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
const vertexData = this.getVertices(options.size.x, options.size.y, options.scale); const vertexData = this.getVertices(options.size.x, options.size.y, options.scale);
const FSIZE = vertexData.BYTES_PER_ELEMENT; const FSIZE = vertexData.BYTES_PER_ELEMENT;
@ -79,9 +64,9 @@ export default class SpriteShaderType extends QuadShaderType {
const u_Transform = gl.getUniformLocation(program, "u_Transform"); const u_Transform = gl.getUniformLocation(program, "u_Transform");
gl.uniformMatrix4fv(u_Transform, false, transformation.toArray()); gl.uniformMatrix4fv(u_Transform, false, transformation.toArray());
// Set texture unit 0 to the sampler // Set up our sampler with our assigned texture unit
const u_Sampler = gl.getUniformLocation(program, "u_Sampler"); const u_Sampler = gl.getUniformLocation(program, "u_Sampler");
gl.uniform1i(u_Sampler, 0); gl.uniform1i(u_Sampler, texture);
// Pass in texShift // Pass in texShift
const u_texShift = gl.getUniformLocation(program, "u_texShift"); const u_texShift = gl.getUniformLocation(program, "u_texShift");
@ -92,7 +77,7 @@ export default class SpriteShaderType extends QuadShaderType {
gl.uniform2fv(u_texScale, options.texScale); gl.uniform2fv(u_texScale, options.texScale);
// Draw the quad // Draw the quad
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4 ); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
} }
/** /**

View File

@ -5,7 +5,6 @@ import StringUtils from "../Utils/StringUtils";
import AudioManager from "../Sound/AudioManager"; import AudioManager from "../Sound/AudioManager";
import Spritesheet from "../DataTypes/Spritesheet"; import Spritesheet from "../DataTypes/Spritesheet";
import WebGLProgramType from "../DataTypes/Rendering/WebGLProgramType"; import WebGLProgramType from "../DataTypes/Rendering/WebGLProgramType";
import PhysicsManager from "../Physics/PhysicsManager";
/** /**
* The resource manager for the game engine. * The resource manager for the game engine.
@ -80,7 +79,8 @@ export default class ResourceManager {
private gl_DefaultShaderPrograms: Map<WebGLProgramType>; private gl_DefaultShaderPrograms: Map<WebGLProgramType>;
private gl_ShaderPrograms: Map<WebGLProgramType>; private gl_ShaderPrograms: Map<WebGLProgramType>;
private gl_Textures: Map<WebGLTexture>; private gl_Textures: Map<number>;
private gl_NextTextureID: number;
private gl_Buffers: Map<WebGLBuffer>; private gl_Buffers: Map<WebGLBuffer>;
private gl: WebGLRenderingContext; private gl: WebGLRenderingContext;
@ -117,6 +117,7 @@ export default class ResourceManager {
this.gl_ShaderPrograms = new Map(); this.gl_ShaderPrograms = new Map();
this.gl_Textures = new Map(); this.gl_Textures = new Map();
this.gl_NextTextureID = 0;
this.gl_Buffers = new Map(); this.gl_Buffers = new Map();
}; };
@ -226,7 +227,7 @@ export default class ResourceManager {
* @param callback The function to cal when the resources are finished loading * @param callback The function to cal when the resources are finished loading
*/ */
loadResourcesFromQueue(callback: Function): void { loadResourcesFromQueue(callback: Function): void {
this.loadonly_typesToLoad = 3; this.loadonly_typesToLoad = 5;
this.loading = true; this.loading = true;
@ -443,7 +444,9 @@ export default class ResourceManager {
this.images.add(key, image); this.images.add(key, image);
// If WebGL is active, create a texture // If WebGL is active, create a texture
this.createWebGLTexture(key); if(this.gl_WebGLActive){
this.createWebGLTexture(key, image);
}
// Finish image load // Finish image load
this.finishLoadingImage(callbackIfLast); this.finishLoadingImage(callbackIfLast);
@ -526,7 +529,7 @@ export default class ResourceManager {
/* ########## WEBGL SPECIFIC FUNCTIONS ########## */ /* ########## WEBGL SPECIFIC FUNCTIONS ########## */
public getTexture(key: string): WebGLTexture { public getTexture(key: string): number {
return this.gl_Textures.get(key); return this.gl_Textures.get(key);
} }
@ -538,10 +541,49 @@ export default class ResourceManager {
return this.gl_Buffers.get(key); return this.gl_Buffers.get(key);
} }
private createWebGLTexture(key:string): void { private createWebGLTexture(imageKey: string, image: HTMLImageElement): void {
if(this.gl_WebGLActive){ // Get the texture ID
const textureID = this.getTextureID(this.gl_NextTextureID);
// Create the texture
const texture = this.gl.createTexture(); const texture = this.gl.createTexture();
this.gl_Textures.add(key, texture);
// Set up the texture
// Enable texture0
this.gl.activeTexture(textureID);
// Bind our texture to texture 0
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
// Set the texture parameters
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
// Set the texture image
this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, image);
// Add the texture to our map with the same key as the image
this.gl_Textures.add(imageKey, this.gl_NextTextureID);
// Increment the key
this.gl_NextTextureID += 1;
}
private getTextureID(id: number): number {
// Start with 9 cases - this can be expanded if needed, but for the best performance,
// Textures should be stitched into an atlas
switch(id){
case 0: return this.gl.TEXTURE0;
case 1: return this.gl.TEXTURE1;
case 2: return this.gl.TEXTURE2;
case 3: return this.gl.TEXTURE3;
case 4: return this.gl.TEXTURE4;
case 5: return this.gl.TEXTURE5;
case 6: return this.gl.TEXTURE6;
case 7: return this.gl.TEXTURE7;
case 8: return this.gl.TEXTURE8;
default: return this.gl.TEXTURE9;
} }
} }

View File

@ -94,7 +94,7 @@ export default class Scene implements Updateable {
* @param options The options for Scene initialization * @param options The options for Scene initialization
*/ */
constructor(viewport: Viewport, sceneManager: SceneManager, renderingManager: RenderingManager, options: Record<string, any>){ constructor(viewport: Viewport, sceneManager: SceneManager, renderingManager: RenderingManager, options: Record<string, any>){
this.sceneOptions = SceneOptions.parse(options); this.sceneOptions = SceneOptions.parse(options? options : {});
this.worldSize = new Vec2(500, 500); this.worldSize = new Vec2(500, 500);
this.viewport = viewport; this.viewport = viewport;
@ -121,6 +121,9 @@ export default class Scene implements Updateable {
this.load = ResourceManager.getInstance(); this.load = ResourceManager.getInstance();
} }
/** A lifecycle method that gets called immediately after a new scene is created, before anything else. */
initScene(init: Record<string, any>): void {}
/** A lifecycle method that gets called when a new scene is created. Load all files you wish to access in the scene here. */ /** A lifecycle method that gets called when a new scene is created. Load all files you wish to access in the scene here. */
loadScene(): void {} loadScene(): void {}

View File

@ -1,7 +1,6 @@
import Scene from "./Scene"; import Scene from "./Scene";
import ResourceManager from "../ResourceManager/ResourceManager"; import ResourceManager from "../ResourceManager/ResourceManager";
import Viewport from "../SceneGraph/Viewport"; import Viewport from "../SceneGraph/Viewport";
import Game from "../Loop/Game";
import RenderingManager from "../Rendering/RenderingManager"; import RenderingManager from "../Rendering/RenderingManager";
/** /**
@ -41,11 +40,14 @@ export default class SceneManager {
* Add a scene as the main scene. * Add a scene as the main scene.
* Use this method if you've created a subclass of Scene, and you want to add it as the main Scene. * Use this method if you've created a subclass of Scene, and you want to add it as the main Scene.
* @param constr The constructor of the scene to add * @param constr The constructor of the scene to add
* @param init An object to pass to the init function of the new scene
*/ */
public addScene<T extends Scene>(constr: new (...args: any) => T, options: Record<string, any>): void { public addScene<T extends Scene>(constr: new (...args: any) => T, init?: Record<string, any>, options?: Record<string, any>): void {
let scene = new constr(this.viewport, this, this.renderingManager, options); let scene = new constr(this.viewport, this, this.renderingManager, options);
this.currentScene = scene; this.currentScene = scene;
scene.initScene(init);
// Enqueue all scene asset loads // Enqueue all scene asset loads
scene.loadScene(); scene.loadScene();
@ -64,16 +66,12 @@ export default class SceneManager {
* Change from the current scene to this new scene. * Change from the current scene to this new scene.
* Use this method if you've created a subclass of Scene, and you want to add it as the main Scene. * Use this method if you've created a subclass of Scene, and you want to add it as the main Scene.
* @param constr The constructor of the scene to change to * @param constr The constructor of the scene to change to
* @param init An object to pass to the init function of the new scene
*/ */
public changeScene<T extends Scene>(constr: new (...args: any) => T, options: Record<string, any>): void { public changeScene<T extends Scene>(constr: new (...args: any) => T, init?: Record<string, any>, options?: Record<string, any>): void {
// unload current scene this.viewport.setCenter(this.viewport.getHalfSize().x, this.viewport.getHalfSize().y);
this.currentScene.unloadScene();
this.resourceManager.unloadAllResources(); this.addScene(constr, init, options);
this.viewport.setCenter(0, 0);
this.addScene(constr, options);
} }
/** /**
@ -88,10 +86,8 @@ export default class SceneManager {
* Renders the current Scene * Renders the current Scene
*/ */
public render(): void { public render(): void {
if(this.currentScene.isRunning()){
this.currentScene.render(); this.currentScene.render();
} }
}
/** /**
* Updates the current Scene * Updates the current Scene

View File

@ -49,14 +49,10 @@ export default class Viewport {
// Set the size of the canvas // Set the size of the canvas
this.setCanvasSize(canvasSize); this.setCanvasSize(canvasSize);
console.log(canvasSize, zoomLevel);
// Set the size of the viewport // Set the size of the viewport
this.setSize(canvasSize); this.setSize(canvasSize);
this.setZoomLevel(zoomLevel); this.setZoomLevel(zoomLevel);
console.log(this.getHalfSize().toString());
// Set the center (and make the viewport stay there) // Set the center (and make the viewport stay there)
this.setCenter(this.view.halfSize.clone()); this.setCenter(this.view.halfSize.clone());
this.setFocus(this.view.halfSize.clone()); this.setFocus(this.view.halfSize.clone());

View File

@ -69,10 +69,10 @@ export default class Color {
} }
/** /**
* Purple color * Magenta color
* @returns rgb(255, 0, 255) * @returns rgb(255, 0, 255)
*/ */
static get PURPLE(): Color { static get MAGENTA(): Color {
return new Color(255, 0, 255, 1); return new Color(255, 0, 255, 1);
} }
@ -127,7 +127,7 @@ export default class Color {
* @returns A new lighter Color * @returns A new lighter Color
*/ */
lighten(): Color { lighten(): Color {
return new Color(MathUtils.clamp(this.r + 40, 0, 255), MathUtils.clamp(this.g + 40, 0, 255), MathUtils.clamp(this.b + 40, 0, 255), this.a); return new Color(MathUtils.clamp(this.r + 40, 0, 255), MathUtils.clamp(this.g + 40, 0, 255), MathUtils.clamp(this.b + 40, 0, 255), MathUtils.clamp(this.a + 10, 0, 255));
} }
/** /**
@ -135,7 +135,7 @@ export default class Color {
* @returns A new darker Color * @returns A new darker Color
*/ */
darken(): Color { darken(): Color {
return new Color(MathUtils.clamp(this.r - 40, 0, 255), MathUtils.clamp(this.g - 40, 0, 255), MathUtils.clamp(this.b - 40, 0, 255), this.a); return new Color(MathUtils.clamp(this.r - 40, 0, 255), MathUtils.clamp(this.g - 40, 0, 255), MathUtils.clamp(this.b - 40, 0, 255), MathUtils.clamp(this.a + 10, 0, 255));
} }
/** /**

View File

@ -1,6 +1,7 @@
import MathUtils from "./MathUtils"; import MathUtils from "./MathUtils";
import Color from "./Color"; import Color from "./Color";
import Perlin from "./Rand/Perlin"; import Perlin from "./Rand/Perlin";
import Vec2 from "../DataTypes/Vec2";
class Noise { class Noise {
p: Perlin = new Perlin(); p: Perlin = new Perlin();
@ -22,6 +23,16 @@ export default class RandUtils {
return Math.floor(Math.random()*(max - min) + min); return Math.floor(Math.random()*(max - min) + min);
} }
/**
* Generates a random float in the specified range
* @param min The min of the range (inclusive)
* @param max The max of the range (exclusive)
* @returns A random float in the range [min, max)
*/
static randFloat(min: number, max: number): number {
return Math.random()*(max - min) + min;
}
/** /**
* Generates a random hexadecimal number in the specified range * Generates a random hexadecimal number in the specified range
* @param min The min of the range (inclusive) * @param min The min of the range (inclusive)
@ -43,6 +54,10 @@ export default class RandUtils {
return new Color(r, g, b); return new Color(r, g, b);
} }
static randVec(minX: number, maxX: number, minY: number, maxY: number): Vec2 {
return new Vec2(this.randFloat(minX, maxX), this.randFloat(minY, maxY));
}
/** A noise generator */ /** A noise generator */
static noise: Noise = new Noise(); static noise: Noise = new Noise();

View File

@ -31,7 +31,7 @@ export default class default_scene extends Scene {
// The first argument is the key of the sprite (you get to decide what it is). // The first argument is the key of the sprite (you get to decide what it is).
// The second argument is the path to the actual image. // The second argument is the path to the actual image.
// Paths start in the "dist/" folder, so start building your path from there // Paths start in the "dist/" folder, so start building your path from there
this.load.image("logo", "demo_assets/wolfie2d_text.png"); this.load.image("logo", "demo_assets/images/wolfie2d_text.png");
} }
// startScene() is where you should build any game objects you wish to have in your scene, // startScene() is where you should build any game objects you wish to have in your scene,

View File

@ -1,7 +1,7 @@
import PlayerController from "./PlatformerPlayerController"; import PlayerController from "./PlatformerPlayerController";
import Vec2 from "./Wolfie2D/DataTypes/Vec2"; import Vec2 from "../Wolfie2D/DataTypes/Vec2";
import AnimatedSprite from "./Wolfie2D/Nodes/Sprites/AnimatedSprite"; import AnimatedSprite from "../Wolfie2D/Nodes/Sprites/AnimatedSprite";
import Scene from "./Wolfie2D/Scene/Scene"; import Scene from "../Wolfie2D/Scene/Scene";
export default class Platformer extends Scene { export default class Platformer extends Scene {
private player: AnimatedSprite; private player: AnimatedSprite;

View File

@ -1,9 +1,9 @@
import AI from "./Wolfie2D/DataTypes/Interfaces/AI"; import AI from "../Wolfie2D/DataTypes/Interfaces/AI";
import Emitter from "./Wolfie2D/Events/Emitter"; import Emitter from "../Wolfie2D/Events/Emitter";
import GameEvent from "./Wolfie2D/Events/GameEvent"; import GameEvent from "../Wolfie2D/Events/GameEvent";
import { GameEventType } from "./Wolfie2D/Events/GameEventType"; import { GameEventType } from "../Wolfie2D/Events/GameEventType";
import Input from "./Wolfie2D/Input/Input"; import Input from "../Wolfie2D/Input/Input";
import AnimatedSprite from "./Wolfie2D/Nodes/Sprites/AnimatedSprite"; import AnimatedSprite from "../Wolfie2D/Nodes/Sprites/AnimatedSprite";
export default class PlayerController implements AI { export default class PlayerController implements AI {
protected owner: AnimatedSprite; protected owner: AnimatedSprite;
@ -16,6 +16,8 @@ export default class PlayerController implements AI {
this.emitter = new Emitter(); this.emitter = new Emitter();
} }
activate(options: Record<string, any>): void {}
handleEvent(event: GameEvent): void { handleEvent(event: GameEvent): void {
// Do nothing for now // Do nothing for now
} }

View File

@ -1,68 +0,0 @@
import Map from "../Wolfie2D/DataTypes/Map";
import Mat4x4 from "../Wolfie2D/DataTypes/Mat4x4";
import Vec2 from "../Wolfie2D/DataTypes/Vec2";
import RectShaderType from "../Wolfie2D/Rendering/WebGLRendering/ShaderTypes/RectShaderType";
/**
* The gradient circle is technically rendered on a quad, and is similar to a rect, so we'll extend the RectShaderType
*/
export default class GradientCircleShaderType extends RectShaderType {
initBufferObject(): void {
this.bufferObjectKey = "gradient_circle";
this.resourceManager.createBuffer(this.bufferObjectKey);
}
render(gl: WebGLRenderingContext, options: Record<string, any>): void {
// Get our program and buffer object
const program = this.resourceManager.getShaderProgram(this.programKey);
const buffer = this.resourceManager.getBuffer(this.bufferObjectKey);
// Let WebGL know we're using our shader program
gl.useProgram(program);
// Get our vertex data
const vertexData = this.getVertices(options.size.x, options.size.y);
const FSIZE = vertexData.BYTES_PER_ELEMENT;
// Bind the buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);
/* ##### ATTRIBUTES ##### */
// No texture, the only thing we care about is vertex position
const a_Position = gl.getAttribLocation(program, "a_Position");
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 2 * FSIZE, 0 * FSIZE);
gl.enableVertexAttribArray(a_Position);
/* ##### UNIFORMS ##### */
// Send the color to the gradient circle
const color = options.color.toWebGL();
const u_Color = gl.getUniformLocation(program, "u_Color");
gl.uniform4fv(u_Color, color);
// Get transformation matrix
// We have a square for our rendering space, so get the maximum dimension of our quad
let maxDimension = Math.max(options.size.x, options.size.y);
// The size of the rendering space will be a square with this maximum dimension
let size = new Vec2(maxDimension, maxDimension).scale(2/options.worldSize.x, 2/options.worldSize.y);
// Center our translations around (0, 0)
const translateX = (options.position.x - options.origin.x - options.worldSize.x/2)/maxDimension;
const translateY = -(options.position.y - options.origin.y - options.worldSize.y/2)/maxDimension;
// Create our transformation matrix
this.translation.translate(new Float32Array([translateX, translateY]));
this.scale.scale(size);
this.rotation.rotate(options.rotation);
let transformation = Mat4x4.MULT(this.translation, this.scale, this.rotation);
// Pass the translation matrix to our shader
const u_Transform = gl.getUniformLocation(program, "u_Transform");
gl.uniformMatrix4fv(u_Transform, false, transformation.toArray());
// Draw the quad
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
}

View File

@ -1,8 +0,0 @@
export enum Homework1Event {
PLAYER_DAMAGE = "PLAYER_DAMAGE",
SPAWN_FLEET = "SPAWN_FLEET"
}
export enum Homework1Shaders {
GRADIENT_CIRCLE = "GRADIENT_CIRCLE"
}

View File

@ -1,107 +0,0 @@
import AABB from "../Wolfie2D/DataTypes/Shapes/AABB";
import Vec2 from "../Wolfie2D/DataTypes/Vec2";
import Graphic from "../Wolfie2D/Nodes/Graphic";
import { GraphicType } from "../Wolfie2D/Nodes/Graphics/GraphicTypes";
import AnimatedSprite from "../Wolfie2D/Nodes/Sprites/AnimatedSprite";
import Scene from "../Wolfie2D/Scene/Scene";
import { Homework1Event, Homework1Shaders } from "./HW1_Enums";
import SpaceshipPlayerController from "./SpaceshipPlayerController";
/**
* In Wolfie2D, custom scenes extend the original scene class.
* This gives us access to lifecycle methods to control our game.
*/
export default class Homework1_Scene extends Scene {
// Here we define member variables of our game, and object pools for adding in game objects
private player: AnimatedSprite;
// Create an object pool for our fleet
private MAX_FLEET_SIZE = 20;
private fleet: Array<AnimatedSprite> = new Array(this.MAX_FLEET_SIZE);
// Create an object pool for our fleet
private MAX_NUM_ASTEROIDS = 6;
private asteroids: Array<Graphic> = new Array(this.MAX_NUM_ASTEROIDS);
// Create an object pool for our fleet
private MAX_NUM_MINERALS = 20;
private minerals: Array<Graphic> = new Array(this.MAX_NUM_MINERALS);
/*
* loadScene() overrides the parent class method. It allows us to load in custom assets for
* use in our scene.
*/
loadScene(){
/* ##### DO NOT MODIFY ##### */
// Load in the player spaceship spritesheet
this.load.spritesheet("player", "hw1_assets/spritesheets/player_spaceship.json");
/* ##### YOUR CODE GOES BELOW THIS LINE ##### */
}
/*
* startScene() allows us to add in the assets we loaded in loadScene() as game objects.
* Everything here happens strictly before update
*/
startScene(){
/* ##### DO NOT MODIFY ##### */
// Create a layer to serve as our main game - Feel free to use this for your own assets
// It is given a depth of 5 to be above our background
this.addLayer("primary", 5);
// Add in the player as an animated sprite
// We give it the key specified in our load function and the name of the layer
this.player = this.add.animatedSprite("player", "primary");
// Set the player's position to the middle of the screen, and scale it down
this.player.position.set(this.viewport.getCenter().x, this.viewport.getCenter().y);
this.player.scale.set(0.5, 0.5);
// Play the idle animation by default
this.player.animation.play("idle");
// Add physics to the player
let playerCollider = new AABB(Vec2.ZERO, new Vec2(64, 64));
// We'll specify a smaller collider centered on the player.
// Also, we don't need collision handling, so disable it.
this.player.addPhysics(playerCollider, Vec2.ZERO, false);
// Add a a playerController to the player
this.player.addAI(SpaceshipPlayerController, {owner: this.player, spawnFleetEventKey: "spawnFleet"});
/* ##### YOUR CODE GOES BELOW THIS LINE ##### */
// Initialize the fleet object pool
// Initialize the mineral object pool
for(let i = 0; i < this.minerals.length; i++){
this.minerals[i] = this.add.graphic(GraphicType.RECT, "primary", {position: new Vec2(0, 0), size: new Vec2(32, 32)});
this.minerals[i].visible = false;
}
// Initialize the asteroid object pool
let gc = this.add.graphic(GraphicType.RECT, "primary", {position: new Vec2(400, 400), size: new Vec2(100, 100)});
gc.useCustomShader(Homework1Shaders.GRADIENT_CIRCLE);
// Subscribe to events
this.receiver.subscribe(Homework1Event.PLAYER_DAMAGE);
this.receiver.subscribe(Homework1Event.SPAWN_FLEET);
}
/*
* updateScene() is where the real work is done. This is where any custom behavior goes.
*/
updateScene(){
// Handle events we care about
while(this.receiver.hasNextEvent()){
let event = this.receiver.getNextEvent();
}
// Check for collisions
for(let i = 0; i < this.minerals.length; i++){
if(this.player.collisionShape.overlaps(this.minerals[i].boundary)){
console.log(true);
}
}
}
}

View File

@ -1,77 +0,0 @@
import AI from "../Wolfie2D/DataTypes/Interfaces/AI";
import Vec2 from "../Wolfie2D/DataTypes/Vec2";
import Debug from "../Wolfie2D/Debug/Debug";
import Emitter from "../Wolfie2D/Events/Emitter";
import GameEvent from "../Wolfie2D/Events/GameEvent";
import Input from "../Wolfie2D/Input/Input";
import AnimatedSprite from "../Wolfie2D/Nodes/Sprites/AnimatedSprite";
import MathUtils from "../Wolfie2D/Utils/MathUtils";
import { Homework1Event } from "./HW1_Enums";
export default class SpaceshipPlayerController implements AI {
// We want to be able to control our owner, so keep track of them
private owner: AnimatedSprite;
// The direction the spaceship is moving
private direction: Vec2;
private MIN_SPEED: number = 0;
private MAX_SPEED: number = 300;
private speed: number;
private ACCELERATION: number = 4;
private rotationSpeed: number;
// An emitter to hook into the event queue
private emitter: Emitter;
initializeAI(owner: AnimatedSprite, options: Record<string, any>): void {
this.owner = owner;
// Start facing up
this.direction = new Vec2(0, 1);
this.speed = 0;
this.rotationSpeed = 2;
this.emitter = new Emitter();
}
handleEvent(event: GameEvent): void {
// We need to handle animations when we get hurt
if(event.type === Homework1Event.PLAYER_DAMAGE){
this.owner.animation.play("shield");
}
}
update(deltaT: number): void {
// We need to handle player input
let forwardAxis = (Input.isPressed('forward') ? 1 : 0) + (Input.isPressed('backward') ? -1 : 0);
let turnDirection = (Input.isPressed('turn_ccw') ? -1 : 0) + (Input.isPressed('turn_cw') ? 1 : 0);
// Space controls - speed stays the same if nothing happens
// Forward to speed up, backward to slow down
this.speed += this.ACCELERATION * forwardAxis;
this.speed = MathUtils.clamp(this.speed, this.MIN_SPEED, this.MAX_SPEED);
// Rotate the player
this.direction.rotateCCW(turnDirection * this.rotationSpeed * deltaT);
// Update the visual direction of the player
this.owner.rotation = -(Math.atan2(this.direction.y, this.direction.x) - Math.PI/2);
// Move the player with physics
this.owner.move(this.direction.scaled(-this.speed * deltaT));
// If the player clicked, we need to spawn in a fleet member
if(Input.isMouseJustPressed()){
this.emitter.fireEvent(Homework1Event.SPAWN_FLEET, {position: Input.getGlobalMousePosition()});
}
// Animations
if(!this.owner.animation.isPlaying("shield")){
if(this.speed > 0){
this.owner.animation.playIfNotAlready("boost");
} else {
this.owner.animation.playIfNotAlready("idle");
}
}
}
}

View File

@ -22,6 +22,15 @@
left: 0px; left: 0px;
} }
#text-canvas {
width: 100%;
height: 100%;
position: absolute;
top: 0px;
left: 0px;
pointer-events: none;
}
#debug-canvas { #debug-canvas {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -38,6 +47,8 @@
<div id="game-window"> <div id="game-window">
<!-- This is the canvas where the actual game is rendered --> <!-- This is the canvas where the actual game is rendered -->
<canvas id="game-canvas"></canvas> <canvas id="game-canvas"></canvas>
<!-- This is the canvas where text is rendered in WebGL -->
<canvas id="text-canvas" hidden></canvas>
<!-- This is the canvas where the debug text and graphics are rendered --> <!-- This is the canvas where the debug text and graphics are rendered -->
<canvas id="debug-canvas"></canvas> <canvas id="debug-canvas"></canvas>
</div> </div>

View File

@ -1,36 +1,24 @@
import Game from "./Wolfie2D/Loop/Game"; import Game from "./Wolfie2D/Loop/Game";
import Homework1_Scene from "./hw1/HW1_Scene"; import default_scene from "./default_scene";
import Registry from "./Wolfie2D/Registry/Registry";
import { Homework1Shaders } from "./hw1/HW1_Enums";
import GradientCircleShaderType from "./hw1/GradientCircleShaderType";
// The main function is your entrypoint into Wolfie2D. Specify your first scene and any options here. // The main function is your entrypoint into Wolfie2D. Specify your first scene and any options here.
(function main(){ (function main(){
// Set up options // Run any tests
let options = { runTests();
canvasSize: {x: 1200, y: 800},
clearColor: {r: 0.1, g: 0.1, b: 0.1},
inputs: [
{ name: "forward", keys: ["w"] },
{ name: "backward", keys: ["s"] },
{ name: "turn_ccw", keys: ["a"] },
{ name: "turn_cw", keys: ["d"] },
],
useWebGL: true,
showDebug: false
}
// We have a custom shader, so lets add it to the registry and preload it // Set up options for our game
Registry.shaders.registerAndPreloadItem( let options = {
Homework1Shaders.GRADIENT_CIRCLE, // The key of the shader program canvasSize: {x: 1200, y: 800}, // The size of the game
GradientCircleShaderType, // The constructor of the shader program clearColor: {r: 0.1, g: 0.1, b: 0.1}, // The color the game clears to
"hw1_assets/shaders/gradient_circle.vshader", // The path to the vertex shader useWebGL: true, // Tell the game we want to use webgl
"hw1_assets/shaders/gradient_circle.fshader"); // the path to the fragment shader showDebug: false // Whether to show debug messages. You can change this to true if you want
}
// Create a game with the options specified // Create a game with the options specified
const game = new Game(options); const game = new Game(options);
// Start our game // Start our game
game.start(); game.start(default_scene, {});
game.getSceneManager().addScene(Homework1_Scene, {});
})(); })();
function runTests(){};