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
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
// 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
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
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!
Great job!