This is just a little side project I decided to put together as a proof of concept: fully functioning first-person 3D in OpenBOR. There’s still plenty of work to do, but the rendering system is in place. It’s not a pseudo-3D raycaster - it uses a fully realized binary space partitioning (BSP) system.
Current features include:
First things first - yes, this is all native OpenBOR. When I say this engine can do anything, I mean it. The script is written for 4.x, but everything you see is supported in 3.x too. Proving that point was part of my motivation for doing this. The other part is that rendering a first-person 3D view is easy money. Really. All the necessary graphics primitives are already there. At the most basic level, you just need some way to scale images or draw geometry, and those are only tiny parts of OpenBOR's native graphics suite.
The tough part - by far - is the mapping and physical layout. OpenBOR already uses a three-axis Cartesian coordinate system, so the coordinates themselves aren't the problem. The problem is that its native level layouts are designed around side-scrolling. From a world-building standpoint, first-person 3D is essentially a top-down layout viewed from a different perspective. That means transposing OpenBOR's side-scrolling conventions into a top-down layout, then making the whole thing flexible enough that every new level or game doesn't require a crap-ton of hard-coded script.
Then you project that world into a first-person camera, which, by comparison, is brain-dead simple.
The stages themselves are constructed from scripted "blocks" that use the same geometry points as native walls, with a few additions:

