[Solved] Creating a Door System


(Ethan) #1

I am currently trying to create a door entity. The goal is to have the door entity linked with another door entity and transport the player to the location of that entity on collision.

What would be a good way to implement this type of door system? My goal is for the Entity to be flexible enough to send the player to other levels as well.

Below is what I came up with and my thought process, however, it doesn’t work. The door entity constructor just takes a X and Y value for its position and an entity (the door that it will send the player to). So in most cases, it will be something like:

var door1 = new Door(x, y, door2);

var door2 = new Door(x, y, door1);

and this is the door class

public class Door extends Entity
{		
	public var portalX:int = 0;
	public var portalY:int = 0;
	
	public function Door(xPos:int, yPos:int, exit:Entity)
	{
		type = "door"
		x = xPos;
		y = yPos;
		setHitbox(32, 26);
		portalX = exit + 15;
		portalY = exit + 27;
	}	
}

and the function in my Player class attempting to use the door

private function useDoor():void
{
	if (collide("door", x, y))
	{
		x = collide("door", x, y).portalX;			
	        y = collide("door", x, y).portalY;
	}
}

but it returns this error

“Error: Access of possibly undefined property portalY through a reference with static type net.flashpunk:Entity. y = collide(“door”, x, y).portalY;”


(Ultima2876) #2

You have to typecast the Entity returned from the collide function as a Door so it knows what class you’re looking for portalX and portalY in:

private function useDoor():void
{
	if (collide("door", x, y))
	{
		x = Door(collide("door", x, y)).portalX;		//"door" is always a Door class type	
	        y = Door(collide("door", x, y)).portalY;
	}
}

More info on typecasting here: http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7f87.html


(Danny) #3

Wouldn’t it be better to store check for collision only once and store the entity/door, because if you change the location (in this case his x coordinate) of the player he might not be colliding with the door the next check. Or will he?

private function useDoor():void
{
    var door:Door = collide("door", x, y) as Door; //returns null if no door?
	if (door) //evaluates to false if door is null
	{
		x = door.portalX;	
	        y = door.portalY;
	}
}

Edit: How can you create two objects at the same time and pass them into each other’s constructors lol

var door1:Door = new Door(x, y);
var door2:Door = new Door(x, y);

door1.setExit(door2);
door2.setExit(door1);

(Ethan) #4

I tried the casting method out and I no longer get an error from my useDoor() function. Now I’m getting an error in my Door class

public class Door extends Entity
{		
	public var portalX:int = 0;
	public var portalY:int = 0;
	
	public function Door(xPos:int, yPos:int, exit:Door)
	{
		type = "door"
		x = xPos;
		y = yPos;
		setHitbox(32, 26);
		
		portalX = exit.x + 15;
		portalY = exit.y + 27;
	}	
}

I get a fault exception at this line when I try to launch the debugger.

portalX = exit.x + 15;

It turns out it doesn’t like me trying to get that x value from an entity that may not or doesn’t exist.


(Ultima2876) #5

The door variable will indeed be null if no door is colliding, to clarify the top part.

To fix the new error, see the second part of @danny135’s post regarding passing doors to each others’ constructors. Instead, make a separate setExit function and handle the stuff that requires the door reference in there.


(Ethan) #6

Your code works as well as Ultima​2876’s suggestion, however I get the same [Fault] exception error.

"[Fault] exception, information=TypeError: Error #1009: Cannot access a property or method of a null object reference. "

Maybe it is from trying to create two Entities that take each other as a parameter. I considered how weird it was and whether or not it would even work as I wrote it. I think it would work better if I could consolidate a door into two separate parts of the same entity (with 2 hitboxes, 2 locations, etc…) but I don’t know if that is possible in Flashpunk.


(Danny) #7

When something’s null, trace everything. :smiley:

An easier way would be to just give each door the exit coordinates, instead of a whole entity.

public function setExit(exitX:int, exitY:int):void
{
    portalX = exitX;
    portalY = exitY;
}

Or you could just have the constructor be: new Door(x, y, exitX, exitY)


(Ethan) #8

But it shouldn’t ever be null, should it? The check in useDoor() is null 90% of the time and causes no issues.

for instance, the code

if (collide("door", x, y))
	{
		trace("door");
	}

It does nothing until I collide with a door, in which case it spams “door” as long as I collide with it. Completely omitting the useDoor() function’s code, the debug build fails to launch at the line.

portalX = exit.x + 15;

But is completely ok with

portalX = exit + 15;

Which doesn’t make sense (adding 15 to an entity object). Granted that unsurprisingly crashes the game when I collide with a door.

However, it sounds like it thinks that the entity (the 2nd door) that is being passed in the Door(int, int, Entity) constructor is null. But from the moment they are added to the level, they already have been passed their values. I think Danny may be right in that when I add them to the game world, I am creating them and passing them each other as values simultaneously which doesn’t make much sense.

public class GameWorld extends World
{	
	private var player:Player = new Player();
	private var door1:Door = new Door(32 * 2, 32 * 4, door2);
	private var door2:Door = new Door(32 * 9, 32 * 2, door1);
	
	public function GameWorld() 
	{
		add(new Level(Assets.MAP));
		add(player);
		add(new Camera(player));
		add(door1);
		add(door2);
		super.begin();
	}
	
}

(Danny) #9

You can’t use variable lines before you make it. There’s your problem:

private var door1:Door = new Door(32 * 2, 32 * 4, door2); //using door2 before it exists
private var door2:Door = new Door(32 * 9, 32 * 2, door1);

Use a separate function to set the exit door.


(Ethan) #10

Yeah, I considered that. I may just have to do that. I opted out of it because I wanted doors to be able to move the player to a new level. However, there is no reason it couldn’t be new Door(x, y, exitX, exitY, level) and just have it ignore the level is it is already loaded.


(Danny) #11

You can move the player to the new level and then set the x and y. Dunno if this would work but something like it. portalLevel is the embedded map file.

 //door class
public function setExit(exitX:int, exitY:int, exitLevel:Class = null):void
        {
               portalX = exitX;
               portalY = exitY;
               portalLevel = exitLevel;
        }


//player class
private function useDoor():void
{
    var door:Door = collide("door", x, y) as Door;
	if (door)
	{
		if (door.portalLevel)
		{
		         FP.world.remove(this);
		         FP.world = new Level(door.portalLevel));
		         FP.world.add(this);
		}
		x = door.portalX;	
	        y = door.portalY;
	}
}

(Ethan) #12

Sweet, I got it all working now thanks to the suggestions! Thank you both for the help.


(Ultima2876) #13

Just to clarify a few technical points of interest :slight_smile:

The reason useDoor’s null doesn’t cause issues is because you check for it with if(door) and don’t use those variables if it happens to be null. However, if you were to check for null and skip over it in your constructor code there’d be no point because it will always be null in the constructor (thus it would just skip over that code every time in the first door).

Having portalX = exit + 15 is “valid” (but don’t do it ;D) because exit evaluates to null, which evaluates to 0. You’re basically saying portalX = 0 + 15. It’s odd that it crashes the game when you collide with the door though.

Glad you got it sorted!