Actionscript 3 - alternatives to .hitTestObject or position constraints - actionscript-3

I need to detect when MC2 is over MC1 that it is inside MC1's borders.
to do this I would usually use 4 separate if x y constraints,
and unfortunately .hitTestObject in my creations also seem to need 4 separate if x y + - constraints.
Does anyone know a more simplistic way to achieve this.
or is x y + - constraints still the only way to do this?
Thank you in advance.

The final solution for your problem to detect hit of two shapes, is to use bitmapData.hitTest(). you can detect hit between any shapes and not only Rectangles. for that, you have to draw both of your shapes on bitmapData like line belo:
var shape1Bitmap:BitmapData = new BitmapData(shape1MC.with,shape1MC.height,true,0x000000);
shape1Bitmap.draw(shape1MC);
var shape2Bitmap:BitmapData = new BitmapData(shape1MC.with,shape1MC.height,true,0x000000);
shape1Bitmap.draw(shape1MC);
shape1Bitmap.hitTest(new Point(),shape2Bitmap):Boolean;******
to continue usint BitmapData.hitTest(), folow the orders here : https://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/BitmapData.html#hitTest()
http://dougmccune.com/blog/2007/02/03/using-hittestpoint-or-hittest-on-transparent-png-images/
It is a little complicated to add the bitmapData.hitTest() samples here. if any further questions left, please let me know to explain.
Good luck

I don't know of a built in way to do this, but it's easy enough using hitTestPoint with each corner of the square:
function isSquareInsideObject(square:DisplayObject, obj:DisplayObject):Boolean {
if(!obj.hitTestPoint(square.x, square.y, true)) return false;
if(!obj.hitTestPoint(square.x + square.width, square.y, true)) return false;
if(!obj.hitTestPoint(square.x + square.width, square.y + square.height, true)) return false;
if(!obj.hitTestPoint(square.x, square.y + square.height, true)) return false;
return true;
}
For more complex shapes than a square, you'd have to add more points to be accurate and it becomes a less elegant and less performant solution then.
You need that shape argument (third parameter for hitTestPoint) set to true if you want to test against the actual circle shape instead of the rectangular bounding box of the circle. If your circle is a bitmap (and not a shape), then I'd suggest putting a circular mask on the object to achieve the same result.
If your square isn't anchored at 0,0, or you don't mind the extra (small) performance hit, you could also use var bounds:Rectangle = square.getBounds(this) and then use the convenience properties of the rectangle object (bounds.bottomLeft, bottomRight, topLeft, topRight)

Related

Speech bubbles in Canvas/FabricJS?

Looking for a way to make a regular speech bubble in my website's FabricJS canvas. Now before you flag this post, I did see this question, it just has no proper answers and is designed for WordPress so it's not particularly of any use to me.
What I'm wanting is pretty clear: A speech bubble with text in it and a tail/handle that you can drag to point it to something.
I've found this library but I can't seem to get it to show up in my FabricJS canvas? If you could either explain to me how to add this library into my canvas or provide another way of making speech bubbles, that would be sublime.
I dug a bit into Fabric.js and managed to create a procedual speech bubble, but I'm not able to quickly convert it into a Fabric.js class (which would make sense if you want to have multiple speech bubbles on your canvas). Maybe it's still helpful for you or someone else https://codepen.io/timohausmann/pen/poywXzg
It basically creates a Textbox and based on the bounding box of the text updates the position of the Rect around it.
var bound = textbox.getBoundingRect();
rect.left = bound.left - boxPadding;
rect.top = bound.top - boxPadding;
rect.width = bound.width + (boxPadding*2);
rect.height = bound.height + (boxPadding*2);
For the tail I simply created a transparent Rect that you can drag around and use its coordinates to draw a polygon with three points between the "handle" and the textbox center [A]. To make sure the tail maintains a certain width no matter the position, I calculate the degree between handle and the speech bubble center [B]. To keep the position of textbox and handle in sync, I calculate how much textbox moved and simply add the difference to the handles position [C].
//calculate degree between textbox and handle [B]
var angleRadians = Math.atan2(handle.top - textbox.top,
handle.left - textbox.left);
var offsetX = Math.cos(angleRadians + (Math.PI/2));
var offsetY = Math.sin(angleRadians + (Math.PI/2));
//update the polygon [A]
poly.points[0].x = handle.left;
poly.points[0].y = handle.top;
poly.points[1].x = textbox.left - (offsetX * arrowWidth);
poly.points[1].y = textbox.top - (offsetY * arrowWidth);
poly.points[2].x = textbox.left + (offsetX * arrowWidth);
poly.points[2].y = textbox.top + (offsetY * arrowWidth);
//update the handle when the textbox moved [C]
if(textbox.left !== textbox.lastLeft ||
textbox.top !== textbox.lastTop) {
handle.left += (textbox.left - textbox.lastLeft);
handle.top += (textbox.top - textbox.lastTop);
handle.setCoords();
}
Disclaimer: I'm not a Fabric.js expert, maybe there are a few shortcuts possible with the library.
The answer by #Til Hausmann works nicely (thanks!).
I run into some problems when I tried to store and load the canvas data (via canvas.toJSON and canvas.loadFromJSON, resp.), though.
After some fiddling around, this could be resolved by
storing lastLeft and lastTop for both polygons in the updateBubble() method:
poly.lastLeft = Math.min(handle.left, textBox.left);
poly.lastTop = Math.min(handle.top, textBox.top);
setting the left / top properties for the polygons after the data were loaded:
canvas.loadFromJSON(jsonData, () => {
const poly = // ...
const poly2 = // ...
poly.left = poly.lastLeft;
poly.top = poly.lastTop;
poly2.left = poly2.lastLeft;
poly2.top = poly2.lastTop;
// ...
// Important:
canvas.renderAll();
});
passing the full set of shape properites to canvas.toJSON()
canvas.toJSON(
['lastLeft', 'lastTop'].concat(
Object.keys(handleProperties),
Object.keys(polyProperties),
Object.keys(poly2Properties),
Object.keys(textRectProperties)
))
I was surprised that step (3) is actually necessary, but it didn't work without it...

How to determine whether a given object is a mask

