DEV Community

Patrick DeVos
Patrick DeVos

Posted on

Generate Infinite RPG Content with AI - No Local GPU Required

Writing RPG content is one of the biggest time sinks in indie game development. One NPC needs an intro, a quest hook, a refusal line, and a reward acknowledgement. Multiply that by 50 NPCs and you've spent two weeks on dialogue trees players will skip in 30 seconds.

The GameForge Content API solves this with a simple trade: you send a structured description of what you want - a weary innkeeper, a cursed blade, a fetch quest in dark ruins - and get back structured JSON you can drop straight into your game. No prompt engineering, no parsing free-form text, no local model to run.

What It Generates

Four content types, all returning clean JSON:

Endpoint What you get
/lore/worldbuild Location descriptions, faction histories, creation myths
/quest/generate Full quest: name, hook, objectives list, reward text
/item/description Lore-rich item descriptions with trait integration
/npc/dialogue Multi-line dialogue with per-line emotion tags

Supported genres: high_fantasy dark_fantasy sci_fi western horror cozy cyberpunk

Quick Start

pip install requests
Enter fullscreen mode Exit fullscreen mode
  1. Sign up at RapidAPI
  2. Search "GameForge Content" by Circle of Wizards
  3. Subscribe free (BASIC plan)
  4. Copy your X-RapidAPI-Key
import requests

KEY  = "YOUR_RAPIDAPI_KEY"
HOST = "gameforge-content.p.rapidapi.com"
BASE = f"https://{HOST}"
HEADERS = {
    "X-RapidAPI-Key": KEY,
    "X-RapidAPI-Host": HOST,
    "Content-Type": "application/json",
}
Enter fullscreen mode Exit fullscreen mode

Generate World Lore

def generate_lore(name: str, subject_type: str, traits: list[str],
                  genre: str = "dark_fantasy", words: int = 80,
                  style: str = "in_world_document") -> str:
    # style options: in_world_document | encyclopaedia_entry | oral_tradition | official_record
    r = requests.post(f"{BASE}/lore/worldbuild", headers=HEADERS, json={
        "subject": {"type": subject_type, "name": name, "traits": traits},
        "context": {"genre": genre, "word_count": words, "style": style, "narrator": "omniscient"},
    })
    r.raise_for_status()
    return r.json()["lore"]


print(generate_lore(
    name="The Shattered Spire",
    subject_type="location",
    traits=["ancient", "cursed", "labyrinthine"],
))
Enter fullscreen mode Exit fullscreen mode

Output:

The Spire's walls, once pristine marble, weep a black ichor that stains the
hands of those who wander its endless halls. Each corridor twists upon itself,
a labyrinth born not of design but of malice. Deep within, the stone remembers
the cataclysm that shattered its peak, whispering secrets to those who listen
too closely - secrets that linger, long after the listener has gone.
Enter fullscreen mode Exit fullscreen mode

Change style to "oral_tradition" for tavern rumor text, or "encyclopaedia_entry" for codex entries.


Generate Quests

def generate_quest(quest_type: str, difficulty: str, setting: str,
                   reward_type: str, genre: str = "dark_fantasy",
                   party_size: int = 4) -> dict:
    r = requests.post(f"{BASE}/quest/generate", headers=HEADERS, json={
        "quest": {
            "type": quest_type,       # fetch | rescue | investigate | escort | eliminate
            "difficulty": difficulty,  # easy | medium | hard
            "setting": setting,
            "reward_type": reward_type,  # gold | knowledge | item | reputation
        },
        "context": {"genre": genre, "party_size": party_size},
    })
    r.raise_for_status()
    return r.json()


quest = generate_quest(
    quest_type="fetch",
    difficulty="medium",
    setting="cursed_ruins",
    reward_type="knowledge",
)

print(f"Quest: {quest['name']}")
print(f"\n{quest['description']}\n")
print("Objectives:")
for obj in quest["objectives"]:
    print(f"  ? {obj}")
print(f"\nReward: {quest['rewards_text']}")
Enter fullscreen mode Exit fullscreen mode

Output:

Quest: Whispers in the Rubble

A merchant's daughter vanished near the cursed ruins of Korrath, where the ground
itself seems to hunger. Locals speak of a rasping voice that lures the curious into
crumbling halls. Find the girl - or what remains of her - and silence the source
of the whispers.

Objectives:
  ? Search the outer ruins for traces of the missing girl
  ? Descend into the catacomb beneath the collapsed tower
  ? Recover the merchant's signet ring from the whispering shade
  ? Escape the ruins before the curse claims another soul

Reward: The merchant presses a pouch of silver into your palm, along with a
tarnished locket that hums faintly - a charm against lies, he claims. And in
your dreams that night, the whispers are a little quieter.
Enter fullscreen mode Exit fullscreen mode

Generate Item Descriptions

def generate_item(name: str, item_type: str, rarity: str,
                  traits: list[str], genre: str = "dark_fantasy") -> str:
    r = requests.post(f"{BASE}/item/description", headers=HEADERS, json={
        "item": {"name": name, "type": item_type, "rarity": rarity, "traits": traits},
        "context": {"genre": genre, "detail_level": "rich"},
    })
    r.raise_for_status()
    return r.json()["description"]


print(generate_item(
    name="Veilblade",
    item_type="weapon",
    rarity="legendary",
    traits=["shadow-forged", "cursed", "sentient"],
))
Enter fullscreen mode Exit fullscreen mode

Output:

Forged from a star's dying light, this blade cuts not flesh but fate itself.
Those who wield it see the path to victory - and the cost.
Enter fullscreen mode Exit fullscreen mode

Generate NPC Dialogue

def generate_dialogue(name: str, role: str, personality: str,
                      player_action: str, setting: str,
                      genre: str = "dark_fantasy", tone: str = "neutral") -> list[dict]:
    r = requests.post(f"{BASE}/npc/dialogue", headers=HEADERS, json={
        "character": {"name": name, "role": role, "personality": personality},
        "player_action": player_action,
        "context": {"genre": genre, "tone": tone, "setting": setting},
    })
    r.raise_for_status()
    return r.json()["lines"]


lines = generate_dialogue(
    name="Mira",
    role="innkeeper",
    personality="weary, suspicious, secretly kind",
    player_action="ask about recent travelers",
    setting="roadside_inn",
    tone="tense",
)

for line in lines:
    print(f"[{line['emotion']:12}] {line['text']}")
Enter fullscreen mode Exit fullscreen mode

Output:

[suspicious  ] You look like trouble. Most do, these days. But your coin spends the same.
[fearful     ] Strange times when even the shadows seem to watch. Lock your door tonight.
[impatient   ] The last traveler who asked too many questions left in a hurry. Or didn't leave at all.
Enter fullscreen mode Exit fullscreen mode

The emotion tag drives your dialogue system - route to the right voice line, portrait expression, or animation state without any extra parsing.


Bulk Content Pipeline

Generate an entire zone's worth of content in one script and cache it as JSON:

import json
from pathlib import Path
from time import sleep


def generate_zone(zone_name: str, genre: str, npcs: list[dict],
                  items: list[dict], output_path: str):
    zone = {"name": zone_name, "genre": genre, "lore": "", "npcs": [], "items": []}

    # Zone lore
    zone["lore"] = generate_lore(zone_name, "location",
                                 traits=["mysterious", "dangerous"],
                                 genre=genre, words=100)

    # NPCs
    for npc in npcs:
        print(f"  Generating {npc['name']}...")
        lines = generate_dialogue(
            name=npc["name"], role=npc["role"],
            personality=npc.get("personality", "neutral"),
            player_action="player greets",
            setting=zone_name, genre=genre,
        )
        zone["npcs"].append({**npc, "dialogue": lines})
        sleep(0.5)  # be kind to rate limits on free tier

    # Items
    for item in items:
        print(f"  Generating {item['name']}...")
        desc = generate_item(item["name"], item["type"],
                             item["rarity"], item.get("traits", []), genre)
        zone["items"].append({**item, "description": desc})
        sleep(0.5)

    Path(output_path).write_text(json.dumps(zone, indent=2))
    print(f"\nSaved to {output_path}")


generate_zone(
    zone_name="The Ashwood",
    genre="dark_fantasy",
    npcs=[
        {"name": "Brother Aldus", "role": "priest",    "personality": "devout, haunted"},
        {"name": "Old Brennan",   "role": "innkeeper", "personality": "gossipy, cowardly"},
    ],
    items=[
        {"name": "Soulbrand",  "type": "weapon", "rarity": "rare",      "traits": ["fire-touched"]},
        {"name": "Grey Cloak", "type": "armor",  "rarity": "uncommon",  "traits": ["woven from fog"]},
    ],
    output_path="ashwood_zone.json",
)
Enter fullscreen mode Exit fullscreen mode

The output file drops straight into a Godot ResourceLoader or Unity AssetDatabase import step.


Game Engine Integration

Godot (GDScript)

# Load pre-generated content at runtime
func load_npc_dialogue(npc_name: String) -> Array:
    var file = FileAccess.open("res://content/zone_data.json", FileAccess.READ)
    var data = JSON.parse_string(file.get_as_text())
    for npc in data["npcs"]:
        if npc["name"] == npc_name:
            return npc["dialogue"]  # [{text, emotion}, ...]
    return []

# Drive your dialogue system
func play_dialogue(lines: Array):
    for line in lines:
        $Portrait.set_emotion(line["emotion"])  # map to sprite frame
        $DialogueBox.text = line["text"]
        await $DialogueBox.finished
Enter fullscreen mode Exit fullscreen mode

Unity (C#)

[System.Serializable]
public class DialogueLine { public string text; public string emotion; }

[System.Serializable]
public class NpcData { public string name; public DialogueLine[] dialogue; }

// Load at scene start, play via your dialogue runner
NpcData npc = JsonUtility.FromJson<NpcData>(Resources.Load<TextAsset>("npcs/mira").text);
Enter fullscreen mode Exit fullscreen mode

Why an API Over a Local Model?

  • No GPU required - works on any machine, including CI/CD content pipelines
  • Structured output every time - no prompt engineering, no JSON parsing failures
  • Genre consistency - vocabulary and tone stay coherent across all calls in a session
  • Free tier covers a full indie game's worth of content (100 req/month on BASIC)

PRO ($9.99/mo) unlocks higher call limits - useful if you're running the pipeline on every content update or letting players trigger generation at runtime.


The API is live on RapidAPI - search "GameForge Content" by Circle of Wizards. If you ship something with it, drop the link in the comments - always curious what people build.

Top comments (0)