-
Notifications
You must be signed in to change notification settings - Fork 560
/
sprite.js
333 lines (301 loc) · 11.7 KB
/
sprite.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
var Crafty = require('../core/core.js');
// Define some variables required for webgl
var fs = require('fs');
var SPRITE_VERTEX_SHADER = fs.readFileSync(__dirname + '/shaders/sprite.vert', 'utf8');
var SPRITE_FRAGMENT_SHADER = fs.readFileSync(__dirname + '/shaders/sprite.frag', 'utf8');
var SPRITE_ATTRIBUTE_LIST = [
{name:"aPosition", width: 2},
{name:"aOrientation", width: 3},
{name:"aLayer", width:2},
{name:"aTextureCoord", width: 2}
];
Crafty.extend({
/**@
* #Crafty.sprite
* @category Graphics
* @sign public this Crafty.sprite([Number tile, [Number tileh]], String url, Object map[, Number paddingX[, Number paddingY[, Boolean paddingAroundBorder]]])
* @param tile - Tile size of the sprite map, defaults to 1
* @param tileh - Height of the tile; if provided, tile is interpreted as the width
* @param url - URL of the sprite image
* @param map - Object where the key is what becomes a new component and the value points to a position on the sprite map
* @param paddingX - Horizontal space in between tiles. Defaults to 0.
* @param paddingY - Vertical space in between tiles. Defaults to paddingX.
* @param paddingAroundBorder - If padding should be applied around the border of the sprite sheet. If enabled the first tile starts at (paddingX,paddingY) instead of (0,0). Defaults to false.
*
* Generates components based on positions in a sprite image to be applied to entities.
*
* Accepts a tile size, URL and map for the name of the sprite and its position.
*
* The position must be an array containing the position of the sprite where index `0`
* is the `x` position, `1` is the `y` position and optionally `2` is the width and `3`
* is the height. If the sprite map has padding, pass the values for the `x` padding
* or `y` padding. If they are the same, just add one value.
*
* If the sprite image has no consistent tile size, `1` or no argument need be
* passed for tile size.
*
* Entities that add the generated components are also given the `2D` component, and
* a component called `Sprite`.
*
* @example
* ~~~
* Crafty.sprite("imgs/spritemap6.png", {flower:[0,0,20,30]});
* var flower_entity = Crafty.e("2D, DOM, flower");
* ~~~
* The first line creates a component called `flower` associated with the sub-image of
* spritemap6.png with top-left corner (0,0), width 20 pixels, and height 30 pixels.
* The second line creates an entity with that image. (Note: The `2D` is not really
* necessary here, because adding the `flower` component automatically also adds the
* `2D` component.)
* ~~~
* Crafty.sprite(50, "imgs/spritemap6.png", {flower:[0,0], grass:[0,1,3,1]});
* ~~~
* In this case, the `flower` component is pixels 0 <= x < 50, 0 <= y < 50, and the
* `grass` component is pixels 0 <= x < 150, 50 <= y < 100. (The `3` means grass has a
* width of 3 tiles, i.e. 150 pixels.)
* ~~~
* Crafty.sprite(50, 100, "imgs/spritemap6.png", {flower:[0,0], grass:[0,1]}, 10);
* ~~~
* In this case, each tile is 50x100, and there is a spacing of 10 pixels between
* consecutive tiles. So `flower` is pixels 0 <= x < 50, 0 <= y < 100, and `grass` is
* pixels 0 <= x < 50, 110 <= y < 210.
*
* @see Sprite
*/
sprite: function (tile, tileh, url, map, paddingX, paddingY, paddingAroundBorder) {
var spriteName, temp, x, y, w, h, img;
//if no tile value, default to 1.
//(if the first passed argument is a string, it must be the url.)
if (typeof tile === "string") {
paddingY = paddingX;
paddingX = map;
map = tileh;
url = tile;
tile = 1;
tileh = 1;
}
if (typeof tileh == "string") {
paddingY = paddingX;
paddingX = map;
map = url;
url = tileh;
tileh = tile;
}
//if no paddingY, use paddingX
if (!paddingY && paddingX) paddingY = paddingX;
paddingX = parseInt(paddingX || 0, 10); //just incase
paddingY = parseInt(paddingY || 0, 10);
var markSpritesReady = function() {
this.ready = true;
this.trigger("Invalidate");
};
img = Crafty.asset(url);
if (!img) {
img = new Image();
img.src = url;
Crafty.asset(url, img);
img.onload = function () {
//all components with this img are now ready
for (var spriteName in map) {
Crafty(spriteName).each(markSpritesReady);
}
};
}
var sharedSpriteInit = function() {
this.requires("2D, Sprite");
this.__trim = [0, 0, 0, 0];
this.__image = url;
this.__coord = [this.__coord[0], this.__coord[1], this.__coord[2], this.__coord[3]];
this.__tile = tile;
this.__tileh = tileh;
this.__padding = [paddingX, paddingY];
this.__padBorder = paddingAroundBorder;
this.sprite(this.__coord[0], this.__coord[1], this.__coord[2], this.__coord[3]);
this.img = img;
//draw now
if (this.img.complete && this.img.width > 0) {
this.ready = true;
this.trigger("Invalidate");
}
//set the width and height to the sprite size
this.w = this.__coord[2];
this.h = this.__coord[3];
if (this.has("WebGL")){
this._establishShader(this.__image, SPRITE_FRAGMENT_SHADER, SPRITE_VERTEX_SHADER, SPRITE_ATTRIBUTE_LIST);
this.program.setTexture( this.webgl.makeTexture(this.__image, this.img, false) );
}
};
for (spriteName in map) {
if (!map.hasOwnProperty(spriteName)) continue;
temp = map[spriteName];
//generates sprite components for each tile in the map
Crafty.c(spriteName, {
ready: false,
__coord: [temp[0], temp[1], temp[2] || 1, temp[3] || 1],
init: sharedSpriteInit
});
}
return this;
}
});
/**@
* #Sprite
* @category Graphics
* @trigger Invalidate - when the sprites change
*
* A component for using tiles in a sprite map.
*
* This is automatically added to entities which use the components created by `Crafty.sprite` or `Crafty.load`.
* Since these are also used to define tile size, you'll rarely need to use this components methods directly.
*
* @see Crafty.sprite, Crafty.load
*/
Crafty.c("Sprite", {
__image: '',
/*
* #.__tile
* @comp Sprite
*
* Horizontal sprite tile size.
*/
__tile: 0,
/*
* #.__tileh
* @comp Sprite
*
* Vertical sprite tile size.
*/
__tileh: 0,
__padding: null,
__trim: null,
img: null,
//ready is changed to true in Crafty.sprite
ready: false,
init: function () {
this.__trim = [0, 0, 0, 0];
this.bind("Draw", this._drawSprite);
},
remove: function(){
this.unbind("Draw", this._drawSprite);
// Webgl components need to be removed from their gl program
if (this.program) {
this.program.unregisterEntity(this);
}
},
_drawSprite: function(e){
var co = e.co,
pos = e.pos,
context = e.ctx;
if (e.type === "canvas") {
//draw the image on the canvas element
context.drawImage(this.img, //image element
co.x, //x position on sprite
co.y, //y position on sprite
co.w, //width on sprite
co.h, //height on sprite
pos._x, //x position on canvas
pos._y, //y position on canvas
pos._w, //width on canvas
pos._h //height on canvas
);
} else if (e.type === "DOM") {
// Get scale (ratio of entity dimensions to sprite's dimensions)
// If needed, we will scale up the entire sprite sheet, and then modify the position accordingly
var vscale = this._h / co.h,
hscale = this._w / co.w,
style = this._element.style,
bgColor = style.backgroundColor;
if (bgColor === "initial") bgColor = "";
// Don't change background if it's not necessary -- this can cause some browsers to reload the image
// See [this chrome issue](https://code.google.com/p/chromium/issues/detail?id=102706)
var newBackground = bgColor + " url('" + this.__image + "') no-repeat";
if (newBackground !== style.background) {
style.background = newBackground;
}
style.backgroundPosition = "-" + co.x * hscale + "px -" + co.y * vscale + "px";
// style.backgroundSize must be set AFTER style.background!
if (vscale != 1 || hscale != 1) {
style.backgroundSize = (this.img.width * hscale) + "px" + " " + (this.img.height * vscale) + "px";
}
} else if (e.type === "webgl") {
// Write texture coordinates
e.program.writeVector("aTextureCoord",
co.x, co.y,
co.x, co.y + co.h,
co.x + co.w, co.y,
co.x + co.w, co.y + co.h
);
}
},
/**@
* #.sprite
* @comp Sprite
* @sign public this .sprite(Number x, Number y[, Number w, Number h])
* @param x - X cell position
* @param y - Y cell position
* @param w - Width in cells. Optional.
* @param h - Height in cells. Optional.
*
* Uses a new location on the sprite map as its sprite. If w or h are ommitted, the width and height are not changed.
*
* Values should be in tiles or cells (not pixels).
*
* @example
* ~~~
* Crafty.e("2D, DOM, Sprite")
* .sprite(0, 0, 2, 2);
* ~~~
*/
/**@
* #.__coord
* @comp Sprite
*
* The coordinate of the slide within the sprite in the format of [x, y, w, h].
*/
sprite: function (x, y, w, h) {
this.__coord = this.__coord || [0, 0, 0, 0];
this.__coord[0] = x * (this.__tile + this.__padding[0]) + (this.__padBorder ? this.__padding[0] : 0) + this.__trim[0];
this.__coord[1] = y * (this.__tileh + this.__padding[1]) + (this.__padBorder ? this.__padding[1] : 0) + this.__trim[1];
if (typeof(w)!=='undefined' && typeof(h)!=='undefined') {
this.__coord[2] = this.__trim[2] || w * this.__tile || this.__tile;
this.__coord[3] = this.__trim[3] || h * this.__tileh || this.__tileh;
}
this.trigger("Invalidate");
return this;
},
/**@
* #.crop
* @comp Sprite
* @sign public this .crop(Number x, Number y, Number w, Number h)
* @param x - Offset x position
* @param y - Offset y position
* @param w - New width
* @param h - New height
*
* If the entity needs to be smaller than the tile size, use this method to crop it.
*
* The values should be in pixels rather than tiles.
*
* @example
* ~~~
* Crafty.e("2D, DOM, Sprite")
* .crop(40, 40, 22, 23);
* ~~~
*/
crop: function (x, y, w, h) {
var old = this._mbr || this.pos();
this.__trim = [];
this.__trim[0] = x;
this.__trim[1] = y;
this.__trim[2] = w;
this.__trim[3] = h;
this.__coord[0] += x;
this.__coord[1] += y;
this.__coord[2] = w;
this.__coord[3] = h;
this._w = w;
this._h = h;
this.trigger("Invalidate", old);
return this;
}
});