/** * Copyright (c) 2015-2019. Docentron PTY LTD. All rights reserved. * This material may not be reproduced, displayed, modified or distributed without * the express prior written permission of the copyright holder. * * This is a Docentron application created using Docentron Web Application API * Click on DC API tab to browse DC API classes and functions. * Click on Asset List tab to browse game assets defined for this applicaiton template. * This applicaiton also uses Phaser/PixiJS game API. * * Many application templates are provided to get started quickly. The templates demonstrate how the API can be used * to quickly create web applications and games. * * Applicaiton Name: Drag To Count Game. * Application type: Arcade Puzzle. * Base game: Matching Game. * Author: Docentron * * version 1.4 Added support for multi point support up to 2 pointers * version 1.2 Updated head comment, added braces for IF statements, added missing comments, renamed names * version 1.1 Added support for sprite for answer images * version : 1.0 * - Add feedback animations like sparkling when clicked on a correct/incorrect places * - Add wonderful animations and sounds when click Done * - Add correct effect sprite * - Add correct effect sound * - Add wrong effect sprite * - Add wrong effect sprite * - Add Victory effect sound * - Add Alert effect sound * - Add 2 more Frame at Robot sprite * - Not destroy answer when wrong * - Destroy with animation 1 by 1 when correct * **************************************************************/ /************************************************************** * Entry point of the game app * This is automatically called when the application is loaded. **************************************************************/ window.ibsgl = ibStartGameLevel; // Do not change this line window.startDCApp = function(){ibStartGameLevel("game_stage_one");}; // DEVELOPER_CODE Do not change this line function ibStartGameLevel(gameContainer){ //----------------------------------------------- // TODO: Step 1: update/replace PuzzleGame class with your own Game Class // TODO: Step 2: update/replace PStatePlay class with your own StatePlay class Genari.prepareGame(DragCountGame, 800, 426, GameControl, PStatePlay); // DO NOT change this // All game assets are accessible using Genari.dcGame.k.assetKeyName // TODO: Step 3: override and customise Genari.StatePlay methods // Genari.Control: GameControl // Genari.Game: DragCountGame // Genari.StatePlay: preloadState() Load theme assets // Genari.StatePlay: loadThemeAssets() // Genari.StatePlay: createLayersGroups() // Genari.StatePlay: createWorld() // Genari.StatePlay: createHud() Setup the HUD of the game: score bars, pause button, player icon,... // Genari.StatePlay: createPlayer() Create player character // Genari.StatePlay: createStateObjects() Create background, tilemaps, monsters, pickup items // Genari.StatePlay: shutdownState() Clean up game assets after completing the game level // Genari.StatePlay: updateState() Update state for each frame // Genari.Group: Add Group classes to manage your game objects (monsters, bullets, pickup items, boss,...) } /************************************************************** * The game class that holds game specific information. * * An instance of this is automatically created by Genari.prepareGame() and stored in Genari.dcGame. * This is not a Phaser.Game object. * Genari.dcGame.phaserGame contains Phaser.Game. * * Use Javascript annotations to document your code: * https://developers.google.com/closure/compiler/docs/js-for-compiler#tags * * @param {Object} game_themes An array of themes. A theme is a dictionary. See app.js for theme data * @param {number|undefined} [game_width] Default 800 Width of the canvas * @param {number|undefined} [game_height] Default 426 Height of the canvas * * @constructor * @extends {Genari.Game} ***************************************************************/ var DragCountGame = function(game_themes){ if (!(this instanceof DragCountGame)) return new DragCountGame(); Genari.Game.call(this, game_themes); // call the parent constructor this.gc_score_text_x = this.game_width - 40; /** @type{Genari.Hud} */ this.hud = null; this.checkEnd = false; }; DragCountGame.prototype = Object.create( Genari.Game.prototype ); // extend DBGame DragCountGame.prototype.constructor = DragCountGame; // set constructor property /** * Check the end of the game level and load the next level if the current level is completed */ DragCountGame.prototype.checkLevelEnd = function(){ // check if answered all questions correctly if (this.scoreBox.getValue() < this.groupAnswers.numberOfAnswers){ return; } if (this.checkEnd === false){ // delay 3 second before level end setTimeout(function(){ this.checkEnd = false; this.saveAndStartNextLevel(); // Record score and start next level }.bind(this), 1000); // make levelEnd just execute once this.checkEnd = true; } }; /************************************************************** * Control class for checking and updating buttons, keyboard, mouse, and finger touchs. * * Manages creation and control of all keyboard controls and on-screen button or joystick controls * * @param {Genari.Game} dcGame The only game object * @param {Genari.Player} player The player character that user controls * * @constructor * @extends {Genari.Control} **************************************************************/ var GameControl = function(dcGame, player, bulletSpriteKey, bulletSoundKey){ if (!(this instanceof GameControl)){return new GameControl(dcGame, player);} Genari.Control.call(this, dcGame, player); // Never redefine inherited properties and methods here. // To override methods, must use prototype. // Constants related control buttons this.BUTTON_EDGE_MARGIN = 10; // Other attributes that we often need to refer to this.player = player; this.dcGame = dcGame; this.pointerUp = [true, true, true]; //array of comment sound effect this.correctCommentSndKey = [Genari.k.correctComment1SndKey, Genari.k.correctComment2SndKey, Genari.k.correctComment3SndKey]; this.wrongCommentSndKey = [Genari.k.wrongComment1SndKey, Genari.k.wrongComment2SndKey, Genari.k.wrongComment3SndKey]; this.isCheck = false; }; GameControl.prototype = Object.create(Genari.Control.prototype); // extends Genari.Control GameControl.prototype.constructor = GameControl; // set constructor property /** * This is called when the play state is about to start for us to prepare the buttons, keyboard shortcut keys, pointers, etc. * * Put here code for creating control buttons etc. */ GameControl.prototype.createControls = function(){ var hintGroup = this.dcGame.groupHints; var answerGroup = this.dcGame.groupAnswers; // Destroy any existing buttons this.destroyControlButtons(); this.pointerUp = [true, true, true]; // Add some buttons here if needed /** DONE BUTTON */ var doneBtn = this.addControlButton( 482, 359, Genari.k.doneBtnKey, function(){ hintGroup.getGroup().forEach( function(hint){ if(hint.posOrder != hintGroup.currentIdx || this.isCheck){ // not current hint or on checking return; } // change isCheck to true to prevent looping this.isCheck = true; var idx = Math.floor(Math.random() * 3); // get random number from 0 to 2 var sum = hint.answerArrayNum.reduce((a, b) => a + b, 0); // get sum of all array elements if(sum === 0){ // && answerGroup.errorNum === 0){ // quantity is correct and no error this.dcGame.updateScore(1); this.dcGame.groupHints.victorySprite.alpha = 1; this.dcGame.audioPlay(Genari.k.victoryEffectSndKey, undefined, undefined, undefined, undefined, undefined, undefined, // callback function function(){ this.dcGame.audioPlay(this.correctCommentSndKey[idx]); this.dcGame.groupHints.victorySprite.alpha = 0; hintGroup.currentIdx++; }.bind(this) ); answerGroup.destroyAnswer(); } else { // quantity is not correct or there is error this.dcGame.groupHints.isIdleAnim = false; this.dcGame.audioPlay(Genari.k.alertEffectSndKey, undefined, undefined, undefined, undefined, undefined, undefined, // callback function function(){ this.dcGame.audioPlay(this.wrongCommentSndKey[idx]); this.dcGame.groupHints.isIdleAnim = true; }.bind(this) ); this.isCheck = false; /* answerGroup.destroyAnswer(); for(var i = 0; i < hint.answerArrayNum.length; i++) hint.answerArrayNum[i] = hint.answerArrayNumOrigin[i]; */ } }, this ); this.dcGame.audioPlay(Genari.k.selectEffectSndKey); }.bind(this), function(){}, this ); // at the moment we are not using any buttons in this game. Comment this out if you are adding some. this.createButtonHitArea(); // if added some buttons here, uncomment to add button hit area for the buttons that are added. // Hide the on-screen buttons until the play-state starts. Buttons will automatically shown when it starts. this.hideOnScreenButtons(); }; /** * Chcek a pointer and update pickup/drag/drop of hint/answer images * @param {Array} pointers Array of pointer states [mousePointerState, touchPointer1State, touchPointer2State] * @param {number} pointerId */ GameControl.prototype.updatePointer = function (pointers, pointerId) { var pointer; pointer = pointers[pointerId]; // get the selected pointer state [x, y, isDown] if(pointer[2]){ // pointer down // pickup a hint image under the pointer. // We make effect sound if pointer moves over hint images // We also us this later to check if the answer image is dropped at the correct location this.dcGame.groupDropBoxes.pickupDropBox(pointer, pointerId); // check if there is a hint image under cur loc and play sound if visiting a new hint image //this.dcGame.groupHints.pickUpHintImage(pointer, pointerId); // check if there is a hint image under cur loc and play sound if visiting a new hint image // if pointer was up, if (this.pointerUp[pointerId]){ // pointer is just down from up state // pickup an answer image if there is an answer image under the pointer this.dcGame.groupAnswers.pickupAnswerImage(pointer, pointerId); // pick up an answer image object and start to drag it this.pointerUp[pointerId] = false; // set to pointer down state. We use this to track state change event } // Perform drag action of a selected answer image if an asnwer image is selected. this.dcGame.groupAnswers.dragAnswerImage(pointer, pointerId); } else { // pointer up // if pointer was down and picked up an ansewr, drop it and check if dropped in the right location. if (!this.pointerUp[pointerId]){ // drop the selected answer image and check if it is dropped at the correct loacation this.dcGame.groupAnswers.dropAnswerImage(pointerId); this.dcGame.groupDropBoxes.selectedDropBox[pointerId] = null; this.pointerUp[pointerId] = true; // set to pointer up } } }; /** * Called by play-state updateState() for each frame, before the frame is rendered to the display. * This allow us to check keyboards, joystick, finger touches, and mouse clicks. * * Make sure we call this from play-state.updateState() * * In this game, we check if a pointer (finger touch or mouse button) is down or up and perform some actions: * pickup an answer image, drag answer image, drop answer image. */ GameControl.prototype.update = function(){ if (this.dcGame.isPaused) return; // TODO: learning point. We get pointer states for all three pointers: mouse pointer, touch pointer 1, touch // game is not paused, so we pickUpHintImage or drag or drop var pointers = Genari.getPointerStates(); // get states of mouse pointer, touch pointer 1, touch // we track pointer actions seperately. Now pickingup/dragging/dropping hint/answer images are indexed by pointerId this.updatePointer(pointers, 0); // pointerId = 0: mouse pointer this.updatePointer(pointers, 1); // pointerId = 1: touch pointer 1 this.updatePointer(pointers, 2); // pointerId = 2: touch pointer 2 // if (!(this.dcGame.isPaused)){ // // game is not paused, so we pickupDropBox // if (Genari.phaserGame.input.activePointer.isDown){ // // a pointer (mouse or finger touch) is in down state // this.dcGame.groupDropBoxes.pickupDropBox(); // check if there is a drop box image under cur loc and play sound if visiting a new drop box image // if (this.pointerUp){ // // pointer is just down from up state // this.dcGame.groupAnswers.pickupAnswerImage(); // pick up an answer image object and start to drag it // this.pointerUp = false; // set to pointer down state. We use this to track state change event // } // // Perform drag action of a selected answer image if an asnwer image is selected. // this.dcGame.groupAnswers.dragAnswerImage(); // } else { // // pointer up // // if pointer was down and picked up an ansewr, drop it and check if dropped in the right location. // if (!this.pointerUp){ // this.dcGame.groupAnswers.dropAnswerImage(); // this.dcGame.groupDropBoxes.selectedDropBox = null; // this.pointerUp = true; // set to pointer up // } // } // } }; /************************************************************** * Phaser state class, Play state * You can extend this class to customise your Play state * @param {DCGame} theGameObj the only game object * @param {string} key name of the state E.g., "play" * * @constructor * @extends {Genari.StatePlay} **************************************************************/ var PStatePlay = function(theGameObj, key){ if (!(this instanceof PStatePlay)) return new PStatePlay(theGameObj, key); Genari.StatePlay.call(this, theGameObj, key); // call parent constructor }; PStatePlay.prototype = Object.create(Genari.StatePlay.prototype); // extend Genari.StatePlay PStatePlay.prototype.constructor = PStatePlay; // set constructor property /** * Load all theme assets before show them */ PStatePlay.prototype.loadThemeAssets = function(){ // Load assets before use Genari.loadThemeImageSpriteAssets(); // load all images and sprites including tileset images here Genari.loadThemeAudioAssets(); this.loadAttributes(); }; /** * Read the attribute asset values */ PStatePlay.prototype.loadAttributes = function(){ var thisGame = this.dcGame; // resize the size of answer image thisGame.multiplierAnswer = Genari.getThemeAttribute(Genari.k.answerSizeMultiplierAttKey, 60); // first answer x-axis position thisGame.answerXpos = Genari.getThemeAttribute(Genari.k.answerXposAttKey, 145); // correct answer y-axis position thisGame.caYpos = Genari.getThemeAttribute(Genari.k.correctAnswerYposAttKey, 112); // incorrect answer y-axis position thisGame.iaYpos = Genari.getThemeAttribute(Genari.k.incorrectAnswerYposAttKey, 365); // Answer Image Intervals (px) thisGame.answerInterval = Genari.getThemeAttribute(Genari.k.answerImageIntervalAttKey, 170); }; /** * Called before play-state starts for us to prepare. * * This allows us to create Layers and Groups for our game. * Layers are used to control the rendering order. Some game objects will be on top of something else. * Groups are used to control groups of game objects such as monsters, pickup items. * @override */ PStatePlay.prototype.createLayersGroups = function(){ Genari.StatePlay.prototype.createLayersGroups.call(this); // destroy previous assets/objects loaded if any. if (this.dcGame.layerBottom) this.dcGame.layerBottom.destroy(true); if (this.dcGame.layerPlayer) this.dcGame.layerPlayer.destroy(true); if (this.dcGame.layerEffects) this.dcGame.layerEffects.destroy(true); if (this.dcGame.layerButtons) this.dcGame.layerButtons.destroy(true); if (this.dcGame.layerDialog) this.dcGame.layerDialog.destroy(true); // Layers to control the rendering order. You can add your own layers to control z-order. // layers are rendered based on the order they are created this.dcGame.layerBottom = new Genari.Layer(); // Use this as the bottom layer, Always show at the bottom. Holds word boxes this.dcGame.layerPlayer = new Genari.Layer(); // holds players, monsters, player interactive items. this.dcGame.layerEffects = new Genari.Layer(); // holds explosions, bullets, effects this.dcGame.layerButtons = new Genari.Layer(); // holds control buttons, frames, windows, HUD items this.dcGame.layerDialog = new Genari.Layer(); // Always show at the top. Holds dialog boxes. Top layer // Groups to manage multiples of similar objects. // Use predefined groups which provides method of creating common types of monsters, word boxes, bullets, pick-up items // Groups also provide methods for handling collisions Genari.Group.checkOverlap() if (this.dcGame.groupDropBoxes) this.dcGame.groupDropBoxes.destroy(); if (this.dcGame.groupHints) this.dcGame.groupHints.destroy(); if (this.dcGame.groupAnswers) this.dcGame.groupAnswers.destroy(); this.dcGame.groupDropBoxes = new Genari.DropBoxGroup(this.dcGame.layerBottom, true, true); // create one object for both hint image objects and answer image objects... this.dcGame.groupHints = new Genari.HintGroup(this.dcGame.layerPlayer, true, true); this.dcGame.groupAnswers = new Genari.AnswersGroup(this.dcGame.layerPlayer, true, true); }; /** * Called before play-state starts for us to prepare. * * This allows us to place the assets to the stage to show. * The assets must be loaded before we can place them. * loadThemeAssets would have already loaded all theme assets for us. * @override */ PStatePlay.prototype.createStateObjects = function(){ var thisGame = this.dcGame; // Prepare the background and set world bounds this.createWorld(); // Create the HUD this.createHud(); // Create effects this.dcGame.correctEffectAnim = Genari.Effect.addExplosion(Genari.k.correctEffectSpriteKey, 'correct', this.dcGame.layerEffects); this.dcGame.wrongEffectAnim = Genari.Effect.addExplosion(Genari.k.wrongEffectSpriteKey, 'wrong', this.dcGame.layerEffects); // Add control keys and onscreen control buttons. We have no player: pass null thisGame.gameControl = new GameControl(thisGame, null, Genari.k.bulletSpriteKey, Genari.k.bulletSoundKey); // game, player, bullet obj, bullet sound key thisGame.gameControl.createControls(); // create game assets thisGame.groupHints.spawnHintImages(); thisGame.groupHints.createCharSprite(); thisGame.groupHints.createVictorySprite(); thisGame.groupHints.createOrnament(); thisGame.groupAnswers.createHoldingArea(); thisGame.groupDropBoxes.spawnDropLoc(); thisGame.groupAnswers.spawnAnswerImageObjs(); }; /** * Called before play-state starts for us to prepare. * * This allow us to place (add) display objects in the stage to show. * @override */ PStatePlay.prototype.createWorld = function(){ var thisGame = this.dcGame; // Add the background image and set the world bound Genari.addBackground( thisGame.layerBottom, Genari.k.backgroundImgKey, thisGame.game_width, // world width thisGame.game_height, // world height null, null, 800, 432 ); }; /** * Called before play-state starts for us to prepare. * * We use this to add HUD items that shows player icon, life remaining, scores, etc. * @override */ PStatePlay.prototype.createHud = function(){ var thisGame = this.dcGame; //Create the hud for the game. thisGame.hud = Genari.add.hud(thisGame.layerButtons); //Create the pause button so that player can pause the game. thisGame.hud.addPauseButton( thisGame.BUTTON_EDGE_MARGIN, thisGame.BUTTON_EDGE_MARGIN, Genari.k.pauseButtonSpriteKey, 45, thisGame.pause.bind(thisGame), //GApp.pages.pause_game.init thisGame ); //Use user's photo to represent score. thisGame.hud.addUserPhoto(thisGame.game_width - 160, thisGame.BUTTON_EDGE_MARGIN, Genari.k.userPhotoKey, Genari.k.userPhotoFrameKey, 40); //Create the elements of the status bar on the top. thisGame.scoreBox = Genari.add.scoreBox(Genari.k.scoreReelSpriteKey, 30, thisGame.gc_score_text_x, 15, thisGame.layerButtons); // Create Level text thisGame.hud.addLevelText( thisGame.game_width, 3, 'Level ' + (thisGame.currentGameLevel + 1), {"fontSize": '18px', "fill": "#000000"} ); // Create Game objective text thisGame.hud.addGameObjectiveText( thisGame.game_width, 25, Genari.getQuestion(), {"fontSize": '14px', "fill": "#000000"} ); }; /** * When play-state finally starts, it shows the level opening dialog box for the player to prepare. * When the user click/enters in the level opening dialog box, this function is called to close the dialog box. * * To customise the level opening dialog box, override this.showLevelOpeningDialogBox. See DC API for its description. * * @param [playBackgroundMusic] Default true. Set to true to play the game level background music * @override */ PStatePlay.prototype.openingDialogAction = function(playBackgroundMusic){ // Call the parent class method. Genari.GameState.prototype.openingDialogAction.call(this, playBackgroundMusic); this.dcGame.audioPlay(Genari.k.openingSndKey); this.dcGame.phaserGame.time.events.add(Phaser.Timer.SECOND * 2, function(){ this.dcGame.audioPlay(this.dcGame.groupHints.currentCardSound); }, this ); }; /** * When play-state is running, this is called at every frame just before the frame is rendered to the display. * ** The frame rate varies. Mobile devices will have very low frame rate. * Use Javascript timer if need to measure time. * @override */ PStatePlay.prototype.updateState = function(){ var thisGame = this.dcGame; if (!(this.isPlaying)) return; // stop processing if the game level is over // ** Must call this this.stateCheck(); // handle control events thisGame.gameControl.update(); // Add here code for updating game states, monsters, players, etc thisGame.groupHints.showHint(); thisGame.groupHints.updateCharSprite(); // Finally check if the game level is completed by checking if all questions are answered thisGame.checkLevelEnd(); }; /** * Called just before the play state is shutdown * @override */ PStatePlay.prototype.shutdownState = function(){ Genari.phaserGame.cache.removeSound(Genari.k.backgroundMusicKey); }; //============================================================================== // We must place classes that extend classes defined in Genari.Group or their subclasses here. // This is because Genari could be loaded after this code is loaded. var myClassLoader = function(){ // DEVELOPER_CODE REMOVE_FOR_THEME // define classes here that extends classes defined in Genari.loadClasses /********************************************************************************************* * the images indicating where the answer images should be dropped. * * @param {Object} layer Phaser.Group. Rendering layer of this group. * @param {boolean} [fixedToCamera] If set to true, it move with camera. The location is then camera offsets. * @param {boolean} [enableBody] If set to true, any objects added will have physics enables * @param {number} [physicsBodyType] Default 0. Phaser.Physics.ARCADE, Phaser.Physics.P2, Phaser.Physics.NINJA * @constructor * @extends {Genari.Group} ********************************************************************************************/ Genari.DropBoxGroup = function( layer, fixedToCamera, enableBody, physicsBodyType ){ if (!(this instanceof Genari.DropBoxGroup)) return new Genari.DropBoxGroup(layer, fixedToCamera, enableBody, physicsBodyType); // make sure this refer to a DragCountGame Genari.Group.call(this, layer, fixedToCamera, enableBody, physicsBodyType); //selected hint will defined in this object this.selectedDropBox = [null, null, null]; }; Genari.DropBoxGroup.prototype = Object.create(Genari.Group.prototype); Genari.DropBoxGroup.prototype.constructor = Genari.DropBoxGroup; /** * Spawn drop location objects in a column. * this.correctAnswer_#.answer is the answer key code that will be used to check if a correct answer image is dropped on it * this.correctAnswer_#.soundKey will be played when a correct answer is dropped on it. * this.correctAnswer_#.atype = 1 says that this is a hint image obj, not answer image obj */ Genari.DropBoxGroup.prototype.spawnDropLoc = function(){ var dropBox; dropBox = this.create(575, 390, Genari.k.answerDropLocImgKey); dropBox.anchor.x = dropBox.anchor.y = 0.5; dropBox.height *= 0.4; dropBox.width *= 0.4; }; /** * Pick up a hint image object under the given location * @param {number} x * @param {number} y * @return {Object|null} answer image object under the location */ Genari.DropBoxGroup.prototype.getDropBoxImage = function(x, y){ var thisGame = this.dcGame; var hover_box = null; // iterate through each drop box sprite added to this group this.getGroup().forEach(function(dropBoxSprite){ // check if this drop box object is under the pointer location if((x > dropBoxSprite.x - (dropBoxSprite.width/3)) && (x < dropBoxSprite.x + (dropBoxSprite.width/3)) && (y < dropBoxSprite.y + (dropBoxSprite.height/10)) && (y > dropBoxSprite.y - (dropBoxSprite.height/2))){ // found a hint image object under the location hover_box = dropBoxSprite; } }); return hover_box; }; /** * This is called when the pointer is in down state. * We check if we are dragging an answer image and * if the pointer is on an hint image, store the hint image object and play sound effect * @param {Array} pointer [x, y, isDown] * @param {number} pointerIdx index of the pointer. 0 = mouse, 1 = pointer1, 2 = pointer2 */ Genari.DropBoxGroup.prototype.pickupDropBox = function(pointer, pointerIdx){ //var pointer = Genari.getActivePointerLocation(); // find a hint image object under the current active point var curDropBoxSprite = this.getDropBoxImage(pointer[0], pointer[1]); if (!(curDropBoxSprite && this.dcGame.groupAnswers.selectedAnswerSprite[pointerIdx])){ // not found a hint image object under the pointer location or not dragging an asnwer image this.selectedDropBox[pointerIdx] = null; return; } else { // we are dragging an answer image and the pointer is on an hint image object if (!this.selectedDropBox[pointerIdx] || this.selectedDropBox[pointerIdx].answer != curDropBoxSprite.answer // if not the same drop box ){ // we have never visited a hint img object before OR // the current hint image and the previously selected hint image are not the same. // we update selectedDropBox and play the select sound this.selectedDropBox[pointerIdx] = curDropBoxSprite; this.dcGame.audioPlay(Genari.k.selectEffectSndKey); } } }; /********************************************************************************************* * Group for managing answer place objects. * * This game generates hint image objects from correct answer data. * Rows of hint images will be shown on the left. * * @param {Object} layer Phaser.Group. Rendering layer of this group. * @param {boolean} [fixedToCamera] If set to true, it move with camera. The location is then camera offsets. * @param {boolean} [enableBody] If set to true, any objects added will have physics enables * @param {number} [physicsBodyType] Default 0. Phaser.Physics.ARCADE, Phaser.Physics.P2, Phaser.Physics.NINJA * @constructor * @extends {Genari.Group} ********************************************************************************************/ Genari.HintGroup = function(layer, fixedToCamera, enableBody, physicsBodyType){ if (!(this instanceof Genari.HintGroup)) return new Genari.HintGroup(layer, fixedToCamera, enableBody, physicsBodyType); Genari.Group.call(this, layer, fixedToCamera, enableBody, physicsBodyType); this.ornamentPosX = 650; this.ornamentPosY = 150; this.currentIdx = 0; this.lastIdx = null; this.isIdleAnim = true; }; Genari.HintGroup.prototype = Object.create(Genari.Group.prototype); Genari.HintGroup.prototype.constructor = Genari.HintGroup; /** * Spawn hint image objects in a column. * this.correctAnswer_#.answer is the answer key code that will be used to check if a correct answer image is dropped on it * this.correctAnswer_#.soundKey will be played when a correct answer is dropped on it. */ Genari.HintGroup.prototype.spawnHintImages = function(){ var x, y, j, k, numText, thisGroup, hintSprite; thisGroup = this.dcGame.groupAnswers; for( var i = 0; i < thisGroup.numberOfAnswers; i++ ){ //hintSprite = this.create(this.ornamentPosX, this.ornamentPosY, thisGroup.answerList[0][i].imageKey); hintSprite = this.addImageAsSpriteAnimation( this.ornamentPosX, this.ornamentPosY, // location of the sprite thisGroup.answerList[0][i].imageKey, 0, // starting frame number thisGroup.answerList[0][i].frameNo, // number of frames "move", // animation name (optional) 10, // frame rate per second true // loop enabled? ); //add properties hintSprite.answer = thisGroup.answerList[0][i].answer; hintSprite.soundKey = thisGroup.answerList[0][i].soundKey; hintSprite.posOrder = i; hintSprite.answerArrayText = []; hintSprite.answerArrayNumOrigin = []; hintSprite.answerArrayNum = thisGroup.answerList[0][i].answer.split(','); numText = 0; for(j = 0; j < hintSprite.answerArrayNum.length; j++){ if(isNaN(hintSprite.answerArrayNum[j])){ hintSprite.answerArrayText[numText++] = hintSprite.answerArrayNum[j]; hintSprite.answerArrayNum.splice(j, 1); } } for(k = 0; k < hintSprite.answerArrayNum.length; k++){ hintSprite.answerArrayNumOrigin[k] = hintSprite.answerArrayNum[k]; } //modify object hintSprite.anchor.x = hintSprite.anchor.y = 0.5; // set center of image as image's anchor hintSprite.width = hintSprite.height = 200; // resize to standard height and width // apply theme scale attribute hintSprite.width *= this.dcGame.multiplierAnswer/100; hintSprite.height *= this.dcGame.multiplierAnswer/100; //make all images invisible hintSprite.alpha = 0; } }; Genari.HintGroup.prototype.showHint = function(){ this.getGroup().forEach( function(hint){ if(this.lastIdx == this.currentIdx){ return; } if(hint.posOrder === this.currentIdx){ // found first order image hint.alpha = 1; // make the image visible this.currentCardSound = hint.soundKey; // store the soundkey this.lastIdx = this.currentIdx; this.dcGame.gameControl.isCheck = false; } else { hint.alpha = 0; } }, this ); }; /** * Create character psrite */ Genari.HintGroup.prototype.createCharSprite = function(){ this.charSprite = this.addSprite(400, 200, Genari.k.charSpriteKey); this.addAnimation(this.charSprite, 'idle', [0,1,2,3], 2, true); this.addAnimation(this.charSprite, 'wrong', [4,5], 5, true); this.playAnimation(this.charSprite, 'idle'); this.charSprite.anchor.x = this.charSprite.anchor.y = 0.5; this.charSprite.height *= 0.75; this.charSprite.width *= 0.75; this.dcGame.layerBottom.addChild(this.charSprite); console.log(this.charSprite); }; /** * Update character psrite */ Genari.HintGroup.prototype.updateCharSprite = function(){ if(!this.charSprite) return; if(this.isIdleAnim === true){ if(this.charSprite.animations.currentAnim.name != "idle") this.playAnimation(this.charSprite, 'idle'); return; } if(this.charSprite.animations.currentAnim.name != "wrong") this.playAnimation(this.charSprite, 'wrong'); }; /** * Create victor psrite */ Genari.HintGroup.prototype.createVictorySprite = function(){ this.victorySprite = this.addSprite(this.dcGame.game_width/2, this.dcGame.game_height/2, Genari.k.victorySpriteKey); this.addAnimation(this.victorySprite, 'victory', [0,1,1,0,1,1,0,1,0,1], 5, true); this.victorySprite.anchor.x = this.victorySprite.anchor.y = 0.5; this.victorySprite.alpha = 0; this.dcGame.layerEffects.addChild(this.victorySprite); }; /** * Create ornament psrite */ Genari.HintGroup.prototype.createOrnament = function(){ var ornament; ornament = this.create(this.ornamentPosX, this.ornamentPosY, Genari.k.ornamentImgKey); ornament.anchor.x = ornament.anchor.y = 0.5; ornament.height *= 0.15; ornament.width *= 0.15; this.dcGame.layerBottom.addChild(ornament); }; /********************************************************************************************* * Group for managing Answer objects. * * This game generates answer image objects from correct answer data. * rows of answer images will be shown at the bottom * * @param {object} layer Phaser.Group. Rendering layer of this group. * @param {boolean} [fixedToCamera] If set to true, it move with camera. The location is then camera offsets. * @param {boolean} [enableBody] If set to true, any objects added will have physics enables * @param {number} [physicsBodyType] Default 0. Phaser.Physics.ARCADE, Phaser.Physics.P2, Phaser.Physics.NINJA * @constructor * @extends {Genari.Group} ********************************************************************************************/ Genari.AnswersGroup = function(layer, fixedToCamera, enableBody, physicsBodyType){ if (!( this instanceof Genari.AnswersGroup )) return new Genari.AnswersGroup(layer, fixedToCamera, enableBody, physicsBodyType); Genari.Group.call(this, layer, fixedToCamera, enableBody, physicsBodyType); //selected answer will defined in this object this.selectedAnswerSprite = [null, null, null]; this.draggingAnswerImage = [false, false, false]; // This is set to true when we picked up an answer image. //array of all answers including their properties this.answerList = Genari.getAnswers(); this.numberOfAnswers = this.answerList[0].length; //set inCorrect Answer Parameters this.iaParams = [ /* 0 xPos */ this.dcGame.answerXpos+45, /* 1 yPos */ this.dcGame.iaYpos, /* 2 xGap */ this.dcGame.answerInterval-30, /* 3 yGap */ 0, /* 4 numObj */ 4 ]; //this.errorNum = 0; }; Genari.AnswersGroup.prototype = Object.create(Genari.Group.prototype); Genari.AnswersGroup.prototype.constructor = Genari.AnswersGroup; /** * Spawn answer image objects in a column. * Player will pickup and drag answer images to hint images. * this.correctAnswer_#.answer is the answer key code that will be used to check if a correct answer image is dropped on it * this.correctAnswer_#.soundKey will be played when a correct answer is dropped on it. * this.correctAnswer_#.atype = 1 says that this is a hint image obj, not answer image obj */ Genari.AnswersGroup.prototype.spawnAnswerImageObjs = function(){ var x, y, answerImageSprite, num; num = 0; for(var i = 0; i < 2; i++){ for(var j = 0; j < 3; j++){ // calculate location for this answer image x = 120 + (i * 100); // lay out in fixed intervals y = 150 + (j * 70); // in a row //answerImageSprite = this.create(x, y, this.answerList[1][num].imageKey); answerImageSprite = this.addImageAsSpriteAnimation( x, y, // location of the sprite this.answerList[1][num].imageKey, 0, // starting frame number this.answerList[1][num].frameNo, // number of frames "move", // animation name (optional) 10, // frame rate per second true // loop enabled? ); //add properties answerImageSprite.answer = this.answerList[1][num].answer; answerImageSprite.soundKey = this.answerList[1][num].soundKey; answerImageSprite.isOrigin = true; answerImageSprite.rightAnswer = false; answerImageSprite.index = null; //modify object answerImageSprite.anchor.x = answerImageSprite.anchor.y = 0.5; // set center of image as image's anchor answerImageSprite.width = answerImageSprite.height = 200; // resize to standard height and width // apply theme scale attribute answerImageSprite.width *= 30/100; answerImageSprite.height *= 30/100; num++; } } }; /** * Create holding area */ Genari.AnswersGroup.prototype.createHoldingArea = function(){ var sprite; sprite = this.addSprite(180, 220, Genari.k.answerHoldingAreaSpriteKey); this.addAnimation( sprite, 'glassAnim', [0,1,2,3], 2, true); sprite.anchor.x = sprite.anchor.y = 0.5; sprite.height *= 0.95; sprite.width *= 0.95; this.dcGame.layerBottom.addChild(sprite); }; /** * Pick an answer image object under the given location. * Return an answer image object if found. * Return null if there are no answer image object under the location. * @param {number} x coordinate of the location to check. * @param {number} y coordinate of the location. * @return {Object|null} Phaser display object body of the clicked answer. */ Genari.AnswersGroup.prototype.getAnswerImage = function(x, y){ var pickedAnswer = null; // Check each answer object this.getGroup().forEach(function(answerSPrite){ // we gets Phaser.Sprite {DisplayObject} if (answerSPrite.getBounds().contains(x,y)){ // Phaser.Rectangle.contains(answerSPrite, x, y) // It is answer image object pickedAnswer = answerSPrite; this.dcGame.audioPlay(Genari.k.selectEffectSndKey); } }.bind(this)); if(pickedAnswer){ if(pickedAnswer.isOrigin){ var clone = this.create(pickedAnswer.x, pickedAnswer.y, pickedAnswer.key); clone.anchor.x = clone.anchor.y = 0.5; clone.width = pickedAnswer.width; clone.height = pickedAnswer.height; clone.answer = pickedAnswer.answer; clone.soundKey = pickedAnswer.soundKey; clone.isOrigin = false; clone.isDrop = false; pickedAnswer = clone; } } return pickedAnswer; }; /** * Move the selected answer image object to the current active pointer location * This drags an anwer image picked up by the player. * @param {Array} pointer [x, y, isDown] * @param {number} pointerIdx index of the pointer. 0 = mouse, 1 = pointer1, 2 = pointer2 */ Genari.AnswersGroup.prototype.dragAnswerImage = function(pointer, pointerIdx){ if (this.draggingAnswerImage[pointerIdx] === false || !this.selectedAnswerSprite[pointerIdx]){ return; } // get pointer current location //var pointer = Genari.getActivePointerLocation(); // move the answer image to the pointer's position this.selectedAnswerSprite[pointerIdx].x = pointer[0]; this.selectedAnswerSprite[pointerIdx].y = pointer[1]; }; /** * Pointer is just down. This is called only once when the pointer is changed from up to down * Pickup an answer image under the current pointer down location. * @param {Array} pointer [x, y, isDown] * @param {number} pointerIdx index of the pointer. 0 = mouse, 1 = pointer1, 2 = pointer2 */ Genari.AnswersGroup.prototype.pickupAnswerImage = function(pointer, pointerIdx){ var answerSprite; //pointer = Genari.getActivePointerLocation(); // get the most recent active pointer location answerSprite = this.getAnswerImage(pointer[0], pointer[1]); if (!answerSprite){ // not found an answer image object under the clicked location return; } else { // we have an answer image under the clicked location this.selectedAnswerSprite[pointerIdx] = answerSprite; // store the Phaser.Sprite object as the selected answer object. this.selectedAnswerSprite[pointerIdx].bringToTop(); // show the image on top this.dcGame.audioPlay(this.selectedAnswerSprite[pointerIdx].soundKey); // store the original pickup location so we can put this object back if dropped in a wrong location this.selectedAnswerSprite[pointerIdx].originX = this.selectedAnswerSprite[pointerIdx].x; this.selectedAnswerSprite[pointerIdx].originY = this.selectedAnswerSprite[pointerIdx].y; // now we drag the answer image this.draggingAnswerImage[pointerIdx] = true; } }; /** * Player drop the dragged image * @param {number} pointerIdx index of the pointer. 0 = mouse, 1 = pointer1, 2 = pointer2 */ Genari.AnswersGroup.prototype.dropAnswerImage = function(pointerIdx){ this.updateProperties(pointerIdx); this.dcGame.groupDropBoxes.selectedDropBox[pointerIdx] = null; this.selectedAnswerSprite[pointerIdx] = null; this.draggingAnswerImage[pointerIdx] = false; }; /** * Update properties * @param {number} pointerIdx index of the pointer. 0 = mouse, 1 = pointer1, 2 = pointer2 */ Genari.AnswersGroup.prototype.updateProperties = function(pointerIdx){ var thisGame, dropBoxGroup, idxAry; thisGame = this.dcGame; dropBoxGroup = this.dcGame.groupDropBoxes; hintGroup = this.dcGame.groupHints; // if not selected an answer, ignore if (!this.selectedAnswerSprite[pointerIdx]){ return; } if(!dropBoxGroup.selectedDropBox[pointerIdx]){ // dropbox not selected if(this.selectedAnswerSprite[pointerIdx].isDrop === false){ // answer never dropped before Genari.Effect.explosion(this.selectedAnswerSprite[pointerIdx], true, this.dcGame.wrongEffectAnim, 'wrong'); this.dcGame.audioPlay(Genari.k.wrongEffectSndKey); return; } // answer already at dropbox location if(this.selectedAnswerSprite[pointerIdx].rightAnswer){ // answer is valuable hintGroup.getGroup().forEach( function(hint){ // ignore not selected answer if(hint.posOrder != hintGroup.currentIdx) return; // player remove valuable answer so increase the requirement quantity of the answer hint.answerArrayNum[this.selectedAnswerSprite[pointerIdx].index]++; }, this ); } else { // answer is not valuable // player remove not valuable answer so decrease the error number //this.errorNum--; } // destroy the answer sprite Genari.Effect.explosion(this.selectedAnswerSprite[pointerIdx], true, this.dcGame.wrongEffectAnim, 'wrong'); this.dcGame.audioPlay(Genari.k.wrongEffectSndKey); } // dropbox selected if(this.selectedAnswerSprite[pointerIdx].isDrop === false){ // answer never dropped before hintGroup.getGroup().forEach( function(hint){ // ignore not selected answer if(hint.posOrder != hintGroup.currentIdx) return; if(hint.answerArrayText.includes(this.selectedAnswerSprite[pointerIdx].answer)){ // dropped valuable answer idxAry = hint.answerArrayText.indexOf(this.selectedAnswerSprite[pointerIdx].answer); hint.answerArrayNum[idxAry]--; this.selectedAnswerSprite[pointerIdx].index = idxAry; this.selectedAnswerSprite[pointerIdx].rightAnswer = true; Genari.Effect.explosion(this.selectedAnswerSprite[pointerIdx], false, this.dcGame.correctEffectAnim, 'correct'); this.dcGame.audioPlay(Genari.k.correctEffectSndKey); this.selectedAnswerSprite[pointerIdx].isDrop = true; } else { // dropped not valuable answer Genari.Effect.explosion(this.selectedAnswerSprite[pointerIdx], true, this.dcGame.wrongEffectAnim, 'wrong'); this.dcGame.audioPlay(Genari.k.wrongEffectSndKey); //this.errorNum++; } }, this ); } }; /** * Destroy answer */ Genari.AnswersGroup.prototype.destroyAnswer = function(){ var answerSprite = []; var num = 0; this.getGroup().forEach( function(answer){ if(answer.isOrigin){ return; } answerSprite[num++] = answer; } ); for(var i = 0; i < answerSprite.length; i++){ this.showCorrectEffectAfterDelay(answerSprite[i], (i+1)*500); } //this.errorNum = 0; }; /** * Show correct efter after some delay on a sprite * @param {Object} sprite * @param {number} delay in ms */ Genari.AnswersGroup.prototype.showCorrectEffectAfterDelay = function(sprite, delay){ setTimeout(function(){ Genari.Effect.explosion(sprite, true, this.dcGame.correctEffectAnim, 'correct'); this.dcGame.audioPlay(Genari.k.correctEffectSndKey); }.bind(this), delay); }; }; // DEVELOPER_CODE REMOVE_FOR_THEME