Skip to content

Instantly share code, notes, and snippets.

Created December 16, 2013 22:41
Show Gist options
  • Save anonymous/7995772 to your computer and use it in GitHub Desktop.
Save anonymous/7995772 to your computer and use it in GitHub Desktop.
A Pen by Eric Brewer.
body
#wrapper
#game
###
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
~ Main game class
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
###
class Game
constructor:(@container, @tileSize, {viewWidth, viewHeight, scale, iso} = {}) ->
viewWidth ?= 600
viewHeight ?= 400
scale ?= 1
iso ?= false
@viewWidth = viewWidth
@viewHeight = viewHeight
@scale = scale
@iso = iso
run: ->
@setup()
@then = Date.now()
setInterval @tick, 30
setup: ->
@world = new World @container, @tileSize, @viewWidth, @viewHeight, @scale, @iso
@inputHandler = new InputHandler( @world )
update:(modifier) ->
@inputHandler.update(modifier)
@world.update()
tick: =>
now = Date.now()
delta = now - @lastUpdate
@lastUpdate = now
@lastElapsed = delta
@update(delta / 1000)
@world.draw()
###
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
~The World Class
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
###
class World
constructor: (@container, tileSize, @viewWidth, @viewHeight, @scale, @iso) ->
@debug = false
@tileWidth = if @iso then tileSize * 2 * @scale else tileSize * @scale
@tileHeight = tileSize * @scale
@ctx = @createCanvas()
@sprites = []
@scrollX = 0
@scrollY = 0
createCanvas: ->
container = document.getElementById @container
canvas = document.createElement('canvas')
canvas.setAttribute('id', 'eNgine_canvas')
canvas.width = @viewWidth
canvas.height = @viewHeight
container.appendChild canvas
@canvas = canvas
@canvas.getContext('2d')
createWorld: (@map) ->
###
create a collision map based on the map
switch all unwalkable tiles to 1s on cmap
for pathfinding
###
@cMap = @map
for row in @cMap
for tile in row
if row[tile] is not 0
row[tile] = 1
@createBackground( @map )
@createPFGrid( @cMap )
createBackground: (map) =>
@background = new Background this, map
createPFGrid: (cMap )->
@grid = new PF.Grid( cMap[0].length, cMap.length, cMap)
#@finder = new PF.AStarFinder()
@finder = new PF.BreadthFirstFinder()
update: (modifier) ->
sprite.update() for sprite in @sprites
draw: ->
@ctx.clearRect 0, 0, @viewWidth, @viewHeight
sprite.draw() for sprite in @sprites
###
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
~The Input Handler Class
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
###
class InputHandler
keysDown : {}
clicked: false
constructor: (@world) ->
@bindMouse()
bindMouse: =>
that = this
@world.canvas.addEventListener('mousemove', (evt)->
that.mousePos = that.getMousePos(evt)
)
getMousePos:(evt) ->
rect = @world.canvas.getBoundingClientRect()
{ x: evt.clientX - rect.left, y: evt.clientY - rect.top }
update: (modifier) ->
###
clear previous flags
###
for i in [0 .. @world.background.tiles.length]
if @world.background.tiles[i]
@world.background.tiles[i].clearFlags 'hover'
###
main input update
###
if @world.background and @world.background.ready and @mousePos and @mousePos.x > 0 and @mousePos.x < @world.viewWidth and @mousePos.y > 0 and @mousePos.y < @world.viewHeight
if @world.iso
tileX = Math.floor ( (@mousePos.x + @world.scrollX) / @world.tileWidth ) + ( (@mousePos.y + @world.scrollY) / @world.tileHeight) - .5
tileY = Math.floor ( (@mousePos.y + @world.scrollY) / @world.tileHeight ) - ( (@mousePos.x + @world.scrollX) / @world.tileWidth) + .5
else
tileX = Math.floor (@mousePos.x + @world.scrollX) / @world.tileWidth
tileY = Math.floor (@mousePos.y + @world.scrollY) / @world.tileHeight
if @world.background.mapTiles[tileY]
if @world.background.mapTiles[tileY][tileX]
theTile = @world.background.mapTiles[tileY][tileX]
if theTile.walkable
theTile.setFlags('hover')
if @clicked
for tile in @world.background.tiles
tile.clearFlags 'selected'
tile.clearFlags 'path'
#if @world.player.path
#for tile in @world.player.path
#@world.background.mapTiles[tile[1]][tile[0]].clearFlags 'path'
theTile.setFlags 'selected'
###
Pathfinding
###
pcoords = @world.player.getTile()
goalX = theTile.rX
goalY = theTile.rY
gridClone = @clone @world.grid
path = @world.finder.findPath pcoords.x, pcoords.y, tileX, tileY, gridClone
for tile in path
@world.background.mapTiles[tile[1]][tile[0]].setFlags 'path'
@world.background.mapTiles[ path[0][1] ][path[0][0]].clearFlags 'path'
@world.player.walkPath path
###
clear click
###
@clicked = false
clone: (obj) ->
if not obj? or typeof obj isnt 'object'
return obj
if obj instanceof Date
return new Date(obj.getTime())
if obj instanceof RegExp
flags = ''
flags += 'g' if obj.global?
flags += 'i' if obj.ignoreCase?
flags += 'm' if obj.multiline?
flags += 'y' if obj.sticky?
return new RegExp(obj.source, flags)
newInstance = new obj.constructor()
for key of obj
newInstance[key] = @clone obj[key]
newInstance
###
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
~The Sprite Class
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
###
class SpriteImage
ready: false
constructor:(@url = 'images/spritesheet.png', world, sprite) ->
image = new Image
#image = document.createElement('img')
image.src = @url
@image = image
@image.onload= =>
@ready = true
#if world.debug
#console.log 'created a ' + sprite
class Sprite
x: 0 #world-relative x
y: 0 #world-relative y
w: 0
h: 0
name: 'sprite'
collides: false
ready: false
image: false
@animationFrames = 1
constructor: (@world, url) ->
if url
@image = new SpriteImage url, @world, @name
console.log @world if @world.debug
@rX = @x
@rY = @y
@world.sprites.push this
setAnimationFrames: (frames) ->
@animationFrames = frames
update: (modifier) ->
@rX = @x + @world.scrollX
@rY = @y + @world.scrollY
draw: ->
if @image.ready
sx = ( @w * @frame )
sy = 0
@world.ctx.drawImage( @image.image, sx, sy, @w, @h, @rX , @rY , @w, @h )
drawFrame: (frame) ->
sx = 0 - ( @w * frame )
sy = 0
@world.ctx.drawImage( @image, sx, sy, @rX, @rY, @w, @h )
###
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
~The Background Class
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
###
class Background extends Sprite
constructor: (@world, map) ->
@x = 0
@y = 0
@w = 0
@h= 0
@rX = @x
@rY = @y
@tiles = []
@mapTiles =[]
@map = map
for rIndex, row of @map
@mapTiles[rIndex] = []
#TODO go backwards to render in sequence
for i in [row.length-1..0] by -1
cIndex = i
column = row[cIndex]
w= @world.tileWidth
h= @world.tileHeight
x = cIndex
y = rIndex
tileNo = [cIndex, rIndex]
walkable = true
if column != 0 then walkable = false
if @world.iso
isoCoords = @twoDToIso x, y, w, h
tile = new DrawnTile @world, isoCoords.x, isoCoords.y, @world.tileWidth, @world.tileHeight, column, tileNo, walkable
else
x = cIndex * w
y = rIndex * h
tile = new Tile @world, x, y, @world.tileWidth, @world.tileHeight, 'tile', tileNo, walkable
@tiles.push tile
@mapTiles[rIndex][cIndex] = tile
@ready = true
console.log @tiles if @world.debug
twoDToIso: (x, y, w, h)->
isoX = (x * w / 2) - (y * w / 2)
isoY = (y * h / 2) + (x * h / 2)
{x: isoX, y: isoY}
backgroundSize: ->
update: (modifier) ->
super
###
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
~The Tile Class
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
###
class Tile extends Sprite
constructor: (@world, @x, @y, @w, @h, @type = 0, @tileNo = [0, 0], @walkable=true)->
@name = 'tile'
image = @getType()
@color = 'rgba(0,0,255,' + (parseInt(@tileNo[0]) / 10) + ')'
@flags = {}
@tileFrame = @getTileFrame()
super @world, image
update: (modifier)->
@rX = @x - @world.scrollX
@rY = @y - @world.scrollY
###
different tiles
###
@frame = @tileFrame
if @flags.hover
@frame = 1
if @flags.selected
@frame = 2
if @flags.path
@frame = 3
draw: (frame = 0)->
#@world.ctx.fillStyle = '#d6abb1'
#@world.ctx.fillRect @x, @y, @w, @h
if @world.debug
@world.ctx.strokeStyle = @color
@world.ctx.strokeRect @rX, @rY, @w, @h
if @image.ready
sx = ( @w * @frame )
sy = 0
@world.ctx.drawImage( @image.image, sx, sy, @w, @h, @rX , @rY, @w, @h )
setFlags: (flag)->
@flags[flag] = true
clearFlags: (flag)->
@flags[flag] = false
getTileFrame: ->
tileFrames=
0: 0,
1: 0,
2: 4,
3: 5,
4: 6,
5: 7
tileFrames[@type]
getType: ->
tileTypes=
'tile': 'images/gameTile.png',
'isoTile': 'images/gameTileIso.png',
type = if @world.iso then 'isoTile' else 'tile'
tileTypes[type]
class DrawnTile extends Tile
colour: ''
constructor: (@world, @x, @y, @w, @h, @type = 0, @tileNo = [0, 0], @walkable=true) ->
@defaultColour = '#d6abb1'
if @type is 1 then @defaultColour = '#d9d9d9'
super
update: (modifier)->
@rX = @x - @world.scrollX
@rY = @y - @world.scrollY
@colour = @defaultColour
if @flags.path
@colour = @getColour 'path'
if @flags.hover
@colour = @getColour 'hover'
if @flags.selected
@colour = @getColour 'selected'
draw: ->
@world.ctx.fillStyle = @colour
@world.ctx.beginPath()
@world.ctx.moveTo @rX + (@world.tileWidth / 2), @rY
@world.ctx.lineTo @rX + @world.tileWidth, @rY + (@world.tileHeight / 2)
@world.ctx.lineTo @rX + (@world.tileWidth / 2), @rY + @world.tileHeight
@world.ctx.lineTo @rX, @rY + (@world.tileHeight / 2)
@world.ctx.closePath()
@world.ctx.fill()
getColour: (flag) ->
colours=
hover: '#cd8791',
selected: 'white',
path: '#de98a2'
colours[flag]
###
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
~The Entity Class
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
###
class Entity extends Sprite
speed:0
vx: 0
vy:0
constructor: (@world, @x, @y, @w, @h, image, @speed) ->
@frame = 0
@w *= @world.scale
@h *= @world.scale
@speedX *= @world.scale
@speedY *= @world.scale
console.log 'created a ' + @name
super @world, image
update: (modifier) ->
@vx = 0 unless @nextX != @x
@vy = 0 unless @nextY != @y
if @vx == 0 and @vy == 0
if @path and @path[2]
@path.shift()
@walkPath @path
@x += @vx
@y += @vy
@rX = @x - @world.scrollX
@rY = @y - @world.scrollY
draw: ->
if @image.ready
sx = ( @w * @frame )
sy = 0
@world.ctx.drawImage( @image.image, sx, sy, @w, @h, @rX - (@w - @world.tileWidth), @rY - (@h - @world.tileHeight), @w * @world.scale, @h*@world.scale )
twoDToIso: (x, y, w, h)->
tileX = Math.floor ( (@rX + @world.scrollX) / @world.tileWidth ) + ( (@rY + @world.scrollY) / @world.tileHeight)
tileY = Math.floor ( (@rY + @world.scrollY) / @world.tileHeight ) - ( (@rX + @world.scrollX) / @world.tileWidth)
{x: tileX, y:tileY}
getTile:(x = @rX, y=@rY) =>
if @world.iso
matrix = @twoDToIso x, y, @world.tileWidth, @world.tileHeight
else
px = Math.floor @rX / @world.tileWidth
py = Math.floor @rY / @world.tileHeight
matrix = {x: Math.floor(@x / @world.tileWidth), y: Math.floor (@y / @world.tileHeight)}
matrix
walkPath: (path) ->
@path = path
if @path[1]
@nextStep = @path[1]
@nextX = @world.background.mapTiles[@nextStep[1]][@nextStep[0]].x
@nextY = @world.background.mapTiles[@nextStep[1]][@nextStep[0]].y
deltaX = @nextX - @x
deltaY = @nextY - @y
if deltaX != 0
if deltaX > 0
#move right
@vx = @speedX
if deltaX < 0
#move left
@vx = -@speedX
if deltaY != 0
if deltaY > 0
#move down
@vy = @speedY
if deltaY < 0
#move up
@vy = -@speedY
else
false
###
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
~The Player Class
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
###
class Player extends Entity
name: 'Player'
constructor: ->
@speedX = 3
@speedY = 3
super
if @world.iso
@speedX *= 2
@world.player = this
update:(modifier) ->
super
draw: ->
super
if @world.debug
@world.ctx.strokeStyle = 'black'
@world.ctx.strokeRect @rX , @rY , @w, @h
class DrawnPlayer extends Player
constructor: ->
@colourTop = '#c4ffeb'
@colourRight = '#9be1c9'
@colourLeft = '#80b9a6'
super
draw: ->
@world.ctx.fillStyle = @colourLeft
@world.ctx.beginPath()
@world.ctx.moveTo @rX, @rY + @world.tileHeight / 2
@world.ctx.lineTo @rX, @rY - @world.tileHeight / 2
@world.ctx.lineTo @rX + (@world.tileWidth / 2), @rY - (@world.tileHeight)
@world.ctx.lineTo @rX + (@world.tileWidth / 2), @rY + @world.tileHeight
@world.ctx.closePath()
@world.ctx.fill()
@world.ctx.fillStyle = @colourRight
@world.ctx.beginPath()
@world.ctx.moveTo @rX + @w, @rY + @world.tileHeight / 2
@world.ctx.lineTo @rX + @w, @rY - @world.tileHeight / 2
@world.ctx.lineTo @rX + (@world.tileWidth / 2), @rY - (@world.tileHeight)
@world.ctx.lineTo @rX + (@world.tileWidth / 2), @rY + @world.tileHeight
@world.ctx.closePath()
@world.ctx.fill()
@world.ctx.fillStyle = @colourTop
@world.ctx.beginPath()
@world.ctx.moveTo @rX + @w, @rY - @world.tileHeight / 2
@world.ctx.lineTo @rX + (@world.tileWidth / 2), @rY - (@world.tileHeight)
@world.ctx.lineTo @rX, @rY - @world.tileHeight / 2
@world.ctx.lineTo @rX + (@world.tileWidth / 2), @rY
@world.ctx.closePath()
@world.ctx.fill()
if @world.debug
@strokeStyle = 'black'
@world.ctx.strokeRect(@rX, @rY, @w, @h)
@strokeStyle = 'blue'
@world.ctx.strokeRect @rX, @rY, 4, 4
###
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
~The helpers
~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~
getTwoDPoint = (world, x, y)->
px = Math.floor x / world.tileWidth
py = Math.floor y / world.tileHeight
getIsoPoint = (world, x, y) ->
px = 'howdy'
###
class Clone
constructor:(obj) ->
if not obj? or typeof obj isnt 'object'
return obj
if obj instanceof Date
return new Date(obj.getTime())
if obj instanceof RegExp
flags = ''
flags += 'g' if obj.global?
flags += 'i' if obj.ignoreCase?
flags += 'm' if obj.multiline?
flags += 'y' if obj.sticky?
return new RegExp(obj.source, flags)
newInstance = new obj.constructor()
for key of obj
newInstance[key] = @clone obj[key]
newInstance
###
START THE GAME!
###
map2 = [
[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,1,1,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,1,1,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,1,1,0,1],
[1,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,1,1,0,1],
[1,0,0,0,0,0,1,1,1,0,0,0,1,0,0,0,1,0,0,1,1,0,1],
[1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1],
[1,0,0,0,0,0,1,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,1,1,1,0,0,0,1,1,1,1,1,1,0,0,0,0,1],
[1,0,0,0,0,0,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,0,1],
[1,0,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,1,1,1,0,1],
[1,0,0,0,0,0,1,1,1,0,0,0,0,1,1,1,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],
]
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,1,1,1,1,1,1,1,1,0,1,0,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,1],
[1,1,1,1,0,0,0,0,1,1,1,1,1,0,1,0,0,0,0,1],
[1,1,1,1,1,1,1,0,1,1,1,1,1,0,0,0,0,0,0,1],
[1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,0,1],
[1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,1,0,1],
[1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,0,0,1],
[1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,0,1,1],
[1,1,1,1,1,0,1,1,1,1,1,0,0,0,0,0,0,0,1,1],
[1,0,0,0,1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1],
[1,0,0,0,0,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1],
[1,0,0,0,1,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,1,1,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
]
window.onload = ->
game = new Game 'game', 30, scale:.5
game.iso = true
game.run()
game.world.createWorld map
game.world.scrollX = -250
#game.world.debug = true
###
bind mousey
###
$('canvas').on('click', ()->
game.inputHandler.clicked = true
)
pX = game.world.background.mapTiles[2][17].rX
pY = game.world.background.mapTiles[2][17].rY
player = new DrawnPlayer game.world,pX, pY, 60, 60, 'images/player.png', 10
#for the demo
###
thePath = [[8,7],[7,7],[6,7],[6,6],[6,5], [5,5], [4,5], [4, 4], [4, 3]]
game.world.background.mapTiles[3][4].setFlags 'selected'
###
#whoodle demo
thePath = [[17,2], [16,2], [16,3], [16,4], [15,4], [14,4], [13,4], [13,3], [13,2]]
game.world.background.mapTiles[2][13].setFlags 'selected'
for tile in thePath
game.world.background.mapTiles[tile[1]][tile[0]].setFlags 'path'
player.walkPath thePath
@import "compass";
.clearfix {
zoom: 1;
&:before, &:after { content: ""; display: table; }
&:after { clear: both; }
}
@mixin css-gradient($from: #dfdfdf, $to: #f8f8f8) {
background-color: $to;
background-image: -webkit-gradient(linear, left top, left bottom, from($from), to($to));
background-image: -webkit-linear-gradient(top, $from, $to);
background-image: -moz-linear-gradient(top, $from, $to);
background-image: -o-linear-gradient(top, $from, $to);
background-image: linear-gradient(to bottom, $from, $to);
}
@mixin box-sizing($type: border-box) {
-webkit-box-sizing: $type;
-moz-box-sizing: $type;
-ms-box-sizing: $type;
box-sizing: $type;
}
@mixin prefix($prop, $value){
-webkit-#{$prop}: $value; -moz-#{$prop}: $value; -o-#{$prop}: $value; -ms-#{$prop}: $value;
}
@mixin listfix(){
display:block; padding:0; margin:0 0 0; list-style:none;
}
//colours
$black: #312736;
$red: #d4838f;
$greyred: #d6abb1;
$grey: #d9d9d9;
$blue: #c4ffeb;
* {box-sizing:border-box; }
body{
display:block;
background:$grey;
}
#wrapper {
display:block;
width:90%;
height:auto;
margin:0 auto;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment