KEMBAR78
Creating masterpieces with raphael | ODP
Creating Masterpieces
with Raphaël
A journey with a JavaScript library

Maurice Maneschi

http://redwaratah.com
Outline
Introducing Raphaël
Making a game board
Making the pieces
Making the controls
Demo
Where to next
Resources
What is Raphaël
A small JavaScript library
Manage vector graphics in web browsers
Easy to use and cross platform
Firefox 3.0+, Safari 3.0+, Chrome 5.0+, Opera 9.5+
and Internet Explorer 6.0+
“It is not supposed to replace Flash, but it probably
does in many cases”
Goal
What will I show
A 1970's board game moved into a browser
How I solved the various drawing issues
Lots of Raphaël code
What I won't show
I used jQuery, AJAX, PHP and MySQL to manage
the infrastructure
Design decisions on the interface
I can't release the full code as it might breach
copyright laws
I can answer questions on these afterwards
W
ARNING
THIS PRESENTATION MAY CONTAIN
TRACES OF TRIGONOMETRY.
VIEW DISCRETION IS ADVISED
ER
Scalable Vector Graphics
an image format implemented in XML
allows precise images to be described
can be scaled, rotated etc without loss of quality
HTML 5 supports SVG images
Modern browsers support it differently
Dmitry Baranovskiy had bad experiences with these
differences so created Raphaël to abstract away the
special cases
Samples – on the Raphaël site
Graphics have layers
Graphics have Layers - 2
Important to manage your layers
Raphaël does not have layers per-se
insertBefore insertAfter
Use sets to keep it together

If you use toFront or toBack, you are doing it wrong
(I have a couple in the current code from debugging)
Making a Game Board
Layer 1
Hexagons to regular movement
Different terrain that impacts movement and combat
Different for each game
Drawing a hexagon

var paper = new Raphael("holder", 200, 200);
var path = paper.path(' M 0 86.6 L 50 173.2 150 173.2 200 86.6 150 0 50 0 Z')
.attr({'stroke: '#000'});
Drawing Lots of Hexagons

1
2
3
4
Drawing lots of Hexagons
while (line < this.HEIGHT) {
var x = 0,
y = line,
s = 'M0 ' + line + 'L';
while (x < this.WIDTH) {
x += this.HEX_SIDE * this.COS60;
y += this.HEX_SIDE * this.SIN60 * (even ? -1 : 1);
s += ' ' + x + ' ' + y;
if (x >= this.WIDTH) break;
x += this.HEX_SIDE;
if (!even || y == 0) {
s += ' ' + x + ' ' + y;
} else {
s += 'M' + x + ' ' + y + 'L';
}
if (x >= this.WIDTH) break;
x += this.HEX_SIDE * this.COS60;
y += this.HEX_SIDE * this.SIN60 * (even ? 1 : -1);
s += ' ' + x + ' ' + y;
if (x >= this.WIDTH) break;
x += this.HEX_SIDE;
if (even) {
s += ' ' + x + ' ' + y;
} else {
s += 'M' + x + ' ' + y + 'L';
}
}
if (!even) line += this.HEX_SIDE * this.SIN60 * 2;
even = !even;
this.paper.path(s).attr(attr);
}
Drawing Rivers
Rivers are paths with a start and finish
You do not want to touch neighbouring cells
You want to have a bit of randomness
Raphaël has many different curve options
C = Curve to
S = Smooth curve to
Q = Quadratic Bezier curve to
T = Smooth quadratic Bezier curve to, etc.

Experiment!
“We can always
count on the
Americans to do
the right thing,
after they have
Drawing Rivers - 2
$.each(source, function (idx, path) { // an array of rivers
var first = new $.Cell(path[0]);
var mid = first.getHexMidpoint("original"),
lastMid;
var line = "M" + mid.x + ' ' + mid.y + 'S';
var i = 1;
game[target].push(first);
while (i < path.length) {
lastMid = mid;
var next = new $.Cell(path[i]);
mid = next.getHexMidpoint("original");
game[target].push(next);
line += ' ' + Raphael.format('{0} {1} {2} {3}',
(mid.x + lastMid.x) / 2, (mid.y + lastMid.y) / 2,
mid.x, mid.y);
i++;
}
game.paper.path(line).attr(attr);
});
Drawing Forests
Forests are closed paths that are filled in
You need to find the boundaries and walk around
them
No colour in the hexes next to the forest
Not perfectly regular
Drawing Forests - 2

A Failed Attempt
Drawing Forests - 3

3

1

2

4
Drawing Forests - 4
// Find a forest cell next to a non forest cell
var forestCell, clearCell, s, direction;
forest.each(function(idx, cell) {
var neighbours = cell.getAdjacentCells();
direction = -1;
$.each(neighbours, function(idx, adjacentCell) {
if (!adjacentCell) return;
var gotOne = forest.indexOf(adjacentCell);
if (gotOne >= 0) return; // Looking for a clear cell
direction = idx;
forestCell = cell;
clearCell = adjacentCell;
return false;
});
if (direction == -1) return; // cell is surrounded by forest cells, so not
needed for drawing
return false; // got one!
});
var mf = forestCell.getHexMidpoint("original"),
mc = clearCell.getHexMidpoint("original");
s = "M" + Math.round((3 * mc.x + 5 * mf.x)/8,2) + ' ' + Math.round((3 * mc.y + 5
* mf.y)/8,2) + 'S';
var countClear = 0;
var firstForest = forestCell, firstClear = clearCell;
Drawing Forests - 5
while (countClear < 6) {
direction = (direction + 1) % 6; // step one clockwise
var nextCell = forestCell.getAdjacentCells()[direction];
var gotOne = forest.indexOf(nextCell);
var mn = nextCell.getHexMidpoint("original");
if (gotOne >= 0) {
var midX = Math.round((10 * (mf.x + mn.x)/2 + 3 * mc.x)/13,2),
midY = Math.round((10 * (mf.y + mn.y)/2 + 3 * mc.y)/13,2);
s += ' ' + midX + ' ' + midY;
countClear = 0;
forestCell = nextCell;
mf = mn;
direction = forestCell.directionTo(clearCell);
} else if (game.forests.contains(nextCell)) {
break; // Gone around and completed the loop. Beware a clearing in the forest though
} else {
countClear += 1;
clearCell = nextCell;
mc = mn;
}
var scale = (countClear % 2) ? {c: 3, f: 5} : {c: 3, f:6};
s += " " + Math.round((scale.c * mc.x + scale.f * mf.x)/(scale.c + scale.f),2) + ' ' +
Math.round((scale.c * mc.y + scale.f * mf.y)/(scale.c + scale.f),2);
if (forestCell.equals(firstForest) && clearCell.equals(firstClear)) break; // We have
cirled the copse
if (direction == -1) break; // algorithm failure
}
game.paper.path(s + 'z').attr({
stroke: "#060",
fill: "#090",
"stroke-width": 1,
"stroke-linejoin": "round"
});
Drawing Villages
Four cubes
At different orientations
At different locations
Seed the random number generator to the hex index
So a redraw does not alter the buildings!
Drawing Slopes
Want a fan effect
Longer if end slope, shorter if next to another slope
Not too regular
A lot of experimentation
Drawing Slopes - 2

Why longer and shorter
Drawing Slopes - 3
$.each(Scenario.rSlopes, function(idx, slope) {
var cell = slope.cell,
direction = slope.direction,
adjacent = cell.getAdjacentCells(),
mid = cell.getHexMidpoint("original"),
angle = Math.PI - direction * Math.PI / 3,
angle2 = angle - 2 * Math.PI / 3,
angle3 = angle2 - Math.PI / 2,
sin3 = Math.sin(angle3),
cos3 = Math.cos(angle3);
game.slopes.add(cell, adjacent[direction], 1);
Math.seedrandom(cell.asCode());
var x = mid.x + game.HEX_SIDE * Math.cos(angle),
y = mid.y - game.HEX_SIDE * Math.sin(angle),
x2 = x + game.HEX_SIDE * Math.cos(angle2),
y2 = y - game.HEX_SIDE * Math.sin(angle2),
slp = 'M' + Math.round(x,2) + ' ' + Math.round(y,2) + 'L' +
Math.round(x2,2) + ' ' + Math.round(y2,2);
var start = game.hasSlope(cell, (direction + 1) % 6) ? 3 : -1,
end = game.hasSlope(cell, (direction + 5) % 6) ? 8 : 12;
for (var i = start; i < end; i++) {
var len = Math.random() * game.HEX_SIDE / 3;
if (i % 2) len += game.HEX_SIDE / 3;
slp += ' ' + Math.round(x2 + (x-x2) * i / 10 + len * cos3,2) + ' ' +
Math.round(y2 + (y-y2) * i / 10 - len * sin3,2);
}
game.paper.path(slp + 'z').attr( {
stroke: "#844",
fill: "#844",
"stroke-width": 1,
"stroke-linejoin": "round"
});
});
And so forth...
Roads are like rivers
Lakes and swamps are like forests
My goal
The result
Making the Pieces
Pieces are cardboard chits with graphics and text
Can stack (and split and join)
Can face different directions
Can have status markers on top
During the game they can
Move
Fire missiles
Attack
Be Eliminated
Drawing the Icons
Drawing the Icons - 2
Graphics Level 2
Stage one – try to write SVG
Stage two – terror
Stage three – check Dmitry's icons
Stage four – terror
Stage five – find an SVG drawing tool
Light bulb moment – LibreOffice can export SVG
Stage six – have a go
Axe and Sword
Axe and Sword - 2
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.2" baseProfile="tiny" width="210mm" height="297mm" viewBox="0 0 21000 29700"
preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve">
<defs>
<font id="EmbeddedFont_1" horiz-adv-x="2048">
<font-face font-family="Arial embedded" units-per-em="2048" font-weight="normal" font-style="normal"
ascent="1852" descent="450"/>
<missing-glyph horiz-adv-x="2048" d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"/>
<glyph unicode="I" horiz-adv-x="186" d="M 191,0 L 191,1466 385,1466 385,0 191,0 Z"/>
<glyph unicode="B" horiz-adv-x="1086" d="M 150,0 L 150,1466 700,1466 C 812,1466 902,1451 970,1422 1037,1392
1090,1346 1129,1285 1167,1223 1186,1158 1186,1091 1186,1028 1169,969 1135,914 1101,859 1050,814 981,780
1070,754 1138,710 1186,647 1233,584 1257,510 1257,425 1257,356 1243,293 1214,234 1185,175 1149,129 1106,97
1063,65 1010,41 946,25 881,8 802,0 709,0 L 150,0 Z M 344,850 L 661,850 C 747,850 809,856 846,867 895,882
933,906 958,940 983,974 995,1017 995,1068 995,1117 983,1160 960,1197 937,1234 903,1259 860,1273 817,1286
742,1293 637,1293 L 344,1293 344,850 Z M 344,173 L 709,173 C 772,173 816,175 841,180 886,188 923,201 953,220
983,239 1008,266 1027,302 1046,337 1056,378 1056,425 1056,480 1042,527 1014,568 986,608 947,636 898,653
848,669 776,677 683,677 L 344,677 344,173 Z"/>
</font>
</defs>
<g visibility="visible" id="MasterSlide_1_Default">
<desc>Master slide
</desc>
<rect fill="none" stroke="none" x="0" y="0" width="21000" height="29700"/>
</g>
<g visibility="visible" id="Slide_1_page1">
<g>
<path fill="rgb(207,231,245)" stroke="none" d="M 1700,14900 L 2100,15400 7400,11500 6900,10900 1700,14900
Z"/>
<path fill="none" stroke="rgb(128,128,128)" id="Drawing_1_0" stroke-linejoin="round" d="M 1700,14900 L
2100,15400 7400,11500 6900,10900 1700,14900 Z"/>
</g>
<g>
<path fill="rgb(207,231,245)" stroke="none" d="M 8807,9464 L 9500,10100 11000,8700 11800,11300 14800,8500
11900,7700 12300,7400 11800,6800 11500,7100 11000,4300 7800,7000 10400,8200 8807,9464 Z"/>
<path fill="none" stroke="rgb(128,128,128)" id="Drawing_2_0" stroke-linejoin="round" d="M 8807,9464 L
9500,10100 11000,8700 11800,11300 14800,8500 11900,7700 12300,7400 11800,6800 11500,7100 11000,4300 7800,7000
10400,8200 8807,9464 Z"/>
</g>
…...
</g>
</svg>
Drawing the Icons - 3
$.UNITS = {
// rawIcon comes from Open Office exported as SVG
BI: {
name: 'Barbarian Infantry',
strength: 6,
type: 'B',
movement: 5,
defHalf: true,
rawIcon: ["M 1700,14900 L 2100,15400 7400,11500 6900,10900 1700,14900 Z",
"M 8807,9464 L 9500,10100 11000,8700 11800,11300 14800,8500 11900,7700
12300,7400 11800,6800 11500,7100 11000,4300 7800,7000 10400,8200 8807,9464 Z",
"M 12700,16000 L 13800,15000 12000,13900 12400,12700 2400,4500
12100,12700 11300,13700 2400,4500 10200,15000 11300,14400 12700,16000 Z"]
},
BW: {
name: 'Bowman',
strength: 0,
type: 'Ff',
movement: 5,
firepower: 2,
range: 2,
rawIcon: ["M 3000,9000 C 3000,9000 5000,4700 11200,4700 17300,4700
18900,9100 18900,9100 18900,9100 17100,5700 11200,5200 4700,5800 3000,9000
3000,9000 Z",
"M 3000,9000 L 11000,13000 18900,9100 11000,13000",
"M 11000,13000 L 11000,4000 10300,4000 11100,3100 12000,4000
11200,4000 11200,13000 11000,13000 Z"],
}
Drawing the Icons - 4
var widgets = [],
unit = $.UNITS[code];
widgets.push($.UNITS.iconPath(code, width/2, x, y – width/8)
.attr({fill: '#666', stroke: textColour}));
widgets.push(game.paper.text(x, y + width / 4,
(unit.defHalf ? '[' + unit.strength + ']' :
(unit.strength ? '' + unit.strength : '*'))
+ ' ' + unit.type + ' ' + unit.movement)
.attr({color: textColour, 'font-family': 'Times',
'font-size': '' + (width / 3.4) + 'px'}));
widgets.push(game.paper.text(x - width * 7 / 16, y - width * 3 / 8, code)
.attr({color: textColour, 'font-family': 'Times',
'font-size': '' + (width / 5) + 'px', 'text-anchor': 'start'}));
if (unit.range != undefined) widgets.push(
game.paper.text(x + game.UNIT_SIDE * 3 / 8, y, '' + unit.range)
.attr({color: textColour, 'font-family': 'Times',
'font-size': '' + (width / 5) + 'px'}));
var prefix = unit.leadership == undefined ?
unit.firepower : unit.leadership;
if (prefix != undefined) widgets.push(
game.paper.text(x - width * 3 / 8, y, '' + prefix)
.attr({color: textColour, 'font-family': 'Times',
'font-size': '' + (width / 5) + 'px'}));
Putting it together
With the original game...
Stack the card board pieces
Move them, counting terrain and different speeds
Declare attacks
Roll the die and apply the results
Use the special behaviour of some pieces
All the while, your opponent is watching you like a
hawk to ensure you don't cheat
The user interface must manage this now
Hence we need controls
Making controls
Graphics Level 3
Deployment Control
Movement Control
Split Stack Control
Waiting Control
Combat Control
Game Menu
Deployment Control
Placing your units at the start of the game
Drag and drop off an “artist palette”
Set facing
Detect stacking violations
Deployment Control - 2
Deployment Control - 3
Raphaël has a method that manages drag and drop
Can be applied to a set of elements
Any element in the set is a handle
Every element in the set moves
I do lots of small movements as not only do the units
drag, so does the whole palette
Also, note the data() method below
Deployment Control - 4
widget
.data('code', code)
.data('idx', idx)
.drag(function (dx, dy, x, y, evt) { // Move
if (control.startX == -1) return;
control.chits[parseInt(this.data('idx'))]
.transform(Raphael.format('...t{0} {1}',
dx - control.cur.dx, dy - control.cur.dy));
control.cur = {dx:dx, dy:dy, x: x, y:y};
},
function(x, y, evt) { // Start
if (control.limits[this.data('idx')] <= 0) {
control.startX = -1;
}
control.cur = {dx: 0, dy:0, x: x, y: y};
},
function(evt) { // End
if (control.startX == -1) return;
var cell = game.getCellAt(control.cur.x, control.cur.y),
stacks = game.getStack(cell.asCode()),
code = this.data('code'),
idx = parseInt(this.data('idx'));
control.chits[idx].transform('...t ' +
-control.cur.dx + ' ' + -control.cur.dy);
Deployment Control - 5
Facing arrows are a bit of Raphaël elegance
Draw the six arrows pointing up
Rotate them around the “F”
Deployment Control - 6
$.each(FACING, function(direction, label) {
var x = 260,
arrow = game.paper.path('M' + (x - 4) +
' 100v-10h-6l10 -13 9 13h-6v10z')
.attr({stroke: '#090', fill: '#0a0', cursor: 'pointer'});
control.arrows.push(arrow);
arrow.mouseover(function (evt) {
arrow.attr({fill: '#dfd'})
}).mouseout(function (evt) {
arrow.attr({fill: '#0a0'})
}).click(function (evt) {
var stack = game.stacks[game.currentStack];
stack.face(direction, 'free');
stack.draw();
}).transform(Raphael.format('R {0} {1},120',
game.toAngle(direction), x));
});
Movement Control
Each stack can move into its facing hexes
Each hex costs a number of points to enter
Only if it has the movement points
Can always move one hex

Changing facing costs one point
If you make a mistake, you can reset and try again
Movement Control - 2
Movement Control - 3
Arrows are rotated same as deployment control
They are hidden if not permitted
Everything has to spring back if the reset is pressed
So watch relative movement
Movement Control - 4
this.stackDescription.attr({text: this.stack.getSummary()});
this.movesRemaining.attr({text: '' + this.stack.movementPlan.remaining + ' MPs'});
var control = this,
stack = this.stack,
adjacentCells = stack.cell.getAdjacentCells(),
canMoveOne = (stack.disrupted == false) &&
(stack.movementPlan.remaining == stack.getStrength().movement);
if (canMoveOne && control.stack.units.length > 1) {
this.splitButton.show();
} else {
this.splitButton.hide();
}
$.each(this.faceArrows, function(direction, arrow) {
if (stack.disrupted || (direction == stack.direction) || (stack.movementPlan.remaining <= 0)) {
arrow.hide();
} else {
arrow.show();
}
});
$.each(this.moveArrows, function(direction, arrow) {
if ((direction != stack.direction) && ((direction + 1) % 6 != stack.direction) &&
((direction + 5) % 6 != stack.direction)) {
arrow.hide();
} else {
var moveCost = control.stack.movementCostFor(adjacentCells[direction]);
if ((moveCost > control.stack.movementPlan.remaining) && !(canMoveOne && moveCost < 50)) {
arrow.hide();
} else {
arrow.show();
}
}
});
Split Stack Control
Units in a stack can split into two stacks
Originally drag and drop,
but as the behaviour was binary,
I changed it to a simple click
Split Stack Control - 2
Split Stack Control - 3
for (var i = 0; i < this.leftUnits.length; i++) {
var unit = control.stack.units[i],
x = 20 + (i % 3) * gap,
y = 60 + Math.floor( i / 3) * gap,
chit = game.paper.rect(x, y, control.UNIT_SIDE,
control.UNIT_SIDE).attr({fill: control.stack.side.colour, stroke: '#666', cursor:
'pointer'});
icon = $.UNITS.draw(unit, control.UNIT_SIDE, x + control.UNIT_SIDE/2, y +
control.UNIT_SIDE/2, stack.side.textColour);
icon.push(chit);
control.chits.push(icon);
$.each(icon, function(idx, widget) {
widget
.data('idx', i)
.click(function (evt) {
control.toggle(this.data('idx'));
});
});
control.isLeft.push(true);
}
this.OKButton = new $.ControlButton(120, 178, 'OK', function () {
control.save();
});
Split Stack Control - 4
var unit = this.stack.units[idx];
if (this.isLeft[idx]) {
this.chits[idx].transform('...t 195 0');
this.rightUnits.push(unit);
for (var i = 0; i < this.leftUnits.length; i++) {
if (this.leftUnits[i] == unit) {
this.leftUnits.splice(i,1);
break;
}
}
} else {
this.chits[idx].transform('...t -195 0');
this.leftUnits.push(unit);
for (var i = 0; i < this.rightUnits.length; i++) {
if (this.rightUnits[i] == unit) {
this.rightUnits.splice(i,1);
break;
}
}
}
this.isLeft[idx] = !this.isLeft[idx];
if ((this.leftUnits.length == 0) || (this.rightUnits.length == 0)) {
this.OKButton.hide();
} else {
this.OKButton.show();
}
Waiting Control
Not really a control
Feed back on how you opponent is faring with their
move
Bar moves across to indicate progress
Waiting Control - 2
Waiting Control - 3
update: function(progress, message) {
if (!this.showing) return;
this.progress = progress;
this.bar.remove();
this.bar = game.paper.rect(12 + this.origin.x,38 + this.origin.y,
(this.width - 20) * progress, 12)
.attr('fill', '#f00');
if (!this.show) this.bar.hide();
this.widgets.push(this.bar);
if (message) this.status.attr('text', message);
return this;
},
Combat Control
No grey boxed needed
When you highlight an assailant, I put a translucent
target over potential victims
When you click a target, I draw a translucent arrow
from the assailant to the victim
(Odds are shown on the right)
Combat Control - 2
Combat Control - 3
var midS = source.getHexMidpoint(),
midT = target.getHexMidpoint(),
range = Math.pow(Math.pow(midT.x - midS.x, 2) +
Math.pow(midT.y - midS.y, 2), .5),
slope = {x: (midT.x - midS.x) / range, y: (midT.y - midS.y) / range};
return game.paper.path(
Raphael.format('M {0} {1}L{2} {3} {4} {5} {6} {7} {8} {9} {10} {11} {12}z',
midS.x, midS.y, midT.x - (slope.x + slope.y / 3) * game.HEX_SIDE,
midT.y - (slope.y - slope.x / 3) * game.HEX_SIDE,
midT.x - (slope.x + 2 * slope.y / 3) * game.HEX_SIDE,
midT.y - (slope.y - 2 * slope.x / 3) * game.HEX_SIDE, midT.x, midT.y,
midT.x - (slope.x - 2 * slope.y / 3) * game.HEX_SIDE,
midT.y - (slope.y + 2 * slope.x / 3) * game.HEX_SIDE,
midT.x - (slope.x - slope.y / 3) * game.HEX_SIDE,
midT.y - (slope.y + slope.x / 3) * game.HEX_SIDE))
.attr({fill: '#900', opacity: .5})
.toFront();
}
Game Menu
Zoom the board
Rotate the board
Resign
Leave
Replay
Credit – All icons are Dmitry's
Game Menu - 2
Game Menu - 3
addControl: function(label, path, click) {
var x = 10 + (this.nextLocation % 4) * 40,
y = 48 + Math.floor(this.nextLocation / 4) * 40,
elements = [];
this.nextLocation ++;
elements.push(game.paper.rect(x, y, 36, 36, 2)
.attr({fill: '#00f', stroke: 'none'}));
elements.push(game.paper.path(path)
.attr({fill: '#ff0', stroke: 'none'})
.transform(Raphael.format('T{0} {1}', x + 2, y + 2)));
elements.push(game.paper.text(this.width / 2, 36, label)
.attr({fill: '#090', stroke: 'none'}).hide());
$.each(elements, function(idx, element) {
element.mouseover(function () {
elements[0].attr({fill: '#77f'});
elements[1].attr({fill: '#990'});
elements[2].show();
}).mouseout(function() {
elements[0].attr({fill: '#00f'});
elements[1].attr({fill: '#ff0'});
elements[2].hide();
}).click(click);
});
}
Game Menu - 3
addControl: function(label, path, click) {
var x = 10 + (this.nextLocation % 4) * 40,
y = 48 + Math.floor(this.nextLocation / 4) * 40,
elements = [];
this.nextLocation ++;
elements.push(game.paper.rect(x, y, 36, 36, 2)
.attr({fill: '#00f', stroke: 'none'}));
elements.push(game.paper.path(path)
.attr({fill: '#ff0', stroke: 'none'})
.transform(Raphael.format('T{0} {1}', x + 2, y + 2)));
elements.push(game.paper.text(this.width / 2, 36, label)
.attr({fill: '#090', stroke: 'none'}).hide());
$.each(elements, function(idx, element) {
element.mouseover(function () {
elements[0].attr({fill: '#77f'});
elements[1].attr({fill: '#990'});
elements[2].show();
}).mouseout(function() {
elements[0].attr({fill: '#00f'});
elements[1].attr({fill: '#ff0'});
elements[2].hide();
}).click(click);
});
}
Demo
Where to next
Touch screens and tablets
More bug fixing and improvements
What can be released?
Post onto Source Forge or Git Hub
Build a community
Resources
Code and documentation – http://raphaeljs.com
Designer - http://dmitry.baranovskiy.com/
Google group https://groups.google.com/forum/#!forum/raphaeljs
jQuery – http://jquery.com
Moi – maurice@redwaratah.com
Creating Masterpieces
with Raphaël
A journey with a JavaScript library

Maurice Maneschi

http://redwaratah.com

Creating masterpieces with raphael

  • 1.
    Creating Masterpieces with Raphaël Ajourney with a JavaScript library Maurice Maneschi http://redwaratah.com
  • 2.
    Outline Introducing Raphaël Making agame board Making the pieces Making the controls Demo Where to next Resources
  • 3.
    What is Raphaël Asmall JavaScript library Manage vector graphics in web browsers Easy to use and cross platform Firefox 3.0+, Safari 3.0+, Chrome 5.0+, Opera 9.5+ and Internet Explorer 6.0+ “It is not supposed to replace Flash, but it probably does in many cases”
  • 4.
  • 5.
    What will Ishow A 1970's board game moved into a browser How I solved the various drawing issues Lots of Raphaël code
  • 6.
    What I won'tshow I used jQuery, AJAX, PHP and MySQL to manage the infrastructure Design decisions on the interface I can't release the full code as it might breach copyright laws I can answer questions on these afterwards
  • 7.
    W ARNING THIS PRESENTATION MAYCONTAIN TRACES OF TRIGONOMETRY. VIEW DISCRETION IS ADVISED ER
  • 8.
    Scalable Vector Graphics animage format implemented in XML allows precise images to be described can be scaled, rotated etc without loss of quality HTML 5 supports SVG images Modern browsers support it differently Dmitry Baranovskiy had bad experiences with these differences so created Raphaël to abstract away the special cases
  • 9.
    Samples – onthe Raphaël site
  • 10.
  • 11.
    Graphics have Layers- 2 Important to manage your layers Raphaël does not have layers per-se insertBefore insertAfter Use sets to keep it together If you use toFront or toBack, you are doing it wrong (I have a couple in the current code from debugging)
  • 12.
    Making a GameBoard Layer 1 Hexagons to regular movement Different terrain that impacts movement and combat Different for each game
  • 13.
    Drawing a hexagon varpaper = new Raphael("holder", 200, 200); var path = paper.path(' M 0 86.6 L 50 173.2 150 173.2 200 86.6 150 0 50 0 Z') .attr({'stroke: '#000'});
  • 14.
    Drawing Lots ofHexagons 1 2 3 4
  • 15.
    Drawing lots ofHexagons while (line < this.HEIGHT) { var x = 0, y = line, s = 'M0 ' + line + 'L'; while (x < this.WIDTH) { x += this.HEX_SIDE * this.COS60; y += this.HEX_SIDE * this.SIN60 * (even ? -1 : 1); s += ' ' + x + ' ' + y; if (x >= this.WIDTH) break; x += this.HEX_SIDE; if (!even || y == 0) { s += ' ' + x + ' ' + y; } else { s += 'M' + x + ' ' + y + 'L'; } if (x >= this.WIDTH) break; x += this.HEX_SIDE * this.COS60; y += this.HEX_SIDE * this.SIN60 * (even ? 1 : -1); s += ' ' + x + ' ' + y; if (x >= this.WIDTH) break; x += this.HEX_SIDE; if (even) { s += ' ' + x + ' ' + y; } else { s += 'M' + x + ' ' + y + 'L'; } } if (!even) line += this.HEX_SIDE * this.SIN60 * 2; even = !even; this.paper.path(s).attr(attr); }
  • 16.
    Drawing Rivers Rivers arepaths with a start and finish You do not want to touch neighbouring cells You want to have a bit of randomness Raphaël has many different curve options C = Curve to S = Smooth curve to Q = Quadratic Bezier curve to T = Smooth quadratic Bezier curve to, etc. Experiment!
  • 17.
    “We can always counton the Americans to do the right thing, after they have
  • 18.
    Drawing Rivers -2 $.each(source, function (idx, path) { // an array of rivers var first = new $.Cell(path[0]); var mid = first.getHexMidpoint("original"), lastMid; var line = "M" + mid.x + ' ' + mid.y + 'S'; var i = 1; game[target].push(first); while (i < path.length) { lastMid = mid; var next = new $.Cell(path[i]); mid = next.getHexMidpoint("original"); game[target].push(next); line += ' ' + Raphael.format('{0} {1} {2} {3}', (mid.x + lastMid.x) / 2, (mid.y + lastMid.y) / 2, mid.x, mid.y); i++; } game.paper.path(line).attr(attr); });
  • 19.
    Drawing Forests Forests areclosed paths that are filled in You need to find the boundaries and walk around them No colour in the hexes next to the forest Not perfectly regular
  • 20.
    Drawing Forests -2 A Failed Attempt
  • 21.
  • 22.
    Drawing Forests -4 // Find a forest cell next to a non forest cell var forestCell, clearCell, s, direction; forest.each(function(idx, cell) { var neighbours = cell.getAdjacentCells(); direction = -1; $.each(neighbours, function(idx, adjacentCell) { if (!adjacentCell) return; var gotOne = forest.indexOf(adjacentCell); if (gotOne >= 0) return; // Looking for a clear cell direction = idx; forestCell = cell; clearCell = adjacentCell; return false; }); if (direction == -1) return; // cell is surrounded by forest cells, so not needed for drawing return false; // got one! }); var mf = forestCell.getHexMidpoint("original"), mc = clearCell.getHexMidpoint("original"); s = "M" + Math.round((3 * mc.x + 5 * mf.x)/8,2) + ' ' + Math.round((3 * mc.y + 5 * mf.y)/8,2) + 'S'; var countClear = 0; var firstForest = forestCell, firstClear = clearCell;
  • 23.
    Drawing Forests -5 while (countClear < 6) { direction = (direction + 1) % 6; // step one clockwise var nextCell = forestCell.getAdjacentCells()[direction]; var gotOne = forest.indexOf(nextCell); var mn = nextCell.getHexMidpoint("original"); if (gotOne >= 0) { var midX = Math.round((10 * (mf.x + mn.x)/2 + 3 * mc.x)/13,2), midY = Math.round((10 * (mf.y + mn.y)/2 + 3 * mc.y)/13,2); s += ' ' + midX + ' ' + midY; countClear = 0; forestCell = nextCell; mf = mn; direction = forestCell.directionTo(clearCell); } else if (game.forests.contains(nextCell)) { break; // Gone around and completed the loop. Beware a clearing in the forest though } else { countClear += 1; clearCell = nextCell; mc = mn; } var scale = (countClear % 2) ? {c: 3, f: 5} : {c: 3, f:6}; s += " " + Math.round((scale.c * mc.x + scale.f * mf.x)/(scale.c + scale.f),2) + ' ' + Math.round((scale.c * mc.y + scale.f * mf.y)/(scale.c + scale.f),2); if (forestCell.equals(firstForest) && clearCell.equals(firstClear)) break; // We have cirled the copse if (direction == -1) break; // algorithm failure } game.paper.path(s + 'z').attr({ stroke: "#060", fill: "#090", "stroke-width": 1, "stroke-linejoin": "round" });
  • 24.
    Drawing Villages Four cubes Atdifferent orientations At different locations Seed the random number generator to the hex index So a redraw does not alter the buildings!
  • 25.
    Drawing Slopes Want afan effect Longer if end slope, shorter if next to another slope Not too regular A lot of experimentation
  • 26.
    Drawing Slopes -2 Why longer and shorter
  • 27.
    Drawing Slopes -3 $.each(Scenario.rSlopes, function(idx, slope) { var cell = slope.cell, direction = slope.direction, adjacent = cell.getAdjacentCells(), mid = cell.getHexMidpoint("original"), angle = Math.PI - direction * Math.PI / 3, angle2 = angle - 2 * Math.PI / 3, angle3 = angle2 - Math.PI / 2, sin3 = Math.sin(angle3), cos3 = Math.cos(angle3); game.slopes.add(cell, adjacent[direction], 1); Math.seedrandom(cell.asCode()); var x = mid.x + game.HEX_SIDE * Math.cos(angle), y = mid.y - game.HEX_SIDE * Math.sin(angle), x2 = x + game.HEX_SIDE * Math.cos(angle2), y2 = y - game.HEX_SIDE * Math.sin(angle2), slp = 'M' + Math.round(x,2) + ' ' + Math.round(y,2) + 'L' + Math.round(x2,2) + ' ' + Math.round(y2,2); var start = game.hasSlope(cell, (direction + 1) % 6) ? 3 : -1, end = game.hasSlope(cell, (direction + 5) % 6) ? 8 : 12; for (var i = start; i < end; i++) { var len = Math.random() * game.HEX_SIDE / 3; if (i % 2) len += game.HEX_SIDE / 3; slp += ' ' + Math.round(x2 + (x-x2) * i / 10 + len * cos3,2) + ' ' + Math.round(y2 + (y-y2) * i / 10 - len * sin3,2); } game.paper.path(slp + 'z').attr( { stroke: "#844", fill: "#844", "stroke-width": 1, "stroke-linejoin": "round" }); });
  • 28.
    And so forth... Roadsare like rivers Lakes and swamps are like forests
  • 29.
  • 30.
  • 31.
    Making the Pieces Piecesare cardboard chits with graphics and text Can stack (and split and join) Can face different directions Can have status markers on top During the game they can Move Fire missiles Attack Be Eliminated
  • 32.
  • 33.
    Drawing the Icons- 2 Graphics Level 2 Stage one – try to write SVG Stage two – terror Stage three – check Dmitry's icons Stage four – terror Stage five – find an SVG drawing tool Light bulb moment – LibreOffice can export SVG Stage six – have a go
  • 34.
  • 35.
    Axe and Sword- 2 <?xml version="1.0" encoding="UTF-8"?> <svg version="1.2" baseProfile="tiny" width="210mm" height="297mm" viewBox="0 0 21000 29700" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve"> <defs> <font id="EmbeddedFont_1" horiz-adv-x="2048"> <font-face font-family="Arial embedded" units-per-em="2048" font-weight="normal" font-style="normal" ascent="1852" descent="450"/> <missing-glyph horiz-adv-x="2048" d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"/> <glyph unicode="I" horiz-adv-x="186" d="M 191,0 L 191,1466 385,1466 385,0 191,0 Z"/> <glyph unicode="B" horiz-adv-x="1086" d="M 150,0 L 150,1466 700,1466 C 812,1466 902,1451 970,1422 1037,1392 1090,1346 1129,1285 1167,1223 1186,1158 1186,1091 1186,1028 1169,969 1135,914 1101,859 1050,814 981,780 1070,754 1138,710 1186,647 1233,584 1257,510 1257,425 1257,356 1243,293 1214,234 1185,175 1149,129 1106,97 1063,65 1010,41 946,25 881,8 802,0 709,0 L 150,0 Z M 344,850 L 661,850 C 747,850 809,856 846,867 895,882 933,906 958,940 983,974 995,1017 995,1068 995,1117 983,1160 960,1197 937,1234 903,1259 860,1273 817,1286 742,1293 637,1293 L 344,1293 344,850 Z M 344,173 L 709,173 C 772,173 816,175 841,180 886,188 923,201 953,220 983,239 1008,266 1027,302 1046,337 1056,378 1056,425 1056,480 1042,527 1014,568 986,608 947,636 898,653 848,669 776,677 683,677 L 344,677 344,173 Z"/> </font> </defs> <g visibility="visible" id="MasterSlide_1_Default"> <desc>Master slide </desc> <rect fill="none" stroke="none" x="0" y="0" width="21000" height="29700"/> </g> <g visibility="visible" id="Slide_1_page1"> <g> <path fill="rgb(207,231,245)" stroke="none" d="M 1700,14900 L 2100,15400 7400,11500 6900,10900 1700,14900 Z"/> <path fill="none" stroke="rgb(128,128,128)" id="Drawing_1_0" stroke-linejoin="round" d="M 1700,14900 L 2100,15400 7400,11500 6900,10900 1700,14900 Z"/> </g> <g> <path fill="rgb(207,231,245)" stroke="none" d="M 8807,9464 L 9500,10100 11000,8700 11800,11300 14800,8500 11900,7700 12300,7400 11800,6800 11500,7100 11000,4300 7800,7000 10400,8200 8807,9464 Z"/> <path fill="none" stroke="rgb(128,128,128)" id="Drawing_2_0" stroke-linejoin="round" d="M 8807,9464 L 9500,10100 11000,8700 11800,11300 14800,8500 11900,7700 12300,7400 11800,6800 11500,7100 11000,4300 7800,7000 10400,8200 8807,9464 Z"/> </g> …... </g> </svg>
  • 36.
    Drawing the Icons- 3 $.UNITS = { // rawIcon comes from Open Office exported as SVG BI: { name: 'Barbarian Infantry', strength: 6, type: 'B', movement: 5, defHalf: true, rawIcon: ["M 1700,14900 L 2100,15400 7400,11500 6900,10900 1700,14900 Z", "M 8807,9464 L 9500,10100 11000,8700 11800,11300 14800,8500 11900,7700 12300,7400 11800,6800 11500,7100 11000,4300 7800,7000 10400,8200 8807,9464 Z", "M 12700,16000 L 13800,15000 12000,13900 12400,12700 2400,4500 12100,12700 11300,13700 2400,4500 10200,15000 11300,14400 12700,16000 Z"] }, BW: { name: 'Bowman', strength: 0, type: 'Ff', movement: 5, firepower: 2, range: 2, rawIcon: ["M 3000,9000 C 3000,9000 5000,4700 11200,4700 17300,4700 18900,9100 18900,9100 18900,9100 17100,5700 11200,5200 4700,5800 3000,9000 3000,9000 Z", "M 3000,9000 L 11000,13000 18900,9100 11000,13000", "M 11000,13000 L 11000,4000 10300,4000 11100,3100 12000,4000 11200,4000 11200,13000 11000,13000 Z"], }
  • 37.
    Drawing the Icons- 4 var widgets = [], unit = $.UNITS[code]; widgets.push($.UNITS.iconPath(code, width/2, x, y – width/8) .attr({fill: '#666', stroke: textColour})); widgets.push(game.paper.text(x, y + width / 4, (unit.defHalf ? '[' + unit.strength + ']' : (unit.strength ? '' + unit.strength : '*')) + ' ' + unit.type + ' ' + unit.movement) .attr({color: textColour, 'font-family': 'Times', 'font-size': '' + (width / 3.4) + 'px'})); widgets.push(game.paper.text(x - width * 7 / 16, y - width * 3 / 8, code) .attr({color: textColour, 'font-family': 'Times', 'font-size': '' + (width / 5) + 'px', 'text-anchor': 'start'})); if (unit.range != undefined) widgets.push( game.paper.text(x + game.UNIT_SIDE * 3 / 8, y, '' + unit.range) .attr({color: textColour, 'font-family': 'Times', 'font-size': '' + (width / 5) + 'px'})); var prefix = unit.leadership == undefined ? unit.firepower : unit.leadership; if (prefix != undefined) widgets.push( game.paper.text(x - width * 3 / 8, y, '' + prefix) .attr({color: textColour, 'font-family': 'Times', 'font-size': '' + (width / 5) + 'px'}));
  • 38.
  • 39.
    With the originalgame... Stack the card board pieces Move them, counting terrain and different speeds Declare attacks Roll the die and apply the results Use the special behaviour of some pieces All the while, your opponent is watching you like a hawk to ensure you don't cheat The user interface must manage this now Hence we need controls
  • 40.
    Making controls Graphics Level3 Deployment Control Movement Control Split Stack Control Waiting Control Combat Control Game Menu
  • 41.
    Deployment Control Placing yourunits at the start of the game Drag and drop off an “artist palette” Set facing Detect stacking violations
  • 42.
  • 43.
    Deployment Control -3 Raphaël has a method that manages drag and drop Can be applied to a set of elements Any element in the set is a handle Every element in the set moves I do lots of small movements as not only do the units drag, so does the whole palette Also, note the data() method below
  • 44.
    Deployment Control -4 widget .data('code', code) .data('idx', idx) .drag(function (dx, dy, x, y, evt) { // Move if (control.startX == -1) return; control.chits[parseInt(this.data('idx'))] .transform(Raphael.format('...t{0} {1}', dx - control.cur.dx, dy - control.cur.dy)); control.cur = {dx:dx, dy:dy, x: x, y:y}; }, function(x, y, evt) { // Start if (control.limits[this.data('idx')] <= 0) { control.startX = -1; } control.cur = {dx: 0, dy:0, x: x, y: y}; }, function(evt) { // End if (control.startX == -1) return; var cell = game.getCellAt(control.cur.x, control.cur.y), stacks = game.getStack(cell.asCode()), code = this.data('code'), idx = parseInt(this.data('idx')); control.chits[idx].transform('...t ' + -control.cur.dx + ' ' + -control.cur.dy);
  • 45.
    Deployment Control -5 Facing arrows are a bit of Raphaël elegance Draw the six arrows pointing up Rotate them around the “F”
  • 46.
    Deployment Control -6 $.each(FACING, function(direction, label) { var x = 260, arrow = game.paper.path('M' + (x - 4) + ' 100v-10h-6l10 -13 9 13h-6v10z') .attr({stroke: '#090', fill: '#0a0', cursor: 'pointer'}); control.arrows.push(arrow); arrow.mouseover(function (evt) { arrow.attr({fill: '#dfd'}) }).mouseout(function (evt) { arrow.attr({fill: '#0a0'}) }).click(function (evt) { var stack = game.stacks[game.currentStack]; stack.face(direction, 'free'); stack.draw(); }).transform(Raphael.format('R {0} {1},120', game.toAngle(direction), x)); });
  • 47.
    Movement Control Each stackcan move into its facing hexes Each hex costs a number of points to enter Only if it has the movement points Can always move one hex Changing facing costs one point If you make a mistake, you can reset and try again
  • 48.
  • 49.
    Movement Control -3 Arrows are rotated same as deployment control They are hidden if not permitted Everything has to spring back if the reset is pressed So watch relative movement
  • 50.
    Movement Control -4 this.stackDescription.attr({text: this.stack.getSummary()}); this.movesRemaining.attr({text: '' + this.stack.movementPlan.remaining + ' MPs'}); var control = this, stack = this.stack, adjacentCells = stack.cell.getAdjacentCells(), canMoveOne = (stack.disrupted == false) && (stack.movementPlan.remaining == stack.getStrength().movement); if (canMoveOne && control.stack.units.length > 1) { this.splitButton.show(); } else { this.splitButton.hide(); } $.each(this.faceArrows, function(direction, arrow) { if (stack.disrupted || (direction == stack.direction) || (stack.movementPlan.remaining <= 0)) { arrow.hide(); } else { arrow.show(); } }); $.each(this.moveArrows, function(direction, arrow) { if ((direction != stack.direction) && ((direction + 1) % 6 != stack.direction) && ((direction + 5) % 6 != stack.direction)) { arrow.hide(); } else { var moveCost = control.stack.movementCostFor(adjacentCells[direction]); if ((moveCost > control.stack.movementPlan.remaining) && !(canMoveOne && moveCost < 50)) { arrow.hide(); } else { arrow.show(); } } });
  • 51.
    Split Stack Control Unitsin a stack can split into two stacks Originally drag and drop, but as the behaviour was binary, I changed it to a simple click
  • 52.
  • 53.
    Split Stack Control- 3 for (var i = 0; i < this.leftUnits.length; i++) { var unit = control.stack.units[i], x = 20 + (i % 3) * gap, y = 60 + Math.floor( i / 3) * gap, chit = game.paper.rect(x, y, control.UNIT_SIDE, control.UNIT_SIDE).attr({fill: control.stack.side.colour, stroke: '#666', cursor: 'pointer'}); icon = $.UNITS.draw(unit, control.UNIT_SIDE, x + control.UNIT_SIDE/2, y + control.UNIT_SIDE/2, stack.side.textColour); icon.push(chit); control.chits.push(icon); $.each(icon, function(idx, widget) { widget .data('idx', i) .click(function (evt) { control.toggle(this.data('idx')); }); }); control.isLeft.push(true); } this.OKButton = new $.ControlButton(120, 178, 'OK', function () { control.save(); });
  • 54.
    Split Stack Control- 4 var unit = this.stack.units[idx]; if (this.isLeft[idx]) { this.chits[idx].transform('...t 195 0'); this.rightUnits.push(unit); for (var i = 0; i < this.leftUnits.length; i++) { if (this.leftUnits[i] == unit) { this.leftUnits.splice(i,1); break; } } } else { this.chits[idx].transform('...t -195 0'); this.leftUnits.push(unit); for (var i = 0; i < this.rightUnits.length; i++) { if (this.rightUnits[i] == unit) { this.rightUnits.splice(i,1); break; } } } this.isLeft[idx] = !this.isLeft[idx]; if ((this.leftUnits.length == 0) || (this.rightUnits.length == 0)) { this.OKButton.hide(); } else { this.OKButton.show(); }
  • 55.
    Waiting Control Not reallya control Feed back on how you opponent is faring with their move Bar moves across to indicate progress
  • 56.
  • 57.
    Waiting Control -3 update: function(progress, message) { if (!this.showing) return; this.progress = progress; this.bar.remove(); this.bar = game.paper.rect(12 + this.origin.x,38 + this.origin.y, (this.width - 20) * progress, 12) .attr('fill', '#f00'); if (!this.show) this.bar.hide(); this.widgets.push(this.bar); if (message) this.status.attr('text', message); return this; },
  • 58.
    Combat Control No greyboxed needed When you highlight an assailant, I put a translucent target over potential victims When you click a target, I draw a translucent arrow from the assailant to the victim (Odds are shown on the right)
  • 59.
  • 60.
    Combat Control -3 var midS = source.getHexMidpoint(), midT = target.getHexMidpoint(), range = Math.pow(Math.pow(midT.x - midS.x, 2) + Math.pow(midT.y - midS.y, 2), .5), slope = {x: (midT.x - midS.x) / range, y: (midT.y - midS.y) / range}; return game.paper.path( Raphael.format('M {0} {1}L{2} {3} {4} {5} {6} {7} {8} {9} {10} {11} {12}z', midS.x, midS.y, midT.x - (slope.x + slope.y / 3) * game.HEX_SIDE, midT.y - (slope.y - slope.x / 3) * game.HEX_SIDE, midT.x - (slope.x + 2 * slope.y / 3) * game.HEX_SIDE, midT.y - (slope.y - 2 * slope.x / 3) * game.HEX_SIDE, midT.x, midT.y, midT.x - (slope.x - 2 * slope.y / 3) * game.HEX_SIDE, midT.y - (slope.y + 2 * slope.x / 3) * game.HEX_SIDE, midT.x - (slope.x - slope.y / 3) * game.HEX_SIDE, midT.y - (slope.y + slope.x / 3) * game.HEX_SIDE)) .attr({fill: '#900', opacity: .5}) .toFront(); }
  • 61.
    Game Menu Zoom theboard Rotate the board Resign Leave Replay Credit – All icons are Dmitry's
  • 62.
  • 63.
    Game Menu -3 addControl: function(label, path, click) { var x = 10 + (this.nextLocation % 4) * 40, y = 48 + Math.floor(this.nextLocation / 4) * 40, elements = []; this.nextLocation ++; elements.push(game.paper.rect(x, y, 36, 36, 2) .attr({fill: '#00f', stroke: 'none'})); elements.push(game.paper.path(path) .attr({fill: '#ff0', stroke: 'none'}) .transform(Raphael.format('T{0} {1}', x + 2, y + 2))); elements.push(game.paper.text(this.width / 2, 36, label) .attr({fill: '#090', stroke: 'none'}).hide()); $.each(elements, function(idx, element) { element.mouseover(function () { elements[0].attr({fill: '#77f'}); elements[1].attr({fill: '#990'}); elements[2].show(); }).mouseout(function() { elements[0].attr({fill: '#00f'}); elements[1].attr({fill: '#ff0'}); elements[2].hide(); }).click(click); }); }
  • 64.
    Game Menu -3 addControl: function(label, path, click) { var x = 10 + (this.nextLocation % 4) * 40, y = 48 + Math.floor(this.nextLocation / 4) * 40, elements = []; this.nextLocation ++; elements.push(game.paper.rect(x, y, 36, 36, 2) .attr({fill: '#00f', stroke: 'none'})); elements.push(game.paper.path(path) .attr({fill: '#ff0', stroke: 'none'}) .transform(Raphael.format('T{0} {1}', x + 2, y + 2))); elements.push(game.paper.text(this.width / 2, 36, label) .attr({fill: '#090', stroke: 'none'}).hide()); $.each(elements, function(idx, element) { element.mouseover(function () { elements[0].attr({fill: '#77f'}); elements[1].attr({fill: '#990'}); elements[2].show(); }).mouseout(function() { elements[0].attr({fill: '#00f'}); elements[1].attr({fill: '#ff0'}); elements[2].hide(); }).click(click); }); }
  • 65.
  • 66.
    Where to next Touchscreens and tablets More bug fixing and improvements What can be released? Post onto Source Forge or Git Hub Build a community
  • 67.
    Resources Code and documentation– http://raphaeljs.com Designer - http://dmitry.baranovskiy.com/ Google group https://groups.google.com/forum/#!forum/raphaeljs jQuery – http://jquery.com Moi – maurice@redwaratah.com
  • 68.
    Creating Masterpieces with Raphaël Ajourney with a JavaScript library Maurice Maneschi http://redwaratah.com