Flappy Birds - Count Score, Game Over and Restart
jem170884

So today I have built my first ever game. It's not too bad but now I am completely stuck!

I am looking to add a score counter and a leaderboard and also a restart game scene. 

Please help and be aware that most of the techy words mean nothing to me!

All 14 Comments
jem170884

I've got the restrt to work but still stuck on the others

Gio

Hi

I'll assume you've been following this flappy bird video tutorial.

Adding a score can be done in a couple of ways - I'll explain the easier one first. The player could get one point when the wall goes past the end of the screen (on the lef-hand side) and is deleted.

This is easy to do: in the wall object's onMoveComplete function, increment a counter:Now wade.app.scoreCounter keeps track of the current score - remember to reset it to 0 when you restart the game.

wade.app.score = (wade.app.score || 0) + 1;

To display this score on the screen, create a new scene object (call it score counter), and add a Text Sprite to it. After doing this, delete the default white sprite that you get when you create a new scene object, so you're left with a scene object that contains only 1 text sprite, and is called 'score counter'.

When you increment the score (and when you reset it to 0), you want to update this sprite with the correct number:

wade.getSceneObject('score counter').getSprite().setText(wade.app.score);

Now this should all work and it's nice and simple. However, if you wanted to be more precise, you'd give a player points as soon as they go through each gap. Now that's slightly more complicated - it's up to you whether you want to take the extra effort to do it that way.

To do that you would have to create another object that you would place right in the middle of the gap - this would be invisible and would act as a trigger: when the player touches it, they get a point.

So you would have to add this extra object, and give it a property such as isTrigger = true.

