-
-
Save mbutler/1919621 to your computer and use it in GitHub Desktop.
Scrollable hex map demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* A scrollable hexagonal map with dynamic loading and unloading of tiles, implemented using JQuery and CraftyJS. | |
* | |
* by Lynna Merrill, http://www.lynnamerrill.com | |
* | |
* Credit for some ideas and/or code snippets goes to the isometric map implementation at http://craftyjs.com/demos/isometric | |
and the game tutorial at https://github.com/starmelt/craftyjstut/wiki/A-first-game. | |
* | |
* All graphics used for this demo are copyrighted and cannot be distributed or reproduced in any way without permission. You may download the graphics if you want to test this demo on your own computer, but please use your own images for any posts or projects of yours. | |
* | |
* 6 December 2011 | |
* | |
*/ | |
window.onload = (function(){ | |
Crafty.init(600, 400); | |
var drawWay = "DOM"; | |
//loading separate images in this demo; use a real spritemap for production | |
Crafty.sprite(96, "http://lynnamerrill.com/wp-content/uploads/2011/12/1.png", { | |
terrain1: [0,0, 1, 1.1] //mountain | |
}); | |
Crafty.sprite(96, "http://lynnamerrill.com/wp-content/uploads/2011/12/2.png", { | |
terrain2: [0,0, 1, 1.1] //hill | |
}); | |
Crafty.sprite(96, "http://lynnamerrill.com/wp-content/uploads/2011/12/3.png", { | |
terrain3: [0,0, 1, 1.1] //grass | |
}); | |
Crafty.sprite(96, "http://lynnamerrill.com/wp-content/uploads/2011/12/4.png", { | |
terrain4: [0,0, 1, 1.1] //water | |
}); | |
Crafty.sprite(96, "http://lynnamerrill.com/wp-content/uploads/2011/12/forest-1.png", { | |
forest1: [0,0, 1, 1.1] //mountain forest | |
}); | |
Crafty.sprite(96, "http://lynnamerrill.com/wp-content/uploads/2011/12/forest-2.png", { | |
forest2: [0,0, 1, 1.1] //hill forest | |
}); | |
Crafty.sprite(96, "http://lynnamerrill.com/wp-content/uploads/2011/12/forest-3.png", { | |
forest3: [0,0, 1, 1.1] //plains forest | |
}); | |
Crafty.sprite(96, "http://lynnamerrill.com/wp-content/uploads/2011/12/woodcuttersShack96.png", { | |
shack: [0,0] | |
}); | |
Crafty.sprite(96, "http://lynnamerrill.com/wp-content/uploads/2011/12/stoneQuarry96.png", { | |
quarry: [0,0] | |
}); | |
var tileWidth=94; //value is smaller than real image width because of drawing gaps; tweak for your own images | |
var tileHeight=104; //same as above | |
var rowLimit = 9; | |
var colLimit = 9; | |
//start map drawing with a tile of which only a piece is visible in the upper left corner | |
var i = -2*tileWidth - tileWidth/2; | |
var j = -2*tileHeight - 3/4*tileHeight; | |
for (var rowCount=1; rowCount<=rowLimit; rowCount++){ | |
if (rowCount%2==0) {//row number is even | |
i = -2*tileWidth; //tile doesn't protrude to the left of visible space | |
} | |
else if (rowCount!=1){ //row number is odd but is not the first row | |
i = -2*tileWidth - tileWidth/2; //tile protrudes to the left | |
} | |
for (var colCount=1; colCount<=colLimit; colCount++){ | |
drawTile (i, j); | |
i=i+tileWidth; | |
} | |
j = j + 3/4*tileHeight; | |
//get first row down | |
} | |
//draw a single tile, with terrain, rivers, etc. | |
function drawTile (tileX, tileY){ | |
//Generate random terrain information for the hexagonal to be drawn | |
terrainType = Crafty.randRange(1,4); | |
theTile = Crafty.e("2D,"+drawWay+", Mouse, terrain"+terrainType).attr({ | |
x: tileX, | |
y: tileY, | |
h:tileHeight+2, | |
w:tileWidth+2 //makes it 96px, the real tile width | |
}) | |
.bind("Click", function(e) { | |
//sample event handling; upon click draw a quarry on the respective terrain tile (you need to use right-click for the terrain tiles with forests or shacks) | |
this.attach(Crafty.e("2D,"+drawWay+", Mouse, "+"quarry").attr({ | |
x: tileX + 10, | |
y: tileY + 30, | |
z: 2//, | |
}))} | |
); | |
if (colCount==1){ //this is a first-in-a-row tile | |
theTile.addComponent("leftTile"); | |
} | |
if (colCount==colLimit){ | |
theTile.addComponent("rightTile"); | |
} | |
if (rowCount==1){ | |
theTile.addComponent("topTile") | |
} | |
if (rowCount==rowLimit){ | |
theTile.addComponent("bottomTile"); | |
} | |
forest = Crafty.randRange(0,1); //randomly determine if there is a forest on the tile or not | |
if (forest==1){ | |
drawTileElement("forest"+terrainType, 1, 0, 0); | |
} | |
shack = Crafty.randRange(0,1); | |
if (shack==1 && terrainType!=4){ //don't draw shacks on water tiles | |
drawTileElement("shack", 2, 0, 0); | |
} | |
function drawTileElement (sprite, zIndex, xAdjust, yAdjust){ | |
theTile.attach(Crafty.e("2D,"+drawWay+", Mouse, "+sprite).attr({ | |
x: i+xAdjust, | |
y: j+yAdjust, | |
z: zIndex//, | |
}).bind("Click", function(e) { | |
//sample event handling: upon right click initiate the "Click" event handler of parent tile (the terrain tile, in this case) | |
if(e.button == 2) { | |
this._parent.trigger("Click", e); | |
} | |
})); | |
} | |
} | |
//Start code for dynamic tile loading upon pan | |
var dxCom = 0; | |
var dyCom = 0; | |
var topPNum = 1; //used for determining whether to shift new rows left or right | |
var bottomPNum = 1; //used for determining whether to shift new rows up or down | |
Crafty.addEvent(this, Crafty.stage.elem, "mousedown", function(e) { | |
if(e.button > 1) return; | |
var base = { | |
x: e.clientX, | |
y: e.clientY | |
}; | |
function scroll(e) { | |
//NOTE: Currently, panning is implemented so that the viewport is not shifted the exact number of pixels the user moves the mouse, but is shifted with 8px with every move. | |
var dx = base.x - e.clientX; | |
var shiftX = 0; | |
if (dx>0){ | |
shiftX = 8; | |
} | |
else if (dx<0){ | |
shiftX = -8; | |
} | |
dxCom += shiftX; | |
var dy = base.y - e.clientY; | |
var shiftY = 0; | |
if (dy>0){ | |
shiftY = 8; | |
} | |
else if (dy<0){ | |
shiftY = -8; | |
} | |
dyCom += shiftY; | |
base = { | |
x: e.clientX, | |
y: e.clientY | |
}; | |
//used for automatic tile loading during drag and drop | |
var theXFormula; | |
var theYFormula; | |
panMap(); | |
if (dxCom>=tileWidth){ //whole map has moved to the left, load respective number of tiles to the right, unload them from the left | |
theXFormula = function (theX, theK){ | |
return theX+theK*tileWidth; | |
}; | |
theYFormula = function (theY, theK){ | |
return theY; | |
} | |
//just one tile loaded and unloaded at a time | |
panningCare ("rightTile", "leftTile", 1, theXFormula, theYFormula); | |
dxCom = 0; | |
} | |
else if (dxCom<=(-1*tileWidth)){ //map moves to the right (view moves to the left); load to the left, unload from the right | |
theXFormula = function (theX, theK){ | |
return theX-theK*tileWidth; | |
}; | |
theYFormula = function (theY, theK){ | |
return theY; | |
}; | |
panningCare ("leftTile", "rightTile", 1, theXFormula, theYFormula); | |
dxCom = 0; | |
} | |
if (dyCom>=3/4*tileHeight){ //whole map moves up, load tiles down, unload up | |
// NOTE: the calculations below assume always an odd number of rows; make sure # is always odd | |
theXFormula = function (theX, theK){ | |
if (bottomPNum%2==0){ //even row to draw below current first, shift to left | |
return theX-1/2*tileWidth; | |
} | |
else { //odd row to draw below current first (counting down, starting at 1), shift to right | |
return theX+1/2*tileWidth; | |
} | |
} | |
theYFormula = function(theY, theK){ | |
return theY+theK*(3/4*tileHeight); | |
} | |
panningCare ("bottomTile", "topTile", 1, theXFormula, theYFormula); | |
dyCom = 0; | |
bottomPNum++; | |
topPNum--; | |
} | |
else if (dyCom<=(-3/4*tileHeight)){ //whole map moves down, load tiles up, unload down | |
theXFormula = function (theX, theK){ | |
if (topPNum%2==0){ //even row to draw above current first, shift to left | |
return theX-1/2*tileWidth; | |
} | |
else { //odd row to draw above current first (counting up, starting at 1), shift to right | |
return theX+1/2*tileWidth; | |
} | |
} | |
theYFormula = function(theY, theK){ | |
return theY-theK*(3/4*tileHeight); | |
} | |
panningCare ("topTile", "bottomTile", 1, theXFormula, theYFormula); | |
dyCom = 0; | |
topPNum++; //loading | |
bottomPNum--; //unloading | |
} | |
//end of code for dynamic loading of tiles upon map panning | |
function panMap(){ | |
//the panning functionality itself | |
Crafty.viewport.x -=shiftX; | |
Crafty.viewport.y -= shiftY; | |
} | |
}; | |
Crafty.addEvent(this, Crafty.stage.elem, "mousemove", scroll); | |
Crafty.addEvent(this, Crafty.stage.elem, "mouseup", function() { | |
Crafty.removeEvent(this, Crafty.stage.elem, "mousemove", scroll); | |
}); | |
}); | |
//help functions for dynamic loading of tiles upon pan | |
function panningCare(tileLabel, tileUnloadLabel, numToDraw, theXFormula, theYFormula){ | |
Crafty(tileLabel).each(function(){ | |
for (var k=1; k<=numToDraw; k++){ | |
neighbor = drawNeighbor(theXFormula(this._x, k), theYFormula(this._y, k)); | |
if (k==numToDraw){ | |
neighbor.addComponent(tileLabel); | |
} | |
if (tileLabel=="leftTile" || tileLabel=="rightTile"){ | |
if (this.__c["topTile"]){ | |
neighbor.addComponent("topTile"); | |
} | |
//"else if" assumes map is not just one row | |
else if(this.__c["bottomTile"]){ | |
neighbor.addComponent("bottomTile"); | |
} | |
} | |
//"else if" assumes map is not just one column | |
if (tileLabel=="topTile" || tileLabel=="bottomTile"){ | |
if (this.has("leftTile")){ | |
neighbor.addComponent("leftTile"); | |
} | |
else if (this.has("rightTile")){ | |
neighbor.addComponent("rightTile"); | |
} | |
} | |
this.removeComponent(tileLabel); | |
} | |
}); | |
Crafty(tileUnloadLabel).each(function(){ | |
theUnloadTile = this; | |
for (var l=1; l<=numToDraw; l++){ | |
theX = theXFormula(theUnloadTile._x, l); | |
theY = theYFormula(theUnloadTile._y, l); | |
neighborsOfUnloaded=Crafty.map.search({ | |
_x:theX, | |
_y:theY, | |
_w:tileWidth, | |
_h:tileHeight | |
}); | |
theUnloadChildren = theUnloadTile._children; //the shacks, quarries, and forests; we need to destroy them together with their terrain when the respective terrain tiles are unloaded | |
for (var m=0; m<theUnloadChildren.length; m++){ | |
theUnloadChildren[m].destroy(); | |
} | |
theUnloadTile.destroy(); | |
for (var a=0; a<neighborsOfUnloaded.length;a++){ | |
if (neighborsOfUnloaded[a]._x==theX && neighborsOfUnloaded[a]._y==theY && (neighborsOfUnloaded[a].has("terrain1")|| neighborsOfUnloaded[a].has("terrain1")||neighborsOfUnloaded[a].has("terrain2") ||neighborsOfUnloaded[a].has("terrain3") || neighborsOfUnloaded[a].has("terrain4"))){ //this is the exact neighboring terrain tile; needed because Crafty.map.search will get everything that intersects the rectangle even for a pixel, not just everything with its origin in the rectangle | |
theUnloadTile = neighborsOfUnloaded[a]; | |
} | |
} | |
if (l==numToDraw){ | |
theUnloadTile.addComponent(tileUnloadLabel); //won't be destroyed, will have the label for top, bottom, left, or right tile instead | |
} | |
} | |
} | |
); | |
} //end of panning care | |
function drawNeighbor(nbX, nbY){ //the drawing function called when dynamically loading tiles, it will only draw mountain tines. Replace with your own drawing functionality for your projects, to draw whatever is needed. | |
return Crafty.e("2D,"+drawWay+", Mouse, terrain1").attr({ | |
x:nbX, | |
y:nbY, | |
h:tileHeight, | |
w:tileWidth | |
}); | |
} | |
//end of help functions for dynamic loading of tiles upon pan | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment