Skip to content

Instantly share code, notes, and snippets.

@dmirtyisme
Created January 6, 2017 01:09
Show Gist options
  • Save dmirtyisme/3d171b8f1f40093615c6522970f642ba to your computer and use it in GitHub Desktop.
Save dmirtyisme/3d171b8f1f40093615c6522970f642ba to your computer and use it in GitHub Desktop.
Mega Man!
<canvas id="js-canvas" width="400" height="300"></canvas>
<p>Use Arrow Keys!</p>

Mega Man!

Exploring canvas by making Mega Man. I've had a life long dream to make a multiplayer Mega Man game. Maybe this is my chance!

A Pen by Drew Conley on CodePen.

License.

const initialState = {
counter: 0,
canvasWidth: 400,
canvasHeight: 300,
characterX: 80,
characterY: 0,
characterWidth: 20, //hitboxW
characterHeight: 24, //hitboxY
inAir: true,
characterFrame: 0,
characterPose: [ [0,0] ],
isAbleToJump: false,
isFacingLeft: true, //default is Right
//Jumping
verticalBoost: 0,
//Keyboard
isKeyboardLeftPressed: false,
isKeyboardRightPressed: false,
walls: [
{
"_id": "placement_1471799473495",
"x": 0,
"y": 0,
"width": 16,
"height": 288
},
{
"_id": "placement_1471799503050",
"x": 16,
"y": 256,
"width": 32,
"height": 32
},
{
"_id": "placement_1471799516043",
"x": 16,
"y": 128,
"width": 16,
"height": 16
},
{
"_id": "placement_1471799517145",
"x": 368,
"y": 144,
"width": 16,
"height": 16
},
{
"_id": "placement_1471804635390",
"x": 384,
"y": 0,
"width": 16,
"height": 288
},
{
"_id": "placement_1471804662424",
"x": 352,
"y": 256,
"width": 32,
"height": 32
},
{
"_id": "placement_1471804690729",
"x": 256,
"y": 224,
"width": 80,
"height": 16
},
{
"_id": "placement_1471804739758",
"x": 144,
"y": 160,
"width": 32,
"height": 32
},
{
"_id": "placement_1471808010628",
"x": 48,
"y": 272,
"width": 160,
"height": 16
},
{
"_id": "placement_1471808096387",
"x": 64,
"y": 64,
"width": 64,
"height": 16
},
{
"_id": "placement_1471808097749",
"x": 272,
"y": 96,
"width": 64,
"height": 16
},
{
"_id": "placement_1471808105275",
"x": 144,
"y": 32,
"width": 112,
"height": 16
},
{
"_id": "placement_1471808244933",
"x": 224,
"y": 128,
"width": 32,
"height": 32
},
{
"_id": "placement_1471808330975",
"x": 64,
"y": 192,
"width": 32,
"height": 32
}
]
}
/* SFX */
window.landingSfx = new Howl({
urls: ['https://s3-us-west-2.amazonaws.com/s.cdpn.io/21542/06_-_MegamanLand.wav'],
volume: 0.5
});
const Data = {
state: {},
prevState: {},
init(initialState) {
this.state = {...initialState}
this.prevState = {...initialState}
},
getState() {
const copy = {...this.state};
return {
...copy
}
},
getPrevState() {
const copy = {...this.prevState};
return {
...copy
}
},
mergeState(newValues={}) {
this.prevState = { ...this.state }
this.state = {
...this.state,
...newValues
};
},
mergeNodeInCollection(collectionName="", nodeId="", newValues={}) {
this.prevState = { ...this.state }
var newState = { ...this.state };
var collection = {...this.state[collectionName]}
var newNode = {
...collection[nodeId],
...newValues
};
collection[nodeId] = {...newNode}
newState[collectionName] = {...collection}
this.state = {
...newState
}
},
removeNodeInCollection(collectionName="", nodeId="") {
this.prevState = { ...this.state }
var newState = { ...this.state };
var collection = {...this.state[collectionName]}
delete collection[nodeId];
newState[collectionName] = {...collection}
this.state = {
...newState
}
}
};
const Positions = {
Stand: [0, 0],
StepOff: [32, 0],
Run1: [0, 32],
Run2: [32, 32],
Run3: [64, 32],
Jump: [0, 64],
//Left
Left_Stand: [0, 96],
Left_StepOff: [32, 96],
Left_Run1: [0, 128],
Left_Run2: [32, 128],
Left_Run3: [64, 128],
Left_Jump: [0, 162],
};
//Frame Arrays
const MegaManPoses = {
Stand: [Positions.Stand],
StepOff: [Positions.StepOff],
Run: [
Positions.Run1, Positions.Run2, Positions.Run3, Positions.Run2
],
Jump: [Positions.Jump],
//Left
Left_Stand: [Positions.Left_Stand],
Left_StepOff: [Positions.Left_StepOff],
Left_Run: [
Positions.Left_Run1, Positions.Left_Run2, Positions.Left_Run3, Positions.Left_Run2
],
Left_Jump: [Positions.Left_Jump],
}
function mergeState(newValues={}) {
Data.mergeState(newValues)
}
function isTouching(my,other) {
return my.x + my.width > other.x &&
my.x <= other.x + other.width &&
my.y + my.height > other.y &&
my.y <= other.y + other.height;
}
function getSolidSurface(my, others=[]) {
//Create 1 px sliver as my underline;
//const underlineModel = {
// height:1,
// width: my.width,
// y: my.y + my.height,
// x: my.x
//};
var touchingModel = null; //Assume False
others.forEach(otherModel => {
if (touchingModel) {
return; //Dont rerun if we already have a match
}
if (isTouching( my, otherModel)) {
touchingModel = {...otherModel};
}
});
return touchingModel;
}
function getSolidSurfaceDown(my, others=[]) {
//Create 1 px sliver as my underline;
const underlineModel = {
height:1,
width: my.width,
y: my.y + my.height,
x: my.x
};
var touchingModel = null; //Assume False
others.forEach(otherModel => {
if (touchingModel) {
return; //Dont rerun if we already have a match
}
if (isTouching( underlineModel, otherModel)) {
touchingModel = {...otherModel};
}
});
return touchingModel;
}
function drawCharacter(ctx, state, assets) {
const characterWidth = state.characterWidth;
const characterHeight = state.characterHeight;
ctx.beginPath();
/* Debug Rectangle */ /* This is the hitbox */
//ctx.fillStyle = "#fff";
//ctx.fillRect(
// state.characterX, state.characterY,
// characterWidth, characterHeight
//);
const currentPose = state.characterPose;
const activeFrame = currentPose[state.characterFrame] || currentPose[0];
ctx.drawImage(
assets.mm,
activeFrame[0], activeFrame[1], //Where in the spritesheet x/y
32,32,
state.characterX - 5, state.characterY - 4, //nudging where the drawing of the sprite is
32,32
);
}
function drawWalls(ctx, state) {
state.walls.forEach(wall => {
ctx.beginPath();
ctx.fillStyle = "#222";
ctx.fillRect(wall.x, wall.y, wall.width, wall.height);
});
}
function draw(canvas, ctx, state, assets) {
drawSky(ctx, state);
drawWalls(ctx, state);
drawCharacter(ctx, state, assets);
}
function drawSky(ctx, state) {
ctx.beginPath();
ctx.fillStyle = "#4AB5E2";
ctx.fillRect(0,0, state.canvasWidth, state.canvasHeight);
}
function runSteps(state, prevState, frameCount, dt) {
//bulletSteps(state);
playerMovement(state, prevState, frameCount, dt);
}
function runInits(state) {
/* Run all of the "kickoff" processses, like keyboard bindings and intervals */
bindKeyboardListeners();
}
function playerMovement(state, prevState, frameCount, dt) {
let isFacingLeft = state.isFacingLeft;
let nextCharacterX = state.characterX;
let nextCharacterY = state.characterY;
let inAir = state.inAir;
let isAbleToJump = state.isAbleToJump;
const downUnit = 5;
const nextDownFrame = {
x: nextCharacterX,
y: nextCharacterY + (downUnit),
width: state.characterWidth,
height: state.characterHeight
};
const surfaceCandidate = getSolidSurface(nextDownFrame, state.walls);
const surface = (surfaceCandidate && surfaceCandidate.y >= nextCharacterY) ? surfaceCandidate : null;
if (!surface) {
inAir = true;
nextCharacterY += downUnit
isAbleToJump = false;
}
if (surface) {
isAbleToJump = true;
}
if (surface && inAir) {
window.landingSfx.play();
inAir = false;
isAbleToJump = true;
//Correcting you
nextCharacterY = surface.y - state.characterHeight;
}
//Horizontal Movement
const xMovementUnit = Math.round(dt * 130);
if (state.isKeyboardLeftPressed) {
const leftUnit = xMovementUnit;
const nextLeftFrame = {
x: nextCharacterX - leftUnit,
y: nextCharacterY,
width: state.characterWidth,
height: state.characterHeight
};
const leftSurface = getSolidSurface(nextLeftFrame, state.walls);
if (!leftSurface) {
nextCharacterX -= leftUnit;
}
isFacingLeft = true;
}
if (state.isKeyboardRightPressed) {
const rightUnit = xMovementUnit;
const nextRightFrame = {
x: nextCharacterX + rightUnit,
y: nextCharacterY,
width: state.characterWidth,
height: state.characterHeight
};
const rightSurface = getSolidSurface(nextRightFrame, state.walls);
if (!rightSurface) {
nextCharacterX += rightUnit;
}
isFacingLeft = false;
}
///////////////////////////////////
let verticalBoost = state.verticalBoost;
/* VERTICAL BOOST */
if (verticalBoost < 0) {
const unit = 9;
const nextUpY = nextCharacterY -= unit;
//CHECK FOR CEILINGS
const nextUpFrame = {
x: nextCharacterX,
y: nextUpY,
width: state.characterWidth,
height: state.characterHeight
};
const surfaceUp = getSolidSurface(nextUpFrame, state.walls);
if (!surfaceUp) {
nextCharacterY = nextUpY;
//move boost back towards 0
verticalBoost = state.verticalBoost + unit;
} else {
verticalBoost = 0; //Kill the boost. Hit your head
}
}
////ANIMATION
//Change active frame
let nextFrame = state.characterFrame;
if (frameCount % 8 == 0) {
nextFrame = (nextFrame <= 2) ? nextFrame + 1 : 0;
}
//Revive!
if (nextCharacterY > 300 + 50) {
nextCharacterY = -50
}
/* Merge all state changes */
mergeState({
characterFrame: nextFrame,
characterPose: getCharacterPose({ //Sprite
...state,
inAir:inAir
}),
characterX: nextCharacterX,
characterY: nextCharacterY,
verticalBoost: verticalBoost,
isFacingLeft: isFacingLeft,
isAbleToJump: isAbleToJump,
inAir: inAir
});
}
function bindKeyboardListeners() {
var jumpSafe = true;
document.addEventListener('keydown', function (e) {
if (e.which == 37) {
mergeState({
isKeyboardLeftPressed: true
});
}
if (e.which == 39) {
mergeState({
isKeyboardRightPressed: true
});
}
//Jump!
if (e.which == 38) {
if ( Data.getState().isAbleToJump ) {
if (jumpSafe) {
jumpSafe = false;
mergeState({
isAbleToJump: false,
verticalBoost: -170
});
}
}
}
}, false);
document.addEventListener('keyup', function (e) {
if (e.which == 37) {
mergeState({
isKeyboardLeftPressed: false
});
}
if (e.which == 39) {
mergeState({
isKeyboardRightPressed: false
});
}
//Release Jump!
if (e.which == 38) {
jumpSafe = true;
mergeState({
verticalBoost: 0
});
}
}, false);
}
////////////////////////////////////////////////////
function getCharacterPose(state) {
const isLeft = state.isFacingLeft;
if (state.inAir) {
return isLeft ? MegaManPoses.Left_Jump : MegaManPoses.Jump;
}
if (state.isKeyboardLeftPressed || state.isKeyboardRightPressed) {
return isLeft ? MegaManPoses.Left_Run : MegaManPoses.Run;
}
return isLeft ? MegaManPoses.Left_Stand : MegaManPoses.Stand;
}
///////////////////////////////////////////////// now to use all this stuff!
//Cache references to canvas and context
var canvas = document.getElementById("js-canvas");
var ctx = canvas.getContext("2d");
//Init the app
Data.init(initialState, canvas, ctx);
//Set up assets
let assets = {
mm: new Image()
};
assets.mm.src = `https://s3-us-west-2.amazonaws.com/s.cdpn.io/21542/mm-blue-sprites.png`;
/* Draw Loop */
var frameCount=1;
var lastTime;
var step = function() {
var now = Date.now();
var dt = (now - lastTime) / 1000.0;
const state = Data.getState();
const prevState = Data.getPrevState();
//Draw the state
draw(canvas, ctx, state, assets);
//Run Steps - adjust state for next pass
runSteps(state, prevState, frameCount, dt);
//Track frame count for character animations
frameCount += 1;
if (frameCount > 64) { frameCount = 1}
lastTime = now;
requestAnimationFrame(step)
};
assets.mm.onload = function() {
const state = Data.getState();
/* Inits */
runInits(state);
requestAnimationFrame(step);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/howler/1.1.29/howler.min.js"></script>
* {
box-sizing: border-box;
}
body {
font-size: 16px;
padding: 3em 1em;
font-family: monospace;
}
p {
text-align: center;
}
canvas {
margin: 0 auto;
display: block;
border-radius: 3px;
;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment