Skip to content

Instantly share code, notes, and snippets.

@topokel
Last active August 29, 2015 14:03
Show Gist options
  • Select an option

  • Save topokel/cca025b413c8300d6102 to your computer and use it in GitHub Desktop.

Select an option

Save topokel/cca025b413c8300d6102 to your computer and use it in GitHub Desktop.

to build:

git clone https://gist.github.com/cca025b413c8300d6102.git && cd cca025b413c8300d6102
npm install crtrdg-gameloop crtrdg-keyboard matrix-utilities howler
browserify game.js -o o.js

Then open index.html in your browser.

var Howler = require('howler')
, howl = Howler.Howl
, howler = Howler.Howler
, music = load([0,1,2,3,4,5,6,7,8,9], 'breaks', this.play)
exports = module.exports = load({click:null,enable:null,disable:null}, 'effects')
exports.mute = function(){howler.mute()}
exports.unmute = function(){howler.unmute()}
exports.stop = function(){music.map(function(sound){sound.stop()})}
exports.play = function(){music[Math.floor(Math.random() * music.length)].play()}
// takes group of sound file names, a folder prefix, and a callback
// to run when that sound's finished playing and returns a group of
// sound objects
function load(sounds, pre, cb) {
if (sounds instanceof Array)
return sounds.map(function(sound){
return new howl({
urls: [
'./' + pre + '/' + sound + '.ogg'
, './' + pre + '/' + sound + '.mp3'
, './' + pre + '/' + sound + '.wav'
]
, onend: cb
})
})
else
for (var sound in sounds)
if(sounds.hasOwnProperty(sound))
sounds[sound] = new howl({ urls: [
'./' + pre + '/' + sound + '.ogg'
, './' + pre + '/' + sound + '.mp3'
, './' + pre + '/' + sound + '.wav'
] })
return sounds
}
var Game = require('crtrdg-gameloop')
, Key = require('crtrdg-keyboard')
, scene = require('./scene')
, audio = require('./audio')
, game = new Game()
, key = new Key(game)
, STATE = { MENU: 'm', PLAY: 'p' }
, state = STATE.MENU
// these must be unfolded with intermediate functions
// because if they are reduced to game.on('update', scene[state].update)
// they wouldn't switch states, the state would be looked up only once.
game.on('update', function(interval) { scene[state].update(interval) })
game.on('draw', function(context) { scene[state].draw(context) })
// emit keydown events to current scene
key.on('keydown', function(k) {
if (k === '<escape>') return scene[state].esc(scene[state])
if (k === '<enter>' || k === '<space>') return scene[state].enter(scene[state])
if (k === '<up>' || k === 'w') return scene[state].up(scene[state])
if (k === '<left>' || k === 'a') return scene[state].left(scene[state])
if (k === '<down>' || k === 's') return scene[state].down(scene[state])
if (k === '<right>' || k === 'd') return scene[state].right(scene[state])
})
scene.create({
name: STATE.MENU
, font: game.width/12 + 'px VT323'
, data: { selected: 2 }
, enter: function (stage) {
if (stage.selected === 2) {
state = STATE.PLAY
audio.play()
} else if (stage.entities[3].enabled) {
stage.entities[3].enabled = false
stage.entities[3].fill = 'rgb(255,204,0)'
audio.mute()
} else {
stage.entities[3].enabled = true
stage.entities[3].fill = 'rgb(52,255,0)'
audio.unmute()
audio.enable.play()
}
}
, up: function (stage) {
if (stage.selected === 2) {
audio.disable.play()
} else {
audio.click.play()
stage.selected = 2
stage.entities[2].fill = 'rgb(255,255,255)'
stage.entities[3].fill = stage.entities[3].enabled
? 'rgba(52,255,0,.6)' : 'rgba(255,204,0,.6)'
}
}
, down: function (stage) {
if (stage.selected === 3) {
audio.disable.play()
} else {
audio.click.play()
stage.selected = 3
stage.entities[2].fill = 'rgba(255,255,255,.2)'
stage.entities[3].fill = stage.entities[3].enabled
? 'rgb(52,255,0)' : 'rgb(255,204,0)'
}
}
, entities: [
{ verts: [[10,10],[game.width - 10,10],[game.width - 10,game.height - 10],[10,game.height - 10]]
, fill: '#f36'
}
, { text: 'ga_madddd33_egf'
, fill: '#fff'
, pos: [game.width * .1, game.height * .2]
, max: game.width * .8
}
, { text: 'play hte g__+='
, fill: 'rgb(255,255,255)'
, pos: [game.width * .1, game.height * .2 + game.width/6]
, max: game.width * .8
}
, { text: 'mute???me'
, fill: 'rgba(52,255,0,.6)'
, pos: [game.width * .1, game.height * .2 + game.width/4]
, max: game.width * .8
, enabled: true
}
]
})
scene.create({
name: STATE.PLAY
, esc: function () { state = STATE.MENU; audio.stop() }
})
<!DOCTYPE html><html><head>
<title>ga_madddd33_egf</title><meta charset="utf-8"/>
<style>@font-face{font-family:'VT323';src:local('VT323'),local('VT323-Regular'),url(http://fonts.gstatic.com/s/vt323/v6/LfMzj2MWAZU6qzlnp1MNbg.woff)
format('woff');}html,body,canvas,noscript{font-family:'VT323',monospace;display:block;position:absolute;top:0;left:0;width:100%;height:100%;margin:0;color:#fff;}html{background:#f36;font-size:36px;}noscript{box-sizing:border-box;padding:10%;}*{font-weight:400;}</style>
</head><body>
<noscript><h1>ga_madddd33_egf</h1><p>if you don't enable javascript this is fucked</p></noscript>
<script src="o.js"></script>
</body></html>
var Matrix = require('matrix-utilities')
/* scene.create(options: {
* // name of scene; scene may be accessed via exports[name]
* // required, must be unique
* name: 'funtime'
*
* // optional object that gets appended to exports[name]
* , data: { whatever: 'you want' }
*
* // view and world entities
* // optional, set to pos:0,0,0 rot:0,0,0 by default
* , view: { pos: [x,y,z], rot: [x,y,z] }
* , world: { pos: [x,y,z], rot: [x,y,z] }
*
* // the entities drawn to 2d canvas by default draw method
* // drawn from 0 to entities.length ordering (bottom to top)
* // optional
* , entities: [
* // a 3d entity
* {
* verts: [[x,y,z]...]
* , fill: '#c01045' // optional
* , stroke: '#420666' // optional
* }
*
* // a 2d entity
* , {
* verts: [[x,y]...] // screen space coordinates
* , fill: 'blue' // optional
* , stroke: '#f00' // optional
* }
*
* // a text entity
* , {
* text: 'the text for the entity'
* , pos: [x,y]
* , max: 130 // max width in pixels
* , font: '12px serif' // optional
* , fill: '#aaaaaa' // optional
* , stroke: '#800813' // optional
* }
* ]
*
* // default fill, stroke and font for entities, when not provided
* // optional; if fill and stroke are set, will fill and stroke for ALL elements
* , fill: '#123456'
* , stroke: 'black'
* , font: '69px comicsans'
*
* // game logic update function
* // optional, doesn't emit by default
* , update: function () {}
*
* // drawing function
* // if you override this default, entities don't render automatically
* // optional, doesn't emit by default
* , draw: function () {}
*
* // utility functions I use for keyboard events
* // optional, do not emit by default
* , enter: function () {}
* , esc: function () {}
* , up: function () {}
* , down: function () {}
* , right: function () {}
* , left: function () {}
* })
*
*/
exports.create = function (opts) {
if (!opts || opts === undefined || opts.name === undefined)
return console.log('Please supply a name for this scene!')
if (exports[opts.name] !== undefined)
return console.log('Please select a different name for scene ' + opts.name)
opts.view = opts.view !== undefined ? opts.view : { pos: [0,0,0], rot: [0,0,0] }
opts.view.pos = opts.view.pos !== undefined ? opts.view.pos : [0,0,0]
opts.view.rot = opts.view.rot !== undefined ? opts.view.rot : [0,0,0]
opts.view.mat = updateView(opts.view)
opts.world = opts.world !== undefined ? opts.world : { pos: [0,0,0], rot: [0,0,0]}
opts.world.pos = opts.world.pos !== undefined ? opts.world.pos : [0,0,0]
opts.world.rot = opts.world.rot !== undefined ? opts.world.rot : [0,0,0]
opts.world.mat = updateWorld(opts.world)
exports[opts.name] = {
// game entities
view: opts.view
, world: opts.world
// used by internal default draw to cache/check changes in matrix state
, _view: opts.view
, _world: opts.world
, _global: Matrix.multiply(opts.world.mat, opts.view.mat)
// array of 3d and 2d polys to render
// only automatically used in default draw function
// warning: does not do culling, only put things that you plan to
// draw into the entities array
, entities: opts.entities !== undefined ? opts.entities : []
// game loop event functions
, update: opts.update !== undefined ? opts.update : function () {}
, draw: opts.draw !== undefined ? opts.draw : function (context) {
// check if view or world matrix has changed, if so update global matrix
if (this._view !== this.view || this._world !== this.world) {
this.view.mat = this._view === this.view ? this.view : updateView(this.view)
this._view = this.view
this.world.mat = this._world === this.world ? this.world : updateWorld(this.world)
this._world = this.world
this._global = Matrix.multiply(this.world.mat, this.view.mat)
}
this._width = context.canvas.width / 2
this._height = context.canvas.height / 2
// render all entities.
this.entities.map(function (entity) {
context.font = entity.font !== undefined ? entity.font : this.font
context.fillStyle = entity.fill !== undefined ? entity.fill : this.fill
context.strokeStyle = entity.stroke !== undefined ? entity.stroke : this.stroke
if (entity.verts !== undefined) {
context.beginPath()
// entity is already 2d poly, draw like normal poly
if (entity.verts[0].length === 2) {
context.moveTo(entity.verts[0][0], entity.verts[0][1])
entity.verts.map(function (vertex) { context.lineTo(vertex[0],vertex[1]) })
}
// entity must be projected to 2d
else if (entity.verts[0].length === 3) entity.verts.map(function (vertex, index) {
// product of global matrix
vertex = Matrix.multiply([[vertex[0],vertex[1],vertex[2],1]], this._global)
// simple depth (divide depth into x/y) manip, then center elem on view
vertex[0][0] = (vertex[0][0] / vertex[0][2]) * this._width + this._width
vertex[0][1] = (vertex[0][1] / vertex[0][2]) * this._height + this._height
// if this is the first vertex, move context drawing to current position
if (index === 0) context.moveTo(vertex[0][0], vertex[0][1])
// move polydraw to next vertex position
context.lineTo(vertex[0][0], vertex[0][1])
})
context.closePath()
} else if (entity.text !== undefined) {
// put the text on [0,0] if we're not told where to put it
entity.pos = entity.pos !== undefined ? entity.pos : [0,0]
context.fillText(entity.text, entity.pos[0], entity.pos[1], entity.max)
} else { return }
if (entity.fill !== undefined || this.fill !== undefined) context.fill()
if (entity.stroke !== undefined || this.stroke !== undefined) context.stroke()
}, this)
}
, fill: opts.fill // warning! will set auto fill for ALL entities
, stroke: opts.stroke // ditto!
, font: opts.font !== undefined ? opts.font : '12px serif'
// keyboard event functions
, enter: opts.enter !== undefined ? opts.enter : function () {}
, esc: opts.esc !== undefined ? opts.esc : function () {}
, up: opts.up !== undefined ? opts.up : function () {}
, down: opts.down !== undefined ? opts.down : function () {}
, left: opts.left !== undefined ? opts.left : function () {}
, right : opts.right !== undefined ? opts.right : function () {}
}
if (opts.data !== undefined)
for (var data in opts.data)
if (opts.data.hasOwnProperty(data) && exports[opts.name][data] === undefined)
exports[opts.name][data] = opts.data[data]
}
function updateView (view) {
// get the sine and cosine of the negative of all angles
var sin = [Math.sin(-view.rot[0]), Math.sin(-view.rot[1]), Math.sin(-view.rot[2])]
, cos = [Math.cos(-view.rot[0]), Math.cos(-view.rot[1]), Math.cos(-view.rot[2])]
// view matrix * translation matrix * rotz matrix * roty matrix * rotx matrix
return Matrix.multiply(Matrix.multiply(Matrix.multiply(Matrix.multiply(Matrix.Identity(),[
[ 1 , 0 , 0 ,0]
, [ 0 , 1 , 0 ,0]
, [ 0 , 0 , 1 ,0]
, [-view.pos[0],-view.pos[1],-view.pos[2],1]
]), [
[ cos[2],sin[2],0,0]
, [-sin[2],cos[2],0,0]
, [ 0 , 0 ,1,0]
, [ 0 , 0 ,0,1]
]), [
[cos[1],0,-sin[1],0]
, [ 0 ,1, 0 ,0]
, [sin[1],0, cos[1],0]
, [ 0 ,0, 0 ,1]
]), [
[1, 0 , 0 ,0]
, [0, cos[0],sin[0],0]
, [0,-sin[0],cos[0],0]
, [0, 0 , 0 ,1]
])
}
function updateWorld (world) {
// get the sine and cosine of all angles
var sin = [Math.sin(world.rot[0]), Math.sin(world.rot[1]), Math.sin(world.rot[2])]
, cos = [Math.cos(world.rot[0]), Math.cos(world.rot[1]), Math.cos(world.rot[2])]
// world matrix * rotx matrix * roty matrix * rotz matrix * translation matrix
return Matrix.multiply(Matrix.multiply(Matrix.multiply(Matrix.multiply(Matrix.Identity(),[
[1, 0 , 0 ,0]
, [0, cos[0],sin[0],0]
, [0,-sin[0],cos[0],0]
, [0, 0 , 0 ,1]
]), [
[cos[1],0,-sin[1],0]
, [ 0 ,1, 0 ,0]
, [sin[1],0, cos[1],0]
, [ 0 ,0, 0 ,1]
]), [
[ cos[2],sin[2],0,0]
, [-sin[2],cos[2],0,0]
, [ 0 , 0 ,1,0]
, [ 0 , 0 ,0,1]
]), [
[ 1 , 0 , 0 ,0]
, [ 0 , 1 , 0 ,0]
, [ 0 , 0 , 1 ,0]
, [world.pos[0],world.pos[1],world.pos[2],1]
])
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment