Properly hovering over isometric tile sprite - actionscript-3

I have four classes: Room, TileGrid, HoverTile, and Tile.
Room is composed of walls and a TileGrid. TileGrid is made out of Tile. Currently, I use this code to generate a TileGrid out of Tiles:
this.mapArray = [[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1, 1, 1, 1, 1, 1, 1]];
this._mapHeight = this.mapArray.length;
this._mapWidth = this.mapArray[0].length;
this._tileHeight = 23;
this._tileWidth = 46;
var initialX:Number = 260;
var initialY:Number = 150;
for (var isoY:int = 0; isoY < mapArray.length; isoY++)
{
for (var isoX:int = 0; isoX < mapArray[isoY].length; isoX++)
{
if (isoX == 0 && isoY == 0)
{
var _tile:Tile = new Tile();
_tile.x = initialX;
_tile.y = initialY;
this.addChild(_tile);
}
if (this.mapArray[isoY][isoX] == 1)
{
var _tile:Tile = new Tile();
_tile.x = initialX - (isoX * 20) - (isoY * 20);
_tile.y = initialY - (isoX * 10) + (isoY * 10);
addChild(_tile);
_tile.addEventListener(MouseEvent.MOUSE_OVER, updateHover);
}
}
}
My current issue is that I want to add a white square around the tile that a mouse is hovering over. The code I used to use wasn't sufficient, because transparent parts of the Tile sprite are still counted as part of it. So even if I'm pointing at another Tile2 (which is next to Tile1), for example, if I'm not far enough onto Tile2, it'll highlight Tile1.
So, here's the current code I'm using:
public function updateHover(e:MouseEvent):void
{
var mX:int = e.stageX - (_tileWidth / 2);
var tPoint:Point = pointToXY(mX, e.stageY);
var isoX = tPoint.x;
var isoY = tPoint.y;
if (isoX >= 0 && isoY >= 0)
{
if (isoY < mapArray.length)
{
if (isoX < mapArray[0].length)
{
tPoint = xyToPoint(isoX, isoY);
_tileHover.x = tPoint.x;
_tileHover.y = tPoint.y;
_tileHover.visible = true;
return;
}
}
}
_tileHover.visible = false;
}
public function pointToXY(x:int, y:int):Point
{
x -= 260;
y -= 150;
var pRatio:int = (_tileWidth / 2) / (_tileHeight / 2);
var tX:int = (y + x / pRatio) * (pRatio / 2) / (_tileWidth / 2);
var tY:int = (y - x / pRatio) * (pRatio / 2) / (_tileWidth / 2);
return new Point(tX, tY);
}
public function xyToPoint(x:int, y:int):Point
{
x -= 1;
var worldPoint:Point = new Point(0, 0);
worldPoint.x = (x * (_tileWidth / 2)) - (y * (_tileWidth / 2));
worldPoint.y = (x * (_tileHeight / 2)) + (y * (_tileHeight / 2));
worldPoint.x = worldPoint.x + (_tileWidth / 2);
worldPoint.y = worldPoint.y + (_tileHeight / 2);
worldPoint.x += 260;
worldPoint.y += 150;
return worldPoint;
}
Sorry I have to post so many code blocks. Now, 260 and 150 are the default starting point for the entire room. That said, I'm really confused on how to get the last two functions in particular to work so that they'll give me the correct answer. This is what I expected from using this code:
That would be perfect. But, again, I don't know why the code isn't working. The sizes are all correct and I believe the offset is, too. So, I'm

First, you should add the listener to this, not to _tile, because then you are locked to stage coordinates to determine the tile that's selected, which is not good. Second, your listener should be against MouseEvent.MOUSE_MOVE event, not over, this way you'll constantly get updated mouse coords to properly move your rectangle over tiles. And you have a minor error out there, you have a (0,0) tile created two times, one being inactive.
for (var isoY:int = 0; isoY < mapArray.length; isoY++)
{
for (var isoX:int = 0; isoX < mapArray[isoY].length; isoX++)
{
if (this.mapArray[isoY][isoX] == 1)
{
var _tile:Tile = new Tile();
_tile.x = initialX - (isoX * 20) - (isoY * 20);
_tile.y = initialY - (isoX * 10) + (isoY * 10);
addChild(_tile);
}
}
}
this.addEventListener(MouseEvent.MOUSE_MOVE, updateHover);
Also, it'll be better that you'd store (x,y) pairs on the array (as tiles, most likely), so that your initial array of zeroes and ones would transform into an array of Tile objects. To do that, you first do this:
this.tileArray=[];
for (var i:int=0;i<this.mapArray.length;i++)
this.tileArray.push(new Array(this.mapArray[i].length));
This will create an array of nulls that matches your mapArray by dimensions, that will serve as placeholder for created Tile objects. After you do this, you call this.tileArray[isoY][isoX]=_tile; to place the newly created tile to its place. After that, you can rewrite your listener to this:
public function updateHover(e:MouseEvent):void
{
var p:Point=pointToXY(e.localX,e.localY);
_tileHover.visible = false; // hide hover for now
if ((p.y<0) || (p.y>=tileArray.length)) return; // range error on Y
if ((p.x<0)||(p.x>=tileArray[p.y].length)) return; // range error on X
if (!tileArray[p.y][p.x]) return; // no tile
var _tile:Tile=tileArray[p.y][p.x];
_tileHover.x=_tile.x;
_tileHover.y=_tile.y; // no need to convert xyToPoint() we have coords stored in tile
_tileHover.visible=true;
}

Related

How to spawn objects in array without repeating?

I have MC's rockThrowers that are added to the stage in 3 different positions. They spawn randomly using a random generator which is working fine. The user clicks a button on the stage and the rockThrowers are added to the stage and pushed in their own array aRockThrowerArray I want to be able to check which of 3 positions they spawned on and not overlap the next rockThrowers that are added to the stage and if so then add a new one to the empty positions. I have tried different strategies mainly booleans and calling them from their own class to my main class but nothing seems to work. Here is my
rockThrowers Class:
private function startPosition():void
{
// y position
this.y = (stage.stageHeight / 2) + 200;
//Start Speed
nSpeed = randomNumber(5, 8);
leftScreenSpawn = randomNumber(1, 3);
//For Left Screen
leftNeg = (stage.stageWidth / 2) - 200;
leftMiddle = (stage.stageWidth / 2) - 150;
leftPos = (stage.stageWidth / 2) - 100;
//Left Screen
if (leftScreenSpawn == 1)
{
this.x = leftNeg;
bLeftNeg = true; // Now if the left Rock thrower is destroyed then turn back to false on main engine class
}else
if (leftScreenSpawn == 2)
{
this.x = leftMiddle;
bLeftMiddle = true;
}else
if (leftScreenSpawn == 3)
{
this.x = leftPos;
bLeftPos = true;
}
//Move
startMoving();
}
Now in my Main Class I have it setup like so for when the user clicks the left screen Btn:
private function rockThrowerSpawn(e:MouseEvent):void
{
//Instantiate screens before hand
rockThrowerSpawnScreen.x = (stage.stageWidth / 2);
rockThrowerSpawnScreen.y = (stage.stageHeight / 2) + 200;
addChild(rockThrowerSpawnScreen);
rockThrowerSpawnScreen.left.addEventListener(MouseEvent.CLICK, chooseSpawnSideRockThrowers);
}
Then the Spawn Function:
private function chooseSpawnSideRockThrowers(e:MouseEvent):void
{
if (e.currentTarget == rockThrowerSpawnScreen.left) // Spawn LEFT
{
//add new rock thrower
rockThrowers = new mcRockThrowers();
//Add object
addChild(rockThrowers);
//Add to Array
aRockThrowerArray.push(rockThrowers);
//trace("LEFT SPAWN");
}
//Subtract resources and update text
nResources -= 10;
updateResourceTextField();
//Remove Listeners
rockThrowerSpawnScreen.left.removeEventListener(MouseEvent.CLICK, chooseSpawnSideRockThrowers);
rockThrowerSpawnScreen.destroy();
}
I understand that this alone will always spawn random positions I deleted everything that wasn't working now I'm back to this square one. Any ideas on how I can accomplish this? All support is appreciate.
Easy. You need a finite Array that produces 3 values in a random order.
var L:Array =
[
(stage.stageWidth / 2) - 200,
(stage.stageWidth / 2) - 150,
(stage.stageWidth / 2) - 100,
];
function fetchPosition():Number
{
// Get a random index based on the current length of L.
var anIndex:int = Math.random() * L.length;
// Record the result.
var result:Number = L[anIndex];
// Remove the result from the list.
L.splice(anIndex, 1);
return result;
}
So, you can validly call fetchPosition() three times per application run, and each run the contents of L will be fetched in random order and, more importantly, you won't get the same value twice, because the fetched value is removed from the data set.

Orbiting objects with equal spacing AS3

I have a monster that produces crystals. I want each crystal to orbit the monster, but when there is more than one crystal, I want them to orbit at an equal distance from each other. I've been trying to get this to work using two blocks of code I already have, but each one does something different and i need one block of code that does it all.
This block simply allows an object to orbit another:
orbitRadius = 110;
angle += orbitSpeed;
rad = (angle * (Math.PI / 180));
orbitX = monster.x + orbitRadius * Math.cos(rad);
orbitY = monster.y + orbitRadius * Math.sin(rad);
Here's a video of what it looks like:
https://www.youtube.com/watch?v=ACclpQBsjPo
This block of code arranges crystals around the monster based on the amount of crystals there are:
radius = 110;
angle = ((Math.PI * 2) / targetArray.length) * targetArray.indexOf(this);
orbitX = monster.x - (radius * Math.cos(angle));
orbitY = monster.y - (radius * Math.sin(angle));
And here's this video: https://www.youtube.com/watch?v=TY0mBHc2A8U
I do not know how to both space the crystals equally and make them circle around the monster at the same time. What needs to be done in order to achieve this?
1) Hierarchical way: put crystals into the same container so they spread equally (like you are doing on the second video) then rotate the container.
2) Math way.
Implementation:
public class Orbiter extends Sprite
{
// Pixels.
public var radius:Number = 100;
// Degrees per second.
public var speed:Number = 360;
public var items:Array;
public var lastTime:int;
public function start()
{
stop();
rotation = 0;
items = new Array;
lastTime = getTimer();
addEventListener(Event.ENTER_FRAME, onFrame);
}
public function stop():void
{
items = null;
removeEventListener(Event.ENTER_FRAME, onFrame);
}
public function onFrame(e:Event = null):void
{
var aTime:int = getTimer();
rotation += speed * (aTime - lastTime) / 1000;
lastTime = aTime;
for (var i:int = 0; i < items.length; i++)
{
// Get the object.
var anItem:DisplayObject = items[i];
// Get the object's designated position.
var aPos:Point = getPosition(i);
// Follow the position smoothly.
anItem.x += (aPos.x - anItem.x) / 10;
anItem.y += (aPos.y - anItem.y) / 10;
}
}
private function getPosition(index:int):Point
{
// Calculate the angle with regard to the present items amount.
var anAngle:Number = (rotation - 360 / items.length) * Math.PI / 180;
var result:Point = new Point;
// Figure the position with regard to (x,y) offset.
result.x = x + radius * Math.cos(anAngle);
result.y = y + radius * Math.sin(anAngle);
return result;
}
}
Usage:
var O:Orbiter = new Orbiter;
// Define the offset.
O.x = monster.x;
O.y = monster.y;
// Set radius and rotation speed.
O.radius = 110;
O.speed = 270;
// Enable the rotation processing.
O.start();
// Append items to orbit.
O.items.push(Crystal1);
O.items.push(Crystal2);
O.items.push(Crystal3);
You can change radius and speed any time, as well as add/remove items, thanks to motion smoothing that all will look equally fine.

How to create stair effect

So Im trying to constantly add elements on the stage and those elements to have a specific X and Y
what i mean and what i want to get is a stairway effect (up and down) :
lets say 1st element is 100 pix above stage.Y
2nd element: Y is 1st element.y + 2nd element.height and its X position is 1st element.x + 2nd element.width(it appears immediately after we can see the whole body of 1st element)
3rd element : Y is 2st element.y + 3nd element.height and its X position is 2st element.x + 3nd element.width(it appears immediately after we can see the whole body of 2st element)
and the last element before the stairway effect goes down will be stage.stageheight - 100
like in this picture
Yet I dont know how to do this (I know i have to put it in a for loop and in there to have a if statement that checks every time for the up and down border (stage.stageHeight - 100 and stage.Y + 100) but i cant figure it out)
what i have so far is this
private var itemsToAnimate:Array = []
private var magnetBuff:Boolean = false;
private var block:Block = new Block();
private var stage_H:int = stage.stageHeight;
private var block_H:int = block.height;
public function AB_Main()
{
this.addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(evt:Event)
{
this.removeEventListener(Event.ADDED_TO_STAGE, init);
this.addEventListener(Event.ENTER_FRAME, onEveryFrame);
}
private function onEveryFrame(ev:Event):void
{
createItems()
animateItems();
}
private function animateItems():void
{
var itemToTrack:Block;
for(var i:uint = 0; i < itemsToAnimate.length; i++)
{
itemToTrack = itemsToAnimate[i];
itemToTrack.x -= 3;
if(itemToTrack.x < -50)
{
itemsToAnimate.splice(i, 1);
this.removeChild(itemToTrack);
}
}
}
private function createItems():void
{
if(Math.random() > 0.75)
{
var itemToTrack:Block = new Block();
itemToTrack.x = stage.stageWidth - 50;
itemToTrack.y = int( Math.random() * stage.stageHeight)
this.addChild(itemToTrack);
itemsToAnimate.push(itemToTrack);
}
}
and that gets me a random positioning blocks like in the picture
I think you could do something like this (writing it from my head without testing, I hope it will run ok):
function draw(min:Number, max:Number, horSpacing:Number, vertSpacing:Number):void {
var increasing:Boolean = true;
var lastY:Number = min + vertSpacing;
for(var i:int=0; i<100; i++) {
var c:Circle = new Circle(); //where Circle would be your dot with registration point in center
c.x = 20 + i * horSpacing; //hardcoded margin
c.y = (increasing) ? lastY - vertSpacing : lastY + vertSpacing;
lastY = c.y;
addChild(c);
if(c.y <= max) increasing = false;
if(c.y >= min) increasing = true;
}
}
draw(stage.stageHeight - 100, 100, 20, 20);
What you have here is a value that will constantly increase along the x axis and then another that will either be increasing or decreasing along the y axis, alternating when you touch the top or bottom. That's as simple as:
var increaseY:Boolean = true; // Whether we are moving up or down the y axis.
var position:Point = new Point(); // Position to place next item.
var move:int = 10; // How much we move along each axis.
var margin:int = 100; // Distance from top or bottom before alternating.
for each(var i:Sprite in itemsToAnimate)
{
i.x = position.x;
i.y = position.y;
if(increaseY) position.y += move;
else position.y -= move;
position.x += move;
if(position.y < margin || position.y > stage.stageHeight - margin)
{
// Reverse direction.
increaseY = !increaseY;
}
}
move can be adjusted to change the distance between each item along the path.

How can I create rules for my spawning platforms?

I'm working on a 2d vertical scrolling game that is based on doodle jump and i'm using flash and as3 to create it. I've put the scrolling and platform spawning and so far so good, but thing I randomize a x and y for each platform and obviously they just spawn wherever they feel like (inside the stage, that's my only actual rule). I wanna create rules so that max distance in between new platform and last one is, let's say 35px.
My current random code is:
public function createPlatform():void
{
//randomY();
var newY:Number = Math.random() * 600;
var X:Number = Math.random() * 500;
var tempPlatform:mcPlatform = new mcPlatform();
tempPlatform.x = X;
tempPlatform.y = newY;
platforms.push(tempPlatform);
mcContent.addChild(tempPlatform);
}
I also tried to do random just for Y this way:
private function randomY():void
{
var flag:Boolean = false;
while (flag == false)
{
newY = Math.random() * 600;
if(newY < lastY && (lastY - newY) < 50 && (lastY - newY) > 10)
{
newY = lastY;
flag = true;
}
}
}
the point of the game is to have character jump from platform to platform and when the game scrolls its content it just spawns a new set of platforms.
P.S.: newY is declared in the beggining of the code as 600 so first one is always starting from stage height.
Instead of just randomly placing platforms, try starting at the bottom of the screen and increasing y by a random amount each time you place a platform.
Something like:
newY = Math.random() * 50;
While (newY < 600) {
var X:Number = Math.random() * 500;
var tempPlatform:mcPlatform = new mcPlatform();
tempPlatform.x = X;
tempPlatform.y = newY;
platforms.push(tempPlatform);
mcContent.addChild(tempPlatform);
newY += 35 + math.random() * 50;
}
Once you have values for x and y of the new platform you will have to check the x and y (or one of them) with the last platform added to the array.
Something like:
...
tempPlatform.x = X;
tempPlatform.y = newY;
lastPlatform = platforms[(platforms.length)-1]; //get the last added platform
var flag:Boolean = false;
while (flag == false)
{
if(lastPlatform.y > tempPlatform.y ...)//set the condition(s) you need
{
//create new value
} else {
flag = true;
}
}
...

