Custom 3D GUI Game Engine With Textures

Hey all. Today I want to show off something that I’ve been wanting to create for years, which is a 3D game engine with textures that is fully rendered on to a GUI without the use of a ViewportFrame!

This is my fast and yet pretty looking Raycast Renderer entirely made within a GUI.

Screenshots

image


This engine works a lot like older 3D games in the early 90s (such as Wolfenstein 3D and DOOM). This engine uses raycasting to simulate a 3D environment within a 2D top-down map.

To cut it short, a 2D Raycast Renderer is the projection of a 2D map into a “third” dimension. From the Player’s position, a bunch of rays are cast with incrementing change of angle (based on the FOV). The ray travels along the 2D map and returns the distance to the first “wall” it hits. For each ray, a thin rectangle is drawn, with height being determined by the returned raycast distance.


Here’s a list of advanced features that the engine currently supports:

  • Textured walls
  • Fake second level walls
  • Sprites/objects
  • Skyboxes
  • Basic lighting and shading

Here’s a list of planned updates and features:

  • Rework of drawing textured columns to improve both look and framerate. The current method is very messy and hacky.
  • Interactable switches
  • Guns and items
  • Animated textures and sprites
  • (Maybe) Allow rendering walls through other transparent walls (such as windows or fences)
  • Better player collisions (will allow your character to slide along walls when ran into)
  • NPCs or enemies
  • Major optimisation of code
  • (Maybe) Add curved/round walls
  • Removal of slight distortion (most noticeable with higher FOV)

Here’s a simple open sourced version of the renderer without the advanced graphics and textures:


I’d love to hear your thoughts and feedback on this renderer. Thank you!

73 Likes

The Renders Are A Little Cheesy And Old Style But Can Be Used On Minor Details On Any Game Or Maps. Not Bad,

Also Is it Just me or is that door looking like the one in minecraft.

2 Likes

Yeah, fake 3D engines are awesome.

Also I tried my best to make a door that would work with 36x36p. So I chose a medieval design.

2 Likes

Wow, just wow. This has been a concept in my head a couple of years back but I never tried it out. Amazing to see this done!

4 Likes

This is so cool! I wish I could use this, but this version isn’t open source.

3 Likes

It surely does look like old games like doom.

4 Likes

Wow I have wanted to make this for so long!

2 Likes

this is amazing! will this ever be open sourced? if that’s too much to ask.

1 Like

I cannot stop looking at this, the possibilities. I wanna make a full game with this ahaha go you!

2 Likes

Very good made GUI mate!

Thanks to you, I can now finally get Baldi’s Basics vibes in a BIG scale :+1:

2 Likes

All I need to do now is find a way to implement 2D sprites for things like enemies, items, and objects.

2 Likes

Once I get things like 2D sprites and floor/ceiling textures, it might get open sourced then.

1 Like

Augh I love it @Ethanthegrand14!!!
You inspired me into this real time ray tracing game and I’m so glad I’m working to make it better and better with you.

I absolutely love that you’ve taken this idea and pushed it to it’s limits and beyond with textures and a skymap, HOW DOES ANY OF THAT EVEN WORK!?!?

That said, you’re missing a huge component that frankly is such a simple addition, and removes the fish eye effect seen here:
Capture

Before committing fully to my 3D ray tracer I worked on a 2D one like this too! I wrote this bit here which removes the curviture of hard edges.

local Dist = (Character.Position - Result.Position).Magnitude
Dist = Dist * math.cos(CharacterAngle - LookAngle)

Result obviously being the result of the raycast.
CharacterAngle is the angle the character is currently turned toward.
Dist, the output will then be turned into a value that can represent the length of each row.
LookAngle is the specific angle of the row the loop is currently on.

With that bit here’s what the sharp edges on mine look like:

2 Likes

Thank you, RepValor!

How does any of that work you say?

Well I’m not going to go too far into how texture rending works, because that is very complicated and would take forever to explain. Not even I fully understand how a bit of works.
Basically for each column, you want to compare the center of the object that your ray hits, with the position of where your ray has landed. And with that distance between the two positions, you can show a portion of the texture on each column that is shifted depending on the distance between those two positions.

