## Question of the Week

 Advanced Example in Flash          by senocular Ok, time to really get our fingers dirty and build a complex fully featured isometric environment. Ok, not really. It probably won't be any more complex than what's been mentioned previously, at least not at first, though it will be set up in a way to be more flexible and easier to throw in more methods once discussed. Before, I said, "...the rest will go faster and with less step by step instruction..." though I don't think I really cohered to that. This time, that statement will be true. So first off, since we are dealing with a gridded system based on 2 value coordinate locations, we should set up a Point class to allow us to work with those coordinates as a single, solid object. In this Point class will be methods making use of those important isometric equations. The code: /*ŻŻŻŻŻ Point Class ŻŻŻŻŻ*/ Point = function(x,y){ this.x = x; this.y = y; } Point.prototype.toString = function(label){ return label + "{x: "+this.x+", y: "+this.y+"}"; } Point.prototype.toIso = function(trans){ var xs = this.x/spacing; var ys = this.y/spacing; var x = (xs + 2*ys)/2; var y = (xs - 2*ys)/2; if (trans){ this.x = x; this.y = y; return this; } return new Point(x,y); } Point.prototype.toScreen = function(trans){ var x = spacing * (this.x + this.y); var y = spacing/2 * (this.x - this.y); if (trans){ this.x = y; this.y = x; return this; } return new Point(x,y); } Point.prototype.round = function(trans){ if (trans){ this.x = Math.round(this.x); this.y = Math.round(this.y); return this; } return new Point(Math.round(this.x),Math.round(this.y)); } /*_____ End Point Class _____*/ toIso takes a screen-based point, such as _x, _y coordinates or a mouse position, and returns the point as it relates to the iso grid. toScreen takes an isometric point and returns that point as it exists on the screen. The trans argument decides whether or not the point being used is itself transformed into the new point or if its just being used to return a new separate point with the transformations applied. round obviously rounds the point and too includes the trans argument. This would be used to get an absolute grid location from something like a mouse point location, like that which was done in the previous Flash example of placing the ball where the mouse was pressed. Of course, also included is the toString method so that if we ever try to trace a point directly, we'll get a formatted string displaying the contents of our point object and not "[object Object]". As this class is set up, it accesses spacing directly as a free, single variable from that scope. By doing so, there lacks the immediate ability to change that easily based on different grid systems. If you needed to do that, then you might consider developing an isometric grid class to let you create distinctive grid systems and incorporate these functions in that. Again, it all depends on the level of complexity that you plan for yourself and what you intend to achieve. Here, I'm assuming a single consistent spacing-based grid which wont change or need to be so complex to have a separate class to define it with. Since we'll need to move movie clips around on the screen based on these points, we should set up our movie clips to be able to handle them a little more easily. Notably, we should be able to tell a movieclip to set its location to a point and have it just happen. With that, we can include a property to allow us to get the mouse location as a point from the scope of that movieclip: /*ŻŻŻŻŻ Movieclip Extending ŻŻŻŻŻ*/ getMCLoc = function(){ return new Point(this._x, this._y); } setMCLoc = function(pt){ this._x = pt.x; this._y = pt.y; } getMCMouseLoc = function(){ return new Point(this._xmouse, this._ymouse); } MovieClip.prototype.addProperty("loc", getMCLoc, setMCLoc); MovieClip.prototype.addProperty("mouseLoc", getMCMouseLoc, null); /*_____ End Movieclip Extending _____*/ Now, to set the position of the movieclip on the screen with a point object, we just use myMc.loc = myPoint; using myMc.loc alone to retrieve the point. myMC.mouseLoc will give the mouse location within the scope of that movieclip as a point. As a quick example of setting a movie clip to grid position (2,3), you can use: myMc.loc = new Point(2,3).toScreen(); This makes up the basic foundation for movieclip to grid relations through their point locations. Now we can start to encompass more functionality, namely, at this point, collision grids. This really doesn't take much since all we have to do is check within a certain point in an array and see if its a valid value or not: Array.prototype.valueAt = function(pt, check){ if (check === undefined) return this[pt.x][pt.y]; if (check === this[pt.x][pt.y]) return true; return false; } MovieClip.prototype.moveToTile = function(pt){ if (collisions.valueAt(pt, 0)) this.loc = pt.toScreen(); }   Next lets implement the key controls, but also include a method to turn the character to face in the direction of movement. This will be based on the key pressed, but instead of stringing everything out in if statements, we can consolidate a little using some math. At this point we also have the option of allowing a character to be moved diagonally on the screen, in both an x and y movement at the same time. That would mean drawing more character positions though, and I'm not up for that! Included is not only key detection, but also methods of moving absolutely or relatively. Absolutely we already have, absolutely has yet to be included: Point.prototype.toFrame = function(){ return 3 + this.x*2 + this.y; } Point.prototype.addTo = function(pt, trans){ if (trans){ this.x += pt.x; this.y += pt.y; return this; } return new Point(this.x+pt.x, this.y+pt.y); } Key.direction = function(){ var m; if (m = Key.isDown(Key.UP)-Key.isDown(Key.DOWN)) return new Point(0,m); else if (m = Key.isDown(Key.RIGHT)-Key.isDown(Key.LEFT)) return new Point(m,0) else return false; } KeyMovement = function(){ var dir = Key.direction(); if (dir){ this.gotoAndStop(dir.toFrame()); this.moveToTile(dir.addTo(this.loc.toIso().round())); } }   KeyMovement isn't prototyped because that will just be assigned directly to an onKeyDown. Other than that you have a function attached to the key object to return a point indicating the direction of the arrow keys using 1 and -1 to distinguish between positive and negative movement, and two new Point prototypes, one which adds two points and one which converts the point to a frame number. This is the math I was talking about. What the point.toFrame does is takes that point from the Key function (direction) and converts it to one of 4 frames which the movieclip is then told to gotoAndStop at. Because we have only 4 possible directions, only 4 possible frames can come out of the 4 possible points - and those frames are 1 (left), 2 (down), 4 (up) and 5 (right). 3 is an empty frame though will never get hit unless toFrame is called and both x and y values are 0. This won't happen because of the if (dir) check in Key.movement, however the possibility still exists to use that method to cause a movieclip to go to frame 3 if you need. Though its not the most efficient way to handle the frames (straight if/elses would be faster though like thousandths of a second) its crafty and takes up little space. On top of this we can include another Movieclip prototype to let us know what direction it's in by its frame number. I'll just use a case statement here though: Point.RIGHT = function(){ return new Point(1,0); } Point.LEFT = function(){ return new Point(-1,0); } Point.UP = function(){ return new Point(0,1); } Point.DOWN = function(){ return new Point(0,-1); } MovieClip.prototype.direction = function(){ switch(this._currentframe){ case 1: return Point.LEFT(); case 2: return Point.DOWN(); case 4: return Point.UP(); case 5: return Point.RIGHT(); default: return false; } } I also included some point functions to return direction points associated with those directions, much like Key.LEFT and Key.RIGHT but since they are objects and not basic values, a function is needed to return a new object of that type and not a reference to the actual Point.LEFT or Point.RIGHT etc. One last thing before going to bed. There's still more to cover, most notably fluid movement (not jumping directly from grid space to grid space) and possibly some path finding, but first lets finish up this Point driven example. The last thing is an action grid - setting a space for an action and then initiating that action when the user tries (whatever it is that allows them to do so). So here we go: Point.prototype.equals = function(pt){ return (this.x == pt.x && this.y == pt.y); } Array.prototype.actionAt = function(pt, args){ return this.valueAt(pt)(arguments.slice(1)); }   Yeah, that's pretty much it. Basically all you do from this is great a new grid and assign a certain position to be the action for that spot. Then you simply run the action in that grid associated with the players loc when its tried. For example: actionsGrid.actionAt(myMC.loc); If no action is set in that grid space in the actionsGrid, nothing happens. What the new Point.equals prototype allows you to do is within your defined function check for the direction of the character to be correct for calling that action. This can be used easily checking to see if the character's direction equals a preset Point.LEFT or Point.RIGHT etc.