Camouflage Dragon
Posted on 18/10/2016 by Shri
Do you remember a movie from the 80's called "Predator" with Arnold Schwarzenegger ? In that movie, there is an alien who can blend into the background like a cameleon. I wondered if it was possible to do this in Wade using a shader and an isometric object. This post shows you one way you could accomplish this effect.
READ MORE

Overview

At the end, this is what we're trying to make. Click a tile to fly the dragon around. Use the 'c' key to turn camouflage on and off. With camouflage on, the dragon will blend into the background tile it is over. Use the mouse wheel to zoom the board in or out.

Step 1 - Isometric Tiles and Dragon

Start by loading files and setting up global app variables using the wade load and init functions.

At the end of the init function, I'll call startGame(). This function in turn calls loadLevel(). In the loadLevel() function, I init the isometric plugin.

		wade.iso.init({numTiles: {x:xTileMax,z:zTileMax},movementDirection: 'diagonal',
		               terrainLayerId: self.BACKGROUND_LAYER, objectsLayerId: self.GAME_LAYER});
			

Add the terrain tiles, which for now will be all grass.

		this.loadTiles = function() {
		    console.log('world create tiles');
		    var tileData = {texture: './grassTile.png'};
		    for (var s=0; s < xTileMax; s++) {
			    for (var t=0; t< zTileMax; t++) {
				    wade.iso.setTile(s,t,tileData);
			    }	// end inner for
		    }	// end outer for
	        };	// end loadTiles
			

Create the isometric dragon character. The data for all the dragon's properties was preloaded from the dragonObj.json file. Note: when using isometric objects in wade, I have developed the habit of referring to the isometric behaviors as object.avatar. That way, when calling any of the built in isometric object calls it is always object.avatar.function(), ( i.e. dragon.avatar.setDestination() ).

		this.createDragon = function() {
		    var dragonData = dragonObjJson.data.dragonData;
		    var isoObj = wade.iso.createObject(dragonData, {x: 0, z: 0}, 
			                                   {name: 'dragon'}).getBehavior();
		    dragon = isoObj.owner;
		    dragon.avatar = isoObj;
	        };	// end createDragon
			

Lastly, setup some mouse handling code. If the player moves the mouse wheel, zoom the board in or out. If the player presses the mouse button then move the dragon to that location. The corollary touch events should also work.

Run the step1.html file and you should see a 10x10 grass tiled isometric board. On the bottom corner of the board, there should be a flying dragon character. Mouse down anywhere on the board to have the dragon fly there. Use the mouse wheel to zoom in or out. The relevant javascript code is in step1.js

Step 2 - Add a Shader for the Dragon

Next, create a simple shader and load it. I use the jquery.get() function to load in and then parse the shader string. Doing it this way, allows me to write the .fs (fragment shader) file just like any other javascript file with block and inline comments. I find this much easier to work with and the comments help me remember what the shader is doing.

		this.loadShaders = function() {
		    console.log('load Shaders');
		    // get shader source with jquery ajax
		    $.get('./shader2.fs',function(data) {
				fsString = data;
				fsString = fsString.replace(/(\/\*([\s\S]*?)\*\/)|(\/\/(.*)$)/gm, '');
		    });
	        };
			

After loading the shader, setup the dragon sprite to use it by adding the following to createDragon().

		dSprite = dragon.getSprite();
		dSprite.setPixelShader(fsString);
			

If you run the step2.html you should get the same thing as in step1. This is because the shader is using the default texture for the dragon image. The relevant javascript code is in step2.js The shader code is in shader2.fs.

Step 3 - Add Camouflage

To make the dragon blend into the background, I am going to use the built in OpenGL mix function. From the definition on the site shaderific: " The mix function returns the linear blend of x and y, i.e. the product of x and (1 - a) plus the product of y and a."

		outColor = mix(blackColor,whiteColor,alpha);
			

Using the above line of shader code as an example. The outputColor would be the linear blend of blackColor and whiteColor based on the value of alpha, If alpha were 0.0 outColor would be full black and if alpha were 1.0 outColor would be full white.

I am going to use mix, to get a linear blend of the default dragon sprite with a background texture based on a variable shared between the javascript app and the shader.

