HTML preprocessors can make writing HTML more powerful or convenient. For instance, Markdown is designed to be easier to write and read for text documents and you could write a loop in Pug.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. So you don't have access to higher-up elements like the <html>
tag. If you want to add classes there that can affect the whole document, this is the place to do it.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. If you need things in the <head>
of the document, put that code here.
The resource you are linking to is using the 'http' protocol, which may not work when the browser is using https.
CSS preprocessors help make authoring CSS easier. All of them offer things like variables and mixins to provide convenient abstractions.
It's a common practice to apply CSS to a page that styles elements such that they are consistent across all browsers. We offer two of the most popular choices: normalize.css and a reset. Or, choose Neither and nothing will be applied.
To get the best cross-browser support, it is a common practice to apply vendor prefixes to CSS properties and values that require them to work. For instance -webkit-
or -moz-
.
We offer two popular choices: Autoprefixer (which processes your CSS server-side) and -prefix-free (which applies prefixes via a script, client-side).
Any URLs added here will be added as <link>
s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.
You can apply CSS to your Pen from any stylesheet on the web. Just put a URL to it here and we'll apply it, in the order you have them, before the CSS in the Pen itself.
You can also link to another Pen here (use the .css
URL Extension) and we'll pull the CSS from that Pen and include it. If it's using a matching preprocessor, use the appropriate URL Extension and we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
JavaScript preprocessors can help make authoring JavaScript easier and more convenient.
Babel includes JSX processing.
Any URL's added here will be added as <script>
s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.
You can apply a script from anywhere on the web to your Pen. Just put a URL to it here and we'll add it, in the order you have them, before the JavaScript in the Pen itself.
If the script you link to has the file extension of a preprocessor, we'll attempt to process it before applying.
You can also link to another Pen here, and we'll pull the JavaScript from that Pen and include it. If it's using a matching preprocessor, we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
Search for and use JavaScript packages from npm here. By selecting a package, an import
statement will be added to the top of the JavaScript editor for this package.
Using packages here is powered by esm.sh, which makes packages from npm not only available on a CDN, but prepares them for native JavaScript ESM usage.
All packages are different, so refer to their docs for how they work.
If you're using React / ReactDOM, make sure to turn on Babel for the JSX processing.
If active, Pens will autosave every 30 seconds after being saved once.
If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.
If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.
Visit your global Editor Settings.
<canvas id="gameCanvas" width="1280" height="720"></canvas>
canvas {
image-rendering: pixelated;
}
body {
margin: 0;
overflow: hidden;
background: #000;
}
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const config = {
moveSpeed: 0.1,
rotationSpeed: 0.02
};
const state = {
pos: [11.5, 10.5],
dir: [-1, 0],
plane: [0, 0.66],
map: [
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,2,2,2,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1],
[1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,3,0,0,0,3,0,0,0,1],
[1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,2,2,0,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
],
textures: {
1: { light: '#2ecc71', shadow: '#145c39' },
2: { light: '#e74c3c', shadow: '#73261e' },
3: { light: '#3498db', shadow: '#174c6d' }
}
};
// obiekt keys będzie przechowywał aktualny stan klawiszy
const keys = {};
// nasłuchujemy zdarzeń klawiatury, aby aktualizować stan klawiszy
document.addEventListener('keydown', e => keys[e.key] = true);
document.addEventListener('keyup', e => keys[e.key] = false);
// step wskazuje kierunek ruchu gracza
// 1 - do przodu, -1 - do tyłu
function move(step) {
// wyciągamy aktualną pozycję gracza
const [x, y] = state.pos;
// obliczamy nową pozycję na podstawie kierunku w który patrzy gracz i ustalonej prędkości ruchu
const newX = x + state.dir[0] * step * config.moveSpeed;
const newY = y + state.dir[1] * step * config.moveSpeed;
// sprawdzamy, czy możemy się tam poruszyć (sprawdzenie kolizji ze ścianą)
if (state.map[Math.floor(newY)][Math.floor(newX)] === 0) {
// jeśli tak, to aktualizujemy pozycję gracza
state.pos = [newX, newY];
}
}
// dir wskazuje kierunek obrotu gracza
// 1 - w lewo, -1 - w prawo
function rotate(dir) {
// wyciągamy aktualny wektor kierunku
const [dirX, dirY] = state.dir;
// oraz wektor płaszczyzny kamery
const [planeX, planeY] = state.plane;
// określamy kąt obrotu na podstawie kierunku i ustawionej prędkości rotacji
// nazwa wynika z faktu, że jest to de facto szybkość obrotu
const speed = dir * config.rotationSpeed;
// obracamy wektor kierunku
state.dir = [
dirX * Math.cos(speed) - dirY * Math.sin(speed),
dirX * Math.sin(speed) + dirY * Math.cos(speed)
];
// obracamy również wektor płaszczyzny kamery
state.plane = [
planeX * Math.cos(speed) - planeY * Math.sin(speed),
planeX * Math.sin(speed) + planeY * Math.cos(speed)
];
}
function castRay(x) {
// normalizujemy współrzędne kamery do zakresu [-1, 1]
const cameraX = 2 * x / canvas.width - 1;
// obliczamy kierunek promienia na podstawie pozycji kamery
const rayDir = [
state.dir[0] + state.plane[0] * cameraX,
state.dir[1] + state.plane[1] * cameraX
];
// ustalamy współrzędne na mapie, gdzie znajduje się kamera
// pamiętajmy, że współrzędne mapy są całkowite, a gracza nie, więc zaokrąglamy
let mapX = Math.floor(state.pos[0]);
let mapY = Math.floor(state.pos[1]);
// obliczamy przyrosty w kierunku osi X i Y według wzorów
const deltaDist = [
Math.abs(1 / rayDir[0]),
Math.abs(1 / rayDir[1])
];
// na podstawie kierunku promienia ustalamy krok kierunkowy po mapie
// oraz odległość boczną, czyli odległość do najbliższej krawędzi komórki
// najpierw wykonamy to dla osi X
let stepX, sideDistX;
if (rayDir[0] < 0) {
stepX = -1;
// odległość boczną obliczamy jako różnicę pozycji kamery i krawędzi komórki
// pomnóżoną przez przyrost w kierunku X
sideDistX = (state.pos[0] - mapX) * deltaDist[0];
} else {
stepX = 1;
sideDistX = (mapX + 1 - state.pos[0]) * deltaDist[0];
}
// teraz dla osi Y
let stepY, sideDistY;
if (rayDir[1] < 0) {
stepY = -1;
sideDistY = (state.pos[1] - mapY) * deltaDist[1];
} else {
stepY = 1;
sideDistY = (mapY + 1 - state.pos[1]) * deltaDist[1];
}
// zaczynamy właściwy algorytm DDA
// zmienna `hit` będzie oznaczać, czy trafiliśmy w obiekt oraz w jaki
let hit = 0;
// zmienna `side` będzie oznaczać, z której strony trafiliśmy
let side;
// tak długo, jak nie trafimy w obiekt
while (hit === 0) {
// sprawdzamy, czy trafiliśmy w krawędź komórki w osi X czy Y
if (sideDistX < sideDistY) {
// jeśli w X, to przesuwamy się w osi X
sideDistX += deltaDist[0];
mapX += stepX;
side = 0;
} else {
// jeśli w Y, to przesuwamy się w osi Y
sideDistY += deltaDist[1];
mapY += stepY;
side = 1;
}
// sprawdzamy, czy trafiliśmy w obiekt
hit = state.map[mapY][mapX];
}
// jeśli trafiliśmy, to obliczamy odległość do trafienia
// w zależności od tego, z której strony trafiliśmy, obliczamy odległość;
// pamiętamy, że nie liczymy odległości euklidesowej, a tylko wzdłuż osi X lub Y;
// (1-stepX)/2 to matematyczny trik na obliczenie wartości `offset`
const distance = side === 0
? (mapX - state.pos[0] + (1 - stepX)/2) / rayDir[0]
: (mapY - state.pos[1] + (1 - stepY)/2) / rayDir[1];
return { distance, hit, side };
}
function render() {
// pobieramy wymiary płótna
const { width, height } = canvas;
// czyścimy płótno przez narysowanie sufitu
ctx.fillStyle = '#2c3e50';
ctx.fillRect(0, 0, width, height/2);
// oraz podłogi
ctx.fillStyle = '#34495e';
ctx.fillRect(0, height/2, width, height/2);
// iterujemy przez każdy piksel w szerokości płótna
for (let x = 0; x < width; x++) {
// rzucamy promień; w wyniku powinniśmy odległość, trafiony obiekt i od której strony trafiliśmy
const { distance, hit, side } = castRay(x);
// obliczamy wysokość linii, która będzie reprezentować trafiony obiekt
const lineHeight = height / distance;
// na podstawie informacji o trafieniu pobieramy odpowiedni kolor z tekstur
const color = state.textures[hit];
// w zależności od strony trafienia ustawiamy kolor cienia lub światła
ctx.fillStyle = side ? color.light : color.shadow;
// rysujemy linię na odpowiedniej pozycji
ctx.fillRect(
x,
(height - lineHeight)/2,
1,
lineHeight
);
}
}
// funkcja do obsługi wejścia z klawiatury
function handleInput() {
// w zaleności od stanu klawiszy wywołujemy funkcje ruchu i obrotu
if (keys['w'] || keys['ArrowUp']) move(1);
if (keys['s'] || keys['ArrowDown']) move(-1);
if (keys['a'] || keys['ArrowLeft']) rotate(1);
if (keys['d'] || keys['ArrowRight']) rotate(-1);
}
// główna pętla gry
function gameLoop() {
// wywołujemy rysowanie
render();
// a następnie obsługujemy wejście
handleInput();
// i wywołujemy rekurencyjnie następną klatkę
// requestAnimationFrame wywołuje wskazaną funkcję przy następnym odświeżeniu ekranu
requestAnimationFrame(gameLoop);
}
// rozpoczynamy pętlę gry
gameLoop();
Also see: Tab Triggers