BdR Posted November 11, 2014 Share Posted November 11, 2014 EDIT: I've put my code for a level select screen here, feel free to useEDIT2: example now also uses separate states for levelselect and game, and stores progress to local storage.https://github.com/BdR76/phaserlevelselect I'm working on a Phaser game and at the moment I'm looking to add a level select screen. It's going to be a 5x5 grid of icons, the icons will represent if a level is unlocked or not yet completed, and it should display stars to how well the player has finished a previous level. This is kind of a defacto standard in mobile games nowadays, see screenshot of Cut The Rope below what I mean: I'm using game.add.button and adding them as a grid of 5x5 buttons putting them into an array. But my question is, how can I add text 1,2,3..25 to each button? And how can I procedurally add the 0..3 stars icons to a button? Or is it better to just use sprites instead of buttons? Any tips would be greatly appreciated. Link to comment Share on other sites More sharing options...
rvizcaino Posted November 11, 2014 Share Posted November 11, 2014 I am not an expert, but you could try with phaser groups, one group for each button and inside the group, the button image, the stars and the number. Then you can generate this with a FOR block. Link to comment Share on other sites More sharing options...
lewster32 Posted November 12, 2014 Share Posted November 12, 2014 The groups suggestion is the best option here I think. Create a function which generates and returns a single button, with its background image, a number (this can be a Text or BitmapText object) and three star images (which can have different frames to represent lit or unlit). Then just use your for loop to call this function however many times you need, placing the returned group at the appropriate position for each iteration. Link to comment Share on other sites More sharing options...
BdR Posted November 14, 2014 Author Share Posted November 14, 2014 Thanks for the tips I've got a working example now, seehttps://github.com/BdR76/phaserlevelselect I've got a follow up question though... The locked ions shake when you touch them to indicate it is locked. This is done using chained tween animations. However, if you repeatedly tap a locked icon it can drift out of position to the left or right. I know this is because it starts with the current x position and then changes it. But is there a way to check if an object still has a current tween animation? Or is there maybe another way to do a shake animation? See code below.// shake it babyvar tween = game.add.tween(IconGroup).to({ x: xpos+4 }, 20, Phaser.Easing.Linear.None) .to({ x: xpos-4 }, 30, Phaser.Easing.Linear.None) .to({ x: xpos+4 }, 40, Phaser.Easing.Linear.None) .to({ x: xpos }, 50, Phaser.Easing.Linear.None) .start(); Link to comment Share on other sites More sharing options...
spencerTL Posted November 15, 2014 Share Posted November 15, 2014 I' ve dealt with this sort of thing by putting an isTweening property on the object, setting it to true and then using the onComplete callback of the tween to set it to false and checking it is false before setting a new tween off I'd like to see if there's a better way though. I bet there is!And thanks for putting up your example on GitHub. That's been really useful for me to see. Link to comment Share on other sites More sharing options...
valueerror Posted November 15, 2014 Share Posted November 15, 2014 you could just set the x and y coordinates everytime before you start the tween.. easy fix that way the tween would definitely start (it would look weird if the tween would be blocked somehow imho.. but it would always start on the same position Link to comment Share on other sites More sharing options...
BdR Posted November 15, 2014 Author Share Posted November 15, 2014 you could just set the x and y coordinates everytime before you start the tween.. easy fix Okay, I've just added this approach, also updated the github example. At the beginning I keep the original x/y position values in the icon object, in separate new variables. And then at the start of a shake tween I just take those original x/y values.// create new groupvar IconGroup = game.add.group();IconGroup.x = xpos;IconGroup.y = ypos;// also keep original position, for restoring after certain tweensIconGroup.xOrg = xpos;IconGroup.yOrg = ypos;Btw coming from different programming language (mainly Delphi/pascal) I'm still getting used to the fact that in JS you can just add any random variable to an instance, even after it's created. valueerror 1 Link to comment Share on other sites More sharing options...
fedora Posted December 16, 2015 Share Posted December 16, 2015 Thank you for posting the sample code -- super useful for my understanding as I create a level selector screen in my game. I do have a couple of probably dumb questions -- How do you tie the Level to the appropriate action -- ie when user clicks Level 1 how does it know where to go (and would you recommend separate .js files for each level)I assume this would be placed in function update() but a sample would helpI assume it would be straight forward to remove the stars and just use the code to pass the level info IS there a "best way" to pass the level data to the menu screenThanks in advance for any help/guidance Link to comment Share on other sites More sharing options...
BdR Posted December 16, 2015 Author Share Posted December 16, 2015 Glad to hear you've found my example useful. First of all I've found it easiest to use the Phaser.States. I typically create a state for the main menu, the level select, and the maingame. Here is a good example of phaser states. And I wouldn't recommend separate .js files or states for each level, see my answer in this other thread. And the best way to pass the selected level index to the MainGame state is I think to just set a levelindex variable in the MainGame state and handle the initialising of the level in MainGame state, based on that levelindex. For an example of passing variables from one state to the other see this thread. Using this LevelSelect example code could go like so: // LevelSelect screen as a Phaser.State mygame.LevelSelect.prototype = { create: function(){ // .. initialise code goes here }, // ..more code onLevelIconDown: function(sprite, pointer) { // retrieve the iconlevel, stored in sprite.health here var levelnr = sprite.health; console.log('onLevelIconDown - levelnr=' + levelnr); // simulate button press animation to indicate selection var IconGroup = this._icons[levelnr - this._offset - 1]; var tween = this.game.add.tween(IconGroup.scale) .to({ x: 0.9, y: 0.9}, 100, Phaser.Easing.Linear.None) .to({ x: 1.0, y: 1.0}, 100, Phaser.Easing.Linear.None) .start(); // start the level after tween is complete tween.onComplete.add(function(){this.onLevelSelected(sprite.health);}, this); }, onLevelSelected: function(levelnr) { console.log('onLevelSelected ---> levelnr=' + levelnr); // pass levelnr variable to 'Game' state this.game.state.states['MainGame']._levelIndex = levelnr-1; this.game.state.start('MainGame'); // etc. } }; Link to comment Share on other sites More sharing options...
fedora Posted December 17, 2015 Share Posted December 17, 2015 Thanks for the information. Let me say, when I take your code and copy it directly -- works like a charm (not surprising) So my next trick was just to try and drop the code into my game and I have been unsuccesful. I am using the following State to show a dashboard and high score of the game. It works fine:var states = { main: "main", game: "game",};var graphicAssets = { menu:{URL:'assets/images/menuscreen-version3.png', name:'menu'},};// MAIN STATEvar mainState = function(game){ this.tf_start;};mainState.prototype = {init: function(score) { var score = score || 0; this.highestScore = this.highestScore || 0; this.highestScore = Math.max(score, this.highestScore); }, preload: function () { game.load.image(graphicAssets.menu.name, graphicAssets.menu.URL); }, create: function () { this.add.sprite(0, 0, 'menu'); //highest score text = "HIGHEST SCORE: "+this.highestScore; style = { font: "15px Arial", fill: "#fff", align: "center" }; var h = this.game.add.text(this.game.width/2, this.game.height/2 + 50, text, style); h.anchor.x = 0.5; h.anchor.y = -6.5; game.input.onDown.addOnce(this.startGame, this); }, startGame: function () { game.state.start(states.game); },};var game = new Phaser.Game(gameProperties.screenWidth, gameProperties.screenHeight, Phaser.AUTO, 'gameDiv'); game.state.add(states.main, mainState); game.state.add(states.game, gameState); game.state.start(states.main);So I tried to apply the same general structure:var states = { main: "main", menu: "menu", game: "game",};var graphicAssets = { menu:{URL:'assets/images/menuscreen-version3.png', name:'menu'}, levelselecticons:{URL:'assets/images/levelselecticons.png', name:'levelselecticons', width:96, height:96}, };var fontAssets = { font72:{URL:['assets/fonts/font72.png', 'assets/fonts/font72.xml'], name:'font72'},};var PLAYER_DATA = [0,3,2,3,1,2];var holdicons = [];// MENU STATEvar menuState = function(game){ };menuState.prototype = { preload: function () { game.load.spritesheet(graphicAssets.levelselecticons.name, graphicAssets.levelselecticons.URL, graphicAssets.levelselecticons.width, graphicAssets.levelselecticons.height, graphicAssets.levelselecticons.frames); //game.load.bitmapFont(fontAssets.font72.name, fontAssets.font72.URL); }, create: function () { game.stage.backgroundColor = 0x80a0ff;game.add.text(256, 24, 'Select a level!', {font: '48px Arial', fill: '#FFFFFF', align: 'center'}); //game.add.bitmapText(256, 24, 'font72', 'Select a level!', 48);this.createLevelIcons();this.animateLevelIcons(); }, update: function () {// nothing to do but wait until player selects a level }, render: function () {// display some debug info..? }, // ------------------------------------- // Add level icon buttons // ------------------------------------- createLevelIcons: function () {var levelnr = 0;for (var y=0; y < 3; y++) {for (var x=0; x < 4; x++) {// player progress info for this levellevelnr = levelnr + 1;var playdata = PLAYER_DATA[levelnr-1];// decide which iconvar isLocked = true; // lockedvar stars = 0; // no starsif (playdata > -1) {isLocked = false; // unlockedif (playdata < 4) {stars = playdata;}; // 0..3 stars};// calculate position on screenvar xpos = 160 + (x*128);var ypos = 120 + (y*128);// create iconholdicons[levelnr-1] = this.createLevelIcon(xpos, ypos, levelnr, isLocked, stars);var backicon = holdicons[levelnr-1].getAt(0);// keep level nr, used in onclick methodbackicon.health = levelnr;// input handlerbackicon.inputEnabled = true;backicon.events.onInputDown.add(this.onSpriteDown, this); }; }; }, createLevelIcon: function (xpos, ypos, levelnr, isLocked, stars) {// create new groupvar IconGroup = game.add.group();IconGroup.x = xpos;IconGroup.y = ypos;// keep original position, for restoring after certain tweensIconGroup.xOrg = xpos;IconGroup.yOrg = ypos;// determine background framevar frame = 0;if (isLocked == false) {frame = 1};// add backgroundvar icon1 = game.add.sprite(0, 0, 'levelselecticons', frame);IconGroup.add(icon1);// add stars, if neededif (isLocked == false) {var txt = game.add.bitmapText(24, 16, 'font72', ''+levelnr, 48);var icon2 = game.add.sprite(0, 0, 'levelselecticons', (2+stars));IconGroup.add(txt);IconGroup.add(icon2);};return IconGroup; }, onSpriteDown: function (sprite, pointer) {// retrieve the iconlevelvar levelnr = sprite.health;if (PLAYER_DATA[levelnr-1] < 0) {// indicate it's locked by shaking left/rightvar IconGroup = holdicons[levelnr-1];var xpos = IconGroup.xOrg;var tween = game.add.tween(IconGroup).to({ x: xpos+4 }, 20, Phaser.Easing.Linear.None).to({ x: xpos-4 }, 30, Phaser.Easing.Linear.None).to({ x: xpos+4 }, 40, Phaser.Easing.Linear.None).to({ x: xpos }, 50, Phaser.Easing.Linear.None).start();} else {// simulate button press animation to indicate selectionvar IconGroup = holdicons[levelnr-1];var tween = game.add.tween(IconGroup.scale).to({ x: 0.9, y: 0.9}, 100, Phaser.Easing.Linear.None).to({ x: 1.0, y: 1.0}, 100, Phaser.Easing.Linear.None).start();tween._lastChild.onComplete.add(function(){console.log('OK level selected! -> ' +sprite.health);}, this)// }; }, animateLevelIcons: function () {// slide all icons into screenfor (var i=0; i < holdicons.length; i++) {// get variablesvar IconGroup = holdicons[i];IconGroup.y = IconGroup.y + 600;var y = IconGroup.y;// tween animationgame.add.tween(IconGroup).to( {y: y-600}, 500, Phaser.Easing.Back.Out, true, (i*40)); }; },};// GENERALvar game = new Phaser.Game(gameProperties.screenWidth, gameProperties.screenHeight, Phaser.AUTO, 'gameDiv');game.state.add(states.main, mainState);game.state.add(states.menu, menuState);game.state.add(states.game, gameState);game.state.start(states.menu);Unfortunately, all I get is the colored background with a blank square. I was able to add the title text but not using the bitmapped font. I am glad I took this step before I tried to tack the passing of level data on figuring out the click to function. I am hoping you can spot my mistake -- Thanks for any assitance. Link to comment Share on other sites More sharing options...
BdR Posted December 17, 2015 Author Share Posted December 17, 2015 var states = { main: "main", menu: "menu", game: "game", };It might be that last comma after "game" but have you tried looking at the JavaScript console? In Chrome you can bring it up with Ctrl + shift + J. That usually displays pretty clear error messages marked in red, including the line number where it went wrong. Link to comment Share on other sites More sharing options...
fedora Posted December 17, 2015 Share Posted December 17, 2015 THANK YOU. Great suggestion and a tool I did not know about, but will definitely add to the tool box. The issue was the font. Although I had commented it out in one section -- //game.add.bitmapText(256, 24, 'font72', 'Select a level!', 48);I missed some area and so it was causing the JS to hang. font72:{URL:['assets/fonts/font72.png', 'assets/fonts/font72.xml'], name:'font72'},// add stars, if neededif (isLocked == false) {var txt = game.add.bitmapText(24, 16, 'font72', ''+levelnr, 48);var icon2 = game.add.sprite(0, 0, 'levelselecticons', (2+stars));IconGroup.add(txt);Not sure why this is causing an issue, but will troubleshoot this evening. I will also be attempting to get the levels information to work and the user input. I will let you know of my progress and hope you don't mind me asking for assistance if I get stuck. Link to comment Share on other sites More sharing options...
fedora Posted December 18, 2015 Share Posted December 18, 2015 I was able to overcome the font issue. At one point, I got confused because there is no .xml for the big_font.png. But now the menu comes up as expected. In working with the fonts, I came across this thread which has online editors that some may fine useful - I know they have been to me tonight. Regarding the level icons and clicking to go to the game -- before passing level data -- all I was attempting to do tonight is to drop in your code and see if I could click a level icon and have it take me to the game state. Unfortunately, I have not been successful (and trust me it is not from lack of trying {or shall we say banging my head against keyboard}. The dropped code worked fine (ie no errors), but I think where I am faulting is the place I need to call the these function. I tried every possible location thinking the most appropriate var PLAYER_DATA = [0,3,2,3,1,1,1,2];var holdicons = [];var LevelData = [ { title:"An easy level to start", layout:"550f000202020000000002020000000202000000000202020001", timelimit:120, backcolor:"#3333ff", mustscore:1000, tutorialmessage:1 }, { title:"Introducing enemies", layout:"550f000000000040000200000200400000400002000002004000", timelimit:75, backcolor:"#33ffcc", mustscore:1600, tutorialmessage:2 }, { title:"Now try this", layout:"550f0000200000021116200024c0180010292202000010000001", timelimit:100, backcolor:"#9933ff", mustscore:2500 },];var menuState = function(game){ var levelparams = LevelData[this._levelindex] }; onLevelIconDown: function(sprite, pointer) { // retrieve the iconlevel, stored in sprite.health here var levelnr = sprite.health; console.log('onLevelIconDown - levelnr=' + levelnr); // simulate button press animation to indicate selection var IconGroup = this._icons[levelnr - this._offset - 1]; var tween = this.game.add.tween(IconGroup.scale) .to({ x: 0.9, y: 0.9}, 100, Phaser.Easing.Linear.None) .to({ x: 1.0, y: 1.0}, 100, Phaser.Easing.Linear.None) .start(); // start the level after tween is complete tween.onComplete.add(function(){this.onLevelSelected(sprite.health);}, this); }, onLevelSelected: function(levelnr) { console.log('onLevelSelected ---> levelnr=' + levelnr); // pass levelnr variable to 'Game' state this.game.state.states['states.game']._levelIndex = levelnr-1; this.game.state.start('states.game'); // etc. }, My thought was that even though the data would be bogus that clicking would take me to the game state. I am hoping you can help. Link to comment Share on other sites More sharing options...
BdR Posted December 22, 2015 Author Share Posted December 22, 2015 I've just updated my github example I've added two separate states for level select and game. When you select a level it starts the game state, and just for testing it immediately awards a random nr of stars. The nr of stars are stored in the progress array, and also the progress is saved to the local storage.https://github.com/BdR76/phaserlevelselect chg 1 Link to comment Share on other sites More sharing options...
fedora Posted December 24, 2015 Share Posted December 24, 2015 Thank you for updating the the code and also the other tips/direction. I am excited to work with it and discover the possibilities. Thanks again it is very much appreciated. Link to comment Share on other sites More sharing options...
Rafaelx Posted October 26, 2016 Share Posted October 26, 2016 Hi guys just making my first game and I manage to incorporate most of your code in this example, unfortunately I'm obligated to use different states so how do I approach adding 2 or more states like in this section here? Is my idea below correct? then I guess I declare the this._levelNumber = 0; as 1 and 2 and so on on each state? I'm a bit confused any help? also let me note that Level1 is on a separate .js all together but it works fine. onLevelSelected: function(levelnr) { console.log('onLevelSelected ---> levelnr=' + levelnr); // pass levelnr variable to 'Game' state this.game.state.states['Level1']._levelIndex = levelnr-1; this.game.state.start('Level1'); this.game.state.states['Level2']._levelIndex = levelnr-1; this.game.state.start('Level2'); // etc. } }; Link to comment Share on other sites More sharing options...
BdR Posted October 26, 2016 Author Share Posted October 26, 2016 1 hour ago, Rafaelx said: unfortunately I'm obligated to use different states so how do I approach adding 2 or more states like in this section here? Is my idea below correct? I don't think your code example is correct, because it will .start() all the level states but you only want the selected level to start. If you have a Phaser.State for each level then you could do it something like this: onLevelSelected: function(levelnr) { console.log('onLevelSelected ---> levelnr=' + levelnr); // determine which state to start var statename = 'Level' + levelnr; // start state 'Level1' or 'Level2' etc. this.game.state.start(statename); } Although I'm not really sure why you would want to have a separate Phaser.State for each level.. Maybe check out this thread about how you could pass different level data to a single gamestate. Link to comment Share on other sites More sharing options...
Rafaelx Posted October 27, 2016 Share Posted October 27, 2016 thanks I will try to use your approach to send data to same state, but mainly the problem is that some states have completely different set of rules not just time difference, also have different sprites, and things. Link to comment Share on other sites More sharing options...
Rafaelx Posted October 27, 2016 Share Posted October 27, 2016 thanks so If I want to pass parameters will this works? onLevelSelected: function(levelnr) { // determine which state to start var statename = 'Level' + levelnr; // pass levelnr variable to 'Game' state this.game.state.states[statename]._levelNumber = levelnr; this.state.start(statename); }, Link to comment Share on other sites More sharing options...
BdR Posted October 27, 2016 Author Share Posted October 27, 2016 2 hours ago, Rafaelx said: thanks so If I want to pass parameters will this works? Well have you tried it? Yes I think that will work, as long as the state has the same variable names as you are setting in the levelselect state in onLevelSelected(). So something like this for example will work. // Level3 state definition Level3 = function(game){ // define variables for Level3 this._timeLimit = 1000; this._myColor = '#FFFFFF'; this._startAnimation = true; }; //.. // And then in your onLevelSelected() this.game.state.states['Level3']._timeLimit = 500; this.game.state.states['Level3']._myColor = '#4CAF50'; this.game.state.states['Level3']._startAnimation = false; Although I don't really understand why you need to pass parameters at the start of each level, because all the rules are already programmed in each Level-state. Isn't it just a matter of starting the correct state name? Link to comment Share on other sites More sharing options...
Recommended Posts