And for the skybox, it is simply a very large image that shifts horizontally depending on the orientation of the player.


And to push more limits, I am also working on optimization and fake multilevel objects and buildings that are basically 2 or 3 maps stacked on top of each-other.

Maybe with the help of your optimization skills, we can fix that :wink:

But because I am raycasting 3 times more than usual, the FPS drops quite a bit. Sooo that’s a work in progress for now.

Ok. I’ve actually attempted this multiple times, but I just simply don’t know what the CharacterAngle and LookAngle fully represent. Do they represent a CFrame axis value? do they represent a vector3 axis value?
Basically I just don’t know what type of angle I should be using here.

Also if it wasn’t for @GreekForge, this textured beauty wouldn’t be here. He helped explain some of the math for rending textures which brought us here!

4 Likes

That’s beautiful!

The CharacterAngle and LookAngle are numbers, like just plain numbers.

If you’re not storing the players direction as a number how are you storing it?

2 Likes

I get that they are numbers, but what I am not getting is the type of orientation that they are.

Are we using roblox’s vector3 orientation where the max angle works like -180 to 180? or are we using a cframe angle offset, or what?

I tried this myself, and all lead to weird outcomes with more distortion.

1 Like

Oh uhm, actually it’s in the form of -PI to PI. So -3.14159 to 3.14159.
However because of the cyclical nature of pi I didn’t even bother to put those limits on it, so my angle starts at 0, when you hold D it is added to, and when you hold A it is subtracted from.
The amount that it changes by is a very small number to coinside with the small range of pi.

For a more comprehensive guide on how I did this, here’s all of the relevent code that goes into my calculations:

local CharacterAngle = 0

function GetFovFromIndex(Index)
    local x1 = Index
    local x2 = Settings.Resolution
    local y1
    local y2 = Settings.FOV

    local a = x1 * y2
    local y1 = a / x2

    return y1
end

game:GetService("RunService").RenderStepped:Connect(function(Delta)
    for i = 1, Settings.Resolution do
	    local Pixel = -- get current row
	    local FovPart = GetFovFromIndex(i)
	    local LookAngle = CharacterAngle + math.rad(FovPart - Settings.FOV/2)
	    local Direction = Vector3.new(math.cos(LookAngle), 0, math.sin(LookAngle))
	    local RayParams = RaycastParams.new()
	    RayParams.FilterDescendantsInstances = {Character}
	    local Result = workspace:Raycast(Origin, Direction * Settings.RenderDistance, RayParams)
	
	    if Result then
		    local Dist = (Character.Position - Result.Position).Magnitude
		    Dist = Dist * math.cos(CharacterAngle - LookAngle)
		    Pixel.Size = UDim2.new(Width, 0, 1 / (Dist*0.1), 0)
	    end
    end
end)
1 Like

Hey uhh, What does FovPart Represent?

Also is there an alternitive to Pi? or is there a way to convert radians or degress to pi? Cause I know nothing about Pi

1 Like

FovPart is this variable I just made above it.

local FovPart = GetFovFromIndex(i)

using the only function I have listed.

also yeah you can totally convert degrees, which is what roblox’s vector3 uses, into radians, which is what pi uses.

If you want to convert a degree number to a radians number all you have to do it say math.rad(MyDegrees) and if you want to convert a number in radians to degrees simply do the reverse: math.deg(MyRadians)

I’ve tried this, and nothing seems to have changed:

local Distance = (ViewRayOrigin - ViewRay.Position).Magnitude -- ViewRay is the RaycastResult

-- Remove that fisheye effect		
local RayAngleX, RayAngleY, RayAngleZ = RayAngle:ToEulerAnglesXYZ()	
local PlayerAngleX, PlayerAngleY, PlayerAngleZ = CameraPart.CFrame:ToEulerAnglesXYZ()	
			
Distance = Distance * math.cos(math.rad(PlayerAngleY) - math.rad(RayAngleY))
1 Like