Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

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.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

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.

+ add another resource

Packages

Add Packages

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.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <canvas width="860" height="480" id="canvas"></canvas>
<ul>
  <li>Right - Accelerate</li>
	<li>Left - Brake</li>
	<li>Up - Jump</li>
	<li>Down (while jumping or falling) - Swap Tracks</li>
	<li>1 - Disable forward movement</li>
	<li>2 - Enable forward movement</li>
</ul>
              
            
!

CSS

              
                body {
  background: #242424;
	color: #666;
	font-size: 8pt;
	font-family: sans-serif;
	padding: 0;
	margin: 0;
}
              
            
!

JS

              
                var _debug = {
  lockPlayerMovement: false,
  showPlayerInfo: false
};

var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");

var Shape = {};

Shape.line = function() {
  this.x1 = 0;
	this.y1 = 0;
	this.x2 = 0;
	this.y2 = 0;
	this.lineWidth = 2;
	this.strokeStyle = "#000000";
	
	this.draw = function() {
		context.beginPath();
		context.moveTo(this.x1, this.y1);
		context.lineTo(this.x2, this.y2);
		context.lineWidth = this.lineWidth;
		context.strokeStyle = this.strokeStyle;
		context.stroke();
	};
};

Shape.circle = function() {
	this.x = 0;
	this.y = 0;
	this.radius = 0;
	this.lineWidth = 2;
	this.strokeStyle = "#000000";
	this.fillStyle = "transparent";
	
	this.draw = function() {
		context.beginPath();
		context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
		context.fillStyle = this.fillStyle;
		context.fill();
		context.lineWidth = this.lineWidth;
		context.strokeStyle = this.strokeStyle;
		context.stroke();
	};
};

Shape.text = function() {
	this.x = 0;
	this.y = 0;
	this.font = "10pt sans-serif";
	this.textAlign = "center";
	this.textBaseline = "middle";
	this.fillStyle = "black";
	this.fillText = "";
	
	this.draw = function() {
		context.font = this.font;
		context.textAlign = this.center;
		context.textBaseline = this.textBaseline;
		context.fillStyle = this.fillStyle;
		context.fillText(this.fillText, this.x, this.y);
	};
};

Shape.rectangle = function() {
	this.x = 0;
	this.y = 0;
	this.width = 0;
	this.height = 0;
	this.lineWidth = 2;
	this.strokeStyle = "#000000";
	this.fillStyle = "transparent";
	
	this.draw = function() {
		context.rect(this.x, this.y, this.width, this.height);
		context.fillStyle = this.fillStyle;
		context.fill();
	};
};

var Key = {
	_pressed: {},

	LEFT: 37,
	UP: 38,
	RIGHT: 39,
	DOWN: 40,
	SPACE: 32,
	SHIFT: 16,
	CTRL: 17,
	CAM_LEFT: 190,
	CAM_RIGHT: 188,
	
	DB_1: 49,
	DB_2: 50,
	DB_3: 51,
	DB_4: 52,
	DB_5: 53,
	DB_6: 54,
	DB_7: 55,
	DB_8: 56,
	DB_9: 57,

	isDown: function(keyCode) {
		return this._pressed[keyCode];
	},

	onKeydown: function(event) {
		this._pressed[event.keyCode] = true;
	},

	onKeyup: function(event) {
		delete this._pressed[event.keyCode];
	}
};
window.addEventListener('keyup', function(event) { Key.onKeyup(event); }, false);
window.addEventListener('keydown', function(event) { Key.onKeydown(event); }, false);

var Camera = function(x, y) {
	this.x = x;
	this.y = y;
	this.radius = 4;
	this.velocity = {};
	this.velocity.x = 0;
	this.velocity.y = 0;
	
	this.addVelocity = function(x, y) {
		this.velocity.x += x;
		this.velocity.y += y;
	};
	
	this.update = function(lines, player) {
		this.x = player.x - (canvas.width / 2);
	};
};