When the level starts, the script reads the block geometry and automatically configures the engine's native walls to match, which gives us native collision and bounding control. It also handles platform entities for more complex Y-axis geometry, such as elevated structures that you can pass underneath.
That's what makes the whole setup feasible. As a creator, you only have to define the layout once through the block coordinates. The scripts handle translating that geometry into native collision structures, so there's no need to double up on the work. We're still using OpenBOR's own primitives, meaning its native AI and collision systems can continue doing their jobs without a total rewrite.
BSP sounds complicated, but the basic idea is straightforward. Imagine drawing an imaginary line through a level, dividing the geometry into two groups - everything on one side and everything on the other. Each group can then be divided again, creating a tree of progressively smaller partitions. Instead of treating the level as one enormous collection of walls, we now have an organized structure that tells us how the geometry relates spatially.
At level startup, we take the exposed wall faces from our layout blocks and build this BSP tree, storing the partitions and their geometry in indexed arrays. This is where much of the heavy lifting happens, and we only have to do it once.
This is also where BSP differs from traditional raycasting. In a classic Wolfenstein-style raycaster, the engine casts a ray outward from the player's position for every vertical column on screen. Each ray travels through a grid of rectangular cells until it hits a wall, and the engine repeats that process every frame as the player moves or turns. BSP takes a different approach. The geometry has already been organized into a tree, so we traverse that existing structure instead of repeatedly searching the level with individual rays. We're also not restricted to a rectangular grid, meaning angled walls and more complex shapes are perfectly fine.
During gameplay, we traverse the tree using the player's position and rotation. The player's position tells us which side of each partition is nearer, while the rotation determines which geometry falls within the camera's view. This allows us to process walls in a useful near-to-far order, discard surfaces facing away from the camera, and ignore geometry outside the visible area.
We then project the remaining wall segments into screen coordinates to determine which horizontal pixels they occupy. If multiple walls overlap the same screen column, we compare their depths and keep the nearest one. This way, we're not wasting draw calls on walls hidden behind other walls.
That's the real advantage of BSP here. The level geometry is organized ahead of time, and the renderer uses that structure to determine what should be visible. We're not casting rays through the level every frame to discover where the walls are.
From there, we work left to right, one screen column at a time. For each column, we retrieve the current sprite pointer from the appropriate wall facing's DC Filmation reference. We calculate which portion of the texture corresponds to that column, how much it needs to be scaled based on distance, its Z layer, and the clipping vectors needed to isolate a single vertical strip.
All that information is fed into one slot of a flattened, indexed array. The process continues until the array is filled - one slot for every horizontal pixel on screen.
Finally, we read the array in order and perform a scripted sprite draw for each entry, applying the calculated drawmethod settings. This sends everything into OpenBOR's native sprite queue, and the engine takes it from there.
The important distinction is that BSP handles visibility, not physical collision. The walls and platforms we generated earlier still handle all the actual interactions through OpenBOR's native systems. The renderer just figures out how to display that same world from a first-person perspective.
I can't tell you exactly how many CPU cycles we're using, because that would require profiling I'm not bothering with, and the results would differ on every machine anyway. All you need to know is that it's a rounding error on any consumer-grade PC made in the last 20 years. My craptastic, 10-year-old, crash-and-burn, small-form-factor work desktop with onboard graphics runs it pegged at 200 FPS - while recording the video and bogged to the gills with other apps.
The reason is twofold. First, OpenBOR itself is a rounding error on modern hardware. Second, we've deliberately traded memory for speed. That's the whole point of caching and BSP. Then we add smaller optimizations like array flattening to shave off even more CPU time.
So that begs the question - how much memory? Same answer: rounding error, but this time I can give you hard numbers.
For those who aren't aware, OpenBOR's script variables use 12 bytes each. That makes calculating the memory requirements pretty straightforward.
Take our rendering buffer. At 480 horizontal pixels, with seven values per column, that's 3,360 variables. Multiply that by 12 bytes, and we're talking 40,320 bytes - just under 40 KiB for the stored values. One or two sound effects can take up more than that.
More importantly, because it's a single, flattened indexed array, that's all contiguous memory. No scattered allocations, no fragmentation, no pointer chasing just to find the next entry. Just one solid block of system RAM, directly addressable by index, friendly to the CPU cache, and simple to reset for the next frame. Furthermore, we allocate that buffer only once, then reuse it every frame. We're not constantly allocating and freeing memory.
The BSP tree is also constructed at level startup, so that work isn't being repeated during gameplay. That's where we trade a little memory for speed.
When it’s ready, I’ll upload the demo module.
DC
Current features include:
- Full three-axis movement.
- Arbitrary wall shapes, including rounded arcs.
- Platforms.
- Animated wall panels and objects.
How it Works
First things first - yes, this is all native OpenBOR. When I say this engine can do anything, I mean it. The script is written for 4.x, but everything you see is supported in 3.x too. Proving that point was part of my motivation for doing this. The other part is that rendering a first-person 3D view is easy money. Really. All the necessary graphics primitives are already there. At the most basic level, you just need some way to scale images or draw geometry, and those are only tiny parts of OpenBOR's native graphics suite.
The tough part - by far - is the mapping and physical layout. OpenBOR already uses a three-axis Cartesian coordinate system, so the coordinates themselves aren't the problem. The problem is that its native level layouts are designed around side-scrolling. From a world-building standpoint, first-person 3D is essentially a top-down layout viewed from a different perspective. That means transposing OpenBOR's side-scrolling conventions into a top-down layout, then making the whole thing flexible enough that every new level or game doesn't require a crap-ton of hard-coded script.
Then you project that world into a first-person camera, which, by comparison, is brain-dead simple.
The Layout
We start with an existing library I wrote a few years ago called DC Filmation, which I've explained in detail elsewhere. Basically, it lets you build scripted animations using definition sheets much like OpenBOR's native ones. The script uses OpenBOR's file-reading API to parse those sheets, builds arrays of animations and loaded sprites, then either displays the appropriate frames or provides their sprite pointers according to whatever conditions and frame timing you specify. For this project, it serves as our texture controller, giving us a centralized, reusable texture pool with built-in support for animated walls.The stages themselves are constructed from scripted "blocks" that use the same geometry points as native walls, with a few additions:
- Arc tangents to support rounded shapes.
- Y positions to support changes in elevation.
- Separate DC Filmation animation references for the North, South, East, West, Top, and Bottom faces.

