/** * 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: Hidden faces * Application type: Arcade Puzzle game. * Base game: Picture card game * Author: Docentron, Alex * * V1.4 Added answerTextTop attribute to allow top position of answer text * V1.3 Added support for sprites for answer images * V1.2 Prevent player click button before event finish * **************************************************************/ /************************************************************** * 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){ //----------------------------------------------- // In order to create a new game based on this based game, follow these steps: // Step 1: update/replace PuzzleGame class with your own Game Class // Step 2: update/replace PStatePlay class with your own StatePlay class Genari.prepareGame(HiddenFaceGame, 800, 426, GameControl, PStatePlay); // DO NOT change this // All game-assets are accessible using Genari.dcGame.k.assetKeyName. These are defined in the asset list (please see Asset List tab). // Step 3: override and customise Genari.StatePlay methods // Genari.Control: GameControl // Genari.Game: HiddenFaceGame // Genari.StatePlay: preloadState() pre-load game theme assets // Genari.StatePlay: loadThemeAssets() // Genari.StatePlay: createLayersGroups() Create rendering layers to organize rendering order and game object groups to manage them. // 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 HiddenFaceGame = function(game_themes){ if (!(this instanceof HiddenFaceGame)) return new HiddenFaceGame();// this ensures Javascript classes functions as intended 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; }; HiddenFaceGame.prototype = Object.create(Genari.Game.prototype); // extend DBGame HiddenFaceGame.prototype.constructor = HiddenFaceGame; // set constructor property /** * Check the end of the game level and load the next level if the current level is completed */ HiddenFaceGame.prototype.checkLevelEnd = function(){ // check if all images are showed if (this.groupPictures.currentPicNum < this.groupPictures.pictureList[0].length) return; this.saveAndStartNextLevel(); }; /************************************************************** * 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 {HiddenFaceGame} HiddenFaceGame The only game object * @param {Genari.RunPlayer} player The player character that user controls * @param {string} bulletSpriteKey sprite key. This must be already loaded during player-state.create() * @param {string} bulletSoundKey sprite key. This must be already loaded during player-state.create() * * @constructor * @extends {Genari.Control} **************************************************************/ var GameControl = function(HiddenFaceGame, player, bulletSpriteKey, bulletSoundKey){ if (!(this instanceof GameControl)){return new GameControl(HiddenFaceGame, player, bulletSpriteKey, bulletSoundKey);} Genari.Control.call(this, HiddenFaceGame, player); // must call the parent class constructor // Never redefine inherited properties and methods. // 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.HiddenFaceGame = HiddenFaceGame; this.clickEN = 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(){ // Destroy any existing buttons this.destroyControlButtons(); // Create Play Again Button var playAgainControlBtn = this.addControlButton(177, 293, Genari.k.playBtnKey, function(){ if(this.clickEN === true){ this.clickEN = false; this.dcGame.audioPlay(Genari.k.selectSndKey); this.dcGame.groupPictures.showPicture(0); } }.bind(this), function(){}, this); // Create Next Button var nextControlBtn = this.addControlButton(500, 293, Genari.k.nextBtnKey, function(){ if(this.clickEN === true){ this.clickEN = false; this.dcGame.audioPlay(Genari.k.selectSndKey); this.dcGame.groupPictures.showPicture(1); this.dcGame.updateScore(1); } }.bind(this), function(){}, this); // Now add click event handlers for the buttons. // Due to bug in Phaser, we must call this whenever we resize the window this.createButtonHitArea(); this.hideOnScreenButtons(); }; /** * 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() */ GameControl.prototype.update = function(){ if (!(this.dcGame.isPaused)){ // game is not paused } }; /************************************************************** * Phaser state class: Play state. * This is started when the player click/enter on the application banner to start the game. * * Customise this class for your own game * @param {HiddenFaceGame} 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 DBGame PStatePlay.prototype.constructor = PStatePlay; // set constructor property /** * Called before play-state starts to for us to prepare. * * This downloads theme assets from server. The loading screen will show the progress of the downloading. * * @override overriding the base class method to cusomise for our purpose */ PStatePlay.prototype.loadThemeAssets = function(){ // Load assets before use Genari.loadThemeImageSpriteAssets(); // load all images and sprites including tileset images here Genari.loadThemeAudioAssets(); }; /** * 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.groupPictures) this.dcGame.groupPictures.destroy(); // create one object for both hint image objects and answer image objects... this.dcGame.groupPictures = new Genari.PictureGroup(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; // before placing objects in the stage, we need to set to scaled mode so we can use unscaled coordiate values when placing objects in the stage. thisGame.setToScaledMode(); this.checkQuestionText(); // Prepare the background and set world bounds this.createWorld(); // Create the HUD this.createHud(); // Create explosion sprite, animation, and add it to layerEffects this.dcGame.starAnimation = Genari.Effect.addExplosion(Genari.k.starSpriteKey, 'stars', 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.groupPictures.spawnPictureObjs(); thisGame.groupPictures.spawnObstacle(); this.createFrame(); // now we go back to based scale mode for proper pointer coordiates thisGame.setToBaseScaleMode(); }; /** * Check question text if contains "collect" word */ PStatePlay.prototype.checkQuestionText = function(){ // get the question's text this.checkQuestion = Genari.getQuestion().includes("Collect") || Genari.getQuestion().includes("collect"); // check if question Text contains "Collect" or "collect" if(this.checkQuestion){ // change the question Genari.getAllQuestions()[0].name = "What is this ?"; Genari.getAllQuestions()[0].question = "What is this ?"; Genari.getAllQuestions()[0].question2 = "What is this ?"; Genari.getAllQuestions()[0].snd1 = 0; } }; /** * Create the answer image's frame */ PStatePlay.prototype.createFrame = function(){ var image, x, y; // calculate location for this frame image x = this.dcGame.game_width/2; y = this.dcGame.game_height; // spawn the Frame image image = Genari.phaserAddImage(x, y, Genari.k.frameImgKey); image.anchor.x = 0.5; image.anchor.y = 1; image.width = 400; image.width *= 0.9; image.height = 400; image.height *= 0.9; this.dcGame.layerEffects.addChild(image); }; /** * 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.groupPictures.showFirstPic(); }; /** * 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 // handle control events thisGame.gameControl.update(); // ** Must call this this.stateCheck(); thisGame.checkLevelEnd(); }; /** * Called just before the play state is shutdown * @override */ PStatePlay.prototype.shutdownState = function(){ Genari.phaserGame.cache.removeSound(Genari.k.backgroundMusicKey); }.bind(this); //============================================================================== // 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 /********************************************************************************************* * Group for managing Answer objects. * * This game generates hint image objects from correct answer data and answer image objects from incorrect answer data. * Rows of hint images will be shown on the left and rows of answer images will be shown on the right. * * @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.PictureGroup = function( layer, fixedToCamera, enableBody, physicsBodyType ){ if (!(this instanceof Genari.PictureGroup)) return new Genari.PictureGroup(layer, fixedToCamera, enableBody, physicsBodyType); Genari.Group.call(this, layer, fixedToCamera, enableBody, physicsBodyType); // answer data from the server. See Genari.getAnswers for the data structure this.pictureList = Genari.getAnswers(); // count how many image already showed this.currentPicNum = 0; // store the current image object this.currentPicture = null; this.currentText = null; this.obstacleSprite = null; }; Genari.PictureGroup.prototype = Object.create( Genari.Group.prototype ); Genari.PictureGroup.prototype.constructor = Genari.PictureGroup; /** * Spawn hint image objects in a column. Max 5 rows. * this.correctAnswer_#.sndKey will be played when a correct answer is dropped on it. */ Genari.PictureGroup.prototype.spawnPictureObjs = function(){ var pictureSprite, x, y; for (var i = 0; i < this.pictureList[0].length; i++){ // calculate location for this answer image x = (this.dcGame.game_width/2); y = (this.dcGame.game_height/2.2); // add a sprite //pictureSprite = this.create(x, y, this.pictureList[0][i].imageKey); pictureSprite = this.addImageAsSpriteAnimation( x, y, // location of the sprite this.pictureList[0][i].imageKey, 0, // starting frame number this.pictureList[0][i].frameNo, // number of frames "move", // animation name (optional) Math.floor(this.pictureList[0][i].frameNo/2), // frame rate per second true // loop enabled? ); //add sound and position properties pictureSprite.soundKey = this.pictureList[0][i].soundKey; // sound key sk_## pictureSprite.originY = y; pictureSprite.posOrder = i; pictureSprite.answer = this.pictureList[0][i].answer; // move the anchor to the center of the image pictureSprite.anchor.x = pictureSprite.anchor.y = 0.5; // resize width and height to 200px pictureSprite.width = pictureSprite.height = 200; //make all images invisible pictureSprite.alpha = 0; } }; Genari.PictureGroup.prototype.spawnObstacle = function(){ this.obstacleSprite = this.addSprite(0, 0, Genari.k.obstacleSpriteKey); this.obstacleSprite.x = this.dcGame.game_width/2; this.obstacleSprite.y = this.dcGame.game_height/1.12; this.obstacleSprite.animations.add('idle', [0]); this.obstacleSprite.animations.add('eaten', [1,2,3]); this.obstacleSprite.animations.play('idle'); this.obstacleSprite.anchor.setTo(0.5, 1); this.obstacleSprite.width *= 1.1; this.obstacleSprite.height *= 1.1; }; /** * Show the first order image */ Genari.PictureGroup.prototype.showFirstPic = function(){ this.getGroup().forEach( function(animal){ if (animal.posOrder === 0){ // found first order image animal.alpha = 1; // make the image visible this.currentCardSound = animal.soundKey; // store the soundkey this.currentPicture = animal; // store the image obj } }, this ); // add event in 3 SECOND play the current image soundkey this.soundEvent = this.dcGame.phaserGame.time.events.add(Phaser.Timer.SECOND * 3, function(){ this.obstacleSprite.animations.play('eaten', 2, false); this.dcGame.audioPlay(Genari.k.obstacleSndKey, undefined, undefined, undefined, undefined, undefined, undefined, // callback function function(){ this.currentText = this.addAnswerText(); this.currentText.anchor.x = this.currentText.anchor.y = 0.5; this.dcGame.audioPlay(Genari.k.effectSndKey, undefined, undefined, undefined, undefined, undefined, undefined, // callback function function(){ this.dcGame.audioPlay(this.currentCardSound, undefined, undefined, undefined, undefined, undefined, undefined, // callback function function(){ this.dcGame.gameControl.clickEN = true; }.bind(this) ); }.bind(this) ); Genari.Effect.explosion(this.currentPicture, false, this.dcGame.starAnimation); }.bind(this) ); }, this ); this.currentPicture.body.y = this.currentPicture.originY; // add effect to the current image Genari.phaserGame.add.tween(this.currentPicture.body).to( { y: 150 }, 1200, Phaser.Easing.Cubic.InOut, true, 0, Number.MAX_VALUE, true); }; /** * Show the next image */ Genari.PictureGroup.prototype.addAnswerText = function(){ return this.addTextBox( this.currentPicture.x, Genari.getThemeAttribute(Genari.k.answerTextTopKey), // 135, //this.currentPicture.y-(this.currentPicture.y/2)+50, this.currentPicture.answer, {"font": '36px Arial', "fill": "#000000", "stroke":'#ffffff', "strokeThickness":6} ); }; /** * Show the next image */ Genari.PictureGroup.prototype.showPicture = function(type){ if(type == 1) this.currentPicNum++; if (this.currentText){ this.currentText.destroy(); } if(this.soundEvent){ this.dcGame.phaserGame.time.events.remove(this.soundEvent); } this.obstacleSprite.animations.play('idle'); if (this.currentPicNum > this.pictureList[0].length){ this.currentPicture.alpha = 0; // set current image invisible } else { this.getGroup().forEach( function(animal){ if (animal.posOrder == this.currentPicNum){ // found next image animal.alpha = 1; // make the image visible this.currentCardSound = animal.soundKey; // store the soundkey this.currentPicture = animal; // store the image obj this.dcGame.audioPlay(Genari.k.defaultQuestionSndKey); // play question sound } else animal.alpha = 0; // set else image invisible this.obstacleSprite.alpha = 1; }, this ); // add event in 3 SECOND play the current image soundkey this.soundEvent = this.dcGame.phaserGame.time.events.add(Phaser.Timer.SECOND * 3, function(){ this.obstacleSprite.animations.play('eaten', 2, false); this.dcGame.audioPlay(Genari.k.obstacleSndKey, undefined, undefined, undefined, undefined, undefined, undefined, // callback function function(){ this.currentText = this.addAnswerText(); this.currentText.anchor.x = this.currentText.anchor.y = 0.5; this.dcGame.audioPlay(Genari.k.effectSndKey, undefined, undefined, undefined, undefined, undefined, undefined, // callback function function(){ this.dcGame.audioPlay(this.currentCardSound, undefined, undefined, undefined, undefined, undefined, undefined, // callback function function(){ this.dcGame.gameControl.clickEN = true; }.bind(this) ); }.bind(this) ); Genari.Effect.explosion(this.currentPicture, false, this.dcGame.starAnimation); }.bind(this) ); }, this ); this.currentPicture.body.y = this.currentPicture.originY; // add effect to the current image Genari.phaserGame.add.tween(this.currentPicture.body).to( { y: 150 }, 1200, Phaser.Easing.Cubic.InOut, true, 0, Number.MAX_VALUE, true); } }; }; // DEVELOPER_CODE REMOVE_FOR_THEME