var Player = function(x, y, r) {
	this.shapes = {};
	this.shapes.circle = new Shape.circle();
	this.shapes.radius = new Shape.line();
	
	this.x = x;
	this.y = y;
	this.radius = r;
	this.velocity = {};
	this.velocity.x = 0;
	this.velocity.y = 0;
	this.angle = 0;
	this.level = 1;
	
	var maxJumpDist = 12;
	var jumpDist = 0;
	
	var lasty = y;
	var lastx = 0;
	
	// States
	this.isJumping = false;
	this.isFalling = false;
	this.isSwappingLevels = false;
	this.isAlive = true;
	
	// Adds velocity to the player
	this.addVelocity = function(x, y) {
		this.velocity.x += x;
		this.velocity.y += y;
	};
	
	// Collides with the world
	this.collide = function(line, adjx, adjy) {
		var run = line.x2 - line.x1;
		var rise = line.y2 - line.y1;
		
		var slope = rise / run;
		var intersect = (line.y1) - (line.x1 * slope);
		var col = (slope * adjx) + intersect;
		
		var angle = radToDeg(Math.atan(rise / run));
		this.angle = angle;
		var normal = angle + 90;
		
		// Collide with track
		if(lasty < col && adjy > col) {
			this.y = col + (this.y - adjy);
			this.isFalling = false;
			this.isSwappingLevels = false;
			jumpDist = 0;
		} else {
			if(!this.isJumping) {
				this.isFalling = true;
			}
		}
	};
	
	this.update = function(lines) {	
		if(this.isAlive) {
			// Accelerate
			if(Key.isDown(Key.RIGHT))
				this.addVelocity(_debug.lockPlayerMovement ? 0.2 : 0.1, 0, 0);
			
			// Brake
			if(Key.isDown(Key.LEFT))
				this.addVelocity(_debug.lockPlayerMovement ? -0.2 : -0.1, 0);
				
			// Jump
			if(Key.isDown(Key.UP)) {
				if(!this.isFalling && !this.isJumping) {
					this.isJumping = true;
				}
			// Stop jumping
			} else {
				if(this.isJumping) {
					this.isJumping = false;
					jumpDist = 0;
				}
			}
		}
	
		this.y += 7.5; // gravity
		if(this.isAlive) this.velocity.x += _debug.lockPlayerMovement ? 0 : 0.2; // automatic accelerate
		this.x += this.velocity.x;
		this.y += this.velocity.y;
		
		// Decay velocity
		this.velocity.x *= 0.985;
		this.velocity.y *= 0.95;

		// Collision point
		var adjx = (this.radius * Math.cos((this.angle + 90) * Math.PI / 180)) + this.x;
        var adjy = (this.radius * Math.sin((this.angle + 90) * Math.PI / 180)) + (this.y + 2); // 2px padding for stroke width
		
		// Finds the line the player is going to collide with
		var playerPos;
		if(lines[0].level === 2) // background track
			playerPos = adjx / levelPrefs.width >> 0;
		else // foreground track
			playerPos = adjx / levelPrefs.width >> 0;
		
		// Loop through the lines and collide with the player
		for(var i = 0; i < lines.length; i++) {
			if(i === playerPos)
				this.collide(lines[i], adjx, adjy);
		}

		// Swap tracks
		if(!this.isSwappingLevels && !this.isJumping && this.isFalling && Key.isDown(Key.DOWN)) {
			if(this.level === 1) {
				this.level = 2;
				this.isSwappingLevels = true;
			} else {
				this.level = 1;
				this.isSwappingLevels = true;
			}
		}
		
		// Find the y direction the player is moving
		var yvel;
		var yfixed = this.y.toFixed(1);
		if(yfixed < lasty) yvel = 1;
		if(yfixed > lasty) yvel = -1;
		if(yfixed == lasty) yvel = 0;
		
		// Apply extra gravity to keep player on slope during downward movement
		// This prevents a bug where the player cannot jump while moving quickly
		// downwards due to being in a "falling" state.
		if(this.angle) {
			if(yvel < 0 && (this.angle < -33 || this.angle > 33) && !this.isJumping) 
				this.y += 2;
		}
		
		// Jumping logic
		if(this.isJumping) {
			var jumpVel = -15;
			jumpDist += 1;
			
			if(jumpDist > maxJumpDist) {
				this.isJumping = false;
				this.isFalling = true;
				jumpDist = 0;
			} else {
				this.velocity.y = jumpVel;
			}	
		}
		
		// Die when falling off the edge of the world
		if( adjy > canvas.height + (this.radius * 2) ||
			adjx < 0 ||
			adjx > lines.length * levelPrefs.width) {
			this.isAlive = false;
		}
		
		// Reset when the player dies
		if(!this.isAlive) {
			this.x = levelStart.x;
			this.y = levelStart.y;
			this.velocity = {x: 0, y: 0};
			this.isAlive = true;
		}
		
		lasty = this.y.toFixed(1); // last known y position
	};
	
	this.draw = function(camera) {
		this.shapes.circle.radius = this.radius;
		this.shapes.circle.strokeStyle = "#218ae0";
		this.shapes.circle.x = this.x - camera.x;
		this.shapes.circle.y = this.y;
		this.shapes.circle.draw();
		
		var x = (this.radius * Math.cos((this.angle + 90) * Math.PI / 180)) + this.x;
        var y = (this.radius * Math.sin((this.angle + 90) * Math.PI / 180)) + this.y;
		this.shapes.radius.x1 = this.x - camera.x;
		this.shapes.radius.y1 = this.y;
		this.shapes.radius.x2 = x - camera.x;
		this.shapes.radius.y2 = y;
		this.shapes.radius.strokeStyle = "#a7c6e0";
		this.shapes.radius.draw();
	};
};