When the level starts, the script reads the block geometry and automatically configures the engine's native walls to match, which gives us native collision and bounding control. It also handles platform entities for more complex Y-axis geometry, such as elevated structures that you can pass underneath.
That's what makes the whole setup feasible. As a creator, you only have to define the layout once through the block coordinates. The scripts handle translating that geometry into native collision structures, so there's no need to double up on the work. We're still using OpenBOR's own primitives, meaning its native AI and collision systems can continue doing their jobs without a total rewrite.
The Render
This is the easy part. We use Binary Space Partitioning (BSP) to determine what actually needs to go on screen.BSP sounds complicated, but the basic idea is straightforward. Imagine drawing an imaginary line through a level, dividing the geometry into two groups - everything on one side and everything on the other. Each group can then be divided again, creating a tree of progressively smaller partitions. Instead of treating the level as one enormous collection of walls, we now have an organized structure that tells us how the geometry relates spatially.
At level startup, we take the exposed wall faces from our layout blocks and build this BSP tree, storing the partitions and their geometry in indexed arrays. This is where much of the heavy lifting happens, and we only have to do it once.
This is also where BSP differs from traditional raycasting. In a classic Wolfenstein-style raycaster, the engine casts a ray outward from the player's position for every vertical column on screen. Each ray travels through a grid of rectangular cells until it hits a wall, and the engine repeats that process every frame as the player moves or turns. BSP takes a different approach. The geometry has already been organized into a tree, so we traverse that existing structure instead of repeatedly searching the level with individual rays. We're also not restricted to a rectangular grid, meaning angled walls and more complex shapes are perfectly fine.
During gameplay, we traverse the tree using the player's position and rotation. The player's position tells us which side of each partition is nearer, while the rotation determines which geometry falls within the camera's view. This allows us to process walls in a useful near-to-far order, discard surfaces facing away from the camera, and ignore geometry outside the visible area.
We then project the remaining wall segments into screen coordinates to determine which horizontal pixels they occupy. If multiple walls overlap the same screen column, we compare their depths and keep the nearest one. This way, we're not wasting draw calls on walls hidden behind other walls.
That's the real advantage of BSP here. The level geometry is organized ahead of time, and the renderer uses that structure to determine what should be visible. We're not casting rays through the level every frame to discover where the walls are.
From there, we work left to right, one screen column at a time. For each column, we retrieve the current sprite pointer from the appropriate wall facing's DC Filmation reference. We calculate which portion of the texture corresponds to that column, how much it needs to be scaled based on distance, its Z layer, and the clipping vectors needed to isolate a single vertical strip.
All that information is fed into one slot of a flattened, indexed array. The process continues until the array is filled - one slot for every horizontal pixel on screen.
Finally, we read the array in order and perform a scripted sprite draw for each entry, applying the calculated drawmethod settings. This sends everything into OpenBOR's native sprite queue, and the engine takes it from there.
The important distinction is that BSP handles visibility, not physical collision. The walls and platforms we generated earlier still handle all the actual interactions through OpenBOR's native systems. The renderer just figures out how to display that same world from a first-person perspective.
Resource Usage
Since I know somebody's going to ask about resources, let's put some actual numbers on it.I can't tell you exactly how many CPU cycles we're using, because that would require profiling I'm not bothering with, and the results would differ on every machine anyway. All you need to know is that it's a rounding error on any consumer-grade PC made in the last 20 years. My craptastic, 10-year-old, crash-and-burn, small-form-factor work desktop with onboard graphics runs it pegged at 200 FPS - while recording the video and bogged to the gills with other apps.
The reason is twofold. First, OpenBOR itself is a rounding error on modern hardware. Second, we've deliberately traded memory for speed. That's the whole point of caching and BSP. Then we add smaller optimizations like array flattening to shave off even more CPU time.
So that begs the question - how much memory? Same answer: rounding error, but this time I can give you hard numbers.
For those who aren't aware, OpenBOR's script variables use 12 bytes each. That makes calculating the memory requirements pretty straightforward.
Take our rendering buffer. At 480 horizontal pixels, with seven values per column, that's 3,360 variables. Multiply that by 12 bytes, and we're talking 40,320 bytes - just under 40 KiB for the stored values. One or two sound effects can take up more than that.
More importantly, because it's a single, flattened indexed array, that's all contiguous memory. No scattered allocations, no fragmentation, no pointer chasing just to find the next entry. Just one solid block of system RAM, directly addressable by index, friendly to the CPU cache, and simple to reset for the next frame. Furthermore, we allocate that buffer only once, then reuse it every frame. We're not constantly allocating and freeing memory.
The BSP tree is also constructed at level startup, so that work isn't being repeated during gameplay. That's where we trade a little memory for speed.
When it’s ready, I’ll upload the demo module.
DC
Last edited: