Stage becomes null - actionscript-3

I'm creating a game with AS3 in Flash Professional CS5.5.
In this game I have an "again" button, so that the player can reset the level and start from new.
My problem now is:
After the clicked "again" the stage becomes null.
All I do in the "ResetLevel" method is, that I set the x and y positions of some elements back to 0, remove some items from the movieclip, but I don't remove ALL items from the display list. So the background, the hud, the plane isn't removed from the movieclip. Here a sketch of my displaylist. The removeable items are sometimes zero, sometimes they are 30 or more items (depends on playtime, and so on)
Displaylist:
stage
|-- Game movieclip
|--LevelBackground
|--Removeable item
|--Removeable item
|--Removeable item
|--Plane
|--HUD
But after removing the "removeable items" and setting the position coordinates of levelbackground and plane the stage is null.
Maybe someone can help me to point me to a solution for this problem.
EDIT:
The "ResetLevel"-method will be called inside the "game movieclip" and the stage will be accessed from the "game movieclip", too. So I don't remove the "game movieclip" from the displaylist when I reset the level. I only remove some elements, that the game movieclip contains from the movieclip.
Here some pseudocode from the "game movieclip class" (GameMC):
public class GameMC extends Sprite {
//Some properties here
public function GameMC() {
//Some code here
//--Events--
this.addEventListener(Event.ADDED_TO_STAGE, Init, false, 0, true);
this.addEventListener(Event.REMOVED_FROM_STAGE, Removed, false, 0, true);
}
private function Init(e:Event) {
this.removeEventListener(Event.ADDED_TO_STAGE, Init);
//Some Code here
}
private function ResetLevel() {
//Some Code here, too
if(removeItemArray.length > 0) {
for(i = 0; i < removeItemArray.length; i++) {
currentRemoveableItem = removeItemArray[i];
this.removeChild(currentRemoveableItem );
removeItemArray.splice(i, 1);
}
}
level.x = 0;
level.y = 0;
trace(stage); //Will output null
}
}

When a DisplayObject is removed from the DisplayList, it does not hold any reference to the stage anymore. So, whatever you need to set/calculate, do it in a valid state. Event.ADDED, Event.ADDED_TO_STAGE, Event.REMOVED and Event.REMOVED_FROM_STAGE help to verify the DisplayObject's state is valid.

Now I used to store the stage into a property and access this:
public class GameMC extends Sprite {
//Some properties here
private var stagevar:Stage;
public function GameMC() {
//Some code here
//--Events--
this.addEventListener(Event.ADDED_TO_STAGE, Init, false, 0, true);
this.addEventListener(Event.REMOVED_FROM_STAGE, Removed, false, 0, true);
}
private function Init(e:Event) {
this.removeEventListener(Event.ADDED_TO_STAGE, Init);
this.stagevar = stage;
//Some Code here
}
private function ResetLevel() {
//Some Code here, too
if(removeItemArray.length > 0) {
for(i = 0; i < removeItemArray.length; i++) {
currentRemoveableItem = removeItemArray[i];
this.removeChild(currentRemoveableItem );
removeItemArray.splice(i, 1);
}
}
level.x = 0;
level.y = 0;
trace(stagevar); //Will output [Object Stage]
}
}

Related

Check when a part of the MovieClip leaves the Stage

I'm creating a Drag and Drop game using AS3, i want to check when a apart of a Movieclip is outside the screen to move the View behind and let the user choose where to drop it.
I cant' test if the MovieClip credentials are bigger that the stage (scaleMode = NO_SCALE) Width/Height, because there is a part of the stage that it's hidden behind the browser window.
It's the same aspect as MOUSE_LEAVE just this time it has to be for MovieClips, i tried to see the code behind MOUSE_LEAVE but i couldn't reach it.
Thank You.
MAIN CLASS
[SWF(width='800', height='800',backgroundColor='#CC99FF', frameRate='60')]
public class DragTest extends Sprite
{
public function DragTest()
{
addChild(new World(this));
this.stage.scaleMode = "noScale";
this.stage.align = "TL";
this.graphics.lineStyle(5,0x555555,0.5);
this.graphics.drawRect(0,0,800,800);
}
}
WORLD CLASS
public class World extends Container // Container from my SWC
{
private var _display:Sprite;
private var _dragPt:Point;
private var _dragedObject:MovieClip;
public function World(display:Sprite)
{
super();
_display = display;
myMC.addEventListener(MouseEvent.MOUSE_DOWN, onPickUp, false, 0, true );
display.stage.addEventListener(MouseEvent.MOUSE_UP, onDrop, false, 0, true );
display.stage.addEventListener(Event.MOUSE_LEAVE, onMouseLeave, false, 0, true );
}
protected function onMouseLeave(event:Event):void
{
trace("Mouse Is Leaving The Stage");
}
protected function onDrop(e:MouseEvent):void
{
_display.stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMoveObject);
}
private function onPickUp(e:MouseEvent)
{
_dragedObject = e.currentTarget as MovieClip;
_display.stage.addEventListener(MouseEvent.MOUSE_MOVE, onMoveObject, false, 0, true);
}
protected function onMoveObject(e:MouseEvent):void
{
var point:Point = new Point(_display.stage.mouseX, _display.stage.mouseY);
(_dragedObject as MovieClip).x = point.x;
(_dragedObject as MovieClip).y = point.y;
}
}
Here is an Example :
Simple Code
The easiest approach would probably be to use getBounds(stage) and compare with stageWidth and stageHeight:
var bounds:Rectangle = _draggedObject.getBounds(stage);
if (bounds.left < 0) {
// left part of object is off-screen
} else if (bounds.right > stage.stageWidth) {
// right part of object is off-screen
}
if (bounds.top < 0) {
// top part of object is offscreen
} else if (bounds.bottom > stage.stageHeight) {
// bottom part of object is off-screen
}
You could move the display in each of these cases.
You can try to create an invisible zone that's a little bit smaller than your stage.
So you can add the MOUSE_LEAVE event to the zone, and when your mouse leaves that zone, you can do what you want.
Check the example here.
In response to Aaron Beall's response:
For a more interesting effect, if you want to wait until the movie clip is completely off stage, you can swap the boundaries you check on the object
var bounds:Rectangle = object.getBounds(stage);
if (bounds.right < 0) {
// do thing
} else if (bounds.left > stage.stageWidth) {
// do thing
}
if (bounds.bottom < 0) {
// do thing
} else if (bounds.top > stage.stageHeight) {
// do thing
}
Make sure you have import flash.geom.Rectangle; imported if this is inside a class.

actionscript 3 - deleting children from stage issue

So im creating a game where the ai shoot at the player. This issue im having is when the AI fires a bullet and is killed before the bullet is destroyed, the bullet freezes in mid air due to the AI being removed from the stage. Here is a video showing what I mean : http://i.gyazo.com/a13a43467ae8c6c3d8b1c988b7010bc2.mp4
The ai's bullets are stored in an array and im wondering if there is a way to remove the children added to the stage by the ai when the ai itself is removed from the stage.
This is the code which handles an AI being hit by a PLAYER bullet. The ai's bullets are stored in an identical way but in another class/file.
for (var i = _bullets.length - 1; i >= 0; i--) {
for (var j = enemies.length - 1; j >= 0; j--) {
if (_bullets[i].hitTestObject(enemies[j])) {
stage.removeChild(enemies[j]); //removes the ai
stage.removeChild(_bullets[i]); //removes the players bullet
_bullets.splice(i, 1);
enemies.splice(j, 1);
return;
}
}
}
You could do what you describe, but I don't recommend it. To do it you can listen for Event.REMOVED_FROM_STAGE and perform some kind of cleanup:
// in your AI class
addEventListener(Event.REMOVED_FROM_STAGE, removedFromStage);
function removedFromStage(e:Event):void {
// remove all the bullets
for each(var bullet:Bullet in _bullets){
stage.removeChild(bullet);
}
_bullets.length = 0;
}
However, this approach isn't realistic (the bullets will disappear mid-flight instead of continuing on their path as you'd expect) and it isn't a very good design.
The better way would be to store all your bullets in your main game class. The AI is responsible for "firing" a bullet, but after that the main class handles the bullet travel, collisions, etc. If you separate your logic like this, you won't have more problems like the the one you ran into.
There are many ways you could implement such a design, but here's a simple example I've used many times:
Your Main game class:
public class Main {
// a list of all enemies in the game
private var enemies:Vector.<Enemy> = new <Enemy>[];
// a list of all bullets in the game
private var bullets:Vector.<Bullet> = new <Bullet>[];
// adds a new enemy to the game
public function addEnemy():void {
var enemy:Enemy = new Enemy(this); // pass a reference to this Main class
enemies.push(enemy);
addChild(enemy);
}
// removes an enemy from the game
public function removeEnemy(enemy:Enemy):void {
enemies.splice(enemies.indexOf(enemy), 1);
removeChild(enemy);
}
// adds a new bullet to the game
public function addBullet():void {
var bullet:Bullet = new Bullet(this); // pass a reference to this Main class
bullets.push(bullet);
addChild(bullet);
}
// removes a bullet from the game
public function removeBullet(bullet:Bullet):void {
bullets.splice(bullets.indexOf(bullet), 1);
removeChild(bullet);
}
}
Your Enemy class:
public class Enemy {
// a reference to the Main class
public var main:Main;
public function Enemy(main:Main){
// store the reference to the Main class
this.main = main;
}
// to shoot a bullet, call the Main class
public function shoot():void {
main.addBullet();
}
// to kill the enemy, call the Main class
public function die():void {
main.removeEnemy(this);
}
}
Your Bullet class:
public class Bullet {
// a reference to the Main class
public var main:Main;
public function Bullet(main:Main){
// store the reference to the Main class
this.main = main;
}
// to remove the bullet when it hits something, call the Main class
public function collide():void {
main.removeBullet(this);
}
}
As you can see, the Main class is responsible for adding and removing enemies and bullets, and various other places in code simply call back to the Main class. This separation will prevent issues like you ran into and be much more flexible down the road.

(AS3) Functions

I managed to fix the whole null stage error by following MJW's guide on debugging Error #1009. But now the function that initializes the bullets doesn't get called.
Snippets:
if (stage) {
 init();
} else {
addEventListener(Event.ADDED_TO_STAGE, init);
}
...
private function init(event:Event = null) {
removeEventListener(Event.ADDED_TO_STAGE, init);
stage.addEventListener(Event.ENTER_FRAME, shoot);
}
...
private function shoot(event:Event) {
var bullet:EnemyBullet = new EnemyBullet();
stage.addChild(bullet);
bullet.x = enemy.x;
bullet.y = enemy.y;
bullet.theta = Math.random() * 360;
bManager.bulletVector.push(bullet);
}
Note that when I put trace() within the second two functions, nothing happens, but the addEventListener() in the first snippet does get called (or so I think).
As a general practice, stage should not be referenced - especially in your case, where your reference is solely to add instances of your bullet class. If it's a matter of z-index, you could instead have a layer in which bullets are placed on top of other display objects on the display list.
Besides complexities loading multiple SWFs on a single stage, your code would become nice isolated functional units by adding display objects to their own hierarchy of the display list. Or, you could leverage a MVC pattern whereby a controller manipulated views.
In order for your code to work, that class must either be the main function of the SWF or added to stage.
If it's the main function of the SWF, init() will be called.
Otherwise, assure it's getting added to the display list via an addChild().
Do you really intend to fire a bullet every frame? That could be 24 to 60 bullets a second. You might want to throttle that with some probability whether a bullet with fire.
Say this was a battlefield, and your battlefield class was added to stage, it could be implemented as this:
package
{
import flash.display.Sprite;
import flash.events.Event;
[SWF(percentWidth = 100, percentHeight = 100, backgroundColor = 0x0, frameRate = 30)]
public class Battlefield extends Sprite
{
public function Battlefield()
{
if (stage)
init();
else
addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
}
protected function addedToStageHandler(event:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
init();
}
protected function init():void
{
addEventListener(Event.ENTER_FRAME, frameHandler);
}
protected function frameHandler(event:Event):void
{
var odds:Number = Math.random();
trace((odds < 0.1 ? "Fire! " : "Skip...") + "Odds were: " + odds);
}
}
}
Which would output:
Skip... Odds were: 0.3539872486144304
Skip... Odds were: 0.742108017206192
Fire! Odds were: 0.025597115512937307
Skip... Odds were: 0.7608889108523726
Fire! Odds were: 0.08514392375946045
Skip... Odds were: 0.27881692815572023
Beyond Stage3D, I've never been fond of this initialization pattern, as you could just as easily rely on stage events, as in:
package
{
import flash.display.Sprite;
import flash.events.Event;
[SWF(percentWidth = 100, percentHeight = 100, backgroundColor = 0x0, frameRate = 30)]
public class Battlefield extends Sprite
{
public function Battlefield()
{
addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
}
protected function addedToStageHandler(event:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
addEventListener(Event.REMOVED_FROM_STAGE, removedFromStageHandler);
addEventListener(Event.ENTER_FRAME, frameHandler);
}
protected function removedFromStageHandler(event:Event):void
{
removeEventListener(Event.ENTER_FRAME, frameHandler);
removeEventListener(Event.REMOVED_FROM_STAGE, removedFromStageHandler);
addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
}
protected function frameHandler(event:Event):void
{
var odds:Number = Math.random();
trace((odds < 0.1 ? "Fire! " : "Skip...") + "Odds were: " + odds);
}
}
}
Therefore, upon added to stage the class initiates its actions, and upon removed from stage the class terminates its actions.
I think the issue is in the first block.
you are checking for stage, if stage is not null, then use a added to stage listener.
you should only be using addEventListener(Event.ADDED_TO_STAGE, init);
however, this is assuming that the class is DisplayObject subclass, objects that do not get added to stage cannot call the ADDED_TO_STAGE listener

HitTest for objects not yet on Stage

I need to add a MovieClip to stage, the limitation being that it should only be added to an empty area on the stage. The stage itself either contains complex shapes or is manipulable by the user i.e. he can drag/move objects to change the empty area. The hitTest and hitTestObject methods need DisplayObject already available on the stage. What is the right way to go - the only solution I can imagine is having added my object on the stage and then repeatedly doing hit tests?
[Imagine it to something like adding sprites in a video game - they must spawn in empty regions; if they pop out from inside of each other, then it'll look really odd.]
Well, when you create a new class, just turn it off with a variable and set the visibility to false, then loop until there is no hitTest.
A silly example:
public class someClass extends Sprite
{
private var objectsOnStage:Array;
public function someClass(objectsArray:Array) {
objectsOnStage = objectsArray;
visible = false;
addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event){
removeEventListener(Event.ADDED_TO_STAGE, init);
addEventListener(Event.ENTER_FRAME, SEARCH);
}
private function SEARCH(e:Event) {
var doesHit:Boolean = false;
x = Math.round(Math.random() * (550 - 0)) + 0;
y = Math.round(Math.random() * (400 - 0)) + 0;
for (var i:int = 0; i < objectsOnStage; i++) {
if (doesHit) break;
if (this.hitTestObject(objectsOnStage[i])) {
doesHit = true;
}
}
if (doesHit) return;
placedInit();
}
private function placedInit() {
visible = true;
removeEventListener(Event.ENTER_FRAME, SEARCH);
//now init the stuff you want.
}
}
You just check if bounding boxes of both clips overlaps. Like this:
import flash.geom.Rectangle;
import flash.display.MovieClip;
// create simple movie clips that has a rectangle shape inside
var sym1 : MovieClip = new Sym1();
var sym2 : MovieClip = new Sym2();
// get a rectanle of both clipt
var boundingBox1 : Rectangle = sym1.getBounds(this);
var boundingBox2 : Rectangle = sym2.getBounds(this);
// check if bounding boxes of both movie clips overlaps
// so it works like hitTestObject() method
trace( boundingBox1.intersects( boundingBox2) )
I know this post is super old, but in case it helps anybody --
If you need to do a hit test on a movieclip that isn't on the stage. A workaround is to rasterize it to a bitmap first.
var bitmapData:BitmapData = new BitmapData(mc.width, mc.height, true, 0x0000000);
bitmapData.draw(mc);
if (bitmapData.getPixel32(x, y) > 0) {
// Hit true.
}

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.