Skip to main content

Making Your First Level

As mentioned earlier, I'll personally be using GitHub to host and review this project, it'll also allow you to copy it to look at things in a much closer fashion so you can adapt stuff into your own project. I'll be basing it from the OpenGOAL mod base available here. I'd suggest you use this one as a template.

Here's a link to the example folder with a copy of everything that'll be done.

Every step will be set as a different commit that'll allow you to see the changes that have been made.

Extracting and Testing the Base Game

We will first extract Jak 1's ISO and test if the game starts correctly. If you're only used to the launcher, all you need to do is to have both the folder with your game's ISO and the main folder of your project with the extractor binary open, then drag and drop the ISO on the extractor.

It should open a console and show you the extraction progress, then compile and start the game automatically.

If everything seems fine, just close up the game and extractor console if it's still open.

File Setup

While it's possible to just edit the base test-zone level, it's important to know how to create your own, especially if you plan on creating multiple different levels within the same mod.

However, we'll copy the files that are already existing and edit them, as that's much easier than writing them from scratch.

Navigate to "custom_assets/jak1", copy the test-zone folder, and rename it to the name of your custom level.

Enter that new folder and change the name of the files according to your level's name. Take note of these as they'll be really important later.

The .gd File

The .gd file, myfirstlevel.gd in my situation, will create the definition for our DGO file, AKA our full level file. In short, we'll have to put everything that needs to be included inside of our level here.

For now, we'll just have to change a few names as such:

Link to the commit: d05e1aa0aabff43dc310790998bac490f43f8440

The .jsonc file

The .jsonc file, my-first-level.jsonc in my situation, has multiple purposes:

  • Telling the compiler which 3D file will be used to create the geometry of our level
  • Setting up some automatic wall detection
  • Adding art groups so the corresponding models will be visible
  • Adding textures that are not included by default (e.g. zoomer HUD textures)
  • Adding custom models
  • Defining all the actors in our level

We'll keep the current automatic wall detection settings for now. More about those in the Advanced Collisions part of this guide.

The actors are all the interactive things in the level that aren't geometry, so every collectable, moving platforms, enemy but also particles, sound effects, etc. More about those in the Common Actors , Advanced Actors , and Particle Systems parts of this guide.

For now we'll just edit the names so the compiler uses the right files as such:

Link to the commit: aba4bd98f52212180ddd8df160a5fdebb219869f

Setting Up to Build the Level in game.gp

We'll skip over the .glb for now, as there's some other code we need to add and edit for the compiler to even use our new files.

Once again, we can copy the already existing test-zone and edit the names to ours.

Location: goal_src/jak1/game.gp

Line: 1621

Link to the commit: 24bd68c09a25ab07d9a23902d5990f795a95fc3a

warning

If you ever edit the game.gp file while the compiler is running, you'll need to exit and relaunch it or you'll get a compiling error. This can also be accomplished with the command (reload) instead of quitting.

Creating the Level in Blender

We'll now open Blender to create our base level and export it. After opening it up, I'll use these quick shortcuts to clean the scene of the default objects:

A (to select every object in the scene)

Del (to delete selected objects)

Placing Your Level in the World

The placement your level geometry has in Blender will be used to place it automatically in the world. It is this crucial to know where to place things in Blender correctly.

Even if you plan on unloading every other level when playing your custom level, the triggers to load other levels are always there and you risk hitting them as you move in your custom level.

Coordinate Axes: OpenGOAL vs. Blender

While "1 meter" in blender will equal "1 meter" in Jak 1, the two use a different coordinate system.

In Blender, up is equal to +Z.

In OpenGOAL, up is equal to +Y.

This means the axes are rotated on the X axis by 90 degrees.

All you really need to know is when you convert coordinates between the two, you'll need to do this conversion:

Jak 1Blender
XX
YZ
ZY (inverted)

The "Stats > Target" Method

One way to find a suitable location is by simply going to that place with Jak and copying the positions from there into Blender.

This is a method I personally do not use that often, but some do prefer to do it this way, so including it here is still important.

To access the target's position, AKA Jak's position, you'll first need to restart the game in debug mode. To do so, press Alt to bring up the top bar and select Debugging > Reboot in Debug Mode!

Next thing to do is to open the in-game debug menu with R3 or the corresponding input on your device and going to Stats… > Target and activate it.

Press R3 again to close the debug menu. You should now see a bunch of statistics about Jak.

The one we're looking for are the 3 numbers after pos: on the fourth line. You can then move Jak to the desired position for your custom level. Copy those values and apply the coordinate conversion explained earlier as such:

This is the position that needs to be set in Blender for an object to appear at that exact same position:

The "Importing Level Background" Method

My preferred method to place my custom level in Jak 1 is to import a level's geometry into my Blender file directly. These will be positioned correctly within Blender. Which means you can just place your level in relation to that 3D file, giving you a better idea of how it'll fit there.

I'll first start by importing village1_background.glb and training_background.glb into my Blender file so I can locate an adequate location.

Note that you might have to adjust the view end clip so the levels don't disappear from the view because they're too far away. I usually use 10000 meters.

I'll use this position in-between Geyser Rock and Sandover Village as there shouldn't be anything in the way and plenty of room for my testing.

Jak 1's ocean height is at exactly Blender Z = 0 (in-game Y = 0). So keep that in mind when making your level as there's no water plane that's imported when importing the level files.

Create Your Level's Geometry

As said plenty of times, this guide isn't going to go over how to use Blender in detail to create your level. I'll still give you some specific tips regarding OpenGOAL level making but you'll still need to learn a lot about Blender on your own.

I'll suggest starting from very basic shapes or importing other assets. However, importing other assets might come with some specific problems linked to those, so you should start with basic shapes created directly in Blender to begin with. You can get fancier with it when everything is well in place and working.

Size Matters

As mentioned earlier, 1 meter in the game is equal to 1 meter in Jak 1. However, the scale of that meter is off compared to real life. 1 meter in the game is quite small. Jak, for example, is around 3 meters tall when using the game's units.

This means you'll always have to keep in mind that those units are about twice as small as they should be. If you're importing or creating something with "correct" size, you'll usually have to scale it up by a factor of 2.

I'll create a platform that's 30 by 30 meters to get enough room to move around and place other things on later.

Collision and Normals

The Jak games work on a single sided collision system. This means that when interacting with e.g. a wall, you'll get blocked one way, but, going the other way, you'll be able to phase through the wall.

The way the game decides which side of the face is solid is using its normal. To see if the normals of your level's geometry are correct, you can check the option "Face Orientation" in the "Viewport Overlays":

Once that's turned on, you can look at your model, the side that is blue is the side that Jak will interact with and the red one is the one he'll phase through.

As you can see, my platform's normals are correctly set, but the imported level's normals are all over the place.

A quick fix that'll work for most objects' normals is:

  • Go into edit mode.
  • Press A to select all vertices.
  • Press M and select "By distance" to merge overlapping vertices.
  • Press Alt + N and select "Recalculate Outside" to recalculate the normals so they're facing outside of the model.

Here's Samos' hut before and after that quick fix:

Some normals are still facing the wrong way, but it'll still save you a lot of time to start from there and finish the rest more manually.

Once you're done with correcting normals, you can disable "Face Orientation" again, do not just deactivate the viewport overlay as that'll hide other useful things.

Triangle Density

Jak 1's collision system has a cap with how many triangles you can interact with. Importing or creating meshes with a high density of triangles will at best make Jak ignore collision and at worst crash the game when you interact with it.

For now, try to use models with a pretty low triangle density. You'll be able to still use high density meshes at some point. More on that in the "Advanced Collisions" part of this guide.

Exporting the Level's Geometry to the .glb

Once your level is "done", we can now export it. You'll always be able to adjust things and export it again to test your changes, so don't worry about having everything perfect on the first go.

Objects You Don't Want to Export

If, like me, you have imported the base game's level geometry into your Blender file, you won't want to export it in the .glb or that geometry will appear when you load your level.

To only export some parts of the Blender file, I'll suggest using different collections in the outliner.

This trick can also be used to create multiple levels within the same Blender file, so you can line them up perfectly.

The other solution is to simply delete the parts of the file you don't want to export, which is what I'll do as my Blender file will be available online with the rest.

Exporting Settings

Go to File > Export > glTF 2.0 (.glb/gltf).

You can then click on the cogwheel to the top right of this window to open the exporting settings. Here's the settings I'll use:

Here's the reason for each of them:

  • glTF Binary (.glb) is the format used by the compiler.

  • Remember Export Settings is pretty self explanatory, it'll save these settings so you don't have to always edit them for every export.

  • Active Collection is only needed if there's part of the level you don't want to export as explained in the previous point. Since I've deleted the part I do not wish to export, I don't need to have this selected.

  • +Y Up will change the axis to make +Y the Up position, this will automatically do the transformation I've talked about in "Axes differences"

  • Apply Modifiers isn't going to be of any use if you're not using any kind of modifiers. However if you do, they'll be automatically applied to the meshes and the change made by those will be in your level.

  • UVs will be a requirement to place textures correctly onto your models later.

  • Normals are required for the game to place collisions correctly as explained before.

  • Vertex Colors will be needed later when adding lighting to your level.

Once the settings are correct, locate the .glb of your level in your files and export directly onto it to overwrite it.

You should also add the level's folder into your Blender bookmark to easily locate it back whenever you need to do so.

Save Your Blender File

Make sure you also save your .blend file. You should never work from the .glb, a lot of extra data Blender uses will be lost that way. Always work from a Blender file and just export when making changes and also save the Blender file to work on.

I saved my Blender file into the level's folder, but you can save it any place you want.

