1 /**************************************************************************** 2 Copyright (c) 2010-2012 cocos2d-x.org 3 Copyright (c) 2008-2010 Ricardo Quesada 4 Copyright (c) 2011 Zynga Inc. 5 6 http://www.cocos2d-x.org 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy 9 of this software and associated documentation files (the "Software"), to deal 10 in the Software without restriction, including without limitation the rights 11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 copies of the Software, and to permit persons to whom the Software is 13 furnished to do so, subject to the following conditions: 14 15 The above copyright notice and this permission notice shall be included in 16 all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 THE SOFTWARE. 25 ****************************************************************************/ 26 27 cc.g_NumberOfDraws = 0; 28 29 //Possible OpenGL projections used by director 30 /** 31 * sets a 2D projection (orthogonal projection) 32 * @constant 33 * @type Number 34 */ 35 cc.DIRECTOR_PROJECTION_2D = 0; 36 37 /** 38 * sets a 3D projection with a fovy=60, znear=0.5f and zfar=1500. 39 * @constant 40 * @type Number 41 */ 42 cc.DIRECTOR_PROJECTION_3D = 1; 43 44 /** 45 * it calls "updateProjection" on the projection delegate. 46 * @constant 47 * @type Number 48 */ 49 cc.DIRECTOR_PROJECTION_CUSTOM = 3; 50 51 /** 52 * Default projection is 3D projection 53 * @constant 54 * @type Number 55 */ 56 cc.DIRECTOR_PROJECTION_DEFAULT = cc.DIRECTOR_PROJECTION_3D; 57 58 //---------------------------------------------------------------------------------------------------------------------- 59 //Possible device orientations 60 /** 61 * Device oriented vertically, home button on the bottom (UIDeviceOrientationPortrait) 62 * @constant 63 * @type Number 64 */ 65 cc.DEVICE_ORIENTATION_PORTRAIT = 0; 66 67 /** 68 * Device oriented horizontally, home button on the right (UIDeviceOrientationLandscapeLeft) 69 * @constant 70 * @type Number 71 */ 72 cc.DEVICE_ORIENTATION_LANDSCAPE_LEFT = 1; 73 74 /** 75 * Device oriented vertically, home button on the top (UIDeviceOrientationPortraitUpsideDown) 76 * @constant 77 * @type Number 78 */ 79 cc.DEVICE_ORIENTATION_PORTRAIT_UPSIDE_DOWN = 2; 80 81 /** 82 * Device oriented horizontally, home button on the left (UIDeviceOrientationLandscapeRight) 83 * @constant 84 * @type Number 85 */ 86 cc.DEVICE_ORIENTATION_LANDSCAPE_RIGHT = 3; 87 88 /** 89 * In browsers, we only support 2 orientations by change window size. 90 * @constant 91 * @type Number 92 */ 93 cc.DEVICE_MAX_ORIENTATIONS = 2; 94 95 /** 96 * OpenGL projection protocol 97 * @class 98 * @extends cc.Class 99 */ 100 cc.DirectorDelegate = cc.Class.extend(/** @lends cc.DirectorDelegate# */{ 101 /** 102 * Called by CCDirector when the projection is updated, and "custom" projection is used 103 */ 104 updateProjection:function () { 105 } 106 }); 107 108 cc.GLToClipTransform = function (transformOut) { 109 var projection = new cc.kmMat4(); 110 cc.kmGLGetMatrix(cc.KM_GL_PROJECTION, projection); 111 112 var modelview = new cc.kmMat4(); 113 cc.kmGLGetMatrix(cc.KM_GL_MODELVIEW, modelview); 114 115 cc.kmMat4Multiply(transformOut, projection, modelview); 116 }; 117 //---------------------------------------------------------------------------------------------------------------------- 118 119 /** 120 * <p> 121 * Class that creates and handle the main Window and manages how<br/> 122 * and when to execute the Scenes.<br/> 123 * <br/> 124 * The cc.Director is also responsible for:<br/> 125 * - initializing the OpenGL context<br/> 126 * - setting the OpenGL pixel format (default on is RGB565)<br/> 127 * - setting the OpenGL pixel format (default on is RGB565)<br/> 128 * - setting the OpenGL buffer depth (default one is 0-bit)<br/> 129 * - setting the projection (default one is 3D)<br/> 130 * - setting the orientation (default one is Protrait)<br/> 131 * <br/> 132 * Since the cc.Director is a singleton, the standard way to use it is by calling:<br/> 133 * - cc.Director.getInstance().methodName(); <br/> 134 * <br/> 135 * The CCDirector also sets the default OpenGL context:<br/> 136 * - GL_TEXTURE_2D is enabled<br/> 137 * - GL_VERTEX_ARRAY is enabled<br/> 138 * - GL_COLOR_ARRAY is enabled<br/> 139 * - GL_TEXTURE_COORD_ARRAY is enabled<br/> 140 * </p> 141 * @class 142 * @extends cc.Class 143 */ 144 cc.Director = cc.Class.extend(/** @lends cc.Director# */{ 145 //Variables 146 _landscape:false, 147 _nextDeltaTimeZero:false, 148 _paused:false, 149 _purgeDirecotorInNextLoop:false, 150 _sendCleanupToScene:false, 151 _animationInterval:0.0, 152 _oldAnimationInterval:0.0, 153 _projection:0, 154 _accumDt:0.0, 155 _contentScaleFactor:1.0, 156 157 _displayStats:false, 158 _deltaTime:0.0, 159 _frameRate:0.0, 160 161 _FPSLabel:null, 162 _SPFLabel:null, 163 _drawsLabel:null, 164 165 _winSizeInPoints:null, 166 167 _lastUpdate:null, 168 _nextScene:null, 169 _notificationNode:null, 170 _openGLView:null, 171 _scenesStack:null, 172 _projectionDelegate:null, 173 _runningScene:null, 174 175 _frames:0, 176 _totalFrames:0, 177 _secondsPerFrame:0, 178 179 _dirtyRegion:null, 180 181 _scheduler:null, 182 _actionManager:null, 183 _touchDispatcher:null, 184 _keyboardDispatcher:null, 185 _accelerometer:null, 186 _mouseDispatcher:null, 187 188 _isBlur:false, 189 190 /** 191 * Constructor 192 */ 193 ctor:function () { 194 this._lastUpdate = Date.now(); 195 if (!cc.isAddedHiddenEvent) { 196 var selfPointer = this; 197 window.addEventListener("focus", function () { 198 selfPointer._lastUpdate = Date.now(); 199 }, false); 200 } 201 }, 202 203 _resetLastUpdate:function () { 204 this._lastUpdate = Date.now(); 205 }, 206 207 /** 208 * initializes cc.Director 209 * @return {Boolean} 210 */ 211 init:function () { 212 // scenes 213 this._oldAnimationInterval = this._animationInterval = 1.0 / cc.defaultFPS; 214 this._scenesStack = []; 215 // Set default projection (3D) 216 this._projection = cc.DIRECTOR_PROJECTION_DEFAULT; 217 // projection delegate if "Custom" projection is used 218 this._projectionDelegate = null; 219 220 //FPS 221 this._accumDt = 0; 222 this._frameRate = 0; 223 this._displayStats = false;//can remove 224 this._totalFrames = this._frames = 0; 225 this._lastUpdate = Date.now(); 226 227 //Paused? 228 this._paused = false; 229 230 //purge? 231 this._purgeDirecotorInNextLoop = false; 232 233 this._winSizeInPoints = cc.size(0, 0); 234 235 this._openGLView = null; 236 this._contentScaleFactor = 1.0; 237 238 //scheduler 239 this._scheduler = new cc.Scheduler(); 240 //action manager 241 this._actionManager = new cc.ActionManager(); 242 this._scheduler.scheduleUpdateForTarget(this._actionManager, cc.PRIORITY_SYSTEM, false); 243 //touchDispatcher 244 this._touchDispatcher = new cc.TouchDispatcher(); 245 this._touchDispatcher.init(); 246 247 //KeyboardDispatcher 248 if(cc.KeyboardDispatcher) 249 this._keyboardDispatcher = cc.KeyboardDispatcher.getInstance(); 250 251 //accelerometer 252 if(cc.Accelerometer) 253 this._accelerometer = new cc.Accelerometer(); 254 255 //MouseDispatcher 256 if(cc.MouseDispatcher){ 257 this._mouseDispatcher = new cc.MouseDispatcher(); 258 this._mouseDispatcher.init(); 259 } 260 261 return true; 262 }, 263 264 /** 265 * calculates delta time since last time it was called 266 */ 267 calculateDeltaTime:function () { 268 var now = Date.now(); 269 270 // new delta time. 271 if (this._nextDeltaTimeZero) { 272 this._deltaTime = 0; 273 this._nextDeltaTimeZero = false; 274 } else { 275 this._deltaTime = (now - this._lastUpdate) / 1000; 276 } 277 278 if ((cc.COCOS2D_DEBUG > 0) && (this._deltaTime > 0.2)) 279 this._deltaTime = 1 / 60.0; 280 281 this._lastUpdate = now; 282 }, 283 284 /** 285 * <p> 286 * converts a UIKit coordinate to an OpenGL coordinate<br/> 287 * Useful to convert (multi) touches coordinates to the current layout (portrait or landscape) 288 * </p> 289 * @param {cc.Point} uiPoint 290 * @return {cc.Point} 291 */ 292 convertToGL:function (uiPoint) { 293 var transform = new cc.kmMat4(); 294 cc.GLToClipTransform(transform); 295 296 var transformInv = new cc.kmMat4(); 297 cc.kmMat4Inverse(transformInv, transform); 298 299 // Calculate z=0 using -> transform*[0, 0, 0, 1]/w 300 var zClip = transform.mat[14] / transform.mat[15]; 301 302 var glSize = this._openGLView.getDesignResolutionSize(); 303 var clipCoord = new cc.kmVec3(2.0 * uiPoint.x / glSize.width - 1.0, 1.0 - 2.0 * uiPoint.y / glSize.height, zClip); 304 305 var glCoord = new cc.kmVec3(); 306 cc.kmVec3TransformCoord(glCoord, clipCoord, transformInv); 307 308 return cc.p(glCoord.x, glCoord.y); 309 }, 310 311 /** 312 * <p>converts an OpenGL coordinate to a UIKit coordinate<br/> 313 * Useful to convert node points to window points for calls such as glScissor</p> 314 * @param {cc.Point} glPoint 315 * @return {cc.Point} 316 */ 317 convertToUI:function (glPoint) { 318 var transform = new cc.kmMat4(); 319 cc.GLToClipTransform(transform); 320 321 var clipCoord = new cc.kmVec3(); 322 // Need to calculate the zero depth from the transform. 323 var glCoord = new cc.kmVec3(glPoint.x, glPoint.y, 0.0); 324 cc.kmVec3TransformCoord(clipCoord, glCoord, transform); 325 326 var glSize = this._openGLView.getDesignResolutionSize(); 327 return cc.p(glSize.width * (clipCoord.x * 0.5 + 0.5), glSize.height * (-clipCoord.y * 0.5 + 0.5)); 328 }, 329 330 /** 331 * Draw the scene. This method is called every frame. Don't call it manually. 332 */ 333 drawScene: function() { 334 // calculate "global" dt 335 this.calculateDeltaTime(); 336 337 //tick before glClear: issue #533 338 if (!this._paused) 339 this._scheduler.update(this._deltaTime); 340 341 this._clear(); 342 343 /* to avoid flickr, nextScene MUST be here: after tick and before draw. 344 XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */ 345 if (this._nextScene) { 346 this.setNextScene(); 347 } 348 349 if (this._beforeVisitScene) this._beforeVisitScene(); 350 351 // draw the scene 352 if (this._runningScene) 353 this._runningScene.visit(); 354 355 // draw the notifications node 356 if (this._notificationNode) 357 this._notificationNode.visit(); 358 359 if (this._displayStats) 360 this._showStats(); 361 362 if (this._afterVisitScene) this._afterVisitScene(); 363 364 this._totalFrames++; 365 366 if (this._displayStats) 367 this._calculateMPF(); 368 }, 369 370 _clearCanvas: function() { 371 //cc.renderContext.clearRect(0, 0, cc.originalCanvasSize.width, -cc.originalCanvasSize.height); 372 cc.renderContext.clearRect(0, 0, cc.canvas.width, -cc.canvas.height); 373 }, 374 375 _clearWebGL: function() { 376 var gl = cc.renderContext; 377 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 378 }, 379 380 _beforeVisitScene: null, 381 _afterVisitScene: null, 382 383 _beforeVisitSceneWebGL: function() { 384 cc.kmGLPushMatrix(); 385 }, 386 387 _afterVisitSceneWebGL: function() { 388 cc.kmGLPopMatrix(); 389 }, 390 391 /** 392 * end director 393 */ 394 end:function () { 395 this._purgeDirecotorInNextLoop = true; 396 }, 397 398 /** 399 * <p>get the size in pixels of the surface. It could be different than the screen size.<br/> 400 * High-res devices might have a higher surface size than the screen size.<br/> 401 * Only available when compiled using SDK >= 4.0. 402 * </p> 403 * @return {Number} 404 */ 405 getContentScaleFactor:function () { 406 return this._contentScaleFactor; 407 }, 408 409 /** 410 * <p> 411 * This object will be visited after the main scene is visited.<br/> 412 * This object MUST implement the "visit" selector.<br/> 413 * Useful to hook a notification object, like CCNotifications (http://github.com/manucorporat/CCNotifications) 414 * </p> 415 * @return {cc.Node} 416 */ 417 getNotificationNode:function () { 418 return this._notificationNode; 419 }, 420 421 /** 422 * <p> 423 * returns the size of the OpenGL view in points.<br/> 424 * It takes into account any possible rotation (device orientation) of the window 425 * </p> 426 * @return {cc.Size} 427 */ 428 getWinSize:function () { 429 return cc.size(this._winSizeInPoints.width, this._winSizeInPoints.height); 430 }, 431 432 /** 433 * <p> 434 * returns the size of the OpenGL view in pixels.<br/> 435 * It takes into account any possible rotation (device orientation) of the window.<br/> 436 * On Mac winSize and winSizeInPixels return the same value. 437 * </p> 438 * @return {cc.Size} 439 */ 440 getWinSizeInPixels:function () { 441 return cc.size(this._winSizeInPoints.width * this._contentScaleFactor, this._winSizeInPoints.height * this._contentScaleFactor); 442 }, 443 444 getVisibleSize:function () { 445 if (this._openGLView) { 446 return this._openGLView.getVisibleSize(); 447 } else { 448 return this.getWinSize(); 449 } 450 }, 451 452 getVisibleOrigin:function () { 453 if (this._openGLView) { 454 return this._openGLView.getVisibleOrigin(); 455 } else { 456 return cc.p(0, 0); 457 } 458 }, 459 460 getZEye:function () { 461 return (this._winSizeInPoints.height / 1.1566 ); 462 }, 463 464 /** 465 * pause director 466 */ 467 pause:function () { 468 if (this._paused) 469 return; 470 471 this._oldAnimationInterval = this._animationInterval; 472 // when paused, don't consume CPU 473 this.setAnimationInterval(1 / 4.0); 474 this._paused = true; 475 }, 476 477 /** 478 * <p> 479 * Pops out a scene from the queue.<br/> 480 * This scene will replace the running one.<br/> 481 * The running scene will be deleted. If there are no more scenes in the stack the execution is terminated.<br/> 482 * ONLY call it if there is a running scene. 483 * </p> 484 */ 485 popScene:function () { 486 if(!this._runningScene) 487 throw "running scene should not null"; 488 489 //this.addRegionToDirtyRegion(cc.rect(0, 0, cc.canvas.width, cc.canvas.height)); 490 491 this._scenesStack.pop(); 492 var c = this._scenesStack.length; 493 494 if (c == 0) 495 this.end(); 496 else { 497 this._sendCleanupToScene = true; 498 this._nextScene = this._scenesStack[c - 1]; 499 } 500 }, 501 502 /** 503 * Removes cached all cocos2d cached data. It will purge the CCTextureCache, CCSpriteFrameCache, CCLabelBMFont cache 504 */ 505 purgeCachedData:function () { 506 cc.LabelBMFont.purgeCachedData(); 507 //cc.TextureCache.getInstance().removeUnusedTextures(); 508 }, 509 510 /** 511 * purge Director 512 */ 513 purgeDirector:function () { 514 //cleanup scheduler 515 this.getScheduler().unscheduleAllCallbacks(); 516 517 // don't release the event handlers 518 // They are needed in case the director is run again 519 this._touchDispatcher.removeAllDelegates(); 520 521 if (this._runningScene) { 522 this._runningScene.onExitTransitionDidStart(); 523 this._runningScene.onExit(); 524 this._runningScene.cleanup(); 525 } 526 527 this._runningScene = null; 528 this._nextScene = null; 529 530 // remove all objects, but don't release it. 531 // runWithScene might be executed after 'end'. 532 this._scenesStack = []; 533 534 this.stopAnimation(); 535 536 // purge bitmap cache 537 cc.LabelBMFont.purgeCachedData(); 538 539 // purge all managers 540 cc.AnimationCache.purgeSharedAnimationCache(); 541 cc.SpriteFrameCache.purgeSharedSpriteFrameCache(); 542 cc.TextureCache.purgeSharedTextureCache(); 543 544 //CCShaderCache::purgeSharedShaderCache(); 545 //CCFileUtils::purgeFileUtils(); 546 //CCConfiguration::purgeConfiguration(); 547 //extension::CCNotificationCenter::purgeNotificationCenter(); 548 //extension::CCTextureWatcher::purgeTextureWatcher(); 549 //extension::CCNodeLoaderLibrary::purgeSharedCCNodeLoaderLibrary(); 550 //cc.UserDefault.purgeSharedUserDefault(); 551 //ccGLInvalidateStateCache(); 552 553 cc.CHECK_GL_ERROR_DEBUG(); 554 555 // OpenGL view 556 //this._openGLView.end(); 557 //this._openGLView = null; 558 }, 559 560 /** 561 * <p> 562 * Suspends the execution of the running scene, pushing it on the stack of suspended scenes.<br/> 563 * The new scene will be executed.<br/> 564 * Try to avoid big stacks of pushed scenes to reduce memory allocation.<br/> 565 * ONLY call it if there is a running scene. 566 * </p> 567 * @param {cc.Scene} scene 568 */ 569 pushScene:function (scene) { 570 if(!scene) 571 throw "the scene should not null"; 572 573 this._sendCleanupToScene = false; 574 575 this._scenesStack.push(scene); 576 this._nextScene = scene; 577 }, 578 579 /** 580 * Replaces the running scene with a new one. The running scene is terminated. ONLY call it if there is a running scene. 581 * @param {cc.Scene} scene 582 */ 583 replaceScene:function (scene) { 584 if(!this._runningScene) 585 throw "Use runWithScene: instead to start the director"; 586 if(!scene) 587 throw "the scene should not be null"; 588 589 var i = this._scenesStack.length; 590 if(i === 0){ 591 this._sendCleanupToScene = true; 592 this._scenesStack[i] = scene; 593 this._nextScene = scene; 594 } else { 595 this._sendCleanupToScene = true; 596 this._scenesStack[i - 1] = scene; 597 this._nextScene = scene; 598 } 599 }, 600 601 /** 602 * resume director 603 */ 604 resume:function () { 605 if (!this._paused) { 606 return; 607 } 608 609 this.setAnimationInterval(this._oldAnimationInterval); 610 this._lastUpdate = Date.now(); 611 if (!this._lastUpdate) { 612 cc.log("cocos2d: Director: Error in gettimeofday"); 613 } 614 615 this._paused = false; 616 this._deltaTime = 0; 617 }, 618 619 /** 620 * <p> 621 * Enters the Director's main loop with the given Scene.<br/> 622 * Call it to run only your FIRST scene.<br/> 623 * Don't call it if there is already a running scene. 624 * </p> 625 * @param {cc.Scene} scene 626 */ 627 runWithScene:function (scene) { 628 if(!scene) 629 throw "This command can only be used to start the CCDirector. There is already a scene present."; 630 if(this._runningScene) 631 throw "_runningScene should be null"; 632 633 this.pushScene(scene); 634 this.startAnimation(); 635 }, 636 637 /** 638 * enables/disables OpenGL alpha blending 639 * @param {Boolean} on 640 */ 641 setAlphaBlending:function (on) { 642 if (on) 643 cc.glBlendFunc(cc.BLEND_SRC, cc.BLEND_DST); 644 else 645 cc.glBlendFunc(cc.renderContext.ONE, cc.renderContext.ZERO); 646 647 //cc.CHECK_GL_ERROR_DEBUG(); 648 }, 649 650 /** 651 * <p> 652 * The size in pixels of the surface. It could be different than the screen size.<br/> 653 * High-res devices might have a higher surface size than the screen size.<br/> 654 * Only available when compiled using SDK >= 4.0. 655 * </p> 656 * @param {Number} scaleFactor 657 */ 658 setContentScaleFactor:function (scaleFactor) { 659 if (scaleFactor != this._contentScaleFactor) { 660 this._contentScaleFactor = scaleFactor; 661 this._createStatsLabel(); 662 } 663 }, 664 665 /** 666 * enables/disables OpenGL depth test 667 * @param {Boolean} on 668 */ 669 setDepthTest:function (on) { 670 if(cc.renderContextType === cc.CANVAS) 671 return; 672 673 if (on) { 674 cc.renderContext.clearDepth(1.0); 675 cc.renderContext.enable(cc.renderContext.DEPTH_TEST); 676 cc.renderContext.depthFunc(cc.renderContext.LEQUAL); 677 //cc.renderContext.hint(cc.renderContext.PERSPECTIVE_CORRECTION_HINT, cc.renderContext.NICEST); 678 } else { 679 cc.renderContext.disable(cc.renderContext.DEPTH_TEST); 680 } 681 //cc.CHECK_GL_ERROR_DEBUG(); 682 }, 683 684 /** 685 * sets the default values based on the CCConfiguration info 686 */ 687 setDefaultValues:function(){ 688 689 }, 690 691 /** 692 * sets the OpenGL default values 693 */ 694 setGLDefaultValues:function () { 695 this.setAlphaBlending(true); 696 // XXX: Fix me, should enable/disable depth test according the depth format as cocos2d-iphone did 697 // [self setDepthTest: view_.depthFormat]; 698 this.setDepthTest(false); 699 this.setProjection(this._projection); 700 701 // set other opengl default values 702 cc.renderContext.clearColor(0.0, 0.0, 0.0, 1.0); 703 }, 704 705 /** 706 * set next delta time is zero 707 * @param {Boolean} nextDeltaTimeZero 708 */ 709 setNextDeltaTimeZero:function (nextDeltaTimeZero) { 710 this._nextDeltaTimeZero = nextDeltaTimeZero; 711 }, 712 713 /** 714 * set next scene 715 */ 716 setNextScene:function () { 717 var runningIsTransition = this._runningScene ? this._runningScene instanceof cc.TransitionScene : false; 718 719 var newIsTransition = this._nextScene ? this._nextScene instanceof cc.TransitionScene : false; 720 721 // If it is not a transition, call onExit/cleanup 722 if (!newIsTransition) { 723 if (this._runningScene) { 724 this._runningScene.onExitTransitionDidStart(); 725 this._runningScene.onExit(); 726 } 727 728 // issue #709. the root node (scene) should receive the cleanup message too 729 // otherwise it might be leaked. 730 if (this._sendCleanupToScene && this._runningScene) 731 this._runningScene.cleanup(); 732 } 733 734 this._runningScene = this._nextScene; 735 736 this._nextScene = null; 737 if ((!runningIsTransition) && (this._runningScene != null)) { 738 this._runningScene.onEnter(); 739 this._runningScene.onEnterTransitionDidFinish(); 740 } 741 }, 742 743 /** 744 * set Notification Node 745 * @param {cc.Node} node 746 */ 747 setNotificationNode:function (node) { 748 this._notificationNode = node; 749 }, 750 751 /** 752 * CCDirector delegate. It shall implemente the CCDirectorDelegate protocol 753 * @return {cc.DirectorDelegate} 754 */ 755 getDelegate:function () { 756 return this._projectionDelegate; 757 }, 758 759 setDelegate:function (delegate) { 760 this._projectionDelegate = delegate; 761 }, 762 763 /** 764 * Set the CCEGLView, where everything is rendered 765 * @param {*} openGLView 766 */ 767 setOpenGLView:function (openGLView) { 768 // set size 769 this._winSizeInPoints = cc.size(cc.canvas.width, cc.canvas.height); //this._openGLView.getDesignResolutionSize(); 770 this._openGLView = openGLView || cc.EGLView.getInstance(); 771 772 if (cc.renderContextType === cc.CANVAS) 773 return; 774 775 // Configuration. Gather GPU info 776 var conf = cc.Configuration.getInstance(); 777 conf.gatherGPUInfo(); 778 conf.dumpInfo(); 779 780 // set size 781 //this._winSizeInPoints = this._openGLView.getDesignResolutionSize(); 782 //this._winSizeInPixels = cc.size(this._winSizeInPoints.width * this._contentScaleFactor, this._winSizeInPoints.height * this._contentScaleFactor); 783 784 //if (this._openGLView != openGLView) { 785 // because EAGLView is not kind of CCObject 786 787 this._createStatsLabel(); 788 789 //if (this._openGLView) 790 this.setGLDefaultValues(); 791 792 /* if (this._contentScaleFactor != 1) { 793 this.updateContentScaleFactor(); 794 }*/ 795 796 this._touchDispatcher.setDispatchEvents(true); 797 //} 798 }, 799 800 /** 801 * Sets the glViewport 802 */ 803 setViewport:function(){ 804 if(this._openGLView) { 805 var locWinSizeInPoints = this._winSizeInPoints; 806 this._openGLView.setViewPortInPoints(0,0, locWinSizeInPoints.width, locWinSizeInPoints.height); 807 } 808 }, 809 810 /** 811 * Sets an OpenGL projection 812 * @param {Number} projection 813 */ 814 setProjection:function (projection) { 815 var size = this._winSizeInPoints; 816 817 if(cc.renderContextType === cc.WEBGL){ 818 this.setViewport(); 819 820 switch (projection) { 821 case cc.DIRECTOR_PROJECTION_2D: 822 cc.kmGLMatrixMode(cc.KM_GL_PROJECTION); 823 cc.kmGLLoadIdentity(); 824 var orthoMatrix = new cc.kmMat4(); 825 cc.kmMat4OrthographicProjection(orthoMatrix, 0, size.width, 0, size.height, -1024, 1024); 826 cc.kmGLMultMatrix(orthoMatrix); 827 cc.kmGLMatrixMode(cc.KM_GL_MODELVIEW); 828 cc.kmGLLoadIdentity(); 829 break; 830 case cc.DIRECTOR_PROJECTION_3D: 831 var zeye = this.getZEye(); 832 var matrixPerspective = new cc.kmMat4(), matrixLookup = new cc.kmMat4(); 833 cc.kmGLMatrixMode(cc.KM_GL_PROJECTION); 834 cc.kmGLLoadIdentity(); 835 836 // issue #1334 837 cc.kmMat4PerspectiveProjection(matrixPerspective, 60, size.width / size.height, 0.1, zeye * 2); 838 // kmMat4PerspectiveProjection( &matrixPerspective, 60, (GLfloat)size.width/size.height, 0.1f, 1500); 839 840 cc.kmGLMultMatrix(matrixPerspective); 841 842 cc.kmGLMatrixMode(cc.KM_GL_MODELVIEW); 843 cc.kmGLLoadIdentity(); 844 var eye = cc.kmVec3Fill(null, size.width / 2, size.height / 2, zeye); 845 var center = cc.kmVec3Fill(null, size.width / 2, size.height / 2, 0.0); 846 var up = cc.kmVec3Fill(null, 0.0, 1.0, 0.0); 847 cc.kmMat4LookAt(matrixLookup, eye, center, up); 848 cc.kmGLMultMatrix(matrixLookup); 849 break; 850 case cc.DIRECTOR_PROJECTION_CUSTOM: 851 if (this._projectionDelegate) 852 this._projectionDelegate.updateProjection(); 853 break; 854 default: 855 cc.log("cocos2d: Director: unrecognized projection"); 856 break; 857 } 858 } 859 this._projection = projection; 860 cc.setProjectionMatrixDirty(); 861 }, 862 863 /** 864 * shows the FPS in the screen 865 */ 866 _showStats:function () { 867 this._frames++; 868 this._accumDt += this._deltaTime; 869 if (this._displayStats) { 870 if (this._FPSLabel && this._SPFLabel && this._drawsLabel) { 871 if (this._accumDt > cc.DIRECTOR_FPS_INTERVAL) { 872 this._SPFLabel.setString(this._secondsPerFrame.toFixed(3)); 873 874 this._frameRate = this._frames / this._accumDt; 875 this._frames = 0; 876 this._accumDt = 0; 877 878 this._FPSLabel.setString(this._frameRate.toFixed(1)); 879 this._drawsLabel.setString((0 | cc.g_NumberOfDraws).toString()); 880 } 881 this._FPSLabel.visit(); 882 this._SPFLabel.visit(); 883 this._drawsLabel.visit(); 884 } else { 885 this._createStatsLabel(); 886 } 887 } 888 cc.g_NumberOfDraws = 0; 889 }, 890 891 /** 892 * <p> 893 * Whether or not the replaced scene will receive the cleanup message.<br> 894 * If the new scene is pushed, then the old scene won't receive the "cleanup" message.<br/> 895 * If the new scene replaces the old one, the it will receive the "cleanup" message. 896 * </p> 897 * @return {Boolean} 898 */ 899 isSendCleanupToScene:function () { 900 return this._sendCleanupToScene; 901 }, 902 903 /** 904 * Get current running Scene. Director can only run one Scene at the time 905 * @return {cc.Scene} 906 */ 907 getRunningScene:function () { 908 return this._runningScene; 909 }, 910 911 /** 912 * Get the FPS value 913 * @return {Number} 914 */ 915 getAnimationInterval:function () { 916 return this._animationInterval; 917 }, 918 919 /** 920 * Whether or not to display the FPS on the bottom-left corner 921 * @return {Boolean} 922 */ 923 isDisplayStats:function () { 924 return this._displayStats; 925 }, 926 927 /** 928 * Display the FPS on the bottom-left corner 929 * @param {Boolean} displayStats 930 */ 931 setDisplayStats:function (displayStats) { 932 this._displayStats = displayStats; 933 }, 934 935 /** 936 * seconds per frame 937 * @return {Number} 938 */ 939 getSecondsPerFrame:function () { 940 return this._secondsPerFrame; 941 }, 942 943 /** 944 * Get the CCEGLView, where everything is rendered 945 * @return {*} 946 */ 947 getOpenGLView:function () { 948 return this._openGLView; 949 }, 950 951 /** 952 * is next delta time zero 953 * @return {Boolean} 954 */ 955 isNextDeltaTimeZero:function () { 956 return this._nextDeltaTimeZero; 957 }, 958 959 /** 960 * Whether or not the Director is paused 961 * @return {Boolean} 962 */ 963 isPaused:function () { 964 return this._paused; 965 }, 966 967 /** 968 * How many frames were called since the director started 969 * @return {Number} 970 */ 971 getTotalFrames:function () { 972 return this._totalFrames; 973 }, 974 975 /** 976 * Sets an OpenGL projection 977 * @return {Number} 978 */ 979 getProjection:function () { 980 return this._projection; 981 }, 982 983 /** 984 * <p> 985 * Pops out all scenes from the queue until the root scene in the queue. <br/> 986 * This scene will replace the running one. <br/> 987 * Internally it will call `popToSceneStackLevel(1)` 988 * </p> 989 */ 990 popToRootScene:function () { 991 this.popToSceneStackLevel(1); 992 }, 993 994 /** 995 * <p> 996 * Pops out all scenes from the queue until it reaches `level`. <br/> 997 * If level is 0, it will end the director. <br/> 998 * If level is 1, it will pop all scenes until it reaches to root scene. <br/> 999 * If level is <= than the current stack level, it won't do anything. 1000 * </p> 1001 * @param {Number} level 1002 */ 1003 popToSceneStackLevel: function (level) { 1004 if(!this._runningScene) 1005 throw "A running Scene is needed"; 1006 1007 var locScenesStack = this._scenesStack; 1008 var c = locScenesStack.length; 1009 1010 if (c == 0) { 1011 this.end(); 1012 return; 1013 } 1014 // current level or lower -> nothing 1015 if (level > c) 1016 return; 1017 1018 // pop stack until reaching desired level 1019 while (c > level) { 1020 var current = locScenesStack.pop(); 1021 if (current.isRunning()) { 1022 current.onExitTransitionDidStart(); 1023 current.onExit(); 1024 } 1025 current.cleanup(); 1026 c--; 1027 } 1028 this._nextScene = locScenesStack[locScenesStack.length - 1]; 1029 this._sendCleanupToScene = false; 1030 }, 1031 1032 /** 1033 * (cc.Scheduler associated with this director) 1034 */ 1035 getScheduler:function () { 1036 return this._scheduler; 1037 }, 1038 1039 setScheduler:function (scheduler) { 1040 if (this._scheduler != scheduler) { 1041 this._scheduler = scheduler; 1042 } 1043 }, 1044 1045 getActionManager:function () { 1046 return this._actionManager; 1047 }, 1048 setActionManager:function (actionManager) { 1049 if (this._actionManager != actionManager) { 1050 this._actionManager = actionManager; 1051 } 1052 }, 1053 1054 getTouchDispatcher:function () { 1055 return this._touchDispatcher; 1056 }, 1057 setTouchDispatcher:function (touchDispatcher) { 1058 if (this._touchDispatcher != touchDispatcher) { 1059 this._touchDispatcher = touchDispatcher; 1060 } 1061 }, 1062 1063 getKeyboardDispatcher:function () { 1064 if(!cc.KeyboardDispatcher) 1065 throw "cc.KeyboardDispatcher is undefined, maybe it has been removed from js loading list."; 1066 return this._keyboardDispatcher; 1067 }, 1068 setKeyboardDispatcher:function (keyboardDispatcher) { 1069 if(!cc.KeyboardDispatcher) 1070 throw "cc.KeyboardDispatcher is undefined, maybe it has been removed from js loading list."; 1071 this._keyboardDispatcher = keyboardDispatcher; 1072 }, 1073 1074 getAccelerometer:function () { 1075 if(!cc.Accelerometer) 1076 throw "cc.Accelerometer is undefined, maybe it has been removed from js loading list."; 1077 return this._accelerometer; 1078 }, 1079 setAccelerometer:function (accelerometer) { 1080 if(!cc.Accelerometer) 1081 throw "cc.Accelerometer is undefined, maybe it has been removed from js loading list."; 1082 if (this._accelerometer != accelerometer) 1083 this._accelerometer = accelerometer; 1084 }, 1085 1086 getDeltaTime:function(){ 1087 return this._deltaTime; 1088 }, 1089 1090 getMouseDispatcher:function () { 1091 if(!cc.MouseDispatcher) 1092 throw "cc.MouseDispatcher is undefined, maybe it has been removed from js loading list."; 1093 return this._mouseDispatcher; 1094 }, 1095 1096 setMouseDispatcher:function (mouseDispatcher) { 1097 if(!cc.MouseDispatcher) 1098 throw "cc.MouseDispatcher is undefined, maybe it has been removed from js loading list."; 1099 if (this._mouseDispatcher != mouseDispatcher) 1100 this._mouseDispatcher = mouseDispatcher; 1101 }, 1102 1103 _createStatsLabel: null, 1104 1105 _createStatsLabelForWebGL:function(){ 1106 if((cc.Director._fpsImageLoaded == null) || (cc.Director._fpsImageLoaded == false)) 1107 return; 1108 1109 var texture = new cc.Texture2D(); 1110 texture.initWithElement(cc.Director._fpsImage); 1111 texture.handleLoadedTexture(); 1112 1113 /* 1114 We want to use an image which is stored in the file named ccFPSImage.c 1115 for any design resolutions and all resource resolutions. 1116 1117 To achieve this, 1118 1119 Firstly, we need to ignore 'contentScaleFactor' in 'CCAtlasNode' and 'CCLabelAtlas'. 1120 So I added a new method called 'setIgnoreContentScaleFactor' for 'CCAtlasNode', 1121 this is not exposed to game developers, it's only used for displaying FPS now. 1122 1123 Secondly, the size of this image is 480*320, to display the FPS label with correct size, 1124 a factor of design resolution ratio of 480x320 is also needed. 1125 */ 1126 var factor = cc.EGLView.getInstance().getDesignResolutionSize().height / 320.0; 1127 if(factor === 0) 1128 factor = this._winSizeInPoints.height / 320.0; 1129 1130 var tmpLabel = new cc.LabelAtlas(); 1131 tmpLabel._setIgnoreContentScaleFactor(true); 1132 tmpLabel.initWithString("00.0", texture, 12, 32 , '.'); 1133 tmpLabel.setScale(factor); 1134 this._FPSLabel = tmpLabel; 1135 1136 tmpLabel = new cc.LabelAtlas(); 1137 tmpLabel._setIgnoreContentScaleFactor(true); 1138 tmpLabel.initWithString("0.000", texture, 12, 32, '.'); 1139 tmpLabel.setScale(factor); 1140 this._SPFLabel = tmpLabel; 1141 1142 tmpLabel = new cc.LabelAtlas(); 1143 tmpLabel._setIgnoreContentScaleFactor(true); 1144 tmpLabel.initWithString("000", texture, 12, 32, '.'); 1145 tmpLabel.setScale(factor); 1146 this._drawsLabel = tmpLabel; 1147 1148 var locStatsPosition = cc.DIRECTOR_STATS_POSITION; 1149 this._drawsLabel.setPosition(cc.pAdd(cc.p(0, 34 * factor), locStatsPosition)); 1150 this._SPFLabel.setPosition(cc.pAdd(cc.p(0, 17 * factor), locStatsPosition)); 1151 this._FPSLabel.setPosition(locStatsPosition); 1152 }, 1153 1154 _createStatsLabelForCanvas:function(){ 1155 var fontSize = 0; 1156 if (this._winSizeInPoints.width > this._winSizeInPoints.height) 1157 fontSize = 0 | (this._winSizeInPoints.height / 320 * 24); 1158 else 1159 fontSize = 0 | (this._winSizeInPoints.width / 320 * 24); 1160 1161 this._FPSLabel = cc.LabelTTF.create("000.0", "Arial", fontSize); 1162 this._SPFLabel = cc.LabelTTF.create("0.000", "Arial", fontSize); 1163 this._drawsLabel = cc.LabelTTF.create("0000", "Arial", fontSize); 1164 1165 var locStatsPosition = cc.DIRECTOR_STATS_POSITION; 1166 var contentSize = this._drawsLabel.getContentSize(); 1167 this._drawsLabel.setPosition(cc.pAdd(cc.p(contentSize.width / 2, contentSize.height * 5 / 2), locStatsPosition)); 1168 contentSize = this._SPFLabel.getContentSize(); 1169 this._SPFLabel.setPosition(cc.pAdd(cc.p(contentSize.width / 2, contentSize.height * 3 / 2), locStatsPosition)); 1170 contentSize = this._FPSLabel.getContentSize(); 1171 this._FPSLabel.setPosition(cc.pAdd(cc.p(contentSize.width / 2, contentSize.height / 2), locStatsPosition)); 1172 }, 1173 1174 _calculateMPF: function () { 1175 var now = Date.now(); 1176 this._secondsPerFrame = (now - this._lastUpdate) / 1000; 1177 } 1178 }); 1179 1180 if (cc.Browser.supportWebGL) { 1181 cc.Director.prototype._clear = cc.Director.prototype._clearWebGL; 1182 cc.Director.prototype._beforeVisitScene = cc.Director.prototype._beforeVisitSceneWebGL; 1183 cc.Director.prototype._afterVisitScene = cc.Director.prototype._afterVisitSceneWebGL; 1184 cc.Director.prototype._createStatsLabel = cc.Director.prototype._createStatsLabelForWebGL; 1185 } else { 1186 cc.Director.prototype._clear = cc.Director.prototype._clearCanvas; 1187 cc.Director.prototype._createStatsLabel = cc.Director.prototype._createStatsLabelForCanvas; 1188 } 1189 1190 /*************************************************** 1191 * implementation of DisplayLinkDirector 1192 **************************************************/ 1193 // should we afford 4 types of director ?? 1194 // I think DisplayLinkDirector is enough 1195 // so we now only support DisplayLinkDirector 1196 /** 1197 * <p> 1198 * DisplayLinkDirector is a Director that synchronizes timers with the refresh rate of the display.<br/> 1199 * Features and Limitations:<br/> 1200 * - Scheduled timers & drawing are synchronizes with the refresh rate of the display<br/> 1201 * - Only supports animation intervals of 1/60 1/30 & 1/15<br/> 1202 * </p> 1203 * @class 1204 * @extends cc.Director 1205 */ 1206 cc.DisplayLinkDirector = cc.Director.extend(/** @lends cc.DisplayLinkDirector# */{ 1207 invalid:false, 1208 1209 /** 1210 * start Animation 1211 */ 1212 startAnimation:function () { 1213 this._nextDeltaTimeZero = true; 1214 this.invalid = false; 1215 cc.Application.getInstance().setAnimationInterval(this._animationInterval); 1216 }, 1217 1218 /** 1219 * main loop of director 1220 */ 1221 mainLoop:function () { 1222 if (this._purgeDirecotorInNextLoop) { 1223 this._purgeDirecotorInNextLoop = false; 1224 this.purgeDirector(); 1225 } 1226 else if (!this.invalid) { 1227 this.drawScene(); 1228 } 1229 }, 1230 1231 /** 1232 * stop animation 1233 */ 1234 stopAnimation:function () { 1235 this.invalid = true; 1236 }, 1237 1238 /** 1239 * set Animation Interval 1240 * @param {Number} value 1241 */ 1242 setAnimationInterval:function (value) { 1243 this._animationInterval = value; 1244 if (!this.invalid) { 1245 this.stopAnimation(); 1246 this.startAnimation(); 1247 } 1248 } 1249 }); 1250 1251 cc.s_SharedDirector = null; 1252 1253 cc.firstUseDirector = true; 1254 1255 /** 1256 * returns a shared instance of the director 1257 * @function 1258 * @return {cc.Director} 1259 */ 1260 cc.Director.getInstance = function () { 1261 if (cc.firstUseDirector) { 1262 cc.firstUseDirector = false; 1263 cc.s_SharedDirector = new cc.DisplayLinkDirector(); 1264 cc.s_SharedDirector.init(); 1265 cc.s_SharedDirector.setOpenGLView(cc.EGLView.getInstance()); 1266 } 1267 return cc.s_SharedDirector; 1268 }; 1269 1270 /** 1271 * is director first run 1272 * @type Boolean 1273 */ 1274 cc.firstRun = true; 1275 1276 /** 1277 * set default fps to 60 1278 * @type Number 1279 */ 1280 cc.defaultFPS = 60; 1281 1282 /* 1283 window.onfocus = function () { 1284 if (!cc.firstRun) { 1285 cc.Director.getInstance().addRegionToDirtyRegion(cc.rect(0, 0, cc.canvas.width, cc.canvas.height)); 1286 } 1287 }; 1288 */ 1289 cc.Director._fpsImage = new Image(); 1290 cc.Director._fpsImage.addEventListener("load", function () { 1291 cc.Director._fpsImageLoaded = true; 1292 this.removeEventListener('load', arguments.callee, false); 1293 }); 1294 cc.Director._fpsImage.src = "data:image/png;base64,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"; 1295 1296 1297