var Line = function(x1, y1, x2, y2) {
	this.shapes = {};
	this.shapes.line = new Shape.line();

	this.x1 = x1;
	this.y1 = y1;
	this.x2 = x2;
	this.y2 = y2;
	this.strokeStyle = "#ffffff";
	this.level = 1;
	
	var lineClass = "line";
	
	this.setLevel = function(level) {
		this.level = level;
		lineClass = "line" + level;
	};
	
	this.draw = function(camera) {
		this.shapes.line.x1 = this.x1 - camera.x;
		this.shapes.line.y1 = this.y1;
		this.shapes.line.x2 = this.x2 - camera.x;
		this.shapes.line.y2 = this.y2;
		this.shapes.line.strokeStyle = this.strokeStyle;
		this.shapes.line.draw();
	};
};

function getNextPoint(frequency, offset, step, width, center) {
	return (Math.sin(frequency * step + offset) * width + center) >> 0;
}

function radToDeg(rad) {
	return rad * (180 / Math.PI);
}

var levelStart = {};
levelStart.x = canvas.width / 2;
levelStart.y = 100;

var camera = new Camera(levelStart.x, levelStart.y);
var background = new Shape.rectangle();
	background.x = 0;
	background.y = 0;
	background.width = canvas.width;
	background.height = canvas.height;
	background.lineWidth = 0;
	background.fillStyle = "#121212";
var player = new Player(levelStart.x, levelStart.y, 20);
	player.level = 2;
var lines = [];
var lines2 = [];

var levelPrefs = {
    width: 30,
    length: 600,
	frequency: 0.1,
	wavelength: 32,
	offset: 380,
	numLines: 50
};

var levelPrefs2 = {
    width: 30,
    length: 600,
	frequency: 0.2,
	wavelength: 48,
	offset: 260,
	numLines: 50
};

for(var i = 0; i < levelPrefs2.length; i ++) {
	var y = getNextPoint(levelPrefs2.frequency, levelPrefs2.frequency * Math.PI / 3, i, levelPrefs2.wavelength, levelPrefs2.offset);
	var y2 = getNextPoint(levelPrefs2.frequency, levelPrefs2.frequency * Math.PI / 3, i + 1, levelPrefs2.wavelength, levelPrefs2.offset);
	var line = new Line(i * levelPrefs2.width, y, i * levelPrefs2.width + levelPrefs2.width, y2);
	line.setLevel(2);
	line.strokeStyle = "#402712";
	lines2.push(line);
}

for(var i = 0; i < levelPrefs.length; i ++) {
	var y, y2;
	if(i % 100 > 90 && i % 100 < 100 ) {
		y = 800;
		y2 = 800;
	} else {
		y = getNextPoint(levelPrefs.frequency, levelPrefs.frequency * Math.PI / 3, i, levelPrefs.wavelength, levelPrefs.offset);
		y2 = getNextPoint(levelPrefs.frequency, levelPrefs.frequency * Math.PI / 3, i + 1, levelPrefs.wavelength, levelPrefs.offset);
	}
	var line = new Line(i * levelPrefs.width, y, i * levelPrefs.width + levelPrefs.width, y2);
	line.setLevel(1);
	line.strokeStyle = "#734720";
	lines.push(line);
}

var update = function() {
	context.save();

	// Use the identity matrix while clearing the canvas
	context.setTransform(1, 0, 0, 1, 0, 0);
	context.clearRect(0, 0, canvas.width, canvas.height);

	// Restore the transform
	context.restore();
	
	camera.update(lines, player);
	player.update(player.level === 2 ? lines2 : lines);
	
	if(Key.isDown(Key.DB_1)) _debug.lockPlayerMovement = true;
	if(Key.isDown(Key.DB_2)) _debug.lockPlayerMovement = false;
};

var draw = function() {
	background.draw();
	
	for(var i = 0; i < lines2.length; i++) {
		lines2[i].draw(camera, true);
	}
	
	player.draw(camera);
		
	for(var i = 0; i < lines.length; i++) {
		lines[i].draw(camera, false);
	}
};

var gameloop = function() {
	update();
	draw();
};

var animationFrame = window.requestAnimationFrame ||
	window.webkitRequestAnimationFrame ||
	window.mozRequestAnimationFrame    ||
	window.oRequestAnimationFrame      ||
	window.msRequestAnimationFrame     ||
	null;

if (animationFrame !== null) {
	var recursiveAnim = function() {
		gameloop();
		animationFrame(recursiveAnim);
	};

	// start the gameloop
	animationFrame(recursiveAnim);
} else {
	setInterval(gameloop, 1000.0 / 60.0);
}
              
            
!
999px

Console