diff --git a/dist/builtin/shaders/label.fshader b/dist/builtin/shaders/label.fshader new file mode 100644 index 0000000..a248cba --- /dev/null +++ b/dist/builtin/shaders/label.fshader @@ -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; + } + } + */ +} \ No newline at end of file diff --git a/dist/builtin/shaders/label.vshader b/dist/builtin/shaders/label.vshader new file mode 100644 index 0000000..093de28 --- /dev/null +++ b/dist/builtin/shaders/label.vshader @@ -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; +} \ No newline at end of file diff --git a/dist/hw1_assets/shaders/gradient_circle.fshader b/dist/hw1_assets/shaders/gradient_circle.fshader deleted file mode 100644 index 3cd2e62..0000000 --- a/dist/hw1_assets/shaders/gradient_circle.fshader +++ /dev/null @@ -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); -} \ No newline at end of file diff --git a/dist/hw1_assets/shaders/gradient_circle.vshader b/dist/hw1_assets/shaders/gradient_circle.vshader deleted file mode 100644 index 9cb98c8..0000000 --- a/dist/hw1_assets/shaders/gradient_circle.vshader +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/dist/hw1_assets/spritesheets/player_spaceship.json b/dist/hw1_assets/spritesheets/player_spaceship.json deleted file mode 100644 index 9ae8470..0000000 --- a/dist/hw1_assets/spritesheets/player_spaceship.json +++ /dev/null @@ -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 - } - ] - } - ] -} \ No newline at end of file diff --git a/dist/hw1_assets/spritesheets/player_spaceship.png b/dist/hw1_assets/spritesheets/player_spaceship.png deleted file mode 100644 index 4b0f482..0000000 Binary files a/dist/hw1_assets/spritesheets/player_spaceship.png and /dev/null differ diff --git a/src/WebGLScene.ts b/src/WebGLScene.ts deleted file mode 100644 index d95cb02..0000000 --- a/src/WebGLScene.ts +++ /dev/null @@ -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 = 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; - } -} \ No newline at end of file diff --git a/src/Wolfie2D/AI/ControllerAI.ts b/src/Wolfie2D/AI/ControllerAI.ts deleted file mode 100644 index 8eb3c62..0000000 --- a/src/Wolfie2D/AI/ControllerAI.ts +++ /dev/null @@ -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): void { - this.owner = owner; - } - - handleEvent(event: GameEvent): void {} - - update(deltaT: number): void {} -} \ No newline at end of file diff --git a/src/Wolfie2D/AI/StateMachineAI.ts b/src/Wolfie2D/AI/StateMachineAI.ts index 1ee0ddf..d0cc34d 100644 --- a/src/Wolfie2D/AI/StateMachineAI.ts +++ b/src/Wolfie2D/AI/StateMachineAI.ts @@ -12,4 +12,6 @@ export default class StateMachineAI extends StateMachine implements AI { // @implemented initializeAI(owner: GameNode, config: Record): void {} + + activate(options: Record): void {} } \ No newline at end of file diff --git a/src/Wolfie2D/DataTypes/Interfaces/AI.ts b/src/Wolfie2D/DataTypes/Interfaces/AI.ts index a7572e9..33d0c86 100644 --- a/src/Wolfie2D/DataTypes/Interfaces/AI.ts +++ b/src/Wolfie2D/DataTypes/Interfaces/AI.ts @@ -10,6 +10,9 @@ export default interface AI extends Updateable { /** Initializes the AI with the actor and any additional config */ initializeAI(owner: GameNode, options: Record): void; + /** Activates this AI from a stopped state and allows variables to be passed in */ + activate(options: Record): void; + /** Handles events from the Actor */ handleEvent(event: GameEvent): void; } \ No newline at end of file diff --git a/src/Wolfie2D/DataTypes/Interfaces/Actor.ts b/src/Wolfie2D/DataTypes/Interfaces/Actor.ts index ea7947d..37dc91a 100644 --- a/src/Wolfie2D/DataTypes/Interfaces/Actor.ts +++ b/src/Wolfie2D/DataTypes/Interfaces/Actor.ts @@ -30,6 +30,7 @@ export default interface Actor { /** * Sets the AI to start/stop for this Actor. * @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): void; } \ No newline at end of file diff --git a/src/Wolfie2D/DataTypes/Shapes/AABB.ts b/src/Wolfie2D/DataTypes/Shapes/AABB.ts index ad6fb42..d80c9db 100644 --- a/src/Wolfie2D/DataTypes/Shapes/AABB.ts +++ b/src/Wolfie2D/DataTypes/Shapes/AABB.ts @@ -23,6 +23,26 @@ export default class AABB extends Shape { 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 getBoundingRect(): AABB { return this.clone(); diff --git a/src/Wolfie2D/DataTypes/Shapes/Circle.ts b/src/Wolfie2D/DataTypes/Shapes/Circle.ts index 8cdca38..004ab79 100644 --- a/src/Wolfie2D/DataTypes/Shapes/Circle.ts +++ b/src/Wolfie2D/DataTypes/Shapes/Circle.ts @@ -7,7 +7,7 @@ import Shape from "./Shape"; */ export default class Circle extends Shape { private _center: Vec2; - private radius: number; + radius: number; /** * Creates a new Circle @@ -32,6 +32,24 @@ export default class Circle extends Shape { 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 getBoundingRect(): AABB { return new AABB(this._center.clone(), new Vec2(this.radius, this.radius)); @@ -51,4 +69,8 @@ export default class Circle extends Shape { clone(): Circle { return new Circle(this._center.clone(), this.radius); } + + toString(): string { + return "(center: " + this.center.toString() + ", radius: " + this.radius + ")"; + } } \ No newline at end of file diff --git a/src/Wolfie2D/DataTypes/Shapes/Shape.ts b/src/Wolfie2D/DataTypes/Shapes/Shape.ts index 93c00b9..04113c9 100644 --- a/src/Wolfie2D/DataTypes/Shapes/Shape.ts +++ b/src/Wolfie2D/DataTypes/Shapes/Shape.ts @@ -71,6 +71,13 @@ export default abstract class Shape { */ 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] { if(A instanceof AABB && B instanceof AABB){ return Shape.getTimeOfCollision_AABB_AABB(A, velA, B, velB); diff --git a/src/Wolfie2D/DataTypes/Vec2.ts b/src/Wolfie2D/DataTypes/Vec2.ts index 10783a9..63d1195 100644 --- a/src/Wolfie2D/DataTypes/Vec2.ts +++ b/src/Wolfie2D/DataTypes/Vec2.ts @@ -269,6 +269,17 @@ export default class Vec2 { 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 * @param other The vector to compute distance squared to diff --git a/src/Wolfie2D/Debug/Debug.ts b/src/Wolfie2D/Debug/Debug.ts index 7cb2090..22cfc8b 100644 --- a/src/Wolfie2D/Debug/Debug.ts +++ b/src/Wolfie2D/Debug/Debug.ts @@ -78,6 +78,36 @@ export default class Debug { 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 * @param from The starting position of the ray diff --git a/src/Wolfie2D/Loop/Game.ts b/src/Wolfie2D/Loop/Game.ts index 47b174c..0e40f36 100644 --- a/src/Wolfie2D/Loop/Game.ts +++ b/src/Wolfie2D/Loop/Game.ts @@ -18,6 +18,7 @@ import EnvironmentInitializer from "./EnvironmentInitializer"; import Vec2 from "../DataTypes/Vec2"; import Registry from "../Registry/Registry"; import WebGLRenderer from "../Rendering/WebGLRenderer"; +import Scene from "../Scene/Scene"; /** * The main loop of the game engine. @@ -130,7 +131,7 @@ export default class Game { /** * Starts the game */ - start(): void { + start(InitialScene: new (...args: any) => Scene, options: Record): void { // Set the update function of the loop this.loop.doUpdate = (deltaT: number) => this.update(deltaT); @@ -142,7 +143,9 @@ export default class Game { // Load the items with the resource manager 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(); }); } @@ -178,14 +181,7 @@ export default class Game { // Clear the canvases Debug.clearCanvas(); - if(this.gameOptions.useWebGL){ - (this.ctx).clearColor(this.clearColor.r, this.clearColor.g, this.clearColor.b, this.clearColor.a); - (this.ctx).clear((this.ctx).COLOR_BUFFER_BIT | (this.ctx).DEPTH_BUFFER_BIT); - } else { - (this.ctx).clearRect(0, 0, this.WIDTH, this.HEIGHT); - (this.ctx).fillStyle = this.clearColor.toString(); - (this.ctx).fillRect(0, 0, this.WIDTH, this.HEIGHT); - } + this.renderingManager.clear(this.clearColor); this.sceneManager.render(); diff --git a/src/Wolfie2D/Nodes/GameNode.ts b/src/Wolfie2D/Nodes/GameNode.ts index 260a687..c3a0fd1 100644 --- a/src/Wolfie2D/Nodes/GameNode.ts +++ b/src/Wolfie2D/Nodes/GameNode.ts @@ -18,6 +18,7 @@ import NavigationPath from "../Pathfinding/NavigationPath"; import TweenManager from "../Rendering/Animations/TweenManager"; import Debug from "../Debug/Debug"; import Color from "../Utils/Color"; +import Circle from "../DataTypes/Shapes/Circle"; /** * 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); } + /** + * 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 /** * @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 - setAIActive(active: boolean): void { + setAIActive(active: boolean, options: Record): void { this.aiActive = active; + this.ai.activate(options); } /*---------- TWEENABLE PROPERTIES ----------*/ @@ -306,8 +317,13 @@ export default abstract class GameNode implements Positioned, Unique, Updateable /** Called if the position vector is modified or replaced */ protected positionChanged(): void { - if(this.hasPhysics){ - this.collisionShape.center = this.position.clone().add(this.colliderOffset); + if(this.collisionShape){ + if(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.hasPhysics && this.collisionShape){ + if(this.collisionShape){ let color = this.isColliding ? Color.RED : Color.GREEN; if(this.isTrigger){ - color = Color.PURPLE; + color = Color.MAGENTA; } color.a = 0.2; - Debug.drawBox(this.inRelativeCoordinates(this.collisionShape.center), this.collisionShape.halfSize.scaled(this.scene.getViewScale()), true, color); + + if(this.collisionShape instanceof AABB){ + 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); + } } } } diff --git a/src/Wolfie2D/Nodes/UIElement.ts b/src/Wolfie2D/Nodes/UIElement.ts index 35bd508..02aec9a 100644 --- a/src/Wolfie2D/Nodes/UIElement.ts +++ b/src/Wolfie2D/Nodes/UIElement.ts @@ -82,7 +82,7 @@ export default abstract class UIElement extends CanvasNode { // See of this object was just clicked if(Input.isMouseJustPressed()){ 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; 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 * @returns The background color of the UIElement */ - calculateBackgroundColor(): string { - return this.backgroundColor.toStringRGBA(); + calculateBackgroundColor(): Color { + return this.backgroundColor; } /** * 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 */ - calculateBorderColor(): string { - return this.borderColor.toStringRGBA(); + calculateBorderColor(): Color { + return this.borderColor; } } \ No newline at end of file diff --git a/src/Wolfie2D/Nodes/UIElements/Button.ts b/src/Wolfie2D/Nodes/UIElements/Button.ts index 4b9db1f..8953854 100644 --- a/src/Wolfie2D/Nodes/UIElements/Button.ts +++ b/src/Wolfie2D/Nodes/UIElements/Button.ts @@ -14,14 +14,14 @@ export default class Button extends Label { } // @override - calculateBackgroundColor(): string { + calculateBackgroundColor(): Color { // Change the background color if clicked or hovered if(this.isEntered && !this.isClicked){ - return this.backgroundColor.lighten().toStringRGBA(); + return this.backgroundColor.lighten(); } else if(this.isClicked){ - return this.backgroundColor.darken().toStringRGBA(); + return this.backgroundColor.darken(); } else { - return this.backgroundColor.toStringRGBA(); + return this.backgroundColor; } } } \ No newline at end of file diff --git a/src/Wolfie2D/Nodes/UIElements/Label.ts b/src/Wolfie2D/Nodes/UIElements/Label.ts index 37e558c..b680401 100644 --- a/src/Wolfie2D/Nodes/UIElements/Label.ts +++ b/src/Wolfie2D/Nodes/UIElements/Label.ts @@ -68,6 +68,10 @@ export default class Label extends UIElement{ 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 * @param ctx The rendering context diff --git a/src/Wolfie2D/Registry/Registries/ShaderRegistry.ts b/src/Wolfie2D/Registry/Registries/ShaderRegistry.ts index 33d4636..ec88e11 100644 --- a/src/Wolfie2D/Registry/Registries/ShaderRegistry.ts +++ b/src/Wolfie2D/Registry/Registries/ShaderRegistry.ts @@ -1,5 +1,6 @@ import Map from "../../DataTypes/Map"; import ShaderType from "../../Rendering/WebGLRendering/ShaderType"; +import LabelShaderType from "../../Rendering/WebGLRendering/ShaderTypes/LabelShaderType"; import PointShaderType from "../../Rendering/WebGLRendering/ShaderTypes/PointShaderType"; import RectShaderType from "../../Rendering/WebGLRendering/ShaderTypes/RectShaderType"; import SpriteShaderType from "../../Rendering/WebGLRendering/ShaderTypes/SpriteShaderType"; @@ -14,6 +15,7 @@ export default class ShaderRegistry extends Map { public static POINT_SHADER = "point"; public static RECT_SHADER = "rect"; public static SPRITE_SHADER = "sprite"; + public static LABEL_SHADER = "label"; private registryItems: Array = new Array(); @@ -21,8 +23,6 @@ export default class ShaderRegistry extends Map { * Preloads all built-in shaders */ public preload(){ - console.log("Preloading"); - // Get the resourceManager and queue all built-in shaders for preloading const rm = ResourceManager.getInstance(); @@ -35,17 +35,17 @@ export default class ShaderRegistry extends Map { // Queue a load for the sprite shader 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 for(let item of this.registryItems){ const shader = new item.constr(item.key); shader.initBufferObject(); this.add(item.key, shader); - console.log("Added", item.key); - // Load if desired if(item.preload !== undefined){ - console.log("Preloading", item.key); rm.shader(item.key, item.preload.vshaderLocation, item.preload.fshaderLocation); } } diff --git a/src/Wolfie2D/Rendering/CanvasRenderer.ts b/src/Wolfie2D/Rendering/CanvasRenderer.ts index 6a72b69..d6f5433 100644 --- a/src/Wolfie2D/Rendering/CanvasRenderer.ts +++ b/src/Wolfie2D/Rendering/CanvasRenderer.ts @@ -19,6 +19,7 @@ import Slider from "../Nodes/UIElements/Slider"; import TextInput from "../Nodes/UIElements/TextInput"; import AnimatedSprite from "../Nodes/Sprites/AnimatedSprite"; import Vec2 from "../DataTypes/Vec2"; +import Color from "../Utils/Color"; /** * An implementation of the RenderingManager class using CanvasRenderingContext2D. @@ -32,6 +33,8 @@ export default class CanvasRenderer extends RenderingManager { protected origin: Vec2; protected zoom: number; + protected worldSize: Vec2; + constructor(){ super(); } @@ -49,6 +52,8 @@ export default class CanvasRenderer extends RenderingManager { canvas.width = width; canvas.height = height; + this.worldSize = new Vec2(width, height); + this.ctx = canvas.getContext("2d"); this.graphicRenderer = new GraphicRenderer(this.ctx); @@ -107,7 +112,10 @@ export default class CanvasRenderer extends RenderingManager { } // Render the uiLayers on top of everything else - uiLayers.forEach(key => uiLayers.get(key).getItems().forEach(node => this.renderNode(node))); + uiLayers.forEach(key => { + if(!uiLayers.get(key).isHidden()) + uiLayers.get(key).getItems().forEach(node => this.renderNode(node)) + }); } /** @@ -221,4 +229,10 @@ export default class CanvasRenderer extends RenderingManager { 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); + } } \ No newline at end of file diff --git a/src/Wolfie2D/Rendering/CanvasRendering/UIElementRenderer.ts b/src/Wolfie2D/Rendering/CanvasRendering/UIElementRenderer.ts index f57cce9..69b02e1 100644 --- a/src/Wolfie2D/Rendering/CanvasRendering/UIElementRenderer.ts +++ b/src/Wolfie2D/Rendering/CanvasRendering/UIElementRenderer.ts @@ -45,11 +45,11 @@ export default class UIElementRenderer { // Stroke and fill a rounded rect and give it text 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, 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.lineWidth = label.borderWidth; this.ctx.strokeRoundedRect(-label.size.x/2, -label.size.y/2, diff --git a/src/Wolfie2D/Rendering/RenderingManager.ts b/src/Wolfie2D/Rendering/RenderingManager.ts index 62f1605..3f4f91d 100644 --- a/src/Wolfie2D/Rendering/RenderingManager.ts +++ b/src/Wolfie2D/Rendering/RenderingManager.ts @@ -8,6 +8,7 @@ import UIElement from "../Nodes/UIElement"; import ResourceManager from "../ResourceManager/ResourceManager"; import UILayer from "../Scene/Layers/UILayer"; import Scene from "../Scene/Scene"; +import Color from "../Utils/Color"; /** * 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, tilemaps: Array, uiLayers: Map): void; + /** Clears the canvas */ + abstract clear(color: Color): void; + /** * Renders a sprite * @param sprite The sprite to render diff --git a/src/Wolfie2D/Rendering/WebGLRenderer.ts b/src/Wolfie2D/Rendering/WebGLRenderer.ts index 1e91d09..1157146 100644 --- a/src/Wolfie2D/Rendering/WebGLRenderer.ts +++ b/src/Wolfie2D/Rendering/WebGLRenderer.ts @@ -1,6 +1,7 @@ import Graph from "../DataTypes/Graphs/Graph"; import Map from "../DataTypes/Map"; import Vec2 from "../DataTypes/Vec2"; +import Debug from "../Debug/Debug"; import CanvasNode from "../Nodes/CanvasNode"; import Graphic from "../Nodes/Graphic"; import { GraphicType } from "../Nodes/Graphics/GraphicTypes"; @@ -10,10 +11,13 @@ import AnimatedSprite from "../Nodes/Sprites/AnimatedSprite"; import Sprite from "../Nodes/Sprites/Sprite"; import Tilemap from "../Nodes/Tilemap"; import UIElement from "../Nodes/UIElement"; +import Label from "../Nodes/UIElements/Label"; import ShaderRegistry from "../Registry/Registries/ShaderRegistry"; import Registry from "../Registry/Registry"; import ResourceManager from "../ResourceManager/ResourceManager"; +import ParallaxLayer from "../Scene/Layers/ParallaxLayer"; import UILayer from "../Scene/Layers/UILayer"; +import Color from "../Utils/Color"; import RenderingUtils from "../Utils/RenderingUtils"; import RenderingManager from "./RenderingManager"; import ShaderType from "./WebGLRendering/ShaderType"; @@ -25,6 +29,7 @@ export default class WebGLRenderer extends RenderingManager { protected worldSize: Vec2; protected gl: WebGLRenderingContext; + protected textCtx: CanvasRenderingContext2D; initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): WebGLRenderingContext { canvas.width = width; @@ -47,6 +52,15 @@ export default class WebGLRenderer extends RenderingManager { // Tell the resource manager we're using WebGL ResourceManager.getInstance().useWebGL(true, this.gl); + // Show the text canvas and get its context + let textCanvas = 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; } @@ -54,6 +68,18 @@ export default class WebGLRenderer extends RenderingManager { for(let node of visibleSet){ this.renderNode(node); } + + uiLayers.forEach(key => { + if(!uiLayers.get(key).isHidden()) + uiLayers.get(key).getItems().forEach(node => this.renderNode(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 { @@ -74,44 +100,32 @@ export default class WebGLRenderer extends RenderingManager { } else { this.renderSprite(node); } + } else if(node instanceof UIElement){ + this.renderUIElement(node); } } protected renderSprite(sprite: Sprite): void { let shader = Registry.shaders.get(ShaderRegistry.SPRITE_SHADER); - - let options = shader.getOptions(sprite); - options.worldSize = this.worldSize; - options.origin = this.origin; - + let options = this.addOptions(shader.getOptions(sprite), sprite); shader.render(this.gl, options); } protected renderAnimatedSprite(sprite: AnimatedSprite): void { let shader = Registry.shaders.get(ShaderRegistry.SPRITE_SHADER); - - let options = shader.getOptions(sprite); - options.worldSize = this.worldSize; - options.origin = this.origin; - - Registry.shaders.get(ShaderRegistry.SPRITE_SHADER).render(this.gl, options); + let options = this.addOptions(shader.getOptions(sprite), sprite); + shader.render(this.gl, options); } protected renderGraphic(graphic: Graphic): void { if(graphic instanceof Point){ let shader = Registry.shaders.get(ShaderRegistry.POINT_SHADER); - let options = shader.getOptions(graphic); - options.worldSize = this.worldSize; - options.origin = this.origin; - + let options = this.addOptions(shader.getOptions(graphic), graphic); shader.render(this.gl, options); } else if(graphic instanceof Rect) { let shader = Registry.shaders.get(ShaderRegistry.RECT_SHADER); - let options = shader.getOptions(graphic); - options.worldSize = this.worldSize; - options.origin = this.origin; - + let options = this.addOptions(shader.getOptions(graphic), graphic); shader.render(this.gl, options); } } @@ -121,16 +135,48 @@ export default class WebGLRenderer extends RenderingManager { } 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 { let shader = Registry.shaders.get(node.customShaderKey); - let options = shader.getOptions(node); - options.worldSize = this.worldSize; - options.origin = this.origin; - + let options = this.addOptions(shader.getOptions(node), node); shader.render(this.gl, options); } + protected addOptions(options: Record, node: CanvasNode): Record { + // 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 = (layer).parallax; + } + + options.origin = this.origin.clone().mult(parallax); + + return options; + } + } \ No newline at end of file diff --git a/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/LabelShaderType.ts b/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/LabelShaderType.ts new file mode 100644 index 0000000..7582ec0 --- /dev/null +++ b/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/LabelShaderType.ts @@ -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): 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 { + let options: Record = { + position: rect.position, + backgroundColor: rect.calculateBackgroundColor(), + borderColor: rect.calculateBorderColor(), + borderWidth: rect.borderWidth, + borderRadius: rect.borderRadius, + size: rect.size, + rotation: rect.rotation + } + + return options; + } +} \ No newline at end of file diff --git a/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/SpriteShaderType.ts b/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/SpriteShaderType.ts index 3753745..511d39a 100644 --- a/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/SpriteShaderType.ts +++ b/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/SpriteShaderType.ts @@ -22,24 +22,9 @@ export default class SpriteShaderType extends QuadShaderType { const program = this.resourceManager.getShaderProgram(this.programKey); const buffer = this.resourceManager.getBuffer(this.bufferObjectKey); const texture = this.resourceManager.getTexture(options.imageKey); - const image = this.resourceManager.getImage(options.imageKey); 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 FSIZE = vertexData.BYTES_PER_ELEMENT; @@ -79,9 +64,9 @@ export default class SpriteShaderType extends QuadShaderType { const u_Transform = gl.getUniformLocation(program, "u_Transform"); 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"); - gl.uniform1i(u_Sampler, 0); + gl.uniform1i(u_Sampler, texture); // Pass in 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); // Draw the quad - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4 ); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); } /** diff --git a/src/Wolfie2D/ResourceManager/ResourceManager.ts b/src/Wolfie2D/ResourceManager/ResourceManager.ts index 6532496..031c644 100644 --- a/src/Wolfie2D/ResourceManager/ResourceManager.ts +++ b/src/Wolfie2D/ResourceManager/ResourceManager.ts @@ -5,7 +5,6 @@ import StringUtils from "../Utils/StringUtils"; import AudioManager from "../Sound/AudioManager"; import Spritesheet from "../DataTypes/Spritesheet"; import WebGLProgramType from "../DataTypes/Rendering/WebGLProgramType"; -import PhysicsManager from "../Physics/PhysicsManager"; /** * The resource manager for the game engine. @@ -80,7 +79,8 @@ export default class ResourceManager { private gl_DefaultShaderPrograms: Map; private gl_ShaderPrograms: Map; - private gl_Textures: Map; + private gl_Textures: Map; + private gl_NextTextureID: number; private gl_Buffers: Map; private gl: WebGLRenderingContext; @@ -117,6 +117,7 @@ export default class ResourceManager { this.gl_ShaderPrograms = new Map(); this.gl_Textures = new Map(); + this.gl_NextTextureID = 0; 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 */ loadResourcesFromQueue(callback: Function): void { - this.loadonly_typesToLoad = 3; + this.loadonly_typesToLoad = 5; this.loading = true; @@ -443,7 +444,9 @@ export default class ResourceManager { this.images.add(key, image); // If WebGL is active, create a texture - this.createWebGLTexture(key); + if(this.gl_WebGLActive){ + this.createWebGLTexture(key, image); + } // Finish image load this.finishLoadingImage(callbackIfLast); @@ -526,7 +529,7 @@ export default class ResourceManager { /* ########## WEBGL SPECIFIC FUNCTIONS ########## */ - public getTexture(key: string): WebGLTexture { + public getTexture(key: string): number { return this.gl_Textures.get(key); } @@ -538,10 +541,49 @@ export default class ResourceManager { return this.gl_Buffers.get(key); } - private createWebGLTexture(key:string): void { - if(this.gl_WebGLActive){ - const texture = this.gl.createTexture(); - this.gl_Textures.add(key, texture); + private createWebGLTexture(imageKey: string, image: HTMLImageElement): void { + // Get the texture ID + const textureID = this.getTextureID(this.gl_NextTextureID); + + // Create the texture + const texture = this.gl.createTexture(); + + // 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; } } diff --git a/src/Wolfie2D/Scene/Scene.ts b/src/Wolfie2D/Scene/Scene.ts index 8dcbcde..0b2f506 100644 --- a/src/Wolfie2D/Scene/Scene.ts +++ b/src/Wolfie2D/Scene/Scene.ts @@ -94,7 +94,7 @@ export default class Scene implements Updateable { * @param options The options for Scene initialization */ constructor(viewport: Viewport, sceneManager: SceneManager, renderingManager: RenderingManager, options: Record){ - this.sceneOptions = SceneOptions.parse(options); + this.sceneOptions = SceneOptions.parse(options? options : {}); this.worldSize = new Vec2(500, 500); this.viewport = viewport; @@ -121,6 +121,9 @@ export default class Scene implements Updateable { this.load = ResourceManager.getInstance(); } + /** A lifecycle method that gets called immediately after a new scene is created, before anything else. */ + initScene(init: Record): void {} + /** 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 {} diff --git a/src/Wolfie2D/Scene/SceneManager.ts b/src/Wolfie2D/Scene/SceneManager.ts index 331c07c..e7d983a 100644 --- a/src/Wolfie2D/Scene/SceneManager.ts +++ b/src/Wolfie2D/Scene/SceneManager.ts @@ -1,7 +1,6 @@ import Scene from "./Scene"; import ResourceManager from "../ResourceManager/ResourceManager"; import Viewport from "../SceneGraph/Viewport"; -import Game from "../Loop/Game"; import RenderingManager from "../Rendering/RenderingManager"; /** @@ -41,11 +40,14 @@ export default class SceneManager { * 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. * @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(constr: new (...args: any) => T, options: Record): void { + public addScene(constr: new (...args: any) => T, init?: Record, options?: Record): void { let scene = new constr(this.viewport, this, this.renderingManager, options); this.currentScene = scene; + scene.initScene(init); + // Enqueue all scene asset loads scene.loadScene(); @@ -64,16 +66,12 @@ export default class SceneManager { * 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. * @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(constr: new (...args: any) => T, options: Record): void { - // unload current scene - this.currentScene.unloadScene(); + public changeScene(constr: new (...args: any) => T, init?: Record, options?: Record): void { + this.viewport.setCenter(this.viewport.getHalfSize().x, this.viewport.getHalfSize().y); - this.resourceManager.unloadAllResources(); - - this.viewport.setCenter(0, 0); - - this.addScene(constr, options); + this.addScene(constr, init, options); } /** @@ -88,9 +86,7 @@ export default class SceneManager { * Renders the current Scene */ public render(): void { - if(this.currentScene.isRunning()){ - this.currentScene.render(); - } + this.currentScene.render(); } /** diff --git a/src/Wolfie2D/SceneGraph/Viewport.ts b/src/Wolfie2D/SceneGraph/Viewport.ts index 320d469..6e85aee 100644 --- a/src/Wolfie2D/SceneGraph/Viewport.ts +++ b/src/Wolfie2D/SceneGraph/Viewport.ts @@ -49,14 +49,10 @@ export default class Viewport { // Set the size of the canvas this.setCanvasSize(canvasSize); - console.log(canvasSize, zoomLevel); - // Set the size of the viewport this.setSize(canvasSize); this.setZoomLevel(zoomLevel); - console.log(this.getHalfSize().toString()); - // Set the center (and make the viewport stay there) this.setCenter(this.view.halfSize.clone()); this.setFocus(this.view.halfSize.clone()); diff --git a/src/Wolfie2D/Utils/Color.ts b/src/Wolfie2D/Utils/Color.ts index 8f936b0..2bbbce9 100644 --- a/src/Wolfie2D/Utils/Color.ts +++ b/src/Wolfie2D/Utils/Color.ts @@ -69,10 +69,10 @@ export default class Color { } /** - * Purple color + * Magenta color * @returns rgb(255, 0, 255) */ - static get PURPLE(): Color { + static get MAGENTA(): Color { return new Color(255, 0, 255, 1); } @@ -127,7 +127,7 @@ export default class Color { * @returns A new lighter 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 */ 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)); } /** diff --git a/src/Wolfie2D/Utils/RandUtils.ts b/src/Wolfie2D/Utils/RandUtils.ts index 42c104a..a1cac17 100644 --- a/src/Wolfie2D/Utils/RandUtils.ts +++ b/src/Wolfie2D/Utils/RandUtils.ts @@ -1,6 +1,7 @@ import MathUtils from "./MathUtils"; import Color from "./Color"; import Perlin from "./Rand/Perlin"; +import Vec2 from "../DataTypes/Vec2"; class Noise { p: Perlin = new Perlin(); @@ -21,6 +22,16 @@ export default class RandUtils { static randInt(min: number, max: number): number { 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 @@ -43,6 +54,10 @@ export default class RandUtils { 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 */ static noise: Noise = new Noise(); diff --git a/src/default_scene.ts b/src/default_scene.ts index 8166edb..9281de1 100644 --- a/src/default_scene.ts +++ b/src/default_scene.ts @@ -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 second argument is the path to the actual image. // 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, diff --git a/src/Platformer.ts b/src/demos/Platformer.ts similarity index 90% rename from src/Platformer.ts rename to src/demos/Platformer.ts index 0f3aabe..9eb409a 100644 --- a/src/Platformer.ts +++ b/src/demos/Platformer.ts @@ -1,7 +1,7 @@ import PlayerController from "./PlatformerPlayerController"; -import Vec2 from "./Wolfie2D/DataTypes/Vec2"; -import AnimatedSprite from "./Wolfie2D/Nodes/Sprites/AnimatedSprite"; -import Scene from "./Wolfie2D/Scene/Scene"; +import Vec2 from "../Wolfie2D/DataTypes/Vec2"; +import AnimatedSprite from "../Wolfie2D/Nodes/Sprites/AnimatedSprite"; +import Scene from "../Wolfie2D/Scene/Scene"; export default class Platformer extends Scene { private player: AnimatedSprite; diff --git a/src/PlatformerPlayerController.ts b/src/demos/PlatformerPlayerController.ts similarity index 81% rename from src/PlatformerPlayerController.ts rename to src/demos/PlatformerPlayerController.ts index 98e4648..4062488 100644 --- a/src/PlatformerPlayerController.ts +++ b/src/demos/PlatformerPlayerController.ts @@ -1,9 +1,9 @@ -import AI from "./Wolfie2D/DataTypes/Interfaces/AI"; -import Emitter from "./Wolfie2D/Events/Emitter"; -import GameEvent from "./Wolfie2D/Events/GameEvent"; -import { GameEventType } from "./Wolfie2D/Events/GameEventType"; -import Input from "./Wolfie2D/Input/Input"; -import AnimatedSprite from "./Wolfie2D/Nodes/Sprites/AnimatedSprite"; +import AI from "../Wolfie2D/DataTypes/Interfaces/AI"; +import Emitter from "../Wolfie2D/Events/Emitter"; +import GameEvent from "../Wolfie2D/Events/GameEvent"; +import { GameEventType } from "../Wolfie2D/Events/GameEventType"; +import Input from "../Wolfie2D/Input/Input"; +import AnimatedSprite from "../Wolfie2D/Nodes/Sprites/AnimatedSprite"; export default class PlayerController implements AI { protected owner: AnimatedSprite; @@ -16,6 +16,8 @@ export default class PlayerController implements AI { this.emitter = new Emitter(); } + activate(options: Record): void {} + handleEvent(event: GameEvent): void { // Do nothing for now } diff --git a/src/hw1/GradientCircleShaderType.ts b/src/hw1/GradientCircleShaderType.ts deleted file mode 100644 index adb6db0..0000000 --- a/src/hw1/GradientCircleShaderType.ts +++ /dev/null @@ -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): 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); - } -} \ No newline at end of file diff --git a/src/hw1/HW1_Enums.ts b/src/hw1/HW1_Enums.ts deleted file mode 100644 index d380546..0000000 --- a/src/hw1/HW1_Enums.ts +++ /dev/null @@ -1,8 +0,0 @@ -export enum Homework1Event { - PLAYER_DAMAGE = "PLAYER_DAMAGE", - SPAWN_FLEET = "SPAWN_FLEET" -} - -export enum Homework1Shaders { - GRADIENT_CIRCLE = "GRADIENT_CIRCLE" -} \ No newline at end of file diff --git a/src/hw1/HW1_Scene.ts b/src/hw1/HW1_Scene.ts deleted file mode 100644 index abba8e3..0000000 --- a/src/hw1/HW1_Scene.ts +++ /dev/null @@ -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 = new Array(this.MAX_FLEET_SIZE); - - // Create an object pool for our fleet - private MAX_NUM_ASTEROIDS = 6; - private asteroids: Array = new Array(this.MAX_NUM_ASTEROIDS); - - // Create an object pool for our fleet - private MAX_NUM_MINERALS = 20; - private minerals: Array = 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); - } - } - } -} \ No newline at end of file diff --git a/src/hw1/SpaceshipPlayerController.ts b/src/hw1/SpaceshipPlayerController.ts deleted file mode 100644 index e4a3ec0..0000000 --- a/src/hw1/SpaceshipPlayerController.ts +++ /dev/null @@ -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): 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"); - } - } - } -} \ No newline at end of file diff --git a/src/index.html b/src/index.html index 4276b15..1d0490d 100644 --- a/src/index.html +++ b/src/index.html @@ -22,6 +22,15 @@ left: 0px; } + #text-canvas { + width: 100%; + height: 100%; + position: absolute; + top: 0px; + left: 0px; + pointer-events: none; + } + #debug-canvas { width: 100%; height: 100%; @@ -38,6 +47,8 @@
+ +
diff --git a/src/main.ts b/src/main.ts index 6acbaa2..f28d2ef 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,36 +1,24 @@ import Game from "./Wolfie2D/Loop/Game"; -import Homework1_Scene from "./hw1/HW1_Scene"; -import Registry from "./Wolfie2D/Registry/Registry"; -import { Homework1Shaders } from "./hw1/HW1_Enums"; -import GradientCircleShaderType from "./hw1/GradientCircleShaderType"; +import default_scene from "./default_scene"; // The main function is your entrypoint into Wolfie2D. Specify your first scene and any options here. (function main(){ - // Set up options - let options = { - 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 - } + // Run any tests + runTests(); - // We have a custom shader, so lets add it to the registry and preload it - Registry.shaders.registerAndPreloadItem( - Homework1Shaders.GRADIENT_CIRCLE, // The key of the shader program - GradientCircleShaderType, // The constructor of the shader program - "hw1_assets/shaders/gradient_circle.vshader", // The path to the vertex shader - "hw1_assets/shaders/gradient_circle.fshader"); // the path to the fragment shader + // Set up options for our game + let options = { + canvasSize: {x: 1200, y: 800}, // The size of the game + clearColor: {r: 0.1, g: 0.1, b: 0.1}, // The color the game clears to + useWebGL: true, // Tell the game we want to use webgl + showDebug: false // Whether to show debug messages. You can change this to true if you want + } // Create a game with the options specified const game = new Game(options); // Start our game - game.start(); - game.getSceneManager().addScene(Homework1_Scene, {}); -})(); \ No newline at end of file + game.start(default_scene, {}); +})(); + +function runTests(){}; \ No newline at end of file