Apparently, in Adobe's wisdom, both the object being mask, and the masking object contain a "mask" property. This leads to a cyclical reference that prevents determining which is the actual mask and which is the masked.
For example...
var clip:MovieClip = new MovieClip();
clip.name = "clip";
addChild(clip);
var boundary:Shape = new Shape();
boundary.name = "boundary";
clip.addChild(boundary);
clip.mask = boundary;
trace(clip.mask.name); // outputs "boundary"
trace(clip.mask.mask.name); // outputs "clip"
I've iterated through the properties of both clip and boundary, and there doesn't seem to be anything unique that sets them apart. My first thought was to force a removal of the superfluous "mask" reference in boundary, however, that also sets the mask property in clip to null, thereby removing the mask.
My second thought was to check the parent relationship of a mask. If the parent is the same as the object's mask, then the object in question is itself the mask.
var a:Array = [clip, boundary];
for each (var item in a) {
if (item.mask == item.parent) {
trace(item.name + " is a mask");
}
}
// outputs "boundary is a mask"
Seems to work, and after checking the API reference on masks, it's clear that when caching, a mask will need to be a child of the masked, however... it's also valid to have a mask at the same depth as the masked (I do this from time to time when a mask needs to not travel with the masked content).
For example...
MainTimeline ¬
0: clip ¬
0: boundary
... can also be laid out as ...
MainTimeline ¬
0: clip ¬
1: boundary
So, there's the conundrum. Any ideas on how to resolve this?
The "best" hack I've found so far is to run hitTestPoint on the objects (after making sure they have something to hit under the target). Masks do not appear to ever return true for a full pixel hit test. This seems to work in most basic situations that I've tested:
public function isMask(displayObject:DisplayObject):Boolean {
// Make sure the display object is a Class which has Graphics available,
// and is part of a mask / maskee pair.
if ((displayObject is Shape || displayObject is Sprite) && displayObject.mask) {
// Add a circle at the target object's origin.
displayObject['graphics'].beginFill(0);
displayObject['graphics'].drawCircle(0, 0, 10);
var origin:Point = displayObject.localToGlobal(new Point());
var maskLocal:Point = displayObject.mask.globalToLocal(origin);
// Add a circle at the same relative position on the "mask".
displayObject.mask['graphics'].beginFill(0);
displayObject.mask['graphics'].drawCircle(maskLocal.x, maskLocal.y, 10);
// No matter which is the actual mask, one circle will reveal the other,
// so hit testing the origin point should return true.
// However, it seems to return false if the object is actually a mask.
var hit:Boolean = displayObject.hitTestPoint(origin.x, origin.y, true);
displayObject['graphics'].clear();
displayObject.mask['graphics'].clear();
// Return true if the hit test failed.
return !hit;
} else {
return false;
}
}
Obviously you'd want to cache the graphics in case the objects already have some, and it could do with something more elegant than casting as Sprite so that it can handle Shapes, but it's a start.
Edit: Accessing ['graphics'] lets this accept Shapes, but obviously isn't super efficient. I'm not sure what the best method would be, short of adding an interface.
Great question, haven't run into this before. I wasn't aware of the cyclical reference.
If your masks are exclusively masks, I would suggest just incorporating that into your naming convention. For example calling it clipMask as opposed to boundary.
As noted in the comments, in the situation where the mask is on the same display list, you could use getChildIndex() to compare their position on the display list of the parent.
Typically in that situation I'll have the mask layered over the other display object. This is not enforced obviously, and I don't believe that it has any effect on the result of the mask visually. But it's easier to maintain for a large group than a naming convention.
Still not ideal obviously.

Resize as a function of distance between mc's

I hope this hasn't been asked too much before. When I search I only get questions pertaining to rescaling to window size.
Now my question. I got one space ship firing a beam against another ship. I want the beam to show for some time and I want it to "bridge" the two ships. In other words, I want the beam to extend its width between the two ships.
I try to do this with a dot movie clip that is 1 pixel wide and high (and aligned left edge). I try to resize it with the following code: (target is the ship to be fire at and owner is the ship firing)
dist.vx = target.x - owner.x;
dist.vy = target.y - owner.y;
dist.dist = Math.sqrt(dist.vx*dist.vx + dist.vy*dist.vy);
width = dist.dist;
x = owner.x;
y = owner.y;
rotation = Math.atan2(target.y-y, target.x-x)*180/Math.PI;
This doesn't work as intended because 1) dot also gets alot bigger in the other dimension - how can I "turn off" this behavior? and 2) sometimes it seems to get way to wide - but only in certain angles...
Any suggestions on either solving the heigh/width scaling or on another way to achieve the same effect?
(I'm new to coding and flash.) Thanks!
By resizing a dot, you will have a rectangle...
You can dynamically create a sprite covering both ships and moveTo the hit point of one ship then lineTo the other ship... You do not need distance calculation at all. What you have to do is being careful on the placement of the sprite. So that you can calculate relative hitting points by simple math.
Suppose you have mc space contining mc ship1 and mc ship2, and hit point coords on ships are named hx, hy and you will use sprite s, calculation will be as follows.
// calculate hit points relative to mc space
var s1HX:int = ship1.x + ship1.hx,
s1HY:int = ship1.y + ship1.hy,
s2HX:int = ship2.x + ship2.hx,
s2HY:int = ship2.y + ship2.hy,
// sprite relative moveTo lineTo coords will be these.
mX: int, mY: int,
lX: int, lY: int;
// top left of sprite will be minimum of the hit coords.
s.x = (s1HX <= s2HX)? s1HX : s2HX;
s.y = (s1HY <= s2HY)? s1HY : s2HY;
// now we can get sprite relative moveTo lineTo coordinates:
mX = s1HX - s.x;
mY = s1HY - s.y;
lX = s2HX - s.x;
lY = s2HY - s.y;
The rest is implementation with using these with fancy line styles etc...
To create a new sprite:
var s:Sprite = new Sprite();
Adding / removing it to/from mc space:
space.addChild(s);
space.removeChild(s);
For graphics use the graphics object of sprite.
s.graphics
For setting line styles you can use:
s.graphics.lineStyle(...) ,
s.graphics.lineBitmapStyle(...),
s.graphics.lineGradientStyle(...)
Functions, please read the manual for usage.
After setting the line style to draw the line use:
s.graphics.moveTo(mX,mY);
s.graphics.lineTo(lX,lY);
For pulsating effects you have to do a little more complicated things such as using tween class which you can read about here: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/fl/transitions/Tween.html
Note that:
Sprites are no complicated magic, they are like mc's but they do not have timelines etc.
Sprites try to scale when width or height change programmatically. So do not touch them, moveTo lineTo automatically sets the size of a sprite...

AS3: Sprite following a Path in high speed

First of all sorry for some english mistakes. Portuguese is my first language(I am from Brazil)
Im trying to make a space game from scratch in AS3 and the way to move the ship is like in the game Air Traffic Chief.
I succeed at some point. But when the ship is very fast it start to shake and its not very smooth and clean as I want.
Here is what i have done: http://megaswf.com/s/2437744
As the code is very big so I pasted in pastebin: pastebin.com/1YVZ23WX
I also wrote some english documentation.
This is my first game and my first post here. I really hope you guys can help me.
Thanks in advance.
Edit:
As the code is very big i will try to clarify here.
When the user MouseDown and MouseMove the ship every coordinate is passed to an array.
When the user MouseUP this array is passed to a function that fix the array.
For example: If the distance between two coordinates is greater than 5px, the function creates a coordinate in the middle of the two coordinates.
if I take this function off the problem seen to be solved. But if the user move the mouse very slow it still happens. It also creates a problem that i was trying to solve with that function. as the distance of the two coordinates are very big when the ship arrive in one coordinate most of the line path disappear.
I uploaded a version without the function that fixes the array. http://megaswf.com/s/2437775
I think there is 2 ways for solving this problem
1- Try to fix the noise in the array of coordinates 2- Take off the function that create an coordinate between two points and try to fix the problem of the line path disappear.
Here is the 2 important functions:
this function moves the ship
private function mover():void
{
if (caminhoCoords[0]!=null) // caminhoCoords is the array that contain the path
{
var angulo:Number = Math.atan2(this.y - caminhoCoords[0][1], this.x - caminhoCoords[0][0]);
this.rotation = angulo / (Math.PI / 180);
this.x = this.x - velocidade * (Math.cos(angulo));
this.y = this.y - velocidade * (Math.sin(angulo));
var testex:Number = Math.abs(this.x - caminhoCoords[0][0]); //test to see the distance between the ship and the position in the array
var testey:Number = Math.abs(this.y - caminhoCoords[0][1]);
if (testey<=velocidade+2 && testex<=velocidade+2) // if is velocidade+2 close then go to the next coordnate
{
caminhoCoords.shift();
}
}
}
This function draw the line:
private function desenhaCaminho():void //draw the black Path
{
if(caminhoCoords.length>=1)
{
caminho.graphics.clear();
caminho.graphics.lineStyle(1, 0x000000, 1,true);
caminho.graphics.moveTo(caminhoCoords[0][0],caminhoCoords[0][1]);
for (var i:int = 1; i < caminhoCoords.length; i++)
{
caminho.graphics.lineTo(caminhoCoords[i][0], caminhoCoords[i][1]);
}
}else
{
caminho.graphics.clear();
}
}
Every time the ship arrive in one coordinate is take that coordinate off the array and redraw the array.
Is there a better way of doing that?
I believe if you set your registration point of the plane to the centre and use .snapto(path), it will improve the action.
Judging from just the look of the stuttering, I would guess you need to smooth out the "line" a fair bit. It's probably picking up a lot of noise in the line, which is then translated into the rotation of the plane. Either smooth out the rotation/position of the plane, or the line itself.

Is there a way to get the actual bounding box of a glyph in ActionScript?

I'm learning ActionScript/Flash. I love to play with text, and have done a lot of that kind of thing with the superb Java2D API.
One of the things I like to know is "where, exactly, are you drawing that glyph?" The TextField class provides the methods getBounds and getCharBoundaries, but these methods return rectangles that extend far beyond the actual bounds of the whole text object or the individual character, respectively.
var b:Sprite = new Sprite();
b.graphics.lineStyle(1,0xFF0000);
var r:Rectangle = text.getCharBoundaries(4);
r.offset(text.x, text.y);
b.graphics.drawRect(r.x,r.y,r.width,r.height);
addChild(b);
b = new Sprite();
b.graphics.lineStyle(1,0x00FF00);
r = text.getBounds(this);
b.graphics.drawRect(r.x,r.y,r.width,r.height);
addChild(b);
Is there any way to get more precise information about the actual visual bounds of text glyphs in ActionScript?
Richard is on the right track, but BitmapData.getColorBounds() is much faster and accurate... I've used it a couple of times, and optimized for your specific needs its not as slow as one might think.
Cory's suggestion of using flash.text.engine is probably the "correct" way to go, but I warn you that flash.text.engine is VERY (very!) hard to use compared to TextField.
Not reasonably possible in Flash 9 -- Richard's answer is a clever work-around, though probably completely unsuitable for production code (as he mentions) :)
If you have access to Flash 10, check out the new text engine classes, particularly TextLine.
I'm afraid all the methods that are available on TextField are supposed to do what you have already found them to do. Unless performance is key in your application (i.e. unless you intend to do this very often) maybe one option would be to draw the text field to a BitmapData, and find the topmost, leftmost, et c colored pixels within the bounding box retrieved by getCharBoundaries()?
var i : int;
var rect : Rectangle;
var top_left : Point;
var btm_right : Point;
var bmp : BitmapData = new BitmapData(tf.width, tf.height, false, 0xffffff);
bmp.draw(tf);
rect = tf.getCharBoundaries(4);
top_left = new Point(Infinity, Infinity);
btm_right = new Point(-Infinity, -Infinity);
for (i=rect.x; i<rect.right; i++) {
var j : int;
for (j=rect.y; j<rect.bottom; j++) {
var px : uint = bmp.getPixel(i, j);
// Check if pixel is black, i.e. belongs to glyph, and if so, whether it
// extends the previous bounds
if (px == 0) {
top_left.x = Math.min(top_left.x, i);
top_left.y = Math.min(top_left.y, j);
btm_right.x = Math.max(btm_right.x, i);
btm_right.y = Math.max(btm_right.y, j);
}
}
}
var actualRect : Rectangle = new Rectangle(top_left.x, top_left.y);
actualRect.width = btm_right.x - top_left.x;
actualRect.height = btm_right.y - top_left.y;
This code should loop through all the pixels that were deemed part of the glyph rectangle by getCharBoundaries(). If a pixel is not black, it gets discarded. If black, the code checks whether the pixels extends further up, down, right or left than any pixel that has previuosly been checked in the loop.
Obviously, this is not optimal code, with nested loops and unnecessary point objects. Hopefully though, the code is readable enough, and you are able to make out the parts that can most easily be optimized.
You might also want to introduce some threshold value instead of ignoring any pixel that is not pitch black.