AS3 which item in an array has the lower value?

I’m trying to make a game like tower defence in AS3 and currently cant find solution to check which item in an array has the lower value of distance between enemy and turret, in order to choose which enemy to attack first.
I'm really stuck with this problem and asking for your help.
Here is a short code:
var enemyArray:Array = new Array();
var turretArray:Array = new Array();
addEventListener(Event.EnterFrame, loop);
// adding enemies
for(var i:int=0; i<3; i++){
var enemy:Enemy = new Enemy();
...
...
enemyArray.push(enemy);
addChild(enemy);
}
// adding turret
for(var t:int=0; t<2; t++){
var turret:Turret = new Turret();
...
...
turret.destinationX = 0;
turret.destinationY = 0;
turret.distance = 0;
turretArray.push(turret);
addChild(turret);
}
// loop
function loop(event:Event):void{
for(var j:int=enemyArray.length-1; j>=0; j--){
for(var k:int=turretArray.length-1; k>=0; k--){
// getting destination
turretArray[k].destinationX = turretArray[k].x - enemyArray[j].x;
turretArray[k].destinationY = turretArray[k].y - enemyArray[j].y;
// getting distance between turret and enemy
turretArray[k].distance = Math.sqrt(turretArray[k].destinationX*turretArray[k].destinationX+turretArray[k].destinationY*turretArray[k].destinationY);
// here i need to get min value from all turrets distance
}
}
}
Looks like you just need to be keeping track of the lowest value you've found as you go rather than overwriting it every time (if I've understood your code, correctly).
// loop
function loop(event:Event):void{
for(var k:int=turretArray.length-1; k>=0; k--)
{
turretArray[k].distance = -1;
for(var j:int=enemyArray.length-1; j>=0; j--)
{
var dx = turretArray[k].x - enemyArray[j].x;
var dy = turretArray[k].y - enemyArray[j].y;
var dist = Math.sqrt(dx * dx + dy * dy);
if(dist < turretArray[k].distance || turretArray[k].distance < 0)
{
turretArray[k].distance = dist;
turretArray[k].destinationX = dx;
turretArray[k].destinationY = dy;
}
}
}
}
Here, we store the initial distance value found in turretArray[k].distance, and only overwrite that if we find a lower one. We set it to -1 each time so we can tell if it's been set, yet, or not.
This is the equation you want:
http://www.mathopenref.com/coorddist.html
sqrt( (turret1X - turret2x)^2 + (turret1Y - turret2Y)^2 )