Added Walls and wall placement functionality

posted in Awoken for project Shapes TD
Published May 11, 2018
Advertisement

 

O.k, one more entry before the weekend,

Heading out for the weekend, and next week I'll be working on unirule, so this'll be it for a couple weeks.
Just like last week I find that as the week goes by my excitement builds for the project I'm working on.  Come Monday, I'll be thinking more about Shapes TD than Unirule, come next Friday the reverse will be true.

Wall placement has been added.
For those who want to give it a whirl:
- press 'w' to toggle wall placement on an off
- select a line ( it will turn yellow ), left click to create, right click to remove.

Update the following files
shapesTD.js

Spoiler


var camera, scene, renderer, keyboard, lightSource;
var mouseVector2, mouseRay;
var grid, gridTargetMesh, cells, walls;
var userTaskManager, gameParameters;


init();
animate();

function init() {

	camera = new THREE.PerspectiveCamera( 20, 1024/768 , 0.01, 30 );
	camera.position.set( 2 , 2 , 2 );
	camera.lookAt( new THREE.Vector3( 0 , 0 , 0 ) );

	//we're going to add a feature to the camera allowing us to toggle the screen size when the user presses 'f'
	camera.renderSizeToggle = false;

	// any visual content we want displayed on screen we'll add to the scene.
	scene = new THREE.Scene();

	renderer = new THREE.WebGLRenderer( { antialias: true } );
	renderer.setSize( 1024 , 768 );
	document.body.appendChild( renderer.domElement );

	lightSource = new THREE.DirectionalLight( 0xffffff, 1 );
	lightSource.position.set( 1 , 1 , 1 );
	scene.add( lightSource );	

	// mouse controls
	document.addEventListener( 'mousedown' , mouseControls.checkMouseDown , false );	

	// keyboard controls
	keyboard = {};
	window.onkeyup = function(e) { keyboard[e.keyCode] = false; }
	window.onkeydown = function(e) { keyboard[e.keyCode] = true; }
	
	// we're going to add a timestamp to the keyboard allowing us to limit how fast the keyfire method fires under
	// certain circumstances.
	keyboard.timeStamp = +new Date();

	/*
	* game content 
	*
	*	first we'll add all the features for a playable game, 
	* 	then we'll add an html overlay to act as our start screen.
	*/

	// PART B
	// - create object to keep track of game parameters
	// - create object to keep track of user selected tasks
	// - create wall Mesh

	// create game parameters
	gameParameters = generateGameContent.createGameParameters();

	// create userSelectionTaskManager
	userTaskManager = generateGameContent.createUserSelectionTaskManager();

	// create a wall mesh which will serve as our visual representation of all walls
	// to save on processing time, we'll treat all walls as a single mesh, therefore we'll 
	// 'buffer' into it all vertices and faces necessary.  Then when a user creates a wall
	// we'll just update the positions of the vertices as needed.
	// for now we'll allow 40 wall segments @ 4 vertices per wall and 2 faces.
	walls = generateGameContent.generateWallMesh();
	scene.add( walls );

	// PART A
	// - create visual grid
	// - create target mesh
	// - create cell array to reference between the visual grid and target mesh, 
	//	 it will also act as our pathfinding array later on.


	// generate a grid and add it to the scene.
	grid = generateGameContent.generateGrid( gameParameters.gridColumns , gameParameters.gridRows );
	scene.add( grid );

	// since the grid will act as our visual reprentation of the playing area, we'll need to know
	// which square in the grid is being selected.  For that we'll add a grid target mesh.
	gridTargetMesh = generateGameContent.generateGridTargetMesh( grid );
	scene.add( gridTargetMesh );

	// create cells to act as the go between grid and gridTargetMesh, 
	// and they will also be used later on for pathfinding.
	cells = generateGameContent.createCells( grid );

}

function animate() {

	requestAnimationFrame( animate );

	keyboardControls.checkKeysPressed( keyboard );

	renderer.render( scene, camera );

}

 

 

add the following to keyboardControls.js

Spoiler


// if the 'w' key is pressed, we'll allow the user to place walls
	// left click to make new wall
	// right click to remove wall

	if( keyboard["87"] ){

		var endTime = +new Date();

		if( ( endTime - keyboard.timeStamp ) > 250 ){

			// If the user presses the same button twice we will cancel the user selection
			if( userTaskManager.createWalls ){

				// reset colors of all previous lines that were selected
				for( var i=0, ii=cells.previousSelection.length; i<ii; i++ ){
					grid.geometry.colors[ cells.previousSelection[i] ].setRGB( 0.3 , 0.3 , 0.3 );
				}

				grid.geometry.colorsNeedUpdate = true;
				// reset previous selection of lines.
				cells.previousSelection = [];

				// we're going to remove this listener from the page as it's no longer needed.
				document.removeEventListener( 'mousemove' , mouseControls.checkMouseMove , false );

				userTaskManager.createWalls = false;
				

			} else {

				// we're going to add this listener to the page.
				document.addEventListener( 'mousemove' , mouseControls.checkMouseMove , false );

				userTaskManager.createWalls = true;

			} 

			// prevent the event from firing too fast again.
			keyboard.timeStamp = endTime;

		}
	}

 

 

update mouseControls.js

Spoiler


var mouseControls = mouseControls || {};

//create objects used to raycaste with a mouse click;
mouseVector2 = new THREE.Vector2();
mouseRay = new THREE.Raycaster();

mouseControls.checkMouseDown = function( event ){

	// if create walls has been selected and a wall location has been determined
	// then we'll assume the the vertices will be stored in cells.previousSelection

	if( userTaskManager.createWalls ){

		// first will see if cells.previousSelection is populated with any vertices
		if( cells.previousSelection.length > 0 ){

			// we're in luck
			// next we'll check if the wall already exists at this location
			if( walls.lookUpArray[ cells.previousSelection[0] ] ){

				// the wall already exists.  check if the user wants to delete the wall
				if( event.buttons == 1 ){

					// no further action required

				} else {

					// the user has right clicked on the wall, so we're going to remove it 
					// and add its vertice location back to the availability pool

					var indexToClear = walls.bufferLink[ cells.previousSelection[0] ];

					// reset vertices to 0,0,0
					walls.geometry.vertices[ indexToClear ].set( 0 , 0 , 0 );
					walls.geometry.vertices[ indexToClear+1 ].set( 0 , 0 , 0 );
					walls.geometry.vertices[ indexToClear+2 ].set( 0 , 0 , 0 );
					walls.geometry.vertices[ indexToClear+3 ].set( 0 , 0 , 0 );

					// add index position to the availability pool
					walls.availableIndexes.push( indexToClear );

					// set lookUpArray to false
					walls.lookUpArray[ cells.previousSelection[0] ] = false;

					// now that we've set the location of the vertices we'll need to update the geometry.
					// the faces will automatically shrink into place as each set of 2 faces is connected to
					// 4 vertices.
					walls.geometry.computeFaceNormals();
					walls.geometry.verticesNeedUpdate = true;

				}

			} else {

				// now we'll see if the walls bufferIndex has any more room
				if( walls.bufferIndex.vertices == walls.bufferIndex.maxVertices ){

					// the maximum number of walls has ben created, check to see if there
					// is any room in the availableIndexes array
					if( walls.availableIndexes.length == 0 ){

						// maximum walls created.
						// no further action required

					} else {

						// create a wall using an index that has been freed up.
						createWall( walls.availableIndexes[0] );

						// add an entry into buffer Link
						walls.bufferLink[ cells.previousSelection[0] ] = walls.availableIndexes[0];

						// remove available Index
						walls.availableIndexes.splice( 0 , 1 );						

					}

				} else {

					// create a wall using a new index
					createWall( walls.bufferIndex.vertices );

					// add an entry into buffer Link
					walls.bufferLink[ cells.previousSelection[0] ] = walls.bufferIndex.vertices;

					// lastly, update the buffer index
					walls.bufferIndex.vertices +=4;

				}
			}
		}


	} else {

		if( camera.renderSizeToggle ){
			mouseVector2.x = ( event.clientX / ( window.innerWidth / 2 ) ) - 1;
			mouseVector2.y = - ( event.clientY / ( window.innerHeight / 2 ) ) + 1;
		} else {
			mouseVector2.x = ( event.clientX / ( 8+( 1024 / 2 )) ) - 1;
			mouseVector2.y = - ( event.clientY / ( 8+( 768 / 2 )) ) + 1;
		}

		mouseRay.setFromCamera( mouseVector2 , camera );

		var intersect = mouseRay.intersectObject( gridTargetMesh );

		// check to see if we intersected the gridTargetMesh
		if( intersect.length > 0 ){

			// reset colors of all previous cells that were selected
			for( var i=0, ii=cells.previousSelection.length; i<ii; i++ ){
				grid.geometry.colors[ cells.previousSelection[i] ].setRGB( 0.3 , 0.3 , 0.3 );
			}

			// reset previous selection of cells.
			cells.previousSelection = [];

			// change the color of the square we've selected
			var face = intersect[ 0 ].face;
			var vectorList = cells.list[ face.index ].lineVertices;

			for( var i=0; i<vectorList.length; i++ ){
				grid.geometry.colors[ vectorList[i] ].setRGB( 0 , 0 , 1 );
				cells.previousSelection.push( vectorList[ i ] );
			}

			grid.geometry.colorsNeedUpdate = true;

		} else {

			// if we click outside the play area we'll reset the colors of the last selected square.
			for( var i=0, ii=cells.previousSelection.length; i<ii; i++ ){
				grid.geometry.colors[ cells.previousSelection[i] ].setRGB( 0.3 , 0.3 , 0.3 );
			}

			// reset previous selection of cells.
			cells.previousSelection = [];

			grid.geometry.colorsNeedUpdate = true;

		}
	}
}

mouseControls.checkMouseMove = function( event ){

	if( camera.renderSizeToggle ){
		mouseVector2.x = ( event.clientX / ( window.innerWidth / 2 ) ) - 1;
		mouseVector2.y = - ( event.clientY / ( window.innerHeight / 2 ) ) + 1;
	} else {
		mouseVector2.x = ( event.clientX / ( 8+( 1024 / 2 )) ) - 1;
		mouseVector2.y = - ( event.clientY / ( 8+( 768 / 2 )) ) + 1;
	}

	mouseRay.setFromCamera( mouseVector2 , camera );

	var intersect = mouseRay.intersectObject( gridTargetMesh );

	if( intersect.length > 0 ){

		// reset colors of all previous lines that were selected
		for( var i=0, ii=cells.previousSelection.length; i<ii; i++ ){
			grid.geometry.colors[ cells.previousSelection[i] ].setRGB( 0.3 , 0.3 , 0.3 );
		}

		// reset previous selection of lines.
		cells.previousSelection = [];

		// change the color of the line we've selected
		var face = intersect[ 0 ].face;
		var vectorList = cells.list[ face.index ].lineVertices;

		// highlight the closest line if within a certain distance.
		// this way it will seem a little more intuitive when selecting a line.
		var closestDistance = 10;
		var temporaryDistance = 0;
		var verticesIndex = -1;
		var allowedDistance = 0.1065; // This number seems to have the right feel to it.

		// we're going to calculate the distance from the intersected point 
		// to each set of vertices used to draw the grid lines, only if it's short enough will
		// we allow a selection.  
		for( var i=0; i<vectorList.length; i+=2 ){
			temporaryDistance = grid.geometry.vertices[ vectorList[i] ].distanceTo( intersect[0].point ) + 
								grid.geometry.vertices[ vectorList[ i+1 ] ].distanceTo( intersect[0].point );
			if( temporaryDistance < closestDistance && temporaryDistance <= allowedDistance ){
				closestDistance = temporaryDistance;
				verticesIndex = vectorList[i];
			}
		}
		
		// check to see if a line has been found.
		if( verticesIndex > -1 ){

			// now we'll highlight the line closest to the intersection point.
			grid.geometry.colors[ verticesIndex ].setRGB( 1 , 1 , 0 );
			grid.geometry.colors[ verticesIndex + 1 ].setRGB( 1 , 1 , 0 );
			cells.previousSelection.push( verticesIndex );
			cells.previousSelection.push( verticesIndex + 1 );	
		}

	} else {

		// reset colors of all previous lines that were selected
		for( var i=0, ii=cells.previousSelection.length; i<ii; i++ ){
			grid.geometry.colors[ cells.previousSelection[i] ].setRGB( 0.3 , 0.3 , 0.3 );
		}

		// reset previous selection of lines.
		cells.previousSelection = [];

	}

	grid.geometry.colorsNeedUpdate = true;
}

function createWall( index ){

	// the wall does not exist and there is room to build.  
	// we'll create one at this location.
	walls.lookUpArray[ cells.previousSelection[0] ] = true;

	// assign two temporary placeholder variables
	var v1 = grid.geometry.vertices[ cells.previousSelection[0] ];
	var v2 = grid.geometry.vertices[ cells.previousSelection[1] ];

	walls.geometry.vertices[ index ].set( v1.x , v1.y , v1.z );
	walls.geometry.vertices[ index+1 ].set( v1.x , v1.y+0.03 , v1.z );

	walls.geometry.vertices[ index+2 ].set( v2.x , v2.y , v2.z );
	walls.geometry.vertices[ index+3 ].set( v2.x , v2.y+0.03 , v2.z );

	// now that we've set the location of the vertices we'll need to update the geometry.
	// the faces will automatically stretch into place as each set of 2 faces is connected to
	// 4 vertices.
	walls.geometry.computeFaceNormals();
	walls.geometry.verticesNeedUpdate = true;

}

 


Finally, add these three functions to generateGameContent.js

Spoiler


generateGameContent.createGameParameters = function(){

	// these paraments will be used throughout

	var gameParaments = {

		gridRows: 7,	
		gridColumns: 13, 
		totalWalls: 40,

	}

	return gameParaments;
}

generateGameContent.createUserSelectionTaskManager = function(){

	// within the task manager we'll keep track of various 
	// user selected tasks such as placing towers, upgrading,
	// menu selections and so on.

	// Since we're going to add a graphical user interface at 
	// a later time we're going to allow for functionality 
	// through hot keys.  

	var userTaskManager = {

		// if a user presses the same button twice then it will cancel the selection.

		createWalls: false, // hotkey w , 87

	}

	return userTaskManager;
}

generateGameContent.generateWallMesh = function(){

	var geometry = new THREE.Geometry();

	var wallMaterial = new THREE.MeshLambertMaterial( { color: 0x444477 , side: THREE.DoubleSide } );

	for( var i=0, ii=gameParameters.totalWalls*4; i<ii; i+=4 ){

		// add four vertices for each corner of a wall
		geometry.vertices.push( 	new THREE.Vector3( 0 , 0 , 0 ) ,
								new THREE.Vector3( 0 , 0 , 0 ) , 
								new THREE.Vector3( 0 , 0 , 0 ) ,
								new THREE.Vector3( 0 , 0 , 0 ) );

		// add two faces for the wall, since the material is double sided we only need two
		geometry.faces.push( new THREE.Face3( i , i+1 , i+2 ) , new THREE.Face3( i+2 , i+1 , i+3 ) );

	}

	var walls = new THREE.Mesh( geometry , wallMaterial );

	// we're going to add a custom array to walls.
	// it will serve as a look up array to see if a wall already exists at a given vertice.
	walls.lookUpArray = [];

	// a bufferIndex to let us know what current index position to use for newly placed walls
	walls.bufferIndex = { vertices: 0 , maxVertices: ( gameParameters.totalWalls*4 ) };

	// a link array telling us which line segement is associated with which buffer index position
	// will come in handy when removing walls.
	walls.bufferLink = [];

	// an available index array incase the user wants to remove a wall segment.
	// will come in handy when removing walls.
	walls.availableIndexes = [];

	return walls;	
}

 

 

Have a great weekend!

2 likes 1 comments

Comments

Rutin

Great job! :) 

May 11, 2018 12:49 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement