CrossBrowdy - Examples

Advanced

Balloon popping game

This is an example of a simple balloon popping game (using the Game engine module from the previous example):

index.html:

<!DOCTYPE html>
<html>
	<head>
		<meta http-equiv="content-type" content="text/html; charset=utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
		<title>Advanced: Balloon popping game - Example</title>
		<!-- Loads the needed CSS files: -->
		<link rel="stylesheet" type="text/css" href="main.css" />
		<!-- Loads FlashCanvas (Flash emulation) before CrossBrowdy. Needed also to use ExplorerCanvas (VML emulation) without problems: -->
		<script type="text/javascript" src="CrossBrowdy/CrossBase/audiovisual/image/canvas/FlashCanvas/pro/bin/flashcanvas.js"></script><!-- FlashCanvas/ExplorerCanvas do not support lazy load. -->
		<!-- Loads CrossBrowdy.js (main file): -->
		<script src="CrossBrowdy/CrossBrowdy.js" type="text/javascript" language="javascript"></script><!-- "type" and "language" parameters for legacy clients. -->
		<!-- Loads the other needed script files: -->
		<script src="main.js" type="text/javascript" language="javascript"></script>
	</head>
	<body>
		<div id="loading">Loading...</div>
		<canvas id="my_canvas">if you read this, canvas is not working</canvas><!-- Some emulation methods will require the canvas element created in HTML (not dynamically by JavaScript). -->
		<canvas id="my_canvas_buffer">if you read this, canvas is not working</canvas><!-- Some emulation methods will require the canvas element created in HTML (not dynamically by JavaScript). -->
		<button id="start_button" onClick="gameStart();">Start game!</button>
		<br />
		<!-- The "CB_console" element will be used automatically in the case that the client does not support console: -->
		<div id="CB_console" style="visibility:hidden; overflow:scroll;">
			<span style="font-weight:bold;">Console:</span><br />
		</div>
		<div id="crossbrowdy_info"><a href="/guides#examples" target="_blank">CrossBrowdy.com</a> example</div>
	</body>
</html>

main.css:

body { background-color:#000000; word-wrap:break-word; }
#crossbrowdy_info { position:fixed; bottom:2px; right:2px; }
#crossbrowdy_info a { color:#00aadd; }
#crossbrowdy_info a:hover { color:#0033aa; }
span { color:#aa0000; }
#loading { position:absolute; left:0px; top:0px; color:#ffff00; }
#my_canvas { position:absolute; left:0px; top:0px; cursor:crosshair; }
#my_canvas_buffer { position:absolute; left:0px; top:0px; visibility:hidden; display:none; cursor:crosshair; }
#start_button
{
	visibility: hidden;
	display: none;
	position:absolute;
	left:25%;
	top:25%;
	width:50%;
	height:50%;
	color:#ff0000;
	font-weight:bold;
	cursor:pointer;
	cursor:hand;
	filter:alpha(opacity=80);
	opacity:0.8;
	-moz-opacity:0.8;
	-khtml-opacity:0.8;
	-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
}
#start_button:hover
{
	color:#ffaa00;
	filter:alpha(opacity=100);
	opacity:1;
	-moz-opacity:1;
	-khtml-opacity:1;
	-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
}

main.js:

//Path to the graphic rendering engine module:
var CB_GEM_PATH = CB_GEM_PATH || "../simple_game_engine_files/";

var CB_GEM_DEBUG_MESSAGES = true; //Shows debug messages.

//Adds the game engine module to CrossBrowdy:
var CB_GEM_MODULE_NEEDED_MODULES = {};
CB_GEM_MODULE_NEEDED_MODULES[CB_GEM_PATH + "game_engine_module.js"] = { load: true, mandatory: true, absolutePath: true };
CB_Modules.addNeededModule(CB_NAME, "GAME_ENGINE_MODULE", CB_GEM_MODULE_NEEDED_MODULES);

CB_init(main); //It will call the "main" function when ready


//This function will be called when CrossBrowdy is ready:
function main()
{
	CB_console("CrossBrowdy and all needed modules loaded. Starting game...");
	
	//Defines the events to detect the input:
	var pointerX = null;
	var pointerY = null;
	CB_Pointer.onDown(function(e) { pointerX = e.clientX; pointerY = e.clientY; }); //Gets the pointer coordinates.
	var pointerCoordinatesClear = function (e) { pointerX = pointerY = null; }; //Clears the pointer coordinates.
	CB_Pointer.onUp(pointerCoordinatesClear);
	CB_Pointer.onCancel(pointerCoordinatesClear);
	CB_Pointer.onLeave(pointerCoordinatesClear);
	CB_Pointer.onOut(pointerCoordinatesClear);

	//Defines needed data:
	var balloonsAppearingMsInitial = 1500;
	CB_GEM.data = //Data stored in the game engine module (can be exported to save the game status):
	{
		gameStarted: false,
		balloonsAppearingMsInitial: balloonsAppearingMsInitial, //Milliseconds to wait before creating a new balloon.
		balloonsAppearingMs: balloonsAppearingMsInitial, //Milliseconds to wait before creating a new balloon.
		balloonsIdCounter: 0,
		balloonsCounter: 0,
		balloonsPopped: 0,
		score: 0
	};
	var balloonsAppearingMsMinimum = 150; //Minimum of milliseconds to wait to create another new balloon. The 'CB_GEM.data.balloonsAppearingMs' integer will never be lower than this value.
	var balloonsAppearingMsDecreasingPerLoop = 1; //Number of milliseconds that will be decreased from the 'CB_GEM.data.balloonsAppearingMs' integer (to increase speed after a balloon appears).
	var balloonsCounterMax = 150;
	var balloonWidth = 40;
	var balloonHeight = 50;
	var balloonLeft = null;
	var balloonLeftMin = balloonWidth;
	var balloonLeftMax = CB_Screen.getWindowWidth() - balloonWidth;
	var balloonTop = null;
	var balloonTopMin = balloonHeight;
	var balloonTopMax = CB_Screen.getWindowHeight() - balloonHeight;
	var balloonColor = null;
	CB_Screen.onResize(function() { balloonLeftMax = CB_Screen.getWindowWidth() - balloonWidth; balloonTopMax = CB_Screen.getWindowHeight() - balloonHeight; }); //Updates the limits when the screen is resized.

	//Sets the desired sprites scene data (can be modified dynamically):
	CB_GEM.spritesGroupsData =
	{
		//'my_sprites_groups_1' ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object). Some missing or non-valid properties will get a default value:
		id: "my_sprites_groups_1", //Identifier of the sprites groups object (also used for the 'CB_GraphicSpritesScene' object). Optional but recommended. It should be unique. By default, it is generated automatically.
		srcWidth: 40, //The value for the "srcWidth" property which will be used as default if not provided (or the provided one was wrong) in the given 'CB_GraphicSprites.SPRITES_OBJECT' objects. Default: CB_GraphicSprites.WIDTH_SOURCE_DEFAULT.
		srcHeight: 40, //The value for the "srcHeight" property which will be used as default if not provided (or the provided one was wrong) in the given 'CB_GraphicSprites.SPRITES_OBJECT' objects. Default: CB_GraphicSprites.HEIGHT_SOURCE_DEFAULT.
		data: { skipAfter: null, duration: null, timeResetAndEnableAfter: null, loop: true, clearPreviousFirst: true }, //Object with any additional data desired which can be any kind. Default: { 'that' : CB_GraphicSprites.SPRITES_OBJECT, 'getThis' = function() { return this.that; } }.
		//Numeric array containing 'CB_GraphicSprites.SPRITES_OBJECT' objects with all the sprites groups that will be used (their "parent" property will be set to point the current 'CB_GraphicSpritesScene' object which contains them):
		spritesGroups:
		[
			{
				id: "info",
				srcType: CB_GraphicSprites.SRC_TYPES.TEXT,
				src:
					"Score: 0\n" +
					"Balloons popped: 0\n" +
					"Balloons shown: 0 (max limit: " + balloonsCounterMax + ")",
				top: 15,
				zIndex: balloonsCounterMax + 2,
				data:
				{
					fontSize: "16px",
					fontFamily: "courier",
					style: "#ffff00",
					fontWeight: "bold",
					stroke: true
				},
				sprites: [ { id: "info_sprite" } ]
			}
		]
	};

	//Defines the callbacks for the game loop:
	var timingLastTime = 0;
	var timingNow = null;
	CB_GEM.onLoopStart = function(graphicSpritesSceneObject, CB_REM_dataObject, expectedCallingTime) //When the game loop starts (before rendering the graphics):
	{
		if (!CB_GEM.data.gameStarted) { return; }
		if (CB_GEM.data.balloonsCounter >= balloonsCounterMax)
		{
			gameEnd("Game over :(");
			return;
		}
		
		//Gets the current timing:
		timingNow = CB_Device.getTiming();
		
		//If enough time has elapsed, creates a new balloon:
		if (timingNow - timingLastTime >= CB_GEM.data.balloonsAppearingMs)
		{
			//Inserts the new balloon:
			balloonLeft = Math.floor(Math.random() * (balloonLeftMax - balloonLeftMin)) + balloonLeftMin;
			balloonTop = Math.floor(Math.random() * (balloonTopMax - balloonTopMin)) + balloonTopMin;
			balloonColor = //Random balloon colour (not too dark):
				Math.floor(Math.random() * (255 - 80)) + ", " + //Red.
				Math.floor(Math.random() * (255 - 80)) + ", " + //Green.
				Math.floor(Math.random() * (255 - 80)); //Blue.
			graphicSpritesSceneObject.insert
			(
				//'CB_GraphicSprites.SPRITES_OBJECT' object for the balloon (to create the internal 'CB_GraphicSprites' object with its internal sprite and sub-sprite):
				{
					id: "balloon_" + CB_GEM.data.balloonsIdCounter,
					srcType: CB_GraphicSprites.SRC_TYPES.ELLIPSE,
					data: { isBalloon: true, radiusX: balloonWidth, radiusY: balloonHeight, startAngle: 2, endAngle: 11, style: "rgba(" + balloonColor + ", 0.8)" },
					left: balloonLeft,
					top: balloonTop,
					zIndex: CB_GEM.data.balloonsIdCounter + 1,
					sprites:
					[
						//'CB_GraphicSprites.SPRITE_OBJECT' object with the ellipse representing the balloon (one single sprite):
						{
							id: "balloon_" + CB_GEM.data.balloonsIdCounter + "_sprite",
							subSprites:
							[
								{
									//'CB_GraphicSprites.SUBSPRITE_OBJECT' object for the text with the identifier of the balloon (one single sub-sprite):
									id: "ballon_" + CB_GEM.data.balloonsIdCounter + "_identifier",
									srcType: CB_GraphicSprites.SRC_TYPES.TEXT,
									src: CB_GEM.data.balloonsIdCounter + 1,
									left: -parseFloat(CB_REM_dataObject.CB_CanvasObjectContext.measureText(CB_GEM.data.balloonsIdCounter + "").width) || 0,
									top: -9,
									data:
									{
										fontSize: "18px",
										fontFamily: "courier",
										style: "#000000",
										fontWeight: "bold"
									}
								}
							]
						}
					]
				}
			);
			
			CB_GEM.data.balloonsIdCounter++;
			CB_GEM.data.balloonsCounter++;

			graphicSpritesSceneObject.getById("info").getById("info_sprite").zIndex = CB_GEM.data.balloonsIdCounter + 1; //Increments z-index of the information panel to keep it above all.
			
			CB_GEM.data.balloonsAppearingMs -= balloonsAppearingMsDecreasingPerLoop;
			if (CB_GEM.data.balloonsAppearingMs < balloonsAppearingMsMinimum) { CB_GEM.data.balloonsAppearingMs = balloonsAppearingMsMinimum; }
			
			timingLastTime = timingNow; //Updates the last timing when the last balloon was created.
			
			updateInfo(graphicSpritesSceneObject, balloonsCounterMax); //Updates the information shown.
		}
	};
	
	CB_GEM.onLoopEnd = function(graphicSpritesSceneObject, CB_REM_dataObject, expectedCallingTime) //When the game loop ends (after rendering the graphics):
	{
		if (!CB_GEM.data.gameStarted) { return; }
		
		if (pointerX === null || pointerY === null) { return; } //If the screen is not being touched, exits.
		
		//Loops through all the balloons in the 'CB_GraphicSpritesScene' object (using reversed z-index order):
		var balloonPopped = false;
		CB_GEM.graphicSpritesSceneObject.forEach
		(
			function(graphicSpritesObject) //functionEach.
			{
				if (graphicSpritesObject.spritesGroup.data.isBalloon)
				{
					if (balloonPopped) { return; } //Only allows to pop once balloon per touch.
					
					//Checks whether a balloon has been pressed:
					if (CB_Collisions.isPointOverEllipse(pointerX, pointerY, graphicSpritesObject.spritesGroup.left, graphicSpritesObject.spritesGroup.top, graphicSpritesObject.spritesGroup.data.radiusX, graphicSpritesObject.spritesGroup.data.radiusY))
					{
						balloonPopped = true;
						pointerCoordinatesClear(); //Clears the pointer coordinates (to prevent to explode more balloons in the next loop with the same action).
						CB_GEM.graphicSpritesSceneObject.removeById(graphicSpritesObject.id); //Removes the balloon.
						CB_GEM.data.balloonsCounter--;
						CB_GEM.data.balloonsPopped++;
						CB_GEM.data.score += balloonsAppearingMsInitial - CB_GEM.data.balloonsAppearingMs + 1; //The faster they appear, the higher is the score you get.
						playSoundFx("balloon_explosion");
						CB_Device.Vibration.start(100);
						updateInfo(graphicSpritesSceneObject, balloonsCounterMax); //Updates the information shown.
					}
				}
			},
			false, //orderedByZIndex.
			0, //delayBetweenEach.
			CB_Arrays.copy(CB_GEM.graphicSpritesSceneObject.spritesGroups.itemsByZIndex).reverse() //graphicSpritesObjects.
		);
	};
	
	//Sets some keyboard events (some controllers can also fire keyboard events):
	CB_Keyboard.onKeyDown
	(
		function(e, keyCode)
		{
			//After pressing the ESC key, ends the game:
			if (keyCode === CB_Keyboard.keys.ESC[0]) { gameEnd("Game aborted"); }
		}
	);
	
	//Starts the game engine module:
	CB_GEM.begin
	(
		//onStart:
		function(graphicSpritesSceneObject, CB_CanvasObject, CB_CanvasObjectBuffer, FPSSprites) //'FPSSprites' contains the 'CB_GraphicSprites.SPRITES_OBJECT' object used to display the FPS counter.
		{
			FPSSprites.setDisabled(false); //Set to true to hide FPS counter.
			FPSSprites.getCurrent().data.fontSize = "18px"; //Sets the font size for the FPS counter.
			FPSSprites.getCurrent().data.style = "#ff0000"; //Sets the font colour for the FPS counter.
			
			CB_Elements.hideById("loading"); //Hides the loading message.
			CB_Elements.showById("start_button"); //Shows the start button.
		},
		
		//onError:
		function(error) { CB_console("Error: " + error); }
	);
}


//Starts the game:
function gameStart()
{
	if (CB_GEM.data.gameStarted) { return; }
	
	//Hides the start button:
	CB_Elements.hideById("start_button");
	
	//Removes all balloons from the 'CB_GraphicSpritesScene' object:
	CB_GEM.graphicSpritesSceneObject.forEach
	(
		function(graphicSpritesObject) //functionEach.
		{
			if (graphicSpritesObject.spritesGroup.data.isBalloon)
			{
				CB_GEM.graphicSpritesSceneObject.removeById(graphicSpritesObject.id);
			}
		},
		false, //orderedByZIndex.
		0, //delayBetweenEach.
		CB_Arrays.copy(CB_GEM.graphicSpritesSceneObject.spritesGroups.items) //graphicSpritesObjects.
	);
	
	//Resets the variables:
	CB_GEM.data.balloonsIdCounter = 0;
	CB_GEM.data.balloonsCounter = 0;
	CB_GEM.data.balloonsPopped = 0;
	CB_GEM.data.score = 0;
	CB_GEM.data.balloonsAppearingMs = CB_GEM.data.balloonsAppearingMsInitial;
	
	//Prepares the sound effects and plays one of them (recommended to do this through a user-driven event):
	prepareSoundFx(); //Prepares sound effects to be used later.
	playSoundFx("start");	
	
	//Sets the game as started:
	CB_GEM.data.gameStarted = true; //When set to true, starts the game automatically as the game loops detect it.
}


//Ends the game:
function gameEnd(message)
{
	if (!CB_GEM.data.gameStarted) { return; }
		
	message = CB_trim(message);
	CB_GEM.data.gameStarted = false;
	CB_Elements.insertContentById("start_button", (message !== "" ? message + "<br />" : "") + "Start game!")
	CB_Elements.showById("start_button"); //Shows the start button again.
}


//Updates the information shown:
function updateInfo(graphicSpritesSceneObject, balloonsCounterMax)
{
	graphicSpritesSceneObject.getById("info").get(0).src =
		"Score: " + CB_GEM.data.score + "\n" +
		"Balloons popped: " + CB_GEM.data.balloonsPopped + "\n" +
		"Balloons shown: " + CB_GEM.data.balloonsCounter + " (max limit: " + balloonsCounterMax + ")";
}


//Prepares sound effects:
var sfx = null; //Global object to play the sounds.
var prepareSoundFxExecuted = false;
function prepareSoundFx()
{
	if (prepareSoundFxExecuted) { return; }
	
	var jsfxObject = CB_Speaker.getJsfxObject(); //Gets the 'jsfx' object.
	if (jsfxObject !== null)
	{
		//Defines the sound effects:
		var library =
		{
			"start":
				jsfx.Preset.Select,
			"balloon_explosion":
				jsfx.Preset.Explosion
		};

		//Loads the sound effects:
		sfx = CB_AudioDetector.isAPISupported("WAAPI") ? jsfxObject.Live(library) : jsfxObject.Sounds(library); //Uses AudioContext (Web Audio API) if available.
	}
	
	prepareSoundFxExecuted = true;
}


//Plays the desired sound effect (by its identifier):
function playSoundFx(id)
{
	if (!sfx || typeof(sfx[id]) !== "function") { return; }

	//Note: at least the first time, it is recommended to do it through a user-driven event (as "onClick", "onTouchStart", etc.) in order to maximize compatibility (as some clients could block sounds otherwise).
	sfx[id]();
}

Try this example

You can check the Guides & Tutorials category as well as the API documentation in the case you need more information.

Go back to Guides & Tutorials

« PrevReturnNext »
CrossBrowdy by Joan Alba Maldonado