├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── README.md
├── index.html
├── media
└── panda.png
└── src
├── engine
├── audio.js
├── camera.js
├── core.js
├── debug.js
├── geometry.js
├── input.js
├── loader.js
├── particle.js
├── physics.js
├── pool.js
├── renderer
│ ├── animation.js
│ ├── container.js
│ ├── core.js
│ ├── fastcontainer.js
│ ├── graphics.js
│ ├── sprite.js
│ ├── spritesheet.js
│ ├── text.js
│ ├── texture.js
│ └── tilingsprite.js
├── scene.js
├── storage.js
├── system.js
├── timer.js
└── tween.js
└── game
├── config.js
└── main.js
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 |
5 | ---
6 |
7 | **Describe the bug**
8 | A clear and concise description of what the bug is.
9 |
10 | **To Reproduce**
11 | Steps to reproduce the behavior:
12 | 1. Go to '...'
13 | 2. Click on '....'
14 | 3. Scroll down to '....'
15 | 4. See error
16 |
17 | **Expected behavior**
18 | A clear and concise description of what you expected to happen.
19 |
20 | **Screenshots**
21 | If applicable, add screenshots to help explain your problem.
22 |
23 | **Info**
24 | - OS: [e.g. Windows]
25 | - Version: [e.g. 1.5.0]
26 |
27 | **Additional context**
28 | Add any other context about the problem here.
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 |
5 | ---
6 |
7 | **Is your feature request related to a problem? Please describe.**
8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
9 |
10 | **Describe the solution you'd like**
11 | A clear and concise description of what you want to happen.
12 |
13 | **Describe alternatives you've considered**
14 | A clear and concise description of any alternative solutions or features you've considered.
15 |
16 | **Additional context**
17 | Add any other context or screenshots about the feature request here.
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.min.js
3 | node_modules
4 | plugins
5 | editor
6 | bamboo
7 | .jscsrc
8 | media/*
9 | !media/panda.png
10 | *.mobileprovision
11 | *.p12
12 | plugin
13 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Code of Conduct
2 |
3 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4 |
5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
6 |
7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8 |
9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10 |
11 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12 |
13 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
14 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing guide
2 |
3 | 1. [Fork the project](https://github.com/ekelokorpi/panda-engine/fork)
4 | 2. Checkout to `develop` branch
5 | 3. Make changes
6 | 4. Validate code
7 |
8 | $ panda lint
9 |
10 | 4. Create new [pull request](https://github.com/ekelokorpi/panda-engine/compare)
11 | 5. Wait for merge or comments
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Panda Engine
2 |
3 | [](https://www.panda2.io/)
4 |
5 | Panda Engine is a part of Panda 2 Game Development Platform, for more information please visit:
6 |
7 | https://www.panda2.io
8 |
9 | ## Learn
10 |
11 | - [Code examples](https://www.panda2.io/examples)
12 | - [Tutorials](https://www.panda2.io/tutorials)
13 | - [Game templates](https://www.panda2.io/templates)
14 | - [API Documentation](https://www.panda2.io/docs/api)
15 |
16 | ## Credits
17 |
18 | - Made by Eemeli Kelokorpi
19 | - Renderer based on [PixiJS](http://www.pixijs.com) by Mat Groves, Goodboy Digital
20 |
21 | ## License
22 |
23 | - Panda Engine is released under the [MIT License](http://opensource.org/licenses/MIT).
24 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Panda Engine
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/media/panda.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ekelokorpi/panda-engine/08eb1822ebe985bc6c43795aebd2baa635c02ff5/media/panda.png
--------------------------------------------------------------------------------
/src/engine/audio.js:
--------------------------------------------------------------------------------
1 | /**
2 | @module audio
3 | **/
4 | game.module(
5 | 'engine.audio'
6 | )
7 | .body(function() {
8 |
9 | /**
10 | Audio manager. Instance automatically created at `game.audio`
11 | @class Audio
12 | **/
13 | game.createClass('Audio', {
14 | /**
15 | Current supported audio formats.
16 | @property {Array} formats
17 | **/
18 | formats: [],
19 | /**
20 | Current music.
21 | @property {Music} music
22 | **/
23 | music: null,
24 | /**
25 | Is all sounds and music muted.
26 | @property {Boolean} muted
27 | @default false
28 | **/
29 | muted: false,
30 | /**
31 | Is music muted.
32 | @property {Boolean} mutedMusic
33 | @default false
34 | **/
35 | mutedMusic: false,
36 | /**
37 | Is all sounds muted.
38 | @property {Boolean} mutedSound
39 | @default false
40 | **/
41 | mutedSound: false,
42 | /**
43 | Currently playing sounds.
44 | @property {Array} sounds
45 | **/
46 | sounds: [],
47 | /**
48 | @property {AudioContext} _context
49 | @private
50 | **/
51 | _context: null,
52 | /**
53 | @property {GainNode} _mainGain
54 | @private
55 | **/
56 | _mainGain: null,
57 | /**
58 | @property {GainNode} _musicGain
59 | @private
60 | **/
61 | _musicGain: null,
62 | /**
63 | @property {Music} _pauseMusic
64 | @private
65 | **/
66 | _pauseMusic: null,
67 | /**
68 | @property {Array} _pauseSounds
69 | @private
70 | **/
71 | _pauseSounds: [],
72 | /**
73 | @property {GainNode} _soundGain
74 | @private
75 | **/
76 | _soundGain: null,
77 |
78 | staticInit: function() {
79 | if (!game.Audio.enabled) return;
80 |
81 | game._normalizeVendorAttribute(window, 'AudioContext');
82 |
83 | var audio = new Audio();
84 | for (var i = 0; i < game.Audio.formats.length; i++) {
85 | if (audio.canPlayType(game.Audio.formats[i].type)) {
86 | this.formats.push(game.Audio.formats[i].ext);
87 | }
88 | }
89 |
90 | if (!this.formats.length) {
91 | game.Audio.enabled = false;
92 | return;
93 | }
94 |
95 | if (!window.AudioContext) return;
96 |
97 | this._context = new AudioContext();
98 |
99 | this._mainGain = this._context.createGain();
100 | this._mainGain.connect(this._context.destination);
101 |
102 | this._musicGain = this._context.createGain();
103 | this._musicGain.gain.setValueAtTime(game.Audio.musicVolume, this._context.currentTime);
104 | this._musicGain.connect(this._mainGain);
105 |
106 | this._soundGain = this._context.createGain();
107 | this._soundGain.gain.setValueAtTime(game.Audio.soundVolume, this._context.currentTime);
108 | this._soundGain.connect(this._mainGain);
109 | },
110 |
111 | /**
112 | Mute all sounds and music.
113 | @method mute
114 | **/
115 | mute: function() {
116 | if (!this._mainGain) return;
117 | this._mainGain.gain.setValueAtTime(0, this._context.currentTime);
118 | this.muted = true;
119 | },
120 |
121 | /**
122 | Mute music.
123 | @method muteMusic
124 | **/
125 | muteMusic: function() {
126 | if (!this._musicGain) return;
127 | this._musicGain.gain.setValueAtTime(0, this._context.currentTime);
128 | this.mutedMusic = true;
129 | },
130 |
131 | /**
132 | Mute all sounds.
133 | @method muteSound
134 | **/
135 | muteSound: function() {
136 | if (!this._soundGain) return;
137 | this._soundGain.gain.setValueAtTime(0, this._context.currentTime);
138 | this.mutedSound = true;
139 | },
140 |
141 | /**
142 | @method playMusic
143 | @param {String} name
144 | @param {Boolean} [noLoop] Do not loop the music
145 | @return {Music}
146 | **/
147 | playMusic: function(name, noLoop) {
148 | var music = new game.Music(name);
149 | if (noLoop) music.loop = false;
150 | music.play();
151 | return music;
152 | },
153 |
154 | /**
155 | @method playSound
156 | @param {String} name
157 | @param {Number} [volume]
158 | @param {Number} [rate]
159 | @return {Sound}
160 | **/
161 | playSound: function(name, volume, rate) {
162 | var sound = new game.Sound(name);
163 | sound.volume = volume || sound.volume;
164 | sound.rate = rate || sound._rate;
165 | sound.play();
166 | return sound;
167 | },
168 |
169 | /**
170 | Stop all sounds.
171 | @method stopAll
172 | **/
173 | stopAll: function() {
174 | this.stopMusic();
175 | for (var i = 0; i < this.sounds.length; i++) {
176 | this.sounds[i].stop();
177 | }
178 | },
179 |
180 | /**
181 | Stop current music.
182 | @method stopMusic
183 | **/
184 | stopMusic: function() {
185 | if (this.music) this.music.stop();
186 | this.music = null;
187 | },
188 |
189 | /**
190 | Toggle mute/unmute all audio.
191 | @method toggle
192 | **/
193 | toggle: function() {
194 | if (this.muted) this.unmute();
195 | else this.mute();
196 | return this.muted;
197 | },
198 |
199 | /**
200 | Unmute all audio.
201 | @method unmute
202 | **/
203 | unmute: function() {
204 | if (!this._mainGain) return;
205 | this._mainGain.gain.setValueAtTime(1, this._context.currentTime);
206 | this.muted = false;
207 | },
208 |
209 | /**
210 | Unmute music.
211 | @method muteMusic
212 | **/
213 | unmuteMusic: function() {
214 | if (!this._musicGain) return;
215 | this._musicGain.gain.setValueAtTime(game.Audio.musicVolume, this._context.currentTime);
216 | this.mutedMusic = false;
217 | },
218 |
219 | /**
220 | Unmute all sounds.
221 | @method muteSound
222 | **/
223 | unmuteSound: function() {
224 | if (!this._soundGain) return;
225 | this._soundGain.gain.setValueAtTime(game.Audio.soundVolume, this._context.currentTime);
226 | this.mutedSound = false;
227 | },
228 |
229 | /**
230 | @method _decode
231 | @param {XMLHttpRequest} request
232 | @param {String} path
233 | @param {Function} callback
234 | @private
235 | **/
236 | _decode: function(request, path, callback) {
237 | this._context.decodeAudioData(
238 | request.response,
239 | this._loaded.bind(this, path, callback),
240 | this._error.bind(this, path, callback)
241 | );
242 | },
243 |
244 | /**
245 | @method _error
246 | @param {String} path
247 | @param {Function} callback
248 | @private
249 | **/
250 | _error: function(path, callback) {
251 | callback('Error loading audio ' + path);
252 | },
253 |
254 | /**
255 | @method _load
256 | @param {String} path
257 | @param {Function} callback
258 | @private
259 | **/
260 | _load: function(path, callback) {
261 | if (!game.Audio.enabled) {
262 | callback();
263 | return;
264 | }
265 | var ext = path.split('?').shift().split('.').pop();
266 | if (this.formats.indexOf(ext) === -1) ext = this.formats[0];
267 |
268 | var realPath = path.replace(/[^\.]+$/, ext + game._nocache);
269 |
270 | if (!window.AudioContext) {
271 | var audio = new Audio();
272 | audio.src = realPath;
273 | this._loaded(path, callback, audio);
274 | }
275 | else {
276 | var request = new XMLHttpRequest();
277 | request.open('GET', realPath, true);
278 | request.responseType = 'arraybuffer';
279 | request.onload = this._decode.bind(this, request, path, callback);
280 | request.onerror = this._error.bind(this, path, callback);
281 | request.send();
282 | }
283 | },
284 |
285 | /**
286 | @method _loaded
287 | @param {String} path
288 | @param {Function} callback
289 | @param {AudioBuffer} buffer
290 | @private
291 | **/
292 | _loaded: function(path, callback, buffer) {
293 | var id = game._getId(path);
294 | game.Audio.cache[id] = buffer;
295 | callback();
296 | },
297 |
298 | /**
299 | @method _systemPause
300 | @private
301 | **/
302 | _systemPause: function() {
303 | if (this.music && this.music.playing) {
304 | this.music.pause();
305 | this._pauseMusic = this.music;
306 | }
307 | for (var i = 0; i < this.sounds.length; i++) {
308 | if (this.sounds[i].playing) {
309 | this.sounds[i].pause();
310 | this._pauseSounds.push(this.sounds[i]);
311 | }
312 | }
313 | },
314 |
315 | /**
316 | @method _systemResume
317 | @private
318 | **/
319 | _systemResume: function() {
320 | if (this._pauseMusic) this._pauseMusic.resume();
321 | for (var i = 0; i < this._pauseSounds.length; i++) {
322 | this._pauseSounds[i].resume();
323 | }
324 | this._pauseMusic = null;
325 | this._pauseSounds.length = 0;
326 | }
327 | });
328 |
329 | game.addAttributes('Audio', {
330 | /**
331 | Cache for audio buffers.
332 | @attribute {Object} cache
333 | **/
334 | cache: {},
335 |
336 | /**
337 | Clear all audio buffers from cache.
338 | @method clearCache
339 | @static
340 | **/
341 | clearCache: function() {
342 | for (var i in this.cache) {
343 | delete this.cache[i];
344 | }
345 | },
346 |
347 | /**
348 | Is audio enabled.
349 | @attribute {Boolean} enabled
350 | @default true
351 | **/
352 | enabled: true,
353 |
354 | /**
355 | List of supported audio formats.
356 | @attribute {Array} formats
357 | **/
358 | formats: [
359 | { ext: 'ogg', type: 'audio/ogg; codecs="vorbis"' },
360 | { ext: 'm4a', type: 'audio/mp4; codecs="mp4a.40.5"' },
361 | { ext: 'mp3', type: 'audio/mp3' },
362 | { ext: 'wav', type: 'audio/wav' }
363 | ],
364 |
365 | /**
366 | Length to trim looped audio from end (seconds).
367 | @attribute {Number} loopEnd
368 | @default 0
369 | **/
370 | loopEnd: 0,
371 |
372 | /**
373 | Length to trim looped audio from start (seconds).
374 | @attribute {Number} loopStart
375 | @default 0
376 | **/
377 | loopStart: 0,
378 |
379 | /**
380 | Initial music volume (0-1).
381 | @attribute {Number} musicVolume
382 | @default 1
383 | **/
384 | musicVolume: 1,
385 |
386 | /**
387 | Initial sound volume (0-1).
388 | @attribute {Number} soundVolume
389 | @default 1
390 | **/
391 | soundVolume: 1,
392 |
393 | /**
394 | Stop all audio, when changing scene.
395 | @attribute {Boolean} stopOnSceneChange
396 | @default true
397 | **/
398 | stopOnSceneChange: true
399 | });
400 |
401 | /**
402 | @class Sound
403 | @constructor
404 | @param {String} id Audio asset id
405 | **/
406 | game.createClass('Sound', {
407 | /**
408 | Is sound looping.
409 | @property {Boolean} loop
410 | @default false
411 | **/
412 | loop: false,
413 | /**
414 | Is sound muted.
415 | @property {Boolean} muted
416 | @default false
417 | **/
418 | muted: false,
419 | /**
420 | Function to call, when sound is completed.
421 | @property {Function} onComplete
422 | **/
423 | onComplete: null,
424 | /**
425 | Is sound paused.
426 | @property {Boolean} paused
427 | @default false
428 | **/
429 | paused: false,
430 | /**
431 | Is sound playing.
432 | @property {Boolean} playing
433 | @default false
434 | **/
435 | playing: false,
436 | /**
437 | @property {AudioBuffer} _buffer
438 | @private
439 | **/
440 | _buffer: null,
441 | /**
442 | @property {AudioContext} _context
443 | @private
444 | **/
445 | _context: null,
446 | /**
447 | @property {GainNode} _gainNode
448 | @private
449 | **/
450 | _gainNode: null,
451 | /**
452 | @property {Number} _rate
453 | @private
454 | **/
455 | _rate: 1,
456 | /**
457 | @property {AudioBufferSourceNode} _source
458 | @private
459 | **/
460 | _source: null,
461 | /**
462 | @property {Number} _volume
463 | @private
464 | **/
465 | _volume: 1,
466 |
467 | staticInit: function(id) {
468 | if (!game.Audio.enabled) return true;
469 |
470 | this._buffer = game.Audio.cache[id];
471 | if (!this._buffer) throw 'Audio ' + id + ' not found';
472 |
473 | if (!window.AudioContext) return;
474 |
475 | this._context = game.audio._context;
476 | this._gainNode = this._context.createGain();
477 | },
478 |
479 | init: function() {
480 | if (this._gainNode) this._gainNode.connect(game.audio._soundGain);
481 | },
482 |
483 | /**
484 | @method fadeIn
485 | @param {Number} time Time in milliseconds
486 | **/
487 | fadeIn: function(time) {
488 | this._fade(time, this.volume);
489 | },
490 |
491 | /**
492 | @method fadeOut
493 | @param {Number} time Time in milliseconds
494 | **/
495 | fadeOut: function(time) {
496 | this._fade(time, 0);
497 | },
498 |
499 | /**
500 | @method mute
501 | **/
502 | mute: function() {
503 | if (!this._gainNode) this._source.volume = 0;
504 | else this._gainNode.gain.setValueAtTime(0, this._context.currentTime);
505 | this.muted = true;
506 | },
507 |
508 | /**
509 | @method pause
510 | **/
511 | pause: function() {
512 | if (!this._source) return;
513 | if (this.paused) return;
514 | if (!this.playing) return;
515 |
516 | this.stop();
517 | this.paused = true;
518 | this._source.pauseTime = (this._context.currentTime - this._source.startTime) % this._buffer.duration;
519 | },
520 |
521 | /**
522 | @method play
523 | @param {Number} [when] When to start playback in seconds, 0 is now
524 | @param {Number} [offset] Offset of playback in seconds
525 | @param {Number} [duration] Duration of playback in seconds
526 | @chainable
527 | **/
528 | play: function(when, offset, duration) {
529 | if (!this._buffer) return;
530 |
531 | this._onStart();
532 | this.playing = true;
533 |
534 | when = when || 0;
535 | offset = offset || 0;
536 | duration = duration || this._buffer.duration - offset;
537 |
538 | if (!this._context) this._source = this._buffer;
539 | else this._source = this._context.createBufferSource();
540 |
541 | this._source.buffer = this._buffer;
542 | this._source.loop = this.loop;
543 | if (this._source.playbackRate && this._context) this._source.playbackRate.setValueAtTime(this.rate, this._context.currentTime);
544 | this._source.onended = this._onComplete.bind(this);
545 | if (this._source.connect) {
546 | this._source.connect(this._gainNode);
547 | this._source.startTime = this._context.currentTime - offset;
548 | }
549 |
550 | if (!this._context) {
551 | this._source.volume = this.volume;
552 | this._source.currentTime = 0;
553 | this._source.play();
554 | }
555 | else if (this.loop) {
556 | this._source.loopStart = game.Audio.loopStart;
557 | this._source.loopEnd = this._source.buffer.duration - game.Audio.loopEnd;
558 | this._source.start(this._context.currentTime + when, offset);
559 | }
560 | else {
561 | this._source.start(this._context.currentTime + when, offset, duration);
562 | }
563 |
564 | return this;
565 | },
566 |
567 | /**
568 | @method resume
569 | **/
570 | resume: function() {
571 | if (!this._source) return;
572 | if (!this.paused) return;
573 |
574 | this.paused = false;
575 | if (!this._source.pauseTime) return;
576 |
577 | this.play(0, this._source.pauseTime);
578 | },
579 |
580 | /**
581 | @method stop
582 | @param {Boolean} [skipOnComplete] Skip onComplete function
583 | **/
584 | stop: function(skipOnComplete) {
585 | if (!this._source) return;
586 | if (this.paused) return;
587 |
588 | this.playing = false;
589 | if (skipOnComplete) this.onComplete = null;
590 | if (!this._context) this._source.pause();
591 | else if (typeof this._source.playbackState !== 'number' || this._source.playbackState === 2) this._source.stop();
592 | },
593 |
594 | /**
595 | @method unmute
596 | **/
597 | unmute: function() {
598 | if (!this._gainNode) this._source.volume = this._volume;
599 | else this._gainNode.gain.setValueAtTime(this._volume, this._context.currentTime);
600 | this.muted = false;
601 | },
602 |
603 | /**
604 | @method _fade
605 | @param {Number} time
606 | @param {Number} to
607 | @private
608 | **/
609 | _fade: function(time, to) {
610 | if (!this._buffer) return;
611 | time = (time || 1000) / 1000;
612 |
613 | var currTime = this._context.currentTime;
614 | var from = to === this.volume ? 0 : this._gainNode.gain.value;
615 |
616 | this._gainNode.gain.linearRampToValueAtTime(from, currTime);
617 | this._gainNode.gain.linearRampToValueAtTime(to, currTime + time);
618 | },
619 |
620 | /**
621 | @method _onComplete
622 | @private
623 | **/
624 | _onComplete: function() {
625 | game.audio.sounds.erase(this);
626 | if (this.paused) return;
627 | if (typeof this.onComplete === 'function') this.onComplete();
628 | },
629 |
630 | /**
631 | @method _onStart
632 | @private
633 | **/
634 | _onStart: function() {
635 | this.stop();
636 | game.audio.sounds.push(this);
637 | }
638 | });
639 |
640 | game.defineProperties('Sound', {
641 | /**
642 | Duration of audio (seconds).
643 | @property {Number} duration
644 | **/
645 | duration: {
646 | get: function() {
647 | if (this._buffer) return this._buffer.duration;
648 | }
649 | },
650 |
651 | /**
652 | Playback rate of audio (speed).
653 | @property {Number} rate
654 | @default 1
655 | **/
656 | rate: {
657 | get: function() {
658 | return this._rate;
659 | },
660 |
661 | set: function(value) {
662 | this._rate = value;
663 | if (this._source) this._source.playbackRate.value = value;
664 | }
665 | },
666 |
667 | /**
668 | Sound volume (0-1).
669 | @property {Number} volume
670 | @default 1
671 | **/
672 | volume: {
673 | get: function() {
674 | return this._volume;
675 | },
676 |
677 | set: function(value) {
678 | this._volume = value;
679 | if (this._gainNode) this._gainNode.gain.setValueAtTime(value, this._context.currentTime);
680 | }
681 | }
682 | });
683 |
684 | /**
685 | @class Music
686 | @extends Sound
687 | @constructor
688 | @param {String} id Audio asset id
689 | **/
690 | game.createClass('Music', 'Sound', {
691 | /**
692 | @property {Boolean} loop
693 | @default true
694 | **/
695 | loop: true,
696 |
697 | init: function() {
698 | if (this._gainNode) this._gainNode.connect(game.audio._musicGain);
699 | },
700 |
701 | _onStart: function() {
702 | if (game.audio.music) game.audio.music.stop();
703 | game.audio.music = this;
704 | }
705 | });
706 |
707 | });
708 |
--------------------------------------------------------------------------------
/src/engine/camera.js:
--------------------------------------------------------------------------------
1 | /**
2 | @module camera
3 | **/
4 | game.module(
5 | 'engine.camera'
6 | )
7 | .body(function() {
8 |
9 | /**
10 | @class Camera
11 | @constructor
12 | @param {Container} [target]
13 | **/
14 | game.createClass('Camera', {
15 | /**
16 | Camera acceleration speed. Higher is faster.
17 | @property {Number} acceleration
18 | @default 3
19 | **/
20 | acceleration: 3,
21 | /**
22 | Limit the camera movement.
23 | @property {Rectangle} limit
24 | @default Infinity
25 | **/
26 | limit: null,
27 | /**
28 | Camera maximum move speed.
29 | @property {Number} maxSpeed
30 | @default 200
31 | **/
32 | maxSpeed: 200,
33 | /**
34 | Camera offset.
35 | @property {Vector} offset
36 | @default game.width / 2, game.height / 2
37 | **/
38 | offset: null,
39 | /**
40 | Current position of the camera.
41 | @property {Vector} position
42 | **/
43 | position: null,
44 | /**
45 | Round camera position.
46 | @property {Boolean} rounding
47 | @default true
48 | **/
49 | rounding: true,
50 | /**
51 | Position of the camera sensor.
52 | @property {Vector} sensorPosition
53 | **/
54 | sensorPosition: null,
55 | /**
56 | Size of the camera sensor.
57 | @property {Vector} sensorSize
58 | @default 200
59 | **/
60 | sensorSize: null,
61 | /**
62 | Current speed of camera.
63 | @property {Vector} speed
64 | **/
65 | speed: null,
66 | /**
67 | @property {Number} threshold
68 | **/
69 | threshold: 1,
70 | /**
71 | Container, that the camera is moving.
72 | @property {Container} container
73 | @private
74 | **/
75 | _container: null,
76 | /**
77 | Container, that camera follows.
78 | @property {Container} target
79 | @private
80 | **/
81 | _target: null,
82 |
83 | staticInit: function(target) {
84 | this.limit = new game.Rectangle(Infinity, Infinity, -Infinity, -Infinity);
85 | this.position = new game.Vector();
86 | this.speed = new game.Vector();
87 | this.offset = new game.Vector(game.width / 2, game.height / 2);
88 | this.sensorPosition = new game.Vector(this.offset.x, this.offset.y);
89 | this.sensorSize = new game.Vector(200);
90 | if (target) this.setTarget(target);
91 | },
92 |
93 | /**
94 | Add camera to container.
95 | @method addTo
96 | @param {Container} container
97 | @chainable
98 | **/
99 | addTo: function(container) {
100 | this._container = container;
101 | this._container.position.set(-this.position.x, -this.position.y);
102 | this._setSensorPosition();
103 | return this;
104 | },
105 |
106 | /**
107 | Set container, that the camera follows.
108 | @method setTarget
109 | @param {Container} target
110 | **/
111 | setTarget: function(target) {
112 | this._target = target;
113 | this._setSensorPosition();
114 | },
115 |
116 | /**
117 | Set camera position.
118 | @method setPosition
119 | @param {Number|Vector|Container} x
120 | @param {Number} [y]
121 | **/
122 | setPosition: function(x, y) {
123 | if (x instanceof game.Vector || x instanceof game.Container) {
124 | y = x.y;
125 | x = x.x;
126 | }
127 |
128 | if (this._container) {
129 | x *= this._container.scale.x;
130 | y *= this._container.scale.y;
131 | }
132 |
133 | this._setPosition(x, y);
134 | },
135 |
136 | /**
137 | Update camera position.
138 | @method update
139 | **/
140 | update: function() {
141 | this._moveSensor();
142 | this._moveCamera();
143 | },
144 |
145 | /**
146 | @method _moveCamera
147 | @private
148 | **/
149 | _moveCamera: function() {
150 | this.speed.x = (this.position.x - this.sensorPosition.x + this.offset.x).limit(-this.maxSpeed, this.maxSpeed);
151 | this.speed.y = (this.position.y - this.sensorPosition.y + this.offset.y).limit(-this.maxSpeed, this.maxSpeed);
152 |
153 | if (this.speed.x > this.threshold ||
154 | this.speed.x < -this.threshold ||
155 | this.speed.y > this.threshold ||
156 | this.speed.y < -this.threshold
157 | ) {
158 | this._setPosition(
159 | this.position.x + this.offset.x - this.speed.x * this.acceleration * game.delta,
160 | this.position.y + this.offset.y - this.speed.y * this.acceleration * game.delta
161 | );
162 | }
163 | else {
164 | this.speed.set(0, 0);
165 | }
166 | },
167 |
168 | /**
169 | @method _moveSensor
170 | @private
171 | **/
172 | _moveSensor: function() {
173 | if (!this._target || !this._container) return;
174 |
175 | var targetWidth = this._target.width * this._container.scale.x;
176 | var targetHeight = this._target.height * this._container.scale.y;
177 | var targetPosX = (this._target.position.x - this._target.anchor.x + this._target.width / 2 * this._target.scale.x) * this._container.scale.x;
178 | var targetPosY = (this._target.position.y - this._target.anchor.y + this._target.height / 2 * this._target.scale.y) * this._container.scale.y;
179 |
180 | if (this.sensorSize.x < targetWidth || this.sensorSize.y < targetHeight) this.sensorSize.set(targetWidth, targetHeight);
181 |
182 | if (targetPosX < this.sensorPosition.x - this.sensorSize.x / 2 + targetWidth / 2) {
183 | this.sensorPosition.x = targetPosX + this.sensorSize.x / 2 - targetWidth / 2;
184 | }
185 | else if (targetPosX + (this.sensorSize.x / 2 + targetWidth / 2) > this.sensorPosition.x + this.sensorSize.x) {
186 | this.sensorPosition.x = targetPosX + (this.sensorSize.x / 2 + targetWidth / 2) - this.sensorSize.x;
187 | }
188 |
189 | if (targetPosY < this.sensorPosition.y - this.sensorSize.y / 2 + targetHeight / 2) {
190 | this.sensorPosition.y = targetPosY + this.sensorSize.y / 2 - targetHeight / 2;
191 | }
192 | else if (targetPosY + (this.sensorSize.y / 2 + targetHeight / 2) > this.sensorPosition.y + this.sensorSize.y) {
193 | this.sensorPosition.y = targetPosY + (this.sensorSize.y / 2 + targetHeight / 2) - this.sensorSize.y;
194 | }
195 | },
196 |
197 | /**
198 | @method _setPosition
199 | @param {Number} x
200 | @param {Number} y
201 | @private
202 | **/
203 | _setPosition: function(x, y) {
204 | this.position.set(x - this.offset.x, y - this.offset.y);
205 |
206 | if (this.position.x < this.limit.x) {
207 | this.position.x = this.limit.x;
208 | this.speed.x = 0;
209 | }
210 | else if (this.position.x > this.limit.width) {
211 | this.position.x = this.limit.width;
212 | this.speed.x = 0;
213 | }
214 | if (this.position.y < this.limit.y) {
215 | this.position.y = this.limit.y;
216 | this.speed.y = 0;
217 | }
218 | else if (this.position.y > this.limit.height) {
219 | this.position.y = this.limit.height;
220 | this.speed.y = 0;
221 | }
222 |
223 | if (this._container) {
224 | this._container.position.x = -(this.rounding ? (this.position.x + 0.5) | 0 : this.position.x);
225 | this._container.position.y = -(this.rounding ? (this.position.y + 0.5) | 0 : this.position.y);
226 | }
227 | },
228 |
229 | /**
230 | @method _setSensorPosition
231 | @private
232 | **/
233 | _setSensorPosition: function() {
234 | if (!this._target || !this._container) return;
235 | var targetPosX = (this._target.position.x - this._target.anchor.x + this._target.width / 2 * this._target.scale.x) * this._container.scale.x;
236 | var targetPosY = (this._target.position.y - this._target.anchor.y + this._target.height / 2 * this._target.scale.y) * this._container.scale.y;
237 | this.sensorPosition.set(targetPosX, targetPosY);
238 | }
239 | });
240 |
241 | });
242 |
--------------------------------------------------------------------------------
/src/engine/geometry.js:
--------------------------------------------------------------------------------
1 | /**
2 | @module geometry
3 | **/
4 | game.module(
5 | 'engine.geometry'
6 | )
7 | .body(function() {
8 |
9 | /**
10 | @class Arc
11 | @constructor
12 | @param {Number} radius
13 | @param {Number} x
14 | @param {Number} y
15 | @param {Number} startAngle
16 | @param {Number} endAngle
17 | **/
18 | game.createClass('Arc', {
19 | /**
20 | @property {Number} endAngle
21 | @default 0
22 | **/
23 | endAngle: 0,
24 | /**
25 | Radius of arc.
26 | @property {Number} radius
27 | @default 0
28 | **/
29 | radius: 0,
30 | /**
31 | @property {Number} startAngle
32 | @default 0
33 | **/
34 | startAngle: 0,
35 | /**
36 | @property {Number} x
37 | @default 0
38 | **/
39 | x: 0,
40 | /**
41 | @property {Number} y
42 | @default 0
43 | **/
44 | y: 0,
45 |
46 | staticInit: function(radius, x, y, startAngle, endAngle) {
47 | this.startAngle = startAngle || this.startAngle;
48 | this.endAngle = endAngle || this.endAngle;
49 | this.radius = radius || this.radius;
50 | this.x = x || this.x;
51 | this.y = y || this.y;
52 | }
53 | });
54 |
55 | /**
56 | @class Circle
57 | @constructor
58 | @param {Number} radius
59 | @param {Number} x
60 | @param {Number} y
61 | **/
62 | game.createClass('Circle', {
63 | /**
64 | Radius of circle.
65 | @property {Number} radius
66 | @default 0
67 | **/
68 | radius: 0,
69 | /**
70 | @property {Number} x
71 | @default 0
72 | **/
73 | x: 0,
74 | /**
75 | @property {Number} y
76 | @default 0
77 | **/
78 | y: 0,
79 |
80 | staticInit: function(radius, x, y) {
81 | this.radius = radius || this.radius;
82 | this.x = x || this.x;
83 | this.y = y || this.y;
84 | }
85 | });
86 |
87 | /**
88 | @class Curve
89 | @constructor
90 | @param {Number} sx
91 | @param {Number} sy
92 | @param {Number} ex
93 | @param {Number} ey
94 | @param {Number} h1x
95 | @param {Number} h1y
96 | @param {Number} h2x
97 | @param {Number} h2y
98 | **/
99 | game.createClass('Curve', {
100 | /**
101 | End position of curve.
102 | @property {Vector} end
103 | **/
104 | end: null,
105 | /**
106 | Position of first control point.
107 | @property {Vector} handle1
108 | **/
109 | handle1: null,
110 | /**
111 | Position of second control point.
112 | @property {Vector} handle2
113 | **/
114 | handle2: null,
115 | /**
116 | Start position of curve.
117 | @property {Vector} start
118 | **/
119 | start: null,
120 |
121 | staticInit: function(sx, sy, ex, ey, h1x, h1y, h2x, h2y) {
122 | this.start = new game.Vector(sx, sy);
123 | if (typeof ex !== 'number') ex = sx;
124 | if (typeof ey !== 'number') ey = sy;
125 | this.end = new game.Vector(ex, ey);
126 | if (typeof h1x !== 'number') h1x = sx;
127 | if (typeof h1y !== 'number') h1y = sy;
128 | if (typeof h2x !== 'number') h2x = ex;
129 | if (typeof h2y !== 'number') h2y = ey;
130 | this.handle1 = new game.Vector(h1x, h1y);
131 | this.handle2 = new game.Vector(h2x, h2y);
132 | },
133 |
134 | /**
135 | Get point from curve.
136 | @method point
137 | @param {Number} percent Location of the point. 0 is start and 1 is the end of the curve.
138 | @param {Vector} [out] Optional vector, where the values are set.
139 | @return {Vector}
140 | **/
141 | point: function(percent, out) {
142 | out = out || new game.Vector();
143 |
144 | var x = this._interpolate(percent, this.start.x, this.handle1.x, this.handle2.x, this.end.x);
145 | var y = this._interpolate(percent, this.start.y, this.handle1.y, this.handle2.y, this.end.y);
146 |
147 | out.set(x, y);
148 | return out;
149 | },
150 |
151 | /**
152 | @method _calcHandle1
153 | @param {Number} t
154 | @param {Number} p
155 | @return {Number}
156 | @private
157 | **/
158 | _calcHandle1: function(t, p) {
159 | var k = 1 - t;
160 | return 3 * k * k * t * p;
161 | },
162 |
163 | /**
164 | @method _calcHandle2
165 | @param {Number} t
166 | @param {Number} p
167 | @return {Number}
168 | @private
169 | **/
170 | _calcHandle2: function(t, p) {
171 | return 3 * (1 - t) * t * t * p;
172 | },
173 |
174 | /**
175 | @method _calcEnd
176 | @param {Number} t
177 | @param {Number} p
178 | @return {Number}
179 | @private
180 | **/
181 | _calcEnd: function(t, p) {
182 | return t * t * t * p;
183 | },
184 |
185 | /**
186 | @method _calcStart
187 | @param {Number} t
188 | @param {Number} p
189 | @return {Number}
190 | @private
191 | **/
192 | _calcStart: function(t, p) {
193 | var k = 1 - t;
194 | return k * k * k * p;
195 | },
196 |
197 | /**
198 | Get point from curve.
199 | @method _interpolate
200 | @param {Number} percent
201 | @param {Number} s
202 | @param {Number} h1
203 | @param {Number} h2
204 | @param {Number} e
205 | @return {Number}
206 | @private
207 | **/
208 | _interpolate: function(percent, s, h1, h2, e) {
209 | s = this._calcStart(percent, s);
210 | h1 = this._calcHandle1(percent, h1);
211 | h2 = this._calcHandle2(percent, h2);
212 | e = this._calcEnd(percent, e);
213 | return s + h1 + h2 + e;
214 | }
215 | });
216 |
217 | /**
218 | @class Polygon
219 | @constructor
220 | @param {Array} points
221 | **/
222 | game.createClass('Polygon', {
223 | /**
224 | List of points in polygon. Can be list of numbers or vectors.
225 | @property {Array} points
226 | **/
227 | points: [],
228 |
229 | staticInit: function(points) {
230 | if (!points) return;
231 | for (var i = 0; i < points.length; i++) {
232 | if (points[i] instanceof game.Vector) {
233 | this.points.push(points[i]);
234 | }
235 | else if (typeof points[i] === 'number') {
236 | this.points.push(new game.Vector(points[i], points[i + 1]));
237 | i++;
238 | }
239 | else {
240 | // Bezier curve
241 | this.points.push(points[i]);
242 | }
243 | }
244 | },
245 |
246 | /**
247 | Close polygon.
248 | @method close
249 | **/
250 | close: function() {
251 | if (this.points[0] !== this.points[this.points.length - 1]) {
252 | this.points.push(this.points[0]);
253 | }
254 | }
255 | });
256 |
257 | /**
258 | @class Rectangle
259 | @constructor
260 | @param {Number} width
261 | @param {Number} [height]
262 | @param {Number} [x]
263 | @param {Number} [y]
264 | **/
265 | game.createClass('Rectangle', {
266 | /**
267 | @property {Number} height
268 | @default 0
269 | **/
270 | height: 0,
271 | /**
272 | @property {Number} width
273 | @default 0
274 | **/
275 | width: 0,
276 | /**
277 | @property {Number} x
278 | @default 0
279 | **/
280 | x: 0,
281 | /**
282 | @property {Number} y
283 | @default 0
284 | **/
285 | y: 0,
286 |
287 | staticInit: function(width, height, x, y) {
288 | this.set(width, height, x, y);
289 | },
290 |
291 | /**
292 | @method set
293 | @param {Number} width
294 | @param {Number} height
295 | @param {Number} x
296 | @param {Number} y
297 | **/
298 | set: function(width, height, x, y) {
299 | this.width = typeof width === 'number' ? width : this.width;
300 | this.height = typeof height === 'number' ? height : this.width;
301 | this.x = typeof x === 'number' ? x : this.x;
302 | this.y = typeof y === 'number' ? y : this.y;
303 | },
304 |
305 | /**
306 | Swap width and height values.
307 | @method swap
308 | **/
309 | swap: function() {
310 | var height = this.height;
311 | this.height = this.width;
312 | this.width = height;
313 | }
314 | });
315 |
316 | /**
317 | Basic vector class with two values.
318 | @class Vector
319 | @constructor
320 | @param {Number} [x]
321 | @param {Number} [y]
322 | **/
323 | game.createClass('Vector', {
324 | /**
325 | @property {Number} x
326 | @default 0
327 | **/
328 | x: 0,
329 | /**
330 | @property {Number} y
331 | @default 0
332 | **/
333 | y: 0,
334 |
335 | staticInit: function(x, y) {
336 | this.set(x, y);
337 | },
338 |
339 | /**
340 | Add to vector values.
341 | @method add
342 | @param {Number|Vector} x
343 | @param {Number} [y]
344 | @chainable
345 | **/
346 | add: function(x, y) {
347 | this.x += x instanceof game.Vector ? x.x : x;
348 | this.y += x instanceof game.Vector ? x.y : (y || ((y !== 0) ? x : 0));
349 | return this;
350 | },
351 |
352 | /**
353 | Get vector angle or angle between two vectors.
354 | @method angle
355 | @param {Vector} [vector]
356 | @return {Number}
357 | **/
358 | angle: function(vector) {
359 | if (vector instanceof game.Vector) {
360 | return Math.atan2(vector.y - this.y, vector.x - this.x);
361 | }
362 | else {
363 | return Math.atan2(this.y, this.x);
364 | }
365 | },
366 |
367 | /**
368 | Get angle between two vectors from origin.
369 | @method angleFromOrigin
370 | @param {Vector} vector
371 | @return {Number}
372 | **/
373 | angleFromOrigin: function(vector) {
374 | return Math.atan2(vector.y, vector.x) - Math.atan2(this.y, this.x);
375 | },
376 |
377 | /**
378 | Clone vector.
379 | @method clone
380 | @return {Vector}
381 | **/
382 | clone: function() {
383 | return new this.constructor(this.x, this.y);
384 | },
385 |
386 | /**
387 | Is values same with another vector.
388 | @method compare
389 | @param {Vector} vector
390 | @return {Boolean}
391 | **/
392 | compare: function(vector) {
393 | return (this.x === vector.x && this.y === vector.y);
394 | },
395 |
396 | /**
397 | Copy values from another vector.
398 | @method copy
399 | @param {Vector} vector
400 | @chainable
401 | **/
402 | copy: function(vector) {
403 | this.x = vector.x;
404 | this.y = vector.y;
405 | return this;
406 | },
407 |
408 | /**
409 | Get distance between two points.
410 | @method distance
411 | @param {Number|Vector} x
412 | @param {Number} [y]
413 | @chainable
414 | **/
415 | distance: function(x, y) {
416 | var x1 = x instanceof game.Vector ? x.x : x;
417 | var y1 = x instanceof game.Vector ? x.y : (y || ((y !== 0) ? x : 0));
418 | x1 = x1 - this.x;
419 | y1 = y1 - this.y;
420 | return Math.sqrt(x1 * x1 + y1 * y1);
421 | },
422 |
423 | /**
424 | Divide vector values.
425 | @method divide
426 | @param {Number|Vector} x
427 | @param {Number} [y]
428 | @chainable
429 | **/
430 | divide: function(x, y) {
431 | this.x /= x instanceof game.Vector ? x.x : x;
432 | this.y /= x instanceof game.Vector ? x.y : (y || ((y !== 0) ? x : 0));
433 | return this;
434 | },
435 |
436 | /**
437 | Get dot of vector.
438 | @method dot
439 | @param {Vector} [vector]
440 | @return {Number}
441 | **/
442 | dot: function(vector) {
443 | if (vector instanceof game.Vector) return this.x * vector.x + this.y * vector.y;
444 | else return this.x * this.x + this.y * this.y;
445 | },
446 |
447 | /**
448 | Get normalized dot of vector.
449 | @method dotNormalized
450 | @param {Vector} [vector]
451 | @return {Number}
452 | **/
453 | dotNormalized: function(vector) {
454 | var len1 = this.length();
455 | var x1 = this.x / len1;
456 | var y1 = this.y / len1;
457 |
458 | if (vector instanceof game.Vector) {
459 | var len2 = vector.length();
460 | var x2 = vector.x / len2;
461 | var y2 = vector.y / len2;
462 | return x1 * x2 + y1 * y2;
463 | }
464 | else return x1 * x1 + y1 * y1;
465 | },
466 |
467 | /**
468 | Get length of vector.
469 | @method length
470 | @return {Number}
471 | **/
472 | length: function() {
473 | return Math.sqrt(this.dot());
474 | },
475 |
476 | /**
477 | Limit vector values.
478 | @method limit
479 | @param {Vector} vector
480 | @chainable
481 | **/
482 | limit: function(vector) {
483 | this.x = this.x.limit(-vector.x, vector.x);
484 | this.y = this.y.limit(-vector.y, vector.y);
485 | return this;
486 | },
487 |
488 | /**
489 | Change values based on distance and angle.
490 | @method move
491 | @param {Number} distance
492 | @param {Vector|Number} angle
493 | **/
494 | move: function(distance, angle) {
495 | if (angle instanceof game.Vector) angle = this.angle(angle);
496 | this.x += distance * Math.cos(angle);
497 | this.y += distance * Math.sin(angle);
498 | },
499 |
500 | /**
501 | Multiply vector values.
502 | @method multiply
503 | @param {Number|Vector} x
504 | @param {Number} [y]
505 | @chainable
506 | **/
507 | multiply: function(x, y) {
508 | this.x *= x instanceof game.Vector ? x.x : x;
509 | this.y *= x instanceof game.Vector ? x.y : (y || ((y !== 0) ? x : 0));
510 | return this;
511 | },
512 |
513 | /**
514 | Multiply and add vector values.
515 | @method multiplyAdd
516 | @param {Number|Vector} x
517 | @param {Number} [y]
518 | @chainable
519 | **/
520 | multiplyAdd: function(x, y) {
521 | this.x += x instanceof game.Vector ? x.x * y : x * y;
522 | this.y += x instanceof game.Vector ? x.y * y : x * y;
523 | return this;
524 | },
525 |
526 | /**
527 | Normalize vector.
528 | @method normalize
529 | @chainable
530 | **/
531 | normalize: function() {
532 | var len = this.length();
533 | this.x /= len || 1;
534 | this.y /= len || 1;
535 | return this;
536 | },
537 |
538 | /**
539 | Rotate vector in radians.
540 | @method rotate
541 | @param {Number} angle
542 | @chainable
543 | **/
544 | rotate: function(angle) {
545 | var c = Math.cos(angle);
546 | var s = Math.sin(angle);
547 | var x = this.x * c - this.y * s;
548 | var y = this.y * c + this.x * s;
549 | this.x = x;
550 | this.y = y;
551 | return this;
552 | },
553 |
554 | /**
555 | Round vector values.
556 | @method round
557 | @param {Number} [precision]
558 | @chainable
559 | **/
560 | round: function(precision) {
561 | this.x = this.x.round(precision);
562 | this.y = this.y.round(precision);
563 | return this;
564 | },
565 |
566 | /**
567 | Set vector values.
568 | @method set
569 | @param {Number} x
570 | @param {Number} [y]
571 | @chainable
572 | **/
573 | set: function(x, y) {
574 | this.x = typeof x === 'number' ? x : this.x;
575 | this.y = typeof y === 'number' ? y : this.x;
576 | return this;
577 | },
578 |
579 | /**
580 | Subtract from vector values.
581 | @method subtract
582 | @param {Number|Vector} x
583 | @param {Number} [y]
584 | @chainable
585 | **/
586 | subtract: function(x, y) {
587 | this.x -= x instanceof game.Vector ? x.x : x;
588 | this.y -= x instanceof game.Vector ? x.y : (y || ((y !== 0) ? x : 0));
589 | return this;
590 | },
591 |
592 | /**
593 | Swap vector values with another vector.
594 | @method swap
595 | @param {Vector} target
596 | **/
597 | swap: function(target) {
598 | var x = this.x;
599 | var y = this.y;
600 | this.copy(target);
601 | target.set(x, y);
602 | }
603 | });
604 |
605 | });
606 |
--------------------------------------------------------------------------------
/src/engine/particle.js:
--------------------------------------------------------------------------------
1 | /**
2 | @module particle
3 | **/
4 | game.module(
5 | 'engine.particle'
6 | )
7 | .require(
8 | 'engine.renderer.sprite',
9 | 'engine.renderer.fastcontainer'
10 | )
11 | .body(function() {
12 |
13 | /**
14 | Particle emitter, that emits particles using Particle class.
15 | @class Particles
16 | @extends FastContainer
17 | @constructor
18 | @param {String|Array} textures Name of texture or array of texture names.
19 | @param {Object} [options]
20 | **/
21 | game.createClass('Particles', 'FastContainer', {
22 | /**
23 | Acceleration angle in radians.
24 | @property {Number} accelAngle
25 | @default Math.PI / 2
26 | **/
27 | accelAngle: Math.PI / 2,
28 | /**
29 | @property {Number} accelAngleVar
30 | @default 0
31 | **/
32 | accelAngleVar: 0,
33 | /**
34 | Acceleration speed.
35 | @property {Number} accelSpeed
36 | @default 0
37 | **/
38 | accelSpeed: 0,
39 | /**
40 | @property {Number} accelSpeedVar
41 | @default 0
42 | **/
43 | accelSpeedVar: 0,
44 | /**
45 | Is emitter active.
46 | @property {Boolean} active
47 | @default true
48 | **/
49 | active: true,
50 | /**
51 | End alpha for particle.
52 | @property {Number} alphaEnd
53 | @default 0
54 | **/
55 | alphaEnd: 0,
56 | /**
57 | Starting alpha for particle.
58 | @property {Number} alphaStart
59 | @default 1
60 | **/
61 | alphaStart: 1,
62 | /**
63 | Emit angle in radians (0 is right).
64 | @property {Number} angle
65 | @default 0
66 | **/
67 | angle: 0,
68 | /**
69 | Variance of emit angle in radians.
70 | @property {Number} angleVar
71 | @default Math.PI
72 | **/
73 | angleVar: Math.PI,
74 | /**
75 | How many particles to emit.
76 | @property {Number} emitCount
77 | @default 1
78 | **/
79 | emitCount: 1,
80 | /**
81 | How long to emit particles in milliseconds (0 is forever).
82 | @property {Number} emitDuration
83 | @default 0
84 | **/
85 | emitDuration: 0,
86 | /**
87 | How often to emit particles in milliseconds.
88 | @property {Number} emitRate
89 | @default 100
90 | **/
91 | emitRate: 100,
92 | /**
93 | Particle's life in ms (0 is forever).
94 | @property {Number} life
95 | @default 2000
96 | **/
97 | life: 2000,
98 | /**
99 | Particle's life variance.
100 | @property {Number} lifeVar
101 | @default 0
102 | **/
103 | lifeVar: 0,
104 | /**
105 | @property {Function} onComplete
106 | **/
107 | onComplete: null,
108 | /**
109 | List of particles.
110 | @property {Array} particles
111 | **/
112 | particles: [],
113 | /**
114 | Particle start position.
115 | @property {Vector} startPos
116 | **/
117 | startPos: null,
118 | /**
119 | Particle start position variance.
120 | @property {Vector} startPosVar
121 | **/
122 | startPosVar: null,
123 | /**
124 | Particle start rotation.
125 | @property {Number} startRot
126 | **/
127 | startRot: 0,
128 | /**
129 | Particle start rotation variance.
130 | @property {Number} startRotVar
131 | **/
132 | startRotVar: 0,
133 | /**
134 | Use random texture from textures list.
135 | @property {Boolean} randomTexture
136 | **/
137 | randomTexture: true,
138 | /**
139 | Particle's sprite rotation speed.
140 | @property {Number} rotate
141 | @default 0
142 | **/
143 | rotate: 0,
144 | /**
145 | Variance for particle's sprite rotation speed.
146 | @property {Number} rotateVar
147 | @default 0
148 | **/
149 | rotateVar: 0,
150 | /**
151 | @property {Number} scaleEnd
152 | @default 1
153 | **/
154 | scaleEnd: 1,
155 | /**
156 | @property {Number} scaleEndVar
157 | @default 0
158 | **/
159 | scaleEndVar: 0,
160 | /**
161 | Starting scale for particle.
162 | @property {Number} scaleStart
163 | @default 1
164 | **/
165 | scaleStart: 1,
166 | /**
167 | @property {Number} scaleStartVar
168 | @default 0
169 | **/
170 | scaleStartVar: 0,
171 | /**
172 | Particle's initial speed.
173 | @property {Number} speed
174 | @default 100
175 | **/
176 | speed: 100,
177 | /**
178 | Variance for particle's initial speed.
179 | @property {Number} speedVar
180 | @default 0
181 | **/
182 | speedVar: 0,
183 | /**
184 | Target position for particles.
185 | @property {Vector} target
186 | **/
187 | target: null,
188 | /**
189 | Target positions force.
190 | @property {Number} targetForce
191 | @default 0
192 | **/
193 | targetForce: 0,
194 | /**
195 | Update target position to all particles every frame. If this is false and you change target, it will affect only particles created after the change.
196 | @property {Boolean} targetUpdate
197 | @default true
198 | **/
199 | targetUpdate: true,
200 | /**
201 | List of textures.
202 | @property {Array} textures
203 | **/
204 | textures: [],
205 | /**
206 | @property {Vector} velocityLimit
207 | @default 0,0
208 | **/
209 | velocityLimit: null,
210 | /**
211 | Particle's velocity rotation speed.
212 | @property {Number} velRotate
213 | @default 0
214 | **/
215 | velRotate: 0,
216 | /**
217 | Variance for particle's velocity rotation speed.
218 | @property {Number} velRotateVar
219 | @default 0
220 | **/
221 | velRotateVar: 0,
222 | /**
223 | @property {Number} _currentTexture
224 | @private
225 | **/
226 | _currentTexture: 0,
227 | /**
228 | @property {Number} _durationTimer
229 | @private
230 | **/
231 | _durationTimer: 0,
232 | /**
233 | @property {Boolean} _onCompleteCalled
234 | @private
235 | **/
236 | _onCompleteCalled: false,
237 | /**
238 | @property {String} poolName
239 | @private
240 | **/
241 | _poolName: null,
242 | /**
243 | @property {Number} _rateTimer
244 | @private
245 | **/
246 | _rateTimer: 0,
247 |
248 | staticInit: function(textures, options) {
249 | this.super();
250 | if (textures) {
251 | if (typeof textures === 'string') this.textures.push(textures);
252 | else this.textures = textures;
253 | }
254 | this._poolName = game.Particles.poolName;
255 | this.startPos = new game.Vector();
256 | this.startPosVar = new game.Vector();
257 | this.target = new game.Vector();
258 | this.velocityLimit = new game.Vector();
259 | game.pool.create(this._poolName);
260 | game.merge(this, options);
261 | },
262 |
263 | /**
264 | Emit particles.
265 | @method emit
266 | @param {Number} [count]
267 | **/
268 | emit: function(count) {
269 | count = count || this.emitCount;
270 | for (var i = 0; i < count; i++) {
271 | this._addParticle();
272 | }
273 | },
274 |
275 | remove: function() {
276 | this.super();
277 | this._remove = true;
278 | },
279 |
280 | /**
281 | Reset emitter timer.
282 | @method reset
283 | **/
284 | reset: function() {
285 | this._rateTimer = 0;
286 | this._durationTimer = 0;
287 | this.active = true;
288 | this._onCompleteCalled = false;
289 | },
290 |
291 | updateTransform: function() {
292 | if (this._remove) {
293 | for (var i = this.children.length - 1; i >= 0; i--) {
294 | this._removeParticle(this.children[i]);
295 | }
296 | return;
297 | }
298 |
299 | this._durationTimer += game.delta * 1000;
300 | if (this.emitDuration > 0) {
301 | this.active = this._durationTimer < this.emitDuration;
302 | if (!this.active && this.children.length === 0 && typeof this.onComplete === 'function' && !this._onCompleteCalled) {
303 | this.onComplete();
304 | this._onCompleteCalled = true;
305 | }
306 | }
307 |
308 | if (this.emitRate && this.active) {
309 | this._rateTimer += game.delta * 1000;
310 | if (this._rateTimer >= 0) {
311 | this._rateTimer = -this.emitRate;
312 | this.emit();
313 | }
314 | }
315 |
316 | for (var i = this.children.length - 1; i >= 0; i--) {
317 | this.children[i]._update();
318 | }
319 |
320 | this.super();
321 | },
322 |
323 | /**
324 | @method _addParticle
325 | @private
326 | **/
327 | _addParticle: function() {
328 | if (this.randomTexture) {
329 | var texture = this.textures.random();
330 | }
331 | else {
332 | var texture = this.textures[this._currentTexture];
333 | this._currentTexture++;
334 | if (this._currentTexture >= this.textures.length) this._currentTexture = 0;
335 | }
336 | if (!texture) return;
337 |
338 | var particle = game.pool.get(this._poolName);
339 |
340 | if (!particle) particle = new game.Particle(texture);
341 | else particle.setTexture(texture);
342 |
343 | particle.emitter = this;
344 | particle.rotation = this.startRot + this._getVar(this.startRotVar);
345 | particle.alpha = this.alphaStart;
346 | particle.position.x = this.startPos.x + this._getVar(this.startPosVar.x);
347 | particle.position.y = this.startPos.y + this._getVar(this.startPosVar.y);
348 |
349 | var angleVar = this._getVar(this.angleVar);
350 | var angle = this.angle + angleVar;
351 | var speed = this.speed + this._getVar(this.speedVar);
352 |
353 | particle.velocity.x = Math.cos(angle) * speed;
354 | particle.velocity.y = Math.sin(angle) * speed;
355 |
356 | if (this.angleVar !== this.accelAngleVar) angleVar = this._getVar(this.accelAngleVar);
357 | angle = this.accelAngle + angleVar;
358 | speed = this.accelSpeed + this._getVar(this.accelSpeedVar);
359 |
360 | particle.accel.x = Math.cos(angle) * speed;
361 | particle.accel.y = Math.sin(angle) * speed;
362 |
363 | particle.life = this.life + this._getVar(this.lifeVar);
364 | particle.rotateAmount = this.rotate + this._getVar(this.rotateVar);
365 | particle.velRotate = this.velRotate + this._getVar(this.velRotateVar);
366 |
367 | if (this.alphaStart !== this.alphaEnd) {
368 | particle.deltaAlpha = this.alphaEnd - this.alphaStart;
369 | particle.deltaAlpha /= particle.life / 1000;
370 | }
371 | else particle.deltaAlpha = 0;
372 |
373 | var scaleStart = this.scaleStart + this._getVar(this.scaleStartVar);
374 | if (this.scaleStart !== this.scaleEnd) {
375 | particle.deltaScale = (this.scaleEnd + this._getVar(this.scaleEndVar)) - scaleStart;
376 | particle.deltaScale /= particle.life / 1000;
377 | }
378 | else particle.deltaScale = 0;
379 | particle.scale.set(scaleStart);
380 |
381 | particle.anchor.x = particle.texture.width / 2;
382 | particle.anchor.y = particle.texture.height / 2;
383 | particle.target.copy(this.target);
384 |
385 | this.addChild(particle);
386 | },
387 |
388 | /**
389 | @method _getVar
390 | @param {Number} value
391 | @return {Number}
392 | @private
393 | **/
394 | _getVar: function(value) {
395 | return (Math.random() * value) * (Math.random() > 0.5 ? -1 : 1);
396 | },
397 |
398 | /**
399 | Remove particle from emitter.
400 | @method _removeParticle
401 | @param {Particle} particle
402 | @private
403 | **/
404 | _removeParticle: function(particle) {
405 | particle.remove();
406 | game.pool.put(this._poolName, particle);
407 | }
408 | });
409 |
410 | game.addAttributes('Particles', {
411 | /**
412 | @attribute {String} poolName
413 | @default particle
414 | **/
415 | poolName: 'particle'
416 | });
417 |
418 | /**
419 | Particle sprite, that is emitted from Particles class.
420 | @class Particle
421 | @extends Sprite
422 | **/
423 | game.createClass('Particle', 'Sprite', {
424 | /**
425 | @property {Vector} accel
426 | **/
427 | accel: null,
428 | /**
429 | @property {Number} deltaScale
430 | @default 0
431 | **/
432 | deltaScale: 0,
433 | /**
434 | @property {Number} deltaAlpha
435 | @default 0
436 | **/
437 | deltaAlpha: 0,
438 | /**
439 | Particle's emitter.
440 | @property {Emitter} emitter
441 | **/
442 | emitter: null,
443 | /**
444 | @property {Number} life
445 | @default 0
446 | **/
447 | life: 0,
448 | /**
449 | @property {Number} rotateAmount
450 | @default 0
451 | **/
452 | rotateAmount: 0,
453 | /**
454 | @property {Vector} target
455 | **/
456 | target: null,
457 | /**
458 | @property {Vector} velocity
459 | **/
460 | velocity: null,
461 | /**
462 | @property {Number} velRotate
463 | @default 0
464 | **/
465 | velRotate: 0,
466 |
467 | staticInit: function(texture) {
468 | this.super(texture);
469 | this.accel = new game.Vector();
470 | this.target = new game.Vector();
471 | this.velocity = new game.Vector();
472 | },
473 |
474 | /**
475 | @method _update
476 | @private
477 | **/
478 | _update: function() {
479 | if (!this.emitter) return;
480 |
481 | if (this.life > 0) {
482 | this.life -= game.delta * 1000;
483 | if (this.life <= 0) return this.emitter._removeParticle(this);
484 | }
485 |
486 | if (this.emitter.targetForce > 0) {
487 | var target = this.emitter.targetUpdate ? this.emitter.target : this.target;
488 | this.accel.set(target.x - this.position.x, target.y - this.position.y);
489 | var len = Math.sqrt(this.accel.x * this.accel.x + this.accel.y * this.accel.y);
490 | this.accel.x /= len || 1;
491 | this.accel.y /= len || 1;
492 | this.accel.x *= this.emitter.targetForce;
493 | this.accel.y *= this.emitter.targetForce;
494 | }
495 |
496 | this.velocity.x += this.accel.x * game.delta;
497 | this.velocity.y += this.accel.y * game.delta;
498 |
499 | if (this.emitter.velocityLimit.x > 0) {
500 | if (this.velocity.x > this.emitter.velocityLimit.x) this.velocity.x = this.emitter.velocityLimit.x;
501 | if (this.velocity.x < -this.emitter.velocityLimit.x) this.velocity.x = -this.emitter.velocityLimit.x;
502 | }
503 |
504 | if (this.emitter.velocityLimit.y > 0) {
505 | if (this.velocity.y > this.emitter.velocityLimit.y) this.velocity.y = this.emitter.velocityLimit.y;
506 | if (this.velocity.y < -this.emitter.velocityLimit.y) this.velocity.y = -this.emitter.velocityLimit.y;
507 | }
508 |
509 | if (this.velRotate) {
510 | var c = Math.cos(this.velRotate * game.delta);
511 | var s = Math.sin(this.velRotate * game.delta);
512 | var x = this.velocity.x * c - this.velocity.y * s;
513 | var y = this.velocity.y * c + this.velocity.x * s;
514 | this.velocity.x = x;
515 | this.velocity.y = y;
516 | }
517 |
518 | this.position.x += this.velocity.x * game.delta;
519 | this.position.y += this.velocity.y * game.delta;
520 |
521 | if (this.deltaAlpha) {
522 | this.alpha = Math.max(0, this.alpha + this.deltaAlpha * game.delta);
523 | }
524 |
525 | if (this.deltaScale) {
526 | this.scale.x += this.deltaScale * game.delta;
527 | this.scale.y += this.deltaScale * game.delta;
528 | }
529 |
530 | if (this.rotateAmount) {
531 | this.rotation += this.rotateAmount * game.delta;
532 | }
533 | }
534 | });
535 |
536 | });
537 |
--------------------------------------------------------------------------------
/src/engine/physics.js:
--------------------------------------------------------------------------------
1 | /**
2 | @module physics
3 | **/
4 | game.module(
5 | 'engine.physics'
6 | )
7 | .require(
8 | 'engine.renderer.sprite'
9 | )
10 | .body(function() {
11 |
12 | /**
13 | Physics body.
14 | @class Body
15 | @constructor
16 | @param {Object} [properties]
17 | **/
18 | game.createClass('Body', {
19 | /**
20 | Group numbers that body collides against.
21 | @property {Array} collideAgainst
22 | @default []
23 | **/
24 | collideAgainst: [],
25 | /**
26 | Body's damping. Should be number between 0 and 1.
27 | @property {Number} damping
28 | @default 0
29 | **/
30 | damping: 0,
31 | /**
32 | Body's force.
33 | @property {Vector} force
34 | @default 0,0
35 | **/
36 | force: null,
37 | /**
38 | Body's position on last frame.
39 | @property {Vector} last
40 | **/
41 | last: null,
42 | /**
43 | Body's mass.
44 | @property {Number} mass
45 | @default 1
46 | **/
47 | mass: 1,
48 | /**
49 | Position of body.
50 | @property {Vector} position
51 | **/
52 | position: null,
53 | /**
54 | Body's shape.
55 | @property {Rectangle|Circle} shape
56 | **/
57 | shape: null,
58 | /**
59 | Is body static.
60 | @property {Boolean} static
61 | @default false
62 | **/
63 | static: false,
64 | /**
65 | Body's velocity.
66 | @property {Vector} velocity
67 | **/
68 | velocity: null,
69 | /**
70 | Body's maximum velocity.
71 | @property {Vector} velocityLimit
72 | @default 980,980
73 | **/
74 | velocityLimit: null,
75 | /**
76 | Body's physic world.
77 | @property {World} world
78 | **/
79 | world: null,
80 | /**
81 | @property {Array} _collides
82 | @private
83 | **/
84 | _collides: [],
85 | /**
86 | @property {Number} _collisionGroup
87 | @private
88 | **/
89 | _collisionGroup: 0,
90 |
91 | staticInit: function() {
92 | this.force = new game.Vector();
93 | this.position = new game.Vector();
94 | this.velocity = new game.Vector();
95 | this.velocityLimit = new game.Vector(980, 980);
96 | this.last = new game.Vector();
97 | },
98 |
99 | init: function(properties) {
100 | game.merge(this, properties);
101 | return true;
102 | },
103 |
104 | /**
105 | Add shape to body.
106 | @method addShape
107 | @param {Rectangle|Circle} shape
108 | @chainable
109 | **/
110 | addShape: function(shape) {
111 | this.shape = shape;
112 | return this;
113 | },
114 |
115 | /**
116 | Add body to world.
117 | @method addTo
118 | @param {World} world
119 | @chainable
120 | **/
121 | addTo: function(world) {
122 | if (this.world) return;
123 | world.addBody(this);
124 | return this;
125 | },
126 |
127 | /**
128 | This is called after hit response.
129 | @method afterCollide
130 | @param {Body} body body that it collided with.
131 | **/
132 | afterCollide: function() {
133 | },
134 |
135 | /**
136 | Apply impulse to body.
137 | @method applyImpulse
138 | @param {Number|Vector} x
139 | @param {Number} [y]
140 | @chainable
141 | **/
142 | applyImpulse: function(x, y) {
143 | this.velocity.add(x, y);
144 | return this;
145 | },
146 |
147 | /**
148 | This is called, when body collides with another body.
149 | @method collide
150 | @param {Body} body body that it collided with.
151 | @param {String} dir direction of collision.
152 | @return {Boolean} Return true, to apply hit response.
153 | **/
154 | collide: function() {
155 | return true;
156 | },
157 |
158 | /**
159 | Remove body from it's world.
160 | @method remove
161 | @chainable
162 | **/
163 | remove: function() {
164 | if (this.world) this.world.removeBody(this);
165 | return this;
166 | },
167 |
168 | /**
169 | Remove collision from body.
170 | @method removeCollision
171 | @chainable
172 | **/
173 | removeCollision: function() {
174 | if (this.world) this.world._removeBodyCollision(this);
175 | return this;
176 | },
177 |
178 | /**
179 | Update body position and velocity.
180 | @method update
181 | @param {Number} [delta]
182 | **/
183 | update: function(delta) {
184 | delta = delta || game.delta;
185 | this.last.copy(this.position);
186 |
187 | if (this.static) return;
188 |
189 | if (this.world) {
190 | this.velocity.x += this.world.gravity.x * this.mass * delta;
191 | this.velocity.y += this.world.gravity.y * this.mass * delta;
192 | }
193 | this.velocity.x += this.force.x * delta;
194 | this.velocity.y += this.force.y * delta;
195 |
196 | if (this.damping > 0 && this.damping < 1) {
197 | var damping = Math.pow(1 - this.damping, delta);
198 | this.velocity.x *= damping;
199 | this.velocity.y *= damping;
200 | }
201 |
202 | if (this.velocityLimit.x > 0) {
203 | if (this.velocity.x > this.velocityLimit.x) this.velocity.x = this.velocityLimit.x;
204 | if (this.velocity.x < -this.velocityLimit.x) this.velocity.x = -this.velocityLimit.x;
205 | }
206 | if (this.velocityLimit.y > 0) {
207 | if (this.velocity.y > this.velocityLimit.y) this.velocity.y = this.velocityLimit.y;
208 | if (this.velocity.y < -this.velocityLimit.y) this.velocity.y = -this.velocityLimit.y;
209 | }
210 |
211 | this.position.x += this.velocity.x * delta;
212 | this.position.y += this.velocity.y * delta;
213 | }
214 | });
215 |
216 | game.defineProperties('Body', {
217 | /**
218 | Collision group for body.
219 | @property {Number} collisionGroup
220 | @default 0
221 | **/
222 | collisionGroup: {
223 | get: function() {
224 | return this._collisionGroup;
225 | },
226 |
227 | set: function(value) {
228 | if (this._collisionGroup === value) return;
229 |
230 | if (this.world && typeof this._collisionGroup === 'number') this.world._removeBodyCollision(this);
231 | this._collisionGroup = value;
232 | if (this.world) this.world._addBodyCollision(this);
233 | }
234 | }
235 | });
236 |
237 | /**
238 | Physics world.
239 | @class Physics
240 | @constructor
241 | @param {Number} [x] Gravity x
242 | @param {Number} [y] Gravity y
243 | @param {Boolean} [manualUpdate] Don't update physics automatically
244 | **/
245 | game.createClass('Physics', {
246 | /**
247 | List of bodies in world.
248 | @property {Array} bodies
249 | **/
250 | bodies: [],
251 | /**
252 | Gravity of physics world.
253 | @property {Vector} gravity
254 | @default 0,980
255 | **/
256 | gravity: null,
257 | /**
258 | @property {Object} _collisionGroups
259 | @private
260 | **/
261 | _collisionGroups: {},
262 |
263 | staticInit: function(x, y, manualUpdate) {
264 | x = typeof x === 'number' ? x : 0;
265 | y = typeof y === 'number' ? y : 980;
266 | this.gravity = new game.Vector(x, y);
267 | if (game.scene && !manualUpdate) game.scene.physics.push(this);
268 | },
269 |
270 | /**
271 | Add body to world.
272 | @method addBody
273 | @param {Body} body
274 | **/
275 | addBody: function(body) {
276 | body.world = this;
277 | body._remove = false;
278 | this.bodies.push(body);
279 | this._addBodyCollision(body);
280 | },
281 |
282 | /**
283 | Perform collision for body.
284 | @method collide
285 | @param {Body} body
286 | **/
287 | collide: function(body) {
288 | var g, i, b, group;
289 |
290 | for (g = 0; g < body.collideAgainst.length; g++) {
291 | body._collides.length = 0;
292 | group = this._collisionGroups[body.collideAgainst[g]];
293 |
294 | if (!group) continue;
295 |
296 | for (i = group.length - 1; i >= 0; i--) {
297 | if (!group) break;
298 | b = group[i];
299 | if (body !== b) {
300 | if (this.hitTest(body, b)) {
301 | body._collides.push(b);
302 | }
303 | }
304 | }
305 | for (i = body._collides.length - 1; i >= 0; i--) {
306 | if (this.hitResponse(body, body._collides[i])) {
307 | body.afterCollide(body._collides[i]);
308 | }
309 | }
310 | }
311 | },
312 |
313 | /**
314 | Hit response a versus b.
315 | @method hitResponse
316 | @param {Body} a
317 | @param {Body} b
318 | @return {Boolean} Returns true, if body is moved.
319 | **/
320 | hitResponse: function(a, b) {
321 | if (a.static) return false;
322 | if (a.shape.width && b.shape.width) {
323 | if (a.last.y + a.shape.height / 2 <= b.last.y - b.shape.height / 2) {
324 | if (a.collide(b, 'DOWN')) {
325 | a.position.y = b.position.y - b.shape.height / 2 - a.shape.height / 2;
326 | return true;
327 | }
328 | }
329 | else if (a.last.y - a.shape.height / 2 >= b.last.y + b.shape.height / 2) {
330 | if (a.collide(b, 'UP')) {
331 | a.position.y = b.position.y + b.shape.height / 2 + a.shape.height / 2;
332 | return true;
333 | }
334 | }
335 | else if (a.last.x + a.shape.width / 2 <= b.last.x - b.shape.width / 2) {
336 | if (a.collide(b, 'RIGHT')) {
337 | a.position.x = b.position.x - b.shape.width / 2 - a.shape.width / 2;
338 | return true;
339 | }
340 | }
341 | else if (a.last.x - a.shape.width / 2 >= b.last.x + b.shape.width / 2) {
342 | if (a.collide(b, 'LEFT')) {
343 | a.position.x = b.position.x + b.shape.width / 2 + a.shape.width / 2;
344 | return true;
345 | }
346 | }
347 | else {
348 | // Inside
349 | if (a.collide(b)) return true;
350 | }
351 | }
352 | else if (a.shape.radius && b.shape.radius) {
353 | var angle = b.position.angle(a.position);
354 | if (a.collide(b, angle)) {
355 | var dist = a.shape.radius + b.shape.radius;
356 | a.position.x = b.position.x + Math.cos(angle) * dist;
357 | a.position.y = b.position.y + Math.sin(angle) * dist;
358 | return true;
359 | }
360 | }
361 | else {
362 | if (a.collide(b)) return true;
363 | }
364 | return false;
365 | },
366 |
367 | /**
368 | Hit test a versus b.
369 | @method hitTest
370 | @param {Body} a
371 | @param {Body} b
372 | @return {Boolean} return true, if bodies hit.
373 | **/
374 | hitTest: function(a, b) {
375 | if (a.shape.width && b.shape.width) {
376 | return !(
377 | a.position.y + a.shape.height / 2 <= b.position.y - b.shape.height / 2 ||
378 | a.position.y - a.shape.height / 2 >= b.position.y + b.shape.height / 2 ||
379 | a.position.x - a.shape.width / 2 >= b.position.x + b.shape.width / 2 ||
380 | a.position.x + a.shape.width / 2 <= b.position.x - b.shape.width / 2
381 | );
382 | }
383 | if (a.shape.radius && b.shape.radius) {
384 | return (a.shape.radius + b.shape.radius > a.position.distance(b.position));
385 | }
386 | if (a.shape.width && b.shape.radius || a.shape.radius && b.shape.width) {
387 | var rect = a.shape.width ? a : b;
388 | var circle = a.shape.radius ? a : b;
389 |
390 | var x = Math.max(rect.position.x - rect.shape.width / 2, Math.min(rect.position.x + rect.shape.width / 2, circle.position.x));
391 | var y = Math.max(rect.position.y - rect.shape.height / 2, Math.min(rect.position.y + rect.shape.height / 2, circle.position.y));
392 |
393 | var dist = Math.pow(circle.position.x - x, 2) + Math.pow(circle.position.y - y, 2);
394 | return dist < (circle.shape.radius * circle.shape.radius);
395 | }
396 | return false;
397 | },
398 |
399 | /**
400 | Remove body from world.
401 | @method removeBody
402 | @param {Body} body
403 | **/
404 | removeBody: function(body) {
405 | if (!body.world) return;
406 | body.world = null;
407 | body._remove = true;
408 | },
409 |
410 | /**
411 | @method _addBodyCollision
412 | @param {Body} body
413 | @private
414 | **/
415 | _addBodyCollision: function(body) {
416 | if (typeof body.collisionGroup !== 'number') return;
417 | this._collisionGroups[body.collisionGroup] = this._collisionGroups[body.collisionGroup] || [];
418 | if (this._collisionGroups[body.collisionGroup].indexOf(body) !== -1) return;
419 | this._collisionGroups[body.collisionGroup].push(body);
420 | },
421 |
422 | /**
423 | @method _removeBodyCollision
424 | @param {Body} body
425 | @private
426 | **/
427 | _removeBodyCollision: function(body) {
428 | if (typeof body.collisionGroup !== 'number') return;
429 | if (!this._collisionGroups[body.collisionGroup]) return;
430 | if (this._collisionGroups[body.collisionGroup].indexOf(body) === -1) return;
431 | this._collisionGroups[body.collisionGroup].erase(body);
432 | },
433 |
434 | /**
435 | @method _update
436 | @private
437 | **/
438 | _update: function() {
439 | var i, j;
440 | for (i = this.bodies.length - 1; i >= 0; i--) {
441 | if (this.bodies[i]._remove) {
442 | this._removeBodyCollision(this.bodies[i]);
443 | this.bodies.splice(i, 1);
444 | }
445 | else {
446 | this.bodies[i].update();
447 | }
448 | }
449 | },
450 |
451 | /**
452 | @method _updateCollision
453 | @private
454 | **/
455 | _updateCollision: function() {
456 | for (i in this._collisionGroups) {
457 | if (this._collisionGroups[i].length === 0) {
458 | delete this._collisionGroups[i];
459 | continue;
460 | }
461 | for (j = 0; j < this._collisionGroups[i].length; j++) {
462 | if (this._collisionGroups[i][j] && this._collisionGroups[i][j].collideAgainst.length > 0) {
463 | this.collide(this._collisionGroups[i][j]);
464 | }
465 | }
466 | }
467 | }
468 | });
469 |
470 | /**
471 | @class PhysicsSprite
472 | @extends Sprite
473 | @constructor
474 | @param {Texture|String} texture
475 | @param {Number} [width|radius]
476 | @param {Number} [height]
477 | **/
478 | game.createClass('PhysicsSprite', 'Sprite', {
479 | /**
480 | @property {Body} body
481 | **/
482 | body: null,
483 | /**
484 | @property {String} shape
485 | @default Rectangle
486 | **/
487 | shape: 'Rectangle',
488 |
489 | staticInit: function(texture, width, height) {
490 | this.super(texture);
491 | this.anchorCenter();
492 | width = width || this.width;
493 | height = height || this.height;
494 | var shape = new game[this.shape](width, height);
495 | this.body = new game.Body();
496 | this.body.addShape(shape);
497 | this.body.collide = this.collide.bind(this);
498 | this.position = this.body.position;
499 | },
500 |
501 | /**
502 | @method addTo
503 | @param {Container} container
504 | @param {Physics} physics
505 | **/
506 | addTo: function(container, physics) {
507 | physics = physics || game.scene.world;
508 | if (!physics) throw 'addTo: Physics world not defined';
509 | this.body.addTo(physics);
510 | return this.super(container);
511 | },
512 |
513 | /**
514 | This is called, when body collides with another body.
515 | @method collide
516 | @param {Body} body body that it collided with.
517 | @param {String} dir direction of collision.
518 | @return {Boolean} Return true, to apply hit response.
519 | **/
520 | collide: function() {
521 | return true;
522 | },
523 |
524 | /**
525 | Removes sprite from it's parent and also body from it's physics world.
526 | @method remove
527 | **/
528 | remove: function() {
529 | this.body.remove();
530 | return this.super();
531 | }
532 | });
533 |
534 | });
535 |
--------------------------------------------------------------------------------
/src/engine/pool.js:
--------------------------------------------------------------------------------
1 | /**
2 | @module pool
3 | **/
4 | game.module(
5 | 'engine.pool'
6 | )
7 | .body(function() {
8 |
9 | /**
10 | Pool manager. Instance automatically created at `game.pool`
11 | @class Pool
12 | **/
13 | game.createClass('Pool', {
14 | /**
15 | @property {Object} pools
16 | **/
17 | pools: {},
18 |
19 | /**
20 | Create new pool.
21 | @method create
22 | @param {String} pool Name of the pool.
23 | @return {Boolean} Returns false, if pool already exists.
24 | **/
25 | create: function(pool) {
26 | if (!this.pools[pool]) {
27 | this.pools[pool] = [];
28 | return true;
29 | }
30 | return false;
31 | },
32 |
33 | /**
34 | Get object from pool.
35 | @method get
36 | @param {String} pool Name of the pool.
37 | @return {Object}
38 | **/
39 | get: function(pool) {
40 | if (this.pools[pool] && this.pools[pool].length) {
41 | return this.pools[pool].pop();
42 | }
43 | },
44 |
45 | /**
46 | Put object to pool.
47 | @method put
48 | @param {String} pool Name of the pool.
49 | @param {Object} object Object to put to the pool.
50 | @return {Boolean} Returns false, if pool not found.
51 | **/
52 | put: function(pool, object) {
53 | if (!this.pools[pool]) return false;
54 | this.pools[pool].push(object);
55 | return true;
56 | }
57 | });
58 |
59 | });
60 |
--------------------------------------------------------------------------------
/src/engine/renderer/animation.js:
--------------------------------------------------------------------------------
1 | /**
2 | @module renderer.animation
3 | **/
4 | game.module(
5 | 'engine.renderer.animation'
6 | )
7 | .require(
8 | 'engine.renderer.sprite'
9 | )
10 | .body(function() {
11 |
12 | /**
13 | Animation that is generated from multiple textures. Animation can also contain multiple animations created with addAnim method.
14 | @class Animation
15 | @extends Sprite
16 | @constructor
17 | @param {Array|String} textures Array of textures or name of atlas file
18 | **/
19 | game.createClass('Animation', 'Sprite', {
20 | /**
21 | List of animations.
22 | @property {Object} anims
23 | **/
24 | anims: {},
25 | /**
26 | Current active animation.
27 | @property {Animation} currentAnim
28 | **/
29 | currentAnim: null,
30 | /**
31 | Current frame index.
32 | @property {Number} currentFrame
33 | @default 0
34 | **/
35 | currentFrame: 0,
36 | /**
37 | Is animation looping.
38 | @property {Boolean} loop
39 | @default true
40 | **/
41 | loop: true,
42 | /**
43 | Function that is called, when animation is completed.
44 | @property {Function} onComplete
45 | **/
46 | onComplete: null,
47 | /**
48 | Is animation playing.
49 | @property {Boolean} playing
50 | @default false
51 | **/
52 | playing: false,
53 | /**
54 | Play animation in random order.
55 | @property {Boolean} random
56 | @default false
57 | **/
58 | random: false,
59 | /**
60 | Play animation in reverse.
61 | @property {Boolean} reverse
62 | @default false
63 | **/
64 | reverse: false,
65 | /**
66 | Animation speed (frames per second).
67 | @property {Number} speed
68 | @default 10
69 | **/
70 | speed: 10,
71 | /**
72 | List of textures.
73 | @property {Array} textures
74 | **/
75 | textures: null,
76 | /**
77 | @property {Number} _frameTime
78 | @private
79 | **/
80 | _frameTime: 0,
81 |
82 | staticInit: function(textures) {
83 | this.currentAnim = this;
84 | this.textures = this.textures || textures;
85 | if (!this.textures || this.textures.length === 0) throw 'Unable to create animation without textures';
86 |
87 | if (typeof this.textures === 'string' && this.textures.indexOf('atlas') !== -1) {
88 | var json = game.getJSON(this.textures);
89 | this.textures = [];
90 | for (var name in json.frames) {
91 | this.textures.push(name);
92 | }
93 | }
94 |
95 | var newTextures = [];
96 | for (var i = 0; i < this.textures.length; i++) {
97 | var texture = this.textures[i];
98 | if (!texture instanceof game.Texture) texture = game.Texture.fromAsset(texture);
99 | newTextures.push(texture);
100 | }
101 | this.textures = newTextures;
102 |
103 | this.super(this.textures[0]);
104 | },
105 |
106 | /**
107 | Add new animation.
108 | @method addAnim
109 | @param {String} name Name of animation.
110 | @param {Array|Number|String} frames List of invidual frame indexes | List of frame names | Start frame index | Name that each frame starts with.
111 | @param {Number|Object} [frameCount] Number of frames or animation properties.
112 | @param {Object} [props] Animation properties.
113 | @chainable
114 | **/
115 | addAnim: function(name, frames, frameCount, props) {
116 | if (!name || typeof frames === undefined) return;
117 |
118 | if (typeof frameCount === 'object') props = frameCount;
119 |
120 | var textures = [];
121 | if (typeof frames === 'string') {
122 | for (var i = 0; i < this.textures.length; i++) {
123 | var texture = this.textures[i];
124 | if (texture.indexOf(frames) === 0) textures.push(texture);
125 | }
126 | if (textures.length === 0) throw 'No textures found starting with ' + frames;
127 | }
128 | else if (frames.length) {
129 | for (var i = 0; i < frames.length; i++) {
130 | if (typeof frames[i] === 'number') textures[i] = this.textures[frames[i]];
131 | else if (typeof frames[i] === 'string') {
132 | var index = this.textures.indexOf(frames[i]);
133 | if (index !== -1) textures[i] = this.textures[index];
134 | }
135 | }
136 | }
137 | else if (typeof frames === 'number' && typeof frameCount === 'number') {
138 | for (var i = 0; i < frameCount; i++) {
139 | textures[i] = this.textures[frames + i];
140 | }
141 | }
142 |
143 | var anim = new game.Animation(textures);
144 | anim.loop = this.loop;
145 | anim.random = this.random;
146 | anim.reverse = this.reverse;
147 | anim.speed = this.speed;
148 | game.merge(anim, props);
149 |
150 | this.anims[name] = anim;
151 | return this;
152 | },
153 |
154 | /**
155 | Jump to specific frame.
156 | @method gotoFrame
157 | @param {String|Number} name Name of animation or frame index
158 | @param {Number} frame Frame index
159 | @chainable
160 | **/
161 | gotoFrame: function(name, frame) {
162 | if (typeof name === 'string') this.currentAnim = this.anims[name] || this;
163 | if (typeof name === 'number') frame = name;
164 |
165 | if (!this.currentAnim.textures) throw 'No textures found for animation';
166 | if (!this.currentAnim.textures[frame]) return;
167 | this.currentFrame = frame;
168 | this._frameTime = 0;
169 | this.setTexture(this.currentAnim.textures[frame]);
170 | return this;
171 | },
172 |
173 | /**
174 | Play animation.
175 | @method play
176 | @param {String|Number} [name] Name of animation or frame index
177 | @param {Number} [frame] Frame index
178 | @chainable
179 | **/
180 | play: function(name, frame) {
181 | this.playing = true;
182 | this.currentAnim = this.anims[name] || this;
183 |
184 | if (typeof name === 'number') frame = name;
185 | if (typeof frame !== 'number' && this.reverse) {
186 | frame = this.textures.length - 1;
187 | }
188 |
189 | this.gotoFrame(frame || 0);
190 | return this;
191 | },
192 |
193 | /**
194 | Stop animation.
195 | @method stop
196 | @param {String|Number} [name] Name of animation or frame index
197 | @param {Number} [frame] Frame index
198 | @chainable
199 | **/
200 | stop: function(name, frame) {
201 | this.playing = false;
202 | this.currentAnim = this.anims[name] || this;
203 |
204 | if (typeof name === 'number') frame = name;
205 | if (typeof frame === 'number') this.gotoFrame(frame);
206 | return this;
207 | },
208 |
209 | updateTransform: function() {
210 | if (this.playing) this._updateAnimation();
211 | this.super();
212 | },
213 |
214 | /**
215 | @method _updateAnimation
216 | @private
217 | **/
218 | _updateAnimation: function() {
219 | if (game.scene.paused && game.scene._pausedAnims.indexOf(this) !== -1) return;
220 | var anim = this.currentAnim;
221 | if (!anim.textures) throw 'No textures found for animation';
222 | this._frameTime += anim.speed * game.delta;
223 |
224 | if (this._frameTime >= 1) {
225 | this._frameTime = this._frameTime % 1;
226 |
227 | if (anim.random && anim.textures.length > 1) {
228 | var nextFrame = this.currentFrame;
229 | while (nextFrame === this.currentFrame) {
230 | nextFrame = Math.round(Math.random(0, anim.textures.length - 1));
231 | }
232 |
233 | this.currentFrame = nextFrame;
234 | this.setTexture(anim.textures[nextFrame]);
235 | return;
236 | }
237 |
238 | var nextFrame = this.currentFrame + (anim.reverse ? -1 : 1);
239 |
240 | if (nextFrame >= anim.textures.length) {
241 | if (anim.loop) {
242 | this.currentFrame = 0;
243 | this.setTexture(anim.textures[0]);
244 | }
245 | else {
246 | this.playing = false;
247 | if (anim.onComplete) anim.onComplete();
248 | }
249 | }
250 | else if (nextFrame < 0) {
251 | if (anim.loop) {
252 | this.currentFrame = anim.textures.length - 1;
253 | this.setTexture(anim.textures.last());
254 | }
255 | else {
256 | this.playing = false;
257 | if (anim.onComplete) anim.onComplete();
258 | }
259 | }
260 | else {
261 | this.currentFrame = nextFrame;
262 | this.setTexture(anim.textures[nextFrame]);
263 | }
264 | }
265 | }
266 | });
267 |
268 | game.addAttributes('Animation', {
269 | /**
270 | Create animation from textures starting with name.
271 | @method fromTextures
272 | @static
273 | @param {String} name
274 | @return {Animation}
275 | **/
276 | fromTextures: function(name) {
277 | var textures = [];
278 | for (var texture in game.Texture.cache) {
279 | if (texture.indexOf(name) !== -1) {
280 | textures.push(texture);
281 | }
282 | }
283 | if (textures.length === 0) {
284 | for (var texture in game.BaseTexture.cache) {
285 | if (texture.indexOf(name) !== -1) {
286 | textures.push(texture);
287 | }
288 | }
289 | }
290 | if (textures.length > 0) {
291 | textures.sort(game.compare);
292 | return new game.Animation(textures);
293 | }
294 | else {
295 | throw 'No textures found for ' + name;
296 | }
297 | }
298 | });
299 |
300 | });
301 |
--------------------------------------------------------------------------------
/src/engine/renderer/core.js:
--------------------------------------------------------------------------------
1 | /**
2 | @module renderer.core
3 | **/
4 | game.module(
5 | 'engine.renderer.core'
6 | )
7 | .require(
8 | 'engine.renderer.animation',
9 | 'engine.renderer.container',
10 | 'engine.renderer.fastcontainer',
11 | 'engine.renderer.graphics',
12 | 'engine.renderer.sprite',
13 | 'engine.renderer.spritesheet',
14 | 'engine.renderer.text',
15 | 'engine.renderer.texture',
16 | 'engine.renderer.tilingsprite'
17 | )
18 | .body(function() {
19 |
20 | /**
21 | Canvas renderer. Instance automatically created at `game.renderer`
22 | @class Renderer
23 | @constructor
24 | @param {Number} width
25 | @param {Number} height
26 | **/
27 | game.createClass('Renderer', {
28 | /**
29 | @property {HTMLCanvasElement} canvas
30 | **/
31 | canvas: null,
32 | /**
33 | @property {CanvasRenderingContext2D} context
34 | **/
35 | context: null,
36 | /**
37 | @property {String} _smoothProperty
38 | @private
39 | **/
40 | _smoothProperty: null,
41 |
42 | init: function(width, height) {
43 | if (!game.device.cocoonCanvasPlus) {
44 | this.canvas = document.getElementById(game.System.canvasId);
45 | }
46 |
47 | if (!this.canvas) {
48 | this.canvas = document.createElement('canvas');
49 | this.canvas.id = game.System.canvasId;
50 | this.canvas.style.display = 'block';
51 | this.canvas.style.outline = 'none';
52 | if (game.Renderer.scaleMode === 'nearest') this.canvas.style.imageRendering = 'pixelated';
53 | this.canvas.tabIndex = 1;
54 | document.body.appendChild(this.canvas);
55 | if (!game.System.center) document.body.style.margin = 0;
56 | }
57 |
58 | game._normalizeVendorAttribute(this.canvas, 'requestFullScreen');
59 |
60 | this._initContext();
61 |
62 | if ('imageSmoothingEnabled' in this.context) this._smoothProperty = 'imageSmoothingEnabled';
63 | else if ('webkitImageSmoothingEnabled' in this.context) this._smoothProperty = 'webkitImageSmoothingEnabled';
64 | else if ('mozImageSmoothingEnabled' in this.context) this._smoothProperty = 'mozImageSmoothingEnabled';
65 | else if ('oImageSmoothingEnabled' in this.context) this._smoothProperty = 'oImageSmoothingEnabled';
66 | else if ('msImageSmoothingEnabled' in this.context) this._smoothProperty = 'msImageSmoothingEnabled';
67 |
68 | this._resize(width, height);
69 | },
70 |
71 | /**
72 | Clear canvas.
73 | @method _clear
74 | @private
75 | **/
76 | _clear: function() {
77 | if (game.scene.backgroundColor) {
78 | this.context.fillStyle = game.scene.backgroundColor;
79 | this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
80 | }
81 | else {
82 | this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
83 | }
84 | },
85 |
86 | /**
87 | Hide canvas.
88 | @method _hide
89 | @private
90 | **/
91 | _hide: function() {
92 | this.canvas.style.display = 'none';
93 | },
94 |
95 | /**
96 | @method _initContext
97 | @private
98 | **/
99 | _initContext: function() {
100 | this.context = this.canvas.getContext('2d');
101 | },
102 |
103 | /**
104 | Set canvas position with CSS.
105 | @method _position
106 | @param {Number} x
107 | @param {Number} y
108 | @private
109 | **/
110 | _position: function(x, y) {
111 | this.canvas.style.position = 'absolute';
112 | this.canvas.style.left = x + 'px';
113 | this.canvas.style.top = y + 'px';
114 | },
115 |
116 | /**
117 | Render container to canvas.
118 | @method _render
119 | @param {Container} container
120 | @private
121 | **/
122 | _render: function(container) {
123 | this.context.setTransform(1, 0, 0, 1, 0, 0);
124 | this.context.globalAlpha = 1;
125 | this.context.globalCompositeOperation = 'source-over';
126 | if (game.Renderer.clearBeforeRender) this._clear();
127 | container._updateChildTransform();
128 | container._render(this.context);
129 | },
130 |
131 | /**
132 | Resize canvas.
133 | @method _resize
134 | @param {Number} width
135 | @param {Number} height
136 | @private
137 | **/
138 | _resize: function(width, height) {
139 | this.canvas.width = width;
140 | this.canvas.height = height;
141 | if (this._smoothProperty) this.context[this._smoothProperty] = (game.Renderer.scaleMode === 'linear');
142 | },
143 |
144 | /**
145 | Show canvas.
146 | @method _show
147 | @private
148 | **/
149 | _show: function() {
150 | this.canvas.style.display = 'block';
151 | },
152 |
153 | /**
154 | Set canvas size with CSS.
155 | @method _size
156 | @param {Number} width
157 | @param {Number} height
158 | @private
159 | **/
160 | _size: function(width, height) {
161 | this.canvas.style.width = width + 'px';
162 | this.canvas.style.height = height + 'px';
163 | }
164 | });
165 |
166 | game.addAttributes('Renderer', {
167 | /**
168 | Clear canvas on start of every frame.
169 | @attribute {Boolean} clearBeforeRender
170 | @default true
171 | **/
172 | clearBeforeRender: true,
173 | /**
174 | Use round positions.
175 | @attribute {Boolean} roundPixels
176 | @default false
177 | **/
178 | roundPixels: false,
179 | /**
180 | Set scaleMode to nearest to disable smoothing (great for scaled pixel art).
181 | @attribute {String} scaleMode
182 | @default linear
183 | **/
184 | scaleMode: 'linear'
185 | });
186 |
187 | game.createClass('Matrix', {
188 | a: 1,
189 | b: 0,
190 | c: 0,
191 | d: 1,
192 | tx: 0,
193 | ty: 0,
194 |
195 | reset: function() {
196 | var proto = this.constructor.prototype;
197 | this.a = proto.a;
198 | this.b = proto.b;
199 | this.c = proto.c;
200 | this.d = proto.d;
201 | this.tx = proto.tx;
202 | this.ty = proto.ty;
203 | return this;
204 | }
205 | });
206 |
207 | game.addAttributes('Matrix', {
208 | empty: new game.Matrix()
209 | });
210 |
211 | });
212 |
--------------------------------------------------------------------------------
/src/engine/renderer/fastcontainer.js:
--------------------------------------------------------------------------------
1 | /**
2 | @module renderer.fastcontainer
3 | **/
4 | game.module(
5 | 'engine.renderer.fastcontainer'
6 | )
7 | .require(
8 | 'engine.renderer.container'
9 | )
10 | .body(function() {
11 |
12 | /**
13 | Special version of Container, that renders faster if it's sprites are not rotated.
14 | @class FastContainer
15 | @extends Container
16 | **/
17 | game.createClass('FastContainer', 'Container', {
18 | /**
19 | @property {Boolean} _isRotated
20 | @private
21 | **/
22 | _isRotated: true,
23 |
24 | _getBounds: function() {
25 | this._worldBounds.x = 0;
26 | this._worldBounds.y = 0;
27 | this._worldBounds.width = 0;
28 | this._worldBounds.height = 0;
29 | return this._worldBounds;
30 | },
31 |
32 | /**
33 | @method _renderBatch
34 | @param {Sprite} child
35 | @param {CanvasRenderingContext2D} context
36 | @private
37 | **/
38 | _renderBatch: function(child, context) {
39 | context.globalAlpha = this._worldAlpha * child.alpha;
40 |
41 | var wt = this._worldTransform;
42 | var texture = child.texture;
43 | if (child._cachedSprite) texture = child._cachedSprite.texture;
44 | var tx = texture.position.x * game.scale;
45 | var ty = texture.position.y * game.scale;
46 | var tw = texture.width * game.scale;
47 | var th = texture.height * game.scale;
48 |
49 | if (child.rotation % (Math.PI * 2) === 0) {
50 | if (this._isRotated) {
51 | context.setTransform(wt.a, wt.b, wt.c, wt.d, wt.tx * game.scale, wt.ty * game.scale);
52 | this._isRotated = false;
53 | }
54 |
55 | var x = (child.position.x - child.anchor.x * child.scale.x) * game.scale;
56 | var y = (child.position.y - child.anchor.y * child.scale.y) * game.scale;
57 |
58 | context.drawImage(texture.baseTexture.source, tx, ty, tw, th, x, y, tw * child.scale.x, th * child.scale.y);
59 | }
60 | else {
61 | this._isRotated = true;
62 |
63 | child.updateTransform();
64 | var cwt = child._worldTransform;
65 | var x = cwt.tx * game.scale;
66 | var y = cwt.ty * game.scale;
67 |
68 | if (game.Renderer.roundPixels) {
69 | x = x | 0;
70 | y = y | 0;
71 | }
72 |
73 | context.setTransform(cwt.a, cwt.b, cwt.c, cwt.d, x, y);
74 | context.drawImage(texture.baseTexture.source, tx, ty, tw, th, 0, 0, tw, th);
75 | }
76 | },
77 |
78 | _renderChildren: function(context) {
79 | this._isRotated = true;
80 |
81 | for (var i = 0; i < this.children.length; i++) {
82 | var child = this.children[i];
83 | var hasTexture = child.texture || child._cachedSprite;
84 | if (!child.visible || child.alpha <= 0 || !child.renderable || !hasTexture) continue;
85 |
86 | this._renderBatch(child, context);
87 | }
88 | },
89 |
90 | _updateChildTransform: function() {
91 | for (var i = this.children.length - 1; i >= 0; i--) {
92 | var child = this.children[i];
93 | if (!child.visible || child.alpha <= 0) continue;
94 | if (child.updateAnimation) child.updateAnimation();
95 | }
96 | }
97 | });
98 |
99 | });
100 |
--------------------------------------------------------------------------------
/src/engine/renderer/graphics.js:
--------------------------------------------------------------------------------
1 | /**
2 | @module renderer.graphics
3 | **/
4 | game.module(
5 | 'engine.renderer.graphics'
6 | )
7 | .require(
8 | 'engine.renderer.container'
9 | )
10 | .body(function() {
11 |
12 | /**
13 | @class Graphics
14 | @extends Container
15 | **/
16 | game.createClass('Graphics', 'Container', {
17 | /**
18 | @property {String} fillColor
19 | @default #fff
20 | **/
21 | fillColor: '#fff',
22 | /**
23 | @property {Number} fillAlpha
24 | @default 1
25 | **/
26 | fillAlpha: 1,
27 | /**
28 | @property {Number} lineAlpha
29 | @default 1
30 | **/
31 | lineAlpha: 1,
32 | /**
33 | @property {String} lineColor
34 | @default #fff
35 | **/
36 | lineColor: '#fff',
37 | /**
38 | @property {Number} lineWidth
39 | @default 0
40 | **/
41 | lineWidth: 0,
42 | /**
43 | @property {String} blendMode
44 | @default source-over
45 | **/
46 | blendMode: 'source-over',
47 | /**
48 | @property {Array} shapes
49 | **/
50 | shapes: [],
51 |
52 | /**
53 | @method beginFill
54 | @param {String} [color]
55 | @param {Number} [alpha]
56 | @chainable
57 | **/
58 | beginFill: function(color, alpha) {
59 | this.fillColor = color || this.fillColor;
60 | this.fillAlpha = alpha || this.fillAlpha;
61 | return this;
62 | },
63 |
64 | /**
65 | @method clear
66 | @chainable
67 | **/
68 | clear: function() {
69 | this.shapes.length = 0;
70 | return this;
71 | },
72 |
73 | /**
74 | @method drawArc
75 | @param {Number} x
76 | @param {Number} y
77 | @param {Number} radius
78 | @param {Number} startAngle
79 | @param {Number} endAngle
80 | @chainable
81 | **/
82 | drawArc: function(x, y, radius, startAngle, endAngle) {
83 | radius *= game.scale;
84 | var shape = new game.Arc(radius, x, y, startAngle, endAngle);
85 | this._drawShape(shape);
86 | return this;
87 | },
88 |
89 | /**
90 | @method drawCircle
91 | @param {Number} x
92 | @param {Number} y
93 | @param {Number} radius
94 | @chainable
95 | **/
96 | drawCircle: function(x, y, radius) {
97 | radius *= game.scale;
98 | var shape = new game.Circle(radius, x, y);
99 | this._drawShape(shape);
100 | return this;
101 | },
102 |
103 | /**
104 | Draw bezier curve.
105 | @method drawCurve
106 | @param {Curve|Number} sx
107 | @param {Number} sy
108 | @param {Number} ex
109 | @param {Number} ey
110 | @param {Number} h1x
111 | @param {Number} h1y
112 | @param {Number} h2x
113 | @param {Number} h2y
114 | @chainable
115 | **/
116 | drawCurve: function(sx, sy, ex, ey, h1x, h1y, h2x, h2y) {
117 | this.lineWidth = this.lineWidth || 1;
118 | var shape = typeof sx === 'number' ? new game.Curve(sx, sy, ex, ey, h1x, h1y, h2x, h2y) : sx;
119 | this._drawShape(shape, true);
120 | return this;
121 | },
122 |
123 | /**
124 | @method drawLine
125 | @param {Number} sx Start x
126 | @param {Number} sy Start y
127 | @param {Number} tx End x
128 | @param {Number} ty End y
129 | @chainable
130 | **/
131 | drawLine: function(sx, sy, tx, ty) {
132 | if (!tx && !ty) {
133 | tx = sx;
134 | ty = sy;
135 | sx = 0;
136 | sy = 0;
137 | }
138 | this.lineWidth = this.lineWidth || 1;
139 | tx *= game.scale;
140 | ty *= game.scale;
141 | var shape = new game.Rectangle(tx, ty, sx, sy);
142 | this._drawShape(shape, true);
143 | return this;
144 | },
145 |
146 | /**
147 | @method drawPolygon
148 | @param {Array} points List of points.
149 | @param {Boolean} [close] Close the polygon.
150 | @chainable
151 | **/
152 | drawPolygon: function(points, close) {
153 | var poly = new game.Polygon(points);
154 | if (close) poly.close();
155 | this._drawShape(poly);
156 | return this;
157 | },
158 |
159 | /**
160 | @method drawRect
161 | @param {Number} x
162 | @param {Number} y
163 | @param {Number} width
164 | @param {Number} height
165 | @chainable
166 | **/
167 | drawRect: function(x, y, width, height) {
168 | height = height || width;
169 | width *= game.scale;
170 | height *= game.scale;
171 | var shape = new game.Rectangle(width, height, x, y);
172 | this._drawShape(shape);
173 | return this;
174 | },
175 |
176 | /**
177 | @method lineStyle
178 | @param {Number} [width]
179 | @param {String} [color]
180 | @param {Number} [alpha]
181 | @chainable
182 | **/
183 | lineStyle: function(width, color, alpha) {
184 | this.lineWidth = width || this.lineWidth;
185 | this.lineColor = color || this.lineColor;
186 | this.lineAlpha = alpha || this.lineAlpha;
187 | return this;
188 | },
189 |
190 | /**
191 | @method _drawShape
192 | @param {Rectangle|Circle} shape
193 | @private
194 | **/
195 | _drawShape: function(shape, isLine) {
196 | var data = new game.GraphicsShape(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, shape, isLine);
197 | this.shapes.push(data);
198 | },
199 |
200 | _getBounds: function() {
201 | var wt = this._worldTransform;
202 | var a = wt.a;
203 | var b = wt.b;
204 | var c = wt.c;
205 | var d = wt.d;
206 | var tx = wt.tx;
207 | var ty = wt.ty;
208 | var width = 0;
209 | var height = 0;
210 |
211 | for (var i = 0; i < this.shapes.length; i++) {
212 | var data = this.shapes[i];
213 | var sx = data.shape.x;
214 | var sy = data.shape.y;
215 |
216 | if (data.shape.radius) {
217 | sx += data.shape.radius / game.scale;
218 | sy += data.shape.radius / game.scale;
219 | }
220 | else {
221 | sx += data.shape.width / game.scale;
222 | sy += data.shape.height / game.scale;
223 | }
224 |
225 | width = Math.max(width, sx);
226 | height = Math.max(height, sy);
227 | }
228 |
229 | var x2 = a * width + tx;
230 | var y2 = b * width + ty;
231 | var x3 = a * width + c * height + tx;
232 | var y3 = d * height + b * width + ty;
233 | var x4 = c * height + tx;
234 | var y4 = d * height + ty;
235 |
236 | var minX = Math.min(tx, x2, x3, x4);
237 | var minY = Math.min(ty, y2, y3, y4);
238 | var maxX = Math.max(tx, x2, x3, x4);
239 | var maxY = Math.max(ty, y2, y3, y4);
240 |
241 | this._worldBounds.x = minX;
242 | this._worldBounds.y = minY;
243 | this._worldBounds.width = maxX - minX;
244 | this._worldBounds.height = maxY - minY;
245 | return this._worldBounds;
246 | },
247 |
248 | _renderCanvas: function(context) {
249 | var wt = this._worldTransform;
250 | var tx = wt.tx * game.scale;
251 | var ty = wt.ty * game.scale;
252 |
253 | context.globalCompositeOperation = this.blendMode;
254 | context.setTransform(wt.a, wt.b, wt.c, wt.d, tx, ty);
255 |
256 | for (var i = 0; i < this.shapes.length; i++) {
257 | this.shapes[i]._render(context, this._worldAlpha);
258 | }
259 | },
260 |
261 | /**
262 | @method _renderMask
263 | @param {CanvasRenderingContext2D} context
264 | @param {Matrix} transform
265 | @private
266 | **/
267 | _renderMask: function(context, transform) {
268 | var wt = transform;
269 | var tx = wt.tx * game.scale;
270 | var ty = wt.ty * game.scale;
271 |
272 | context.save();
273 | context.setTransform(wt.a, wt.b, wt.c, wt.d, tx, ty);
274 | context.beginPath();
275 | for (var i = 0; i < this.shapes.length; i++) {
276 | this.shapes[i]._renderShape(context);
277 | }
278 | context.closePath();
279 | context.clip();
280 | }
281 | });
282 |
283 | /**
284 | @class GraphicsShape
285 | @constructor
286 | @param {Number} lineWidth
287 | @param {String} lineColor
288 | @param {Number} lineAlpha
289 | @param {String} fillColor
290 | @param {Number} fillAlpha
291 | @param {Rectangle|Circle} shape
292 | **/
293 | game.createClass('GraphicsShape', {
294 | /**
295 | @property {Number} fillAlpha
296 | **/
297 | fillAlpha: 1,
298 | /**
299 | @property {String} fillColor
300 | **/
301 | fillColor: '',
302 | /**
303 | @property {Boolean} isLine
304 | **/
305 | isLine: false,
306 | /**
307 | @property {Number} lineAlpha
308 | **/
309 | lineAlpha: 0,
310 | /**
311 | @property {String} lineColor
312 | **/
313 | lineColor: '',
314 | /**
315 | @property {Number} lineWidth
316 | **/
317 | lineWidth: 0,
318 | /**
319 | @property {Arc|Circle|Rectangle} shape
320 | **/
321 | shape: null,
322 |
323 | staticInit: function(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, shape, isLine) {
324 | this.lineWidth = lineWidth;
325 | this.lineColor = lineColor;
326 | this.lineAlpha = lineAlpha;
327 | this.fillColor = fillColor;
328 | this.fillAlpha = fillAlpha;
329 | this.shape = shape;
330 | this.isLine = isLine || this.isLine;
331 | },
332 |
333 | /**
334 | @method _render
335 | @param {CanvasRenderingContext2D} context
336 | @param {Number} alpha
337 | @private
338 | **/
339 | _render: function(context, alpha) {
340 | context.globalAlpha = this.fillAlpha * alpha;
341 | context.fillStyle = this.fillColor;
342 | context.strokeStyle = this.lineColor;
343 | context.lineWidth = this.lineWidth * game.scale;
344 | context.beginPath();
345 |
346 | this._renderShape(context);
347 |
348 | if (this.fillColor && this.fillAlpha && !this.isLine) context.fill();
349 | if (this.lineWidth) {
350 | context.globalAlpha = this.lineAlpha * alpha;
351 | context.stroke();
352 | }
353 | },
354 |
355 | /**
356 | @method _renderShape
357 | @param {CanvasRenderingContext2D} context
358 | @private
359 | **/
360 | _renderShape: function(context) {
361 | var shape = this.shape;
362 | var x = shape.x * game.scale;
363 | var y = shape.y * game.scale;
364 |
365 | if (this.isLine && shape.start) {
366 | context.moveTo(shape.start.x * game.scale, shape.start.y * game.scale);
367 | context.bezierCurveTo(
368 | shape.handle1.x * game.scale,
369 | shape.handle1.y * game.scale,
370 | shape.handle2.x * game.scale,
371 | shape.handle2.y * game.scale,
372 | shape.end.x * game.scale,
373 | shape.end.y * game.scale
374 | );
375 | }
376 | else if (this.isLine) {
377 | context.moveTo(x, y);
378 | context.lineTo(shape.width, shape.height);
379 | }
380 | else if (shape.width) {
381 | context.rect(x, y, shape.width, shape.height);
382 | }
383 | else if (shape.radius) {
384 | if (typeof shape.startAngle === 'number' && typeof shape.endAngle === 'number') {
385 | context.moveTo(x, y);
386 | context.arc(x, y, shape.radius, shape.startAngle, shape.endAngle);
387 | context.closePath();
388 | }
389 | else {
390 | context.arc(x, y, shape.radius, 0, Math.PI * 2);
391 | }
392 | }
393 | else if (shape.points) {
394 | context.moveTo(0, 0);
395 | for (var i = 0; i < shape.points.length; i++) {
396 | var point = shape.points[i];
397 | var x = point.x;
398 | var y = point.y;
399 | if (x === undefined && point.length === undefined) {
400 | x = point;
401 | y = shape.points[i + 1];
402 | i++;
403 | }
404 | else if (x === undefined) {
405 | context.bezierCurveTo(point[0], point[1], point[2], point[3], point[4], point[5]);
406 | continue;
407 | }
408 | context.lineTo(x * game.scale, y * game.scale);
409 | }
410 | }
411 | }
412 | });
413 |
414 | });
415 |
--------------------------------------------------------------------------------
/src/engine/renderer/sprite.js:
--------------------------------------------------------------------------------
1 | /**
2 | @module renderer.sprite
3 | **/
4 | game.module(
5 | 'engine.renderer.sprite'
6 | )
7 | .require(
8 | 'engine.renderer.container'
9 | )
10 | .body(function() {
11 |
12 | /**
13 | @class Sprite
14 | @extends Container
15 | @constructor
16 | @param {Texture|String} texture
17 | @param {Object} [props]
18 | **/
19 | game.createClass('Sprite', 'Container', {
20 | /**
21 | Blend mode for sprite rendering.
22 | @property {String} blendMode
23 | @default source-over
24 | **/
25 | blendMode: 'source-over',
26 | /**
27 | Texture for sprite.
28 | @property {Texture} texture
29 | **/
30 | texture: null,
31 | /**
32 | Tint sprite with color.
33 | @property {String} tint
34 | **/
35 | tint: null,
36 | /**
37 | Alpha of sprite tint.
38 | @property {Number} tintAlpha
39 | @default 1
40 | **/
41 | tintAlpha: 1,
42 | /**
43 | Crop tint area. x is from left, y is from top, width is from right and height is from bottom.
44 | @property {Rectangle} tintCrop
45 | **/
46 | tintCrop: null,
47 | /**
48 | @property {Number} _tintAlpha
49 | @private
50 | **/
51 | _tintAlpha: 1,
52 | /**
53 | @property {String} _tintColor
54 | @private
55 | **/
56 | _tintColor: null,
57 |
58 | staticInit: function(texture, props) {
59 | this.tintCrop = new game.Rectangle();
60 | this.super(props);
61 | this.setTexture(this.texture || texture);
62 | },
63 |
64 | /**
65 | Change texture to sprite.
66 | @method setTexture
67 | @param {Texture|String} texture Asset id or instance of Texture
68 | **/
69 | setTexture: function(texture) {
70 | this.texture = texture instanceof game.Texture ? texture : game.Texture.fromAsset(texture);
71 | },
72 |
73 | /**
74 | @method _destroyTintedTexture
75 | @private
76 | **/
77 | _destroyTintedTexture: function() {
78 | if (this._tintedTexture) {
79 | this._tintedTexture.baseTexture.remove();
80 | this._tintedTexture.remove();
81 | }
82 | this._tintedTexture = null;
83 | this._tintedTextureGenerated = false;
84 | },
85 |
86 | /**
87 | @method _generateTintedTexture
88 | @param {String} color
89 | @return {Texture}
90 | @private
91 | **/
92 | _generateTintedTexture: function(color, alpha) {
93 | var canvas = game.Sprite._canvas;
94 | var context = game.Sprite._context;
95 |
96 | canvas.width = this.texture.width * game.scale;
97 | canvas.height = this.texture.height * game.scale;
98 |
99 | this._tintColor = color;
100 | this._tintAlpha = alpha || 1;
101 |
102 | context.fillStyle = this._tintColor.substr(0, 7);
103 | context.globalAlpha = this._tintAlpha;
104 | var x = this.tintCrop.x * game.scale;
105 | var y = this.tintCrop.y * game.scale;
106 | var width = canvas.width - x - this.tintCrop.width * game.scale;
107 | var height = canvas.height - y - this.tintCrop.height * game.scale;
108 | context.fillRect(x, y, width, height);
109 | context.globalAlpha = 1;
110 |
111 | var blendMode = this.blendMode;
112 | this.blendMode = 'destination-atop';
113 | alpha = this._worldAlpha;
114 | this._worldAlpha = 1;
115 | this._renderCanvas(context, game.Matrix.empty);
116 | this._worldAlpha = alpha;
117 | this.blendMode = blendMode;
118 |
119 | var texture = game.Texture.fromImage(canvas.toDataURL());
120 | texture.width = canvas.width;
121 | texture.height = canvas.height;
122 | game.Sprite._tintedTextures.push(texture);
123 | return texture;
124 | },
125 |
126 | _getBounds: function(transform) {
127 | if (this._cachedSprite) {
128 | this._worldBounds.x = this._worldTransform.tx + this._cachedSprite.position.x;
129 | this._worldBounds.y = this._worldTransform.ty + this._cachedSprite.position.y;
130 | this._worldBounds.width = this._cachedSprite.texture.width * this._worldTransform.a;
131 | this._worldBounds.height = this._cachedSprite.texture.height * this._worldTransform.d;
132 | return this._worldBounds;
133 | }
134 |
135 | if (this._lastTransformUpdate < game.Timer._lastFrameTime) this.updateTransform();
136 |
137 | var width = this.texture.width;
138 | var height = this.texture.height;
139 | var lt = transform || this._worldTransform;
140 | var a = lt.a;
141 | var b = lt.b;
142 | var c = lt.c;
143 | var d = lt.d;
144 | var tx = lt.tx;
145 | var ty = lt.ty;
146 | var x2 = a * width + tx;
147 | var y2 = b * width + ty;
148 | var x3 = a * width + c * height + tx;
149 | var y3 = d * height + b * width + ty;
150 | var x4 = c * height + tx;
151 | var y4 = d * height + ty;
152 |
153 | var minX = Math.min(tx, x2, x3, x4);
154 | var minY = Math.min(ty, y2, y3, y4);
155 | var maxX = Math.max(tx, x2, x3, x4);
156 | var maxY = Math.max(ty, y2, y3, y4);
157 |
158 | for (var i = 0; i < this.children.length; i++) {
159 | if (!this.children[i].visible || this.children[i].alpha <= 0) continue;
160 | var childBounds = this.children[i]._getBounds();
161 | var childMinX = childBounds.x;
162 | var childMaxX = childMinX + childBounds.width;
163 | var childMinY = childBounds.y;
164 | var childMaxY = childMinY + childBounds.height;
165 | minX = Math.min(minX, childMinX);
166 | minY = Math.min(minY, childMinY);
167 | maxX = Math.max(maxX, childMaxX);
168 | maxY = Math.max(maxY, childMaxY);
169 | }
170 |
171 | this._worldBounds.x = minX;
172 | this._worldBounds.y = minY;
173 | this._worldBounds.width = maxX - minX;
174 | this._worldBounds.height = maxY - minY;
175 | return this._worldBounds;
176 | },
177 |
178 | /**
179 | @method _renderCanvas
180 | @param {CanvasRenderingContext2D} context
181 | @param {Matrix} [transform]
182 | @param {Rectangle} [rect]
183 | @param {Rectangle} [offset]
184 | @private
185 | **/
186 | _renderCanvas: function(context, transform, rect, offset) {
187 | if (!this.texture) return true;
188 | if (!this.texture.baseTexture.loaded) return true;
189 |
190 | if (!this.texture.width && this.texture.baseTexture.width) {
191 | this.texture.width = this.texture.baseTexture.width;
192 | }
193 | if (!this.texture.height && this.texture.baseTexture.height) {
194 | this.texture.height = this.texture.baseTexture.height;
195 | }
196 |
197 | if (!this.texture.width || !this.texture.height) return true;
198 |
199 | if (this.tint && !this._tintedTexture && !this._tintedTextureGenerated && this.tintAlpha > 0) {
200 | this._tintedTextureGenerated = true;
201 | this._tintedTexture = this._generateTintedTexture(this.tint, this.tintAlpha);
202 | }
203 | else if (this.tint && this.tint !== this._tintColor && this.tintAlpha > 0 || this.tint && this.tintAlpha !== this._tintAlpha && this.tintAlpha > 0) {
204 | this._destroyTintedTexture();
205 | this._tintedTextureGenerated = true;
206 | this._tintedTexture = this._generateTintedTexture(this.tint, this.tintAlpha);
207 | }
208 | else if (!this.tint && this._tintedTexture || this.tintAlpha === 0 && this._tintedTexture) {
209 | this._destroyTintedTexture();
210 | }
211 |
212 | context.globalCompositeOperation = this.blendMode;
213 | context.globalAlpha = this._worldAlpha;
214 |
215 | var t = this._tintedTexture || this.texture;
216 | var wt = transform || this._worldTransform;
217 | var tx = wt.tx;
218 | var ty = wt.ty;
219 |
220 | if (game.Renderer.roundPixels) {
221 | tx = tx | 0;
222 | ty = ty | 0;
223 | }
224 |
225 | tx *= game.scale;
226 | ty *= game.scale;
227 |
228 | var x = t.position.x * game.scale;
229 | var y = t.position.y * game.scale;
230 | var width = t.width * game.scale;
231 | var height = t.height * game.scale;
232 |
233 | if (rect) {
234 | x = rect.x;
235 | y = rect.y;
236 | width = rect.width;
237 | height = rect.height;
238 | }
239 |
240 | if (offset) {
241 | tx += offset.x;
242 | ty += offset.y;
243 | }
244 |
245 | context.setTransform(wt.a, wt.b, wt.c, wt.d, tx, ty);
246 |
247 | if (t._trim.x) width = t._trim.x;
248 | if (t._trim.y) height = t._trim.y;
249 | var sx = t._offset.x - t._anchor.x;
250 | var sy = t._offset.y - t._anchor.y;
251 |
252 | context.drawImage(t.baseTexture.source, x, y, width, height, sx, sy, width, height);
253 | }
254 | });
255 |
256 | game.defineProperties('Sprite', {
257 | width: {
258 | get: function() {
259 | return Math.abs(this.scale.x) * this.texture.width;
260 | },
261 |
262 | set: function(value) {
263 | this.scale.x = value / this.texture.width;
264 | }
265 | },
266 |
267 | height: {
268 | get: function() {
269 | return Math.abs(this.scale.y) * this.texture.height;
270 | },
271 |
272 | set: function(value) {
273 | this.scale.y = value / this.texture.height;
274 | }
275 | }
276 | });
277 |
278 | game.addAttributes('Sprite', {
279 | /**
280 | @attribute {HTMLCanvasElement} _canvas
281 | @private
282 | **/
283 | _canvas: null,
284 | /**
285 | @attribute {CanvasRenderingContext2D} _context
286 | @private
287 | **/
288 | _context: null,
289 | /**
290 | @attribute {Array} _tintedTextures
291 | @private
292 | **/
293 | _tintedTextures: [],
294 |
295 | /**
296 | @method _clearTintedTextures
297 | @static
298 | @private
299 | **/
300 | _clearTintedTextures: function() {
301 | for (var i = 0; i < this._tintedTextures.length; i++) {
302 | this._tintedTextures[i].baseTexture.remove();
303 | this._tintedTextures[i].remove();
304 | }
305 | this._tintedTextures.length = 0;
306 | }
307 | });
308 |
309 | if (typeof document !== 'undefined') {
310 | game.Sprite._canvas = document.createElement('canvas');
311 | game.Sprite._context = game.Sprite._canvas.getContext('2d');
312 | }
313 |
314 | });
315 |
--------------------------------------------------------------------------------
/src/engine/renderer/spritesheet.js:
--------------------------------------------------------------------------------
1 | /**
2 | @module renderer.spritesheet
3 | **/
4 | game.module(
5 | 'engine.renderer.spritesheet'
6 | )
7 | .require(
8 | 'engine.renderer.sprite'
9 | )
10 | .body(function() {
11 |
12 | /**
13 | Sprite which contains multiple textures from sprite sheet with fixed frame size.
14 | @class SpriteSheet
15 | @extends Sprite
16 | @constructor
17 | @param {String} id Asset id
18 | @param {Number} width Sprite frame width
19 | @param {Number} height Sprite frame height
20 | **/
21 | game.createClass('SpriteSheet', 'Sprite', {
22 | /**
23 | Width of frame.
24 | @property {Number} frameWidth
25 | **/
26 | frameWidth: 0,
27 | /**
28 | Height of frame.
29 | @property {Number} frameHeight
30 | **/
31 | frameHeight: 0,
32 | /**
33 | List of textures.
34 | @property {Array} textures
35 | **/
36 | textures: [],
37 |
38 | staticInit: function(id, frameWidth, frameHeight) {
39 | this.frameWidth = this.frameWidth || frameWidth;
40 | this.frameHeight = this.frameHeight || frameHeight;
41 | if (!this.frameHeight) this.frameHeight = this.frameWidth;
42 | var baseTexture = game.BaseTexture.cache[game.paths[id]];
43 | if (!baseTexture) throw 'No texture found for ' + id;
44 | var sx = Math.floor(baseTexture.width / this.frameWidth);
45 | var sy = Math.floor(baseTexture.height / this.frameHeight);
46 | this.frames = sx * sy;
47 |
48 | for (var i = 0; i < this.frames; i++) {
49 | var x = (i % sx) * this.frameWidth;
50 | var y = Math.floor(i / sx) * this.frameHeight;
51 | var texture = new game.Texture(baseTexture, x, y, this.frameWidth, this.frameHeight);
52 | this.textures.push(texture);
53 | }
54 |
55 | this.super(this.textures[0]);
56 | },
57 |
58 | /**
59 | Set texture to specific frame.
60 | @method frame
61 | @param {Number} index Frame index
62 | **/
63 | frame: function(index) {
64 | if (!this.textures[index]) return;
65 | this.texture = this.textures[index];
66 | return this;
67 | }
68 | });
69 |
70 | game.addAttributes('SpriteSheet', {
71 | /**
72 | Create animation from spritesheet.
73 | @method anim
74 | @static
75 | @param {String} id Asset id
76 | @param {Number} width Sprite frame width
77 | @param {Number} height Sprite frame height
78 | @param {Number|Array} frames List or number of frames
79 | @param {Number} [startIndex] The index to begin with, default to 0
80 | @param {Boolean} [onlyTextures] Return only textures in array
81 | @return {Animation|Array}
82 | **/
83 | anim: function(id, frameWidth, frameHeight, frames, startIndex, onlyTextures) {
84 | var sprite = new game.SpriteSheet(id, frameWidth, frameHeight);
85 |
86 | startIndex = startIndex || 0;
87 | frames = frames || sprite.textures.length;
88 | var textures = [];
89 | if (frames.length > 0) {
90 | for (var i = 0; i < frames.length; i++) {
91 | textures.push(sprite.textures[startIndex + frames[i]]);
92 | }
93 | }
94 | else {
95 | for (var i = 0; i < frames; i++) {
96 | textures.push(sprite.textures[startIndex + i]);
97 | }
98 | }
99 | if (onlyTextures) return textures;
100 | return new game.Animation(textures);
101 | }
102 | });
103 |
104 | });
105 |
--------------------------------------------------------------------------------
/src/engine/renderer/text.js:
--------------------------------------------------------------------------------
1 | /**
2 | @module renderer.text
3 | **/
4 | game.module(
5 | 'engine.renderer.text'
6 | )
7 | .require(
8 | 'engine.renderer.sprite'
9 | )
10 | .body(function() {
11 |
12 | /**
13 | @class Font
14 | @constructor
15 | @param {XML|JSON} data
16 | **/
17 | game.createClass('Font', {
18 | /**
19 | @property {BaseTexture} baseTexture
20 | **/
21 | baseTexture: null,
22 | /**
23 | @property {Object} chars
24 | **/
25 | chars: {},
26 | /**
27 | @property {Number} letterSpacing
28 | @default 0
29 | **/
30 | letterSpacing: 0,
31 | /**
32 | @property {Number} lineHeight
33 | **/
34 | lineHeight: 0,
35 | /**
36 | @property {Number} spaceWidth
37 | **/
38 | spaceWidth: 0,
39 |
40 | staticInit: function(data) {
41 | if (data.getElementsByTagName) {
42 | var image = data.getElementsByTagName('page')[0].getAttribute('file');
43 | var info = data.getElementsByTagName('info')[0];
44 | var common = data.getElementsByTagName('common')[0];
45 | var chars = data.getElementsByTagName('char');
46 | }
47 | else {
48 | var image = data.pages[0].file;
49 | var info = data.info;
50 | var common = data.common;
51 | var chars = data.chars;
52 | }
53 |
54 | this.baseTexture = game.BaseTexture.fromImage(game._getFilePath(image));
55 | if (data.getElementsByTagName) this.lineHeight = parseInt(common.getAttribute('lineHeight'));
56 | else this.lineHeight = parseInt(common.lineHeight);
57 |
58 | for (var i = 0; i < chars.length; i++) {
59 | if (data.getElementsByTagName) {
60 | var xadvance = parseInt(chars[i].getAttribute('xadvance'));
61 | var id = parseInt(chars[i].getAttribute('id'));
62 | }
63 | else {
64 | var xadvance = parseInt(chars[i].xadvance);
65 | var id = parseInt(chars[i].id);
66 | }
67 |
68 | if (id === 32) {
69 | this.spaceWidth = xadvance;
70 | continue;
71 | }
72 |
73 | if (data.getElementsByTagName) {
74 | var xoffset = parseInt(chars[i].getAttribute('xoffset'));
75 | var yoffset = parseInt(chars[i].getAttribute('yoffset'));
76 | var x = parseInt(chars[i].getAttribute('x')) / game.scale;
77 | var y = parseInt(chars[i].getAttribute('y')) / game.scale;
78 | var width = parseInt(chars[i].getAttribute('width')) / game.scale;
79 | var height = parseInt(chars[i].getAttribute('height')) / game.scale;
80 | }
81 | else {
82 | var xoffset = parseInt(chars[i].xoffset);
83 | var yoffset = parseInt(chars[i].yoffset);
84 | var x = parseInt(chars[i].x) / game.scale;
85 | var y = parseInt(chars[i].y) / game.scale;
86 | var width = parseInt(chars[i].width) / game.scale;
87 | var height = parseInt(chars[i].height) / game.scale;
88 | }
89 |
90 | var texture = new game.Texture(this.baseTexture, x, y, width, height);
91 |
92 | this.chars[id] = {
93 | texture: texture,
94 | xadvance: xadvance,
95 | xoffset: xoffset,
96 | yoffset: yoffset
97 | };
98 | }
99 | }
100 | });
101 |
102 | game.addAttributes('Font', {
103 | /**
104 | @attribute {Object} cache
105 | **/
106 | cache: {},
107 |
108 | /**
109 | @method fromData
110 | @static
111 | @param {XML|JSON} data
112 | **/
113 | fromData: function(data) {
114 | if (data.getElementsByTagName) {
115 | var info = data.getElementsByTagName('info')[0];
116 | var face = info.getAttribute('face');
117 | }
118 | else {
119 | var face = data.info.face;
120 | }
121 |
122 | var font = game.Font.cache[face];
123 |
124 | if (!font) {
125 | font = new game.Font(data);
126 | game.Font.cache[face] = font;
127 | if (!game.Text.defaultFont) game.Text.defaultFont = face;
128 | }
129 |
130 | return font;
131 | },
132 |
133 | /**
134 | @method clearCache
135 | @static
136 | **/
137 | clearCache: function() {
138 | for (var i in this.cache) {
139 | delete this.cache[i];
140 | }
141 | }
142 | });
143 |
144 | /**
145 | Text that uses bitmap fonts for rendering.
146 | @class Text
147 | @extends Container
148 | @constructor
149 | @param {String} text
150 | @param {Object} [props]
151 | **/
152 | game.createClass('Text', 'Container', {
153 | /**
154 | Align for multi-lined text. Can be left, center or right.
155 | @property {String} align
156 | **/
157 | align: 'left',
158 | /**
159 | Name of the font that this text is using.
160 | @property {String} font
161 | **/
162 | font: null,
163 | /**
164 | Font class that this text is using.
165 | @property {Font} fontClass
166 | **/
167 | fontClass: null,
168 | /**
169 | If text height is higher than maxHeight value, text will be scaled down to fit maxHeight. 0 to disable.
170 | @property {Number} maxHeight
171 | @default 0
172 | **/
173 | maxHeight: 0,
174 | /**
175 | If text width is higher than maxWidth value, text will be scaled down to fit maxWidth. 0 to disable.
176 | @property {Number} maxWidth
177 | @default 0
178 | **/
179 | maxWidth: 0,
180 | /**
181 | Current text value.
182 | @property {String} text
183 | **/
184 | text: null,
185 | /**
186 | If text width is higher than wrap value, text will be wrapped to multiple lines. 0 to disable.
187 | @property {Number} wrap
188 | **/
189 | wrap: 0,
190 | /**
191 | @property {Object} _lines
192 | @private
193 | **/
194 | _lines: null,
195 |
196 | staticInit: function(text, props) {
197 | this.super();
198 | text = (typeof text === 'string' || typeof text === 'number') ? text : '';
199 | this.text = this.text || text.toString();
200 | game.merge(this, props);
201 | this.font = this.font || game.Text.defaultFont;
202 | if (this.font) this.setFont(this.font);
203 | },
204 |
205 | /**
206 | Set new font for text.
207 | @method setFont
208 | @param {String} fontName
209 | @chainable
210 | **/
211 | setFont: function(fontName) {
212 | this.font = fontName;
213 | this.fontClass = game.Font.cache[fontName];
214 | if (!this.fontClass) throw 'Font ' + fontName + ' not found';
215 | if (this.text) this.setText(this.text);
216 | return this;
217 | },
218 |
219 | /**
220 | Set new text.
221 | @method setText
222 | @param {String|Number} text
223 | @chainable
224 | **/
225 | setText: function(text) {
226 | this.text = text.toString();
227 | this.updateText();
228 | return this;
229 | },
230 |
231 | /**
232 | Update text texture.
233 | @method updateText
234 | **/
235 | updateText: function() {
236 | if (!this.fontClass) return;
237 |
238 | this.removeAll();
239 |
240 | var SPACE = 0;
241 | var WORD = 1;
242 | var lines = [{ words: [], width: 0 }];
243 | var curLine = 0;
244 | var curWordWidth = 0;
245 | var wordText = '';
246 | var wordNum = 1;
247 |
248 | for (var i = 0; i < this.text.length; i++) {
249 | var charCode = this.text.charCodeAt(i);
250 |
251 | // Space or line break
252 | if (charCode === 32 || charCode === 10) {
253 | if (curWordWidth > 0) {
254 | // Word before space or line break
255 | var lineWidth = lines[curLine].width + curWordWidth;
256 | if (lineWidth > this.wrap && this.wrap > 0 && lines[curLine].words.length > 0) {
257 | // Insert new line
258 | curLine++;
259 | lines.push({ words: [], width: 0 });
260 | }
261 |
262 | // Insert new word
263 | lines[curLine].words.push({
264 | width: curWordWidth,
265 | type: WORD,
266 | text: wordText,
267 | num: wordNum
268 | });
269 | lines[curLine].width += curWordWidth;
270 | wordText = '';
271 | wordNum++;
272 | }
273 | }
274 | else {
275 | wordText += this.text[i];
276 | }
277 |
278 | if (charCode === 32) {
279 | // Insert space
280 | lines[curLine].words.push({
281 | width: this.fontClass.spaceWidth,
282 | type: SPACE,
283 | text: ' ',
284 | num: wordNum
285 | });
286 | lines[curLine].width += this.fontClass.spaceWidth;
287 | curWordWidth = 0;
288 | wordNum++;
289 | continue;
290 | }
291 |
292 | if (charCode === 10) {
293 | // Insert line break
294 | curLine++;
295 | lines.push({ words: [], width: 0 });
296 | curWordWidth = 0;
297 | continue;
298 | }
299 |
300 | var charObj = this.fontClass.chars[charCode];
301 | if (!charObj) continue;
302 |
303 | curWordWidth += charObj.xadvance + this.fontClass.letterSpacing;
304 | }
305 |
306 | // Add last word
307 | if (curWordWidth > 0) {
308 | var lineWidth = lines[curLine].width + curWordWidth;
309 | if (lineWidth > this.wrap && this.wrap > 0 && lines[curLine].words.length > 0) {
310 | // New line
311 | curLine++;
312 | lines.push({ words: [], width: 0 });
313 | }
314 |
315 | lines[curLine].words.push({
316 | width: curWordWidth,
317 | type: WORD,
318 | text: wordText,
319 | num: wordNum
320 | });
321 | lines[curLine].width += curWordWidth;
322 | }
323 |
324 | var width = 0;
325 | for (var i = 0; i < lines.length; i++) {
326 | var line = lines[i];
327 | if (line.width > width) width = line.width;
328 | for (var o = line.words.length - 1; o >= 0; o--) {
329 | if (line.words[o].type === SPACE) {
330 | line.width -= this.fontClass.spaceWidth;
331 | }
332 | else break;
333 | }
334 | }
335 |
336 | this._lines = lines;
337 | this._generateText(width);
338 | },
339 |
340 | /**
341 | @method _generateText
342 | @param {Number} width
343 | @private
344 | **/
345 | _generateText: function(width) {
346 | var x = 0;
347 | var y = 0;
348 | var curLine = 0;
349 | var curWord = 0;
350 | var prevChar = 0;
351 |
352 | if (this.align === 'center') x = width / 2 - this._lines[0].width / 2;
353 | if (this.align === 'right') x = width - this._lines[0].width;
354 |
355 | for (var i = 0; i < this.text.length; i++) {
356 | var line = this._lines[curLine];
357 |
358 | // End of line
359 | if (!line.words[curWord]) {
360 | if (line.words.length === 0) y += this.fontClass.lineHeight; // Empty line
361 | curLine++;
362 | if (!this._lines[curLine]) curLine--;
363 | curWord = 0;
364 |
365 | if (x > 0) y += this.fontClass.lineHeight;
366 | x = 0;
367 |
368 | if (this.align === 'center') x = width / 2 - this._lines[curLine].width / 2;
369 | if (this.align === 'right') x = width - this._lines[curLine].width;
370 | }
371 |
372 | var charCode = this.text.charCodeAt(i);
373 |
374 | // Space
375 | if (charCode === 32) {
376 | // Only add space if not beginning of line
377 | if (x > 0) {
378 | x += this.fontClass.spaceWidth;
379 | if (prevChar !== 32) curWord++;
380 | }
381 | curWord++;
382 | }
383 |
384 | // Line break
385 | if (charCode === 10 && x > 0) {
386 | y += this.fontClass.lineHeight;
387 | x = 0;
388 | curWord++;
389 | }
390 |
391 | prevChar = charCode;
392 |
393 | var charObj = this.fontClass.chars[charCode];
394 | if (!charObj || charCode === 10) continue;
395 |
396 | var texture = charObj.texture;
397 | if (i === 0) x -= charObj.xoffset;
398 |
399 | var sprite = new game.Sprite(texture);
400 | sprite.position.x = (x + charObj.xoffset) / game.scale;
401 | sprite.position.y = (y + charObj.yoffset) / game.scale;
402 | this.addChild(sprite);
403 |
404 | x += charObj.xadvance + this.fontClass.letterSpacing;
405 | }
406 |
407 | this.updateTransform();
408 |
409 | if (!this.maxWidth && !this.maxHeight) return;
410 |
411 | var scale = 1;
412 |
413 | if (this.maxWidth && this.width > this.maxWidth) {
414 | scale = this.maxWidth / this.width;
415 | }
416 | if (this.maxHeight && this.height > this.maxHeight) {
417 | scale = this.maxHeight / this.height;
418 | }
419 |
420 | this.scale.set(scale);
421 | }
422 | });
423 |
424 | game.addAttributes('Text', {
425 | /**
426 | Default font for text.
427 | @attribute {String} defaultFont
428 | **/
429 | defaultFont: null
430 | });
431 |
432 | /**
433 | Text that uses canvas fillText for rendering.
434 | @class SystemText
435 | @extends Container
436 | @constructor
437 | @param {String} text
438 | @param {Object} [props]
439 | **/
440 | game.createClass('SystemText', 'Container', {
441 | /**
442 | Align of the text. Can be left, right or center.
443 | @property {String} align
444 | @default left
445 | **/
446 | align: 'left',
447 | /**
448 | Baseline alignment.
449 | @property {String} baseline
450 | @default alphabetic
451 | **/
452 | baseline: 'alphabetic',
453 | /**
454 | Color of the text.
455 | @property {String} color
456 | @default #fff
457 | **/
458 | color: '#fff',
459 | /**
460 | Font used for the text.
461 | @property {String} font
462 | @default Arial
463 | **/
464 | font: 'Arial',
465 | /**
466 | Size of the text.
467 | @property {Number} size
468 | @default 14
469 | **/
470 | size: 14,
471 | /**
472 | Current text.
473 | @property {String} text
474 | **/
475 | text: '',
476 |
477 | staticInit: function(text, props) {
478 | this.super(props);
479 | this.text = text || this.text;
480 | },
481 |
482 | _renderCanvas: function(context) {
483 | var wt = this._worldTransform;
484 |
485 | context.globalAlpha = this._worldAlpha;
486 | context.setTransform(wt.a, wt.b, wt.c, wt.d, wt.tx * game.scale, (wt.ty + this.size) * game.scale);
487 | context.fillStyle = this.color;
488 | context.font = this.size * game.scale + 'px ' + this.font;
489 | context.textAlign = this.align;
490 | context.textBaseline = this.baseline;
491 | context.fillText(this.text, 0, 0);
492 | }
493 | });
494 |
495 | });
496 |
--------------------------------------------------------------------------------
/src/engine/renderer/texture.js:
--------------------------------------------------------------------------------
1 | /**
2 | @module renderer.texture
3 | **/
4 | game.module(
5 | 'engine.renderer.texture'
6 | )
7 | .body(function() {
8 |
9 | /**
10 | @class BaseTexture
11 | @constructor
12 | @param {HTMLImageElement|HTMLCanvasElement} source
13 | @param {Function} loadCallback
14 | **/
15 | game.createClass('BaseTexture', {
16 | /**
17 | @property {Number} height
18 | **/
19 | height: 0,
20 | /**
21 | @property {Boolean} loaded
22 | @default false
23 | **/
24 | loaded: false,
25 | /**
26 | @property {HTMLImageElement|HTMLCanvasElement} source
27 | **/
28 | source: null,
29 | /**
30 | @property {Number} width
31 | **/
32 | width: 0,
33 | /**
34 | @property {Number|String} _id
35 | @private
36 | **/
37 | _id: null,
38 | /**
39 | @property {Function} _loadCallback
40 | @private
41 | **/
42 | _loadCallback: null,
43 |
44 | staticInit: function(source, loadCallback) {
45 | this.source = source;
46 | this._loadCallback = loadCallback;
47 |
48 | if (source.getContext) {
49 | this._onload();
50 | }
51 | else {
52 | source.onload = this._onload.bind(this);
53 | source.onerror = this._onerror.bind(this);
54 | }
55 | },
56 |
57 | /**
58 | Remove base texture from cache.
59 | @method remove
60 | **/
61 | remove: function() {
62 | for (var name in game.BaseTexture.cache) {
63 | if (game.BaseTexture.cache[name] === this) {
64 | delete game.BaseTexture.cache[name];
65 | return;
66 | }
67 | }
68 | },
69 |
70 | /**
71 | @method _onerror
72 | @private
73 | **/
74 | _onerror: function() {
75 | if (this._loadCallback) this._loadCallback('Error loading image ' + this._id);
76 | else throw 'Error loading image ' + this._id;
77 | },
78 |
79 | /**
80 | @method _onload
81 | @private
82 | **/
83 | _onload: function() {
84 | this.loaded = true;
85 | this.width = this.source.width / game.scale;
86 | this.height = this.source.height / game.scale;
87 | if (this._loadCallback) this._loadCallback();
88 | }
89 | });
90 |
91 | game.addAttributes('BaseTexture', {
92 | /**
93 | @attribute {Object} cache
94 | **/
95 | cache: {},
96 | /**
97 | Default crossOrigin property to use in all Image sources
98 | @attribute {String} crossOrigin
99 | @default ''
100 | **/
101 | crossOrigin: '',
102 | /**
103 | @method clearCache
104 | @static
105 | **/
106 | clearCache: function() {
107 | for (var i in this.cache) {
108 | delete this.cache[i];
109 | }
110 | },
111 | /**
112 | @method fromAsset
113 | @static
114 | @param {String} id
115 | @return {BaseTexture}
116 | **/
117 | fromAsset: function(id) {
118 | var path = game.paths[id];
119 | var baseTexture = this.cache[path];
120 |
121 | if (!baseTexture) baseTexture = this.fromImage(path);
122 |
123 | return baseTexture;
124 | },
125 | /**
126 | @method fromCanvas
127 | @static
128 | @param {HTMLCanvasElement} canvas
129 | @return {BaseTexture}
130 | **/
131 | fromCanvas: function(canvas) {
132 | if (!canvas._id) canvas._id = 'canvas_' + this._id++;
133 |
134 | var baseTexture = this.cache[canvas._id];
135 |
136 | if (!baseTexture) {
137 | baseTexture = new game.BaseTexture(canvas);
138 | baseTexture._id = canvas._id;
139 | this.cache[canvas._id] = baseTexture;
140 | }
141 |
142 | return baseTexture;
143 | },
144 | /**
145 | @method fromImage
146 | @static
147 | @param {String} path
148 | @param {Function} loadCallback
149 | @return {BaseTexture}
150 | **/
151 | fromImage: function(path, loadCallback) {
152 | var baseTexture = this.cache[path];
153 |
154 | if (!baseTexture) {
155 | var source = document.createElement('img');
156 | if (this.crossOrigin) source.crossOrigin = this.crossOrigin;
157 |
158 | var sourcePath = path;
159 | if (path.indexOf('data:image') === -1) sourcePath += game._nocache;
160 | source.src = sourcePath;
161 |
162 | baseTexture = new game.BaseTexture(source, loadCallback);
163 | baseTexture._id = path;
164 | this.cache[path] = baseTexture;
165 | }
166 | else if (loadCallback) loadCallback();
167 |
168 | return baseTexture;
169 | },
170 | /**
171 | @attribute {Number} _id
172 | @private
173 | **/
174 | _id: 1
175 | });
176 |
177 | /**
178 | @class Texture
179 | @constructor
180 | @param {BaseTexture|String} baseTexture
181 | @param {Number} [x]
182 | @param {Number} [y]
183 | @param {Number} [width]
184 | @param {Number} [height]
185 | **/
186 | game.createClass('Texture', {
187 | /**
188 | @property {BaseTexture} baseTexture
189 | **/
190 | baseTexture: null,
191 | /**
192 | @property {Number} height
193 | **/
194 | height: 0,
195 | /**
196 | @property {Vector} position
197 | **/
198 | position: null,
199 | /**
200 | @property {Number} width
201 | **/
202 | width: 0,
203 | /**
204 | @property {Vector} _anchor
205 | @private
206 | **/
207 | _anchor: null,
208 | /**
209 | @property {Vector} _offset
210 | @private
211 | **/
212 | _offset: null,
213 | /**
214 | @property {Vector} _trim
215 | @private
216 | **/
217 | _trim: null,
218 |
219 | staticInit: function(baseTexture, x, y, width, height) {
220 | this._anchor = new game.Vector();
221 | this._offset = new game.Vector();
222 | this._trim = new game.Vector();
223 | this.baseTexture = baseTexture instanceof game.BaseTexture ? baseTexture : game.BaseTexture.fromAsset(baseTexture);
224 | this.position = new game.Vector(x, y);
225 | this.width = width || this.baseTexture.width;
226 | this.height = height || this.baseTexture.height;
227 | },
228 |
229 | /**
230 | Remove texture from cache.
231 | @method remove
232 | **/
233 | remove: function() {
234 | for (var name in game.Texture.cache) {
235 | if (game.Texture.cache[name] === this) {
236 | delete game.Texture.cache[name];
237 | return;
238 | }
239 | }
240 | }
241 | });
242 |
243 | game.addAttributes('Texture', {
244 | /**
245 | @attribute {Object} cache
246 | **/
247 | cache: {},
248 | /**
249 | @method clearCache
250 | @static
251 | **/
252 | clearCache: function() {
253 | for (var i in this.cache) {
254 | delete this.cache[i];
255 | }
256 | },
257 | /**
258 | @method fromAsset
259 | @static
260 | @param {String} id
261 | @return {Texture}
262 | **/
263 | fromAsset: function(id) {
264 | var path = game.paths[id] || id;
265 | var texture = this.cache[path];
266 |
267 | if (!texture) {
268 | texture = game.Texture.fromImage(path);
269 | }
270 |
271 | return texture;
272 | },
273 | /**
274 | @method fromCanvas
275 | @static
276 | @param {HTMLCanvasElement} canvas
277 | @return {Texture}
278 | **/
279 | fromCanvas: function(canvas) {
280 | var texture = this.cache[canvas._id];
281 |
282 | if (!texture) {
283 | var baseTexture = game.BaseTexture.fromCanvas(canvas);
284 | texture = new game.Texture(baseTexture);
285 | this.cache[canvas._id] = texture;
286 | }
287 |
288 | return texture;
289 | },
290 | /**
291 | @method fromImage
292 | @static
293 | @param {String} path
294 | @return {Texture}
295 | **/
296 | fromImage: function(path) {
297 | var texture = this.cache[path];
298 |
299 | if (!texture) {
300 | texture = new game.Texture(game.BaseTexture.fromImage(path));
301 | this.cache[path] = texture;
302 | }
303 |
304 | return texture;
305 | }
306 | });
307 |
308 | });
309 |
--------------------------------------------------------------------------------
/src/engine/renderer/tilingsprite.js:
--------------------------------------------------------------------------------
1 | /**
2 | @module renderer.tilingsprite
3 | **/
4 | game.module(
5 | 'engine.renderer.tilingsprite'
6 | )
7 | .require(
8 | 'engine.renderer.sprite'
9 | )
10 | .body(function() {
11 |
12 | /**
13 | @class TilingSprite
14 | @extends Container
15 | @constructor
16 | @param {Texture|String} texture
17 | @param {Number} [width]
18 | @param {Number} [height]
19 | **/
20 | game.createClass('TilingSprite', 'Container', {
21 | /**
22 | @property {Texture} texture
23 | **/
24 | texture: null,
25 | /**
26 | @property {Vector} tilePosition
27 | **/
28 | tilePosition: null,
29 | /**
30 | @property {Vector} _pos
31 | @private
32 | **/
33 | _pos: [],
34 | /**
35 | @property {Vector} _rect
36 | @private
37 | **/
38 | _rect: [],
39 | /**
40 | @property {Sprite} _sprite
41 | @private
42 | **/
43 | _sprite: null,
44 | /**
45 | @property {Boolean} _updateSprites
46 | @private
47 | **/
48 | _updateSprites: false,
49 |
50 | staticInit: function(texture, width, height) {
51 | this.super();
52 | this.tilePosition = new game.Vector();
53 | this._pos = new game.Vector();
54 | this._rect = new game.Vector();
55 |
56 | this.texture = this.texture || texture;
57 | this.texture = this.texture instanceof game.Texture ? this.texture : game.Texture.fromAsset(this.texture);
58 |
59 | this._width = this._width || width || this.texture.width;
60 | this._height = this._height || height || this.texture.height;
61 |
62 | this._generateSprites();
63 | },
64 |
65 | updateTransform: function() {
66 | this.super();
67 | if (this._updateSprites) this._generateSprites();
68 | },
69 |
70 | /**
71 | @method _generateSprites
72 | @private
73 | **/
74 | _generateSprites: function() {
75 | if (!this.texture.baseTexture.loaded) return;
76 | if (this._sprite) {
77 | delete game.TilingSprite.cache[this._sprite.texture.baseTexture._id];
78 | this._sprite.texture.baseTexture.remove();
79 | this._sprite.texture.remove();
80 | }
81 | var canvas = game.TilingSprite._canvas;
82 | var context = game.TilingSprite._context;
83 |
84 | var tx = Math.ceil(this.width / this.texture.width);
85 | var ty = Math.ceil(this.height / this.texture.height);
86 |
87 | var width = tx * this.texture.width;
88 | var height = ty * this.texture.height;
89 |
90 | canvas.width = width * game.scale;
91 | canvas.height = height * game.scale;
92 |
93 | this._pos.set(0);
94 |
95 | var sprite = new game.Sprite(this.texture);
96 | for (var y = 0; y < ty; y++) {
97 | for (var x = 0; x < tx; x++) {
98 | this._pos.x = x * sprite.width * game.scale;
99 | this._pos.y = y * sprite.height * game.scale;
100 | sprite._renderCanvas(context, null, null, this._pos);
101 | }
102 | }
103 |
104 | var texture = game.Texture.fromImage(canvas.toDataURL());
105 | texture.width = canvas.width;
106 | texture.height = canvas.height;
107 | this.tw = texture.width;
108 | this.th = texture.height;
109 | game.TilingSprite.cache[this.texture.baseTexture._id] = texture;
110 |
111 | this._sprite = new game.Sprite(texture);
112 | this._sprite._parent = this;
113 |
114 | this._updateSprites = false;
115 | },
116 |
117 | _getBounds: function() {
118 | if (this.rotation) {
119 | var width = this.width;
120 | var height = this.height;
121 | var wt = this._worldTransform;
122 | var a = wt.a;
123 | var b = wt.b;
124 | var c = wt.c;
125 | var d = wt.d;
126 | var tx = wt.tx;
127 | var ty = wt.ty;
128 | var x2 = a * width + tx;
129 | var y2 = b * width + ty;
130 | var x3 = a * width + c * height + tx;
131 | var y3 = d * height + b * width + ty;
132 | var x4 = c * height + tx;
133 | var y4 = d * height + ty;
134 |
135 | var minX = Math.min(tx, x2, x3, x4);
136 | var minY = Math.min(ty, y2, y3, y4);
137 | var maxX = Math.max(tx, x2, x3, x4);
138 | var maxY = Math.max(ty, y2, y3, y4);
139 | }
140 | else {
141 | var minX = this._worldTransform.tx;
142 | var minY = this._worldTransform.ty;
143 | var maxX = minX + this.width;
144 | var maxY = minY + this.height;
145 | }
146 |
147 | for (var i = 0; i < this.children.length; i++) {
148 | var child = this.children[i];
149 | var childBounds = child._getBounds();
150 | var childMaxX = childBounds.x + childBounds.width;
151 | var childMaxY = childBounds.y + childBounds.height;
152 | if (childBounds.x < minX) minX = childBounds.x;
153 | if (childBounds.y < minY) minY = childBounds.y;
154 | if (childMaxX > maxX) maxX = childMaxX;
155 | if (childMaxY > maxY) maxY = childMaxY;
156 | }
157 |
158 | this._worldBounds.x = minX;
159 | this._worldBounds.y = minY;
160 | this._worldBounds.width = maxX - minX;
161 | this._worldBounds.height = maxY - minY;
162 | return this._worldBounds;
163 | },
164 |
165 | _renderCanvas: function(context) {
166 | if (!this.texture) return true;
167 | if (!this.texture.baseTexture.loaded) return true;
168 |
169 | if (!this.texture.width && this.texture.baseTexture.width) {
170 | this.texture.width = this.texture.baseTexture.width;
171 | }
172 | if (!this.texture.height && this.texture.baseTexture.height) {
173 | this.texture.height = this.texture.baseTexture.height;
174 | }
175 |
176 | if (!this.texture.width || !this.texture.height) return true;
177 |
178 | var scaleX = this._worldTransform.a / this._cosCache;
179 | var scaleY = this._worldTransform.d / this._cosCache;
180 | var tw = this.tw;
181 | var th = this.th;
182 | var width = this.width / scaleX * game.scale;
183 | var height = this.height / scaleY * game.scale;
184 | var tileX = this.tilePosition.x * game.scale;
185 | var tileY = this.tilePosition.y * game.scale;
186 |
187 | var x = tileX % tw;
188 | var y = tileY % th;
189 | if (x > 0) x -= tw;
190 | if (y > 0) y -= th;
191 |
192 | for (var i = 0; i < 4; i++) {
193 | if (y >= height) break;
194 |
195 | this._rect.x = 0;
196 | this._rect.y = 0;
197 | this._rect.width = tw;
198 | this._rect.height = th;
199 | this._pos.x = x * scaleX;
200 | this._pos.y = y * scaleY;
201 |
202 | if (x + tw > width) {
203 | this._rect.width = Math.ceil(width - x);
204 | }
205 |
206 | if (y + th > height) {
207 | this._rect.height = Math.ceil(height - y);
208 | }
209 |
210 | if (x < 0) {
211 | this._rect.x = -x;
212 | this._pos.x = 0;
213 | }
214 |
215 | if (y < 0) {
216 | this._rect.y = -y;
217 | this._pos.y = 0;
218 | }
219 |
220 | if (this._rect.width > width) {
221 | this._rect.width = width;
222 | }
223 |
224 | if (this._rect.height > height) {
225 | this._rect.height = height;
226 | }
227 |
228 | if (this._rect.x + this._rect.width > tw) {
229 | this._rect.width = tw - this._rect.x;
230 | }
231 |
232 | if (this._rect.y + this._rect.height > th) {
233 | this._rect.height = th - this._rect.y;
234 | }
235 |
236 | this._sprite._worldAlpha = this._worldAlpha;
237 | this._sprite._renderCanvas(context, this._worldTransform, this._rect, this._pos);
238 |
239 | x += tw;
240 | if (x >= width) {
241 | x = tileX % tw;
242 | if (x > 0) x -= tw;
243 | y += th;
244 | }
245 | }
246 | }
247 | });
248 |
249 | game.addAttributes('TilingSprite', {
250 | /**
251 | @attribute {Object} cache
252 | **/
253 | cache: {},
254 | /**
255 | @attribute {HTMLCanvasElement} _canvas
256 | @private
257 | **/
258 | _canvas: null,
259 | /**
260 | @attribute {CanvasRenderingContext2D} _context
261 | @private
262 | **/
263 | _context: null,
264 |
265 | /**
266 | @method clearCache
267 | @static
268 | **/
269 | clearCache: function() {
270 | for (var i in this.cache) {
271 | this.cache[i].baseTexture.remove();
272 | this.cache[i].remove();
273 | delete this.cache[i];
274 | }
275 | }
276 | });
277 |
278 | if (typeof document !== 'undefined') {
279 | game.TilingSprite._canvas = document.createElement('canvas');
280 | game.TilingSprite._context = game.TilingSprite._canvas.getContext('2d');
281 | }
282 |
283 | game.defineProperties('TilingSprite', {
284 | width: {
285 | get: function() {
286 | var scaleX = this._worldTransform.a / this._cosCache;
287 | return this._width * scaleX;
288 | },
289 |
290 | set: function(value) {
291 | if (this._width !== value) this._updateSprites = true;
292 | this._width = value;
293 | }
294 | },
295 |
296 | height: {
297 | get: function() {
298 | var scaleY = this._worldTransform.d / this._cosCache;
299 | return this._height * scaleY;
300 | },
301 |
302 | set: function(value) {
303 | if (this._height !== value) this._updateSprites = true;
304 | this._height = value;
305 | }
306 | }
307 | });
308 |
309 | });
310 |
--------------------------------------------------------------------------------
/src/engine/scene.js:
--------------------------------------------------------------------------------
1 | /**
2 | @module scene
3 | **/
4 | game.module(
5 | 'engine.scene'
6 | )
7 | .body(function() {
8 |
9 | /**
10 | Game scene. Instance of current scene is at `game.scene`
11 | @class Scene
12 | **/
13 | game.createClass('Scene', {
14 | /**
15 | Background color of scene.
16 | @property {String} backgroundColor
17 | @default null
18 | **/
19 | backgroundColor: null,
20 | /**
21 | @property {Boolean} isMouseDown
22 | @default false
23 | **/
24 | isMouseDown: false,
25 | /**
26 | List of objects in scene.
27 | @property {Array} objects
28 | **/
29 | objects: [],
30 | /**
31 | Is scene paused.
32 | @property {Boolean} paused
33 | @default false
34 | **/
35 | paused: false,
36 | /**
37 | List of physics worlds in scene.
38 | @property {Array} physics
39 | **/
40 | physics: [],
41 | /**
42 | Main container for scene.
43 | @property {Container} stage
44 | **/
45 | stage: null,
46 | /**
47 | List of timers in scene.
48 | @property {Array} timers
49 | **/
50 | timers: [],
51 | /**
52 | List of tweens in scene.
53 | @property {Array} tweens
54 | **/
55 | tweens: [],
56 | /**
57 | @property {Object} _backgroundColorRgb
58 | @private
59 | **/
60 | _backgroundColorRgb: null,
61 | /**
62 | @property {Number} _mouseDownTime
63 | @private
64 | **/
65 | _mouseDownTime: null,
66 | /**
67 | @property {Number} _mouseDownX
68 | @private
69 | **/
70 | _mouseDownX: null,
71 | /**
72 | @property {Number} _mouseDownY
73 | @private
74 | **/
75 | _mouseDownY: null,
76 | /**
77 | @property {Array} _pausedAnims
78 | @private
79 | **/
80 | _pausedAnims: [],
81 | /**
82 | @property {Array} _pausedObjects
83 | @private
84 | **/
85 | _pausedObjects: [],
86 | /**
87 | @property {Array} _pausedTimers
88 | @private
89 | **/
90 | _pausedTimers: [],
91 | /**
92 | @property {Array} _pausedTweens
93 | @private
94 | **/
95 | _pausedTweens: [],
96 | /**
97 | @property {Array} _updateOrder
98 | @private
99 | **/
100 | _updateOrder: [],
101 |
102 | staticInit: function() {
103 | this.backgroundColor = this.backgroundColor || game.Scene.backgroundColor;
104 | if (!this.backgroundColor && game.device.cocoonCanvasPlus) {
105 | this.backgroundColor = '#000';
106 | }
107 |
108 | if (game.input) game.input._reset();
109 | if (game.keyboard) game.keyboard._reset();
110 |
111 | game.scene = this;
112 |
113 | this.stage = new game.Container();
114 | this.stage.stage = this.stage;
115 |
116 | for (var i = 0; i < game.Scene.updateOrder.length; i++) {
117 | this._updateOrder.push(game.Scene.updateOrder[i].ucfirst());
118 | }
119 | },
120 |
121 | /**
122 | Add object to scene, so it's update function get's called every frame.
123 | @method addObject
124 | @param {Object} object
125 | **/
126 | addObject: function(object) {
127 | if (this.objects.indexOf(object) === -1) {
128 | object._remove = false;
129 | this.objects.push(object);
130 | }
131 | },
132 |
133 | /**
134 | Called, when mouse or touch is released and no swipe is triggered.
135 | @method click
136 | @param {Number} x
137 | @param {Number} y
138 | @param {Number} id
139 | @param {InputEvent} event
140 | **/
141 | click: function() {},
142 |
143 | /**
144 | Called, before scene is changed.
145 | @method exit
146 | @param {String} sceneName
147 | @return {Boolean} Return true to abort exit.
148 | **/
149 | exit: function() {},
150 |
151 | /**
152 | Called, when key is pressed.
153 | @method keydown
154 | @param {String} key
155 | @param {Boolean} shift
156 | @param {Boolean} ctrl
157 | @param {Boolean} alt
158 | @return {Boolean} return true to prevent default keydown action.
159 | **/
160 | keydown: function() {},
161 |
162 | /**
163 | Called, when key is released.
164 | @method keyup
165 | @param {String} key
166 | **/
167 | keyup: function() {},
168 |
169 | /**
170 | Called, when mouse or touch is down.
171 | @method mousedown
172 | @param {Number} x
173 | @param {Number} y
174 | @param {Number} id
175 | @param {InputEvent} event
176 | **/
177 | mousedown: function() {},
178 |
179 | /**
180 | Called, when mouse or touch is moved.
181 | @method mousemove
182 | @param {Number} x
183 | @param {Number} y
184 | @param {Number} id
185 | @param {InputEvent} event
186 | **/
187 | mousemove: function() {},
188 |
189 | /**
190 | Called, when mouse goes out of canvas.
191 | @method mouseout
192 | @param {InputEvent} event
193 | **/
194 | mouseout: function() {},
195 |
196 | /**
197 | Called, when mouse or touch is released.
198 | @method mouseup
199 | @param {Number} x
200 | @param {Number} y
201 | @param {Number} id
202 | @param {InputEvent} event
203 | **/
204 | mouseup: function() {},
205 |
206 | /**
207 | Called, when scene is paused.
208 | @method onPause
209 | **/
210 | onPause: function() {},
211 | /**
212 | Called, when system is resized.
213 | @method onResize
214 | **/
215 | onResize: function() {},
216 | /**
217 | Called, when paused scene is resumed.
218 | @method onResume
219 | **/
220 | onResume: function() {},
221 |
222 | /**
223 | Pause scene. All current objects, timers and tweens are saved and restored when pause is resumed. Also physics are not updated when scene is paused.
224 | @method pause
225 | **/
226 | pause: function() {
227 | if (this.paused) return;
228 | this._pausedAnims.length = 0;
229 | this._pausedObjects.length = 0;
230 | this._pausedTimers.length = 0;
231 | this._pausedTweens.length = 0;
232 | for (var i = 0; i < this.objects.length; i++) {
233 | this._pausedObjects.push(this.objects[i]);
234 | }
235 | for (var i = 0; i < this.timers.length; i++) {
236 | this.timers[i]._pauseCache = this.timers[i]._pause;
237 | this.timers[i].pause();
238 | this._pausedTimers.push(this.timers[i]);
239 | }
240 | for (var i = 0; i < this.tweens.length; i++) {
241 | this._pausedTweens.push(this.tweens[i]);
242 | }
243 | this._getPausedAnims();
244 | this.objects.length = 0;
245 | this.timers.length = 0;
246 | this.tweens.length = 0;
247 | this.paused = true;
248 | this.onPause();
249 | },
250 |
251 | /**
252 | Remove object from scene, so it's update function doesn't get called anymore.
253 | @method removeObject
254 | @param {Object} object
255 | **/
256 | removeObject: function(object) {
257 | object._remove = true;
258 | },
259 |
260 | /**
261 | Remove timer from scene.
262 | @method removeTimer
263 | @param {Timer} timer
264 | @param {Boolean} [doCallback]
265 | **/
266 | removeTimer: function(timer, doCallback) {
267 | if (!timer) return;
268 | if (!doCallback) timer.callback = null;
269 | timer.repeat = false;
270 | timer.set(0);
271 | },
272 |
273 | /**
274 | Remove all timers from scene.
275 | @method removeTimers
276 | @param {Boolean} [doCallback]
277 | **/
278 | removeTimers: function(doCallback) {
279 | for (var i = this.timers.length - 1; i >= 0; i--) {
280 | this.removeTimer(this.timers[i], doCallback);
281 | }
282 | },
283 |
284 | /**
285 | Remove all tweens from scene.
286 | @method removeTweens
287 | **/
288 | removeTweens: function() {
289 | for (var i = 0; i < this.tweens.length; i++) {
290 | this.tweens[i]._shouldRemove = true;
291 | }
292 | },
293 |
294 | /**
295 | Resume paused scene.
296 | @method resume
297 | **/
298 | resume: function() {
299 | if (!this.paused) return;
300 | for (var i = 0; i < this._pausedObjects.length; i++) {
301 | this.objects.push(this._pausedObjects[i]);
302 | }
303 | for (var i = 0; i < this._pausedTimers.length; i++) {
304 | if (this._pausedTimers[i]._pauseCache > 0) this._pausedTimers[i].pause();
305 | else this._pausedTimers[i].resume();
306 | this.timers.push(this._pausedTimers[i]);
307 | }
308 | for (var i = 0; i < this._pausedTweens.length; i++) {
309 | this.tweens.push(this._pausedTweens[i]);
310 | }
311 | this._pausedAnims.length = 0;
312 | this.paused = false;
313 | this.onResume();
314 | },
315 |
316 | /**
317 | Callback for swipe.
318 | @method swipe
319 | @param {String} direction
320 | **/
321 | swipe: function() {},
322 |
323 | /**
324 | This is called every frame.
325 | @method update
326 | **/
327 | update: function() {},
328 |
329 | /**
330 | @method _exit
331 | @param {String} sceneName
332 | @private
333 | **/
334 | _exit: function(sceneName) {
335 | if (game.audio && game.Audio.stopOnSceneChange) {
336 | game.audio.stopMusic();
337 | for (var i = 0; i < game.audio.sounds.length; i++) {
338 | game.audio.sounds[i].stop(true);
339 | }
340 | }
341 |
342 | var exit = this.exit(sceneName);
343 | return exit;
344 | },
345 |
346 | _getPausedAnims: function(container) {
347 | container = container || this.stage;
348 | for (var i = 0; i < container.children.length; i++) {
349 | var child = container.children[i];
350 | if (child instanceof game.Animation) this._pausedAnims.push(child);
351 | if (child.children.length) this._getPausedAnims(child);
352 | }
353 | },
354 |
355 | /**
356 | @method _mousedown
357 | @param {Number} x
358 | @param {Number} y
359 | @param {Number} id
360 | @param {InputEvent} event
361 | @private
362 | **/
363 | _mousedown: function(x, y, id, event) {
364 | this.isMouseDown = true;
365 | this._mouseDownTime = game.Timer.time;
366 | this._mouseDownX = x;
367 | this._mouseDownY = y;
368 | this.mousedown(x, y, id, event);
369 | },
370 |
371 | /**
372 | @method _mousemove
373 | @param {Number} x
374 | @param {Number} y
375 | @param {Number} id
376 | @param {InputEvent} event
377 | @private
378 | **/
379 | _mousemove: function(x, y, id, event) {
380 | this.mousemove(x, y, id, event);
381 | if (!this._mouseDownTime) return;
382 | if (x - this._mouseDownX >= game.Scene.swipeDist) this._swipe('RIGHT');
383 | else if (x - this._mouseDownX <= -game.Scene.swipeDist) this._swipe('LEFT');
384 | else if (y - this._mouseDownY >= game.Scene.swipeDist) this._swipe('DOWN');
385 | else if (y - this._mouseDownY <= -game.Scene.swipeDist) this._swipe('UP');
386 | },
387 |
388 | /**
389 | @method _mouseup
390 | @param {Number} x
391 | @param {Number} y
392 | @param {Number} id
393 | @param {InputEvent} event
394 | @private
395 | **/
396 | _mouseup: function(x, y, id, event) {
397 | this.isMouseDown = false;
398 | if (this._mouseDownTime) {
399 | var time = game.Timer.time - this._mouseDownTime;
400 | if (game.Input.clickTimeout === 0 || time < game.Input.clickTimeout) this.click(x, y, id, event);
401 | }
402 | this._mouseDownTime = null;
403 | this.mouseup(x, y, id, event);
404 | },
405 |
406 | /**
407 | @method _pause
408 | @private
409 | **/
410 | _pause: function() {
411 | if (game.audio) game.audio._systemPause();
412 | },
413 |
414 | /**
415 | @method _resume
416 | @private
417 | **/
418 | _resume: function() {
419 | if (game.audio) game.audio._systemResume();
420 | },
421 |
422 | /**
423 | @method _swipe
424 | @param {String} dir
425 | @private
426 | **/
427 | _swipe: function(dir) {
428 | var time = game.Timer.time - this._mouseDownTime;
429 | this._mouseDownTime = null;
430 | if (time <= game.Scene.swipeTime || game.Scene.swipeTime === 0) {
431 | this.swipe(dir);
432 | }
433 | },
434 |
435 | /**
436 | @method _update
437 | @private
438 | **/
439 | _update: function() {
440 | for (var i = 0; i < this._updateOrder.length; i++) {
441 | this['_update' + this._updateOrder[i]]();
442 | }
443 | },
444 |
445 | /**
446 | @method _updateCollision
447 | @private
448 | **/
449 | _updateCollision: function() {
450 | if (this.paused) return;
451 | for (var i = 0; i < this.physics.length; i++) {
452 | this.physics[i]._updateCollision();
453 | }
454 | },
455 |
456 | /**
457 | @method _updateObjects
458 | @private
459 | **/
460 | _updateObjects: function() {
461 | for (var i = this.objects.length - 1; i >= 0; i--) {
462 | if (typeof this.objects[i].update === 'function' && !this.objects[i]._remove) this.objects[i].update();
463 | if (this.objects[i]._remove) this.objects.splice(i, 1);
464 | }
465 | },
466 |
467 | /**
468 | @method _updatePhysics
469 | @private
470 | **/
471 | _updatePhysics: function() {
472 | if (this.paused) return;
473 | for (var i = 0; i < this.physics.length; i++) {
474 | this.physics[i]._update();
475 | }
476 | },
477 |
478 | /**
479 | @method _updateRenderer
480 | @private
481 | **/
482 | _updateRenderer: function() {
483 | if (game.renderer) game.renderer._render(this.stage);
484 | },
485 |
486 | /**
487 | @method _updateScene
488 | @private
489 | **/
490 | _updateScene: function() {
491 | this.update();
492 | },
493 |
494 | /**
495 | @method _updateTimers
496 | @private
497 | **/
498 | _updateTimers: function() {
499 | for (var i = this.timers.length - 1; i >= 0; i--) {
500 | if (this.timers[i].time() === 0) {
501 | if (typeof this.timers[i].callback === 'function') this.timers[i].callback();
502 | if (this.timers[i].repeat) this.timers[i]._base = game.Timer.time;
503 | else this.timers.splice(i, 1);
504 | }
505 | }
506 | },
507 |
508 | /**
509 | @method _updateTweens
510 | @private
511 | **/
512 | _updateTweens: function() {
513 | for (var i = this.tweens.length - 1; i >= 0; i--) {
514 | if (!this.tweens[i]._update()) this.tweens.splice(i, 1);
515 | }
516 | }
517 | });
518 |
519 | game.addAttributes('Scene', {
520 | /**
521 | Default background color.
522 | @attribute {String} backgroundColor
523 | @default #000
524 | **/
525 | backgroundColor: '#000',
526 | /**
527 | Minimum distance to trigger swipe.
528 | @attribute {Number} swipeDist
529 | @default 100
530 | **/
531 | swipeDist: 100,
532 | /**
533 | Maximum time to trigger swipe (ms).
534 | @attribute {Number} swipeTime
535 | @default 500
536 | **/
537 | swipeTime: 500,
538 | /**
539 | Update order for each frame.
540 | @attribute {Array} updateOrder
541 | @default physics,tweens,collision,timers,scene,objects,renderer
542 | **/
543 | updateOrder: [
544 | 'physics',
545 | 'tweens',
546 | 'collision',
547 | 'timers',
548 | 'scene',
549 | 'objects',
550 | 'renderer'
551 | ]
552 | });
553 |
554 | });
555 |
--------------------------------------------------------------------------------
/src/engine/storage.js:
--------------------------------------------------------------------------------
1 | /**
2 | @module storage
3 | **/
4 | game.module(
5 | 'engine.storage'
6 | )
7 | .body(function() {
8 |
9 | /**
10 | Local storage manager. Instance automatically created at `game.storage`, when `Storage.id` is defined
11 | @class Storage
12 | @constructor
13 | @param {String} [id]
14 | **/
15 | game.createClass('Storage', {
16 | /**
17 | @property {String} id
18 | **/
19 | id: '',
20 | /**
21 | Is local storage supported.
22 | @property {Boolean} supported
23 | **/
24 | supported: false,
25 |
26 | init: function(id) {
27 | this.id = id || game.Storage.id;
28 | this.supported = this._isSupported();
29 | },
30 |
31 | /**
32 | Clear storage. This removes ALL keys.
33 | @method clear
34 | **/
35 | clear: function() {
36 | for (var i = localStorage.length - 1; i >= 0; i--) {
37 | var key = localStorage.key(i);
38 | if (key.indexOf(this.id + '.') !== -1) localStorage.removeItem(key);
39 | }
40 | },
41 |
42 | /**
43 | Get value from storage.
44 | @method get
45 | @param {String} key
46 | @param {*} [defaultValue]
47 | @return {*} value
48 | **/
49 | get: function(key, defaultValue) {
50 | var val = localStorage.getItem(this.id + '.' + key);
51 | if (val === null) return defaultValue;
52 | try {
53 | return this._decode(val);
54 | }
55 | catch (e) {
56 | return val;
57 | }
58 | },
59 |
60 | /**
61 | Check if a key exists in storage.
62 | @method has
63 | @param {String} key
64 | @return {Boolean}
65 | **/
66 | has: function(key) {
67 | return localStorage.getItem(this.id + '.' + key) !== null;
68 | },
69 |
70 | /**
71 | Remove key from storage.
72 | @method remove
73 | @param {String} key
74 | **/
75 | remove: function(key) {
76 | localStorage.removeItem(this.id + '.' + key);
77 | },
78 |
79 | /**
80 | Set value to storage.
81 | @method set
82 | @param {String} key
83 | @param {*} value
84 | @return {*} value
85 | **/
86 | set: function(key, value) {
87 | if (this.supported) localStorage.setItem(this.id + '.' + key, this._encode(value));
88 | return value;
89 | },
90 |
91 | /**
92 | @method _decode
93 | @private
94 | **/
95 | _decode: function(str) {
96 | return JSON.parse(str);
97 | },
98 |
99 | /**
100 | @method _encode
101 | @private
102 | **/
103 | _encode: function(val) {
104 | return JSON.stringify(val);
105 | },
106 |
107 | /**
108 | @method _isSupported
109 | @private
110 | **/
111 | _isSupported: function() {
112 | if (typeof localStorage !== 'object') return false;
113 | try {
114 | localStorage.setItem('localStorage', 1);
115 | localStorage.removeItem('localStorage');
116 | }
117 | catch (e) {
118 | return false;
119 | }
120 | return true;
121 | }
122 | });
123 |
124 | game.addAttributes('Storage', {
125 | /**
126 | @attribute {String} id
127 | **/
128 | id: ''
129 | });
130 |
131 | });
132 |
--------------------------------------------------------------------------------
/src/engine/system.js:
--------------------------------------------------------------------------------
1 | /**
2 | @module system
3 | **/
4 | game.module(
5 | 'engine.system'
6 | )
7 | .body(function() {
8 |
9 | /**
10 | System manager. Instance automatically created at `game.system`
11 | @class System
12 | **/
13 | game.createClass('System', {
14 | /**
15 | Canvas height.
16 | @property {Number} canvasHeight
17 | **/
18 | canvasHeight: 0,
19 | /**
20 | Canvas width.
21 | @property {Number} canvasWidth
22 | **/
23 | canvasWidth: 0,
24 | /**
25 | Time since last frame (seconds), shorthand game.delta
26 | @property {Number} delta
27 | **/
28 | delta: 0,
29 | /**
30 | Height of the game canvas (pixels), shorthand game.height
31 | @property {Number} height
32 | **/
33 | height: 0,
34 | /**
35 | Is engine in HiRes mode.
36 | @property {Boolean} hires
37 | **/
38 | hires: false,
39 | /**
40 | Original height.
41 | @property {Number} originalHeight
42 | **/
43 | originalHeight: 0,
44 | /**
45 | Original width.
46 | @property {Number} originalWidth
47 | **/
48 | originalWidth: 0,
49 | /**
50 | Is engine paused.
51 | @property {Boolean} paused
52 | **/
53 | paused: false,
54 | /**
55 | Current scene (game.scene).
56 | @property {Scene} scene
57 | **/
58 | scene: null,
59 | /**
60 | Name of current scene.
61 | @property {String} sceneName
62 | **/
63 | sceneName: null,
64 | /**
65 | Width of the game canvas (pixels), shorthand game.width
66 | @property {Number} width
67 | **/
68 | width: 0,
69 | /**
70 | @property {String} _newSceneName
71 | @private
72 | **/
73 | _newSceneName: null,
74 | /**
75 | @property {Boolean} _pausedOnHide
76 | @private
77 | **/
78 | _pausedOnHide: false,
79 | /**
80 | @property {Boolean} _rotateScreenVisible
81 | @default false
82 | @private
83 | **/
84 | _rotateScreenVisible: false,
85 | /**
86 | @property {Number} _runLoopId
87 | @private
88 | **/
89 | _runLoopId: 0,
90 | /**
91 | @property {Boolean} _running
92 | @private
93 | **/
94 | _running: false,
95 | /**
96 | @property {Number} _windowWidth
97 | @private
98 | **/
99 | _windowWidth: 0,
100 | /**
101 | @property {Number} _windowHeight
102 | @private
103 | **/
104 | _windowHeight: 0,
105 |
106 | init: function() {
107 | this._updateWindowSize();
108 |
109 | game.width = this.width = this.originalWidth = game.System.width;
110 | game.height = this.height = this.originalHeight = game.System.height;
111 | game.delta = this.delta;
112 |
113 | var realWidth = this.originalWidth;
114 | var realHeight = this.originalHeight;
115 | if (game.System.hidpi) {
116 | realWidth /= game.device.pixelRatio;
117 | realHeight /= game.device.pixelRatio;
118 | }
119 |
120 | for (var i = 2; i <= game.System.hires; i += 2) {
121 | var ratio = game.System.hiresRatio * (i / 2);
122 | var width = game.System.hiresDeviceSize ? game.device.screen.width : this._windowWidth;
123 | var height = game.System.hiresDeviceSize ? game.device.screen.height : this._windowHeight;
124 | if (width >= realWidth * ratio && height >= realHeight * ratio) {
125 | this.hires = true;
126 | game.scale = i;
127 | }
128 | }
129 |
130 | this.canvasWidth = this.originalWidth * game.scale;
131 | this.canvasHeight = this.originalHeight * game.scale;
132 |
133 | if (typeof document === 'undefined') return;
134 |
135 | document.addEventListener(this._getVisibilityChangeFunction(), this._visibilityChange);
136 |
137 | if (game.System.resize) game.System.center = false;
138 |
139 | this._initRenderer();
140 |
141 | if (game.System.hidpi) {
142 | this.canvasWidth /= game.device.pixelRatio;
143 | this.canvasHeight /= game.device.pixelRatio;
144 | }
145 |
146 | this._onWindowResizeFunc = this._onWindowResize.bind(this);
147 | if (game.device.WKWebView) window.addEventListener('orientationchange', this._onWindowResizeFunc);
148 | else window.addEventListener('resize', this._onWindowResizeFunc);
149 | this._onWindowResize();
150 | },
151 |
152 | /**
153 | Request fullscreen mode.
154 | @method fullscreen
155 | **/
156 | fullscreen: function() {
157 | if (game.renderer.canvas.requestFullscreen) game.renderer.canvas.requestFullscreen();
158 | else if (game.renderer.canvas.requestFullScreen) game.renderer.canvas.requestFullScreen();
159 | },
160 |
161 | /**
162 | Test fullscreen support.
163 | @method fullscreenSupport
164 | @return {Boolean} Return true, if browser supports fullscreen mode.
165 | **/
166 | fullscreenSupport: function() {
167 | return !!(game.renderer.canvas.requestFullscreen || game.renderer.canvas.requestFullScreen);
168 | },
169 |
170 | /**
171 | Load new scene using default loader.
172 | @method loadScene
173 | @param {String} scenenName
174 | **/
175 | loadScene: function(sceneName) {
176 | if (!game.mediaQueue.length) this.setScene(sceneName);
177 | else this.setScene(game.System.loader, sceneName);
178 | },
179 |
180 | /**
181 | Resize system.
182 | @method resize
183 | @param {Number} width
184 | @param {Number} height
185 | **/
186 | resize: function(width, height) {
187 | if (this.width === width && this.height === height) return;
188 | game.width = this.width = width / game.scale;
189 | game.height = this.height = height / game.scale;
190 | game.renderer._resize(width, height);
191 | if (this.scene && this.scene.onResize) this.scene.onResize();
192 | },
193 |
194 | /**
195 | Pause game engine.
196 | @method pause
197 | @param {Boolean} onHide
198 | **/
199 | pause: function(onHide) {
200 | if (this.paused) return;
201 | if (onHide) this._pausedOnHide = true;
202 | else this.paused = true;
203 | if (this.scene && this.scene._pause) this.scene._pause();
204 | },
205 |
206 | /**
207 | Resume game engine.
208 | @method resume
209 | @param {Boolean} onHide
210 | **/
211 | resume: function(onHide) {
212 | if (onHide && this.paused) return;
213 | if (!onHide && !this.paused) return;
214 | if (onHide) this._pausedOnHide = false;
215 | else this.paused = false;
216 | game.Timer.last = Date.now();
217 | if (this.scene && this.scene._resume) this.scene._resume();
218 | },
219 |
220 | /**
221 | Change current scene. If you don't define sceneName, current scene will be restarted.
222 | @method setScene
223 | @param {String} sceneName
224 | @param {String} [param]
225 | **/
226 | setScene: function(sceneName, param) {
227 | if (!sceneName && this.scene) sceneName = this.scene.constructor._name;
228 | if (!game[sceneName]) throw 'Scene ' + sceneName + ' not found';
229 | if (this._running && !this.paused) {
230 | this._newSceneName = sceneName;
231 | this._newSceneParam = param;
232 | }
233 | else this._setSceneNow(sceneName, param);
234 | },
235 |
236 | /**
237 | @method _getVisibilityChangeFunction
238 | @private
239 | **/
240 | _getVisibilityChangeFunction: function() {
241 | if (typeof document.hidden !== 'undefined') {
242 | return 'visibilitychange';
243 | }
244 | else if (typeof document.mozHidden !== 'undefined') {
245 | return 'mozvisibilitychange';
246 | }
247 | else if (typeof document.msHidden !== 'undefined') {
248 | return 'msvisibilitychange';
249 | }
250 | else if (typeof document.webkitHidden !== 'undefined') {
251 | return 'webkitvisibilitychange';
252 | }
253 | },
254 |
255 | /**
256 | @method _hideRotateScreen
257 | @private
258 | **/
259 | _hideRotateScreen: function() {
260 | if (!this._rotateScreenVisible) return;
261 | this._rotateScreenVisible = false;
262 | game.renderer._show();
263 | document.body.className = '';
264 | },
265 |
266 | /**
267 | @method _initRenderer
268 | @private
269 | **/
270 | _initRenderer: function() {
271 | game.renderer = new game.Renderer(this.canvasWidth, this.canvasHeight);
272 | },
273 |
274 | /**
275 | @method _onWindowResize
276 | @private
277 | **/
278 | _onWindowResize: function() {
279 | this._updateWindowSize();
280 | if (this._toggleRotateScreen()) return;
281 |
282 | var scalePercent = game.System.scalePercent / 100;
283 | this._scale(this._windowWidth * scalePercent, this._windowHeight * scalePercent);
284 | this._resize(this._windowWidth, this._windowHeight);
285 |
286 | if (game.System.center) {
287 | game.renderer._position((this._windowWidth - this.canvasWidth) / 2, (this._windowHeight - this.canvasHeight) / 2);
288 | }
289 |
290 | if (game.System.scale || game.System.resize || game.System.hidpi && game.device.pixelRatio > 1) {
291 | game.renderer._size(this.canvasWidth, this.canvasHeight);
292 | }
293 |
294 | if (game.isStarted && !game.scene) game.onStart();
295 | },
296 |
297 | /**
298 | Remove all event listeners.
299 | @method _remove
300 | @private
301 | **/
302 | _remove: function() {
303 | document.removeEventListener(this._getVisibilityChangeFunction(), this._visibilityChange);
304 | if (game.device.WKWebView) window.removeEventListener('orientationchange', this._onWindowResizeFunc);
305 | else window.removeEventListener('resize', this._onWindowResizeFunc);
306 | },
307 |
308 | /**
309 | @method _resize
310 | @param {Number} width
311 | @param {Number} height
312 | @private
313 | **/
314 | _resize: function(width, height) {
315 | if (!game.System.resize) return;
316 | if (!game.System.scale) {
317 | this.resize(width, height);
318 | this.canvasWidth = width;
319 | this.canvasHeight = height;
320 | return;
321 | }
322 |
323 | var widthSpace = width - this.canvasWidth;
324 | var heightSpace = height - this.canvasHeight;
325 |
326 | if (widthSpace > 0) {
327 | var ratio = widthSpace / this.canvasWidth;
328 | var newWidth = ~~(this.originalWidth * game.scale + this.originalWidth * ratio * game.scale);
329 |
330 | this.resize(newWidth, this.originalHeight * game.scale);
331 | this.canvasWidth += widthSpace;
332 | }
333 | else if (heightSpace > 0) {
334 | var ratio = heightSpace / this.canvasHeight;
335 | var newHeight = ~~(this.originalHeight * game.scale + this.originalHeight * ratio * game.scale);
336 |
337 | this.resize(this.originalWidth * game.scale, newHeight);
338 | this.canvasHeight += heightSpace;
339 | }
340 | },
341 |
342 | /**
343 | @method _run
344 | @private
345 | **/
346 | _run: function() {
347 | if (this.paused || this._pausedOnHide) return;
348 |
349 | game.Timer.update();
350 | game.delta = this.delta = game.Timer.delta / 1000;
351 |
352 | if (game.input) game.input._update();
353 | this.scene._update();
354 |
355 | if (this._newSceneName) this._setSceneNow(this._newSceneName, this._newSceneParam);
356 | },
357 |
358 | /**
359 | @method _scale
360 | @param {Number} width
361 | @param {Number} height
362 | @private
363 | **/
364 | _scale: function(width, height) {
365 | if (!game.System.scale) return;
366 |
367 | if (width / this.originalWidth < height / this.originalHeight) {
368 | this.canvasWidth = width;
369 | this.canvasHeight = ~~(width * (this.originalHeight / this.originalWidth));
370 | }
371 | else {
372 | this.canvasWidth = ~~(height * (this.originalWidth / this.originalHeight));
373 | this.canvasHeight = height;
374 | }
375 |
376 | if (game.System.scaleMax > 0) {
377 | var maxWidth = this.originalWidth * (game.System.scaleMax / 100);
378 | var maxHeight = this.originalHeight * (game.System.scaleMax / 100);
379 | if (this.canvasWidth > maxWidth) this.canvasWidth = maxWidth;
380 | if (this.canvasHeight > maxHeight) this.canvasHeight = maxHeight;
381 | }
382 | },
383 |
384 | /**
385 | @method _setSceneNow
386 | @param {String} sceneName
387 | @param {*} [param]
388 | @private
389 | **/
390 | _setSceneNow: function(sceneName, param) {
391 | this.sceneName = sceneName;
392 | this._newSceneName = null;
393 | if (this.scene && this.scene._exit(sceneName)) return;
394 | if (this.paused) this.paused = false;
395 | game.TilingSprite.clearCache();
396 | game.Sprite._clearTintedTextures();
397 | this.scene = new game[sceneName](param);
398 | this._startRunLoop();
399 | },
400 |
401 | /**
402 | @method _showRotateScreen
403 | @private
404 | **/
405 | _showRotateScreen: function() {
406 | if (this._rotateScreenVisible) return;
407 | this._rotateScreenVisible = true;
408 | game.renderer._hide();
409 | document.body.className = game.System.rotateScreenClass;
410 | },
411 |
412 | /**
413 | @method _startRunLoop
414 | @private
415 | **/
416 | _startRunLoop: function() {
417 | if (!this.scene) return;
418 | if (this._runLoopId) this._stopRunLoop();
419 | this._runLoopId = game._setGameLoop(this._run.bind(this));
420 | this._running = true;
421 | },
422 |
423 | /**
424 | @method _stopRunLoop
425 | @private
426 | **/
427 | _stopRunLoop: function() {
428 | game._clearGameLoop(this._runLoopId);
429 | this._running = false;
430 | },
431 |
432 | /**
433 | @method _toggleRotateScreen
434 | @private
435 | **/
436 | _toggleRotateScreen: function() {
437 | if (!game.device.mobile || !game.System.rotateScreen) return false;
438 |
439 | if (this.originalWidth > this.originalHeight && this._windowWidth < this._windowHeight ||
440 | this.originalHeight > this.originalWidth && this._windowHeight < this._windowWidth) {
441 | this._showRotateScreen();
442 | return true;
443 | }
444 |
445 | this._hideRotateScreen();
446 | return false;
447 | },
448 |
449 | /**
450 | @method _updateWindowSize
451 | @private
452 | **/
453 | _updateWindowSize: function() {
454 | if (typeof document === 'undefined') return;
455 | if (game.device.android && window.innerHeight - this._windowHeight === 24) return; // Fix Android 6 screen size bug
456 | this._windowWidth = game.device.WKWebView ? document.documentElement.clientWidth : window.innerWidth;
457 | this._windowHeight = game.device.WKWebView ? document.documentElement.clientHeight : window.innerHeight;
458 | if (game.device.crosswalk && this._windowWidth === 0) {
459 | this._windowWidth = window.screen.width;
460 | this._windowHeight = window.screen.height;
461 | }
462 | if (Math.abs(window.orientation) === 90 && game.device.iPhone && game.device.safari) {
463 | // Fix iPhone Safari landscape fullscreen
464 | this._windowHeight++;
465 | }
466 | },
467 |
468 | /**
469 | @method _visibilityChange
470 | @private
471 | **/
472 | _visibilityChange: function() {
473 | if (game.System.pauseOnHide) {
474 | var hidden = !!game._getVendorAttribute(document, 'hidden');
475 | if (hidden) game.system.pause(true);
476 | else game.system.resume(true);
477 | }
478 | }
479 | });
480 |
481 | game.addAttributes('System', {
482 | /**
483 | Id for canvas element, where game is placed. If none found, it will be created.
484 | @attribute {String} canvasId
485 | @default canvas
486 | **/
487 | canvasId: 'canvas',
488 | /**
489 | Position canvas to center of window.
490 | @attribute {Boolean} center
491 | @default true
492 | **/
493 | center: true,
494 | /**
495 | Set custom frame rate for game loop.
496 | @attribute {Number} frameRate
497 | @default null
498 | **/
499 | frameRate: null,
500 | /**
501 | System height.
502 | @attribute {Number} height
503 | @default 768
504 | **/
505 | height: 768,
506 | /**
507 | Scale canvas for HiDPI screens.
508 | @attribute {Boolean} hidpi
509 | @default true
510 | **/
511 | hidpi: true,
512 | /**
513 | HiRes mode multiplier.
514 | @attribute {Number} hires
515 | @default 0
516 | **/
517 | hires: 0,
518 | /**
519 | Use device size instead of window size on HiRes mode.
520 | @attribute {Boolean} hiresDeviceSize
521 | @default false
522 | **/
523 | hiresDeviceSize: false,
524 | /**
525 | Ratio value, when HiRes mode is used.
526 | @attribute {Number} hiresRatio
527 | @default 2
528 | **/
529 | hiresRatio: 2,
530 | /**
531 | Default loader class.
532 | @attribute {String} loader
533 | @default Loader
534 | **/
535 | loader: 'Loader',
536 | /**
537 | Pause engine, when page is hidden.
538 | @attribute {Boolean} pauseOnHide
539 | @default true
540 | **/
541 | pauseOnHide: true,
542 | /**
543 | Resize canvas to fill window.
544 | @attribute {Boolean} resize
545 | @default false
546 | **/
547 | resize: false,
548 | /**
549 | Use rotate screen on mobile.
550 | @attribute {Boolean} rotateScreen
551 | @default true
552 | **/
553 | rotateScreen: true,
554 | /**
555 | Class name for document body, when rotate screen visible.
556 | @attribute {String} rotateScreenClass
557 | @default rotate
558 | **/
559 | rotateScreenClass: 'rotate',
560 | /**
561 | Scale canvas to fit window.
562 | @attribute {Boolean} scale
563 | @default true
564 | **/
565 | scale: true,
566 | /**
567 | Maximum percent of scaling (0 = disabled).
568 | @attribute {Number} scaleMax
569 | @default 0
570 | **/
571 | scaleMax: 0,
572 | /**
573 | @attribute {Number} scalePercent
574 | @default 100
575 | **/
576 | scalePercent: 100,
577 | /**
578 | Name of start scene.
579 | @attribute {String} startScene
580 | @default Main
581 | **/
582 | startScene: 'Main',
583 | /**
584 | System width.
585 | @attribute {Number} width
586 | @default 1024
587 | **/
588 | width: 1024
589 | });
590 |
591 | });
592 |
--------------------------------------------------------------------------------
/src/engine/timer.js:
--------------------------------------------------------------------------------
1 | /**
2 | @module timer
3 | **/
4 | game.module(
5 | 'engine.timer'
6 | )
7 | .body(function() {
8 |
9 | /**
10 | @class Timer
11 | @constructor
12 | @param {Number} [time] Timer's target time (milliseconds)
13 | **/
14 | game.createClass('Timer', {
15 | /**
16 | Function to call, when timer's time is reached.
17 | @property {Function} callback
18 | **/
19 | callback: null,
20 | /**
21 | Is timer paused.
22 | @property {Boolean} paused
23 | **/
24 | paused: false,
25 | /**
26 | Should timer repeat.
27 | @property {Boolean} repeat
28 | **/
29 | repeat: false,
30 | /**
31 | Timer's target time.
32 | @property {Number} target
33 | **/
34 | target: 0,
35 | /**
36 | @property {Number} _base
37 | @private
38 | **/
39 | _base: 0,
40 | /**
41 | @property {Number} _last
42 | @private
43 | **/
44 | _last: 0,
45 | /**
46 | @property {Number} _pauseTime
47 | @private
48 | **/
49 | _pauseTime: 0,
50 |
51 | init: function(time) {
52 | this._last = game.Timer.time;
53 | this.set(time);
54 | },
55 |
56 | /**
57 | Clear timer.
58 | @method clear
59 | **/
60 | clear: function() {
61 | this.callback = null;
62 | this.repeat = false;
63 | this.set(0);
64 | },
65 |
66 | /**
67 | Get time since last frame.
68 | @method delta
69 | @return {Number} delta
70 | **/
71 | delta: function() {
72 | var delta = game.Timer.time - this._last;
73 | this._last = game.Timer.time;
74 | return this.paused ? 0 : delta;
75 | },
76 |
77 | /**
78 | Pause timer.
79 | @method pause
80 | **/
81 | pause: function() {
82 | if (this.paused) return;
83 | this._pauseTime = game.Timer.time;
84 | this.paused = true;
85 | },
86 |
87 | /**
88 | Reset timer.
89 | @method reset
90 | **/
91 | reset: function() {
92 | this._base = game.Timer.time;
93 | this._pauseTime = 0;
94 | this.paused = false;
95 | },
96 |
97 | /**
98 | Resume paused timer.
99 | @method resume
100 | **/
101 | resume: function() {
102 | if (!this.paused) return;
103 | this._base += game.Timer.time - this._pauseTime;
104 | this._pauseTime = 0;
105 | this.paused = false;
106 | },
107 |
108 | /**
109 | Set target time for timer.
110 | @method set
111 | @param {Number} ms
112 | **/
113 | set: function(ms) {
114 | if (typeof ms !== 'number') ms = 0;
115 | this.target = ms;
116 | this.reset();
117 | },
118 |
119 | /**
120 | Get time left.
121 | @method time
122 | @return {Number} time
123 | **/
124 | time: function() {
125 | var time = this._base + this.target - (this._pauseTime || game.Timer.time);
126 | return time < 0 ? 0 : time;
127 | }
128 | });
129 |
130 | game.addAttributes('Timer', {
131 | /**
132 | Main timer's delta (ms).
133 | @attribute {Number} delta
134 | **/
135 | delta: 0,
136 | /**
137 | Main timer's minimum fps.
138 | @attribute {Number} minFPS
139 | @default 20
140 | **/
141 | minFPS: 20,
142 | /**
143 | Main timer's speed factor.
144 | @attribute {Number} speed
145 | @default 1
146 | **/
147 | speed: 1,
148 | /**
149 | Current time.
150 | @attribute {Number} time
151 | **/
152 | time: 0,
153 | /**
154 | @attribute {Number} _last
155 | @private
156 | **/
157 | _last: 0,
158 | /**
159 | @attribute {Number} _lastFrameTime
160 | @private
161 | **/
162 | _lastFrameTime: 0,
163 | /**
164 | @attribute {Number} _realDelta
165 | @private
166 | **/
167 | _realDelta: 0,
168 | /**
169 | Add timer to scene.
170 | @method add
171 | @static
172 | @param {Number} time Time (ms).
173 | @param {Function} callback Callback function to run, when timer ends.
174 | @param {Boolean} [repeat]
175 | @param {Boolean} [instant]
176 | @return {Timer}
177 | **/
178 | add: function(time, callback, repeat, instant) {
179 | var timer = new game.Timer(time);
180 | timer.repeat = !!repeat;
181 | timer.callback = callback;
182 | game.scene.timers.push(timer);
183 | if (instant) callback();
184 | return timer;
185 | },
186 | /**
187 | Update main timer.
188 | @method update
189 | @static
190 | **/
191 | update: function() {
192 | var now = Date.now();
193 | if (!this._last) this._last = now;
194 | this._realDelta = now - this._last;
195 | this.delta = Math.min(this._realDelta, 1000 / this.minFPS) * this.speed;
196 | this._lastFrameTime = this.time;
197 | this.time += this.delta;
198 | this._last = now;
199 | }
200 | });
201 |
202 | });
203 |
--------------------------------------------------------------------------------
/src/game/config.js:
--------------------------------------------------------------------------------
1 | game.config = {
2 | system: {
3 | width: 1024,
4 | height: 768,
5 | center: true,
6 | scale: true
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/src/game/main.js:
--------------------------------------------------------------------------------
1 | game.module(
2 | 'game.main'
3 | )
4 | .body(function() {
5 |
6 | game.addAsset('panda.png');
7 |
8 | game.createScene('Main', {
9 | init: function() {
10 | var sprite = new game.Sprite('panda.png');
11 | sprite.addTo(this.stage);
12 | }
13 | });
14 |
15 | });
16 |
--------------------------------------------------------------------------------