November 8, 2024

How to Create a Large Rosebud AI Game Project

Create apps, games, and websites without coding skills!
CREATE NOW

Over the last few months, our community has come up with a long list of cool tricks to get larger Rosebud projects to work better. Whether that be writing detailed comments for Rosie in your code or even linking Rosebud projects within Rosebud projects.

In this article, you’ll find two essential tutorials: one for 2D projects and another for 3D projects:

The 2D tutorial walks you through moving less frequently modified code out of the Code Tab and into script assets stored in the Asset Tab. This method might feel a bit daunting at first, but it’s surprisingly beginner-friendly and can save you loads of time when your projects grow beyond 1,000 lines of code.

For 3D projects, we’ve introduced a tailored approach to help you tackle the unique challenges of managing larger, more complex setups. This new guide incorporates feedback from our incredible community and simplifies the process to make it more accessible.

If you have any questions about following these steps, join our Discord and let us know how we can help make your experience even better!

Working with Large 2D Projects

Step 1: Add a Script Loader to your code

The first step is to ask Rosie to add the following script loader to your project.

You can simply copy and paste it:

function loadScript(url) {
   return new Promise((resolve, reject) => {
       // Check if the script is already loaded
       if (document.querySelector(`script[src="${url}"]`)) {
           resolve();
           return;
       }

       var script = document.createElement('script');
       script.type = 'text/javascript';
       script.src = url;

       script.onload = () => resolve();
       script.onerror = () => reject(new Error('Script loading failed for ' + url));

       document.head.appendChild(script);
   });
}

You can also ask Rosie to create one from scratch, but showing her this already working one will help her succeed more reliably.

To clarify what I mean by "script," a script is simply a single file that has code that is able to run. Your game code in the Code Tab is also a script. Our goal with this tutorial is to add a script loader to our game that can load other scripts. That way, we can add features to our game without filling up our Code Tab!

A good example of this is the AI Character template. If you remix it and look at its code, the code for the chat interface isn't in the Code Tab. So where is it coming from? It's coming from a script that we're loading with the script loader (see line 119 of the AI Character project code).

Step 2: Ensure your project is broken into separate pieces

The next step is for us to make sure our project is broken into separate pieces.

This is called modularization, and it's super important if we want to move certain pieces of our code outside of the Code Tab.

To give an analogy, imagine your room at home had too much stuff, and you wanted to move some of it to the basement. What if the biggest thing in your room, the thing you want to move most, was an unused desk?

If that desk was too big to fit through the door, had a bunch of books on it, and a bunch of wires tangles around it, you wouldn't just forcibly pull it out of your room. That would cause damage and leave you in a worse spot.

You would instead first get the books off and put them in their own pile, untangle + separate the wires, and then disassemble the table to make it easier to bring to the basement.

This is exactly what we need to do with our project before moving parts of it out.

Pick a small part of your game that you know you won't be changing much for the rest of the project (e.g. the user interface). Ask Rosie to make that part of the code into a modular class. In programming, think of a "class" as a building block of your project.

Use a prompt similar to the following (make sure what you chose to turn into a modular class matches your own project):

Please add the following script loader to the project.

function loadScript(url) {
   return new Promise((resolve, reject) => {
       // Check if the script is already loaded
       if (document.querySelector(`script[src="${url}"]`)) {
           resolve();
           return;
       }

       var script = document.createElement('script');
       script.type = 'text/javascript';
       script.src = url;

       script.onload = () => resolve();
       script.onerror = () => reject(new Error('Script loading failed for ' + url));

       document.head.appendChild(script);
   });
}

Please then turn the user interface into a modular class that I can then move myself to an external script. I'm going to load the script with our new script loader. Please make sure it's easy for me to copy and paste the new modular class from your response.

Rosie should then alter your project code to have the part of the code you specified to be a modular class.

For example, if you asked Rosie to turn the UI into a class, it should look something like this now:

class UIScene extends Phaser.Scene {
   constructor() {
       super('UIScene');
       this.score = 0;
   }

   create() {
       this.scoreText = this.add.text(10, 10, 'Score: 0', { fontSize: '32px', fill: '#fff' });
       
       this.button = this.add.text(400, 300, 'Click me!', { fontSize: '32px', fill: '#0f0' })
           .setOrigin(0.5)
           .setInteractive({ useHandCursor: true })
           .on('pointerdown', () => this.incrementScore());
   }

   incrementScore() {
       this.score += 1;
       this.scoreText.setText('Score: ' + this.score);
   }
}

Step 3: Move the code to a script file

Next, we need to create a new file, copy the modular code from Step 2, and paste it into the new file.

Please head to your desktop and create a file called {something}_script.js.

Make sure to replace the {something} with whatever the feature you modularized in Step 2 is.

Ensure that the file you create has  .js at the end. If you don't know how to do this, first just create a regular text file, and then, when it's already there, change the ending from .txt , or whatever it is, to .js.

Next, please paste the Code from Step 2 into the file. You can now delete the Step 2 code from the Code Tab. We're going to load it from the script, so it no longer needs to be in the Code Tab!

Step 4: Uploading and using our new script file

Now, we can upload that script to the asset tab!

Please head over to the asset tab and click the Upload Asset button. Then, select the new file you created.

In the chat tab, use the '@' sign to select the asset, and then tell Rosie to load it as a script using the script loader. I would strongly recommend that you directly show Rosie the code inside the script in that same prompt, as she isn't able to open the script and see it for herself. Here is an example prompt:

I moved the modular user interface class to an external script. Here is the script @{select script from asset list}.

Here is what's inside for reference:

class UIScene extends Phaser.Scene {
   constructor() {
       super('UIScene');
       this.score = 0;
   }

   create() {
       this.scoreText = this.add.text(10, 10, 'Score: 0', { fontSize: '32px', fill: '#fff' });
       
       this.button = this.add.text(400, 300, 'Click me!', { fontSize: '32px', fill: '#0f0' })
           .setOrigin(0.5)
           .setInteractive({ useHandCursor: true })
           .on('pointerdown', () => this.incrementScore());
   }

   incrementScore() {
       this.score += 1;
       this.scoreText.setText('Score: ' + this.score);
   }
}

After that, your project should work with the feature you chose in Step 2, even though it isn't in the Code Tab.

Your project should now be more lightweight, making it easier to send prompts, but still have the same features!

Working with Large 3D Projects

Step 1: Make Commonly-Used Things Available to All External Files

The first step is to make any imported module available to external files. Why do you have to do this? The reason why is that external files won't be able to see/use things in the Code Tab by default.

For example, if you had a character that checks how close it is to the ground before it jumps, but you moved ground to an external file, this wouldn't work unless to made character available to external files. How do you make things available to external files? All you need to do is, after you've created said thing, write a line like this:

window.thing = thing;

Now, every single external file will be able to see it! I strongly recommend, no matter what your project is, to do this right away for every single imported module at the top of your code (see example below), and also for world and scene. Example:

import * as THREE from 'three';
import {
   OrbitControls
} from 'three/addons/controls/OrbitControls.js';
import {
   EffectComposer
} from 'three/addons/postprocessing/EffectComposer.js';
import {
   RenderPass
} from 'three/addons/postprocessing/RenderPass.js';
import {
   UnrealBloomPass
} from 'three/addons/postprocessing/UnrealBloomPass.js';
import {
   SMAAPass
} from 'three/addons/postprocessing/SMAAPass.js';

// Expose Three.js and other imports to the global window object
window.THREE = THREE;
window.OrbitControls = OrbitControls;
window.EffectComposer = EffectComposer;
window.RenderPass = RenderPass;
window.UnrealBloomPass = UnrealBloomPass;
window.SMAAPass = SMAAPass;

...

Step 2: Choose What to Move Out of the Code Tab

This is probably the most important step, as a bad choice can make the process many times more difficult. While choosing is more of an art than a science, there are key things to consider.

The best things to move are classes. You do not need to understand the details of classes, but think of them as blueprints. They are blueprints in your code that tell the code how to make a specific thing without reinventing the wheel.

I made sure to include a detailed visual example in the attached video, but a quick summary is that:

Let's say that you created a cool bouncing ball. It's red, medium-sized, and bounces a medium amount. If you wanted to create a purple version of that ball that was big and bounced super high + a yellow one that was tiny and barely bounced at all, you'd have to go through the entire process of creating the ball, defining the physics, etc., 3 times.

If your project is structured this way, not only is it confusing for Rosie, but you'll hit the project size limit in no time. The solution to this is classes. Classes allow you to define a blueprint of how bouncing balls are made. If you make the original red bouncing ball into a class called BouncingBall, then the purple and yellow ones can now just be instances of the BouncingBall class.

Think of instances as physical prototypes of a blueprint. The blueprint is the manual, and the instance is the actual thing. Because the purple ball is now an instance of BouncingBall, it now just copies all the underlying functionality of BouncingBall (the red ball) and then makes some minor changes to make it different. No re-inventing the wheel here!

If your project does not have a lot of classes, try to modify some code into classes before moving to Step 3. If your project is already too big for you to prompt for this, copy a chunk of related things in your code into a new project and prompt there instead. You can then move the code back when it's in class form.

Be careful though! Remember that Rosie can't see the code in external files. Don't move any classes that you plan to prompt to change any time soon. Only move classes and things that will stay the same.

Important Note:

Keep in mind that it's not just classes that can get moved. Long strings, large functions, etc., can be moved as well following a similar approach.

Step 3: Move Code out of the Code Tab and to a new Script

Alright! Now, we have our classes (or other code objects) ready to move.

Copy the part of the code that you want to move to your clipboard (CTRL + C or Command + C). You can now delete it from the Code Tab. Make sure to paste it somewhere (e.g. Notepad) for safekeeping.

You now want to paste it into a code editor so we can make a JS module. I recommend VS Code Web if you're unfamiliar with code editors, but any that supports Javascript files is good enough.

We simply need to open up our editor, paste our code into a new file, and make sure the file is marked as Javascript (see bottom right if using VS Code).

There is one more thing to do before the next step, though. It is crucial that you type the word export right before your class.

e.g.

export class BouncyBall { ... }

This is because, by default, the Code Tab will not be able to see what's inside this file, as we discussed. Putting the keyword export before the class will make it usable by the Code Tab.

Note:

Note that not everything inside your external file needs to be exported. If you have a bunch of functions, classes, etc., that will only be used in this one external file,  you don't have to put the export keyword before them.

Only use the keyword for things that will get used by the Code Tab. If something is going to be used by another external file, you need to use the window.thing approach we discussed (I don't recommend doing this, though. I recommend just combining both external files in that case).

Step 4: Upload your Script to the Asset Tab and then Use it in the Code Tab

You're almost done! Not that we have our file ready, use CTRL + S or Command + S to save it. Make sure to name it something that makes sense. A good approach is { Theme of What's in Here}_external.js.

IMPORTANT: Make sure your filename ends with .js. This tells code tools, including Rosebud, that this file is a Javascript file.

In this case, bouncing_ball_external.js would work well as a name.

Once we've saved this file to our device, we now need to upload this to the Asset Tab like any other asset. Switch to the Asset Tab and use the Upload Asset button at the top. Then, select our new JS file.

Now, under where all the imports and window.things are, fill in the following import statement template:

import { your_class_name } from 'your_uploaded_js_file_URL';

Make sure to fill in the class name with the exact one in your JS file. Fill in the URL by using the Copy Url button on the asset (go to the asset tab and hover your mouse over the asset you uploaded; you'll see a square icon on the top right. Clicking it will automatically copy its unique Rosebud URL. You can then simply copy and paste it into the URL slot of the import statement template I provided above).

In my case, the top of my project code now looks like this:

import * as THREE from 'three';
import {
   OrbitControls
} from 'three/addons/controls/OrbitControls.js';
import {
   EffectComposer
} from 'three/addons/postprocessing/EffectComposer.js';
import {
   RenderPass
} from 'three/addons/postprocessing/RenderPass.js';
import {
   UnrealBloomPass
} from 'three/addons/postprocessing/UnrealBloomPass.js';
import {
   SMAAPass
} from 'three/addons/postprocessing/SMAAPass.js';

// Expose Three.js and other imports to the global window object
window.THREE = THREE;
window.OrbitControls = OrbitControls;
window.EffectComposer = EffectComposer;
window.RenderPass = RenderPass;
window.UnrealBloomPass = UnrealBloomPass;
window.SMAAPass = SMAAPass;

import { BouncingBall } from 'https://play.rosebud.ai/assets/bouncingball_external.js?MzlE';

Now, refresh your game. It should run even though you've moved some code to an external file!

Step 5: That's it!

Great job sticking through this! The first one or two times may seem overwhelming, but after that, the process gets much faster! Happy creating, and feel free to ask clarifying questions or suggest tips and tricks below!

If you need any further help with your long project, join our Discord and let us know in 🌹┃rosebud-support channel!

Turn your ideas into games, apps, and websites!
GET STARTED NOW