Link to the commit: c6496df152233ed79362a02bb8a91d94c87f8e38

Creating the Checkpoint in the level-info.gc

We're almost done with being able to get to our level in the game. The last thing we'll need is to create a continue-point to be able to load and spawn into it.

Once again, we can copy the already existing test-zone and edit it to fit our level.

Location: goal_src/jak1/engine/level/level-info.gc

Line: 1975

(Set the zoom to 200% to see the text or just open the commit to see the code)

Link to the commit: 7431e2dcf66bacf2bf1f5d028d23304d843f28d6

Edit: Keep the ocean set to #f or you'll risk crashing when loading the level, this is fixed in a later commit.

You also need to change the index to a unique one, 27 instead of 26 in this case.

Finding the continue-point's Position

To know exactly where we'll set Jak's position, we'll use Blender. We'll see later in "Checkpoints and Load Boundaries" how to get all the information about Jak and the camera.

Back in Blender, you can do a "Shift + Right Click" anywhere on your mesh to set the cursor's position on that face. We could already take the position of the cursor, but I find it really useful to create empties for the position of every continue-point as well as actors.

Press Shift + A and select Empty > Any option. I usually use Arrows for continue-points

The empty will serve as a point in space we can always look back at and change. Name it the same as the name of the continue-point in level-info.gc, so you can easily know what it is and find it through the search.

Now that you have an empty set for your continue-point, you can look at its position to copy it over to level-info.gc. Don't forget the axes conversion.

Link to commit: 5bf474f4f76feb569c7445f770d16c0a80fecb49

It's also a good idea to organize your Blender file with collections inside the main one for each type of objects like this:

Adding Our Level to the List

If you were to try to go in the game right now, you would still not be able to find your continue-point in the list. We first need to tell the compiler to add it. To do so, go to the bottom of the level-info.gc file.

Once again, you can copy the line for test-zone and edit the name.

Link to commit: 4a67fd5fc1241822136a01a1ddd0e4f62a6176af

This also has the fix for the ocean and index.

Test Your Level

Everything should now be set up to test your level. Make sure you've saved every change in all your files and exported the last version of the .glb for the level's geometry.

Using the Compiler

To use the compiler, start the goalc binary and run the (mi) command. Don't forget the parentheses.

If everything compiled correctly, the end of the logs should show your level being compiled as well.

If an error occurs, check into the log to see from which file and line and double check the part talking about that specific edit.

Loading the Level

We should now be able to start the game and load our continue-point to our custom level.

Start gk to start the game and press Alt to open the top bar and restart in debug mode.

Once in debug, you can press R3 to open the debug menu and navigate to Game... > Continue... > Your level's checkpoint

If everything was done correctly, you should have now spawned in your custom level, at the right position and with the level loaded and placed exactly where you wanted it.

Congratulations!

If the game crashes when loading your level, make sure you've fixed the ocean and index as explained earlier.

If it still crashes despite those fixes, check how to attach the debugger to the game in the OpenGOAL documentation, so you can copy and read the crash logs.

Advice to Work Efficiently

This whole setup is, fortunately, only required once per new level added. You'll be able to work much quickly now to actually create the level.

Keeping the Compiler and Game Open

After starting the compiler and the game for the first time, you can keep both of them open as you go make the next changes.

Keeping the compiler open allows the next (mi) run to only recompile the file it needs to. If you're only making changes to the .jsonc or the .glb of the level, the compile time will be reduced by a lot. Some files will still have you recompile most of the game.

Keeping the game open allows you to instantly press the "Reboot now!" button after a successful compile to see and test the changes.

Changing the Debug continue-point

The debug continue-point can be changed, doing so can save you a lot of time if you have to do a lot of iteration and always need to open the debug menu to load in your level.

If You're Using the Mod Base

The mod base has an extra file for mod related settings. This file also contains an overwrite for the starting debug continue-point.

Navigate to: goal_src/jak1/engine/mods/mods-settings.gc

Line: 40

Then simply change the continue-point to the one in your level.

Link to the commit: fee907ca02c665167bf1c003d28e96b60fa55f13

Another advantage of this method is that you're not changing some of the base code of the game and editing this later on makes for a very quick recompile with (mi).

If You're Not Using the Mod Base

Even if you're not using the mod base, you'll still be able to change this, which I'd still suggest doing. The compile time whenever you want to change it will be a lot longer, but it's probably still going to be a huge time save overall.

Navigate to: goal_src/jak1/engine/game/game-info.gc

Line: 219

Find the *debug-segment* continue-point, which should be village1-hut by default and replace it with your level's continue-point.

Link to the commit: db993fc0adf1d1b11de144910833a51a390e0200

Test Your Changes Often

Compiling and testing your changes doesn't take a long time, especially if you follow the last two pieces of advice.

So it's a very good idea to test any big changes, especially in the code. You don't want to have changed ten files and then have your level crash without any idea why.