CrossBrowdy - Examples

Advanced

Simple game engine

This is an example of a simple game engine (using the Graphic rendering engine module from the previous example):

index.html:

<!DOCTYPE html>
<html>
	<head>
		<!-- This file belongs to a CrossBrowdy.com example, made by Joan Alba Maldonado. Creative Commons Attribution 4.0 International License. -->
		<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" />
		<link rel="canonical" href="https://crossbrowdy.com/examples/advanced/simple_game_engine/try" />
		<title>Advanced: Simple game engine - 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: -->
		<!-- Note: it is recommended to download CrossBrowdy instead of hotlinking the online version. This is just for the example! -->
		<script src="https://crossbrowdy.com/CrossBrowdy/CrossBase/audiovisual/image/canvas/FlashCanvas/pro/bin/flashcanvas.js" type="text/javascript" language="javascript"></script><!-- FlashCanvas/ExplorerCanvas do not support lazy load. -->
		<!-- Loads CrossBrowdy.js (main file): -->
		<!-- Note: it is recommended to download CrossBrowdy instead of hotlinking the online version. This is just for the example! -->
		<script src="https://crossbrowdy.com/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>
		<span id="controls">
			<button onClick="gameLoopStartStop();">Stops/starts game loop</button><br />
			<button onClick="getGameData();">Gets game data</button><br />
			<button onClick="getGameData(true);">Gets game data (stringified)</button>
		</span>
		<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). -->
		<br />
		<!-- The "CB_console" element will be used automatically in the case that the client does not support console: -->
		<div id="CB_console" style="display:none; visibility:hidden; overflow:scroll;">
			<span style="font-weight:bold;">Console:</span><br />
		</div>
		<div id="crossbrowdy_info"><a href="https://crossbrowdy.com/examples/advanced/simple_game_engine" target="_blank">CrossBrowdy.com example</a></div>
	</body>
</html>

main.css:

/* This file belongs to a CrossBrowdy.com example, made by Joan Alba Maldonado. Creative Commons Attribution 4.0 International License. */

body { background-color:#aaddee; word-wrap:break-word; }
#crossbrowdy_info { position:fixed; bottom:2px; right:2px; }
#crossbrowdy_info a { color:#00aadd; }
#crossbrowdy_info a:hover { color:#0033aa; }
#CB_console { width:460px; height:100px; background-color:#aaaaaa; color:#ddddff; }
span { color:#aa0000; }
#my_canvas { position:absolute; left:0px; top:0px; }
#my_canvas_buffer { position:absolute; left:0px; top:0px; visibility:hidden; display:none; }
#controls { position:absolute; right:10px; top:10px; z-index:99; }

game_engine.js:

/* This file belongs to a CrossBrowdy.com example, made by Joan Alba Maldonado. Creative Commons Attribution 4.0 International License. */

//Defines the needed properties:
CB_GEM.REM = CB_GEM.REM || null; //It will store the CB_REM object (Graphic Rendering Engine Module object).
CB_GEM.REM_renderGraphicScene_data = null; //It will store the "data" parameter when calling the 'CB_GEM.REM#renderGraphicScene' method internally.
CB_GEM.REM_renderGraphicScene_useBuffer = null; //It will store the "useBuffer" parameter when calling the 'CB_GEM.REM#renderGraphicScene' method internally.
CB_GEM.graphicSpritesSceneObject = null; //It will store the CB_GraphicSpritesScene object.
CB_GEM.CB_CanvasObject = null; //It will store the CB_Canvas element (the main canvas and the one for the buffer can be internally-alternated automatically if desired).
CB_GEM.CB_CanvasObjectContext = null; //It will store the context of the CB_Canvas element.
CB_GEM.CB_CanvasObjectBuffer = null; //It will store the CB_Canvas element used as the buffer (the main canvas and the one for the buffer can be internally-alternated automatically if desired).
CB_GEM.CB_CanvasObjectBufferContext = null; //It will store the context of the CB_Canvas element used as the buffer.

//Sets the desired options:
CB_GEM.options = CB_GEM.options || {};
CB_GEM.options.LOOP_REFRESH_RATE = CB_GEM.options.LOOP_REFRESH_RATE || 16; //A refresh rate of 16 is about 60 FPS (Frames Per Second) when the cycles per loop is set to 1.
CB_GEM.options.RENDERING_CYCLES_PER_LOOP = CB_GEM.options.RENDERING_CYCLES_PER_LOOP || 1; //The number of rendering cycles per loop. It will affect the FPS.
CB_GEM.options.FPS_SPRITE_DATA = //The 'data' object used by the 'CB_GraphicSprites.SPRITE_OBJECT' object to display the FPS:
	CB_GEM.options.FPS_SPRITE_DATA ||
	{
		fontSize: "12px",
		fontFamily: "courier",
		style: "#aa5522",
		fontStyle: "normal",
		fontVariant: "normal",
		fontWeight: "bold"
	};
CB_GEM.options.canvasId = CB_GEM.options.canvasId || "my_canvas";
CB_GEM.options.canvasBufferId = CB_GEM.options.canvasBufferId || "my_canvas_buffer";
CB_GEM.options.contextMenuDisable = CB_GEM.options.contextMenuDisable === true || CB_GEM.options.contextMenuDisable === false ? CB_GEM.options.contextMenuDisable : true; //Disables the context menu (when pressing mouse's right button) by default.
CB_GEM.options.overwriteViewPort = CB_GEM.options.overwriteViewPort === true || CB_GEM.options.overwriteViewPort === false ? CB_GEM.options.overwriteViewPort : true; //Overwrites the view port ('viewport' meta-tag) by default.

//Sets some options:
var CB_OPTIONS = CB_OPTIONS || { CrossBrowdy: {} };
CB_OPTIONS.CrossBrowdy = CB_OPTIONS.CrossBrowdy || {};
CB_OPTIONS.CrossBrowdy.CB_console_ALLOW_ALERTS = false; //It will not show alerts in the browsers that doesn't have console.

//If desired, sets the needed options to force emulation:
if (CB_GEM.options.CANVAS_FORCED_EMULATION_METHOD) //Forces an emulation mode which can be 'SILVERLIGHT', 'FLASH', 'DHTML' or 'VML' (testing purposes). Use null or undefined to disable it.
{
	CB_OPTIONS.CrossBase = CB_OPTIONS.CrossBase || {};
	CB_OPTIONS.CrossBase.CB_Canvas_PREFERRED_EMULATION_METHODS = [ CB_GEM.options.CANVAS_FORCED_EMULATION_METHOD ];
	if (CB_GEM.options.CANVAS_FORCED_EMULATION_METHOD === "SILVERLIGHT") { CB_OPTIONS.CrossBase.SLCANVAS_LOAD = true; }
	else if (CB_GEM.options.CANVAS_FORCED_EMULATION_METHOD === "FLASH") { CB_OPTIONS.CrossBase.FLASHCANVAS_LOAD = true; } //Note: Flash emulation will not work if native canvas is supported.
	else if (CB_GEM.options.CANVAS_FORCED_EMULATION_METHOD === "DHTML") { CB_OPTIONS.CrossBase.CANBOX_LOAD = true; }
	else if (CB_GEM.options.CANVAS_FORCED_EMULATION_METHOD === "VML") { CB_OPTIONS.CrossBase.EXCANVAS_LOAD = CB_OPTIONS.CrossBase.CANVAS_TEXT_LOAD = true; }
}


//Main initialization function (internal usage only, it will be called automatically when the module loads and gets ready):
CB_GEM._init = function()
{
	//If we want to, tries to keeps the screen awake and prevents it from turning off (must be executed through a user-driven event as onClick, etc.):
	if (!CB_GEM.options.allowScreenSleep)
	{
		var keepAwakeEventExecuted = false;
		var keepAwakeEvent = function()
		{
			if (!keepAwakeEventExecuted)
			{
				keepAwakeEventExecuted = true;
				CB_Screen.keepAwake(function() { CB_console("[CB_GEM] Keep awake set successfully!"); }, function() { CB_console("[CB_GEM] Keep awake could not be set successfully!"); });
				CB_Events.remove(document, "click", keepAwakeEvent, true);
				CB_Events.remove(document, "touchstart", keepAwakeEvent, true);
			}
		};
		CB_Events.add(document, "click", keepAwakeEvent, true, true, true);
		CB_Events.add(document, "touchstart", keepAwakeEvent, true, true, true);
	}
};


//Sets the desired options (not given ones will keep their current value):
CB_GEM.setOptions = function(optionsObject)
{
	return CB_GEM.options = CB_combineArraysOrObjects
	(
		CB_GEM.options,
		{
			LOOP_REFRESH_RATE: 16, //A refresh rate of 16 is about 60 FPS (Frames Per Second) when the cycles per loop is set to 1. Default: 16.
			RENDERING_CYCLES_PER_LOOP: 1, //The number of rendering cycles per loop. It will affect the FPS. Default: 1.
			CANVAS_FORCED_EMULATION_METHOD: undefined, //Forces a canvas emulation mode which can be 'SILVERLIGHT', 'FLASH', 'DHTML' or 'VML' (testing purposes). Use null or undefined to disable it. Default: undefined.
			canvasId: "my_canvas", //Identifier for the canvas element. Default: 'my_canvas'.
			canvasBufferId: "my_canvas_buffer" //Identifier for the buffer canvas element. Default: 'my_canvas_buffer'.
		}
	);
}


//This function will be called when CrossBrowdy is ready:
CB_GEM.begin = function(onStart, onError, avoidLoopStart)
{
	//If desired, overwrites the view port ('viewport' meta-tag):
	if (CB_GEM.options.overwriteViewPort)
	{
		CB_console("[CB_GEM] Overwring/creating 'viewport' meta-tag...");
		//Changes the viewport:
		CB_Screen.setViewport
		(
			"device-width", //'width'. Optional.
			"device-height", //'height'. Optional.
			false, //'userScalable'. Optional.
			"1.0", //'initialScale'. Optional.
			"1.0", //'minimumScale'. Optional.
			"1.0", //'maximumScale'. Optional.
			"no" //'shrinkToFit'. Optional.
		);
	}

	//Function to execute when a canvas is created:
	var canvasLoaded = 0;
	var onLoadCanvas = function()
	{
		if (CB_GEM.DEBUG_MESSAGES) { CB_console("[CB_GEM] Canvas '" + this.getId() + "' loaded! Mode used: " + this.getMode()); }

		canvasLoaded++;

		//Gets the "context" object to start working with the canvas:
		var canvasContext = this.getContext();
		if (!canvasContext)
		{
			CB_console("[CB_GEM] ERROR: canvas context could not be obtained! Drawing cannot be performed.");
			if (typeof(onError) === "function") { onError.call(CB_GEM, "No canvas context found"); }
			return;
		}

		//Prevents canvas dragging and default touch actions:
		this.get().style.draggable = false;
		this.get().style.touchAction = "none";

		//Stores the canvas in the 'canvases' object:
		canvases[this.getId()] = this;

		//If both canvas (normal and buffer) have been created, proceeds with the rendering:
		if (canvasLoaded >= 2)
		{
			//If desired, disables the context menu for both canvases:
			if (CB_GEM.options.contextMenuDisable)
			{
				CB_console("[CB_GEM] Disabling context menu for different items...");
				CB_Elements.contextMenuDisable(); //Affects 'document' (whole document).
				CB_Elements.contextMenuDisable(canvases[CB_GEM.options.canvasId].get());
				CB_Elements.contextMenuDisable(canvases[CB_GEM.options.canvasBufferId].get());
			}
			
			//Disables some undesired effects/behaviour:
			if (document.body && document.body.style)
			{
				document.body.style.zoom = 1; //Disables zoom under some circumstances for some web clients.
				document.body.style.touchAction = document.body.style.msTouchAction = "none"; //Prevents default touch actions for some web clients.
			}

			//Stores both canvases and their contexts:
			CB_GEM.CB_CanvasObject = canvases[CB_GEM.options.canvasId];
			CB_GEM.CB_CanvasObjectContext = CB_GEM.CB_CanvasObject.getContext();
			CB_GEM.CB_CanvasObjectBuffer = canvases[CB_GEM.options.canvasBufferId];
			CB_GEM.CB_CanvasObjectBufferContext = CB_GEM.CB_CanvasObjectBuffer.getContext();
			
			//When the screen changes its size or its orientation, both canvases will be re-adapted:
			var onResizeOrChangeOrientationTimeout = null;
			var onResizeOrChangeOrientation = function()
			{
				clearTimeout(onResizeOrChangeOrientationTimeout);
				onResizeOrChangeOrientationTimeout = setTimeout(CB_GEM.resizeCanvasesToScreenSize, 100); //NOTE: needs a delay as some clients on iOS update the screen size information in two or more steps (last step is the correct value).
			};
			CB_Screen.onResize(onResizeOrChangeOrientation);
			
			//Resizes the canvases (Firefox Android fix):
			onResizeOrChangeOrientation();

			//Clears both canvas:
			canvases[CB_GEM.options.canvasId].clear();
			canvases[CB_GEM.options.canvasBufferId].clear();
			
			//Disables anti-aliasing to avoid problems with adjacent sprites:
			canvases[CB_GEM.options.canvasId].disableAntiAliasing();
			canvases[CB_GEM.options.canvasBufferId].disableAntiAliasing();
			
			//Creates the sprites groups:
			CB_GEM.graphicSpritesSceneObject = CB_GEM._createSpritesGroups();

			//Caches all needed images (performance purposes) and starts rendering sprites groups when all are loaded:
			CB_GEM.REM = new CB_REM();
			
			CB_GEM.REM.cacheImages
			(
				CB_GEM.graphicSpritesSceneObject, //CB_GraphicSpritesSceneObject.
				undefined, //reload.
				function(imagesLoaded) //onLoad.
				{
					//Sets the current time as the start time to start counting the FPS (erased each second automatically):
					CB_GEM.REM._startTimeFPS = CB_Device.getTiming();

					//Gets the 'CB_GraphicSprites.SPRITES_OBJECT' object used to display the FPS counter:
					var FPSSprites = CB_GEM.graphicSpritesSceneObject.getById("fps_group").getById("fps");

					//Calls the 'onStart' callback (if any):
					if (typeof(onStart) === "function") { onStart.call(CB_GEM, CB_GEM.graphicSpritesSceneObject, canvases[CB_GEM.options.canvasId], canvases[CB_GEM.options.canvasBufferId], FPSSprites); }
					
					//Show the FPS (Frames Per Second) every time there is a new value:
					CB_GEM.REM.onUpdatedFPS(function(FPS) { FPSSprites.src = "FPS: " + FPS; });
					
					//Creates the empty callbacks if they do not exist:
					if (typeof(CB_GEM.onLoopStart) !== "function") { CB_GEM.onLoopStart = function() {}; }
					if (typeof(CB_GEM.onLoopEnd) !== "function") { CB_GEM.onLoopEnd = function() {}; }

					//If we do not want to avoid starting the game loop, starts it:
					if (!avoidLoopStart) { CB_GEM.loopStart(); }
				}
			);
		}
	};
	
	//Creates the canvases:
	var canvases = {};
	canvases[CB_GEM.options.canvasId] = new CB_Canvas
	(
		CB_GEM.options.canvasId, //canvasId. Unique required parameter.
		"2d", //contextType. NOTE: some emulation methods only support "2d". Default: "2d".
		CB_Screen.getWindowWidth(), //canvasWidth. Use 'CB_Screen.getWindowWidth()' for complete width. Default: CB_Canvas.WIDTH_DEFAULT.
		CB_Screen.getWindowHeight(), //canvasHeight. Use 'CB_Screen.getWindowHeight()' for complete height. Default: CB_Canvas.HEIGHT_DEFAULT.
		onLoadCanvas, //onLoad.
		function(error) { CB_console("[CB_GEM] Canvas object problem! Error: " + error); if (typeof(onError) === "function") { onError.call(CB_GEM, error); } }, //onError.
		undefined, undefined, !!CB_GEM.options.CANVAS_FORCED_EMULATION_METHOD, !!CB_GEM.options.CANVAS_FORCED_EMULATION_METHOD //Forces emulation method.
	);
	canvases[CB_GEM.options.canvasBufferId] = new CB_Canvas
	(
		CB_GEM.options.canvasBufferId, //canvasId. Unique required parameter.
		"2d", //contextType. NOTE: some emulation methods only support "2d". Default: "2d".
		CB_Screen.getWindowWidth(), //canvasWidth. Use 'CB_Screen.getWindowWidth()' for complete width. Default: CB_Canvas.WIDTH_DEFAULT.
		CB_Screen.getWindowHeight(), //canvasHeight. Use 'CB_Screen.getWindowHeight()' for complete height. Default: CB_Canvas.HEIGHT_DEFAULT.
		onLoadCanvas, //onLoad.
		function(error) { CB_console("[CB_GEM] Canvas object problem! Error: " + error); if (typeof(onError) === "function") { onError.call(CB_GEM, error); } }, //onError.
		undefined, undefined, !!CB_GEM.options.CANVAS_FORCED_EMULATION_METHOD, !!CB_GEM.options.CANVAS_FORCED_EMULATION_METHOD //Forces emulation method.
	);
}


//Resizes both canvases (main and buffer one) to adapt them to the screen size:
CB_GEM.resizeCanvasesToScreenSize = function(avoidCallingOnResize, addedPixels)
{
	addedPixels = addedPixels || 0;
	
	//Resizes the canvas:
	CB_GEM.CB_CanvasObject.setWidth(CB_Screen.getWindowWidth() + addedPixels);
	CB_GEM.CB_CanvasObject.setHeight(CB_Screen.getWindowHeight());
	CB_GEM.CB_CanvasObject.clear();
	CB_GEM.CB_CanvasObject.disableAntiAliasing();
	
	//Resizes the buffer canvas:
	CB_GEM.CB_CanvasObjectBuffer.setWidth(CB_Screen.getWindowWidth() + addedPixels);
	CB_GEM.CB_CanvasObjectBuffer.setHeight(CB_Screen.getWindowHeight());
	CB_GEM.CB_CanvasObjectBuffer.clear();
	CB_GEM.CB_CanvasObjectBuffer.disableAntiAliasing();
	
	//Calls the 'onResize' event set, if any:
	if (!avoidCallingOnResize && typeof(CB_GEM.onResize) === "function")
	{
		CB_GEM.onResize.call(CB_GEM, CB_GEM.graphicSpritesSceneObject, CB_GEM.REM_renderGraphicScene_data, CB_GEM.CB_CanvasObject, CB_GEM.CB_CanvasObjectBuffer);
	}
}


//Creates the sprites groups:
CB_GEM._createSpritesGroups = function()
{
	//Sanitizes sprites group data:
	CB_GEM.spritesGroupsData = CB_GEM.spritesGroupsData || { id: "my_sprites_groups_1", srcWidth: 40, srcHeight: 40, data: { skipAfter: null, duration: null, timeResetAndEnableAfter: null, loop: true, clearPreviousFirst: false } };
	CB_GEM.spritesGroupsData.spritesGroups = CB_GEM.spritesGroupsData.spritesGroups || [];
	
	//Adds the sprites group to show the FPS (Frames Per Second):
	CB_GEM.spritesGroupsData.spritesGroups[CB_GEM.spritesGroupsData.spritesGroups.length] =
		//'fps_group' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
		{
			id: "fps_group",
			srcType: CB_GraphicSprites.SRC_TYPES.TEXT,
			zIndex: Number.MAX_SAFE_INTEGER || 999999999999,
			sprites:
			[
				//'fps' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
				{
					id: "fps",
					src: "FPS: Calculating...",
					left: 0,
					top: 0,
					data: CB_GEM.options.FPS_SPRITE_DATA
				}
			]
		};
	
	//Creates the graphic sprites object:
	return new CB_GraphicSpritesScene(CB_GEM.spritesGroupsData);	
}


//Processes the sprites groups:
CB_GEM._processSpritesGroups_timer = null;
CB_GEM._processSpritesGroups = function(expectedCallingTime) //The "expectedCallingTime" parameter is received from the callback used in the 'CB_symmetricCall' function (first time will be zero). Also passed to the callbacks.
{
	if (CB_GEM.stopped) { return; }

	for (var x = 0; x < CB_GEM.options.RENDERING_CYCLES_PER_LOOP; x++)
	{
		//Executes the 'onLoopStart' callback (if any):
		var skipRendering = CB_GEM.onLoopStart.call(CB_GEM, CB_GEM.graphicSpritesSceneObject, CB_GEM.REM_renderGraphicScene_data, expectedCallingTime);
		
		if (skipRendering !== false)
		{
			//Renders the scene:
			CB_GEM.REM.renderGraphicScene
			(
				CB_GEM.graphicSpritesSceneObject, //graphicSpritesSceneObject. Mandatory. The 'CB_GraphicSpritesScene' object to render.
				CB_GEM.REM_renderGraphicScene_data, //data.
				CB_GEM.REM_renderGraphicScene_useBuffer, //useBuffer. Optional. Default: false. Defines whether to use canvas buffer.
				true //alternateBuffer. Optional. Default: false. Defines whether to alternate visibility between canvas or not (if not, it will copy the buffer canvas content to the visible canvas always).
			);

			//Executes the 'onLoopEnd' callback (if any):
			CB_GEM.onLoopEnd.call(CB_GEM, CB_GEM.graphicSpritesSceneObject, CB_GEM.REM_renderGraphicScene_data, expectedCallingTime);
		}
	}

	//Calls itself again:
	CB_GEM._processSpritesGroups_timer = CB_symmetricCall //Note: we could also use 'requestAnimationFrame'.
	(
		function(expectedCallingTime) { CB_GEM._processSpritesGroups(expectedCallingTime); },
		CB_GEM.options.LOOP_REFRESH_RATE,
		"_processSpritesGroupsTimerId"
	);
}


//Starts the game loop:
CB_GEM.loopStart = function()
{
	//Defines the "data" used by the rendering engine:
	CB_GEM.REM_renderGraphicScene_data =
	{
		"CB_CanvasObject": CB_GEM.CB_CanvasObject, //Main canvas. Mandatory.
		"CB_CanvasObjectContext": CB_GEM.CB_CanvasObjectContext, //Context of the main canvas. Mandatory.
		"CB_CanvasObjectBuffer": CB_GEM.CB_CanvasObjectBuffer, //Buffer canvas. Mandatory if "useBuffer" is set to true.
		"CB_CanvasObjectBufferContext": CB_GEM.CB_CanvasObjectBufferContext //Context of the buffer canvas. Optional. Mandatory if "useBuffer" is set to true.
	};

	//Defines whether to use buffer or not:
	CB_GEM.REM_renderGraphicScene_useBuffer = !CB_GEM.options.CANVAS_FORCED_EMULATION_METHOD && CB_REM.BUFFER_RECOMMENDED;

	//Starts processing the sprites groups:
	CB_GEM.stopped = false;
	if (CB_GEM.DEBUG_MESSAGES) { CB_console("[CB_GEM] Starts processing graphic sprites scene ('CB_GraphicSpritesScene' object) constantly..."); }
	CB_GEM._processSpritesGroups(0);
}


//Stops the game loop:
CB_GEM.loopStop = function()
{
	//Stops processing the sprites groups:
	clearTimeout(CB_GEM._processSpritesGroups_timer);
	CB_GEM.stopped = true;
}


//Returns the current data of the game engine:
CB_GEM.getData_object = {};
CB_GEM.getData = function(stringify, avoidRenderingEngineData, avoidGraphicSpritesSceneData, propertiesToKeepGraphicSpritesSceneObject) //Note: to save/restore we should never forget the audio status.
{
	//Updates the internal object with the data:
	CB_GEM.getData_object.data = CB_GEM.data;
	CB_GEM.getData_object.REM_data = avoidRenderingEngineData ? null : CB_GEM.REM.getData(false, CB_GEM.graphicSpritesSceneObject, avoidGraphicSpritesSceneData, propertiesToKeepGraphicSpritesSceneObject);
	
	//Returns the data (stringified if desired):
	return stringify ? JSON.stringify(CB_GEM.getData_object) : CB_GEM.getData_object; //It could be a good idea to stringify functions ('JSON.stringify' method does not).
}


//Restores the given data into the game engine:
CB_GEM.setData = function(getDataObject, graphicSpritesSceneObject, avoidGraphicSpritesSceneData)
{
	if (typeof(getDataObject) === "undefined" || getDataObject === null || typeof(getDataObject) !== "object") { return false; }
	
	//Restores the given data:
	CB_GEM.data = getDataObject.data;
	CB_GEM.REM.setData(getDataObject.REM_data, graphicSpritesSceneObject);
	
	return true;
}


//TODO: A 'reset' method could be useful sometimes.


if (CB_GEM.DEBUG_MESSAGES) { CB_console("[CB_GEM] game_engine.js inserted in the document"); }

game_engine_module.js:

/* This file belongs to a CrossBrowdy.com example, made by Joan Alba Maldonado. Creative Commons Attribution 4.0 International License. */

//Constants and variables:
var CB_GEM = CB_this.CB_GEM || {}; //Game Engine Module object.
CB_GEM.PATH = CB_this.CB_GEM_PATH || ""; //Path to the game rendering engine module.
CB_GEM.REM_PATH = CB_GEM.REM_PATH || "../graphic_rendering_engine_files/"; //Path to the graphic rendering engine module.
CB_GEM.DEBUG_MESSAGES = !!CB_this.CB_GEM_DEBUG_MESSAGES; //Sets whether to show debug messages or not.


//Module basic configuration:
CB_Modules.modules["GAME_ENGINE_MODULE"] =
{
	//Name of the module:
	"name" : "GAME_ENGINE_MODULE",

	//Status (UNKNOWN, UNLOADED, LOADING, LOADED, READY or FAILED):
	"status" : CB_Modules.STATUSES.UNLOADED,

	//Function to call as soon as the module is called (before loading its files):
	"onCall" :
		function(scriptPathGiven)
		{
			//Stores the desired options for the rendering engine:
			CB_this.CB_REM_DEBUG_MESSAGES = CB_GEM.DEBUG_MESSAGES;
			CB_this.CB_REM_PATH = CB_GEM.REM_PATH;

			//If a 'CB_GEM.PATH' is given, updates the needed files and modules to search for them in the desired path:
			if (CB_GEM.PATH)
			{
				CB_Modules.modules["GAME_ENGINE_MODULE"].neededFiles = {};
				CB_Modules.modules["GAME_ENGINE_MODULE"].neededFiles[CB_GEM.PATH + "game_engine.js"] = { load: true, mandatory: true, absolutePath: true };
			}
			
			//If a 'CB_GEM.REM_PATH' is given, updates the needed files and modules to search for them in the desired path:
			if (CB_GEM.REM_PATH)
			{			
				//Adds the rendering engine module to the game engine module:
				var CB_REM_MODULE_NEEDED_MODULES = {};
				CB_REM_MODULE_NEEDED_MODULES[CB_GEM.REM_PATH + "rendering_engine_module.js"] = { load: true, mandatory: true, absolutePath: true };
				CB_Modules.modules["GAME_ENGINE_MODULE"].neededModules = null;
				CB_Modules.addNeededModule("GAME_ENGINE_MODULE", "RENDERING_ENGINE_MODULE", CB_REM_MODULE_NEEDED_MODULES);
			}

			if (CB_GEM.DEBUG_MESSAGES) { CB_console("[CB_GEM] GAME_ENGINE_MODULE called"); }
			CB_Modules.setStatus("GAME_ENGINE_MODULE", CB_Modules.STATUSES.LOADED);
		},

	//Callback function to call when the module has been loaded successfully:
	"onLoad" :
		function(scriptPathGiven)
		{
			if (CB_GEM.DEBUG_MESSAGES) { CB_console("[CB_GEM] GAME_ENGINE_MODULE loaded"); }
			
			//Sets the module ready when CrossBase module is ready:
			var checkRenderingEngineReady =
				function()
				{
					//If CrossBase module is not ready yet:
					if (!CB_Modules.modules["RENDERING_ENGINE_MODULE"] || CB_Modules.modules["RENDERING_ENGINE_MODULE"].status !== CB_Modules.STATUSES.READY)
					{
						return setTimeout(checkRenderingEngineReady, 1); //Calls this checking function again.
					}
					//...otherwise, if CrossBase module is ready, proceeds:
					else
					{
						CB_GEM._init(); //Calls initialization function.
						
						//Applies the options set by the user (if any):
						CB_applyOptions("CrossBrowdy");
						CB_applyOptions("CrossBase");
						
						CB_Modules.setStatus("GAME_ENGINE_MODULE", CB_Modules.STATUSES.READY); //Sets the GAME_ENGINE_MODULE as ready.
					}
				};
			checkRenderingEngineReady();
		},

	//Callback function to call when the module is ready:
	"onReady" :
		function(scriptPathGiven)
		{
			if (CB_GEM.DEBUG_MESSAGES) { CB_console("[CB_GEM] GAME_ENGINE_MODULE ready"); }
		},

	//Needed files:
	"neededFiles" :
		{
			//Filepaths:
			"game_engine.js" : { load: true, mandatory: true, absolutePath: true } //Needs to be loaded. Mandatory. Relative path.
		},

	//Needed modules:
	"neededModules" :
		[
			{
				"name" : "RENDERING_ENGINE_MODULE",
				"neededFiles" : { "rendering_engine_module.js" : { load: true, mandatory: true, absolutePath: true } }
			}
		],

	//Credits:
	"credits" : "[CB] - GAME_ENGINE_MODULE by Joan Alba Maldonado" //Credits will be shown in the console when loading.
};


if (CB_GEM.DEBUG_MESSAGES) { CB_console("[CB_GEM] game_engine_module.js (GAME_ENGINE_MODULE file) inserted in the document"); }

main.js:

/* This file belongs to a CrossBrowdy.com example, made by Joan Alba Maldonado. Creative Commons Attribution 4.0 International License. */

//Defines whether to shows debug messages or not:
var CB_GEM_DEBUG_MESSAGES = true;

//Adds the game engine module to CrossBrowdy:
CB_Modules.addNeededModule(CB_NAME, "GAME_ENGINE_MODULE", { "game_engine_module.js" : { load: true, mandatory: true, absolutePath: true } });


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 engine example...");
	
	//Sets the desired sprites scene data (can be modified dynamically):
	var pandaLeftDefault = 100;
	var pandaTopDefault = 100;
	var pandaWidthDefault = 100;
	var pandaHeightDefault = 100;
	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: { loop: 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:
		[
			//'panda_sprites' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
			{
				
				id: "panda_sprites", //Identifier of the sprites group (also used for the 'CB_GraphicSprites' object). Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
				left: pandaTopDefault, //Left (horizontal) position in the destiny (inside the sprites group). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.LEFT_DEFAULT.
				top: pandaTopDefault, //Top (vertical) position in the destiny (inside the sprites group). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.TOP_DEFAULT.
				width: pandaWidthDefault, //Width of the destiny. Unit agnostic (only numeric values allowed). Default: this.parent.width || CB_GraphicSprites.WIDTH_DEFAULT.
				height: pandaHeightDefault, //Height of the destiny. Unit agnostic (only numeric values allowed). Default: this.parent.height || CB_GraphicSprites.HEIGHT_DEFAULT.
				//Object with any additional data desired which can be any kind:
				//NOTE: it will always have a "that" property pointing to the 'CB_GraphicSprites.SPRITES_OBJECT' object where it belongs to and a function in its "getThis" property returning the same value (added automatically).
				data: { skipAfter: 500, positionAbsolute: true }, //Object with any additional data desired which can be any kind. Default: CB_combineJSON(this.parent.data, this.data) || this.parent.data || { 'that' : CB_GraphicSprites.SPRITES_OBJECT, 'getThis' = function() { return this.that; } }.
				//Numeric array containing 'CB_GraphicSprites.SPRITE_OBJECT' objects with all the sprites that will be used:
				sprites:
				[
					//'panda_sprites_sprite_1' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "panda_sprites_sprite_1", //Identifier for the sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
						src: "img/panda_1.gif" //Source of origin. Can be a path or identifier of an image, text, bitmap, 3D object, etc. Optional but recommended. Default: this.parent.src || "".
					},
					//'panda_sprites_sprite_2' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "panda_sprites_sprite_2", //Identifier for the sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
						src: "img/panda_2.gif" //Source of origin. Can be a path or identifier of an image, text, bitmap, 3D object, etc. Optional but recommended. Default: this.parent.src || "".
					}
				]
			},
			//'bird_sprites' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
			{
				id: "bird_sprites", //Identifier of the sprites group (also used for the 'CB_GraphicSprites' object). Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
				src: "img/bird_sprites.gif", //Source of origin. Can be a path or identifier of an image, text, bitmap, 3D object, etc. Optional but recommended. Default: this.parent.src || "".
				srcWidth: 38, //Width of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcWidth || CB_GraphicSprites.WIDTH_SOURCE_DEFAULT.
				srcHeight: 36, //Height of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcHeight || CB_GraphicSprites.HEIGHT_SOURCE_DEFAULT.
				left: 300, //Left (horizontal) position in the destiny (inside the sprites group). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.LEFT_DEFAULT.
				width: 190, //Width of the destiny. Unit agnostic (only numeric values allowed). Default: this.parent.width || CB_GraphicSprites.WIDTH_DEFAULT.
				height: 160, //Height of the destiny. Unit agnostic (only numeric values allowed). Default: this.parent.height || CB_GraphicSprites.HEIGHT_DEFAULT.
				//Object with any additional data desired which can be any kind:
				//NOTE: it will always have a "that" property pointing to the 'CB_GraphicSprites.SPRITES_OBJECT' object where it belongs to and a function in its "getThis" property returning the same value (added automatically).
				data: { skipAfter: 600, clearPreviousFirst: true, onlyUseInMap: false /* Set to true to only display in maps */ }, //Object with any additional data desired which can be any kind. Default: CB_combineJSON(this.parent.data, this.data) || this.parent.data || { 'that' : CB_GraphicSprites.SPRITES_OBJECT, 'getThis' = function() { return this.that; } }.
				//Numeric array containing 'CB_GraphicSprites.SPRITE_OBJECT' objects with all the sprites that will be used:
				sprites:
				[
					//'bird_sprite_1' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "bird_sprite_1", //Identifier for the sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
						//Numeric array containing 'CB_GraphicSprites.SUBSPRITE_OBJECT' objects with the sub-sprites that this sprite uses:
						subSprites:
						[
							//'bird_sprite_1_subsprite_1' ('CB_GraphicSprites.SUBSPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the parent sprite:
							{
								id: "bird_sprite_1_subsprite_1", //Identifier for the sub-sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
								src: "img/sol.gif",
								srcLeft: 0, //Left (horizontal) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcLeft || CB_GraphicSprites.LEFT_SOURCE_DEFAULT.
								srcTop: 0, //Top (vertical) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcTop || CB_GraphicSprites.TOP_SOURCE_DEFAULT.
								srcWidth: 80, //Width of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcWidth || CB_GraphicSprites.WIDTH_SOURCE_DEFAULT.
								srcHeight: 80, //Height of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcHeight || CB_GraphicSprites.HEIGHT_SOURCE_DEFAULT.
								left: 20, //Left (horizontal) position in the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.LEFT_DEFAULT.
								top: 170, //Top (vertical) position in the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.TOP_DEFAULT.
								width: 40, //Width of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.width || CB_GraphicSprites.WIDTH_DEFAULT.
								height: 40, //Height of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.height || CB_GraphicSprites.HEIGHT_DEFAULT.
								data: { duration: 200, timeResetAndEnableAfter: 200 },
								zIndex: 2
							},
							//'bird_sprite_1_subsprite_2' ('CB_GraphicSprites.SUBSPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the parent sprite:
							{
								id: "bird_sprite_1_subsprite_2", //Identifier for the sub-sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
								src: "img/seta.gif",
								srcLeft: 0, //Left (horizontal) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcLeft || CB_GraphicSprites.LEFT_SOURCE_DEFAULT.
								srcTop: 0, //Top (vertical) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcTop || CB_GraphicSprites.TOP_SOURCE_DEFAULT.
								srcWidth: 40, //Width of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcWidth || CB_GraphicSprites.WIDTH_SOURCE_DEFAULT.
								srcHeight: 40, //Height of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcHeight || CB_GraphicSprites.HEIGHT_SOURCE_DEFAULT.
								top: 200, //Top (vertical) position in the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.TOP_DEFAULT.
								width: 12, //Width of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.width || CB_GraphicSprites.WIDTH_DEFAULT.
								height: 12, //Height of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.height || CB_GraphicSprites.HEIGHT_DEFAULT.
								data:
								{
									duration: null
								}
							}
						]
					},
					//'bird_sprite_2' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "bird_sprite_2", //Identifier for the sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
						srcLeft: 38, //Left (horizontal) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcLeft || CB_GraphicSprites.LEFT_SOURCE_DEFAULT.
						//Numeric array containing 'CB_GraphicSprites.SUBSPRITE_OBJECT' objects with the sub-sprites that this sprite uses:
						subSprites:
						[
							//'bird_sprite_2_subsprite_1' ('CB_GraphicSprites.SUBSPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the parent sprite:
							{
								id: "bird_sprite_2_subsprite_1", //Identifier for the sub-sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
								src: "img/sol.gif",
								srcLeft: 0, //Left (horizontal) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcLeft || CB_GraphicSprites.LEFT_SOURCE_DEFAULT.
								srcTop: 0, //Top (vertical) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcTop || CB_GraphicSprites.TOP_SOURCE_DEFAULT.
								srcWidth: 80, //Width of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcWidth || CB_GraphicSprites.WIDTH_SOURCE_DEFAULT.
								srcHeight: 80, //Height of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcHeight || CB_GraphicSprites.HEIGHT_SOURCE_DEFAULT.
								left: 20, //Left (horizontal) position in the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.LEFT_DEFAULT.
								top: 170, //Top (vertical) position in the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.TOP_DEFAULT.
								width: 40, //Width of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.width || CB_GraphicSprites.WIDTH_DEFAULT.
								height: 40, //Height of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.height || CB_GraphicSprites.HEIGHT_DEFAULT.
								data: { duration: 200, timeResetAndEnableAfter: 200 },
								zIndex: 2
							},
							//'bird_sprite_2_subsprite_1' ('CB_GraphicSprites.SUBSPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the parent sprite:
							{
								id: "bird_sprite_2_subsprite_2", //Identifier for the sub-sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
								src: "img/seta.gif",
								srcLeft: 0, //Left (horizontal) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcLeft || CB_GraphicSprites.LEFT_SOURCE_DEFAULT.
								srcTop: 0, //Top (vertical) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcTop || CB_GraphicSprites.TOP_SOURCE_DEFAULT.
								srcWidth: 40, //Width of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcWidth || CB_GraphicSprites.WIDTH_SOURCE_DEFAULT.
								srcHeight: 40, //Height of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcHeight || CB_GraphicSprites.HEIGHT_SOURCE_DEFAULT.
								top: 200, //Top (vertical) position in the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.TOP_DEFAULT.
								width: 12, //Width of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.width || CB_GraphicSprites.WIDTH_DEFAULT.
								height: 12, //Height of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.height || CB_GraphicSprites.HEIGHT_DEFAULT.
								data: { duration: null }
							}
						]
					}
				 ]
			}
		]
	};

	//Sets the desired options for the game engine module:
	CB_GEM.setOptions
	(
		{
			LOOP_REFRESH_RATE: 16, //A refresh rate of 16 is about 60 FPS (Frames Per Second) when the cycles per loop is set to 1. Default: 16.
			RENDERING_CYCLES_PER_LOOP: 1, //The number of rendering cycles per loop. It will affect the FPS.
			CANVAS_FORCED_EMULATION_METHOD: undefined, //Forces a canvas emulation mode which can be 'SILVERLIGHT', 'FLASH', 'DHTML' or 'VML' (testing purposes). Use null or undefined to disable it. Default: undefined.
			canvasId: "my_canvas", //Identifier for the canvas element. Default: 'my_canvas'.
			canvasBufferId: "my_canvas_buffer" //Identifier for the buffer canvas element. Default: 'my_canvas_buffer'.
		}
	);
	
	//Defines the callbacks for the game loop:
	CB_GEM.data = //Data stored in the game engine module (can be exported to save the game status):
	{
		pandaLeft: pandaLeftDefault,
		pandaTop: pandaTopDefault,
		pandaLeftPrevious: 0,
		pandaTopPrevious: 0,
		pandaWidth: pandaWidthDefault,
		pandaHeight: pandaHeightDefault,
		pixelsMovement: 5
	};
	var moving = false;
	var panda = null;
	CB_GEM.onLoopStart = function(graphicSpritesSceneObject, CB_REM_dataObject, expectedCallingTime) //When the game loop starts, before rendering the graphics (if it returns false, it will skip rendering in this loop):
	{
		//Moves the panda according to the input received through the keyboard (and controllers that fire keyboard events):
		if (CB_Keyboard.isKeyDown(CB_Keyboard.keys.UP)) { CB_GEM.data.pandaTop -= CB_GEM.data.pixelsMovement; }
		if (CB_Keyboard.isKeyDown(CB_Keyboard.keys.DOWN)) { CB_GEM.data.pandaTop += CB_GEM.data.pixelsMovement; }
		if (CB_Keyboard.isKeyDown(CB_Keyboard.keys.LEFT)) { CB_GEM.data.pandaLeft -= CB_GEM.data.pixelsMovement; }
		if (CB_Keyboard.isKeyDown(CB_Keyboard.keys.RIGHT)) { CB_GEM.data.pandaLeft += CB_GEM.data.pixelsMovement; }
		
		panda = CB_GEM.graphicSpritesSceneObject.getById("panda_sprites");
		
		if (CB_GEM.data.pandaLeft < 0) { CB_GEM.data.pandaLeft = 0; }
		else if (CB_GEM.data.pandaLeft > CB_GEM.CB_CanvasObject.getWidth() - panda.getCurrent().width) { CB_GEM.data.pandaLeft = CB_GEM.CB_CanvasObject.getWidth() - panda.getCurrent().width; }
		if (CB_GEM.data.pandaTop < 0) { CB_GEM.data.pandaTop = 0; }
		else if (CB_GEM.data.pandaTop > CB_GEM.CB_CanvasObject.getHeight() - panda.getCurrent().height) { CB_GEM.data.pandaTop = CB_GEM.CB_CanvasObject.getHeight() - panda.getCurrent().height; }
		
		moving = false;
		if (CB_GEM.data.pandaLeft !== CB_GEM.data.pandaLeftPrevious) { panda.setPropertyCascade("left", CB_GEM.data.pandaLeft); moving = true; }
		if (CB_GEM.data.pandaTop !== CB_GEM.data.pandaTopPrevious) { panda.setPropertyCascade("top", CB_GEM.data.pandaTop); moving = true; }
		
		CB_GEM.data.pandaLeftPrevious = CB_GEM.data.pandaLeft;
		CB_GEM.data.pandaTopPrevious = CB_GEM.data.pandaTop;
		
		if (moving) { CB_GEM.data.pixelsMovement++ } //Accelerates.
		else { CB_GEM.data.pixelsMovement = 5; } //Restores normal speed.
	};
	
	CB_GEM.onLoopEnd = function(graphicSpritesSceneObject, CB_REM_dataObject, expectedCallingTime) //When the game loop ends, after rendering the graphics (not executed if the 'CB_GEM.onLoopStart' function returned false):
	{
		//Changes the panda size according to whether it is moving or not:
		if (moving)
		{
			CB_GEM.data.pandaWidth = panda.getCurrent().width - 1;
			CB_GEM.data.pandaHeight = panda.getCurrent().height - 1;
		}
		else
		{
			CB_GEM.data.pandaWidth = pandaWidthDefault;
			CB_GEM.data.pandaHeight = pandaHeightDefault;
		}
		
		panda.setPropertyCascade("width", CB_GEM.data.pandaWidth);
		panda.setPropertyCascade("height", CB_GEM.data.pandaHeight);
	};
	
	//Sets some keyboard events (some controllers can also fire keyboard events):
	CB_Keyboard.onKeyDown
	(
		function(e, keyCode)
		{
			//After pressing the ESC key, stops or starts the game loop (depending on its current status):
			if (keyCode === CB_Keyboard.keys.ESC[0]) { gameLoopStartStop(); }
			//...otherwise, after pressing the SPACEBAR key, it will show the current data in the console:
			else if (keyCode === CB_Keyboard.keys.SPACEBAR[0]) { getGameData(); } //Not stringified.
		}
	);
	
	//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.
		},
		
		//onError:
		function(error) { CB_console("Error: " + error); }
	);
}


//Starts the game loop if not started (is stopped) or stops it otherwise:
function gameLoopStartStop()
{
	if (CB_GEM.stopped) { CB_console("Starting game loop..."); CB_GEM.loopStart(); }
	else { CB_console("Stopping game loop..."); CB_GEM.loopStop(); }
}


//Function that prints the game data in the console:
function getGameData(stringified)
{
	CB_console("Showing current data (" + (stringified ? "stringified" : "object") + "):");
	var data = CB_GEM.getData(stringified);
	CB_console(data);
	alert(stringified ? data : JSON.stringify(data));
}

Additional files used (inside the "img" folder): bird_sprites.gif, panda_1.gif, panda_2.gif, seta.gif and sol.gif.

Try this example

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

All the examples together can be downloaded here.

Go back to Guides & Tutorials

Try this example












Share