QuimB Posted June 21, 2016 Share Posted June 21, 2016 Hi all, I'm trying to find a way to save my game progress, and after some reading it seems LocalStorage is the way to go. I wish to save several variables between sessions, namely: var scoreText; var worker1Amount = 0; var worker3Amount = 0; var workers = 0; var clicks; var clicksPerSecond = 0; var workerCost = 15; (Note: I probably don't need to save the last variable since it's the only one whose value doesn't change.) How can I achieve this? With arrays? All the examples I checked only used one value for storing the game score. var game = new Phaser.Game(800, 600, Phaser.AUTO, 'phaser-example', { preload: preload, create: create, update: update }); var scoreText; var worker1Amount = 0; var worker3Amount = 0; var workers = 0; var clicks; var clicksPerSecond = 0; var workerCost = 15; function preload() { game.load.image('clickBox', 'assets/ClickMe.png'); game.load.image('generator1', 'assets/Gen1.png'); game.load.image('generator3', 'assets/Gen3.png'); game.stage.backgroundColor = '#182d3b'; // If browser tab is displayed, game runs when out of focus. Needs fixing. game.stage.disableVisibilityChange = true; } function create() { clicks = 0; button = game.add.button(game.width/2, game.height/2, 'clickBox', upScoreText); btn_buyWorker = game.add.button(30,400, 'generator1', buyWorker); btn_buyWorker3 = game.add.button(160,400, 'generator3', buyWorker3); scoreText = game.add.text(30, 30, "CLICKS: " + clicks + " WORKERS: " + workers, { font: "20px Arial", fill: "#ffffff", align: "left" }); button.scale.x = 0.5; button.scale.y = 0.5; button.anchor.setTo(0.5); button.inputEnabled = true; btn_buyWorker.inputEnabled = true; timer = game.time.create(false); timer.loop(1000, updateCounter, this) timer.start(); function updateCounter () { clicks += clicksPerSecond; } var style = { font: "22px Courier", fill: "#00ff44" }; var buyWorkerText = game.add.text(0, 0, "Cost: 15", style); buyWorkerText.x = btn_buyWorker.x; buyWorkerText.y = btn_buyWorker.y+100; var buyWorker3Text = game.add.text(0,100, "Cost: 30",style); btn_buyWorker3.addChild(buyWorker3Text); } function update () { //clicks += workers; scoreText.setText("CLICKS: " + clicks + "\nCLICKS PER SEC: " + clicksPerSecond + "\n\+1 AMOUNT: " + worker1Amount + "\n\+3 AMOUNT: " + worker3Amount + "\nTOTAL WORKERS: " + workers); } function upScoreText () { clicks++; } //Need to find a way to disable button if player doesn't have enough clicks to buy it function buyWorker () { if (clicks >= 15) { workers++; worker1Amount += 1; clicks -= 15; clicksPerSecond += 1; } } function buyWorker3 () { if (clicks >= 30) { workers++; worker3Amount += 1; clicks -= 30; clicksPerSecond += 3; } } Thanks in advance. Link to comment Share on other sites More sharing options...
drhayes Posted June 21, 2016 Share Posted June 21, 2016 Stick the values you care about in an object. Call JSON.stringify on the object. Put that string in local storage. To get it out, get the string from local storage. Call JSON.parse on the string. Now you have the object back. The docs on localStorage are here: https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage QuimB, WombatTurkey, LTNGames and 1 other 4 Link to comment Share on other sites More sharing options...
WombatTurkey Posted June 22, 2016 Share Posted June 22, 2016 Just to add on DrHayes beautiful answer, if you need to store more data, you can consider using LZW for compression. https://gist.github.com/revolunet/843889 drhayes and QuimB 2 Link to comment Share on other sites More sharing options...
QuimB Posted June 22, 2016 Author Share Posted June 22, 2016 Thanks for the input. I kinda understand the logic behind the process but I'm not sure how to implement it. Let's say I want to use localStorage for the "clicks" variable. I'm trying to keep things really simple, so I'll just save one variable for now. I'm not sure if I need to create a function for this, but I did it anyway; named it "storeGameClicks". function storeGameClicks () { localStorage.setItem('totalClicks', JSON.stringify('clicks')); totalClicks = JSON.parse(localStorage.getItem('clicks')); } I don't get error messages on the console but I'm sure this isn't right. Thanks again. Link to comment Share on other sites More sharing options...
WombatTurkey Posted June 22, 2016 Share Posted June 22, 2016 1 hour ago, QuimB said: Thanks for the input. I kinda understand the logic behind the process but I'm not sure how to implement it. Let's say I want to use localStorage for the "clicks" variable. I'm trying to keep things really simple, so I'll just save one variable for now. I'm not sure if I need to create a function for this, but I did it anyway; named it "storeGameClicks". function storeGameClicks () { localStorage.setItem('totalClicks', JSON.stringify('clicks')); totalClicks = JSON.parse(localStorage.getItem('clicks')); } I don't get error messages on the console but I'm sure this isn't right. Thanks again. localStorage.setItem('totalClicks', JSON.stringify(clicks)); clicks should be an object literal, not a string Also, getItem('totalClicks') instead of clicks QuimB 1 Link to comment Share on other sites More sharing options...
drhayes Posted June 23, 2016 Share Posted June 23, 2016 Nope, that's really all there is to it. But you do have to JSON.stringify your object before putting it in storage. localStorage can only store strings: https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem Just make sure you use the same key going in and coming out and you're golden. EDIT: Just popped back in to add some caveats about using localStorage. You're sharing the allotted space with anything else in that origin (protocol host port). I thought the limit was around 5MB, but apparently it's now around 10MB. It can also get cleared by the browser or the user at pretty much any time. It's also a synchronous operation that touches disk so it can jank up your game if you use it too much. QuimB 1 Link to comment Share on other sites More sharing options...
QuimB Posted June 23, 2016 Author Share Posted June 23, 2016 Thanks for the replies. Still having difficulties after numerous iterations, but I'm getting there. With the help of this website I came up with this: function storeGameClicks () { localStorage.setItem('totalClicks', JSON.stringfy('clicks')); var totalClicks = JSON.parse(localStorage.getItem('totalClicks')); } According with the resource I posted above, this should do the trick. I probably don't even need a function for this. What are your thoughts? Thanks again. Link to comment Share on other sites More sharing options...
mattstyles Posted June 23, 2016 Share Posted June 23, 2016 Is that 'click' (in quotes) a typo? It needs to be without quotes so that you stringify the object, rather than stringify the string 'clicks'. @WombatTurkey mentioned that You also dont need the in and the out in the same function, but I guess you're just doing that for display in this forum. QuimB 1 Link to comment Share on other sites More sharing options...
QuimB Posted June 24, 2016 Author Share Posted June 24, 2016 Thanks for the reply mattstyles, You're right, WombatTurkey mentioned it, but somehow I missed it. Programming when you're tired is not a good idea. If I'm understanding correctly, the code should look like this: localStorage.setItem('totalClicks', JSON.stringfy(clicks)); var totalClicks = JSON.parse(localStorage.getItem('totalClicks')); (Note: I deleted the storeGameClicks function from my previous post) By the way, where should those two lines of codes be placed? Anywhere? Inside of the create/update function(s)? Because those lines are at the end of the code. One more thing: I'm using Google Chrome (v49.0.2623.112 - 64-bit) with OS X 10.8.5. The files are installed locally (through MAMP). Could this setup cause issues? Sorry for so many questions, but the more I read and code, the more questions arise. Thanks for the help. Link to comment Share on other sites More sharing options...
VitaZheltyakov Posted June 24, 2016 Share Posted June 24, 2016 //---------- Функция записи массива данных в локальное хранилище -------------// // array - сам массив, section - раздел данных (название массива) function dataSave(array, section) { for (key in array) { if (typeof section !== "undefined") { window.localStorage[section+'-'+key] = array[key]; } else window.localStorage[key] = array[key]; } }; //----------------------------------------------------------------------------// //--------- Функция чтения массива данных из локального хранилища ------------// // section - раздел данных (название массива) function dataLoad(array, section) { for (key in array) { if (typeof window.localStorage[section+'-'+key] !== "undefined") { if (!isNaN(window.localStorage[section+'-'+key])) array[key] = parseInt(window.localStorage[section+'-'+key], 10); else array[key] = window.localStorage[section+'-'+key]; } } }; //----------------------------------------------------------------------------// Link to comment Share on other sites More sharing options...
mattstyles Posted June 25, 2016 Share Posted June 25, 2016 @VitaZheltyakov thats massively over-engineered, simplicity is key, particularly in the case of this question. The OP just needs to store an object persistently, iterating over it is totally pointless. 9 hours ago, QuimB said: Thanks for the reply mattstyles, localStorage.setItem('totalClicks', JSON.stringfy(clicks)); var totalClicks = JSON.parse(localStorage.getItem('totalClicks')); (Note: I deleted the storeGameClicks function from my previous post) By the way, where should those two lines of codes be placed? Anywhere? Inside of the create/update function(s)? Because those lines are at the end of the code. Anywhere that makes sense for your app, accessing local storage is synchronous (this means it blocks and happens immediately, nothing else in your program will happen at the same time, the opposite is asynchronous, such as making an http request, this will not block and instead JS will stick a function on the stack to be invoked when the request completes) so technically it could cause a slow down, but, you're doing this very rarely, you'll never notice it and you have no choice anyway, localStorage is a DOM api. At a push I'd say you'd want to grab that data either in preload or create and you can save it whenever you like, presumably all your inputs get collated into the update function so you'd probably call it from there (or from a function originating from update). 9 hours ago, QuimB said: One more thing: I'm using Google Chrome (v49.0.2623.112 - 64-bit) with OS X 10.8.5. The files are installed locally (through MAMP). Could this setup cause issues? No issues, good setup, although, unless you're explicitly using sql or php you dont need mamp, a mac comes with apache installed and sql/php are easy to install anyway and use without the overhead of mamp. For small front-end projects, I normally end up requiring a build step which means I need to use node with the project so I normally use a node process to serve my assets, which is far easier than worrying about which directories apache is serving and how. Quote Sorry for so many questions, but the more I read and code, the more questions arise. Thats good, thats the way it should be. Never stop questioning. Never stop exploring. drhayes 1 Link to comment Share on other sites More sharing options...
QuimB Posted June 26, 2016 Author Share Posted June 26, 2016 Hi mattstyles, Thanks for the support. I just placed the localStorage inside of the the preload function. From other examples I studies, the code should be working, Unfortunately, every time I refresh the screen, the variable is always 0. var game = new Phaser.Game(800, 600, Phaser.AUTO, 'phaser-example', { preload: preload, create: create, update: update }); var scoreText; var worker1Amount = 0; var worker3Amount = 0; var workers = 0; var clicks = 0; var clicksPerSecond = 0; function preload() { localStorage.setItem('totalClicks', JSON.stringify(clicks)); var totalClicks = JSON.parse(localStorage.getItem('totalClicks')); game.load.image('clickBox', 'assets/ClickMe.png'); game.load.image('generator1', 'assets/Gen1.png'); game.load.image('generator3', 'assets/Gen3.png'); game.stage.backgroundColor = '#182d3b'; // If browser tab is displayed, game runs when out of focus. Needs fixing. game.stage.disableVisibilityChange = true; } function create() { clicks = 0; button = game.add.button(game.width/2, game.height/2, 'clickBox', upScoreText); btn_buyWorker = game.add.button(30,400, 'generator1', buyWorker); btn_buyWorker3 = game.add.button(160,400, 'generator3', buyWorker3); scoreText = game.add.text(30, 30, "CLICKS: " + clicks + " WORKERS: " + workers, { font: "20px Arial", fill: "#ffffff", align: "left" }); button.scale.x = 0.5; button.scale.y = 0.5; button.anchor.setTo(0.5); button.inputEnabled = true; btn_buyWorker.inputEnabled = true; timer = game.time.create(false); timer.loop(1000, updateCounter, this) timer.start(); function updateCounter () { clicks += clicksPerSecond; } var style = { font: "22px Courier", fill: "#00ff44" }; var buyWorkerText = game.add.text(0, 0, "Cost: 15", style); buyWorkerText.x = btn_buyWorker.x; buyWorkerText.y = btn_buyWorker.y+100; var buyWorker3Text = game.add.text(0,100, "Cost: 30",style); btn_buyWorker3.addChild(buyWorker3Text); } function update () { scoreText.setText("CLICKS: " + clicks + "\nCLICKS PER SEC: " + clicksPerSecond + "\n\+1 AMOUNT: " + worker1Amount + "\n\+3 AMOUNT: " + worker3Amount + "\nTOTAL WORKERS: " + workers); } function upScoreText () { clicks++; } //Need to find a way to disable button if player doesn't have enough clicks to buy it function buyWorker () { if (clicks >= 15) { workers++; worker1Amount += 1; clicks -= 15; clicksPerSecond += 1; } } function buyWorker3 () { if (clicks >= 30) { workers++; worker3Amount += 1; clicks -= 30; clicksPerSecond += 3; } } Link to comment Share on other sites More sharing options...
drhayes Posted June 27, 2016 Share Posted June 27, 2016 The very first thing you do in your preload is this: "localStorage.setItem('totalClicks', JSON.stringify(clicks));". You've just declared clicks to be 0 and now you're storing it. That will overwrite whatever is there with the value of 0. You should try getItem first; if the result of that is undefined *then* you can store the initial value. Something more like this: var clicksString = localStorage.getItem('clicks'); if (clicksString === undefined) { clicks = 0; localStorage.setItem('clicks', JSON.stringify(clicks)); } else { clicks = JSON.parse(clicksString, 10); } JSON.stringify doesn't mean much if you're really just storing a number -- you can just store the number directly (e.g. localStorage.setItem('clicks', clicks)) and use parseInt when you read it like I did in that example. mattstyles and QuimB 2 Link to comment Share on other sites More sharing options...
QuimB Posted June 28, 2016 Author Share Posted June 28, 2016 Hi, drhayes. Thanks for the input. I wrote a long reply, but it was messy to read and was all over the place. My mind is sluggish today. I'll try to be succinct. At the end of the file I added the following function. I know it doesn't work but it's only to have something to work it, instead of just copy/pasting. function recordClicks() { var maxClicks = localStorage.getItem('clicks'); if (maxClicks === undefined) { clicks = 0; localStorage.setItem('clicks', JSON.stringify(clicks)); } else { clicks = JSON.parse(maxClicks, 10); } } I have "var clicks = 0;" at the beginning of the file. Without it, I would get an error message (main.js:42: Uncaught ReferenceError: clicks is not defined). Without the recordClicks function, when I refreshed the browser, the score text would display "Clicks: null" and then (after a second) "Clicks: 0". Also, couldn't I program something like: if currentClicks > clicks, record that number and store it as clicks? From what I read localStorage alone is enough for the job but I'm looking for alternatives. I have been stuck on this over a week. Stringify might be overkill for numbers, but I'll let it slide for now until I get a better grasp of the basics. Thanks again. Link to comment Share on other sites More sharing options...
mattstyles Posted June 29, 2016 Share Posted June 29, 2016 // Big fat global (dont worry about it for now, it'll be fine, you // can learn better patterns later) var clicks = 0 // More globals, just for this example var button = null var element = null function preload () { // local variable, we're only using this to store the localStorage // value temporarily var totalClicks = localStorage.getItem('clicks') // check that it is exists if (totalClicks) { clicks = parseInt(totalClicks, 10) } create() } // For brevity we're just going to create a DOM button and add an event listener function create () { button = document.createElement('button') button.innerHTML = 'click me' element = document.createElement('h1') element.innerHTML = clicks document.body.appendChild(button) document.body.appendChild(element) // Add a handler to the click button button.addEventListener('click', function(event) { clicks++ save() update() }) } function update () { element.innerHTML = clicks } function save () { localStorage.setItem('clicks', clicks) } // Kickstart everything preload() See if you understand what is happening here and hopefully that will help We initialise clicks to 0 when the program starts, we then call `preload` (similar to Phaser under the hood) which queries localStorage, if it finds something then it changes clicks to equal it, otherwise clicks stays at 0. The program then continues to create some DOM and append a handler. Each click updates the DOM with the mutations from the action (increment the counter) and saves the count to local storage. Not sure it can be any simpler, localStorage is a really simple synchronous key/value store. Try loading that script into the browser, hitting the button a few times and refreshing the page, you'll see a persistent counter. Fire it up in another browser and you'll be back to 0. Hope it helps drhayes and QuimB 2 Link to comment Share on other sites More sharing options...
QuimB Posted July 1, 2016 Author Share Posted July 1, 2016 Thanks for taking your time to write that down. I used JSfiddle to see how the code worked. I had to do a bit of research to learn about parseInt, especially the importance of the radix parameter. Even though your example seems to be pure javascript, I understand the logic behind it. Now, I just need to grab that concept and translate it to Phaser. I decided to remove the "excess" code for now so I could focus on the basics. Not quite there yet, but it feels good to make progress. Thanks. [Redacted old and complicated code] Link to comment Share on other sites More sharing options...
QuimB Posted July 3, 2016 Author Share Posted July 3, 2016 Hi all, Finally got it working. I removed all the fluff for newbies to have an easier time to understand what's going on. The lines that do the magic: clicks = +localStorage.getItem('clicks') || 0; and... localStorage.setItem('clicks', clicks); Thanks for the help. Here's the full code. var game = new Phaser.Game(800, 600, Phaser.AUTO, 'phaser-example', { preload: preload, create: create, update: update }); var scoreText; function preload() { game.load.image('clickBox', 'assets/ClickMe.png'); game.stage.backgroundColor = '#182d3b'; game.stage.disableVisibilityChange = true; } function create() { clicks = +localStorage.getItem('clicks') || 0; button = game.add.button(game.width/2, game.height/2, 'clickBox', upScoreText); scoreText = game.add.text(30, 30, "CLICKS: " + clicks, {font: "20px Arial", fill: "#ffffff", align: "left"}); button.scale.x = 0.5; button.scale.y = 0.5; button.anchor.setTo(0.5); button.inputEnabled = true; } function update () { scoreText.setText("CLICKS: " + clicks); localStorage.setItem('clicks', clicks); } function upScoreText () { clicks++; } Link to comment Share on other sites More sharing options...
Recommended Posts