Legacy GM 3D raycasting engine - simplified

slojanko

Member
I don't normally post "tutorials" but I'll make an exception and share my work with you.

GM Version: GMS
Target Platform: All (with reasonable CPU power)
Source: download link - Updated
Precompiled: download link - Updated

Summary:
This is a basic tutorial/example of a game using what's called raycasting to generate a 3D view on your environment. This was used in the original Doom and Wolfenstein 3D.

Tutorial:
The code used is currently reasonably documented and is organized enough for an intermediate user to figure out what is happening. The project contains 5 scripts which are appropriately named for better recognition.




The view on the test room is split into 2, one being the 2D view on the actual position of the objects and a 3D representation using raycasting. This works by firing a set of rays that detect collision in the direction of the camera. These rays go across the room while only being generated in your FOV and return a position of its collision. This is then used to calculate how far/close the pixel a ray has hit should be drawn using a vertical line.

Because using a ray a few thousand times per frame is very resource intensive I've decided to limit the density of rays and precision of each ray through 2 script variables attached to the camera's Create event.

Current limitations are:
- can't draw horizontal planes (floor)
- no depth checking - massive performance boost when implemented!
- bad performance!

I would like to thank Rusky for helping me write the code necessary for drawing lines which are used to show a 3D view on the room. Everything else was written by me following tutorials in javascript.
 
Last edited:

chance

predictably random
Forum Staff
Moderator
It's great that you want to share this, but I think it's premature as a tutorial. It has excellent potential, but it's not really suitable as a tutorial yet. As you said yourself, comments are lacking. And many of the comments are in Slovenian. So that probably limits the audience on an international forum such as this.

Also, you should take special care whenever you "pin" the mouse, as is the case here using display_mouse_set(). For many users, that means they cannot exit or close the example without using the task manager. So if there are other keys that can close the example, you should make that more clear.

That said, there are some interesting coding techniques here. So it's worth a look for more advanced users. Like I said, excellent potential.
 

slojanko

Member
Thank you for the suggestion,

I've changed a lot with the presentation of the project along with a better representation of how the engine actually works by drawing 2D rays. If you want to disable this, look for parts marked as "Debug information" and comment them out or simply remove them. They don't in any way affect what the final product of the engine is and are only there to offer some assistance while running. The code is also reasonably commented and should be a lot better to understand what it's doing.

The mouse controls are now enabled by holding any mouse button down.
 

lolslayer

Member
I've done a non raycasting pseudo 3D fps, completely done by math, so no per vertical colomb checking of walls, it was just perfect but also waaaaay to hard to add depth checks :/
 
How would I go about adding depth checking to the engine?
Edit: Sorry if this counts as a necro/there are rules about necroposting, new here :I
Edit2: If possible also doom style enemies?
 
Last edited:

slojanko

Member
I've added depth checking to the current project but I haven't yet uploaded it.
A problem with how game maker handles collision_lines is that it returns the smallest id of the object you're looking for (when multiple instances of the same instance are in your view.

You can get around this by getting getting a new id for each iteration of the binary search operation. Basically each time you search for a collision point in a smaller radius you get the instance that was hit in that step. Another problem that occurs now is what if you have instances of different objects. You'll have sort and draw them in an order from furthest to closest. This seems to be the most logical way of rendering objects at the moment.

To sum up: for each ray you have to scan for all objects added into the engine and get the closest instance of each object. Then sort the instances by distance. If an instance of an object isn't found at the maximum render distance you can abandon it completely within that ray!
 
Last edited:
I've added depth checking to the current project but I haven't yet uploaded it.
A problem with how game maker handles collision_lines is that it returns the smallest id of the object you're looking for (when multiple instances of the same instance are in your view.

You can get around this by getting getting a new id for each iteration of the binary search operation. Basically each time you search for a collision point in a smaller radius you get the instance that was hit in that step. Another problem that occurs now is what if you have instances of different objects. You'll have sort and draw them in an order from furthest to closest. This seems to be the most logical way of rendering objects at the moment.

To sum up: for each ray you have to scan for all objects added into the engine and get the closest instance of each object. Then sort the instances by distance. If an instance of an object isn't found at the maximum render distance you can abandon it completely within that ray!
Okay well I'll try Juju's tip of collision_line_first, thanks for the help :D
 
Sorry but I'm still confused about how you would actually go about doing this, any chance you could give me the r3d_draw_object code for the updated version?
 

ras maxim

Member
Very interesting project, slojanko. I did an even simplier implementation of raycasting with GMS, too. Will post it soon.

It is interesting to know how you are going to map textures. If walls were aligned to the square grid then you could use the coordinates of the intersection point as a texture offset. But your walls are not necessary aligned with the grid, so I have no idea here. Can you please share you thoughts on this?
 

slojanko

Member
Very interesting project, slojanko. I did an even simplier implementation of raycasting with GMS, too. Will post it soon.

It is interesting to know how you are going to map textures. If walls were aligned to the square grid then you could use the coordinates of the intersection point as a texture offset. But your walls are not necessary aligned with the grid, so I have no idea here. Can you please share you thoughts on this?
Because I'm getting the "precise" point of collision of each ray and know the object sprite origin I can get an estimate for which part of the texture should be visible there.
Imagine having the situation below:

You can get an approx. intersection using the code in my project. The intersection point can then be used to calculate the "part" of the texture (sprite) to be drawn as "intersectionX - object.x" for the image above. Putting in some fancy / and * for getting distance scaling to work and then you're good to go.

I will port this to GMS2 when the IDE becomes usable for me again.
 

ras maxim

Member
Yes, I understand this. On grid-based map you just choose either x or y coordinate as an offset for drawing a wall column depending on wall being either vertical or horizontal. I just don't get how to test what side of the wall the ray hit if the corners are not 90 degrees.
 

slojanko

Member
I am currently porting it to GMS2 and using the path editor for creating "complex" closed shapes. This could be used for the map itself. Regarding smaller objects, I still haven't found a good solution, unless I create a program for making polygons.

PS: I tried sending you a message but your profile is locked.
 

jujubs

Member
Firstly, thanks for sharing your work with us. It looks amazing, and doesn't look so resource-intensive as you say.

That being said, how do I change the port size? I'm trying to draw on a low-res, vertical screen (think of the DMG), but the view just looks like a cropped, mashed up version of the window in your project:
 

Attachments

slojanko

Member
Firstly, thanks for sharing your work with us. It looks amazing, and doesn't look so resource-intensive as you say.

That being said, how do I change the port size? I'm trying to draw on a low-res, vertical screen (think of the DMG), but the view just looks like a cropped, mashed up version of the window in your project:
The version that's uploaded here is quite old, I have an "up-to-date" project on my laptop with better performance and general experience. It might or might not work, but try changing all 3 resolution values in the only existing room (for me it's test_2_room, but I might have renamed it). These are:
- Room size and View 0 dimensions should match if you want the game to take advantage of the whole window.
- Port size on screen under View 0 is basically your scale.

upload_2018-4-8_17-31-22.png

If you're still having trouble I'll send you my project, which should probably be rewritten anyway and not be taken as bug free.
 

jujubs

Member
The version that's uploaded here is quite old, I have an "up-to-date" project on my laptop with better performance and general experience. It might or might not work, but try changing all 3 resolution values in the only existing room (for me it's test_2_room, but I might have renamed it). These are:
- Room size and View 0 dimensions should match if you want the game to take advantage of the whole window.
- Port size on screen under View 0 is basically your scale.
Like this?
also disturbing.png

Those are my current settings, which are the same as my other, 2D rooms (I'm trying to make a bonus level of sorts).

If you're still having trouble I'll send you my project, which should probably be rewritten anyway and not be taken as bug free.
I'd gladly take a look at it, even if we figure things out with the outdated project. That textured wall looks mighty interesting :D

EDIT: I tried changing the views on your Project, and got similar results to what turned out on mine
ohno.png
 
Last edited:
I don't normally post "tutorials" but I'll make an exception and share my work with you.

GM Version: GMS
Target Platform: All (with reasonable CPU power)
Source: download link - Updated
Precompiled: download link - Updated

Summary:
This is a basic tutorial/example of a game using what's called raycasting to generate a 3D view on your environment. This was used in the original Doom and Wolfenstein 3D.

Tutorial:
The code used is currently reasonably documented and is organized enough for an intermediate user to figure out what is happening. The project contains 5 scripts which are appropriately named for better recognition.




The view on the test room is split into 2, one being the 2D view on the actual position of the objects and a 3D representation using raycasting. This works by firing a set of rays that detect collision in the direction of the camera. These rays go across the room while only being generated in your FOV and return a position of its collision. This is then used to calculate how far/close the pixel a ray has hit should be drawn using a vertical line.

Because using a ray a few thousand times per frame is very resource intensive I've decided to limit the density of rays and precision of each ray through 2 script variables attached to the camera's Create event.

Current limitations are:
- can't draw horizontal planes (floor)
- no depth checking - massive performance boost when implemented!
- bad performance!

I would like to thank Rusky for helping me write the code necessary for drawing lines which are used to show a 3D view on the room. Everything else was written by me following tutorials in javascript.
update the link please
 
Top