Cruelty Squad

Cruelty Squad

Not enough ratings
Some advanced mapping tricks
By wget
here's a guide for how to implement a bunch of the elements present in my Graduation Day[crus.cc] map into your own map
   
Award
Favorite
Favorited
Unfavorite
Intro
btw this isn't really a mapping trick, but in case you didn't know, here's a mod that lets you install pretty much every single available map rn all at once:
(to get the latest version, download it from the github link instead of the mediafire link)
https://crus.cc/mod/infinite_levels_patch_for_crus_mod_base/
normally the CruS Mod Base will take a long while to start up and usually crash your game when you install too many maps, but this lets you install as many maps as you want with no limit



i will assume that you already know the basics of mapping for this game
if you want starter guides, you can read trashki's and keith mason's guides:
https://hackmd.io/@OsM6oUcXSwG3mLNvTlPMZg/SkYQwbONu
https://steamproxy-script.pipiskins.com/sharedfiles/filedetails/?id=2628055129

those may be a little outdated as of writing, but they should still be mostly good
a few things i can think of that you shouldn't follow from those guides:
1) do not follow the step in trashki's tutorial to install "Project Setup Tools" because it's no longer necessary and can break things
2) you have to install both "Qodot" and "GodotSteam GDNative" from godot's AssetLib because of the latest crus update
3) do not edit your map's tscn file in a text editor as keith mason's guide advises, because if you've set up your decompiled crus project correctly, then you can simply edit node properties in the Inspector at the right side of your screen in the editor

this guide isn't supposed to be super rigid, it's just a way to let others know about things they can do for their custom cruelty squad maps
this is supposed to be a jumping off point and i encourage you to experiment with things yourself

IMPORTANT: all file paths i mention in this guide uses the notation that godot uses
https://docs.godotengine.org/en/3.5/tutorials/io/data_paths.html

that means a file path like res://Maps means the path to the "Maps" folder inside wherever you're keeping your decompiled Cruelty Squad project (it's where the project.godot file is)

and a file path like user://levels translates to %appdata%/Cruelty Squad/levels

ALSO IMPORTANT: please please PLEASE PLEASE make sure you keep letter cases in mind when you type in file paths
a folder named "Sfx" should ALWAYS be written as "Sfx" and NEVER as "sfx" or "SFX"
a folder named "levels" should ALWAYS be written as "levels" and NEVER as "Levels" or "LEVELS"
THIS GOES FOR FILE EXTENSIONS TOO:
a file named "example1.png" should ALWAYS be written as "example1.png" and NEVER as "example1.PNG"
a file named "example2.PNG" should ALWAYS be written as "example2.PNG" and NEVER as "example2.png"
EVEN IF YOU THINK IGNORING CASES WORKS FINE FOR NOW, IT WILL EVENTUALLY BREAK BECAUSE GODOT'S VIRTUAL FILESYSTEM IS CASE-SENSITIVE
SIMPLY STAY CONSISTENT TO SAVE YOURSELF THE HEADACHE
Custom textures (Part 1)
There's 3 different ways to include custom textures in your map:
1) Using a zip file inside your level's folder
2) Embedding textures in your level's scene file
3) Storing them as individual files in your level's folder

The 3rd way is the way I use for Graduation Day, but you need to go through the effort having to edit a lot of *.png.import files to have them point to the correct file paths
Doing that manually is insane so I wrote a script that does it for me (unfortunately only works on linux)

The 2nd way involves using the "Make Unique" option in the godot editor to embed texture data into your map's tscn file
This allows you to keep your map assets stored in a single file, but it's pretty inefficient for your map's file size since you're storing binary data in a text format

The 1st way is the most sane and easiest way to implement custom textures for your map, so this is the way that I will actually teach you

First, you should make things significantly easier for yourself by keeping your png files in the same folder to keep things organised
I recommend keeping them in a folder like res://Maps/textures/<levelname>
with "<levelname>" being a name that's unique to your map (to avoid conflicts with other maps and mods)

OPTIONAL: Disable ETC2 compression to halve the total size of files you need for custom textures and to significantly speed up importing of textures
1) In the title bar, click on "Project", then "Project settings..."
2) Go to "Rendering" > "VRAM Compression"
3) Untick the "Import ETC2" property
4) Save and restart the project

To actually use custom textures in the editor, here's an example of changing the textures of a civilian npc:
1) Right-click on the npc's node and turn on the "Editable Children" option

2) You now have access to the npc's MeshInstance nodes, this is where you can change the npc's textures, now select whichever one you want to change the texture of

3) In the Inspector node at the right side of your screen, open the "Geometry" category and right-click on the resource in the "Material Override" property
This gives you the option to either create a ShaderMaterial resource or a SpatialMaterial resource
For now, I'll choose ShaderMaterial

4) You have now created your very own Resource: an empty ShaderMaterial
Click on it

5) Click on the empty "Shader" property then click on "Quick Load"

6) Select "realpsx.shader" (this is the shader that makes npcs look wobbly)

7) Open the "Shader Param" category that shows up, then click on the empty "Albedo Tex" property, then click on "Quick Load"

8) Now go find the image file that you want to use, for this example I'm using the jerma.png file that I stored in the folder res://Maps/textures/jermasbasement

9) There you have it, this npc now uses a custom texture

10) If you want to use a SpatialMaterial instead of a ShaderMaterial, then the process is mostly the same except you don't set a shader resource and you set the texture through the "Albedo" category's "Texture" property


You need to know to differentiate between "materials" and "textures", because these two things are NOT synonymous
You need to understand what a Resource is in Godot: https://docs.godotengine.org/en/stable/tutorials/scripting/resources.html
TLDR: Basically they're Godot's data containers, they can be anything from textures or shaders or meshes or scripts or scenes or etc
The material override property of a MeshInstance node takes a Material resource, and both ShaderMaterial and SpatialMaterial are different types of a Material resource
Both of those material resources have an albedo texture property that takes a Texture resource, and when you use "Quick Load" to load a texture you're typically creating a StreamTexture resource from whatever png file you chose

If that sounds complicated to you, then what it essentially means is that:
- a texture resource is what contains raw image data
- a material resource contains a reference to a texture resource and information about how it should be rendered

Unless you want the material resource's properties to be different (such as using a different shader), then you shouldn't repeat the process above when you want a different MeshInstance to use the same material (such as from the Head_Mesh to the Torso_Mesh)
Instead, you should right-click on the MeshInstance's "Material Override" property name and click on "Copy Property"
Then go to the other MeshInstance and right-click on its "Material Override" property name and click on "Paste Property"

This allows you to copy a reference to the same material resource instead of creating unnecessary duplicates of the same information

Because that reuses the same single material resource, it means any change that you make will affect all MeshInstance nodes that use it
If you actually want changes to a node's resource to affect only that node, then right-click on the resource and click on "Make Unique"
Custom textures (Part 2)
Now that you know how to use custom textures, you need to know how to actually share them along with your level for other people to play

Most custom maps so far rely on instructing the user to install a separate mod that contains their map's custom textures
However, you generally want users to be able to just drop your map's folder into user://levels without any additional steps
It's best to keep things simple for the user (because they're freaks and will mess up instructions so you should give them as few instructions as possible to minimise the chances of failure)

Out of the 3 ways I've mentioned in the beginning, I'm choosing the easiest way to include custom textures with your map: Using a zip file in your level's folder

Going off of our example from before, the custom texture is a file named jerma.png stored in the folder res://Maps/textures/jermasbasement

You'd think that we'd simply need to zip up just one file with this path:
Maps/textures/jermasbasement/jerma.png
But you'd be wrong

In fact, you don't need to share the jerma.png file at all, Godot doesn't actually use png files for textures, instead it automatically converts images into a format that's far more efficient for your graphics card to use

What you actually need to share in the zip is the jerma.png.import file right next to jerma.png, and the jerma.png-1e08ab17a5f3086369aba230610bb995.s3tc.stex file in the res://.import folder

The *.png.import file is a text file that contains a path to the *.png-<hash>.s3tc.stex file, which is the file that stores the actual raw texture data

There's gonna be a lot of other files in the res://.import folder, but it shouldn't be difficult to find the file that you need to share for your level's custom texture
The beginning of the filename is the same as your png file's name, and it will always end with s3tc.stex as its file extension
If there's multiple s3tc.stex files that start with your png file's name, then choose the one with the latest modification date
(or read the png.import file to see the exact name you need, but it's usually gonna be the latest one)

So for our example, our zip file will contain the following:
Maps/textures/jermasbasement/jerma.png.import
.import/jerma.png-1e08ab17a5f3086369aba230610bb995.s3tc.stex

You can download this if you wanna check out the folder structure: https://gist.github.com/wgetJane/2be1b20bc50c1123037eada8cbc17b9d/raw/examplelevelcontent.zip

When you have many multiple custom textures, then you need to do all of this for each and every texture you've used, but it's actually not as difficult as it sounds, you'll get used to it

Where to actually put the zip file is in the same folder where your level.json is, simply plop it in there and there you have it!
Users can now install your map along with its custom textures by simply putting your map's folder into their user://levels folder, in one simple single step

The zip file can have ANY name, and you can even put multiple zip files into your level's folder

Bonus tip: Because you can put multiple zip files into your level's folder, if your map has a mod dependency like "mapbase" or "Ringo's Mapping Enhancements" or whatever, then you can actually just put their mod.zip folder into your level's folder to make your level's installation even easier
Fixing bloated scene size from duplicating NPCs
TBA
Compressed level scene
Your level's scene file, which has a file extension of "tscn", is actually a "text scene" file, which stores your scene's data in a text format

Having scene data in a text format is useful for when you want to make changes to it in a text editor (which I don't recommend AT ALL unless you REALLY know what you're doing)

You can save your scene with the file extension of "scn" to use a binary format instead, which stores scene data significantly more efficiently

Ideally, when you're gonna share your map, you should share it as a scn file instead of a tscn file

To do this, in the title bar go to "Scene" then "Save Scene As..."

Then change the file extension from "tscn" to "scn" before saving
Now you'll have a compressed scn file that you can share instead of your tscn file
You also need to change the file extension from "tscn" to "scn" in the "level_scene" property of your level.json file

This shrinks the file size of your level's scene dramatically by many magnitudes
(for example GraduationDay.tscn is 2.2 MB while GraduationDay.scn is merely 246 KB)

This is also much better for your level's load times especially on a hard drive and especially when your map has a ton of data that's easily compressible and decompressible (like level/mesh geometry)

I've squished down 100 MB scenes down to 1 MB through compression, it really can make a massive difference
Reducing 3D clutter in the editor
The player node in your level is gonna look like this in the editor

I find this quite annoying to work with, so let's fix this

Open the res://Entities/Weapon.tscn scene in the Godot editor


You can toggle the visibility of a node by clicking on the little eye icon to the right of the node's name:


Hide the following nodes then save the scene:
Kicksound
Batonsound
Kicksound2
FT_Sound
RayCast
Collision_Ray
Sniper_Barrel_End
Sniper_Tracer_Ray
Use_Raycast
Player_Weapon
Alert
Laser_Dot
Front_Pos_Helper
Object_Hold_Pos
Radiation_Area
Radiation_Light
orbarms
JetPack

Now this looks a LOT better:

We can go a little bit further by hiding the weird stuff around the player

Open the res://Entities/Player.tscn scene in the Godot editor


Hide the following nodes then save the scene:
Top_Checkr
Crush_Checkr
Crush_Checkr2
Top_Check
Crush_Check
CollisionShape
CrouchCollision
Stair_Ray_Front
RayCast
Ray_Rotation
Foot_Step
Gunksound
Boostjump
Particles
Kicksound
Armorsound
Soundrotator
LeanRay
Aim_Point

We finally end up with this clutter-free player node:


Another thing that bothers me is the really long lines that will protrude all over the place which originate from npcs:
Open res://Entities/Bodies/Grunt_Body.tscn and hide the nodes "Player_Ray", "Velocity_Ray", and "Muzzleflash" and it'll get rid of that clutter:
(this doesn't cover every npc, i can't be bothered to list them all down, you can just do the same process for each of them)
Door swing direction
Let's have these 4 doors, with their hinges all on the left side (from the perspective of being in the centre)


And let's also say we want them to open away from the centre, like so:


However, when they're actually opened, it looks like this:

As you can see, doors in CruS only open clockwise and assumes the right side is where the hinges are (from the world's perspective, facing north)

Let's work on the north door first, the issue here is that the hinges are supposed to be on the left side of the door:


First, make sure that grid snapping is enabled
The little magnet icon here should be blue (click to toggle):

(note that you can tell direction from the 3D handles, the red arrow always points north while the blue arrow always points east)

You should also set a good grid snap interval, go to "Transform" then "Configure Snap..."

I like to set it to 0.125 units, which translates to 2 units in Trenchbroom (if you're using the default "Inverse Scale Factor" of 16 in your QodotMap node)


Now, make sure that this icon is blue (click to toggle) so that we can move our selected node(s) around in 3D:


Have the north door node selected:

Then grab the blue arrow handle (click and hold it down with your mouse)...

And drag it to the left until the door's right edge is exactly where its left edge used to be:

(Tip: while you're dragging your selection in 3D, you can see how many units you're moving it by at the bottom-left corner of the 3D screen, and you can also cancel it by right-clicking)

Now select the door's children (both the MeshInstance and the CollisionShape):

Then grab the blue arrow handle...

And drag it to the right until the door looks like how it should look:


What this achieves is it moves the door's origin point to the left:

So now, in-game, the north door rotates at its hinges like it should:

However, we still want the door to open away from the centre, so it should rotate counterclockwise instead of clockwise

With the door node selected, these are currently the only properties under "Script Variables" that we can edit in the Inspector:

But the door's script resource actually has more secret properties that we can expose

Click on the script icon to the right of the door node's name:

This brings up the scripting screen:

Don't be scared if you're not a programmer, we're not gonna stay here for very long

You only need to change this line that declares the "open" variable:

Add the word "export" at the start of the line, then save with Ctrl+S


This exposes the "Open" property for all door nodes in the Inspector:

(you only need to do this once and it affects all doors in any future map you'll work on)

Now tick this checkbox for the north door, and then in-game the door will now rotate counterclockwise:

With the north door fixed, we can move on to the 3 other doors

The west door doesn't rotate at its hinges so it needs the same treatment as the north door: its origin point needs to be moved and its "Open" property needs to be enabled
The east and south door properly rotate at their hinges but they open towards the centre: the only change they need is to have their "Open" property enabled

Now all the doors rotate away from the centre like we wanted


You can download this scene file if you wanna see the doors in action yourself: https://gist.github.com/wgetJane/2be1b20bc50c1123037eada8cbc17b9d/raw/doorsexample.zip
Nodraw textures
Using nodraw textures is a good way to optimise your level because it prevents Qodot from generating unnecessary brush faces on your level's mesh

The benefits are:
- Better framerate, because the engine has a lot less faces to render
- Smaller file size, because of the smaller polycount
- Simpler and faster navmesh, because Godot won't bake cells onto all the unreachable areas
- Higher quality lighting data for baked lighting, because atlas space isn't wasted on faces that will never be lit

First you need an image to use as your nodraw texture, here's what I use:

I like using this because the gradient makes it easier to tell brush edges and the depth of the surfaces

Save it to somewhere in your res://Maps/textures folder
For this example I'll store it in res://Maps/textures/example/nodraw.png

In your level scene's QodotMap node's "Brush Clip Texture" and "Face Skip Texture" properties, enter the nodraw texture's file path
(relative to the "Base Texture Dir" property, which should be res://Maps/textures)
(also without the png file extension)

(btw if you didn't know, you don't actually need to enter the full file path of your map file in the QodotMap node's "Map File" property, so like if your map file's path is res://Maps/example.map, then you can simply enter "Maps/example.map")

Now when you build your map with Qodot, all the brush faces that use the nodraw texture will be ignored

If you don't know how to actually use nodraw textures in your map, the principle is basically to only texture the brush faces that will be visible to the player, while everything else needs to be nodraw

The best way to implement this principle is to build your map's geometry with nodraw brushes first and then only texture it afterwards when you know which faces should be drawn
(This doesn't mean that you need to complete the ENTIRE map's geometry before you should start texturing, I just mean small parts of the map geometry like individual rooms and such)

As an example, I will build a simple room in Trenchbroom with a total of 8 brushes:
- a floor
- a ceiling
- four walls
- a pillar in the middle
- a small crate against a corner

Floor and ceiling and three of the four walls...

And then the rest

Now I have this neat little room with every face of every brush entirely textured with nodraw

The total number of brush faces present here is 48, but I'm not gonna texture most of them because only a fraction of them can even be seen by the player

Let's count the brush faces that can be visible to the player:
- the top face of the floor brush (+1 face)
- the bottom face of the ceiling brush (+1 face)
- the face of each wall that is facing towards the room (+4 faces)
- the faces of the pillar except its top and bottom faces (+4 faces)
- the faces of the crate that aren't touching the walls or floor (+3 faces)
That's a total of 13 faces, so that's the only number of faces that I will apply textures to

Any brush face that is facing towards the void or is completely touching against another face doesn't need to be textured because the player will not be able to see it

Read this section of the Trenchbroom manual to learn how to select individual brush faces and how to copypaste textures to other individual faces:
https://trenchbroom.github.io/manual/latest/#assigning-textures-manually

After texturing, here's how the inside of my room looks:

And here's the outside:

Without the ceiling and 2 walls, you can see some of the faces of the pillar and crate that I didn't texture:

Without the floor, here's a view from below of the bottom faces of the crate and pillar that I didn't texture:


You can download this Trenchbroom map file here: https://gist.github.com/wgetJane/2be1b20bc50c1123037eada8cbc17b9d/raw/nodrawexample.zip

Now back in the Godot editor, and because I've set up my QodotMap node to recognise my nodraw texture, it now builds my level's mesh with only 13 faces:
Occluder nodes (Part 1)
What you need to keep in mind is that just because you literally can't see something on your screen, it doesn't mean that it's not being drawn

For example, if you have an npc behind a wall, then you obviously can't see it


However, believe it or not, your computer actually still draws that npc


The only reason you can't see the npc is because the wall gets drawn above it on your screen

If you don't get it, here's a real-world analogy: imagine painting a graffiti on a surface before immediately painting over it with white paint, so now the only thing you can see on the surface is white paint as if the graffiti was never even there, but that obviously doesn't mean that you undid the act of painting the graffiti, you just can't see it anymore, you can't "unspend" the time and effort and materials spent on painting the graffiti *after* the fact

This means that your computer is wasting valuable computation time on drawing something that you literally can't even see, and this becomes even more wasteful the more stuff gets drawn behind walls

Now it's not too bad, because the Godot engine performs what is called frustum culling, which basically means that stuff that's outside the player's camera will not be drawn
(so for example, an enemy behind the player will not be drawn)

But while frustum culling helps with your framerate immensely, it still doesn't really solve our issue of npcs being drawn behind walls

What we need for that is occlusion culling, which basically means to not draw stuff that's being obscured by something else

The Godot engine has a rooms+portals system (if you're familiar with the Hammer editor, it's similar to the areaportals systems in the Source engine)
https://docs.godotengine.org/en/3.5/tutorials/3d/portals/index.html
However, this is a massive pain in the ass to implement because you have to do EVERYTHING manually (because apparently modern game engines struggle with stuff that John Carmack already solved in the 90's with the Quake engine)

Instead, let's turn our attention to Godot's Occluder nodes
https://docs.godotengine.org/en/3.5/classes/class_occluder.html
These are nowhere near as good as a well-implemented rooms+portals system, but they're good enough for our purposes, and they're relatively very simple to use

Fixing Objective Indicators
Because occluders will prevent anything behind them from being rendered, they will actually prevent you from seeing the red target icon through walls

This is not ideal because we typically want the player to able to know where their mission target is at all times

1) Turn on the "Editable Children" option for your target npc's "Body" node:

2) Then select the "Objective_Indicator" node:

3) In the Inspector, go to the "Geometry" category...

and set the "Extra Cull Margin" property to the max value of 16384:


You need to do this for all of your level's target npcs if you have multiple
Occluder nodes (Part 2)
How to manipulate Occluder nodes
If you prefer, you can read the official Godot guide for using occluders here:
https://docs.godotengine.org/en/3.5/tutorials/3d/occluders.html
Or if you want a video guide, here's a video that someone else made:
(set the playback speed to 1.5 since they talk kinda slowly)
https://www.youtube.com/watch?v=VBJeOCJKOPk
Otherwise, I'll try to explain it myself in this section

To create an Occluder node, press Ctrl+A, which will open the "Create New Node" window
Find and choose "Occluder", which should be under Node > Spatial > Occluder


This creates an Occluder node without an OccluderShape resource, and if you click on the little warning icon to the right of the the Occluder node's name, it'll say just that:


Select the Occluder node, then in the Inspector create a new OccluderShapePolygon resource for its "Shape" property:


Now our occluder takes the form of a flat plane


You can move around an Occluder node just like any other 3D object in the editor using the 3D arrow handles (remember to keep grid snapping enabled)
So let's try to give this wall an occluder...


Again just like any other 3D object, you can rotate an Occluder node using the 3D rotation handles

So now we need to rotate our occluder to match the wall's orientation...

Btw you should know that occluders can be also be diagonal and can also be rotated in any axis, so you can use occluders for a diagonal wall or even a floor or ceiling

Also, you'll probably notice a lot of z-fighting between the occluder's purple texture and the wall's texture, but that's completely normal

Since our occluder is too small, let's drag its OccluderShapePolygon's orange point handles to cover the entire wall:




Btw currently Godot has a bug so moving one of the OccluderShapePolygon's point handles makes all of its point handles disappear
https://github.com/godotengine/godot/issues/79939
This is incredibly annoying, but you can get around it by deselecting the Occluder node then selecting it again

Anyway let's move on to giving the 2nd wall an occluder
You can duplicate nodes by selecting them and pressing Ctrl+D, and the duplicate 3D node(s) will be created in the same position

So let's duplicate the Occluder node and align it with the 2nd wall:

Well, this wall is a little bit more complicated, so we'll have to make some changes to the Occluder node's OccluderShapePolygon resource

However, before we do that, there's something important that we have to do first

When you duplicate nodes with Ctrl+D, you don't actually duplicate any of its resources, instead the duplicate node(s) will reference the same resources

That's good if the duplicate node needs to use the same resources anyway, it prevents the game engine from having to store identical resources, which is a waste of your computer's memory

As you can see from this video, both Occluder nodes are actually using the same OccluderShapePolygon resource, so changes to it will affect both nodes:


But in our case, we can't use the same OccluderShapePolygon resource because the shape of both walls are different from each other

Select the 2nd Occluder node, then in the Inspector, right-click on OccluderShapePolygon and click on "Make Unique":

This creates a duplicate of the OccluderShapePolygon resource, so the 2nd Occluder node no longer has a reference to the same resource that the 1st Occluder node has
Instead, the 2nd Occluder node now has its own copy of the resource, so from this point any changes to this resource won't affect the 1st Occluder node

With that out of the way, let's work on this occluder's shape to better fit the shape of the wall

This isn't good, the wall's shape has 5 sides, but our OccluderShapePolygon only has enough points to make 4 sides

In the Inspector, click on OccluderShapePolygon to reveal the resource's properties
Then click on the "Polygon Points" property's "PoolVector2Array" to reveal the array's properties


Increase the array's "Size" property from 4 to 5 and this will cause a 5th point handle to appear:

Now the shape can properly fit the wall:

You can use these points to make any sort of convex 2D polygon for your occluder's plane, but you'll usually be using rectangles

We're not done yet, because, if you haven't noticed, there appears to be a gaping hole in our wall

Because we can see through this doorway, we can't allow the occluder to cover it

In the Inspector, increase the OccluderShapePolygon's "Hole Points" array size from 0 to 4:

This causes 4 little blue point handles to appear:

So now you can move the points to make a hole in the occluder that fits the doorway:


There you have it, you now know how to manipulate occluders in the editor
Occluder nodes (Part 3)
The theory of using Occluder nodes
Before you put occluders into practice, you'll first need to understand how to use them in a way that actually helps with your level's visibility optimisation

Using occluders in your level has the potential to dramatically increase your framerate, even a single occluder can make a massive difference

Generally, the effect of occluders on your fps has diminishing returns, which means that the first few occluders that you use will usually have huge gains, but the fps gains start to get smaller and smaller as you add more and more occluders, until you add too many and it actually starts to decrease your fps

Occluders increase your fps because the cost of calculating the occlusion is outweighed by the cost of rendering (which is relatively expensive)

So if an occluder isn't blocking anything from being rendered, then it's actually wasting computation time (though it's pretty cheap so you shouldn't really worry that much)

Now for you to learn the theory, let's start with a simple example, imagine a top-down view of a level that's one big room with a big wall in the middle:

(white = visible and playable space)
(black = filled with unreachable void; blocks line of sight where it meets with the white space)

This room looks empty, but let's just imagine that it's filled to the brim with all sorts of exciting stuff like npcs and props and etc which all cost a lot of rendering time for your computer to draw

You've probably already figured it out: we should place an occluder along the middle wall like so:

We don't need to place occluders for the walls that enclose the room, because there is nothing but void outside of it, therefore nothing to be occluded

Now let's try an example that requires multiple occluders, so instead of a wall, the room has a big fat pillar in the middle:


If you thought that we needed 4 occluders at the edges of the pillar, then you thought wrong

Remember, occluders can be rotated diagonally, they don't need to be perpendicular

And because the pillar contains only void, it's ok to have occluders across it

So we can use 2 occluders going from corner to corner like so:


Now let's try an example with multiple rooms:


Placing an occluder like so will prevent most stuff in the right-side rooms from rendering while you're in the left-side room (and vice-versa), and you need to make a hole in the occluder for the doorway:

You'll notice that the occluder extends into the void: this is completely fine

In fact, you SHOULD have your occluders be extending far into the void
(up to a reasonable degree, just so it's not annoying to work with)

That's because occluders work by checking the bounding boxes of drawable stuff, which might extend past walls or even the floor, so making occluders extend into the wall/floor/ceiling is good to make sure that they block everything

Finally, place 2 more occluders like so:


Now let's try an example with a room that has multiple entrances from the same side:


Because an OccluderShapePolygon can have only one hole, we'll need 2 occluders to separate the left-side room from the right-side rooms:

You'll notice that these 2 occluders overlap each other: this is completely fine

In fact, you SHOULD have your occluders be overlapping each other, again for the same reasons as letting your occluders extend into the void

If you had 2 occluders just only touching their edges instead of overlapping, then they won't block stuff around where they're touching, so you need those occluders to have as much overlap as there can reasonably be

Finally, we just need an occluder between the two right-side rooms:


Now we've been looking at occluders from a top-down perspective, but remember that occluders can be rotated in any axis, which means that you can use horizontal occluders against the floor or ceiling

So if you have a basement below a room or a second floor above it, then you should put a horizontal occluder between them at the floor/ceiling (with a hole where the staircase or ladder or elevator is)

Horizontal occluders can make a really big difference if your level has an entire underground section below it

Also, occluders are completely dynamic, they can be toggled on and off or even attached to a moving object

Unfortunately, Ville (the author of CruS) pretty much utterly ignored "signals" in Godot (a very powerful feature that we'll get into in later sections), so we can't easily do neat optimisation tricks like having an occluder that toggles on and off when a door is closed or opened

An example of what you can still do to take advantage of the fact that occluders are dynamic is to parent an occluder to a destructible wall, so that it blocks visibility until it's destroyed

You can download this scene file if you wanna check out how I've used occluders for a simple example level:
https://gist.github.com/wgetJane/2be1b20bc50c1123037eada8cbc17b9d/raw/occludersexample.zip
I've scattered around a bunch of meshes that will render through everything else so that you can see the occluders doing their work in action
As you walk around in the example level, you'll notice meshes are sometimes popping in when it looks like they should be occluded, so the occluder system isn't perfect, but it's a lot better than no occlusion at all
Also, this example has a LOT of occluder nodes, but you often don't need this many to greatly improve the fps in your level, because like I said it has diminishing returns, you need to figure out which parts of your level will benefit from occluders the most

(note that this whole specific theory of using Occluder nodes assumes an absence of an implementation of the rooms+portals system, in that case then you'll be using occluders in a completely different manner in conjunction with portals, where they'll mostly be reserved for free-standing objects inside rooms)
Groups and layers in Trenchbroom
if you're seeing this then im currently working on this section, stop reading NOW

asdfasdf

Layers are really useful for working on a level with many floors in Trenchbroom because you can toggle their visibility to make things much easier to work with

asdfasdf

Unfortunately, Qodot completely ignores the layer and group names that you set in Trenchbroom, so the resulting nodes in Godot will just be named numerically which is annoying

asdfasdf

In your level's QodotMap node, you can enable the "Use Trenchbroom Group Hierarchy" property to

asdfadsf

In Trenchbroom, you can group a single brush or multiple brushes together with Ctrl+G, and this is useful for different reasons, like it makes it so that you don't need to individually select every brush whenever you want to move a bunch of brushes

Outside of Trenchbroom, Qodot separates the mesh and collisions of a group of brushes from the "worldspawn" StaticBody and into its own StaticBody node

This can be a useful optimisation trick because now that that mesh is separate from the rest of the level's mesh, it can be culled with frustum culling and occlusion culling

However, I wouldn't rely on that, because it's a very time-consuming process to split up your entire level into little groups
For my Graduation Day map, I did do that and I moved every resulting MeshInstance and CollisionShape into the "worldspawn" StaticBody so that there isn't way too many StaticBody nodes, and that was a lot of effort just for a very small optimisation

If you really want to use brush grouping to optimise your map, then at most I would only recommend grouping up the little parts of your map that has complex geometry and fits nicely inside their own bounding box, such as staircases, railings, statues, etc

Another use of brush grouping is for creating our own custom brush entities, which we'll get into in later sections of this guide
Psycho Shock Troopers (custom enemies)
TBA
Door that opens when you press a button
TBA
Door that opens when objective is complete
TBA
NPCs that can see through glass
TBA
NPCs that can climb stairs
TBA
Baked lighting
TBA
3 Comments
wget  [author] 29 Dec, 2024 @ 4:59am 
@Jolly Jsoupthefirst
i REALLY should've finished this a year ago when i could still remember most of this, now i have to re-learn everything
Jsoupthefirst 28 Dec, 2024 @ 4:47pm 
Any day now...
femboybussyslayer 21 Aug, 2023 @ 4:06am 
AWESOME. PERFECTION!!!!!!!!!!!!!!!!!!!!!!!!!!!!!