Well, if you want to make a _simple_ 3d engine, ala WolfenStein3d, you basically need to plot out your maze on a 2d graph. The easiest way is to just fill a square if it's a wall, and leave it empty if it's empty (for example:
struct square
{
int isfilled;
) grid[xsize][ysize];
You can of course add fields to hold more data like what texture you want to put on each face, etc. Finally, you need a scalar factor like:
#define METERS_PER_SQUARE 10.
In the 3d world, your character will have a position X,Z (we'll forget Y since this is a flat game). To figure out if you should be in a space or not, you don't have to check any 3d stuff, but instead you can check against your 2d data. So if the square at X/METERS_PER_SQUARE,Z/METERS_PER_SQUARE is a 'filled' one, there's been a collision and the player shouldn't move there. Trees and other large objects can be added to the square structure (for example a tree can be a circular radius inside the square where the player can't go.) If you're using C++ you can make a square class and add a method called bool LegalMove() to it, which would make things more tidy.
A step up from that system would have each square empty, but add:
#define NORTH 0
#define EAST 1
#define SOUTH 2
#define WEST 3
int wallexists[4]
to your square structure. This way you can have thin walls instead of fat, cubefaced walls.
Collision detection would now happen if the player would end up in a different square from the one they're in now (in other words, if they cross a potential wall), so it is almost as simple. You have the added benefit of making one-way doors this way also.
The next step up from that method would be a 2d system that uses a list or array of lines for walls. Collision checks are still 2d and not so complicated (given a player's current position A and potential ending position B, draw a line from A to B. If it intersects any wall-line, a collision has occured), and you can have walls facing in any direction, not just along the cardinal directions.
Hope this gets you started
-ns