To do that, I'll first define a variable called blendFactor and then load a grassTexture file.

		var blendFactor = 0.9;
		...
		wade.loadImage('./grassTexture.png');
			

Next, I make some modifications to createDragon(). Add the otherTexture and mixAlpha as shader uniforms. Define them again as sprite variables.

		dSprite.setPixelShader(fsString,{ "otherTexture": "sampler2D", "mixAlpha": "float"});
		dSprite.mixAlpha = 0.0;
		dSprite.grassTexture = './grassTexture.png';
			

Then, add a mechanism for setting the mixAlpha and otherTexture variables using onKeyDown. Note, here is where I make use of the previously defined blend factor. From, trial and error, setting the alpha to 0.9 in the mix function gave what I thought was the best looking camouflage.

		this.onKeyDown = function(eventData) {
		    console.log(eventData.keyName);
		    this.setAlpha(eventData.keyName);
	        }	// end onKeyDown
	...
	...
	       this.setAlpha = function(key) {
		        dSprite.mixAlpha = 0.0;
		        switch (key) {
			        case 'g':
				        dSprite.mixAlpha = blendFactor;
				        dSprite.otherTexture = dSprite.grassTexture;
			        break;
			        default:
			        break;
		        }
	        };
			

Lastly, a few lines have to be added to the shader.

		vec4 oTexture = texture2D(otherTexture,uv,0.);
		...
		outColor = mix(texColor,oTexture,mixAlpha);
			

Run the step3.html Press the 'g' key to have the dragon blend into the background. Press any other key to return the dragon to its' default view. The relevant javascript code is in step3.js The shader code is in shader3.fs.

Step 4 - Automatic Blending

Now, it's just a matter of extending the blending method in the previous step to more terrain types. Load water and lava tiles and textures.

		wade.loadImage('./waterTile.png');
		wade.loadImage('./lavaTile.png');
		...
		wade.loadImage('./waterTexture.png');
		wade.loadImage('./lavaTexture.png');
			

Modify loadTiles() so terrain is assigned randomly

		var tileArray = ["./grassTile.png","./waterTile.png","./lavaTile.png"];
		...
		var index = Math.floor(Math.random()*tileArray.length);
		var tileData = {texture: tileArray[index]};
		wade.iso.setTile(s,t,tileData);
			

Extend the dragon sprite variables

		dSprite.setPixelShader(fsString,{ "otherTexture": "sampler2D", "mixAlpha": "float"});
		dSprite.mixAlpha = 0.0;
		dSprite.waterTexture = './waterTexture.png';
		dSprite.grassTexture = './grassTexture.png';
		dSprite.lavaTexture = './lavaTexture.png';
			

And critically, add an updateDragon() function. This function will fire every 100ms and check what tile type the dragon is over. If camouflage is on, then the mixAlpha variable is set to blendFactor and the otheTexture variable will be set to the texture type the dragon is flying over. The shader code is identical to the shader from the previous step.

		this.updateDragon = function() {
		    var worldCoords = dragon.getPosition();
		    var cellCoords = wade.iso.getFlatTileCoordinates(worldCoords.x, worldCoords.y);
		    var tileData = wade.iso.getTileData(cellCoords.x,cellCoords.z);
		    if (camouflaged) {
			    self.setAlpha(tileData.texture);
		    }
		    setTimeout(function() {self.updateDragon();},100);
	    };	// end updateDragon
			

Lastly, make sure you kick off the updateDragon() function in loadLevel()

		setTimeout(function() {self.updateDragon();},100);
			

Run the step4.html Press the 'c' key to turn camouflage on or off. If camouflage is on, the dragon will blend into the background it is flying over. With camouflage off, the dragon will appear in its' default colors. The relevant javascript code is in step4.js The shader code is in shader4.fs and is identical to the shader from step 3.

Conclusion

And there you have it, an isometric character that can automatically blend into the background.

The isometric tiles are freely downloadable from the clockwork chilli site. The dragon images are provided courtesy of this gentleman

A special thanks goes to Gio at ClockworkChilli for helping me to clean up the shader code.

Everything in the post is downloadable as a zip file

I hope you enjoyed this post - cheers - shri



This post was published here with permission from Shri of Ashatej Software.

Post a Comment
Add Attachment
Submit Comment
Please Login to Post a Comment