Reputation: 319
I'm trying to check if the parent has child in it. So for example, I have this code in my bullet class enterframe. The bullet needs to check if the parent(which is the level) has the zombie inside of it.
if (MovieClip(parent).zombie != null)
{
MovieClip(parent).zombie.checkCollisionWithEnemies(this);
}
However, not all levels have zombies, so I get an error if this bullet is added to the parent.
This is the function that sets off when the bullet is added. This code is in the zombie class.
public function checkCollisionWithEnemies(bullet: MovieClip)
{
if (this.hitTestObject(bullet))
{
if (this.meter != null)
{
if (this.meter.scaleX > 0)
{
this.meter.scaleX -= 0.5;
}
if (this.meter.scaleX <= 0.01)
{
this.gotoAndStop(2);
}
}
this.parent.removeChild(bullet);
}
}
So since the zombie is only in a certain level, if I'm in a level that has no zombies, it will throw in this error:
TypeError: Error #1009: Cannot access a property or method of a null object reference. at Bullet/update()[Bullet.as:47]
That error is coming from this line:
if (MovieClip(parent).zombie != null)
I'm trying to check if the parent has zombie inside of it, and if it does, then run the function for the zombie.
I've tried these codes below, but still got the same error.
if (MovieClip(parent).contains(zombie))
if (MovieClip(parent).getChildByName("zombie"))
Upvotes: 0
Views: 216
Reputation: 5255
There's your real problem:
I have this code in my bullet class enterframe
This code should not be in the Bullet
class! In order to test a collision, you need two objects that you want to test against each other. Therefore, if you want to do it in the Bullet
class, the bullet object has to get a reference to the other object (which is where you run into problems).
Requiring a reference to another object needlessly complicates the matter, because all that your bullet
does is pass itself to some function of the zombie
. Instead of doing that method call in the Bullet
class, it should happen where both zombie
and Bullet
are known: the level
.
The level has a reference to both the bullet
and the zombie
object, which makes it easy to perform collision detection there:
var zombie:Zombie = new Zombie();
addChild(zombie);
var bullet:Bullet = new Bullet();
addChild(bullet);
addEventListener(Event.ENTER_FRAME, gameLoop);
function gameLoop(e:Event):void
{
zombie.checkCollisionWithEnemies(bullet);
}
It's very common and recommended to have one single ENTER_FRAME
function that calls other functions of all the objects in your game. It's a lot cleaner than having en ENTER_FRAME
function on every object (and it's bad for performance, too)
You can further improve your code by overriding hitTestObject
instead of introducing a new method name checkCollisionWithEnemies
which essentially does the same thing.
override public function hitTestObject(obj:DisplayObject):Boolean
{
var result:Boolean = super.hitTestObject(obj);
if(result)
{
if (this.meter != null)
{
if (this.meter.scaleX > 0)
{
this.meter.scaleX -= 0.5;
}
if (this.meter.scaleX <= 0.01)
{
this.gotoAndStop(2);
}
}
this.parent.removeChild(bullet);
}
return result;
}
This is way you don't have to remember any new method name and your class simply adds a bit of functionality to the existing hitTestObject
function.
Now if you want to have a specialized function that only works with bullets, then you should be more specific with your types, accepting only Bullet
:
public function checkCollisionWithEnemies(bullet: Bullet)
{
if (this.hitTestObject(bullet))
{
if (this.meter != null)
{
if (this.meter.scaleX > 0)
{
this.meter.scaleX -= 0.5;
}
if (this.meter.scaleX <= 0.01)
{
this.gotoAndStop(2);
}
}
this.parent.removeChild(bullet);
}
}
If you were calling methods that only the Bullet
class has, this would be the way to go. But you aren't doing that, which is why you should simply override hitTestObject
.
Your method hitTestObject
formerly known as checkCollisionWithEnemies
still has a problem. It's this line:
this.parent.removeChild(bullet);
It's a similar problem as with your Bullet
, just the other way round. Now your zombie
is doing something with the bullet
. It works in this case, because the bullet
is passed as a parameter, but the idea is still flawed, because it relies on the structure of the display list to work.
If you later decide to move some bullets into a container or to have a crowd of zombies that you want to group with a container, this code will fail. It assumes that both zombie
and bullet
have the same parent.
The solution is the same as in the first part of my answer: move those parts of the code, that are concerned with other objects out to the level
:
override public function hitTestObject(obj:DisplayObject):Boolean
{
var result:Boolean = super.hitTestObject(obj);
if(result)
{
if (this.meter != null)
{
if (this.meter.scaleX > 0)
{
this.meter.scaleX -= 0.5;
}
if (this.meter.scaleX <= 0.01)
{
this.gotoAndStop(2);
}
}
// line removed
}
return result;
}
Now the level code would look something like this:
var zombie:Zombie = new Zombie();
addChild(zombie);
var bullet:Bullet = new Bullet();
addChild(bullet);
addEventListener(Event.ENTER_FRAME, gameLoop);
function gameLoop(e:Event):void
{
if(zombie.hitTestObject(bullet))
{
removeChild(bullet);
//or
bulletContainer.removeChild(bullet);
//or whatever
}
}
tl;dr
Try to create objects that are not tied too much to each other. In you case, having zombie-specific code in the Bullet
class and bullet-specific code in the Zombie
class creates unnecessary coupling between the two. Such coupling is also called a dependency, because each of the classes cannot be used independently any more.
The solution that I pointed out in this answer is to stay with the existing APIs in the form of hitTestObject
and move the application specific logic to another place. Thsi way each class works on its own and in the case of Zombie
handles some additional logic that relates to itself when being hit.
This is a lot cleaner that spilling !=null
all over the place, which as you found out is not a good solution.
Admittedly, this was a lot of reasons why you shouldn't write code the way you did in your question, but non of that actually addressed the problem at hand. I cannot stress enough (and thus do it again, now) that the problem you encountered is mainly because of a flawed code design, which will lead to many more problems down the road.
Ok, enough of that, now the problem:
You have tried these 3 lines and you get a #1009 on all of them
if (MovieClip(parent).zombie != null)
if (MovieClip(parent).contains(zombie))
if (MovieClip(parent).getChildByName("zombie"))
Here's a hard fast rule of thumb that tells you if some code is horrible: it contains a cast to MovieClip
like so: MovieClip(...)
. There are situations in which it makes sense to do such a cast, but for the most part, people abuse it to get some very horrible code to work.
This is because MovieClip
is a dynamic
class and if you cast to such a class, the compiler will throw less errors. That sounds good at first, but the problem is it doesn't really solve any of the issues that cause the errors in the first place. It just hides them in a "let's hope this works" fashion. Just like you said:
I've tried these codes below
Please, try to understand the code and don't just throw more "solutions" at it in an attempt to "solve" the problem.
If something is null
, then you should first find out what is null
. In your case, the result of MovieClip(parent)
is null
, which means that the cast failed. In other words: parent
is not a MovieClip
.
The reason for that is very likely that you added your bullets to stage, like so:
stage.addChild(bullet);
Which is another common bad practice. The thing is that stage
is not a MovieClip
and you shouldn't even addChild
anything to it. Why that's the case is not within the scope of this already long answer. The short answer is that you should never add anythign to stage
, just add things like I did in the above code, without stage.
. This will add it to the timeline. (no, stage
is not the main time line)
Upvotes: 1