TextBlade is an early-development text-based JRPG game maker. Some details about TextBlade:
- Create games entirely through JSON, without writing any code! Focus on game design, theme, and content!
- Customize your game by writing C# code to fit your specific requirements!
- Almost everything has audio cues! Add strong audio design to make your game really shine (and really interesting to blind players)!
If you need help, support, or would even like changes made to TextBlade, join my Discord server here: https://discord.gg/XxHNM3UuKg.
Anything related to Void Walkers is content for the sample game.
- Quick Start
- Creating Your First Game
- Game Structure
- Content Creation Guide
- Audio System
- Game Switches
- Advanced Customization
- Building & Running
The fastest way to get started is to look at the VoidWalker sample game in source/VoidWalker.Main/. It demonstrates all the core features of TextBlade, including:
- Quest givers with pre/post-quest dialogue
- Switches and OnTalk actions for quest flow
- Dungeons with procedurally generated monsters
- Shops (equipment and items)
- Skills and status effects
- Audio integration
To run VoidWalker:
cd source\VoidWalker.Main
dotnet runKey Files to Explore:
Content/Locations/KingsVale/ThroneRoom.json- Quest giver with OnTalk actionContent/Locations/KingsVale/KingsVale.json- Location with switch-gated dungeon entranceContent/Dungeons/NorthSeasideCave.json- Dungeon configurationContent/Data/- Items, monsters, skills, and statuses
- Create a new .NET Console project referencing TextBlade.Core and TextBlade.ConsoleRunner:
<ItemGroup>
<ProjectReference Include="..\TextBlade.ConsoleRunner\TextBlade.ConsoleRunner.csproj" />
<ProjectReference Include="..\TextBlade.Core\TextBlade.Core.csproj" />
</ItemGroup>- Set up your Program.cs:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using TextBlade.ConsoleRunner;
using TextBlade.Core.Game;
namespace YourGame;
internal class Program
{
public static void Main(string[] args)
{
using var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((_, services) =>
{
services.AddTextBlade();
})
.Build();
host.Services.GetRequiredService<IGame>().Run();
}
}- Create a Content folder in your project directory with the following structure:
Content/
├── Game.json
├── Credits.txt
├── Data/
│ ├── Items.json
│ ├── Skills.json
│ ├── Monsters.json
│ └── Statuses.json
├── Locations/
│ └── YourTown/
│ └── YourTown.json
├── Dungeons/
└── Audio/
├── bgm/ (background music)
├── bgse/ (background sound effects)
└── sfx/ (sound effects)
- Ensure Content files are copied to output by adding this to your .csproj:
<ItemGroup>
<Content Include="Content/**/*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>TextBlade games are built using JSON configuration files in the Content folder. The engine handles all game logic, battle systems, inventory management, and progression. You just define your content!
Core Principle: Design your game in JSON first. Add C# code only when you need custom behavior.
Create Content/Game.json to configure your game:
{
"GameName": "My Awesome Game",
"StartingLocationId": "Locations/StartingTown/StartingTown",
"StartingParty": [
{
"Name": "Hero",
"CurrentHealth": 100,
"TotalHealth": 100,
"CurrentSkillPoints": 50,
"TotalSkillPoints": 50,
"Strength": 20,
"Toughness": 10,
"Special": 10,
"SpecialDefense": 10,
"SkillsLearnedAtLevel": [
{"Item1": "Double Strike", "Item2": 2},
{"Item1": "Heal", "Item2": 5}
]
}
]
}Properties:
GameName: Display name of your gameStartingLocationId: Path to the initial location JSON (without .json extension)StartingParty: Array of characters with their stats and skillsSkillsLearnedAtLevel: Skills learned at specific levels- Note: Yes,
Item1andItem2are the actual property names! This is how .NET serializes tuples. Item1= skill name (string)Item2= level when learned (number)
- Note: Yes,
Define your party members with these stats:
- Health:
CurrentHealthandTotalHealth - Skill Points:
CurrentSkillPointsandTotalSkillPoints(used for skills/magic) - Strength: Physical attack power
- Toughness: Physical defense
- Special: Magic/special attack power
- SpecialDefense: Magic/special defense
Create location files in Content/Locations/. Example: Content/Locations/KingsVale/KingsVale.json
{
"Name": "King's Vale",
"Description": "A small village ruled by King Sulayman.",
"BackgroundAudio": "bgse/seagulls",
"LinkedLocations": [
{"Id": "Locations/KingsVale/Inn", "Description": "The King's Inn"},
{"Id": "Locations/KingsVale/Shop", "Description": "The Item Shop"},
{"Id": "Dungeons/FirstCave", "Description": "Explore the cave", "SwitchRequired": "TalkedTo_King"}
],
"NPCs": [
{
"Name": "Villager",
"Texts": [
"Welcome to our village!",
"The king is worried about monsters.",
"Please help us!"
]
}
]
}Properties:
Name: Location display nameDescription: What players see when enteringBackgroundAudio: Path to audio file (relative to Content/Audio/)LinkedLocations: Exits/connections to other locationsId: Path to destination locationDescription: How the exit appears to playersSwitchRequired(optional): Game switch that must be true to show this exit
NPCs: Non-player characters in this location
Dungeons are special locations with random battles. Create in Content/Dungeons/:
{
"$type": "TextBlade.Core.Locations.Dungeon, TextBlade.Core",
"Name": "North Seaside Cave",
"Description": "A dark cave that smells of salt.",
"BackgroundAudios": [
"bgse/cave",
"bgse/bat-wings"
],
"LinkedLocations": [
{"Id": "Locations/KingsVale/KingsVale", "Description": "Exit to King's Vale"}
],
"NumFloors": 7,
"Monsters": ["Goblin", "Spider", "Gargoyle"],
"FloorLoot": {
"B2": ["Iron Sword", "Potion-A"],
"B5": ["Potion-B", "Iron Armour"]
},
"Boss": "Dragon King"
}Properties:
$type: Must be"TextBlade.Core.Locations.Dungeon, TextBlade.Core"NumFloors: Number of floors to completeMonsters: List of monster names that can appear (from Monsters.json)FloorLoot: Items found on specific floors (B1, B2, etc.)Boss: Boss monster name (appears on final floor)BackgroundAudios: Multiple audio tracks can play randomly/layered
Automatic Switch: Completing a dungeon sets CompletedDungeon_{DungeonName} (e.g., CompletedDungeon_NorthSeasideCave)
{
"$type": "TextBlade.Core.Locations.ItemShop, TextBlade.Core",
"Name": "Potions R Us",
"Description": "A room that smells like herbs.",
"BackgroundAudio": "bgse/bubbling",
"LinkedLocations": [
{"Id": "Locations/Town/Town", "Description": "Back to Town"}
],
"Items": [
"Potion-A",
"Potion-B"
]
}{
"$type": "TextBlade.Core.Locations.EquipmentShop, TextBlade.Core",
"Name": "Bronzebeard's Wares",
"Description": "Weapons and armor line the walls.",
"LinkedLocations": [
{"Id": "Locations/Town/Town", "Description": "Back to Town"}
],
"Items": [
"Iron Sword",
"Iron Helmet",
"Iron Armour"
]
}{
"$type": "TextBlade.Core.Locations.Inn, TextBlade.Core",
"Name": "The Cozy Inn",
"Description": "A warm, inviting inn.",
"LinkedLocations": [
{"Id": "Locations/Town/Town", "Description": "Back to Town"}
],
"Cost": 50
}Note: Players can rest at inns to restore HP/SP for a cost.
NPCs can say multiple things. Each time you talk to them, they rotate through their texts:
{
"Name": "Helpful Man",
"Texts": [
"Hello there!",
"Nice weather today.",
"Come back anytime!"
]
}Quest givers have different dialogue before and after quest completion:
{
"$type": "TextBlade.Core.Characters.QuestGiver, TextBlade.Core",
"Name": "King Sulayman",
"Texts": [
"Please clear the monsters from the cave!"
],
"PostQuestTexts": [
"Thank you for your service!"
],
"QuestCompleteSwitchName": "CompletedDungeon_NorthSeasideCave"
}Automatic Switch: Talking to a quest giver sets TalkedTo_{QuestGiverName} (e.g., TalkedTo_KingSulayman)
You can set custom switches when NPCs are talked to using the OnTalk action:
{
"Name": "Retired Hero",
"Texts": ["Some helpful advice..."],
"OnTalk": {
"$type": "TextBlade.Core.Game.Actions.SetSwitchAction, TextBlade.Core",
"SwitchName": "MetRetiredHero",
"Value": true
}
}This works for both regular NPCs and Quest Givers. Quest Givers automatically set their TalkedTo_ switch unless you override OnTalk. You can also explicitly set the switch in JSON:
{
"$type": "TextBlade.Core.Characters.QuestGiver, TextBlade.Core",
"Name": "King Sulayman",
"Texts": ["Please clear the cave!"],
"PostQuestTexts": ["Thank you!"],
"QuestCompleteSwitchName": "CompletedDungeon_Cave",
"OnTalk": {
"$type": "TextBlade.Core.Game.Actions.SetSwitchAction, TextBlade.Core",
"SwitchName": "TalkedTo_KingSulayman",
"Value": true
}
}The OnTalk action executes only once, the first time you talk to the NPC.
Define items in Content/Data/Items.json:
{
"Iron Sword": {
"$type": "TextBlade.Core.Inv.Equipment, TextBlade.Core",
"ItemType": "Weapon",
"StatsModifiers": { "Strength": 7 },
"Value": 200,
"DamageType": "Sword"
}
}{
"Iron Helmet": {
"$type": "TextBlade.Core.Inv.Equipment, TextBlade.Core",
"ItemType": "Helmet",
"StatsModifiers": { "Toughness": 3 },
"Value": 100
},
"Iron Armour": {
"$type": "TextBlade.Core.Inv.Equipment, TextBlade.Core",
"ItemType": "Armour",
"StatsModifiers": { "Toughness": 5 },
"Value": 150
}
}{
"Potion-A": {
"$type": "TextBlade.Core.Inv.Consumable, TextBlade.Core",
"ItemType": "Consumable",
"Value": 25,
"RestoreHealth": 50
},
"Potion-B": {
"$type": "TextBlade.Core.Inv.Consumable, TextBlade.Core",
"ItemType": "Consumable",
"Value": 75,
"RestoreHealth": 100,
"RestoreSkillPoints": 25
}
}Equipment Types: Weapon, Helmet, Armour, Accessory Damage Types: Sword, Fire, Ice, Poison, etc.
Define skills in Content/Data/Skills.json:
{
"Double Strike": {
"DamageMultiplier": 2.0,
"Cost": 10
},
"Whirlwind": {
"DamageMultiplier": 1.25,
"Cost": 20,
"Target": "AllEnemies"
},
"Fire A": {
"DamageMultiplier": 1.1,
"Cost": 7,
"DamageType": "Fire",
"StatusInflicted": "Burn",
"StatusStacks": 5
},
"Heal": {
"DamageMultiplier": 0,
"Cost": 15,
"Target": "Self",
"HealAmount": 50
}
}Properties:
DamageMultiplier: Multiplier for damage (0 = no damage)Cost: Skill points requiredTarget: "Single" (default), "AllEnemies", "Self", "AllAllies"DamageType: Fire, Ice, Poison, etc.StatusInflicted: Status effect to applyStatusStacks: Number of status stacks to applyHealAmount: HP restored (for healing skills)
Define monsters in Content/Data/Monsters.json:
{
"Goblin": {
"Health": 45,
"Strength": 15,
"Toughness": 3,
"Weakness": "Sword",
"Gold": 5,
"ExperiencePoints": 10
},
"Spider": {
"Health": 55,
"SkillPoints": 15,
"Strength": 15,
"Toughness": 5,
"Weakness": "Fire",
"Gold": 10,
"ExperiencePoints": 20,
"SkillNames": [
{ "Name": "Web", "Probability": 0.3 }
]
},
"Dragon King": {
"Health": 500,
"SkillPoints": 200,
"Strength": 50,
"Toughness": 30,
"Special": 40,
"SpecialDefense": 35,
"Weakness": "Ice",
"Gold": 1000,
"ExperiencePoints": 500,
"SkillNames": [
{ "Name": "Fire Breath", "Probability": 0.5 }
]
}
}Properties:
Health: Monster HPSkillPoints: SP for using skills (optional)Strength,Toughness,Special,SpecialDefense: Combat statsWeakness: Damage type this monster is weak toGold: Money dropped on defeatExperiencePoints: XP awardedSkillNames: Skills the monster can use with probability (0.0-1.0)
Define status effects in Content/Data/Statuses.json (poison, burn, etc.).
TextBlade supports three types of audio:
- Background Music (BGM): Place in
Content/Audio/bgm/ - Background Sound Effects (BGSE): Place in
Content/Audio/bgse/(ambient sounds like wind, water) - Sound Effects (SFX): Place in
Content/Audio/sfx/(action sounds)
Supported Formats: .wav, .mp3, .ogg
Usage in JSON:
"BackgroundAudio": "bgse/seagulls"
"BackgroundAudios": ["bgse/cave", "bgse/dripping-water"]Paths are relative to Content/Audio/.
Game switches are boolean flags that track game state (quests completed, events triggered, etc.).
Accessing in Code:
using TextBlade.Core.Game;
// Check a switch
if (GameSwitches.Switches["CompletedDungeon_FirstCave"])
{
// Do something
}
// Set a switch
GameSwitches.Switches["CustomEvent"] = true;Automatic Switches Set by TextBlade:
TalkedTo_{QuestGiverName}: Set when talking to a quest giverCompletedDungeon_{DungeonName}: Set when clearing a dungeon
Using Switches in JSON:
"LinkedLocations": [
{
"Id": "Dungeons/SecretArea",
"Description": "Enter the secret area",
"SwitchRequired": "CompletedDungeon_FirstCave"
}
]This exit only appears if the switch is true.
The VoidWalker sample game demonstrates a complete quest flow using switches and OnTalk actions:
1. King Sulayman's Quest (ThroneRoom.json):
{
"$type": "TextBlade.Core.Characters.QuestGiver, TextBlade.Core",
"Name": "King Sulayman",
"Texts": ["Please clear the monsters from the cave!"],
"PostQuestTexts": ["Thank you for your service!"],
"QuestCompleteSwitchName": "CompletedDungeon_NorthSeasideCave",
"OnTalk": {
"$type": "TextBlade.Core.Game.Actions.SetSwitchAction, TextBlade.Core",
"SwitchName": "TalkedTo_KingSulayman",
"Value": true
}
}2. Cave Unlocks After Talking (KingsVale.json):
"LinkedLocations": [
{
"Id": "Dungeons/NorthSeasideCave",
"Description": "Explore the North Seaside Cave",
"SwitchRequired": "TalkedTo_KingSulayman"
}
]How it works:
- Player talks to King Sulayman →
TalkedTo_KingSulaymanswitch is set totrue - Cave entrance appears in King's Vale (because switch requirement is met)
- Player clears the cave →
CompletedDungeon_NorthSeasideCaveis set automatically - King Sulayman shows
PostQuestTexts(because quest complete switch is true)
This pattern lets you create complex quest chains entirely in JSON without writing any code!
Add custom C# code to locations:
- Add LocationClass to JSON:
{
"Name": "Throne Room",
"LocationClass": "ThroneRoom",
...
}- Create the C# class:
using TextBlade.Core.UserCode;
using TextBlade.Core.Game;
[LocationCode]
public class ThroneRoom : LocationCodeBehind
{
public ThroneRoom()
{
// Custom initialization
if (!GameSwitches.Switches.ContainsKey("EnteredThroneRoom"))
{
GameSwitches.Switches["EnteredThroneRoom"] = true;
// Trigger custom event
}
}
}The [LocationCode] attribute tells TextBlade to load your custom class.
# Build the project
dotnet build
# Run the game
dotnet run
# Watch mode (auto-rebuild on changes)
dotnet watch run# Publish for Windows
dotnet publish -c Release -r win-x64 --self-contained
# Publish for Linux
dotnet publish -c Release -r linux-x64 --self-contained
# Publish for macOS
dotnet publish -c Release -r osx-x64 --self-containedPublished games will be in bin/Release/net8.0/{runtime}/publish/
Players can use these commands while playing:
- look / l: Examine current location
- go [location] / g [location]: Travel to a linked location
- talk [npc] / t [npc]: Talk to an NPC
- status / st: View party status
- inventory / i: View inventory
- equip [item]: Equip an item
- use [item]: Use a consumable
- buy [item]: Purchase from shops
- sleep: Rest at an inn (restores HP/SP)
- fight: Engage in battle (in dungeons)
- save: Save the game
- credits: View game credits
- help / h: Show help
- quit / q: Exit game
- Start Small: Create one town, one dungeon, and a few monsters first
- Balance Progression: Make sure monster difficulty scales with player levels
- Use Switches Creatively: Lock areas behind quests, create branching storylines
- Audio Matters: Good audio makes games more immersive, especially for blind players
- Test Frequently: Play through your game often to catch balance issues
- Study VoidWalker: The sample game demonstrates best practices
Create a Content/Credits.txt file to display credits when players type credits:
My Awesome Game created by [Your Name]
Special thanks to:
- [Contributor 1]
- [Contributor 2]
Audio resources:
- [Sound attribution]
- [Music attribution]
- Discord: https://discord.gg/XxHNM3UuKg
- Sample Game: Check out VoidWalker in
source/VoidWalker.Main/ - Documentation: See
TextBlade.mdfor additional details
Happy game making! ⚔️🎮
