3. Prompt Engineering
b. Organizing Project Structure

Organizing Project Structure for Roblox Games

⚠️ Comprehensive Guide Notice

This is a comprehensive guide that might be too long to read and won't be necessary if you're just starting out with SuperbulletAI.

For Beginners: You can use ChatGPT/DeepSeek or any other AI models to summarize this guide to save time, or just start building and refer back to specific sections as needed.

For Experienced Developers: If you have years of game development experience and want to get familiar with how SuperbulletAI follows the Superbullet Framework V1, reading this entire guide will help you understand the architecture much better.

When building Roblox games with SuperbulletAI using an Object-Oriented Programming (OOP) approach and the Knit framework, proper project organization is crucial for maintainability, security, and development efficiency.

1. Standard Project Structure

SuperbulletAI follows this organized folder structure for Roblox projects:

src/
├── ReplicatedStorage/
│   ├── ClientSource/
│   │   └── Client/          << All Client Systems stored here
│   └── SharedSource/
│       └── Datas/           << Shared Data/Settings for both client and server
└── ServerScriptService/
    └── ServerSource/
        └── Server/          << All Server Systems stored here

2. Modified Knit Framework: Get() and Set() Components

SuperbulletAI uses a modified version of Knit that introduces Get().lua and Set().lua components to solve IntelliSense limitations and enforce clean architecture.

2a. Why Get().lua and Set().lua Components?

The standard Knit framework has a critical flaw: IntelliSense doesn't work properly with many Language Server Protocols (LSPs). Our modified framework solves this by introducing:

  • Get().lua - Handles read-only operations or data retrieval logic
  • Set().lua - Handles write operations or data modifications

2b. Benefits of This Approach

  1. IntelliSense Support - Full autocomplete and type checking across LSPs
  2. Clean Architecture - Enforced separation between read and write operations
  3. Long-term Sustainability - Clear boundaries make code easier to maintain
  4. Component Organization - Break down large systems into manageable pieces

2c. Component Structure Example

-- PlayerDataService/Get().lua (Read Operations)
local PlayerDataGet = {}
 
function PlayerDataGet:GetPlayerLevel(player)
    return self._playerData[player.UserId].Level
end
 
function PlayerDataGet:GetPlayerCurrency(player)
    return self._playerData[player.UserId].Currency
end
 
return PlayerDataGet
-- PlayerDataService/Set().lua (Write Operations)
local PlayerDataSet = {}
 
function PlayerDataSet:AddExperience(player, amount)
    local data = self._playerData[player.UserId]
    data.Experience += amount
    self:_CheckLevelUp(player)
end
 
function PlayerDataSet:SpendCurrency(player, amount)
    local data = self._playerData[player.UserId]
    if data.Currency >= amount then
        data.Currency -= amount
        return true
    end
    return false
end
 
return PlayerDataSet
-- PlayerDataService/Others/DataValidator.lua (Additional Component)
local DataValidator = {}
 
function DataValidator:ValidatePlayerData(data)
    return data.Level >= 1 and data.Currency >= 0
end
 
function DataValidator:SanitizePlayerName(name)
    return string.match(name, "^[%w_]+$") and name or "Player"
end
 
return DataValidator

2d. Complete System Architecture

SuperbulletAI's modified Knit framework supports three component types:

  1. Get().lua - Read-only operations
  2. Set().lua - Write operations
  3. Others/[ComponentScript] - Additional specialized components
PlayerDataService/
├── init.lua                    << Main service file
└── Components/
    ├── Get().lua               << Read operations
    ├── Set().lua               << Write operations
    └── Others/
        ├── DataValidator.lua   << Validation logic
        ├── DataMigration.lua   << Data migration utilities
        └── DataAnalytics.lua   << Analytics tracking

2e. Component Loading Pattern

SuperbulletAI uses a standardized component loading pattern:

-- Standard component loading in init.lua
local ServiceName = Knit.CreateService({
    Name = "ServiceName"
})
 
-- Components initialization
local componentsInitializer = require(ReplicatedStorage.SharedSource.Utilities.ScriptsLoader.ComponentsInitializer)
 
-- Load Others/ components into Components table
ServiceName.Components = {}
for _, v in pairs(script:WaitForChild("Components", 10).Others:GetChildren()) do
    ServiceName.Components[v.Name] = require(v)
end
 
-- Load Get and Set components
ServiceName.GetComponent = require(script:WaitForChild("Components", 10)["Get()"])
ServiceName.SetComponent = require(script:WaitForChild("Components", 10)["Set()"])

Access Patterns:

  • Others/ components: ServiceName.Components.DataValidator:ValidateData()
  • Get operations: ServiceName.GetComponent:GetPlayerLevel(player)
  • Set operations: ServiceName.SetComponent:AddExperience(player, amount)

3. Component Communication Rules

Understanding the communication rules between different component types is critical to avoid cyclical dependencies and maintain clean architecture.

3a. Communication Restrictions

3a.i. Others/ Components

  • ❌ CANNOT be called directly by other systems
  • ✅ CAN be called by Get().lua and Set().lua components within the same system
  • ✅ CAN communicate with other Others/ components within the same system

Communication Method: Use require() within the parent system's source files or access through Components table

-- PlayerDataService/Components/Set().lua
local DataValidator = require(script.Parent.Others.DataValidator)
 
function PlayerDataSet:UpdatePlayerData(player, newData)
    if DataValidator:ValidatePlayerData(newData) then
        -- Update data
    end
end

Or access from parent system:

-- From init.lua using Components table
local validData = PlayerDataService.Components.DataValidator:ValidatePlayerData(data)

3a.ii. Set().lua Components

  • ✅ CAN be called by other systems
  • ✅ CAN be called by Others/ components
  • ❌ CANNOT be called by Get().lua components
  • ❌ CANNOT directly communicate with Get().lua components

External System Access:

-- From another system
local PlayerDataService = Knit.GetService("PlayerDataService")
PlayerDataService.SetComponent:AddExperience(player, 100)

3a.iii. Get().lua Components

  • ✅ CAN be called by other systems
  • ✅ CAN be called by Others/ components
  • ❌ CANNOT be called by Set().lua components
  • ❌ CANNOT directly communicate with Set().lua components

External System Access:

-- From another system
local PlayerDataService = Knit.GetService("PlayerDataService")
local level = PlayerDataService.GetComponent:GetPlayerLevel(player)

3b. Forbidden Communication: Get().lua ↔ Set().lua

Get().lua and Set().lua components are FORBIDDEN from directly communicating with each other.

❌ WRONG:

-- PlayerDataService/Set().lua - DON'T DO THIS!
local PlayerDataGet = require(script.Parent["Get()"]) -- FORBIDDEN!
 
function PlayerDataSet:SomeFunction()
    local level = PlayerDataGet:GetPlayerLevel(player) -- BAD!
end

✅ CORRECT:

-- PlayerDataService/init.lua (Parent System)
local PlayerDataService = Knit.CreateService({
    Name = "PlayerDataService"
})
 
-- Components
local componentsInitializer = require(ReplicatedStorage.SharedSource.Utilities.ScriptsLoader.ComponentsInitializer)
 
PlayerDataService.Components = {}
for _, v in pairs(script:WaitForChild("Others", 10):GetChildren()) do
    PlayerDataService.Components[v.Name] = require(v)
end
PlayerDataService.GetComponent = require(script["Get()"])
PlayerDataService.SetComponent = require(script["Set()"])
 
function PlayerDataService:TransferCurrency(fromPlayer, toPlayer, amount)
    -- Parent system coordinates between Get and Set
    local fromCurrency = self.GetComponent:GetPlayerCurrency(fromPlayer)
    if fromCurrency >= amount then
        self.SetComponent:SpendCurrency(fromPlayer, amount)
        self.SetComponent:AddCurrency(toPlayer, amount)
        return true
    end
    return false
end

3c. Communication Flow Diagram

Other Systems

Parent System (init.lua) ←→ Others/Components
     ↕              ↕
   Get().lua      Set().lua
     ↕              ↕
Others/Components  Others/Components

Key Rules:

  • Parent System acts as coordinator between Get().lua and Set().lua
  • Others/ components can talk to Get().lua and Set().lua but not other systems
  • External systems access through parent system or directly through GetComponent/SetComponent
  • Parent System Communication is ALLOWED between different parent systems
  • Components Table automatically loads all Others/ components into ServiceName.Components[ComponentName]

3d. Parent System Communication

Parent systems can communicate with each other freely. Examples of parent systems include:

Server Parent Systems:

  • PointsService - Manages player points and rewards
  • ProfileService - Handles player profile data
  • ShopService - Manages in-game purchases
  • DataService - Core data management

Client Parent Systems:

  • EffectsController - Handles visual effects
  • ShopController - Manages shop UI
  • DataController - Client-side data management

✅ ALLOWED:

-- PointsService can call ProfileService
local ProfileService = Knit.GetService("ProfileService")
local playerProfile = ProfileService:GetProfile(player)
 
-- ShopService can call PointsService
local PointsService = Knit.GetService("PointsService")
PointsService:AddPoints(player, purchaseReward)

4. When to Use Get/Set Components

4a. USE Get/Set/Others When:

  1. System has both read and write operations

    "Create a PlayerDataService with Get() for retrieving stats and Set() for updating player progress"
  2. File approaching 300+ lines

    "Break down this InventoryService into Get() and Set() components for better organization"
  3. Complex business logic with mixed operations

    "Create a ShopService where Get() handles item listings and Set() handles purchases"
  4. Need specialized utility components

    "Build a CombatService with Get() for damage calculations, Set() for applying effects, and Others/DamageCalculator for complex damage formulas"
  5. Multiple related but distinct functionalities

    "Create a ShopService with Others/PriceCalculator for dynamic pricing and Others/DiscountManager for handling sales"

4b. DON'T Use Get/Set When:

  1. Simple, single-purpose systems (< 100 lines)

    -- Simple notification service doesn't need Get/Set
    local NotificationService = Knit.CreateService({
        Name = "NotificationService"
    })
  2. Pure utility functions

    -- Math utilities are just functions, no state management
    local MathUtils = {}
  3. Event-only services

    -- Services that only fire events don't need Get/Set separation
    local GameEventService = Knit.CreateService({
        Name = "GameEventService"
    })

5. System Grouping Best Practices

To avoid cluttering, group related systems together:

5a. Folder Organization Guidelines

Keep maximum 1 folder deep to avoid complex file trees:

✅ CORRECT:

ServerScriptService/ServerSource/Server/PlayerSystems

❌ WRONG:

ServerScriptService/ServerSource/Server/PlayerSystems/Progression

This keeps the file structure clean and prevents deep nesting that becomes hard to navigate.

Exception: Others/ components can go as deep as needed within their folder:

✅ ALLOWED:

PlayerSystems/PlayerDataService/Others/
├── Validation/
│   ├── DataValidator.lua
│   └── SchemaValidator.lua
└── Migration/
    ├── V1toV2.lua
    └── V2toV3.lua
src/ServerSource/Server/
├── PlayerSystems/
│   ├── PlayerDataService/
│   │   ├── init.lua
│   │   ├── Get().lua
│   │   ├── Set().lua
│   │   └── Others/
│   │       ├── DataValidator.lua
│   │       └── DataMigration.lua
│   └── PlayerStatsService/
│       ├── init.lua
│       ├── Get().lua
│       ├── Set().lua
│       └── Others/
│           └── StatCalculator.lua
├── EconomySystems/
│   ├── ShopService/
│   │   ├── init.lua
│   │   ├── Get().lua
│   │   ├── Set().lua
│   │   └── Others/
│   │       ├── PriceCalculator.lua
│   │       └── DiscountManager.lua
│   └── CurrencyService/
│       ├── init.lua
│       ├── Get().lua
│       └── Set().lua
└── CombatSystems/
    ├── DamageService/
    │   ├── init.lua
    │   ├── Get().lua
    │   ├── Set().lua
    │   └── Others/
    │       └── DamageCalculator.lua
    └── EffectsService/
        └── init.lua  << Simple service, no Get/Set needed

6. The 300-Line Rule

Rule of thumb: Break down components when approaching 300 lines of code.

6a. When to Follow the Rule:

  • File becoming hard to navigate
  • Multiple distinct responsibilities in one file
  • Complex business logic mixing reads and writes
  • Team members struggling to find specific functions

6b. When to Ignore the Rule:

  • Splitting would create artificial separation
  • Logic is tightly coupled and belongs together
  • File is mostly data/configuration (not logic)
  • Breaking down would hurt readability

7. Practical Prompting Guidelines

Since SuperbulletAI already understands the component architecture, you don't need to explicitly explain the structure. Focus on what you want to achieve.

7a. Adding New Features/Components

"Add a new feature to track player achievements in the PlayerDataService"
"Create a discount system for the ShopService"
"Add validation logic for player usernames"

SuperbulletAI will automatically organize these into the appropriate components (Get/Set/Others) based on functionality.

7b. Refactoring Best Practices

🎯 CRITICAL: Refactor Component by Component

When refactoring systems over 300 lines, follow these rules for 99%+ success rate:

✅ CORRECT - One Component at a Time:

"Refactor ONLY ONE component from this PlayerDataService. ONLY ONE."
"Refactor ONLY the LARGEST COMPONENT from the Get() component. ONLY ONE."
"Refactor ONLY the LARGEST COMPONENT from the Set() component. ONLY ONE."
"Extract ONLY the validation logic into a separate component. ONLY ONE."
"Move ONLY the price calculation logic into its own component. ONLY ONE."
"Refactor ONLY the UI handling part of this InventoryController. ONLY ONE."

❌ WRONG - Multiple Components at Once:

"Break down this entire 500-line service into components"
"Refactor this whole system to use proper component architecture"
"Split this controller into multiple components"

Why This Matters:

  • One component refactoring: 99%+ success rate - AI focuses on specific functionality
  • Multiple components refactoring: 50% success rate - Often results in missing code and errors

7c. Safe Refactoring Process

Step-by-step approach for large systems (300+ lines):

  1. Identify what needs refactoring:

    "Analyze this system and tell me which part would benefit most from refactoring"
  2. Refactor ONLY ONE piece:

    "Refactor ONLY ONE component from this system"
  3. Test and verify the refactored component

  4. Move to next piece:

    "Now refactor ONLY ONE more component from the remaining code"
  5. Repeat until complete

7d. Feature Addition Examples

Simple feature additions:

"Add player level calculation to the existing system"
"Add cooldown tracking for player actions"
"Create a backup system for player data"

Complex feature additions:

"Add a complete friend system with friend requests, acceptance, and friend lists"
"Implement a guild system with member management and permissions"
"Create an auction house system for player trading"

7e. Experimentation and Growth

These are solid rules of thumb, but don't stop here!

Once you understand these foundational principles:

  • Experiment with your own prompting techniques
  • Test different approaches and see what works best for your specific use cases
  • Iterate and refine your prompts based on results
  • Push boundaries while keeping the core "ONLY ONE" principle

The goal: Eventually you'll surpass the knowledge in this guide and become a monster at making Roblox games with your own advanced prompting mastery.

This guide gives you the foundation - your experimentation will build the expertise! 🚀

8. Understanding the Three Categories

8a. Frontend-Only (Client)

Location: src/ReplicatedStorage/ClientSource/Client

These are systems that only run on the player's device and handle user interface, input, and visual effects.

Examples:

  • UI Controllers (inventory display, shop interface, settings menu)
  • Input handlers (mouse clicks, keyboard shortcuts)
  • Visual effects (particles, animations, camera effects)
  • Sound effects and music players
  • Local data caching for UI responsiveness

When to explicitly prompt for client-only:

"Create a client-only inventory UI system that displays player items"
"Build a frontend camera controller for third-person view"
"Make a client-side particle effect system for spell casting"

8b. Backend-Only (Server)

Location: src/ServerScriptService/ServerSource/Server

These are systems that only run on the server and handle game logic, data persistence, and security-critical operations.

Examples:

  • Player data management (saving/loading stats, inventory)
  • Game logic enforcement (damage calculation, win conditions)
  • Anti-cheat systems and validation
  • Economy systems (currency transactions, shop purchases)
  • Matchmaking and server events

When to explicitly prompt for server-only:

"Create a server-only player data service that saves stats to DataStore"
"Build a backend damage calculation system with anti-cheat validation"
"Make a server-side economy service for handling purchases"

8c. Shared (Both Frontend and Backend)

Location: src/ReplicatedStorage/SharedSource/Datas

These are configurations, constants, and utilities that both client and server need access to.

Examples:

  • Game configuration (weapon stats, level data, shop items)
  • Shared utilities (math functions, data structures)
  • Constants and enums (item types, game states)
  • Data schemas and interfaces
  • Shared validation functions

9. Prompting Best Practices

9a. For Client-Only Features

"Create a CLIENT-ONLY UI system for displaying player inventory with smooth animations"

9b. For Server-Only Features

