QuimB

2.4.8 - Saving Multiple Variables with LocalStorage

Recommended Posts

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.

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites
//---------- Функция записи массива данных в локальное хранилище -------------//
// 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];
		}
	}
};
//----------------------------------------------------------------------------//

 

Share this post


Link to post
Share on other sites

@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.

Share this post


Link to post
Share on other sites

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;
            }

}

 

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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.

 

Share this post


Link to post
Share on other sites
// 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

Share this post


Link to post
Share on other sites

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]

 

Share this post


Link to post
Share on other sites

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++;
}

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.