Dynamic focus management [ AIR / AS3 ] - actionscript-3
I've run into a situation with a project I'm working on and I'm hoping to get some insight on how I might go about tackling it. Basically I am writing an AIR/AS3 app that allows the user to move focus around using the arrow keys on the keyboard. The problem I am trying to over come is that the clips are dynamically placed so I have no idea ahead of time what their position will be relative to each other. In addition to that, all of the clips have different shapes and sizes, so part of the weirdness is that some clips would occupy multiple columns, or multiple rows if you were laying out their positions on a grid for example.
So, the goal would be to have some logic that evaluates all of the clips sizes and/or positions and creates some form of map that allows clips to know who their nearest neighbor is when pressing right, left, up or down. The question is, what is the best approach for tackling something like this? I ultimately need it to feel very natural when moving around, and be far more intelligent then a tab-index list for example.
If I haven't provided enough information, please let me know and I can share additional details as needed. Just didn't want to go all crazy trying to explain things to start with.
Thanks in advance for any help!
Using the reference image, assume block "D" has focus. Using the logic #Pier's provided below, if I was to press "UP", would it know to go to block "A", or would it go to block "B" because it's distance and angle are less? In this case I think the user would expect it to go to block "A", but I'm thinking the code would evaluate to block "B" if I understand it correctly.
Calculating the distance and relative angle between two clips is easy (simple trigonometry). here are the function to calculate distance and angle between 2 x,y positions.
function angle(x1,y1,x2,y2):Number{
return radToDeg(Math.atan2((y2 - y1),(x2 - x1)));
}
function radToDeg(angle:Number):Number{
// to convert radians to degrees
return angle * 180 / Math.PI;
}
function distance(x1,y1,x2,y2):Number{
var dx:Number = x1 - x2;
var dy:Number = y1 - y2;
return Math.sqrt(dx * dx + dy * dy);
}
Knowing that, simply look what arrow the user has pressed. Then look the angles and distances of the other clips, and look how many clips there are in current direction (eg: what clips have an angle offset less than +-45 degrees), then look which one is closer, then change focus if there is any clip that meets the conditions.
I presume you won't have hundreds of clips placed on the stage, because in that case you should only calculate the angles for the closest clips.
Sorry for the delay following up here, but I ended up solving my issue by dynamically generating a grid map of all the clips positions which then allowed me to scan in various directions to find the next clip getting focus. Here is an output based on the example picture I provided originally:
[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,3,3,3,3,3,3,3,3,3]
[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,3,3,3,3,3,3,3,3,3]
[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,3,3,3,3,3,3,3,3,3]
[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,3,3,3,3,3,3,3,3,3]
[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,3,3,3,3,3,3,3,3,3]
[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,3,3,3,3,3,3,3,3,3]
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4]
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4]
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4]
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4]
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4]
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,4,4,4,4,4,4,4,4,4]
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-,-,-,-,-,-,-,-,-,5,5,5,5,5,5,5,5,5]
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-,-,-,-,-,-,-,-,-,5,5,5,5,5,5,5,5,5]
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-,-,-,-,-,-,-,-,-,5,5,5,5,5,5,5,5,5]
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-,-,-,-,-,-,-,-,-,5,5,5,5,5,5,5,5,5]
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-,-,-,-,-,-,-,-,-,5,5,5,5,5,5,5,5,5]
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-,-,-,-,-,-,-,-,-,5,5,5,5,5,5,5,5,5]
I've had to adjust the grid size to balance the performance vs. fidelity, but it seems to be working great in the various scenarios I needed it.
Thanks again for the help #Pier. Although I didn't end up using the ray casting approach, it was very informative learning about it. I could see how in a game for example that could be super powerful.
Related
hitTestObject not working properly AS3
How to make the objects detect each other without having to be so accurate? Currently, the program that I'm working on only allow the items to be matched when it is accurately matched at the upper left edge but i did not use hitTestPoint, I used hitTestObject. Below are my codes. if (bin1.hitTestObject(item)){ updateShape(item, bin1);
If bin1 and item are both DisplayObjects, which I don't see how they couldn't be (DisplayObject is a base class that's extended by a bunch of others like MovieClip and Sprite) then you should be able to just do a little quick and dirty calculation yourself. The way you worded your question led me to believe that bin1 and item have their origins in the top left, so you should be able to use this code to see if the complete rectangles that both of them inhabit are hitting, which isn't all that accurate if the objects are rotated, but it sounds like that's almost what you're looking for: if(Math.abs((bin1.x+bin1.width/2)-(item.x+item.width.2)) < bin1.width/2 + item.width/2 && Math.abs((bin1.y+bin1.height/2)-(item.y+item.height/2)) < bin1.height/2 + item.height/2) updateShape(item,bin1); If their origins aren't actually in the top left and are centered, remove all the .width/2 and .height/2 parts with the .x and .y parts in the parentheses. Hopefully this helps!
Rotation issue While Reflected (2D Platform Game)
My player's arm is programmed to follow my mouse and rotate accordingly and I've programmed bullets to be fired using this rotational value (Math.atan2(this._dy, this._dx) * 180 / Math.PI where _dy is the y location of the mouse (-) the y of my player's arm and the _dx is the x location of mouse (-) the y of my player's arm. However, when I program the player to reflect when the mouse has crossed the x-coordinates, the bullet angle is also reflected. How would I fix this issue? I've already tried subtracting 180 from the angle but it still doesn't fire towards the direction of the mouse.
First, make sure you have this parent-child-sibling relationship: "A" should be the parent of "B" and "C". "B" and "C" should have no direct link. Their connection is that they have the same parent. So when you want to move the character, move the parent, and both will move. Now, for the good stuff: Use key frames and sibling relationship beginner level approach Make the character and the arm both children of the same parent display object container (Movie Clip in this case). Now, instead of flipping anything by xScale, which I assume you did, you can just have both MC children (arm and character) go to frame 2 (or whatever is available) where the graphics are flipped. xScale body, move arm to frame 2, change z order moderate level approach (best result)* Alternatively, you could do that same "sibling" setup as above, and then scale the character but not the arm (I think scaling the arm will mess it up again, but you could have the arm go to frame 2 and have it drawn reversed so the thumb and handle are pointing the right way. Bonus points for changing the z stacking order so the arm goes to the other side of the body. xScale for only the body allows you to only have one set of frames for animation of his legs and torso etc. but also avoid scaling the arm at all). Global properties advanced approach A third option is to use global rotation and global points. I won't illustrate that here because I'm not that advanced and it would take me a while to figure out the exact syntax. If you already have mastered global properties, try this; if not, try one of the ones above. * Example (best result) if (facingRight == true && stage.mouseX < totalChar.x){ // totalChar is on the stage // and contains two children: // armAndGun and bodyHeadLegs totalChar.armAndGun.gotoAndStop(2); // in frame 2 of the arm MC, draw the // arm and gun in the flipped orientation totalChar.addChild(bodyHeadLegs); // re-ads body to parent so it's // z-order is above the arm; totalChar.bodyHeadLegs.xScale = -1;// flips body and any animation of legs and head facingRight = false; // use a variable or property like this // to keep him from constantly flipping } You'll need similar code to flip him back the other way.
as3 - how to randomize object positions without colliding?
If I have two instances called block1 and block2. And they move off the stage. It scrolls down the y position and it respawns back on top. But I don't want the x/y position colliding with the other blocks? I want it to respawn back to position, but I want it randomized but at the same time I don't want it touching each other? Heres my code: if (block1.y > stage.stageHeight) { block1.y = -550; block1.x = (Math.floor(Math.random() * (maxNum - minNum + 5)) + minNum); } I'm pretty sure I'm calculating the respawn coordinates the wrong way, but I'm not sure how to put it in a random x and y position without colliding with other blocks.
A very simple method can be just to spawn your box, do a collision check, then if collision, remove and respawn and recheck until you find an empty spot where it fits This is obviously quite inefficient, but is pretty simple to implement quickly if you have some sort of collision detection already working. Keep in mind if there is no spot that it can fit in, then it'll loop forever so you may want to set a max try count or something of that sort. How fast/well it'll actually work will depend on if the spawn area is pretty sparse or pretty dense, which will increase/decrease the percentage that it'll find a good empty spot the first few times. There is some room for improvement, going down this path though, such as if your collision detection system gives a minimum translation vector, you could just move the new shape over and use that position to spawn. Other simple methods could involve keeping track of known occupied positions and adjusting your random range to avoid those values.
AS3 smooth rotation direction
I'm not very good with radial calculations, I can't imagine thus I can't be sure. I need some explanation of Math.atan2() thing, please. Usual task - to make an object rotate after the mouse. I get the differences, get the angle, I see angles in the text areas and DIRECTLY the object does follow the mouse. What I need now is everything to be smooth. I need angles to be 0-360 but after 180 object rotation becomes -180 and counts backwards, and mouse rotation becomes -90 after 270 and also counts back to 0. More deeply, I want a smooth rotation, it means a set speed of say 2 per frame, to meet the mouse angle the shortest way. It takes to set conditions and I can't do that cause I don't even understand the logic of these values. They are almost random! I don't need it to be done or copied, I need to understand to move on so if you could please explain how does it work and what I do wrong... Code is simple: angle = Math.atan2(deltaY,deltaX)/(Math.PI/180) + 90; //+90 cause it lacks it to look at the mouse// Object01.rotation = angle; So the problem is I don't even get how it works... if 2 values are different the object can't point at the mouse but it does. Numbers lie and if I need something based on these numbers it will be wrong. Very wrong... Need organization. Meaning I want everything to be ready for further coding that will be based on the rotations to not jump up and down cause of misfit ends. Add: Explanation of how does it happen, what I described above. Why such a chaos of the values? And an advice on how could I arrange it for further coding, just as I said. Animation alone wont work if I want to make rotation an element of important events such as shooting direction and aiming speed. Or changes of speed rotation of a lockpicked lock. Or anything much more complicated that wont work if I don't make straight and clear values: from A to Z, from 1 to 10, no 8s between 2 and 3, no R before B, no mouse angle 270 when object facing it -90 when they both started from 0 and reached 180 together. Oh, and as I said, mouse facing works but when I try to make a certain speed of chasing mouse the shortest way it turns the object wrong directions in all 4 quarters. I assume it's also about this arctangens thing that has issues with delta values becoming negative in different quarters. And when I change it, some other value goes wrong... So I need to know exactly what I'm doing to know what's wrong and how to fix it. So yep. Need explanation. Please. Add2: angleZ = Math.atan2(oppSide,adjSide)/(Math.PI/180); So I divided rotation to 4 quarters, for each I count atan as opp. side to adj. side, then add 90, 180 and 270 respectively. My mouse rotation does 360, but the object that follow through simple object.rotation = angleZ; still goes to 180, then from -180 to 0 on the left side. Why does it ignore the simple command? The rotation fits but I need it to be equal, no surprises! Why is it happening? How can a number I directly set to be equal to another number as a base of the action change itself to the one of same rotation but completely different number? It doesn't even know it's degrees! It's as simple as "object.rotation, please be equal to the number I choose!"
It's just different coordinate systems. Like how x starts at 0 at the left of the stage, goes +x to the right, and -x to the left, object rotation starts at 0˚ pointing up, and goes +180˚ clockwise and -180˚ anti-clockwise. Math.atan2 happens to start at 0 pointing left (-x), and go +270˚ clockwise and -90˚ anti-clockwise, which is annoying, but it just means you have to convert between coordinate systems by adding 90˚. You can spin something around over and over of course, so the numbers jump so that they always stay within the same range, because 361˚ is the same as 1˚, and -270˚ is the same as 90˚. You can tell an object to rotate outside of the -180˚ to 180˚ range, and it will normalise the rotation to within those values. As mitim described, to smoothly animate rotation you'll either need to use Event.ENTER_FRAME, a Timer, or a tweening library like TweenLite. Most tweening libraries can work out the shortest rotation direction for you, but otherwise you can simply calculate both and see which is smaller.
As an idea, since it seems like you know the angle you need to rotate towards and it's direction, would it be easier to just animate towards that angle to get your smooth rotation? Basically treat it like any other animatable property and just add on your rotation speed (2 degrees it looks like) per frame tick, until it reaches the desired rotation. Find angle amount needed to rotate by Figure out if clockwise or counter clockwise direction and set the rotation amount. This can be figured out by checking if the angle is great then 180 / positive or negative Add the rotation amount * direction every frame tick, until the desired rotation is less then or equal to the rotation amount per frame Set rotation to desired rotation
Drawing a series of isometric enemies in the correct order or dealing with the dirty rectangle?
I'm wonderring if anyone can help me with making sure my... uhhh ... Z-index (bad pun, you'll see why in a moment) is in the wrong order. I've been doing this for a few hours straight now and my eyes are going buggy - but - maybe leaving a question on Stack overnight will help push this in the right direction. I've been working on the code for https://github.com/AlexChesser/jsSprite and I'm as far as the 6th test. Use the W key to run, A and D to turn left and right: http://chesser.ca/jsSprite/06-brainnsss....php (Gettit? Z-Index?! Hilarious). Anyways, You'll notice that if you run around the screen for a bit. the individual Zombies' white squares / dirty rectangles overlap the other zombies' squares. When working with multiple overlapping sprites, how does one go about making sure they all get drawn without upsetting any of the other sprites? (You see z is for zombies, but z index like when you're dealing with overlapping in CSS - probably way funnier when you've been coding for a number of hours straight). Thanks for your Brainsss......
It's not a z-index issue, your zombies themselves are okay. Your problem is really with the second line of drawFrame drawFrame: function(){ Sprite.ctx.clearRect(0,0,Sprite.width,Sprite.height); //clear previous frame // I am trouble: MainContext.clearRect(Sprite.Xpos, Sprite.Ypos, Sprite.width, Sprite.height); It is clearing a rectangle of the main canvas where the zombie once was every time you draw a zombie, which can affect nearby objects! So instead you should be clearing the entire canvas each time. Try commenting out the MainContext.clearRect in drawFrame, and instead add one in runloop like below. It should fix your problems. runloop = function(m) { // New clear put here! MainContext.clearRect(0,0,canvas.width,canvas.height); m.drawFrame(); for (Z in Zarr) { // For ZOMBIE in "Zombie Array" Aaaaarrrgghhh... Zarr[Z].pointTo(m); Zarr[Z].drawFrame(); MainContext.drawImage(Zarr[Z].canvas, Zarr[Z].Xpos, Zarr[Z].Ypos); }; MainContext.drawImage(m.canvas, m.Xpos, m.Ypos); };
How about sorting your array (Zarr) by the y coordinate Ypos of each zombie before rendering? Or are you getting at the problem with (a lack of) transparency?