added webGL support
This commit is contained in:
parent
eeaf73bab4
commit
4214ef7fd4
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -7,6 +7,12 @@ dist/*
|
|||
# Include the demo_assets folder
|
||||
!dist/demo_assets/
|
||||
|
||||
# Include the built-in asset folder
|
||||
!dist/builtin/
|
||||
|
||||
# Include the hw1 assets
|
||||
!dist/hw1_assets/
|
||||
|
||||
### IF YOU ARE MAKING A PROJECT, YOU MAY WANT TO UNCOMMENT THIS LINE ###
|
||||
# !dist/assets/
|
||||
|
||||
|
|
7
dist/builtin/shaders/point.fshader
vendored
Normal file
7
dist/builtin/shaders/point.fshader
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
precision mediump float;
|
||||
|
||||
uniform vec4 u_Color;
|
||||
|
||||
void main(){
|
||||
gl_FragColor = u_Color;
|
||||
}
|
8
dist/builtin/shaders/point.vshader
vendored
Normal file
8
dist/builtin/shaders/point.vshader
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
attribute vec4 a_Position;
|
||||
|
||||
uniform float u_PointSize;
|
||||
|
||||
void main(){
|
||||
gl_Position = a_Position;
|
||||
gl_PointSize = u_PointSize;
|
||||
}
|
7
dist/builtin/shaders/rect.fshader
vendored
Normal file
7
dist/builtin/shaders/rect.fshader
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
precision mediump float;
|
||||
|
||||
uniform vec4 u_Color;
|
||||
|
||||
void main(){
|
||||
gl_FragColor = u_Color;
|
||||
}
|
7
dist/builtin/shaders/rect.vshader
vendored
Normal file
7
dist/builtin/shaders/rect.vshader
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
attribute vec4 a_Position;
|
||||
|
||||
uniform mat4 u_Transform;
|
||||
|
||||
void main(){
|
||||
gl_Position = u_Transform * a_Position;
|
||||
}
|
9
dist/builtin/shaders/sprite.fshader
vendored
Normal file
9
dist/builtin/shaders/sprite.fshader
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
precision mediump float;
|
||||
|
||||
uniform sampler2D u_Sampler;
|
||||
|
||||
varying vec2 v_TexCoord;
|
||||
|
||||
void main(){
|
||||
gl_FragColor = texture2D(u_Sampler, v_TexCoord);
|
||||
}
|
13
dist/builtin/shaders/sprite.vshader
vendored
Normal file
13
dist/builtin/shaders/sprite.vshader
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
attribute vec4 a_Position;
|
||||
attribute vec2 a_TexCoord;
|
||||
|
||||
uniform mat4 u_Transform;
|
||||
uniform vec2 u_texShift;
|
||||
uniform vec2 u_texScale;
|
||||
|
||||
varying vec2 v_TexCoord;
|
||||
|
||||
void main(){
|
||||
gl_Position = u_Transform * a_Position;
|
||||
v_TexCoord = a_TexCoord*u_texScale + u_texShift;
|
||||
}
|
24
dist/hw1_assets/shaders/gradient_circle.fshader
vendored
Normal file
24
dist/hw1_assets/shaders/gradient_circle.fshader
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
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);
|
||||
}
|
11
dist/hw1_assets/shaders/gradient_circle.vshader
vendored
Normal file
11
dist/hw1_assets/shaders/gradient_circle.vshader
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
attribute vec4 a_Position;
|
||||
|
||||
uniform mat4 u_Transform;
|
||||
|
||||
varying vec4 v_Position;
|
||||
|
||||
void main(){
|
||||
gl_Position = u_Transform * a_Position;
|
||||
|
||||
v_Position = a_Position;
|
||||
}
|
145
dist/hw1_assets/spritesheets/player_spaceship.json
vendored
Normal file
145
dist/hw1_assets/spritesheets/player_spaceship.json
vendored
Normal file
|
@ -0,0 +1,145 @@
|
|||
{
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
BIN
dist/hw1_assets/spritesheets/player_spaceship.png
vendored
Normal file
BIN
dist/hw1_assets/spritesheets/player_spaceship.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
48
src/WebGLScene.ts
Normal file
48
src/WebGLScene.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
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;
|
||||
}
|
||||
}
|
167
src/Wolfie2D/DataTypes/Mat4x4.ts
Normal file
167
src/Wolfie2D/DataTypes/Mat4x4.ts
Normal file
|
@ -0,0 +1,167 @@
|
|||
import Vec2 from "./Vec2";
|
||||
|
||||
/** A 4x4 matrix0 */
|
||||
export default class Mat4x4 {
|
||||
private mat: Float32Array;
|
||||
|
||||
constructor(){
|
||||
this.mat = new Float32Array([
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0
|
||||
]);
|
||||
}
|
||||
|
||||
// Static members
|
||||
static get IDENTITY(): Mat4x4 {
|
||||
return new Mat4x4().identity();
|
||||
}
|
||||
|
||||
static get ZERO(): Mat4x4 {
|
||||
return new Mat4x4().zero();
|
||||
}
|
||||
|
||||
// Accessors
|
||||
set _00(x: number) {
|
||||
this.mat[0] = x;
|
||||
}
|
||||
|
||||
set(col: number, row: number, value: number): Mat4x4 {
|
||||
if(col < 0 || col > 3 || row < 0 || row > 3){
|
||||
throw `Error - index (${col}, ${row}) is out of bounds for Mat4x4`
|
||||
}
|
||||
this.mat[row*4 + col] = value;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
get(col: number, row: number): number {
|
||||
return this.mat[row*4 + col];
|
||||
}
|
||||
|
||||
setAll(...items: Array<number>): Mat4x4 {
|
||||
this.mat.set(items);
|
||||
return this;
|
||||
}
|
||||
|
||||
identity(): Mat4x4 {
|
||||
return this.setAll(
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
)
|
||||
}
|
||||
|
||||
zero(): Mat4x4 {
|
||||
return this.setAll(
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes this Mat4x4 a rotation matrix of the specified number of radians ccw
|
||||
* @param zRadians The number of radians to rotate
|
||||
* @returns this Mat4x4
|
||||
*/
|
||||
rotate(zRadians: number): Mat4x4 {
|
||||
return this.setAll(
|
||||
Math.cos(zRadians), -Math.sin(zRadians), 0, 0,
|
||||
Math.sin(zRadians), Math.cos(zRadians), 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns this Mat4x4 into a translation matrix of the specified translation
|
||||
* @param translation The translation in x and y
|
||||
* @returns this Mat4x4
|
||||
*/
|
||||
translate(translation: Vec2 | Float32Array): Mat4x4 {
|
||||
// If translation is a vec, get its array
|
||||
if(translation instanceof Vec2){
|
||||
translation = translation.toArray();
|
||||
}
|
||||
|
||||
return this.setAll(
|
||||
1, 0, 0, translation[0],
|
||||
0, 1, 0, translation[1],
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
);
|
||||
}
|
||||
|
||||
scale(scale: Vec2 | Float32Array | number): Mat4x4 {
|
||||
// Make sure scale is a float32Array
|
||||
if(scale instanceof Vec2){
|
||||
scale = scale.toArray();
|
||||
} else if(!(scale instanceof Float32Array)){
|
||||
scale = new Float32Array([scale, scale]);
|
||||
}
|
||||
|
||||
return this.setAll(
|
||||
scale[0], 0, 0, 0,
|
||||
0, scale[1], 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Mat4x4 that represents the right side multiplication THIS x OTHER
|
||||
* @param other The other Mat4x4 to multiply by
|
||||
* @returns a new Mat4x4 containing the product of these two Mat4x4s
|
||||
*/
|
||||
mult(other: Mat4x4, out?: Mat4x4): Mat4x4 {
|
||||
let temp = new Float32Array(16);
|
||||
|
||||
for(let i = 0; i < 4; i++){
|
||||
for(let j = 0; j < 4; j++){
|
||||
let value = 0;
|
||||
for(let k = 0; k < 4; k++){
|
||||
value += this.get(k, i) * other.get(j, k);
|
||||
}
|
||||
temp[j*4 + i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if(out !== undefined){
|
||||
return out.setAll(...temp);
|
||||
} else {
|
||||
return new Mat4x4().setAll(...temp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies all given matricies in order. e.g. MULT(A, B, C) -> A*B*C
|
||||
* @param mats A list of Mat4x4s to multiply in order
|
||||
* @returns A new Mat4x4 holding the result of the operation
|
||||
*/
|
||||
static MULT(...mats: Array<Mat4x4>): Mat4x4 {
|
||||
// Create a new array
|
||||
let temp = Mat4x4.IDENTITY;
|
||||
|
||||
// Multiply by every array in order, in place
|
||||
for(let i = 0; i < mats.length; i++){
|
||||
temp.mult(mats[i], temp);
|
||||
}
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
toArray(): Float32Array {
|
||||
return this.mat;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `|${this.mat[0].toFixed(2)}, ${this.mat[1].toFixed(2)}, ${this.mat[2].toFixed(2)}, ${this.mat[3].toFixed(2)}|\n` +
|
||||
`|${this.mat[4].toFixed(2)}, ${this.mat[5].toFixed(2)}, ${this.mat[6].toFixed(2)}, ${this.mat[7].toFixed(2)}|\n` +
|
||||
`|${this.mat[8].toFixed(2)}, ${this.mat[9].toFixed(2)}, ${this.mat[10].toFixed(2)}, ${this.mat[11].toFixed(2)}|\n` +
|
||||
`|${this.mat[12].toFixed(2)}, ${this.mat[13].toFixed(2)}, ${this.mat[14].toFixed(2)}, ${this.mat[15].toFixed(2)}|`;
|
||||
}
|
||||
}
|
5
src/Wolfie2D/DataTypes/Rendering/WebGLGameTexture.ts
Normal file
5
src/Wolfie2D/DataTypes/Rendering/WebGLGameTexture.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export default class WebGLGameTexture {
|
||||
webGLTextureId: number;
|
||||
webGLTexture: WebGLTexture;
|
||||
imageKey: string;
|
||||
}
|
29
src/Wolfie2D/DataTypes/Rendering/WebGLProgramType.ts
Normal file
29
src/Wolfie2D/DataTypes/Rendering/WebGLProgramType.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/** A container for info about a webGL shader program */
|
||||
export default class WebGLProgramType {
|
||||
/** A webGL program */
|
||||
program: WebGLProgram;
|
||||
|
||||
/** A vertex shader */
|
||||
vertexShader: WebGLShader;
|
||||
|
||||
/** A fragment shader */
|
||||
fragmentShader: WebGLShader;
|
||||
|
||||
/**
|
||||
* Deletes this shader program
|
||||
*/
|
||||
delete(gl: WebGLRenderingContext): void {
|
||||
// Clean up all aspects of this program
|
||||
if(this.program){
|
||||
gl.deleteProgram(this.program);
|
||||
}
|
||||
|
||||
if(this.vertexShader){
|
||||
gl.deleteShader(this.vertexShader);
|
||||
}
|
||||
|
||||
if(this.fragmentShader){
|
||||
gl.deleteShader(this.fragmentShader);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -383,6 +383,10 @@ export default class Vec2 {
|
|||
this.onChange = f;
|
||||
}
|
||||
|
||||
toArray(): Float32Array {
|
||||
return this.vec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs linear interpolation between two vectors
|
||||
* @param a The first vector
|
||||
|
|
|
@ -16,6 +16,8 @@ import GameLoop from "./GameLoop";
|
|||
import FixedUpdateGameLoop from "./FixedUpdateGameLoop";
|
||||
import EnvironmentInitializer from "./EnvironmentInitializer";
|
||||
import Vec2 from "../DataTypes/Vec2";
|
||||
import Registry from "../Registry/Registry";
|
||||
import WebGLRenderer from "../Rendering/WebGLRenderer";
|
||||
|
||||
/**
|
||||
* The main loop of the game engine.
|
||||
|
@ -36,7 +38,7 @@ export default class Game {
|
|||
readonly WIDTH: number;
|
||||
readonly HEIGHT: number;
|
||||
private viewport: Viewport;
|
||||
private ctx: CanvasRenderingContext2D;
|
||||
private ctx: CanvasRenderingContext2D | WebGLRenderingContext;
|
||||
private clearColor: Color;
|
||||
|
||||
// All of the necessary subsystems that need to run here
|
||||
|
@ -73,8 +75,12 @@ export default class Game {
|
|||
this.WIDTH = this.gameOptions.canvasSize.x;
|
||||
this.HEIGHT = this.gameOptions.canvasSize.y;
|
||||
|
||||
// For now, just hard code a canvas renderer. We can do this with options later
|
||||
this.renderingManager = new CanvasRenderer();
|
||||
// This step MUST happen before the resource manager does anything
|
||||
if(this.gameOptions.useWebGL){
|
||||
this.renderingManager = new WebGLRenderer();
|
||||
} else {
|
||||
this.renderingManager = new CanvasRenderer();
|
||||
}
|
||||
this.initializeGameWindow();
|
||||
this.ctx = this.renderingManager.initializeCanvas(this.GAME_CANVAS, this.WIDTH, this.HEIGHT);
|
||||
this.clearColor = new Color(this.gameOptions.clearColor.r, this.gameOptions.clearColor.g, this.gameOptions.clearColor.b);
|
||||
|
@ -131,8 +137,14 @@ export default class Game {
|
|||
// Set the render function of the loop
|
||||
this.loop.doRender = () => this.render();
|
||||
|
||||
// Start the loop
|
||||
this.loop.start();
|
||||
// Preload registry items
|
||||
Registry.preload();
|
||||
|
||||
// Load the items with the resource manager
|
||||
this.resourceManager.loadResourcesFromQueue(() => {
|
||||
// When we're dont loading, start the loop
|
||||
this.loop.start();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -164,12 +176,17 @@ export default class Game {
|
|||
*/
|
||||
render(): void {
|
||||
// Clear the canvases
|
||||
this.ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT);
|
||||
Debug.clearCanvas();
|
||||
|
||||
// Game Canvas
|
||||
this.ctx.fillStyle = this.clearColor.toString();
|
||||
this.ctx.fillRect(0, 0, this.WIDTH, this.HEIGHT);
|
||||
if(this.gameOptions.useWebGL){
|
||||
(<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();
|
||||
|
||||
// Debug render
|
||||
|
|
|
@ -20,6 +20,9 @@ export default class GameOptions {
|
|||
/* Whether or not the stats rendering should occur */
|
||||
showStats: boolean;
|
||||
|
||||
/* Whether or not to use webGL */
|
||||
useWebGL: boolean;
|
||||
|
||||
/**
|
||||
* Parses the data in the raw options object
|
||||
* @param options The game options as a Record
|
||||
|
@ -34,6 +37,7 @@ export default class GameOptions {
|
|||
gOpt.inputs = options.inputs ? options.inputs : [];
|
||||
gOpt.showDebug = !!options.showDebug;
|
||||
gOpt.showStats = !!options.showStats;
|
||||
gOpt.useWebGL = !!options.useWebGL;
|
||||
|
||||
return gOpt;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ export default abstract class CanvasNode extends GameNode implements Region {
|
|||
private _size: Vec2;
|
||||
private _scale: Vec2;
|
||||
private _boundary: AABB;
|
||||
private _hasCustomShader: boolean;
|
||||
private _customShaderKey: string;
|
||||
|
||||
/** A flag for whether or not the CanvasNode is visible */
|
||||
visible: boolean = true;
|
||||
|
@ -24,6 +26,8 @@ export default abstract class CanvasNode extends GameNode implements Region {
|
|||
this._scale.setOnChange(() => this.scaleChanged());
|
||||
this._boundary = new AABB();
|
||||
this.updateBoundary();
|
||||
|
||||
this._hasCustomShader = false;
|
||||
}
|
||||
|
||||
get size(): Vec2 {
|
||||
|
@ -56,6 +60,14 @@ export default abstract class CanvasNode extends GameNode implements Region {
|
|||
this.scale.y = value;
|
||||
}
|
||||
|
||||
get hasCustomShader(): boolean {
|
||||
return this._hasCustomShader;
|
||||
}
|
||||
|
||||
get customShaderKey(): string {
|
||||
return this._customShaderKey;
|
||||
}
|
||||
|
||||
// @override
|
||||
protected positionChanged(): void {
|
||||
super.positionChanged();
|
||||
|
@ -89,6 +101,15 @@ export default abstract class CanvasNode extends GameNode implements Region {
|
|||
return this.boundary.halfSize.clone().scaled(zoom, zoom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a custom shader to this CanvasNode
|
||||
* @param key The registry key of the ShaderType
|
||||
*/
|
||||
useCustomShader(key: string): void {
|
||||
this._hasCustomShader = true;
|
||||
this._customShaderKey = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the point (x, y) is inside of this canvas object
|
||||
* @param x The x position of the point
|
||||
|
|
|
@ -176,6 +176,7 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
|
|||
// Set the collision shape if provided, or simply use the the region if there is one.
|
||||
if(collisionShape){
|
||||
this.collisionShape = collisionShape;
|
||||
this.collisionShape.center = this.position;
|
||||
} else if (isRegion(this)) {
|
||||
// If the gamenode has a region and no other is specified, use that
|
||||
this.collisionShape = (<any>this).boundary.clone();
|
||||
|
|
|
@ -8,9 +8,17 @@ export default class AnimatedSprite extends Sprite {
|
|||
/** The number of columns in this sprite sheet */
|
||||
protected numCols: number;
|
||||
|
||||
get cols(): number {
|
||||
return this.numCols;
|
||||
}
|
||||
|
||||
/** The number of rows in this sprite sheet */
|
||||
protected numRows: number;
|
||||
|
||||
get rows(): number {
|
||||
return this.numRows;
|
||||
}
|
||||
|
||||
/** The animationManager for this sprite */
|
||||
animation: AnimationManager;
|
||||
|
||||
|
|
98
src/Wolfie2D/Registry/Registries/ShaderRegistry.ts
Normal file
98
src/Wolfie2D/Registry/Registries/ShaderRegistry.ts
Normal file
|
@ -0,0 +1,98 @@
|
|||
import Map from "../../DataTypes/Map";
|
||||
import ShaderType from "../../Rendering/WebGLRendering/ShaderType";
|
||||
import PointShaderType from "../../Rendering/WebGLRendering/ShaderTypes/PointShaderType";
|
||||
import RectShaderType from "../../Rendering/WebGLRendering/ShaderTypes/RectShaderType";
|
||||
import SpriteShaderType from "../../Rendering/WebGLRendering/ShaderTypes/SpriteShaderType";
|
||||
import ResourceManager from "../../ResourceManager/ResourceManager";
|
||||
|
||||
/**
|
||||
* A registry that handles shaders
|
||||
*/
|
||||
export default class ShaderRegistry extends Map<ShaderType> {
|
||||
|
||||
// Shader names
|
||||
public static POINT_SHADER = "point";
|
||||
public static RECT_SHADER = "rect";
|
||||
public static SPRITE_SHADER = "sprite";
|
||||
|
||||
private registryItems: Array<ShaderRegistryItem> = new Array();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
// Queue a load for the point shader
|
||||
this.registerAndPreloadItem(ShaderRegistry.POINT_SHADER, PointShaderType, "builtin/shaders/point.vshader", "builtin/shaders/point.fshader");
|
||||
|
||||
// Queue a load for the rect shader
|
||||
this.registerAndPreloadItem(ShaderRegistry.RECT_SHADER, RectShaderType, "builtin/shaders/rect.vshader", "builtin/shaders/rect.fshader");
|
||||
|
||||
// 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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a shader in the registry and loads it before the game begins
|
||||
* @param key The key you wish to assign to the shader
|
||||
* @param constr The constructor of the ShaderType
|
||||
* @param vshaderLocation The location of the vertex shader
|
||||
* @param fshaderLocation the location of the fragment shader
|
||||
*/
|
||||
public registerAndPreloadItem(key: string, constr: new (programKey: string) => ShaderType, vshaderLocation: string, fshaderLocation: string): void {
|
||||
let shaderPreload = new ShaderPreload();
|
||||
shaderPreload.vshaderLocation = vshaderLocation;
|
||||
shaderPreload.fshaderLocation = fshaderLocation;
|
||||
|
||||
let registryItem = new ShaderRegistryItem();
|
||||
registryItem.key = key;
|
||||
registryItem.constr = constr;
|
||||
registryItem.preload = shaderPreload;
|
||||
|
||||
this.registryItems.push(registryItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a shader in the registry. NOTE: If you use this, you MUST load the shader before use.
|
||||
* If you wish to preload the shader, use registerAndPreloadItem()
|
||||
* @param key The key you wish to assign to the shader
|
||||
* @param constr The constructor of the ShaderType
|
||||
*/
|
||||
public registerItem(key: string, constr: new (programKey: string) => ShaderType): void {
|
||||
let registryItem = new ShaderRegistryItem();
|
||||
registryItem.key = key;
|
||||
registryItem.constr = constr;
|
||||
|
||||
this.registryItems.push(registryItem);
|
||||
}
|
||||
}
|
||||
|
||||
class ShaderRegistryItem {
|
||||
key: string;
|
||||
constr: new (programKey: string) => ShaderType;
|
||||
preload: ShaderPreload;
|
||||
}
|
||||
|
||||
class ShaderPreload {
|
||||
vshaderLocation: string;
|
||||
fshaderLocation: string;
|
||||
}
|
16
src/Wolfie2D/Registry/Registry.ts
Normal file
16
src/Wolfie2D/Registry/Registry.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import ShaderRegistry from "./Registries/ShaderRegistry";
|
||||
|
||||
/**
|
||||
* The Registry is the system's way of converting classes and types into string
|
||||
* representations for use elsewhere in the application.
|
||||
* It allows classes to be accessed without explicitly using constructors in code,
|
||||
* and for resources to be loaded at Game creation time.
|
||||
*/
|
||||
export default class Registry {
|
||||
|
||||
public static shaders = new ShaderRegistry();
|
||||
|
||||
static preload(){
|
||||
this.shaders.preload();
|
||||
}
|
||||
}
|
|
@ -85,6 +85,15 @@ export default class AnimationManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the specified animation is currently playing
|
||||
* @param key The key of the animation to check
|
||||
* @returns true if the specified animation is playing, false otherwise
|
||||
*/
|
||||
isPlaying(key: string): boolean {
|
||||
return this.currentAnimation === key && this.animationState === AnimationState.PLAYING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current animation index and advances the animation frame
|
||||
* @returns The index of the animation frame
|
||||
|
@ -147,7 +156,7 @@ export default class AnimationManager {
|
|||
* @param loop Whether or not to loop the animation. False by default
|
||||
* @param onEnd The name of an event to send when this animation naturally stops playing. This only matters if loop is false.
|
||||
*/
|
||||
playIfNotAlready(animation: string, loop: boolean = false, onEnd?: string): void {
|
||||
playIfNotAlready(animation: string, loop?: boolean, onEnd?: string): void {
|
||||
if(this.currentAnimation !== animation){
|
||||
this.play(animation, loop, onEnd);
|
||||
}
|
||||
|
@ -159,18 +168,27 @@ export default class AnimationManager {
|
|||
* @param loop Whether or not to loop the animation. False by default
|
||||
* @param onEnd The name of an event to send when this animation naturally stops playing. This only matters if loop is false.
|
||||
*/
|
||||
play(animation: string, loop: boolean = false, onEnd?: string): void {
|
||||
play(animation: string, loop?: boolean, onEnd?: string): void {
|
||||
this.currentAnimation = animation;
|
||||
this.currentFrame = 0;
|
||||
this.frameProgress = 0;
|
||||
this.loop = loop;
|
||||
this.animationState = AnimationState.PLAYING;
|
||||
|
||||
// If loop arg was provided, use that
|
||||
if(loop !== undefined){
|
||||
this.loop = loop;
|
||||
} else {
|
||||
// Otherwise, use what the json file specified
|
||||
this.loop = this.animations.get(animation).repeat;
|
||||
}
|
||||
|
||||
if(onEnd !== undefined){
|
||||
this.onEndEvent = onEnd;
|
||||
} else {
|
||||
this.onEndEvent = null;
|
||||
}
|
||||
|
||||
// Reset pending animation
|
||||
this.pendingAnimation = null;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ export enum AnimationState {
|
|||
export class AnimationData {
|
||||
name: string;
|
||||
frames: Array<{index: number, duration: number}>;
|
||||
repeat: boolean = false;
|
||||
}
|
||||
|
||||
export class TweenData {
|
||||
|
|
|
@ -131,7 +131,7 @@ export default class CanvasRenderer extends RenderingManager {
|
|||
}
|
||||
|
||||
this.ctx.setTransform(xScale, 0, 0, yScale, (node.position.x - this.origin.x)*this.zoom, (node.position.y - this.origin.y)*this.zoom);
|
||||
this.ctx.rotate(node.rotation);
|
||||
this.ctx.rotate(-node.rotation);
|
||||
let globalAlpha = this.ctx.globalAlpha;
|
||||
this.ctx.globalAlpha = node.alpha;
|
||||
|
||||
|
|
136
src/Wolfie2D/Rendering/WebGLRenderer.ts
Normal file
136
src/Wolfie2D/Rendering/WebGLRenderer.ts
Normal file
|
@ -0,0 +1,136 @@
|
|||
import Graph from "../DataTypes/Graphs/Graph";
|
||||
import Map from "../DataTypes/Map";
|
||||
import Vec2 from "../DataTypes/Vec2";
|
||||
import CanvasNode from "../Nodes/CanvasNode";
|
||||
import Graphic from "../Nodes/Graphic";
|
||||
import { GraphicType } from "../Nodes/Graphics/GraphicTypes";
|
||||
import Point from "../Nodes/Graphics/Point";
|
||||
import Rect from "../Nodes/Graphics/Rect";
|
||||
import AnimatedSprite from "../Nodes/Sprites/AnimatedSprite";
|
||||
import Sprite from "../Nodes/Sprites/Sprite";
|
||||
import Tilemap from "../Nodes/Tilemap";
|
||||
import UIElement from "../Nodes/UIElement";
|
||||
import ShaderRegistry from "../Registry/Registries/ShaderRegistry";
|
||||
import Registry from "../Registry/Registry";
|
||||
import ResourceManager from "../ResourceManager/ResourceManager";
|
||||
import UILayer from "../Scene/Layers/UILayer";
|
||||
import RenderingUtils from "../Utils/RenderingUtils";
|
||||
import RenderingManager from "./RenderingManager";
|
||||
import ShaderType from "./WebGLRendering/ShaderType";
|
||||
|
||||
export default class WebGLRenderer extends RenderingManager {
|
||||
|
||||
protected origin: Vec2;
|
||||
protected zoom: number;
|
||||
protected worldSize: Vec2;
|
||||
|
||||
protected gl: WebGLRenderingContext;
|
||||
|
||||
initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): WebGLRenderingContext {
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
this.worldSize = Vec2.ZERO;
|
||||
this.worldSize.x = width;
|
||||
this.worldSize.y = height;
|
||||
|
||||
// Get the WebGL context
|
||||
this.gl = canvas.getContext("webgl");
|
||||
|
||||
this.gl.viewport(0, 0, canvas.width, canvas.height);
|
||||
|
||||
this.gl.disable(this.gl.DEPTH_TEST);
|
||||
this.gl.enable(this.gl.BLEND);
|
||||
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
|
||||
this.gl.enable(this.gl.CULL_FACE);
|
||||
|
||||
// Tell the resource manager we're using WebGL
|
||||
ResourceManager.getInstance().useWebGL(true, this.gl);
|
||||
|
||||
return this.gl;
|
||||
}
|
||||
|
||||
render(visibleSet: CanvasNode[], tilemaps: Tilemap[], uiLayers: Map<UILayer>): void {
|
||||
for(let node of visibleSet){
|
||||
this.renderNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
protected renderNode(node: CanvasNode): void {
|
||||
// Calculate the origin of the viewport according to this sprite
|
||||
this.origin = this.scene.getViewTranslation(node);
|
||||
|
||||
// Get the zoom level of the scene
|
||||
this.zoom = this.scene.getViewScale();
|
||||
|
||||
if(node.hasCustomShader){
|
||||
// If the node has a custom shader, render using that
|
||||
this.renderCustom(node);
|
||||
} else if(node instanceof Graphic){
|
||||
this.renderGraphic(node);
|
||||
} else if(node instanceof Sprite){
|
||||
if(node instanceof AnimatedSprite){
|
||||
this.renderAnimatedSprite(node);
|
||||
} else {
|
||||
this.renderSprite(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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
shader.render(this.gl, options);
|
||||
}
|
||||
}
|
||||
|
||||
protected renderTilemap(tilemap: Tilemap): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
protected renderUIElement(uiElement: UIElement): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
shader.render(this.gl, options);
|
||||
}
|
||||
|
||||
}
|
44
src/Wolfie2D/Rendering/WebGLRendering/ShaderType.ts
Normal file
44
src/Wolfie2D/Rendering/WebGLRendering/ShaderType.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import Map from "../../DataTypes/Map";
|
||||
import CanvasNode from "../../Nodes/CanvasNode";
|
||||
import ResourceManager from "../../ResourceManager/ResourceManager";
|
||||
|
||||
/**
|
||||
* A wrapper class for WebGL shaders.
|
||||
* This class is a singleton, and there is only one for each shader type.
|
||||
* All objects that use this shader type will refer to and modify this same type.
|
||||
*/
|
||||
export default abstract class ShaderType {
|
||||
/** The name of this shader */
|
||||
protected name: string;
|
||||
|
||||
/** The key to the WebGLProgram in the ResourceManager */
|
||||
protected programKey: string;
|
||||
|
||||
/** A reference to the resource manager */
|
||||
protected resourceManager: ResourceManager;
|
||||
|
||||
constructor(programKey: string){
|
||||
this.programKey = programKey;
|
||||
this.resourceManager = ResourceManager.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes any buffer objects associated with this shader type.
|
||||
* @param gl The WebGL rendering context
|
||||
*/
|
||||
abstract initBufferObject(): void;
|
||||
|
||||
/**
|
||||
* Loads any uniforms
|
||||
* @param gl The WebGL rendering context
|
||||
* @param options Information about the object we're currently rendering
|
||||
*/
|
||||
abstract render(gl: WebGLRenderingContext, options: Record<string, any>): void;
|
||||
|
||||
/**
|
||||
* Extracts the options from the CanvasNode and gives them to the render function
|
||||
* @param node The node to get options from
|
||||
* @returns An object containing the options that should be passed to the render function
|
||||
*/
|
||||
getOptions(node: CanvasNode): Record<string, any> {return {};}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
import Debug from "../../../Debug/Debug";
|
||||
import Point from "../../../Nodes/Graphics/Point";
|
||||
import ResourceManager from "../../../ResourceManager/ResourceManager";
|
||||
import RenderingUtils from "../../../Utils/RenderingUtils";
|
||||
import ShaderType from "../ShaderType";
|
||||
|
||||
export default class PointShaderType extends ShaderType {
|
||||
|
||||
protected bufferObjectKey: string;
|
||||
|
||||
constructor(programKey: string){
|
||||
super(programKey);
|
||||
}
|
||||
|
||||
initBufferObject(): void {
|
||||
this.bufferObjectKey = "point";
|
||||
this.resourceManager.createBuffer(this.bufferObjectKey);
|
||||
}
|
||||
|
||||
render(gl: WebGLRenderingContext, options: Record<string, any>): void {
|
||||
let position = RenderingUtils.toWebGLCoords(options.position, options.origin, options.worldSize);
|
||||
let color = RenderingUtils.toWebGLColor(options.color);
|
||||
|
||||
const program = this.resourceManager.getShaderProgram(this.programKey);
|
||||
const buffer = this.resourceManager.getBuffer(this.bufferObjectKey);
|
||||
|
||||
gl.useProgram(program);
|
||||
|
||||
const vertexData = position;
|
||||
|
||||
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_Color = gl.getUniformLocation(program, "u_Color");
|
||||
gl.uniform4fv(u_Color, color);
|
||||
|
||||
const u_PointSize = gl.getUniformLocation(program, "u_PointSize");
|
||||
gl.uniform1f(u_PointSize, options.pointSize);
|
||||
|
||||
gl.drawArrays(gl.POINTS, 0, 1);
|
||||
}
|
||||
|
||||
getOptions(point: Point): Record<string, any> {
|
||||
let options: Record<string, any> = {
|
||||
position: point.position,
|
||||
color: point.color,
|
||||
pointSize: point.size,
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import Mat4x4 from "../../../DataTypes/Mat4x4";
|
||||
import ShaderType from "../ShaderType";
|
||||
|
||||
/** Represents any WebGL objects that have a quad mesh (i.e. a rectangular game object composed of only two triangles) */
|
||||
export default abstract class QuadShaderType extends ShaderType {
|
||||
/** The key to the buffer object for this shader */
|
||||
protected bufferObjectKey: string;
|
||||
|
||||
/** The scale matric */
|
||||
protected scale: Mat4x4;
|
||||
|
||||
/** The rotation matrix */
|
||||
protected rotation: Mat4x4;
|
||||
|
||||
/** The translation matrix */
|
||||
protected translation: Mat4x4;
|
||||
|
||||
constructor(programKey: string){
|
||||
super(programKey);
|
||||
|
||||
this.scale = Mat4x4.IDENTITY;
|
||||
this.rotation = Mat4x4.IDENTITY;
|
||||
this.translation = Mat4x4.IDENTITY;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
import Mat4x4 from "../../../DataTypes/Mat4x4";
|
||||
import Vec2 from "../../../DataTypes/Vec2";
|
||||
import Rect from "../../../Nodes/Graphics/Rect";
|
||||
import ResourceManager from "../../../ResourceManager/ResourceManager";
|
||||
import QuadShaderType from "./QuadShaderType";
|
||||
|
||||
export default class RectShaderType extends QuadShaderType {
|
||||
|
||||
constructor(programKey: string){
|
||||
super(programKey);
|
||||
this.resourceManager = ResourceManager.getInstance();
|
||||
}
|
||||
|
||||
initBufferObject(): void {
|
||||
this.bufferObjectKey = "rect";
|
||||
this.resourceManager.createBuffer(this.bufferObjectKey);
|
||||
}
|
||||
|
||||
render(gl: WebGLRenderingContext, options: Record<string, any>): void {
|
||||
const color = options.color.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_Color = gl.getUniformLocation(program, "u_Color");
|
||||
gl.uniform4fv(u_Color, color);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
So as it turns out, WebGL has an issue with non-square quads.
|
||||
It doesn't like when you don't have a 1-1 scale, and rotations are entirely messed up if this is not the case.
|
||||
To solve this, I used the scale of the LARGEST dimension of the quad to make a square, then adjusted the vertex coordinates inside of that.
|
||||
A diagram of the solution follows.
|
||||
|
||||
There is a bounding square for the quad with dimensions hxh (in this case, since height is the largest dimension).
|
||||
The offset in the vertical direction is therefore 0.5, as it is normally.
|
||||
However, the offset in the horizontal direction is not so straightforward, but isn't conceptually hard.
|
||||
All we really have to do is a range change from [0, height/2] to [0, 0.5], where our value is t = width/2, and 0 <= t <= height/2.
|
||||
|
||||
So now we have our rect, in a space scaled with respect to the largest dimension.
|
||||
Rotations work as you would expect, even for long rectangles.
|
||||
|
||||
0.5
|
||||
__ __ __ __ __ __ __
|
||||
| |88888888888| |
|
||||
| |88888888888| |
|
||||
| |88888888888| |
|
||||
-0.5|_ _|88888888888|_ _|0.5
|
||||
| |88888888888| |
|
||||
| |88888888888| |
|
||||
| |88888888888| |
|
||||
|___|88888888888|___|
|
||||
-0.5
|
||||
|
||||
The getVertices function below does as described, and converts the range
|
||||
*/
|
||||
/**
|
||||
* 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: Rect): Record<string, any> {
|
||||
let options: Record<string, any> = {
|
||||
position: rect.position,
|
||||
color: rect.color,
|
||||
size: rect.size,
|
||||
rotation: rect.rotation
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
import Mat4x4 from "../../../DataTypes/Mat4x4";
|
||||
import Vec2 from "../../../DataTypes/Vec2";
|
||||
import Debug from "../../../Debug/Debug";
|
||||
import AnimatedSprite from "../../../Nodes/Sprites/AnimatedSprite";
|
||||
import Sprite from "../../../Nodes/Sprites/Sprite";
|
||||
import ResourceManager from "../../../ResourceManager/ResourceManager";
|
||||
import QuadShaderType from "./QuadShaderType";
|
||||
|
||||
/** A shader for sprites and animated sprites */
|
||||
export default class SpriteShaderType extends QuadShaderType {
|
||||
constructor(programKey: string){
|
||||
super(programKey);
|
||||
this.resourceManager = ResourceManager.getInstance();
|
||||
}
|
||||
|
||||
initBufferObject(): void {
|
||||
this.bufferObjectKey = "sprite";
|
||||
this.resourceManager.createBuffer(this.bufferObjectKey);
|
||||
}
|
||||
|
||||
render(gl: WebGLRenderingContext, options: Record<string, any>): void {
|
||||
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;
|
||||
|
||||
// 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, 4 * FSIZE, 0 * FSIZE);
|
||||
gl.enableVertexAttribArray(a_Position);
|
||||
|
||||
const a_TexCoord = gl.getAttribLocation(program, "a_TexCoord");
|
||||
gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, 4 * FSIZE, 2*FSIZE);
|
||||
gl.enableVertexAttribArray(a_TexCoord);
|
||||
|
||||
// Uniforms
|
||||
// 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);
|
||||
|
||||
// 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());
|
||||
|
||||
// Set texture unit 0 to the sampler
|
||||
const u_Sampler = gl.getUniformLocation(program, "u_Sampler");
|
||||
gl.uniform1i(u_Sampler, 0);
|
||||
|
||||
// Pass in texShift
|
||||
const u_texShift = gl.getUniformLocation(program, "u_texShift");
|
||||
gl.uniform2fv(u_texShift, options.texShift);
|
||||
|
||||
// Pass in texScale
|
||||
const u_texScale = gl.getUniformLocation(program, "u_texScale");
|
||||
gl.uniform2fv(u_texScale, options.texScale);
|
||||
|
||||
// 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, scale: Float32Array): Float32Array {
|
||||
let x, y;
|
||||
|
||||
if(h > w){
|
||||
y = 0.5;
|
||||
x = w/(2*h);
|
||||
} else {
|
||||
x = 0.5;
|
||||
y = h/(2*w);
|
||||
}
|
||||
|
||||
// Scale the rendering space if needed
|
||||
x *= scale[0];
|
||||
y *= scale[1];
|
||||
|
||||
return new Float32Array([
|
||||
-x, y, 0.0, 0.0,
|
||||
-x, -y, 0.0, 1.0,
|
||||
x, y, 1.0, 0.0,
|
||||
x, -y, 1.0, 1.0
|
||||
]);
|
||||
}
|
||||
|
||||
getOptions(sprite: Sprite): Record<string, any> {
|
||||
let texShift;
|
||||
let texScale;
|
||||
|
||||
if(sprite instanceof AnimatedSprite){
|
||||
let animationIndex = sprite.animation.getIndexAndAdvanceAnimation();
|
||||
let offset = sprite.getAnimationOffset(animationIndex);
|
||||
texShift = new Float32Array([offset.x / (sprite.cols * sprite.size.x), offset.y / (sprite.rows * sprite.size.y)]);
|
||||
texScale = new Float32Array([1/(sprite.cols), 1/(sprite.rows)]);
|
||||
} else {
|
||||
texShift = new Float32Array([0, 0]);
|
||||
texScale = new Float32Array([1, 1]);
|
||||
}
|
||||
|
||||
let options: Record<string, any> = {
|
||||
position: sprite.position,
|
||||
rotation: sprite.rotation,
|
||||
size: sprite.size,
|
||||
scale: sprite.scale.toArray(),
|
||||
imageKey: sprite.imageId,
|
||||
texShift,
|
||||
texScale
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
|
@ -4,6 +4,8 @@ import { TiledTilemapData } from "../DataTypes/Tilesets/TiledData";
|
|||
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.
|
||||
|
@ -68,6 +70,21 @@ export default class ResourceManager {
|
|||
/** The total number of "types" of things that need to be loaded (i.e. images and tilemaps) */
|
||||
private loadonly_typesToLoad: number;
|
||||
|
||||
/* ########## INFORMATION SPECIAL TO WEBGL ########## */
|
||||
private gl_WebGLActive: boolean;
|
||||
|
||||
private loadonly_gl_ShaderProgramsLoaded: number;
|
||||
private loadonly_gl_ShaderProgramsToLoad: number;
|
||||
private loadonly_gl_ShaderLoadingQueue: Queue<KeyPath_Shader>;
|
||||
|
||||
private gl_DefaultShaderPrograms: Map<WebGLProgramType>;
|
||||
private gl_ShaderPrograms: Map<WebGLProgramType>;
|
||||
|
||||
private gl_Textures: Map<WebGLTexture>;
|
||||
private gl_Buffers: Map<WebGLBuffer>;
|
||||
|
||||
private gl: WebGLRenderingContext;
|
||||
|
||||
private constructor(){
|
||||
this.loading = false;
|
||||
this.justLoaded = false;
|
||||
|
@ -91,6 +108,16 @@ export default class ResourceManager {
|
|||
this.loadonly_audioToLoad = 0;
|
||||
this.loadonly_audioLoadingQueue = new Queue();
|
||||
this.audioBuffers = new Map();
|
||||
|
||||
this.loadonly_gl_ShaderProgramsLoaded = 0;
|
||||
this.loadonly_gl_ShaderProgramsToLoad = 0;
|
||||
this.loadonly_gl_ShaderLoadingQueue = new Queue();
|
||||
|
||||
this.gl_DefaultShaderPrograms = new Map();
|
||||
this.gl_ShaderPrograms = new Map();
|
||||
|
||||
this.gl_Textures = new Map();
|
||||
this.gl_Buffers = new Map();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -105,6 +132,19 @@ export default class ResourceManager {
|
|||
return this.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates or deactivates the use of WebGL
|
||||
* @param flag True if WebGL should be used, false otherwise
|
||||
* @param gl The instance of the graphics context, if applicable
|
||||
*/
|
||||
public useWebGL(flag: boolean, gl: WebGLRenderingContext): void {
|
||||
this.gl_WebGLActive = flag;
|
||||
|
||||
if(this.gl_WebGLActive){
|
||||
this.gl = gl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an image from file
|
||||
* @param key The key to associate the loaded image with
|
||||
|
@ -199,15 +239,26 @@ export default class ResourceManager {
|
|||
console.log("Loaded Images");
|
||||
this.loadAudioFromQueue(() => {
|
||||
console.log("Loaded Audio");
|
||||
// Done loading
|
||||
this.loading = false;
|
||||
this.justLoaded = true;
|
||||
callback();
|
||||
|
||||
if(this.gl_WebGLActive){
|
||||
this.gl_LoadShadersFromQueue(() => {
|
||||
console.log("Loaded Shaders");
|
||||
this.finishLoading(callback);
|
||||
});
|
||||
} else {
|
||||
this.finishLoading(callback);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private finishLoading(callback: Function): void {
|
||||
// Done loading
|
||||
this.loading = false;
|
||||
this.justLoaded = true;
|
||||
callback();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -232,6 +283,12 @@ export default class ResourceManager {
|
|||
this.loadonly_audioLoaded = 0;
|
||||
this.loadonly_audioToLoad = 0;
|
||||
this.audioBuffers.clear();
|
||||
|
||||
// WebGL
|
||||
// Delete all programs through webGL
|
||||
this.gl_ShaderPrograms.forEach(key => this.gl_ShaderPrograms.get(key).delete(this.gl));
|
||||
this.gl_ShaderPrograms.clear();
|
||||
this.gl_Textures.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -385,6 +442,9 @@ export default class ResourceManager {
|
|||
// Add to loaded images
|
||||
this.images.add(key, image);
|
||||
|
||||
// If WebGL is active, create a texture
|
||||
this.createWebGLTexture(key);
|
||||
|
||||
// Finish image load
|
||||
this.finishLoadingImage(callbackIfLast);
|
||||
}
|
||||
|
@ -464,6 +524,192 @@ export default class ResourceManager {
|
|||
}
|
||||
}
|
||||
|
||||
/* ########## WEBGL SPECIFIC FUNCTIONS ########## */
|
||||
|
||||
public getTexture(key: string): WebGLTexture {
|
||||
return this.gl_Textures.get(key);
|
||||
}
|
||||
|
||||
public getShaderProgram(key: string): WebGLProgram {
|
||||
return this.gl_ShaderPrograms.get(key).program;
|
||||
}
|
||||
|
||||
public getBuffer(key: string): WebGLBuffer {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public createBuffer(key: string): void {
|
||||
if(this.gl_WebGLActive){
|
||||
let buffer = this.gl.createBuffer();
|
||||
|
||||
this.gl_Buffers.add(key, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues loading of a new shader program
|
||||
* @param key The key of the shader program
|
||||
* @param vShaderFilepath
|
||||
* @param fShaderFilepath
|
||||
*/
|
||||
public shader(key: string, vShaderFilepath: string, fShaderFilepath: string): void {
|
||||
let splitPath = vShaderFilepath.split(".");
|
||||
let end = splitPath[splitPath.length - 1];
|
||||
|
||||
if(end !== "vshader"){
|
||||
throw `${vShaderFilepath} is not a valid vertex shader - must end in ".vshader`;
|
||||
}
|
||||
|
||||
splitPath = fShaderFilepath.split(".");
|
||||
end = splitPath[splitPath.length - 1];
|
||||
|
||||
if(end !== "fshader"){
|
||||
throw `${fShaderFilepath} is not a valid vertex shader - must end in ".fshader`;
|
||||
}
|
||||
|
||||
let paths = new KeyPath_Shader();
|
||||
paths.key = key;
|
||||
paths.vpath = vShaderFilepath;
|
||||
paths.fpath = fShaderFilepath;
|
||||
|
||||
this.loadonly_gl_ShaderLoadingQueue.enqueue(paths);
|
||||
}
|
||||
|
||||
private gl_LoadShadersFromQueue(onFinishLoading: Function): void {
|
||||
this.loadonly_gl_ShaderProgramsToLoad = this.loadonly_gl_ShaderLoadingQueue.getSize();
|
||||
this.loadonly_gl_ShaderProgramsLoaded = 0;
|
||||
|
||||
// If webGL isn'active or there are no items to load, we're finished
|
||||
if(!this.gl_WebGLActive || this.loadonly_gl_ShaderProgramsToLoad === 0){
|
||||
onFinishLoading();
|
||||
}
|
||||
|
||||
while(this.loadonly_gl_ShaderLoadingQueue.hasItems()){
|
||||
let shader = this.loadonly_gl_ShaderLoadingQueue.dequeue();
|
||||
this.gl_LoadShader(shader.key, shader.vpath, shader.fpath, onFinishLoading);
|
||||
}
|
||||
}
|
||||
|
||||
private gl_LoadShader(key: string, vpath: string, fpath: string, callbackIfLast: Function): void {
|
||||
this.loadTextFile(vpath, (vFileText: string) => {
|
||||
const vShader = vFileText;
|
||||
|
||||
this.loadTextFile(fpath, (fFileText: string) => {
|
||||
const fShader = fFileText
|
||||
|
||||
// Extract the program and shaders
|
||||
const [shaderProgram, vertexShader, fragmentShader] = this.createShaderProgram(vShader, fShader);
|
||||
|
||||
// Create a wrapper type
|
||||
const programWrapper = new WebGLProgramType();
|
||||
programWrapper.program = shaderProgram;
|
||||
programWrapper.vertexShader = vertexShader;
|
||||
programWrapper.fragmentShader = fragmentShader;
|
||||
|
||||
// Add to our map
|
||||
this.gl_ShaderPrograms.add(key, programWrapper);
|
||||
|
||||
// Finish loading
|
||||
this.gl_FinishLoadingShader(callbackIfLast);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private gl_FinishLoadingShader(callback: Function): void {
|
||||
this.loadonly_gl_ShaderProgramsLoaded += 1;
|
||||
|
||||
if(this.loadonly_gl_ShaderProgramsLoaded === this.loadonly_gl_ShaderProgramsToLoad){
|
||||
// We're done loading shaders
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
private createShaderProgram(vShaderSource: string, fShaderSource: string){
|
||||
const vertexShader = this.loadVertexShader(vShaderSource);
|
||||
const fragmentShader = this.loadFragmentShader(fShaderSource);
|
||||
|
||||
if(vertexShader === null || fragmentShader === null){
|
||||
// We had a problem intializing - error
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create a shader program
|
||||
const program = this.gl.createProgram();
|
||||
if(!program) {
|
||||
// Error creating
|
||||
console.log("Failed to create program");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Attach our vertex and fragment shader
|
||||
this.gl.attachShader(program, vertexShader);
|
||||
this.gl.attachShader(program, fragmentShader);
|
||||
|
||||
// Link
|
||||
this.gl.linkProgram(program);
|
||||
if(!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)){
|
||||
// Error linking
|
||||
const error = this.gl.getProgramInfoLog(program);
|
||||
console.log("Failed to link program: " + error);
|
||||
|
||||
// Clean up
|
||||
this.gl.deleteProgram(program);
|
||||
this.gl.deleteShader(vertexShader);
|
||||
this.gl.deleteShader(fragmentShader);
|
||||
return null;
|
||||
}
|
||||
|
||||
// We successfully create a program
|
||||
return [program, vertexShader, fragmentShader];
|
||||
}
|
||||
|
||||
private loadVertexShader(shaderSource: string): WebGLShader{
|
||||
// Create a new vertex shader
|
||||
return this.loadShader(this.gl.VERTEX_SHADER, shaderSource);
|
||||
}
|
||||
|
||||
private loadFragmentShader(shaderSource: string): WebGLShader{
|
||||
// Create a new fragment shader
|
||||
return this.loadShader(this.gl.FRAGMENT_SHADER, shaderSource);
|
||||
}
|
||||
|
||||
private loadShader(type: number, shaderSource: string): WebGLShader{
|
||||
const shader = this.gl.createShader(type);
|
||||
|
||||
// If we couldn't create the shader, error
|
||||
if(shader === null){
|
||||
console.log("Unable to create shader");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add the source to the shader and compile
|
||||
this.gl.shaderSource(shader, shaderSource);
|
||||
this.gl.compileShader(shader);
|
||||
|
||||
// Make sure there were no errors during this process
|
||||
if(!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)){
|
||||
// Not compiled - error
|
||||
const error = this.gl.getShaderInfoLog(shader);
|
||||
console.log("Failed to compile shader: " + error);
|
||||
|
||||
// Clean up
|
||||
this.gl.deleteShader(shader);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Sucess, so return the shader
|
||||
return shader;
|
||||
}
|
||||
|
||||
/* ########## GENERAL LOADING FUNCTIONS ########## */
|
||||
|
||||
private loadTextFile(textFilePath: string, callback: Function): void {
|
||||
let xobj: XMLHttpRequest = new XMLHttpRequest();
|
||||
xobj.overrideMimeType("application/json");
|
||||
|
@ -476,6 +722,8 @@ export default class ResourceManager {
|
|||
xobj.send(null);
|
||||
}
|
||||
|
||||
/* ########## LOADING BAR INFO ########## */
|
||||
|
||||
private getLoadPercent(): number {
|
||||
return (this.loadonly_tilemapsLoaded/this.loadonly_tilemapsToLoad
|
||||
+ this.loadonly_spritesheetsLoaded/this.loadonly_spritesheetsToLoad
|
||||
|
@ -499,6 +747,12 @@ export default class ResourceManager {
|
|||
}
|
||||
|
||||
class KeyPathPair {
|
||||
key: string
|
||||
path: string
|
||||
key: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
class KeyPath_Shader {
|
||||
key: string;
|
||||
vpath: string;
|
||||
fpath: string;
|
||||
}
|
|
@ -88,7 +88,9 @@ export default class SceneManager {
|
|||
* Renders the current Scene
|
||||
*/
|
||||
public render(): void {
|
||||
this.currentScene.render();
|
||||
if(this.currentScene.isRunning()){
|
||||
this.currentScene.render();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -83,7 +83,7 @@ export default class SceneGraphArray extends SceneGraph {
|
|||
let visibleSet = new Array<CanvasNode>();
|
||||
|
||||
for(let node of this.nodeList){
|
||||
if(!node.getLayer().isHidden() && this.viewport.includes(node)){
|
||||
if(!node.getLayer().isHidden() && node.visible && this.viewport.includes(node)){
|
||||
visibleSet.push(node);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,6 +138,14 @@ export default class 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this color as an array
|
||||
* @returns [r, g, b, a]
|
||||
*/
|
||||
toArray(): [number, number, number, number] {
|
||||
return [this.r, this.g, this.b, this.a];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color as a string of the form #RRGGBB
|
||||
* @returns #RRGGBB
|
||||
|
@ -164,4 +172,17 @@ export default class Color {
|
|||
}
|
||||
return "rgba(" + this.r.toString() + ", " + this.g.toString() + ", " + this.b.toString() + ", " + this.a.toString() +")"
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns this color into a float32Array and changes color range to [0.0, 1.0]
|
||||
* @returns a Float32Array containing the color
|
||||
*/
|
||||
toWebGL(): Float32Array {
|
||||
return new Float32Array([
|
||||
this.r/255,
|
||||
this.g/255,
|
||||
this.b/255,
|
||||
this.a
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -57,6 +57,10 @@ export default class MathUtils {
|
|||
}
|
||||
}
|
||||
|
||||
static changeRange(x: number, min: number, max: number, newMin: number, newMax: number): number {
|
||||
return this.lerp(newMin, newMax, this.invLerp(min, max, x));
|
||||
}
|
||||
|
||||
/**
|
||||
* Linear Interpolation
|
||||
* @param a The first value for the interpolation bound
|
||||
|
|
28
src/Wolfie2D/Utils/RenderingUtils.ts
Normal file
28
src/Wolfie2D/Utils/RenderingUtils.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import Vec2 from "../DataTypes/Vec2";
|
||||
import Color from "./Color";
|
||||
import MathUtils from "./MathUtils";
|
||||
|
||||
export default class RenderingUtils {
|
||||
static toWebGLCoords(point: Vec2, origin: Vec2, worldSize: Vec2): Float32Array {
|
||||
return new Float32Array([
|
||||
MathUtils.changeRange(point.x, origin.x, origin.x + worldSize.x, -1, 1),
|
||||
MathUtils.changeRange(point.y, origin.y, origin.y + worldSize.y, 1, -1)
|
||||
]);
|
||||
}
|
||||
|
||||
static toWebGLScale(size: Vec2, worldSize: Vec2): Float32Array {
|
||||
return new Float32Array([
|
||||
2*size.x/worldSize.x,
|
||||
2*size.y/worldSize.y,
|
||||
]);
|
||||
}
|
||||
|
||||
static toWebGLColor(color: Color): Float32Array {
|
||||
return new Float32Array([
|
||||
MathUtils.changeRange(color.r, 0, 255, 0, 1),
|
||||
MathUtils.changeRange(color.g, 0, 255, 0, 1),
|
||||
MathUtils.changeRange(color.b, 0, 255, 0, 1),
|
||||
color.a
|
||||
]);
|
||||
}
|
||||
}
|
68
src/hw1/GradientCircleShaderType.ts
Normal file
68
src/hw1/GradientCircleShaderType.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
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);
|
||||
}
|
||||
}
|
8
src/hw1/HW1_Enums.ts
Normal file
8
src/hw1/HW1_Enums.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
export enum Homework1Event {
|
||||
PLAYER_DAMAGE = "PLAYER_DAMAGE",
|
||||
SPAWN_FLEET = "SPAWN_FLEET"
|
||||
}
|
||||
|
||||
export enum Homework1Shaders {
|
||||
GRADIENT_CIRCLE = "GRADIENT_CIRCLE"
|
||||
}
|
107
src/hw1/HW1_Scene.ts
Normal file
107
src/hw1/HW1_Scene.ts
Normal file
|
@ -0,0 +1,107 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
77
src/hw1/SpaceshipPlayerController.ts
Normal file
77
src/hw1/SpaceshipPlayerController.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
src/main.ts
44
src/main.ts
|
@ -1,30 +1,36 @@
|
|||
import Game from "./Wolfie2D/Loop/Game";
|
||||
import Platformer from "./Platformer";
|
||||
import Homework1_Scene from "./hw1/HW1_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.
|
||||
(function main(){
|
||||
// These are options for initializing the game
|
||||
// Here, we'll set the size of the viewport, color the background, and set up key bindings.
|
||||
// Set up options
|
||||
let options = {
|
||||
canvasSize: {x: 800, y: 600},
|
||||
zoomLevel: 4,
|
||||
clearColor: {r: 34, g: 32, b: 52},
|
||||
canvasSize: {x: 1200, y: 800},
|
||||
clearColor: {r: 0.1, g: 0.1, b: 0.1},
|
||||
inputs: [
|
||||
{ name: "left", keys: ["a"] },
|
||||
{ name: "right", keys: ["d"] },
|
||||
{ name: "jump", keys: ["space", "w"]}
|
||||
]
|
||||
{ name: "forward", keys: ["w"] },
|
||||
{ name: "backward", keys: ["s"] },
|
||||
{ name: "turn_ccw", keys: ["a"] },
|
||||
{ name: "turn_cw", keys: ["d"] },
|
||||
],
|
||||
useWebGL: true,
|
||||
showDebug: false
|
||||
}
|
||||
|
||||
// Create our game. This will create all of the systems.
|
||||
const demoGame = new Game(options);
|
||||
// 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
|
||||
|
||||
// Run our game. This will start the game loop and get the updates and renders running.
|
||||
demoGame.start();
|
||||
// Create a game with the options specified
|
||||
const game = new Game(options);
|
||||
|
||||
// For now, we won't specify any scene options.
|
||||
let sceneOptions = {};
|
||||
|
||||
// Add our first scene. This will load this scene into the game world.
|
||||
demoGame.getSceneManager().addScene(Platformer, sceneOptions);
|
||||
// Start our game
|
||||
game.start();
|
||||
game.getSceneManager().addScene(Homework1_Scene, {});
|
||||
})();
|
Loading…
Reference in New Issue
Block a user