Every time you spawn a wall, you also spawn one of these invisible objects (exactly the same code that you're using now to clone walls, get them to move and delete them).

You would have to change the logic in your player object's onUpdate function too. Instead of just doing

if (this.getOverlappingObjects().length > 0)
{
    ....
}

You would have to do:

var overlappingObjects = this.getOverlappingObjects();
for (var i=0; i<overlappingObjects.length; i++)
{
    if (overlappingObjects[i].isWall)
    {
        // do what you were doing before
        // ....
    }
    else if (overlappingObjects[i].isTrigger)
    {
        // score points
        // .....
    }
}

I hope that makes sense, let me know if it doesn't :)

For leaderboards, do you mean local ones (i.e. you keep track of player scores on one computer) or a global one with your own server that keeps track of scores submitted by everyone?

jem170884

Thanks Gio, went for the easy option and it worked!

I'm after an on device leaderboard where someone can type their name and this will be dispayed ay the end on the came on the replay screen. Can you help with this?

jem170884

Also, the score does reset to zero when you restart the game but as soon as you get a point it adds onto your end score of the previous version, can you help with this too?

Gio

To reset the score, are you doing this when restarting the game?

wade.app.score = 0;

For a local leaderboard, I think i have some code somewhere that does exactly that. I'll see if I can find it and post it here.

jem170884

Gio, you are an absolute hero! It worked!!!

jem170884

Hi Gio,

Managed to get this hosted on a server so am looking for a global leaderboard. Can you help with this at all?

 

Thanks

Jemma

Gio

Hi

For a local leaderboard, create a new SceneObject, then look in the top-right corner of the screen for a blue icon that looks like two curly braces { }. This lets you edit the raw JSON data for the object - note that the same icon is present elsewhere (for Sprites and Animations), however you want to edit the SceneObject in this instance.

Clicking the icon opens a dialog with some code - delete everything and paste this into that window:

{
	"type": "SceneObject",
	"position": {
		"x": -162,
		"y": -140
	},
	"rotation": 0,
	"behaviors": [],
	"sprites": [
		{
			"type": "TextSprite",
			"text": "Leaderboard",
			"name": "",
			"font": "36px Courier New",
			"alignment": "left",
			"color": "white",
			"visible": true,
			"layer": 1,
			"maxWidth": 0,
			"shadowColor": "#000",
			"shadowBlur": 0,
			"shadowOffset": {
				"x": 0,
				"y": 0
			},
			"lineSpacing": 1,
			"maxLines": 0,
			"outlineColor": "#000",
			"outlineWidth": 0,
			"boundsScale": {
				"x": 1,
				"y": 1
			},
			"sortPoint": {
				"x": 0,
				"y": 0
			},
			"fixedSize": false,
			"properties": {}
		}
	],
	"spriteOffsets": [
		{
			"x": 0,
			"y": 0,
			"angle": 0
		}
	],
	"alignment": {
		"x": "center",
		"y": "center"
	},
	"name": "leaderboard",
	"isTemplate": false,
	"path": "",
	"addToScene": {
		"autoListen": true,
		"params": null
	},
	"properties": {
		"maxScoreLength": 7,
		"maxNameLength": 8,
		"storageKey": "'myLeaderboard'",
		"numScores": 5
	},
	"functions": {
		"refresh": "function (data)\n{\n\t// retrieve stored data\n\tvar data = wade.retrieveLocalObject(this.storageKey);\n\tif (!data)\n\t{\n\t    this.getSprite().setText('');\n\t    return;\n\t}\n\t\n\t// set max width based on user preferences\n\tvar text = '';\n\tvar lineLength = this.maxNameLength + this.maxScoreLength + 1;\n\t\n\t// show the scores\n\tvar text = '';\n\tfor (var i=0; i < data.length && i < this.numScores; i++)\n\t{\n\t    var name =data[i].name.substr(0, this.maxNameLength);\n\t    var score = data[i].score.toString().substr(-this.maxScoreLength);\n\t    text += name;\n\t    for (var j=name.length; j<=this.maxNameLength; j++)\n\t    {\n\t        text += ' ';\n\t    }\n\t    for (j=score.length; j<=this.maxScoreLength; j++)\n\t    {\n\t        text += ' ';\n\t    }\n\t    text += score + '\\n';\n\t}\n\tthis.getSprite().setText(text);\n}",
		"onAddToScene": "function (data)\n{\n\tthis.refresh();\n}",
		"addScore": "function (data)\n{\n\tvar scores = wade.retrieveLocalObject(this.storageKey) || [];\n\tscores.push(data);\n\tscores.sort(function(a, b)\n\t{\n\t    return b.score - a.score;\n\t});\n\tscores.length = Math.min(scores.length, this.numScores);\n\twade.storeLocalObject(this.storageKey, scores);\n\tthis.refresh();\n}"
	}
}

Now you have a local, persistent leaderboard. If you look in the object custom properties there are a few parameters to customize things such as the maximum length of player names, etc.

To use it, simply do something like this:

wade.getSceneObject('leaderboard').addScore({name: 'Your name', score: 12345});

A global persistent leaderboard is a much more complicated matter. We have a blog post that guides you through the various steps of setting up a Node.js server for that purpose. Still, it takes a bit of effort and you need to be able to host node.js and mongodb somewhere - there are a number of web hosts that offer this, we use amazon aws.

jem170884

Thanks Gio,

I'm struggling with this one. I cant get it to display. Also, where would the user enter their name? Is there a video of how to do this at all?

Jemma

Gio

It's not really supposed to diplay anything until you add scores to it.

To get things working quickly, I'd just use a JavaScript prompt to get people to enter their names. So when the game is over, do something like this:

var name = prompt('Enter your name');
wade.getSceneObject('leaderboard').addScore({name: name, score: 12345});

Obviously replace 12345 with the actual score :)

jem170884

Hi Gio,

I have tried again and failed. I think I am adding the code to the wrong function but everything I try gives me the same result.

Gio

If you attach your project's zip here I'll take a look.

Or you could send me a private message (by clicking my name), if you don't want to share it publicly.

jem170884

Hi Gio,

Thanks for all of your help on this. 

I have trialled the game with a few people and they have said it is too hard, I am now looking to make the first 10 walls easier to navigate and then get harder as the game moves on. I thought I would do this by creating levels but this seems to be harder than I thought it was going to be. 

My spawn code is 

if(this.getPosition().x < 0)
{
    var newBarrier = this.clone();
    var offset = Math.floor(Math.random()*3);

So as soon as my wall gets past 0 then another is spawned, can I set a limit to how many times I want the wall to spawn and once this has been completed, another scene appears with a smaller wall offset? 

 

foxcode

Hi jem. I have not seen the code for our flappy birds in a long time, but I dont think you want to be creating new scenes for this. Try to break the problem down.

Your goal is to make the game easier at the start, and then get harder.

To start with, make the gap between the sprites very large so it is very easy, this will be the starting state for the game. Now we have only 1 job, making it harder up until a certain point, we dont want the game to be impossible.

There are many approaches you could take, but one thing you need is a counter, how many walls you past. I suggest adding the following to the initial loadscene callback function.

wade.app.wallsPassed = 0;    // Number of walls successfully passed
wade.app.maxDifficulty = 30; // After passing this many walls, the game will not get any harder

Now we need to increment this wallsPassed variable every time we pass a wall, or close enough. The user cannot see this number change so it doesn't have to be perfect. To keep things simple, every time you spawn a new barrier, I would add 1 to wallsPassed, so your spawn code would then look like this

if(this.getPosition().x < 0)
{
    wade.app.wallsPassed++;
    var newBarrier = this.clone();
    var offset = Math.floor(Math.random()*3);

Now we know roughly how many walls we have passed. The next step is to decrease the distance between the sprites as this number gets larger. I set the max difficulty to 30, so after you have passed 30 walls, the gap will be 30 pixels smaller. You can change the number to anything you want, but first we need to make the sprites closer.

This bit is a little tricky because we need to worry about 2 offsets.

1. We have 2 sprite offsets, this is the distance each crate image is from the center of the scene object, this is the value we need to change.

2. We have scene object offsets. This is what puts the barrier either high, low, or in the middle. You could add more options to this, but it is not relevant to this problem so we will ignore these ones for now.

In the tutorial, we set sprite offsets for each crate object once. We now need to do this in code.

We will use 2 functions that belong to the scene object prototype

getSpriteOffset()
setSpriteOffset()

So. Just to keep what we have so far,  we need to modify your code that spawns a new barrier.

if(this.getPosition().x < 0)
{
    var newBarrier = this.clone();
    newBarrier.setSpriteOffset[0] = -350; // This sets one sprite to be 350 pixels above the obj
    newBarrier.setSpriteOffset[1] = 350;  // This sets the other  to be 350 pixels below the obj

We are almost done, I realise this is a long reply, if you run into problems just reply on this thread :)

At the moment, you should realise our sprites are 350*2, so 700 pixels apart. Now the sprites themselves have a size so the gap between them is much smaller than 700 pixels, but you can change the 350 value to something else until you get the starting gap size you want.

Now the last step is easy, we simply change the above code to this.

if(this.getPosition().x < 0)
{
    var addDifficulty = wade.app.wallsPassed;  // Set the difficulty
    if(addDifficulty > wade.app.maxDifficulty) // Limit the difficulty to the max allowed
    {
        addDifficulty = wade.app.maxDifficulty;
    }
    var newBarrier = this.clone();
    newBarrier.setSpriteOffset[0] = -350 + addDifficulty; 
    newBarrier.setSpriteOffset[1] = 350  - addDifficulty;


Now the gap is no longer 700. It starts as 700 but gets smaller with each gap we pass. Pretend the max difficulty is 50. that would reduce the gap to 600 at the hardest level.

 

This is only one approach, there are many ways of doing what you want. I suggest you try to follow this. Don't worry if you get stuck, it is a little trick and given that it's nearly half eleven I may well have made a mistake. Good luck

 

Post a reply
Add Attachment
Submit Reply
Login to Reply