Why isn't touch listener removed when Sprite is removed? - cocos2d-x

I have the following code for checking for touches on a sprite:
void SpriteBlock::addEvents()
{
auto listener = cocos2d::EventListenerTouchOneByOne::create();
listener->setSwallowTouches(true);
listener->onTouchBegan = [&](cocos2d::Touch* touch, cocos2d::Event* event)
{
Vec2 p = touch->getLocation();
Rect rect = this->getBoundingBox();
if(rect.containsPoint(p))
{
return true; // to indicate that we have consumed it.
}
return false; // we did not consume this event, pass thru.
};
listener->onTouchEnded = [=](cocos2d::Touch* touch, cocos2d::Event* event)
{
SpriteBlock::touchEvent(touch);
};
cocos2d::Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(listener, 30);
}
void SpriteBlock::touchEvent(cocos2d::Touch* touch)
{
}
This seems to work fine, but even after the sprite is destroyed, it still gets triggered (if I click on the last place the sprite existed), and It crashes with:
"
Thread1: EXC_BAD_ACCESS (code=2, address=0x7....)
"
at the following line:
Rect rect = this->getBoundingBox();
Now it seems clear to me that the sprite is destroyed because my destructor is set to display a log message when it is triggered (which it does):
SpriteBlock::~SpriteBlock() {
CCLOG("Block destroyed");
}
So what is the problem here ? Why isn't the listener destroyed with my sprite ? I destroy my sprite by doing the following:
mysprite->removeFromParent();
When I create the sprite, I do not store any references. I just add it to the main layer of my scene, so it shouldn't remain. I create it using:
SpriteBlock *block = SpriteBlock::create();
How do I make sure the touch listener is also removed when the sprite is removed ?

auto listener = cocos2d::EventListenerTouchOneByOne::create();
store it as a variable in SpriteBlock class.
Then in SpriteBlock destructor remove a listener.

Related

Set object's `visible` to `true` when a `tap` event is performed then set it back to `false`

I have an object as MovieClip and I have a button as Button on my flash timeline.
When the button is tapped, I want to set the object.visible to true then when the button is not tapped, I want to set it back to false.
How can I do that?
I have tried this code, but it won't works as I want. I only can show the object but cannot hide it back.
button1.addEventListener(TouchEvent.TOUCH_TAP, touchTap);
function touchTap(e:TouchEvent): void {
mcObj.visible = true;
stage.addEventListener(TouchEvent.TOUCH_END, touchEnd);
}
function touchEnd(e:TouchEvent): void {
mcObj.visible = false;
stage.removeEventListener(TouchEvent.TOUCH_END, touchEnd);
}
I think this code could work.
button1.addEventListener(TouchEvent.TOUCH_BEGIN, touchTap);
function touchTap(e:TouchEvent): void {
mcObj.visible = true;
button1.addEventListener(TouchEvent.TOUCH_END, touchEnd);
}
function touchEnd(e:TouchEvent): void {
mcObj.visible = false;
button1.removeEventListener(TouchEvent.TOUCH_END, touchEnd);
}
I changed
1: TouchEvent.TOUCH_TAP to TouchEvent.TOUCH_BEGIN
2: stage.addEventListener to button1.addEventListener
Before saying anything about your problem, let's take a look on the definitions of the TouchEvent.TOUCH_BEGIN, TouchEvent.TOUCH_END and TouchEvent.TOUCH_TAP events :
The TouchEvent.TOUCH_BEGIN is :
Dispatched when the user first contacts a touch-enabled device ...
The TouchEvent.TOUCH_END is :
Dispatched when the user removes contact with a touch-enabled device ...
The TouchEvent.TOUCH_TAP is :
Dispatched when the user lifts the point of contact over the same InteractiveObject instance on which the contact was initiated on a touch-enabled device ...
And with some tests, we can see that the TouchEvent.TOUCH_END event is, in almost cases, fired before the TouchEvent.TOUCH_TAP one (by 1 or 2 milliseconds), so we can understand the we are able to detect if the user has already removed contact with the device (TouchEvent.TOUCH_END is fired) then if that was on the same InteractiveObject object on which the contact was initiated (TouchEvent.TOUCH_TAP is fired).
And that's why your code is not working.
Now, let's see your problem : you want to show a MovieClip just when your user tap a button and hide it when he releases that button but only for a very short time (the time of a tap ~= 300 milliseconds).
In this case, I recommend you to use a TouchEvent.TOUCH_BEGIN event listener with a timeout to hide that object even if your user didn't release the button.
For that, take this example :
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
btn.addEventListener(TouchEvent.TOUCH_BEGIN, on_touchBegin);
function on_touchBegin(e:TouchEvent): void
{
obj.visible = true;
hide_obj();
}
function hide_obj(): void
{
// you can use a Timer object instead of setTimeout()
var timeout:int = setTimeout(function(){
clearTimeout(timeout);
obj.visible = false;
}, 300);
}
Hope that can help.

Flash Senocular Transform Tool - controls are not showing on the tool

I am having trouble with the controls showing up for my transform tool. When I click my image I get the bounding box (to scale or rotate the image), but when I hover over the corner I do not get the cursor to transform it.
I am using these files:
TransformTool.as
TransformToolControl.as
TransformToolCursor.as
This is my code to call the transform tool:
var tool:TransformTool = new TransformTool();
addChild(tool);
And this later on to make the tool show up when the image is clicked and make the tool disappear when the stage is clicked:
tmpImage.addEventListener(MouseEvent.CLICK, select);
function select(e:MouseEvent):void {
tool.target = e.currentTarget as Sprite;
stage.addEventListener(MouseEvent.MOUSE_DOWN, deselect);
}
function deselect(e:MouseEvent):void {
tool.target = null;
tmpImage.addEventListener(MouseEvent.CLICK, select);
}
My image selection for the bounding box to appear and disappear work perfectly. All my code works as expected.... except the actual controls on the bounding box. Please help!
Edit
The concept is the user can click an image from a menu and drag a new instance of that image to the stage. Then the user can click the new instance and should be able to rotate or scale it. Then they can click off the image to make the bounding box disappear. (They can add as many images to the stage that they want).
Here is some code that shows the basic click, create new instance, and drag process I have implemented.
//sb1 is the menu area that contains a group of images
//hill is one of the images the user can add to the stage
sb1.hill.addEventListener(MouseEvent.MOUSE_DOWN, createCopy);
var i:int=0;
var tmpImage:Sprite; //to store which image is being dragged currently
function createCopy(e:MouseEvent):void {
tmpImage = new Hill_mc();
tmpImage.name = "hillChild"+(i++); //increment every copy
container.addChild(tmpImage);
tmpImage.x = mouseX-470;
tmpImage.y = mouseY-270;
tmpImage.startDrag();
tmpImage.addEventListener(MouseEvent.MOUSE_DOWN, onDown); //add the mouse down to this new object
stage.addEventListener(MouseEvent.MOUSE_UP, onUp); //since the mouse is currently down, we need to listen for mouse up to tell the current copy to stop dragging
}
//this will be called when click a copy
function onDown(e:MouseEvent):void {
tmpImage = Sprite(e.currentTarget); //get a reference to the one that was clicked, so we know which object to stop dragging on the global mouse up.
container.addEventListener(MouseEvent.MOUSE_UP, onUp); //listen for the mouse up
tmpImage.startDrag();
}
function onUp(e:MouseEvent):void {
container.removeEventListener(MouseEvent.MOUSE_UP,onUp);
if (tmpImage.hitTestObject(thesubmenu1)) {
container.removeChild(tmpImage);
}
else {
tmpImage.stopDrag();
}
tmpImage.addEventListener(MouseEvent.CLICK, select);
}
function select(e:MouseEvent):void {
tool.target = e.currentTarget as Sprite;
tmpImage.addEventListener(MouseEvent.MOUSE_DOWN, deselect);
}
function deselect(e:MouseEvent):void {
tool.target = null;
tmpImage.addEventListener(MouseEvent.CLICK, select);
}
EDIT
I found this code and placed it in my TransformTool.as. I feel like it's so close and that there must be something called incorrectly because I get an error for a null object reference:
public function select(event:Event):void {
// the selected object will either be the
// event target or current target. The current
// target is checked first followed by target.
// The parent of the target must match the
// parent of the tool to be selected this way.
if (event.currentTarget != this
&& event.currentTarget.parent == parent){
setTarget(event.currentTarget as DisplayObject, event);
}else if (event.target != this
&& event.target.parent == parent){
setTarget(event.target as DisplayObject, event);
}
}
/**
* Helper selection handler for deselecting target objects. Set this
* handler as the listener for an event that would cause the
* deselection of a target object.
* It is not required that you use this event handler. It is only a
* helper function that can optionally be used to help ease
* development.
*/
public function deselect(event:Event):void {
if (_target != null && event.eventPhase == EventPhase.AT_TARGET){
setTarget(null, null);
}
}
You give too little information to determine what exactly is wrong.
However, there is a very good sample code that does exactly what you want here :
http://www.senocular.com/demo/TransformToolAS3/TransformTool.html
(Click on the link at the bottom of the image.)
I am sure that you are going to make it with this.
EDIT :
Try to use the built-in handlers. I would do something like this :
Instead of this :
tmpImage.addEventListener(MouseEvent.CLICK, select);
function select(e:MouseEvent):void {
tool.target = e.currentTarget as Sprite;
stage.addEventListener(MouseEvent.MOUSE_DOWN, deselect);
}
function deselect(e:MouseEvent):void {
tool.target = null;
tmpImage.addEventListener(MouseEvent.CLICK, select);
}
Do this :
tmpImage.addEventListener(MouseEvent.MOUSE_DOWN, tool.select);
stage.addEventListener(MouseEvent.MOUSE_DOWN, tool.deselect);
EDIT :
If you do not have the handlers, I am not sure :) but I would recommand removing event listeners in each method as they might be interfering with each other.
tmpImage.addEventListener(MouseEvent.CLICK, select);
function select(e:MouseEvent):void {
tmpImage.removeEventListener(MouseEvent.CLICK, select);
tool.target = e.currentTarget as Sprite;
stage.addEventListener(MouseEvent.MOUSE_DOWN, deselect);
}
function deselect(e:MouseEvent):void {
stage.removeEventListener(MouseEvent.MOUSE_DOWN, deselect);
tool.target = null;
tmpImage.addEventListener(MouseEvent.CLICK, select);
}

How to get the width of a MovieClip for a different frame instantly?

Is there a way to get the width of a MovieClip (that does have a name) on a different frame? I have tried to using .width and .getBounds(null).width, however, both of them will give me only the width of the current frame. I have tried to do gotoAndStop(frameiwant), but the information doesn't seem to be correct until at least the next frame
I would like to get the width of the frame instantly so I don't have to wait until the next frame for the width.
The only way I could think of doing this was to have an initial phase in your project which will:
Run through all of the frames in your timeline. Create an object which will hold information about the children in that frame. It can be called Frame.
Iterate over all the children that are added to the stage in that frame and add a definition object that describes that child. The description can be as basic or vast as you need. We can call this class an ObjectDefintion.
The downside of this process is that you need to wait for the FRAME_CONSTRUCTED event like #Larusso pointed out in his answer. This means that the frame actually has to finish rendering before you are able to get information about its children, which of course means you have to go through and render every single frame in your timeline during this phase. All you can really do to mitigate this problem is set the frameRate to something high and then set it back when you're done assessing all the frames.
I have set this up and it works well - I'll paste each class and try explain what they do.
So for your document class (or whichever MovieClip holds the frames you want to look at), I have this:
public class Main extends MovieClip
{
private var _userFrameRate:int;
private var _frames:Vector.<Frame> = new <Frame>[];
public function Main()
{
_userFrameRate = stage.frameRate;
stage.frameRate = 120;
addEventListener(Event.FRAME_CONSTRUCTED, _assess);
}
public function getFrame(index:int):Frame
{
return _frames[index - 1];
}
private function _assess(e:Event):void
{
var frame:Frame = new Frame(this);
_frames.push(frame);
if(currentFrame === totalFrames)
{
removeEventListener(Event.FRAME_CONSTRUCTED, _assess);
gotoAndStop(1);
stage.frameRate = _userFrameRate;
ready();
}
else play();
}
public function ready():void
{
// Start here.
// There is a MovieClip on frame 10 with the instance name 'test'.
// We can get the width of it like this.
trace( getFrame(10).define("test").property("width") );
}
}
This basically initializes the phase in which we will run over each frame in the MovieClip and assess its children. The ready() method is used as the entry point for your code post-assessment.
Next we have the Frame class, which serves to hold information about children related to a frame:
public class Frame
{
private var _main:Main;
private var _content:Object = {};
public function Frame(main:Main)
{
_main = main;
update();
}
public function update():void
{
_content = {};
for(var i:int = 0; i < _main.numChildren; i++)
{
var target:DisplayObject = _main.getChildAt(i);
// This will be explained below.
var definition:ObjectDefinition = new ObjectDefinition(target, "x", "y", "width", "height");
_content[target.name] = definition;
}
}
public function define(name:String):ObjectDefinition
{
return _content[name];
}
}
It's pretty straightforward - you give it a reference to Main so that it can check children that are existent within it each frame.
The ObjectDefinition class is also pretty straightforward, acting purely as a repository for data that you want to keep track of on each child of the frame:
public class ObjectDefinition
{
private var _definition:Object = {};
public function ObjectDefinition(target:DisplayObject, ...properties)
{
for each(var i:String in properties)
{
_definition[i] = target[i];
}
}
public function property(property:String):*
{
return _definition[property];
}
}
You'll notice that the constructor accepts the target DisplayObject that will be defined, as well as any amount of properties you want to keep track of as strings (see above within Frame for implementation).
Once complete, you can chain the methods Main.getFrame(), Frame.define() and ObjectDefinition.property() to get properties of children that will exist throughout the timeline. For example, if you have a MovieClip with the instance name square on frame 15 and you want to get its width and height, you can do this within .ready() like so:
var square:ObjectDefinition = getFrame(15).define("square");
trace(square.property("width"), square.property("height"));
Of course this process is not ideal - but unfortunately it is the only way I can see that what you want to achieve is possible.
You have to listen to a specific event before you can ask for the information.
clip.addEventListener(Event.FRAME_CONSTRUCTED, frameReadyHandler);
clip.gotoAndStop(frame);
function frameReadyHandler(event:Event):void
{
clip.removeEventListener(Event.FRAME_CONSTRUCTED, frameReadyHandler);
var width = clip.width;
}
The Frame constructed event is the first of several events that gets dispatched. It gets dispatches right before the frame script gets executed. You could also wait for the on enter frame event.
You could add an event listener for 1 millisecond and test if the previousWidth you had stored is different. If it is, there you go. If not, its probably listening to the same frame.
A 1 millisecond timer is not such a big deal, stop it if you don't need it, resume it if you do, else, keep it running constantly. When it changes, dispatch an event or whatever needs to happen.
If you know the maximum size of the MovieClip, you may try this:
// Create movie clip
var movie :MovieClip = new MovieClipWith3Frames();
// Move to second frame
movie.gotoAndStop(2);
// Create bitmap witch magenta background
var bd :BitmapData = new BitmapData(200, 200, false, 0xFF00FF);
// Draw second frame
bd.draw(movie);
// Found the bounds of shape
var movieBounds:Rectangle = bd.getColorBoundsRect(0xFFFFFF, 0xFF00FF, false);
trace(movieBounds); // (x=42, y=15, w=32, h=33)

Click event outside MovieClip in AS3

Is there any way to detect if the user click outside a MovieClip?
For instance, I need to detect it to close a previously opened menu (like Menu bar style: File, Edition, Tools, Help, etc).
How can I detect this kind of event? Thanks!
Add a listener to stage and check if stage is the target of the event.
Example of code here:
http://wonderfl.net/c/eFao
package
{
import flash.display.Sprite;
import flash.events.MouseEvent;
public class FlashTest extends Sprite
{
private var _menu : Sprite;
public function FlashTest()
{
_menu = new Sprite();
_menu.x = 100;
_menu.y = 100;
_menu.alpha = 0.5;
with(_menu.graphics)
{
beginFill(0xFF0000, 1);
drawRect(0, 0, 300, 300);
endFill();
}
addChild(_menu);
_menu.addEventListener(MouseEvent.CLICK, onClickHandler);
stage.addEventListener(MouseEvent.CLICK, onClickHandler);
}
private function onClickHandler(event : MouseEvent) : void
{
switch(event.target)
{
case _menu:
_menu.alpha = 0.5;
break;
case stage:
_menu.alpha = 1;
break;
}
}
}
}
You can add a listener to the click event of the root element:
MovieClip(root).addEventListener(MouseEvent.CLICK, clickObject);
then in the function clickObject, you can check to see what you are clicking.
function clickObject(e:Event):void
{
var hoverArray:Array = MovieClip(root).getObjectsUnderPoint(new Point(stage.mouseX, stage.mouseY));
var hoverOverObject:* = hoverArray[hoverArray.length - 1];
}
hoverOverObject references the element that you are clicking on. Often this will be the shape within the movie clip, so you'll need to look at it's parent then compare it to your movie clip. If the click wasn't on the drop down movie clip, trigger the close.
var container:MovieClip = new MovieClip();
var mc:MovieClip = new MovieClip();
with(mc.graphics){
beginFill(0xff0000,1);
drawCircle(0,0,30);
endFill();
}
mc.name = "my_mc";
container.addChild(mc);
addChild(container);
stage.addEventListener(MouseEvent.CLICK, action);
function action (e:MouseEvent):void
{
if(e.target.name != "my_mc"){
if(container.numChildren != 0)
{
container.removeChild(container.getChildByName("my_mc"));
}
}
}
Use capture phase:
button.addEventListener(MouseEvent.CLICK, button_mouseClickHandler);
button.stage.addEventListener(MouseEvent.CLICK, stage_mouseClickHandler, true);
//...
private function button_mouseClickHandler(event:MouseEvent):void
{
trace("Button CLICK");
}
private function stage_mouseClickHandler(event:MouseEvent):void
{
if (event.target == button)
return;
trace("Button CLICK_OUTSIDE");
}
Note that using stopPropagation() is good for one object, but failed for several. This approach works good for me.
Use a stage and a sprite (menu) click listener with the sprite listener executing first and apply the stopPropagation() method to the click handler of the sprite. Like this:
menu.addEventListener(MouseEvent.CLICK, handleMenuClick);
stage.addEventListener(MouseEvent.CLICK, handleStageClick);
private function handleMenuClick(e:MouseEvent):void{
// stop the event from propagating up to the stage
// so handleStageClick is never executed.
e.stopPropagation();
// note that stopPropagation() still allows the event
// to propagate to all children so if there are children
// within the menu overlay that need to respond to click
// events that still works.
}
private function handleStageClick(e:MouseEvent):void{
// put hide or destroy code here
}
The idea is that a mouse click anywhere creates a single MouseEvent.CLICK event that bubbles from the stage, down through all children to the target, then back up through the parents of the target to the stage. Here we interrupt this cycle when the target is the menu overlay by not allowing the event to propagate back up to the parent stage, ensuring that the handleStageClick() method is never invoked. The nice thing about this approach is that it is completely general. The stage can have many children underneath the overlay and the overlay can have its own children that can respond to clicks and it all works.

MouseUpEvent stops working while Dragging MovieClip

There is a mouseUpEvent listener attached to the stage in the main class, while dragging the movieClip the mouseUpEvent doesnot trigger the handler and the movieClip gets sticks to the mouse. While the item is being dragged and the mouse is moved away from the movieClip i.e somewhere not on the item but on the stage the mouseUp is detected.
Edit: Here is the scenario I am using a static (Singleton) class as a movieClip as a "DragManager". Whenever a movieClip has to be dragged it is passed to the DragManager and is added as a child, when a mouseUp from stage is detected another static function of dragManager is called to stop the drag and put the movieClip on the appropriate layer. Here is the static function in the DragManager which is called on MouseDown from variouslayers.
public static function startDragMethod(item:Item):void
{
instance.addChild(item); //This is the instance of the DragManager
var boundArea:Rectangle = new Rectangle(0,0,610,760);
item.startDrag(false,boundArea);
}
In the constructor of the main class I add the eventHandler for the MouseUpEvent
this.addEventListener(MouseEvent.MOUSE_UP,stageMouseUpHandler);
The MouseUpHandler in the main class
private function stageMouseUpHandler(event:MouseEvent):void
{
DragManager.itemMouseUpHandler(event);
}
If there is something wrong with technique please guide me, my goal is to implement drag drop between various layers and with as less coupling as possible.
Well there are many ways to get the task done. Put some condition at your static listener function or when the object is dragged.
like
public static function startDragMethod(item:Item):void
{
instance.addChild(item); //This is the instance of the DragManager
var boundArea:Rectangle = new Rectangle(0,0,610,760);
item.startDrag(false,boundArea);
check = true;
}
private function stageMouseUpHandler(event:MouseEvent):void
{
if(check==true)
{
DragManager.itemMouseUpHandler(event);
check = false;
}
}