"Create a SERVER-ONLY player data service that handles saving and loading player stats with DataStore2"

9c. For Shared Components

"Create shared configuration data for weapon stats that both client and server can access"

9d. Security-Conscious Prompting

"Create a server-side combat system where clients can only request attacks, but the server calculates all damage and validates hit detection"

10. Example System Breakdown

10a. Player Inventory System

Client-Side (ClientSource/Client):

  • InventoryController - Displays UI, handles clicks
  • InventoryAnimations - Smooth opening/closing effects

Server-Side (ServerSource/Server):

  • InventoryService - Manages actual inventory data
  • InventoryDataStore - Saves/loads inventory from DataStore

Shared (SharedSource/Datas):

  • ItemConfigurations - Item stats, descriptions, icons
  • InventoryTypes - TypeScript/Luau type definitions

11. Common Mistakes to Avoid

  1. Putting sensitive logic on client - Always keep important calculations server-side
  2. Trusting client remote event parameters - Validate everything on server
  3. Not using shared configurations - Leads to duplicate code and inconsistencies
  4. Mixing client and server code - Keep them separated for clarity and security

12. Summary

12a. Project Structure

  • Client: UI, effects, input handling (ClientSource/Client)
  • Server: Game logic, data, security (ServerSource/Server)
  • Shared: Configurations and constants (SharedSource/Datas)

12b. Modified Knit Components

  • Get().lua: Read-only operations (can be called by other systems and Others/)
  • Set().lua: Write operations (can be called by other systems and Others/)
  • Others/: Specialized components (cannot be called by other systems directly)

12c. Communication Rules

  • Get().lua ↔ Set().lua: FORBIDDEN - use parent system for coordination
  • Others/ → External: FORBIDDEN - use GetComponent/SetComponent or parent system
  • Parent System: Acts as coordinator between all components
  • Component Loading: Use ComponentsInitializer pattern for proper component organization

12d. Security & Architecture

  • Security: Never trust the client, always validate on server
  • Remote Events: Use action-based events, not value-based ones
  • 300-Line Rule: Break down large files, but use judgment
  • IntelliSense: Modified Knit framework ensures full LSP support

Remember: If an exploiter can see it or interact with it on their screen, they can potentially modify it. Design your architecture accordingly!

13. Security Best Practices: Preventing Exploiters

13a. Critical Security Rule

What exploiters CAN manipulate: Anything that runs on the frontend/client/player's device can be modified by exploiters no matter what you do.

13b. Remote Events Security with Knit

When using Knit's remote events (like PointsService.AddPoints:Fire()), follow these security principles:

13b.i. NEVER DO THIS - Trusting Client Data

-- BAD: Client tells server how many points to add
-- Exploiter can easily change the amount
PointsService.AddPoints:Fire(player, 99999) -- Exploitable!

13b.ii. ALWAYS DO THIS - Server Validates Everything

-- GOOD: Client requests action, server determines the reward
PointsService.CompleteQuest:Fire(player, questId) -- Server calculates points

13c. Security Guidelines for Remote Events

  1. Never trust numerical values from client

    • Don't let clients specify how much currency/XP/items they get
    • Server should calculate rewards based on validated actions
  2. Use action-based remote events

    -- Good examples:
    CombatService.AttackTarget:Fire(targetId)
    QuestService.TurnInQuest:Fire(questId)
    ShopService.PurchaseItem:Fire(itemId)
  3. Always validate on server

    -- Server-side validation example
    function ShopService:PurchaseItem(player, itemId)
        local item = GameData.Items[itemId]
        if not item then return false end
     
        if player.Currency.Value < item.Price then
            return false -- Not enough currency
        end
     
        -- Proceed with purchase...
    end

14. The Future: ECS Architecture (Superbullet Framework V2)

Entity Component System (ECS) has been the industry standard for years, not just for Roblox games but across the entire game development industry.

However, Object-Oriented Programming (OOP) is much more understood and beginner-friendly, which is why SuperbulletAI currently uses the modified Knit framework.

14a. Coming Soon

ECS framework will be available soon for advanced developers who want to leverage the performance and architectural benefits of Entity Component Systems.

14b. Remember This

It doesn't matter what framework you use on Roblox - there are game developers out there earning millions of dollars despite having messy codebases. By using SuperbulletAI, you're already coding way better than many successful developers!

Focus on building great games, and the architecture will support your success. 🚀