8000 crt effect · sjefvanleeuwen/shooter@93df0d8 · GitHub
[go: up one dir, main page]

Skip to content

Commit 93df0d8

Browse files
crt effect
1 parent 1b24727 commit 93df0d8

File tree

3 files changed

+277
-28
lines changed

3 files changed

+277
-28
lines changed

js/CanvasManager.js

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
11
class CanvasManager {
2-
constructor(canvasId, virtualWidth, virtualHeight) {
3-
this.canvas = document.getElementById(canvasId);
4-
this.ctx = this.canvas.getContext('2d');
5-
this.virtualWidth = virtualWidth;
6-
this.virtualHeight = virtualHeight;
7-
this.scale = 1;
8-
this.offsetX = 0;
9-
this.offsetY = 0;
2+
constructor(canvas) {
3+
this.canvas = canvas;
4+
if (!this.canvas) throw new Error('Canvas element required');
5+
6+
// Force exact 1024x1024 dimensions
7+
this.canvas.width = 1024;
8+
this.canvas.height = 1024;
109

11-
this.setupCanvas();
12-
this.bindEvents();
10+
// Set up context with fixed dimensions
11+
this.ctx = this.canvas.getContext('2d');
12+
this.ctx.imageSmoothingEnabled = false;
13+
14+
// Store virtual dimensions
15+
this.virtualWidth = 1024;
16+
this.virtualHeight = 1024;
1317
}
14-
18+
19+
clearScreen() {
20+
this.ctx.fillStyle = '#000000';
21+
this.ctx.fillRect(0, 0, 1024, 1024);
22+
}
23+
1524
setupCanvas() {
1625
const displayWidth = window.innerWidth;
1726
const displayHeight = window.innerHeight;
@@ -27,13 +36,6 @@ class CanvasManager {
2736
this.ctx.setTransform(this.scale, 0, 0, this.scale, this.offsetX, this.offsetY);
2837
}
2938

30-
clearScreen() {
31-
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
32-
this.ctx.fillStyle = '#000000';
33-
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
34-
this.applyTransform();
35-
}
36-
3739
bindEvents() {
3840
let resizeTimeout;
3941
window.addEventListener('resize', () => {
@@ -44,6 +46,21 @@ class CanvasManager {
4446
});
4547
}
4648

49+
resize(scale, left, top) {
50+
// Maintain fixed dimensions
51+
this.canvas.width = this.virtualWidth;
52+
this.canvas.height = this.virtualHeight;
53+
54+
// Apply exact transforms
55+
this.canvas.style.width = `${this.virtualWidth}px`;
56+
this.canvas.style.height = `${this.virtualHeight}px`;
57+
this.canvas.style.position = 'absolute';
58+
this.canvas.style.transformOrigin = '0 0';
59+
this.canvas.style.transform = `scale(${scale})`;
60+
this.canvas.style.left = `${left}px`;
61+
this.canvas.style.top = `${top}px`;
62+
}
63+
4764
getContext() {
4865
return this.ctx;
4966
}

js/effects/CRTEffect.js

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
class CRTEffect {
2+
constructor(targetCanvas, container) {
3+
this.gameCanvas = targetCanvas;
4+
5+
// Create WebGL canvas with fixed 1024x1024 dimensions
6+
this.glCanvas = document.createElement('canvas');
7+
this.glCanvas.width = 1024;
8+
this.glCanvas.height = 1024;
9+
10+
// Force exact pixel dimensions in style
11+
this.glCanvas.style.width = '1024px';
12+
this.glCanvas.style.height = '1024px';
13+
this.glCanvas.style.display = 'block';
14+
15+
// Center in container
16+
container.appendChild(this.glCanvas);
17+
18+
// Init WebGL with correct size
19+
this.gl = this.glCanvas.getContext('webgl2', {
20+
premultipliedAlpha: false,
21+
alpha: false
22+
});
23+
24+
this.createShaders();
25+
this.createBuffers();
26+
this.createTexture();
27+
}
28+
29+
createShaders() {
30+
const vsSource = `#version 300 es
31+
in vec2 a_position;
32+
in vec2 a_texCoord;
33+
out vec2 v_texCoord;
34+
void main() {
35+
gl_Position = vec4(a_position, 0, 1);
36+
v_texCoord = a_texCoord;
37+
}`;
38+
39+
const fsSource = `#version 300 es
40+
precision highp float;
41+
42+
uniform sampler2D u_image;
43+
uniform vec2 u_resolution;
44+
uniform float u_time;
45+
46+
in vec2 v_texCoord;
47+
out vec4 outColor;
48+
49+
#define SCANLINE_INTENSITY 0.1
50+
#define VIGNETTE_STRENGTH 0.2
51+
52+
void main() {
53+
vec2 uv = v_texCoord;
54+
55+
// Screen curve
56+
vec2 curve_uv = uv * 2.0 - 1.0;
57+
vec2 offset = curve_uv.yx * curve_uv.yx * vec2(0.075, 0.075);
58+
curve_uv += curve_uv * offset;
59+
uv = curve_uv * 0.5 + 0.5;
60+
61+
// Check bounds
62+
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
63+
outColor = vec4(0.0, 0.0, 0.0, 1.0);
64+
return;
65+
}
66+
67+
// Sample with subtle RGB shift
68+
float r = texture(u_image, uv + vec2(0.001, 0.0)).r;
69+
float g = texture(u_image, uv).g;
70+
float b = texture(u_image, uv - vec2(0.001, 0.0)).b;
71+
vec4 color = vec4(r, g, b, 1.0);
72+
73+
// Scanlines
74+
float scanline = sin(uv.y * u_resolution.y * 1.5) * 0.5 + 0.5;
75+
color *= 1.0 - scanline * SCANLINE_INTENSITY;
76+
77+
// Vignette
78+
float vignette = 1.0 - length(curve_uv) * VIGNETTE_STRENGTH;
79+
color *= vignette;
80+
81+
// Subtle noise
82+
float noise = fract(sin(dot(uv, vec2(12.9898, 78.233))) * 43758.5453);
83+
color *= 0.98 + noise * 0.02;
84+
85+
outColor = color;
86+
}`;
87+
88+
const vertexShader = this.createShader(this.gl.VERTEX_SHADER, vsSource);
89+
const fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, fsSource);
90+
91+
this.program = this.createProgram(vertexShader, fragmentShader);
92+
93+
// Get locations
94+
this.positionLoc = this.gl.getAttribLocation(this.program, 'a_position');
95+
this.texCoordLoc = this.gl.getAttribLocation(this.program, 'a_texCoord');
96+
this.resolutionLoc = this.gl.getUniformLocation(this.program, 'u_resolution');
97+
this.timeLoc = this.gl.getUniformLocation(this.program, 'u_time');
98+
}
99+
100+
createBuffers() {
101+
// Vertex positions
102+
const positions = new Float32Array([
103+
-1, -1, // bottom left
104+
1, -1, // bottom right
105+
-1, 1, // top left
106+
1, 1, // top right
107+
]);
108+
109+
this.positionBuffer = this.gl.createBuffer();
110+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
111+
this.gl.bufferData(this.gl.ARRAY_BUFFER, positions, this.gl.STATIC_DRAW);
112+
113+
// Texture coordinates
114+
const texCoords = new Float32Array([
115+
0, 1, // bottom left
116+
1, 1, // bottom right
117+
0, 0, // top left
118+
1, 0, // top right
119+
]);
120+
121+
this.texCoordBuffer = this.gl.createBuffer();
122+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);
123+
this.gl.bufferData(this.gl.ARRAY_BUFFER, texCoords, this.gl.STATIC_DRAW);
124+
}
125+
126+
createTexture() {
127+
this.texture = this.gl.createTexture();
128+
this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
129+
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
130+
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
131+
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
132+
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
133+
}
134+
135+
createShader(type, source) {
136+
const shader = this.gl.createShader(type);
137+
this.gl.shaderSource(shader, source);
138+
this.gl.compileShader(shader);
139+
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
140+
throw new Error(this.gl.getShaderInfoLog(shader));
141+
}
142+
return shader;
143+
}
144+
145+
createProgram(vertexShader, fragmentShader) {
146+
const program = this.gl.createProgram();
147+
this.gl.attachShader(program, vertexShader);
148+
this.gl.attachShader(program, fragmentShader);
149+
this.gl.linkProgram(program);
150+
if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) {
151+
throw new Error(this.gl.getProgramInfoLog(program));
152+
}
153+
return program;
154+
}
155+
156+
setScale(scale) {
157+
this.glCanvas.style.transform = `scale(${scale})`;
158+
}
159+
160+
render(time) {
161+
const gl = this.gl;
162+
163+
// Ensure viewport matches fixed canvas size
164+
gl.viewport(0, 0, 1024, 1024);
165+
gl.clearColor(0, 0, 0, 0);
166+
gl.clear(gl.COLOR_BUFFER_BIT);
167+
168+
gl.useProgram(this.program);
169+
170+
// Upload game canvas as texture
171+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
172+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.gameCanvas);
173+
174+
// Set uniforms
175+
gl.uniform2f(this.resolutionLoc, gl.canvas.width, gl.canvas.height);
176+
gl.uniform1f(this.timeLoc, time * 0.001);
177+
178+
// Set attributes
179+
gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
180+
gl.enableVertexAttribArray(this.positionLoc);
181+
gl.vertexAttribPointer(this.positionLoc, 2, gl.FLOAT, false, 0, 0);
182+
183+
gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer);
184+
gl.enableVertexAttribArray(this.texCoordLoc);
185+
gl.vertexAttribPointer(this.texCoordLoc, 2, gl.FLOAT, false, 0, 0);
186+
187+
// Draw
188+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
189+
}
190+
}
191+
192+
export default CRTEffect;

js/game.js

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Player from './player.js';
22
import { ParticleEngine, LaserEngine } from './particleEngine.js';
33
import ImageBackgroundScroller from './imageBackgroundScroller.js';
4-
import PatternFormation from './PatternFormation.js'; // Add this import
4+
import PatternFormation from './PatternFormation.js';
55
import IntroScreen from './screens/IntroScreen.js';
66
import MusicPlayer from './audio/MusicPlayer.js';
77
import StartupScreen from './screens/StartupScreen.js';
@@ -11,21 +11,47 @@ import InputManager from './InputManager.js';
1111
import HUDManager from './managers/HUDManager.js';
1212
import GameStateManager from './managers/GameStateManager.js';
1313
import GameScreen from './screens/GameScreen.js';
14+
import CRTEffect from './effects/CRTEffect.js'; // Add this import
1415

1516
class Game {
1617
constructor() {
17-
// Make the game instance globally accessible
18-
window.game = this;
18+
// Create container div for centering
19+
this.container = document.createElement('div');
20+
this.container.style.position = 'fixed';
21+
this.container.style.width = '100%';
22+
this.container.style.height = '100%';
23+
this.container.style.display = 'flex';
24+
this.container.style.justifyContent = 'center';
25+
this.container.style.alignItems = 'center';
26+
this.container.style.background = '#000';
27+
document.body.appendChild(this.container);
28+
29+
// Fixed dimensions
30+
this.virtualWidth = 1024;
31+
this.virtualHeight = 1024;
32+
33+
// Setup main canvas
34+
this.canvas = document.getElementById('gameCanvas');
35+
if (!this.canvas) throw new Error('Canvas not found');
36+
this.canvas.width = this.virtualWidth;
37+
this.canvas.height = this.virtualHeight;
38+
this.canvas.style.display = 'none';
39+
40+
// Initialize managers with fixed dimensions
41+
this.canvasManager = new CanvasManager(this.canvas);
42+
this.ctx = this.canvasManager.getContext();
1943

20-
this.virtualWidth = 1080; // Changed from 1920 to match height
21-
this.virtualHeight = 1080; // Keeping this the same
44+
// Initialize CRT effect with fixed dimensions
45+
this.crtEffect = new CRTEffect(this.canvas, this.container);
2246

23-
// Initialize managers
24-
this.canvasManager = new CanvasManager('gameCanvas', this.virtualWidth, this.virtualHeight);
47+
// Then initialize other managers that need the context
2548
this.inputManager = new InputManager();
2649
this.gameState = new GameStateManager();
27-
this.hudManager = new HUDManager(this.canvasManager.getContext(), this.virtualWidth, this.virtualHeight);
28-
50+
this.hudManager = new HUDManager(this.ctx, this.virtualWidth, this.virtualHeight);
51+
52+
// Make the game instance globally accessible
53+
window.game = this;
54+
2955
this.ctx = this.canvasManager.getContext();
3056

3157
// Remove setupCanvas() and bindEvents() calls
@@ -37,7 +63,7 @@ class Game {
3763
this.offsetX = 0;
3864
this.offsetY = 0;
3965

40-
this.viewportWidth = 1080; // Changed from 1920 to match new virtual width
66+
this.viewportWidth = 1024; // Changed from 1920 to match new virtual width
4167
this.checkerSize = 64;
4268

4369
this.lastTime = 0;
@@ -113,6 +139,17 @@ class Game {
113139
this.debugWindow = new DebugWindow();
114140
}
115141

142+
resize() {
143+
// Get scale to fit screen while maintaining 1:1 ratio
144+
const minDimension = Math.min(window.innerWidth, window.innerHeight) - 40;
145+
const scale = minDimension / 1024;
146+
147+
// Update CRT effect scale only
148+
if (this.crtEffect) {
149+
this.crtEffect.setScale(scale);
150+
}
151+
}
152+
116153
gameOver() {
117154
// Switch to intro screen with game over flag
118155
this.screens.intro = new IntroScreen(this.ctx, {
@@ -190,6 +227,9 @@ class Game {
190227
if(this.debugWindow.visible) {
191228
this.debugWindow.draw(this.ctx);
192229
}
230+
231+
// Render CRT effect last
232+
this.crtEffect.render(performance.now());
193233
}
194234

195235
startGameLoop() {

0 commit comments

Comments
 (0)
0