This is written in as3 and FlashPunk.
My problem? I have a tank that can move 4 tiles in the 4 directions; north,south,east and west.
Image can be found at http://www.aroderick.com/isolatedTiles.jpg
I've started with a square grid (Lt gray on the image) and used an array to mark out a diamond pattern (in red on the image) from the square grid, which are all the possible movement choices given the tank can only move 4 spaces in the 4 directions.
The tiles with numbers on the image are the actual tiles you see in the game, the numbers are the "col-row" numbers.
Where is gets a little more complex is that I've also removed tiles from the diamond where there are obstacles (water,trees,mountains) which in turn increases the movement cost to get to tiles beyond the obstacle(s).
I should mention at this point this is based on the A* algorithm and uses A* for movement but these are the movement choices that need to be established before a destination tile is chosen.
My problem is isolated tiles that are beyond the move ability of the tank and are isolated on their own from the main movement area where tiles are joined and make a complete path for A* and the tank can be moved from one tile to the next.
Is there a simple, elegant way to deal(get rid of) with these rogue tiles?
I've tried a system of rules i.e.;
//same row going east
if(ob.row == co.row && ob.row == startNode.row && ob.col < co.col && ob.col > startNode.col && ob.c < co.c )
{
extraCost = co.c;
reason = 1;
break;
}
//same row going west
else if(ob.row == co.row && ob.row == startNode.row && ob.col > co.col && ob.col < startNode.col && ob.c < co.c )
{
extraCost = co.c;
reason = 2;
break;
}
Where "c" a a property representing the tile movement "cost" as the crow flies.
But these seem to create as many problems as they solve.
//reusable tile grid
public static function makeTileGrid(entityLoc:Point,moveGrid:Array,travelMax:int,tsize:int = 64):Array
{
//node list
var nodeLst:Array = [];
//counter
var tileCount:int = 0;
//for tile naming
var co_ordX:String = "";
var co_ordY:String = "";
if(moveNode == null) var moveNode:Object;
//subtract the tile range from the current location
//tile range times two because you can go forewards
//or backwards tRange spaces
for (var col:int = travelMax * 2; col >=0;col--)
{
//placeX is an x value so you must multiply both row and tRange by the tile width
var placeX:Number = entityLoc.x - (travelMax*64 - col*64);
//trace(placeX);
for(var row:int = travelMax * 2; row >=0;row--)
{
var placeY:Number = entityLoc.y - (travelMax*64 - row*64);
//trace(moveGrid[col]);
//use tile grid map array
if(moveGrid[tileCount] == 1)
{
//use coordinates for the name value e.g.
co_ordX = col.toString();
co_ordY = row.toString();
moveNode = {col:col,row:row,obst:false,node:co_ordX+"-"+co_ordY,nX:placeX,nY:placeY,ph:0,h:0,g:0,f:0,c:0};
nodeLst.push(moveNode);
}
tileCount ++;
}
}
return nodeLst;
}
My grid code. In case.
Thanks guys,
james-
I think you should use a limited (distance-wise) breadth-first A* search right over that grid, with obstacles playing as walls in the algorithm. This will generate a set of reachable nodes each with distance_left attribute available, in a way that your "isolated tiles" will not get listed. Your code seems to just get the diamond pattern out of starting position and passage matrix, without actual check of the path distance.
public static function getReachableTiles(startTile:Point,distance:int):Array {
var d:Dictionary=new Dictionary(); // will hold visited tiles point by point
var o:Object={d:distance,px:startTile.x,py:startTile.y}; // a simple object info
// add fields as necessary
var a:Array=new Array(); // output array
a.push(o);
d[startTile.y*256+startTile.x]=o; // the simplest hash. You have to ensure
// these never overlap for different pairs of [x,y], and the same hash
// function is used across this method
while (distance>0) {
for each (o in a) {
if (o.d!=distance) continue; // already parsed
var to:Object={d:distance-1,px:o.x-1,py:o.y};
if (Game.isPassable(to.x,to.y)&&!d[to.y*256+to.x]) {
// a new cell, and is valid for the tank to pass
// "isPassable" returns true if (x,y) corresponds to a passable and
// valid position. For example, (-1,2) might return false as it's off borders
d[to.y*256+to.x]=to; // add to dictionary - a parsed cell
a.push(to); // and to output
}
// doing the same for 3 other possible moves
to={d:distance-1,px:o.x,py:o.y-1};
if (Game.isPassable(to.x,to.y)&&!d[to.y*256+to.x]) {
d[to.y*256+to.x]=to;
a.push(to);
}
to={d:distance-1,px:o.x+1,py:o.y};
if (Game.isPassable(to.x,to.y)&&!d[to.y*256+to.x]) {
d[to.y*256+to.x]=to;
a.push(to);
}
to={d:distance-1,px:o.x,py:o.y+1};
if (Game.isPassable(to.x,to.y)&&!d[to.y*256+to.x]) {
d[to.y*256+to.x]=to;
a.push(to);
}
} // okay, entire array was parsed for new cells
distance--; // we've iterated A* once more, lessen the distance
}
// at this point, we've iterated A* "distance" times, return
return a;
}
You need to adjust this snippet for your variable structure, so that Game.isPassable() will be a valid call, and there would be the actual grid under your drawn map. Note, startTile is a grid-based set of coordinates, not pixel-based. Hope this helps.
Related
Hey everyone so I am having some trouble trying to get this to work correctly. I have a MC Object called character and another called "points". I have a container object called planetContainer I add the character to the planetContainer the character is rotating around the planets that are also added to the container. The main issue I am having is when the points power up is activated I want the points to move off the other planets and to the charactercenter position. It was working perfect but had to update some code and remove the Points out of the planetContainer and attach them to the planets instead. I know I might have to use localToGlobal but not too sure.
Here is how I setup the character:
private function newCounterClockWise():void
{
planetContainer.addChild(character);
character.rotation = (Math.atan2(character.y - planetHit.y, character.x - planetHit.x) * 180 / Math.PI);
}
How the points are added to the Planets:
private function addPoints():void
{
points = new mcPoints();
var planetPosition:Point = planetContainer.parent.localToGlobal(new Point(0, 0));
points.x = planetPosition.x;
points.y = planetPosition.y;
outerPlanets.addChild(points);
aPointsArray.push(points);
}
Now this is the main function that handles the points to move to the character but it is not working correctly. The points move but they move off the screen or cause the game to kinda tweak out and do different things. Also the "magnetHandler(); is in my EnterFRame Event:
private function magnetHandler():void
{
for (var i:int = 0; i < aPointsArray.length; i++)
{
var currentPoints:mcPoints = aPointsArray[i];
var characterPosition:Point = planetContainer.parent.globalToLocal(new Point(character.x, character.y));
if (currentPoints.hitTestObject(playScreen.mcPointsHit))
{
trace("POINTS MID STAGE");
currentPoints.x -= (currentPoints.x - characterPosition.x);
currentPoints.y -= (currentPoints.y - characterPosition.y);
//currentPoints.x = character.x;
//currentPoints.y = character.y;
//TweenMax.to(currentPoints, 0.5, {x:characterGlobalPosition.x, y:characterGlobalPosition.y , ease:Power1.easeInOut } );
}
}
}
Can anyone see what I am doing wrong?
It's a hard to understand your question fully (or to understand why you're putting things that relate to each other in separate containers), but likely this line is where it's falling down:
var characterPosition:Point = planetContainer.parent.globalToLocal(new Point(character.x, character.y));
What you want to do, is get the characters x/y coordinates in the currentPoints parent space. To do that, you would do something like this:
//first, find the global position of character:
var globalCharacterPoint:Point = character.localToGlobal(new Point());
//then, convert that to the currentPoints parent local space:
var localCharacterPoint:Point = currentPoints.parent.globalToLocal(globalCharacterPoint);
Also, in this code of yours:
points = new mcPoints();
var planetPosition:Point = planetContainer.parent.localToGlobal(new Point(0, 0));
points.x = planetPosition.x;
points.y = planetPosition.y;
You are getting the global space of the planetContainer's parent, which is probably NOT what you want. You likely want:
planetContainer.localToGlobal(new Point()); //this gives you the global location of the planet container's top left corner
And, since you're adding the points object to outerPlanets, you probably want to convert to its local space (unless it's positioned at 0,0 globally - then it doesn't especially matter).
var outerPoint:Point = outerPlanets.globalToLocal(planetPosition);
points.x = outerPoint.x;
points.y = outerPoint.y;
Needless to say, for games it's best to have everything in the global coordinate space unless it's truly encapsulated assets (like smoke on a rocket etc.)
I am using a tileList in ActionScript 3 to display movieclips. However I have the problem that not all reference points of the movieclips are in the correct place. This leads to that these movieclips are shown partly outside of their cell in the tileList.
I have tried to adjust the x and y position of the movieClip before adding it to the tileList, but this did not change anything. Now I have tried to find if it is possible to change the x and y position of an object already in the tileList, but without finding any answers.
I hope that I have made my problem clear.
Thanks in advance!
EDIT:
This is the code I tried:
private function initTileList():void {
for(var i:int = 0; i < _movieClips.length; i++) {
changePos(_movieClips[i]);
tileList.addItem({label: _movieClips[i].name, source: _movieClips[i]});
}
}
private function changePos(mc:MovieClip):void {
if(MovieClip(mc).getRect(mc).x != 0) {
mc.x -= MovieClip(mc).getRect(stateMachineRef).x;
}
if(MovieClip(mc).getRect(mc).y != 0) {
mc.y -= MovieClip(mc).getRect(stateMachineRef).y;
}
}
I do not have any errors, it just doesn't affect the position of the object in the tileList.
Example of how the problem looks.
Hard to say where's the problem without knowing these things:
1. What tileList.AddItem() does exactly;
2. What is stateMachineRef
3. How MovieClips are loaded. If they are loaded from a network, that'll be a whole different story.
By the way, you don't have to cast MovieClip(mc) as mc is already a MovieClip. Also, there is no difference as to when you will correct the coordinates: before or after adding to the tileList. Should work either way.
So, given that information on your problem is not complete, I would just suggest you insure the following steps:
-We assume all tiles are displayed inside a tile container. It can be Stage or a MovieClip or any suitable DisplayObjectContainer, so let's call it just tileContainer from now on.
-We assume all tiles are of the same width and height. If you are not sure, you should check it again.
-We assume that each tile in the tileContainer is displayed at some regular grid coordinates. I.e. it conforms the following code:
for (var pos_y:int = 0; pos_y < GRID_SIZE_Y; pos_y++) {
for (var pos_x:int = 0; pos_x < GRID_SIZE_X; pos_x++) {
var tile:Tile = getNextTile(); // just get a tile from somewhere
tile.source.x = pos_x * TILE_WIDTH; // using your tile structure
tile.source.y = pos_y * TILE_HEIGHT;
tileContainer.addChild(tile.source);
}
}
Now I see your problem that some tiles are created in a way that they have their source movieclip coordinates shifted from (0,0). So they will not align with the grid.
What are you doing seems to be a proper way of aligning them but I don't know exactly what happens in your code so I'll just rewrite it:
function changePos(mc:MovieClip) {
var r:Rectangle = mc.getRect(mc);
mc.x -= r.x; // note you don't need any if's
mc.y -= r.y;
}
And in the above loop just add the changePos() AFTER setting the grid coordinates:
tile.source.x = pos_x * TILE_WIDTH;
tile.source.y = pos_y * TILE_HEIGHT;
changePos(tile.source);
tileContainer.addChild(tile.source);
If you're following all these steps, that's basically all you need and it will work for sure.
I have searched and simply cannot find what I need (if it exists).
A window will have a large picture.
The picture will be divided into zones (such as border lines that separate states on a map).
When a person clicks within a zone, then I will raise the appropriate event.
I've used AS3 with MXML to create a database program. All is working great except for this last step. I cannot figure out how the user is within a particular area of the picture when he clicks or when he touches.
I've read and tried to come up with an approach, and there must be (hopefully so) an easier way than the muddled nonsense I'm coming up with.
Thanks
VL
Are you drawing it in flash professional CS6? If so then why can't you just have the picture as a symbol and then just self divide the lines and make those divided areas into symbols that are children of the picture symbol. You could keep the individual state symbols right where they so that they stay true to the overall picture.
A first thought would be to make an instance of this picture symbol through the code, and then loop though all the children of that picture and add a click event to each one.
var picture:Picture=new Picture();
for(var i:int=0; i<picture.numChildren-1; i++){
picture.getChildAt(i).addEventListener(MouseEvent.CLICK, mouseEventHandler);
}
Please comment if I am missing something, or this does not work.
EDIT
Well, if you know the dimensions of the image, you could divide its width and height by 3(your number of rows and columns) and this is your zone dimensions. You then could take the mouse's click point relative to the top left of your picture and then divide its width by the zone with, and its height by the zone height, and then get its integer floor value, you could get which region it is. Code it below:
//This is all for a constat region list (like a window, or floor tiles, not things irregular)
import flash.display.Sprite;
var regionsX:int = 3; //Your number of windows across the row
var regionsY:int = 3; // across the column
var regions:Array = new Array(); // an array to hold the values that you will get from where the user clicks
// All of this used a 2D array method
for(var x:int = 0; x < regionX; x++) {
regions[regionsX] = new Array();
for(var y:int = 0; y < regionY; y++) {
regions[regionsX][regionsY] = "region(".concat(x).concat(",").concat(y);
// Here you make this equal to anything you want to get a value of,
//once the correct region is found (I just have a string version here for an example)
}
}
... // other stuff..
var picture:Picture = new Picture(); // your window picture
var regionWidth:Number = picture.width / regionsX; // Gets each region's width
var regionHeight:Number = picture.height / regionsY; // Get each regoin's height
...
picture.addEventListener(MouseEvent.CLICK, mouseEventListener); // add a click listener to the picture
function mouseEventListener(event:MouseEvent):void{
var mouseX:Number = picture.globalToLocal(event.stageX); // gets where the user clicked, and then converts it
//to the picture's cordinate space. ( 50,100 acording to the stage, could be (25,200) to the picture)
var mouseY:Number = picture.globalToLocal(event.stageY); // same for the Y
var regionIntX:Number = Math.floor(mouseX / regionWidth); // Dives the point by each region's width, and then
// converts it to a while integer. (For instance, if a region's width is 100 and you click at 288, then if you do the
// math, you clicked in the 3rd region, but it returns 2... why? (becaue the array counter starts at 0, so 0 is the 1st
// region, 1 is the second and so on...
var regionIntY:Number = Math.floor(mouseY / regionHeight); // Same for Y
var yourValue:String = regions[regionIntX][regionIntY]; // This returns that you initialy put into your 2d array
// by using the regionIntX and regionIntY for the array values. You have to decide what is stored in this array...
}
The simplest solution would be to add an event listener for MouseEvent.CLICK to the picture and in the handler check properties mouseX and mouseY of the picture. Define the bounds of each area in an XML or similar and check against current mouseX/Y to see which area has been clicked.
EDIT I have had a rethink: I am going to use a much easier to implement tree structure with a grid so that each block can have up to 4 neighbours.
I am trying to attach blocks on to the stalk (series of blocks) of this flower. Green blocks are the existing stalk and the blue block is the one attached to the mouse.
The block attached to the mouse will correctly snap to the nearest edges (I'm allowing diagonals for now) however the blocks will also be able to go 'inside' the stalk (image 2) by snapping to a block above or below.
My question is, how can I stop this? I considered
Iterating the list again but ignoring the block I just tried to attach to; I think this will just result in the same problem really by attaching to another block 'inside' the stalk.
Find which block I am intersecting and get another connection point from it, repeat until there are no intersections; This seems the better option but could be very messy when intersecting more than 1 block at a time
I should note that I plan on having a smoother snap, not just arbitrarily to an edge, so a grid is pretty much out of the question. I am pretty confident there must be an elegant solution, I'm just not seeing it!
Here is my snapping code as it currently stands
var mousePos:Point = new Point(mouseX, mouseY);// new Point(e.stageX, e.stageY);
var nearestPoint:Point = null;
var nearestDistance:Number = 0;
for (var i:int = 0; i < mPlant.length; ++i) {
var part:PlantPart = mPlant[i];
if (part is Stalk) {
var connectionPoint:Point = (part as Stalk).getNearestConnectionPoint(mousePos);
var distance:Number = Point.distance(mousePos, connectionPoint);
if (nearestPoint == null || distance < nearestDistance) {
nearestPoint = connectionPoint;
nearestDistance = distance;
}
}
}
if (nearestPoint != null) {
mMousePointer.x = nearestPoint.x;
mMousePointer.y = nearestPoint.y;
}
You can always pre-calculate a bounding Rectangle for the stalk, and then do the snapping check against that Rectangle.
The Rectangle can be calculated using something like this (un-tested code, though)
var _bounding:Rectangle = new Rectangle(int.MAX_VALUE, int.MAX_VALUE,0,0);
for each( var part:PlantPart in mPlant)
{
if(part is Stalk)
{
_bounding.width = part.width; // Width will always be the same
_bounding.height = Math.max( _bounding.height, part.y );
_bounding.x = Math.min( _bounding.x, part.x );
_bounding.y = Math.min( _bounding.y, part.y );
}
}
I've got a grid of sprites.
Now I would like to drag an image on a grid-element.
Therefore I need to know which x/y of the grid-element is the closest point to the mouse-position.
All the grid-elements are stored in an array.
How can I achieve that?
You must loop through all the elements and find the smallest distance to the mouse. Then store the array index of the element. Try something like this:
// Setup variables outside of loop.
var mousePoint:Point = new Point(mouseX, mouseY);
var elementPoint:Point = new Point();
var element:Sprite;
var closestIndex:uint = 0;
var closestDist:Number;
// Loop through elements
for (var i:int = 0; i < gridElements.length; i++)
{
element = gridElements[i] as Sprite;
// Set the elementPoint's x and y rather than creating a new Point object.
elementPoint.x = element.x;
elementPoint.y = element.y;
// Find distance from mouse to element.
var dist:Number = Point.distance(mousePoint, elementPoint);
// Update closestIndex and closestDist if it's the closest.
if (i == 0 || dist < closestDist)
{
closestDist = dist;
closestIndex = i;
}
}
// Can now use closestIndex to get the element from the array.
trace('The closest element is at index', closestIndex, ', with a distance of', closestDist);
Of course, this will only get you the first closest element, so you must decide what you want to happen if there are two elements an equal distance away.
You will also need to allow for the origins of your elements. The will probably have there origins set to there top left, so you need to allow for this in your distance calculation.
You could also check for a minimum distance. So if the user is dragging too far away from any of the elements then do nothing.
If all you need is the x,y of the closest grid then all you have to do is.
var gridX:int = Math.floor(mouseX / NumberOfColumns);
var gridY:int = Math.floor(mouseY / NumberOfRows);
This will convert your mouse coordinates to your grid coordinates.
Now comes the problem. If your storing them in a 2d array then you have your x/y if your storing them in a flat array (1d) you need to look it up just like you created it.
var myObject:Object = my2dArray[gridX, gridY];
var myObject:Object = myFlatArray[(gridX * NumberOfRows) + gridY];
If you have taken care of how you create your array and push the items in it, it should be no problem to retrieve stuff without searching it.