01 Overview
This C# variables lesson 1 part 3 continues the introduction to basic data types and variable usage for technical art workflows.
Money vs. gameplay math. decimal is exact but 10–100× slower than float/double — use it only for prices and currency, never inside Update() or any per-frame loop. Keep gameplay math (positions, physics, timers) on float.
decimal — money and financial calculations
Beginner explanation: The MOST precise decimal type, designed specifically for money and financial calculations. Has ~28-29 digits of precision and avoids the rounding errors that plague float/double. ALWAYS use decimal for currency, prices, and financial data. Much slower than float/double, so NEVER use in game loops or real-time calculations. Requires 'm' or 'M' suffix!
Technical specs
- Size: 16 bytes (128 bits)
- Precision: ~28-29 significant digits
- Range: ±1.0 × 10⁻²⁸ to ±7.9228 × 10²⁸
- Default Value: 0.0m
Performance notes
- VERY SLOW - 10-100x slower than float/double
- No hardware acceleration (software-emulated)
- Uses 4x memory of float (16 vs 4 bytes)
- EXACT - no rounding errors for decimal values
- Perfect for financial calculations
- NEVER use in Update()/FixedUpdate() - too slow!
- Use ONLY for money, prices, currency
When to use decimal
- Money/Currency (prices, costs, balances)
- Financial calculations (interest, tax, exchange rates)
- Accounting/billing systems
- Shop UI (display prices)
- Any calculation where exact decimal precision is legally required
- NEVER in game loops (Update, FixedUpdate)
- NEVER for positions, physics, transforms
- NEVER for real-time calculations
- Don't use if float/double are sufficient
Syntax and declaration
// CRITICAL: Always add 'm' or 'M' suffix!
decimal price = 19.99m; // Correct
decimal tax = 0.15m; // Correct (15%)
decimal wrong = 19.99; // Error! Needs 'm' suffix
// Financial examples
decimal accountBalance = 1234567.89m;
decimal interestRate = 0.0425m; // 4.25%
decimal productPrice = 49.95m;
decimal totalCost = 199.99m;
// High precision
decimal precise = 0.1234567890123456789012345678m; // 28 digits!
Practical use cases
// In-game shop/economy (ALWAYS use decimal for money!)
decimal itemPrice = 9.99m;
decimal playerGold = 1500.50m;
decimal taxRate = 0.08m; // 8% tax
decimal CalculateTotalCost(decimal price, decimal taxRate)
{
return price * (1m + taxRate); // Exact calculation
}
decimal totalCost = CalculateTotalCost(itemPrice, taxRate);
playerGold -= totalCost;
// Shop system
class ShopItem
{
public string Name;
public decimal Price;
public int Quantity;
}
ShopItem sword = new ShopItem
{
Name = "Legendary Sword",
Price = 499.99m,
Quantity = 1
};
// Financial calculations (exact, no rounding errors!)
decimal principal = 1000.00m;
decimal annualRate = 0.05m; // 5%
decimal interest = principal * annualRate; // Exactly 50.00
decimal CompoundInterest(decimal principal, decimal rate, int years)
{
decimal amount = principal;
for (int i = 0; i < years; i++)
{
amount *= (1m + rate);
}
return amount - principal;
}
// Currency conversion
decimal usdAmount = 100.00m;
decimal exchangeRate = 1.18m; // USD to EUR
decimal eurAmount = usdAmount * exchangeRate; // Exactly 118.00
// Inventory system with precise values
decimal weaponPrice = 499.99m;
decimal armorPrice = 799.99m;
decimal discount = 0.15m; // 15% off
decimal CalculateDiscountedPrice(decimal originalPrice, decimal discountPercent)
{
return originalPrice * (1.0m - discountPercent);
}
decimal totalOriginal = weaponPrice + armorPrice; // 1299.98
decimal totalDiscounted = CalculateDiscountedPrice(totalOriginal, discount); // 1104.98
// Subscription/billing
decimal monthlyFee = 14.99m;
decimal yearlyFee = monthlyFee * 12m; // Exactly 179.88
decimal yearlyWithDiscount = 149.99m; // Annual plan price
decimal savings = yearlyFee - yearlyWithDiscount; // 29.89 saved
// Tax calculations
decimal CalculateSalesTax(decimal amount, decimal taxRate)
{
return amount * taxRate;
}
decimal CalculatePriceWithTax(decimal price, decimal taxRate)
{
return price + CalculateSalesTax(price, taxRate);
}
decimal itemPrice = 29.99m;
decimal taxRate = 0.08m; // 8%
decimal tax = CalculateSalesTax(itemPrice, taxRate); // 2.3992
decimal total = CalculatePriceWithTax(itemPrice, taxRate); // 32.3892
// Multiple items with exact totals
class CartItem
{
public decimal Price;
public int Quantity;
public decimal Total => Price * Quantity;
}
List<CartItem> cart = new List<CartItem>
{
new CartItem { Price = 19.99m, Quantity = 2 },
new CartItem { Price = 5.50m, Quantity = 3 },
new CartItem { Price = 12.75m, Quantity = 1 }
};
decimal cartTotal = cart.Sum(item => item.Total); // Exact sum
Common pitfalls
// PITFALL 1: Forgetting 'm' suffix
decimal price = 19.99; // Error!
decimal price = 19.99m; // Correct
// PITFALL 2: Using in game loops (VERY SLOW!)
void Update() // BAD! Called 60 times per second!
{
decimal time = (decimal)Time.deltaTime; // WRONG! deltaTime is float
decimal calculation = time * 5.5m; // 10-100x slower than float!
}
// CORRECT: Use decimal ONLY for money/UI, not real-time calculations
void BuyItem() // Called once when player clicks "Buy"
{
decimal cost = 19.99m; // OK - not in real-time loop
playerMoney -= cost;
}
// PITFALL 3: Mixing with float/double requires casting
float health = 100.0f;
decimal price = 19.99m;
decimal wrong = health * price; // Error! Can't mix float and decimal
// Fix: Cast explicitly (but why are you mixing?)
decimal correct = (decimal)health * price; // Works, but reconsider design
// PITFALL 4: Math class doesn't support decimal!
decimal value = 25.0m;
decimal result = Math.Sqrt(value); // Error! Math.Sqrt takes double
// Fix: Cast to double, calculate, cast back (loses precision!)
decimal result = (decimal)Math.Sqrt((double)value);
// PITFALL 5: Much slower than float/double (~10-100x!)
// Benchmark comparison:
float floatCalc = 0.0f;
for (int i = 0; i < 1000000; i++)
{
floatCalc += 0.1f; // Fast (~2ms)
}
decimal decimalCalc = 0.0m;
for (int i = 0; i < 1000000; i++)
{
decimalCalc += 0.1m; // Slow (~200ms) - 100x slower!
}
// PITFALL 6: Division precision
decimal result = 1m / 3m; // 0.3333333333333333333333333333 (28 digits)
// Still limited, just more precise than float/double
// PITFALL 7: Performance in tight loops
// WRONG - never use decimal for gameplay!
void FixedUpdate()
{
decimal position = (decimal)transform.position.x; // TERRIBLE!
position += 0.1m;
transform.position = new Vector3((float)position, 0, 0);
}
// CORRECT - use float for gameplay, decimal for money
float position = transform.position.x;
position += 0.1f;
transform.position = new Vector3(position, 0, 0);
Pro tips
// TIP 1: ALWAYS use decimal for money - it's the law in financial software!
decimal productPrice = 29.99m;
decimal tax = productPrice * 0.08m; // Exact: 2.3992 (no rounding errors)
decimal total = productPrice + tax; // Exact: 32.3892
// TIP 2: Rounding money correctly
decimal amount = 19.996m;
decimal rounded = Math.Round(amount, 2); // 20.00 (2 decimal places)
decimal roundedDown = Math.Floor(amount * 100m) / 100m; // 19.99 (floor)
decimal roundedUp = Math.Ceiling(amount * 100m) / 100m; // 20.00 (ceiling)
// TIP 3: Banker's rounding (MidpointRounding)
decimal value = 2.5m;
decimal banker = Math.Round(value, MidpointRounding.ToEven); // 2.0 (even)
decimal away = Math.Round(value, MidpointRounding.AwayFromZero); // 3.0
// Always specify mode for financial calculations
decimal RoundMoney(decimal amount)
{
return Math.Round(amount, 2, MidpointRounding.AwayFromZero);
}
// TIP 4: Currency formatting
decimal price = 1234.56m;
string formatted = price.ToString("C"); // "$1,234.56" (culture-specific)
string custom = price.ToString("0.00"); // "1234.56"
string withSymbol = price.ToString("$#,##0.00"); // "$1,234.56"
// TIP 5: Comparison (no epsilon needed - exact!)
decimal a = 0.1m + 0.2m;
decimal b = 0.3m;
if (a == b) // TRUE! (exact comparison works with decimal)
{
// Decimal has no precision errors like float/double!
}
// TIP 6: Don't use decimal for game calculations
// WRONG:
decimal playerPosition = 10.5m; // Use float!
decimal velocity = 5.5m; // Use float!
decimal deltaTime = 0.016m; // Use float!
// CORRECT:
decimal itemPrice = 19.99m; // Money
decimal accountBalance = 500.0m; // Money
decimal taxAmount = 1.60m; // Money
// TIP 7: Store prices as integers (cents) for database (alternative)
// Alternative approach: Store as int (cents), convert to decimal for display
int priceInCents = 1999; // $19.99 stored as 1999 cents
decimal displayPrice = priceInCents / 100.0m; // Convert to 19.99m
int CentsToInt(decimal price)
{
return (int)(price * 100m);
}
decimal IntToCents(int cents)
{
return cents / 100.0m;
}
// TIP 8: Separate financial logic from gameplay logic
class GameEconomy // Money calculations - use decimal
{
public decimal PlayerGold { get; set; }
public decimal ItemPrice { get; set; }
public bool CanAfford(decimal cost)
{
return PlayerGold >= cost;
}
public void Purchase(decimal cost)
{
if (CanAfford(cost))
{
PlayerGold -= cost;
}
}
}
class GameCharacter // Gameplay - use float
{
public float Health { get; set; }
public float Speed { get; set; }
}
// TIP 9: Percentage calculations (exact)
decimal CalculatePercentage(decimal value, decimal total)
{
if (total == 0m) return 0m;
return (value / total) * 100m;
}
decimal percentage = CalculatePercentage(75m, 100m); // 75.0
// TIP 10: Discount calculations
decimal ApplyDiscount(decimal price, decimal discountPercent)
{
decimal discount = price * discountPercent;
return price - discount;
}
decimal originalPrice = 99.99m;
decimal discountedPrice = ApplyDiscount(originalPrice, 0.20m); // 79.99
// TIP 11: Split bill calculations (exact)
decimal SplitBill(decimal total, int people)
{
return Math.Round(total / people, 2, MidpointRounding.AwayFromZero);
}
decimal billTotal = 127.50m;
decimal perPerson = SplitBill(billTotal, 3); // 42.50 each
// TIP 12: Tax calculation helpers
class TaxCalculator
{
public decimal CalculateTax(decimal amount, decimal rate)
{
return Math.Round(amount * rate, 2, MidpointRounding.AwayFromZero);
}
public decimal CalculateTotal(decimal amount, decimal taxRate)
{
decimal tax = CalculateTax(amount, taxRate);
return amount + tax;
}
public decimal CalculatePreTaxAmount(decimal total, decimal taxRate)
{
return total / (1m + taxRate);
}
}
// TIP 13: Constants for financial calculations
const decimal VAT_RATE = 0.20m; // 20% VAT
const decimal SALES_TAX = 0.08m; // 8% sales tax
const decimal DISCOUNT_RATE = 0.15m; // 15% discount
// TIP 14: Min/Max for decimal
decimal min = Math.Min(price1, price2);
decimal max = Math.Max(price1, price2);
// TIP 15: Clamp for decimal
decimal Clamp(decimal value, decimal min, decimal max)
{
if (value < min) return min;
if (value > max) return max;
return value;
}
decimal clampedPrice = Clamp(userInput, 0.01m, 9999.99m);
char — single character
Beginner explanation: Stores a single character like 'A', '5', '@', or '中'. Uses single quotes, not double quotes! Internally it's just a number (0-65535) representing a Unicode character. Perfect for keyboard input, single letters, or text parsing.
Technical specs
- Size: 2 bytes (16 bits)
- Range: '\u0000' to '\uffff' (0 to 65,535 - Unicode characters)
- Default Value: '\0' (null character)
Performance notes
- Fast (just a 16-bit integer internally)
- 2 bytes (same as ushort)
- Good for menu systems, keyboard input
- String is usually more practical for text
- Use char for single-character operations only
When to use char
- Single character storage
- Keyboard input (single key)
- Menu options (A, B, C, D)
- Character-by-character text parsing
- Grades, ratings (A, B, C)
- Direction indicators (N, S, E, W)
- Don't use for multi-character text (use string)
- Emojis need string (surrogate pairs)
Syntax and declaration
// Basic declaration (use SINGLE quotes!)
char letter = 'A'; // Correct
char number = '5'; // Correct (character '5', not number 5!)
char symbol = '@'; // Correct
char wrong = "A"; // Error! Double quotes are for strings
// Special characters (escape sequences)
char newline = '\n'; // Newline
char tab = '\t'; // Tab
char backslash = '\\'; // Backslash
char quote = '\''; // Single quote
char nullChar = '\0'; // Null character (default)
// Unicode characters
char smiley = '\u263A'; // ☺ (Unicode code)
char chinese = '中'; // Chinese character
char euro = '€'; // Euro symbol
// Getting char from string
string name = "Player";
char firstLetter = name[0]; // 'P'
char lastLetter = name[name.Length - 1]; // 'r'
Practical use cases
// Keyboard input
char keyPressed = 'W'; // WASD movement
char menuOption = 'A'; // Menu selection (A, B, C)
char direction = 'N'; // N, S, E, W
void ProcessInput(char key)
{
switch (key)
{
case 'W': MoveForward(); break;
case 'A': MoveLeft(); break;
case 'S': MoveBackward(); break;
case 'D': MoveRight(); break;
default: break;
}
}
// Unity keyboard input
void Update()
{
if (Input.anyKeyDown)
{
foreach (char c in Input.inputString)
{
ProcessCharacter(c);
}
}
}
// Game mechanics
char grade = 'A'; // Student grade (A, B, C, D, F)
char rank = 'S'; // S, A, B, C rank system
char difficulty = 'H'; // E (Easy), N (Normal), H (Hard)
// Menu system
char[] menuOptions = { 'A', 'B', 'C', 'D' };
char selectedOption = 'A';
// Text-based commands
char command = GetUserCommand();
if (command == 'Q') QuitGame();
if (command == 'I') OpenInventory();
if (command == 'M') OpenMap();
// Character analysis
char letter = 'A';
bool isUpper = char.IsUpper(letter); // true
bool isLower = char.IsLower(letter); // false
bool isLetter = char.IsLetter(letter); // true
bool isDigit = char.IsDigit(letter); // false
// Parsing text
string text = "HP:100";
for (int i = 0; i < text.Length; i++)
{
char c = text[i];
if (char.IsDigit(c))
{
int digit = c - '0'; // Convert char digit to int
}
}
// Direction system
enum Direction { North, South, East, West }
Direction GetDirectionFromChar(char c)
{
return c switch
{
'N' or 'n' => Direction.North,
'S' or 's' => Direction.South,
'E' or 'e' => Direction.East,
'W' or 'w' => Direction.West,
_ => Direction.North
};
}
Common pitfalls
// PITFALL 1: Single vs double quotes
char letter = 'A'; // char - single quotes
string word = "A"; // string - double quotes
char wrong = "A"; // Error!
// PITFALL 2: '5' is NOT the number 5!
char charFive = '5'; // Character '5' (Unicode 53)
int numFive = 5; // Number 5
if (charFive == 5) // FALSE! (53 != 5)
if (charFive == '5') // TRUE!
// Convert char digit to int:
int digit = charFive - '0'; // '5' - '0' = 5 (the number!)
// PITFALL 3: char is actually a number (ushort)
char a = 'A'; // Internally: 65
char b = (char)(a + 1); // 'B' (66)
char c = (char)(a + 32); // 'a' (97) - lowercase!
int asciiValue = (int)a; // 65
// PITFALL 4: Emojis need TWO chars (surrogate pairs)
char emoji = '😀'; // Doesn't work correctly!
string emojiStr = "😀"; // Use string for emojis
// Most emojis are outside the 16-bit range (> U+FFFF)
// PITFALL 5: Can't store multiple characters
char multiple = 'AB'; // Error! Only single character allowed
string multiple = "AB"; // Use string
// PITFALL 6: Case sensitivity
char upper = 'A';
char lower = 'a';
if (upper == lower) // FALSE! ('A' != 'a')
// Case-insensitive comparison
if (char.ToLower(upper) == char.ToLower(lower)) // TRUE!
// PITFALL 7: Null character vs null
char nullChar = '\0'; // Null character (valid char, value = 0)
string nullStr = null; // Null reference (no object)
// These are DIFFERENT!
Pro tips
// TIP 1: Check if char is letter/digit/etc
char input = 'A';
bool isLetter = char.IsLetter(input); // true
bool isDigit = char.IsDigit(input); // false
bool isUpper = char.IsUpper(input); // true
bool isLower = char.IsLower(input); // false
bool isWhiteSpace = char.IsWhiteSpace(' '); // true
bool isLetterOrDigit = char.IsLetterOrDigit(input); // true
bool isPunctuation = char.IsPunctuation('!'); // true
// TIP 2: Convert case
char lower = char.ToLower('A'); // 'a'
char upper = char.ToUpper('a'); // 'A'
// TIP 3: Keyboard input in Unity
void Update()
{
if (Input.anyKeyDown)
{
foreach (char c in Input.inputString)
{
if (char.IsLetter(c))
{
ProcessLetter(c);
}
}
}
}
// TIP 4: Parse single character commands
char command = GetUserInput();
switch (command)
{
case 'W': case 'w': MoveForward(); break;
case 'A': case 'a': MoveLeft(); break;
case 'S': case 's': MoveBack(); break;
case 'D': case 'd': MoveRight(); break;
case 'Q': case 'q': Quit(); break;
default: InvalidCommand(); break;
}
// TIP 5: Convert between char and int
char letter = 'A';
int ascii = (int)letter; // 65
char back = (char)ascii; // 'A'
// Unicode value
char unicode = '\u0041'; // 'A'
int unicodeValue = (int)unicode; // 65
// TIP 6: Iterate through alphabet
for (char c = 'A'; c <= 'Z'; c++)
{
Console.WriteLine(c); // A, B, C, ..., Z
}
for (char c = 'a'; c <= 'z'; c++)
{
Console.WriteLine(c); // a, b, c, ..., z
}
// TIP 7: Check character range
bool IsVowel(char c)
{
c = char.ToLower(c);
return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
}
bool IsConsonant(char c)
{
return char.IsLetter(c) && !IsVowel(c);
}
// TIP 8: Convert char digit to int
char digit = '7';
if (char.IsDigit(digit))
{
int number = digit - '0'; // 7
}
// TIP 9: Check if char is in range
bool IsUppercaseLetter(char c)
{
return c >= 'A' && c <= 'Z';
}
bool IsLowercaseLetter(char c)
{
return c >= 'a' && c <= 'z';
}
bool IsDigitChar(char c)
{
return c >= '0' && c <= '9';
}
// TIP 10: Character comparison (case-insensitive)
bool EqualsIgnoreCase(char a, char b)
{
return char.ToLower(a) == char.ToLower(b);
}
// TIP 11: Get character from string safely
char GetCharAt(string text, int index)
{
if (index >= 0 && index < text.Length)
return text[index];
return '\0'; // Return null char if out of bounds
}
// TIP 12: Build string from chars
char[] chars = { 'H', 'e', 'l', 'l', 'o' };
string word = new string(chars); // "Hello"
// TIP 13: Char array for text processing
char[] buffer = new char[100];
int length = 0;
void AddChar(char c)
{
if (length < buffer.Length)
{
buffer[length++] = c;
}
}
string GetText()
{
return new string(buffer, 0, length);
}
// TIP 14: Character validation
bool IsValidUsernameChar(char c)
{
return char.IsLetterOrDigit(c) || c == '_' || c == '-';
}
// TIP 15: Escape sequence reference
char newline = '\n'; // Line feed
char carriageReturn = '\r'; // Carriage return
char tab = '\t'; // Horizontal tab
char backspace = '\b'; // Backspace
char formFeed = '\f'; // Form feed
char backslash = '\\'; // Backslash
char singleQuote = '\''; // Single quote
char doubleQuote = '\"'; // Double quote (in char literal)
char zeroChar = '\0'; // Null character
string — text sequences
Beginner explanation: Stores text of any length - from empty "" to entire novels! Uses double quotes. Strings are IMMUTABLE (can't be changed after creation) - modifying a string actually creates a new one in memory. Use for names, messages, UI text, file paths - anything textual.
Technical specs
- Size: Dynamic (depends on text length)
- Type: Reference type (stored on heap, not stack)
- Immutable: Once created, cannot be modified
- Default Value: null
Performance notes
- Immutable - every modification creates new string object
- Concatenation in loops is VERY slow (use StringBuilder)
- String interpolation ($"") is optimized by compiler
- String comparison is fast (interned strings)
- Use StringBuilder for 10+ concatenations
- Memory: Stores on heap (garbage collected)
When to use string
- Player names, item names, descriptions
- UI text, dialogue, messages
- File paths, URLs
- JSON, XML, serialized data
- User input, commands
- Don't concatenate in loops (use StringBuilder)
- Don't use for single characters (use char)
Syntax and declaration
// Basic declaration
string playerName = "Hero";
string message = "Hello, World!";
string empty = ""; // Empty string (not null!)
string nullString = null; // No string assigned
// Multi-line strings (verbatim string literal)
string multiLine = @"Line 1
Line 2
Line 3";
// Escape sequences
string withQuotes = "He said \"Hello\""; // He said "Hello"
string withNewline = "Line1\nLine2"; // Line break
string path = "C:\\Users\\Player\\"; // Backslashes
string verbatimPath = @"C:\Users\Player\"; // Verbatim (no escaping needed)
// String interpolation (MODERN WAY - use this!)
int score = 100;
string text = $"Your score is {score}"; // "Your score is 100"
string complex = $"Player: {playerName}, HP: {health}/100";
// Old way (concatenation - avoid!)
string old = "Score: " + score; // Slower, less readable
Practical use cases
// Game examples
string playerName = "Steve";
string dialogueText = "Welcome to the adventure!";
string itemName = "Legendary Sword";
string itemDescription = "A powerful weapon forged in ancient times.";
// UI text
string healthText = $"HP: {currentHealth}/{maxHealth}";
string scoreText = $"Score: {score:N0}"; // "Score: 1,000,000" (formatted)
string timerText = $"Time: {timeRemaining:F2}s"; // "Time: 45.32s"
// File paths (use @ verbatim strings!)
string savePath = @"C:\Users\Player\Saves\save001.dat";
string assetPath = "Assets/Prefabs/Player.prefab"; // Unity style
string configPath = Path.Combine(Application.dataPath, "config.json");
// JSON/Data
string jsonData = "{\"name\":\"Player\",\"level\":10}";
string csvLine = "Name,Health,Score\nPlayer,100,1500";
// Commands/Input
string userInput = Console.ReadLine();
string command = GetConsoleInput().ToLower().Trim();
if (command == "attack")
{
PerformAttack();
}
// Dialogue system
Dictionary<string, string> dialogues = new Dictionary<string, string>
{
{ "greeting", "Hello, traveler!" },
{ "quest", "I need your help with something..." },
{ "farewell", "Safe travels!" }
};
string dialogue = dialogues["greeting"];
// Save data
string SaveToJson()
{
return JsonUtility.ToJson(playerData);
}
// Localization
Dictionary<string, string> translations = new Dictionary<string, string>
{
{ "en_greeting", "Hello" },
{ "ru_greeting", "Здравствуйте" },
{ "jp_greeting", "こんにちは" }
};
string GetLocalizedText(string key, string language)
{
return translations[$"{language}_{key}"];
}
Common pitfalls
// PITFALL 1: Strings are IMMUTABLE!
string name = "Bob";
name.ToUpper(); // This does NOTHING! Doesn't modify 'name'
// ToUpper() returns a NEW string, doesn't change original
// Correct:
string upperName = name.ToUpper(); // "BOB" (new string)
name = name.ToUpper(); // Reassign to same variable
// PITFALL 2: String concatenation in loops is SLOW!
string result = "";
for (int i = 0; i < 1000; i++)
{
result += i.ToString(); // Creates 1000 string objects! Very slow!
}
// Use StringBuilder for loops:
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
sb.Append(i); // Much faster!
}
string result = sb.ToString();
// PITFALL 3: null vs empty string
string nullStr = null;
string emptyStr = "";
if (nullStr == "") // NullReferenceException!
if (emptyStr == null) // false
// Safe check:
if (string.IsNullOrEmpty(nullStr)) // true
if (string.IsNullOrEmpty(emptyStr)) // true
if (string.IsNullOrWhiteSpace(" ")) // true (also checks whitespace)
// PITFALL 4: Comparing strings (case-sensitive!)
string a = "Hello";
string b = "hello";
if (a == b) // false (case-sensitive!)
if (a.Equals(b, StringComparison.OrdinalIgnoreCase)) // true (ignore case)
// PITFALL 5: Forgetting to use @ for paths
string path1 = "C:\Users\New\test.txt"; // \U and \N are escape sequences!
string path2 = @"C:\Users\New\test.txt"; // Verbatim - no escaping
// PITFALL 6: String methods don't modify original
string text = " hello ";
text.Trim(); // Does nothing to 'text'!
text = text.Trim(); // Correct - reassign result
// PITFALL 7: Using + instead of interpolation
string name = "Player";
int level = 10;
string bad = "Name: " + name + ", Level: " + level; // Slow, ugly
string good = $"Name: {name}, Level: {level}"; // Fast, readable
Pro tips
// TIP 1: String interpolation is your friend!
int health = 75;
int maxHealth = 100;
string oldWay = "HP: " + health + "/" + maxHealth; // Ugly, slow
string newWay = $"HP: {health}/{maxHealth}"; // Clean, fast
// TIP 2: Formatting numbers
decimal price = 1234.56m;
string formatted = $"Price: ${price:F2}"; // "Price: $1234.56"
string withCommas = $"{score:N0}"; // "1,234,567"
string percent = $"{ratio:P0}"; // "75%" (if ratio = 0.75)
// Format specifiers:
// F2 - fixed-point, 2 decimals
// N0 - number with thousands separator, 0 decimals
// P0 - percentage, 0 decimals
// C - currency (culture-specific)
// TIP 3: Multi-line interpolation
string report = $@"
Player Stats:
-----------------
Name: {playerName}
Health: {health}/{maxHealth}
Score: {score:N0}
Level: {level}
";
// TIP 4: Common string operations
string text = "Hello World";
bool contains = text.Contains("World"); // true
bool starts = text.StartsWith("Hello"); // true
bool ends = text.EndsWith("World"); // true
string upper = text.ToUpper(); // "HELLO WORLD"
string lower = text.ToLower(); // "hello world"
string replaced = text.Replace("World", "Unity"); // "Hello Unity"
string[] words = text.Split(' '); // ["Hello", "World"]
string trimmed = " text ".Trim(); // "text"
string trimStart = " text ".TrimStart(); // "text "
string trimEnd = " text ".TrimEnd(); // " text"
// TIP 5: Substring operations
string full = "Player_001";
string prefix = full.Substring(0, 6); // "Player"
string number = full.Substring(7); // "001"
int index = full.IndexOf('_'); // 6
int lastIndex = full.LastIndexOf('_'); // 6
// TIP 6: Check for null or empty (Unity-safe)
if (!string.IsNullOrEmpty(playerName))
{
DisplayName(playerName);
}
if (!string.IsNullOrWhiteSpace(userInput))
{
ProcessInput(userInput);
}
// TIP 7: StringBuilder for lots of concatenation
StringBuilder sb = new StringBuilder();
sb.Append("Hello");
sb.Append(" ");
sb.Append("World");
sb.AppendLine("!"); // Adds newline
sb.Insert(0, ">> "); // Insert at position
sb.Remove(0, 3); // Remove 3 chars from position 0
string result = sb.ToString();
// TIP 8: String joining
string[] items = { "Sword", "Shield", "Potion" };
string inventory = string.Join(", ", items); // "Sword, Shield, Potion"
// With newlines
string list = string.Join("\n", items);
// TIP 9: Parsing from strings
string numberStr = "123";
int number = int.Parse(numberStr); // 123
// Safer with TryParse:
if (int.TryParse(numberStr, out int result))
{
// result = 123, parsing succeeded
}
else
{
// Parsing failed, result = 0
}
// Other types:
float.TryParse("3.14", out float floatResult);
bool.TryParse("true", out bool boolResult);
// TIP 10: Path operations (use Path class!)
string combined = Path.Combine("Assets", "Prefabs", "Player.prefab");
string directory = Path.GetDirectoryName(filePath);
string filename = Path.GetFileName(filePath);
string extension = Path.GetExtension(filePath); // ".prefab"
string withoutExt = Path.GetFileNameWithoutExtension(filePath);
// TIP 11: String comparison options
string a = "Hello";
string b = "HELLO";
bool equal = a.Equals(b, StringComparison.OrdinalIgnoreCase); // true
bool contains = a.Contains("ello", StringComparison.OrdinalIgnoreCase); // true (.NET 5+)
// Comparison types:
// Ordinal - byte-by-byte comparison (fastest)
// OrdinalIgnoreCase - case-insensitive byte comparison
// CurrentCulture - culture-specific comparison
// InvariantCulture - culture-invariant comparison
// TIP 12: String padding
string text = "123";
string padded = text.PadLeft(5, '0'); // "00123"
string paddedRight = text.PadRight(5, '0'); // "12300"
// TIP 13: Removing characters
string text = "Hello, World!";
string noCommas = text.Replace(",", ""); // "Hello World!"
string noSpaces = text.Replace(" ", ""); // "Hello,World!"
// Remove specific chars
char[] charsToRemove = { ',', '!' };
string cleaned = new string(text.Where(c => !charsToRemove.Contains(c)).ToArray());
// TIP 14: Checking if string contains only certain chars
bool IsAlphanumeric(string text)
{
return text.All(char.IsLetterOrDigit);
}
bool IsNumeric(string text)
{
return text.All(char.IsDigit);
}
// TIP 15: String constants (better than magic strings)
public static class GameText
{
public const string GAME_OVER = "Game Over";
public const string PAUSED = "Paused";
public const string LOADING = "Loading...";
}
// Use:
uiText.text = GameText.GAME_OVER;
// TIP 16: Format templates
string FormatHealth(int current, int max)
{
return $"HP: {current}/{max} ({(float)current / max:P0})";
}
// "HP: 75/100 (75%)"
// TIP 17: Empty string constant
string empty1 = "";
string empty2 = string.Empty; // Same thing, more explicit
// TIP 18: String builder capacity (optimization)
StringBuilder sb = new StringBuilder(1000); // Pre-allocate for 1000 chars
// Avoids multiple memory reallocations
bool — true/false values
Beginner explanation: Stores only two values: true or false. Essential for game logic, conditions, and states. Think of it as an on/off switch, yes/no answer, or enabled/disabled flag. Uses 1 byte of memory despite being just 1 bit of information.
Technical specs
- Size: 1 byte (8 bits) - but logically 1 bit
- Values: true or false only
- Default Value: false
Performance notes
- Very fast (1 byte, simple operations)
- Comparison operators are optimized
- Short-circuit evaluation saves CPU
- Despite being 1 bit logically, uses 1 byte (memory alignment)
- For thousands of flags, consider bit packing
When to use bool
- Any yes/no, true/false, on/off state
- Conditions and flags
- Ability checks (canJump, canAttack)
- State tracking (isAlive, isPaused)
- UI visibility (showMenu, isEnabled)
- Don't use int (0/1) instead of bool
- Don't use for multi-state (use enum instead)
Syntax and declaration
// Basic declaration
bool isAlive = true;
bool hasKey = false;
bool gameOver = false;
// From comparisons
bool isGreater = 10 > 5; // true
bool isEqual = health == 100; // true/false
bool isNotEqual = score != 0; // true/false
// Logical operators
bool canAttack = isAlive && hasWeapon; // AND
bool canMove = !isStunned; // NOT (negation)
bool shouldHeal = health < 50 || hasBuff; // OR
// Default value
bool flag; // Defaults to false (not null!)
Practical use cases
// Game state flags
bool isPlayerAlive = true;
bool isPaused = false;
bool isGameOver = false;
bool isLevelComplete = false;
bool isLoading = false;
// Player abilities
bool canJump = true;
bool canDoubleJump = false;
bool canDash = false;
bool isGrounded = false;
bool isCrouching = false;
bool isSprinting = false;
// Inventory/Items
bool hasKey = false;
bool hasWeapon = true;
bool hasArmor = false;
bool inventoryFull = false;
bool canPickup = true;
// UI state
bool isMenuOpen = false;
bool isInventoryVisible = false;
bool showHealthBar = true;
bool showMinimap = true;
bool muteSound = false;
// Enemy AI
bool playerDetected = false;
bool isChasing = false;
bool isAttacking = false;
bool canSeePlayer = false;
bool isPatrolling = true;
// Conditions
if (isPlayerAlive && !isGameOver)
{
UpdateGameplay();
}
if (hasKey && isNearDoor)
{
OpenDoor();
}
// Toggle
bool soundEnabled = true;
soundEnabled = !soundEnabled; // Toggle (true → false, false → true)
// Movement system
class PlayerController
{
bool isGrounded;
bool isJumping;
bool isFalling;
void Update()
{
isGrounded = CheckGrounded();
if (Input.GetKeyDown(KeyCode.Space) && isGrounded)
{
Jump();
isJumping = true;
}
}
}
// Ability cooldown
bool canUseAbility = true;
float abilityCooldown = 5.0f;
void UseAbility()
{
if (canUseAbility)
{
PerformAbility();
canUseAbility = false;
StartCoroutine(AbilityCooldown());
}
}
IEnumerator AbilityCooldown()
{
yield return new WaitForSeconds(abilityCooldown);
canUseAbility = true;
}
Common pitfalls
// PITFALL 1: Redundant comparisons
bool isAlive = true;
if (isAlive == true) // Redundant! 'isAlive' is already bool
{
// ...
}
// Correct:
if (isAlive) // Clean, readable
{
// ...
}
if (!isAlive) // Check for false
{
// ...
}
// PITFALL 2: Using 0/1 instead of bool
int flag = 1; // Don't use int for true/false!
bool flag = true; // Use bool
// PITFALL 3: Confusing = with ==
bool isAlive = true;
if (isAlive = false) // ASSIGNMENT, not comparison!
{
// This SETS isAlive to false and executes the block!
}
if (isAlive == false) // Comparison
{
// Checks if isAlive is false
}
// Better:
if (!isAlive) // Most readable
{
// ...
}
// PITFALL 4: Complex boolean expressions without parentheses
if (a && b || c && d) // Unclear! What's the order?
if ((a && b) || (c && d)) // Clear with parentheses
// PITFALL 5: Not initializing flags
bool initialized; // Defaults to false, but be explicit!
bool initialized = false; // Better - clear intent
// PITFALL 6: Using bool for multi-state
// Bad: Multiple bools for states
bool isIdle = true;
bool isWalking = false;
bool isRunning = false;
// What if multiple are true?
// Better: Use enum for states
enum PlayerState { Idle, Walking, Running }
PlayerState state = PlayerState.Idle;
// PITFALL 7: Negative boolean names
bool notReady = true; // Confusing!
if (!notReady) // Double negative - hard to read
// Better:
bool isReady = false;
if (isReady) // Clear and readable
Pro tips
// TIP 1: Use meaningful names (is/has/can/should prefixes)
bool isAlive; // State
bool hasWeapon; // Possession
bool canJump; // Ability
bool shouldUpdate; // Condition
bool willRespawn; // Future state
// Bad names:
bool flag1, flag2, alive, weapon;
// TIP 2: Logical operators
bool a = true, b = false;
bool and = a && b; // AND: both must be true → false
bool or = a || b; // OR: at least one must be true → true
bool not = !a; // NOT: inverts the value → false
bool xor = a ^ b; // XOR: exactly one must be true → true
// TIP 3: Short-circuit evaluation
if (player != null && player.health > 0)
{
// If player is null, player.health is never evaluated!
// Prevents NullReferenceException
}
if (isGameOver || CheckWinCondition())
{
// If isGameOver is true, CheckWinCondition() is not called!
// Saves CPU if function is expensive
}
// TIP 4: Ternary operator (conditional expression)
bool isAlive = health > 0;
string status = isAlive ? "Alive" : "Dead";
int damage = isCritical ? baseDamage * 2 : baseDamage;
// Multi-line for readability
string message = hasKey
? "You unlocked the door!"
: "You need a key.";
// TIP 5: Toggle pattern
bool soundEnabled = true;
soundEnabled = !soundEnabled; // Flip value
// Or with XOR (advanced):
soundEnabled ^= true; // Toggle
// TIP 6: Combining conditions
bool canAttack = isAlive && !isStunned && hasWeapon && !isReloading;
bool shouldRespawn = isDead && (respawnTime <= 0 || playerPressedButton);
// TIP 7: Bool methods for clarity
bool IsEnemyInRange()
{
float distance = Vector3.Distance(player.position, enemy.position);
return distance <= attackRange;
}
bool CanAfford(int cost)
{
return playerGold >= cost;
}
// Usage:
if (IsEnemyInRange())
{
Attack();
}
// TIP 8: Bool arrays for grid-based games
bool[,] tileOccupied = new bool[10, 10];
tileOccupied[5, 3] = true; // Mark tile as occupied
bool IsTileBlocked(int x, int y)
{
return tileOccupied[x, y];
}
// TIP 9: Validation patterns
bool IsValidInput(string input)
{
if (string.IsNullOrEmpty(input)) return false;
if (input.Length > 100) return false;
if (!char.IsLetter(input[0])) return false;
return true;
}
// TIP 10: State machine with bools
bool isIdle = true;
bool isWalking = false;
bool isRunning = false;
void SetState(string newState)
{
// Reset all
isIdle = isWalking = isRunning = false;
// Set new state
switch (newState)
{
case "Idle": isIdle = true; break;
case "Walk": isWalking = true; break;
case "Run": isRunning = true; break;
}
}
// TIP 11: Nullable bool for tri-state
bool? nullableBool = null; // Can be true, false, or null
bool value = nullableBool ?? false; // Use false if null
// Usage:
bool? userChoice = GetUserChoice(); // null if not chosen yet
if (userChoice == true) { }
if (userChoice == false) { }
if (userChoice == null) { } // Not decided yet
// TIP 12: Bit flags (advanced - store multiple bools in one int)
[Flags]
enum PlayerFlags
{
None = 0,
IsAlive = 1 << 0, // Bit 0
HasWeapon = 1 << 1, // Bit 1
CanJump = 1 << 2, // Bit 2
IsInvincible = 1 << 3 // Bit 3
}
PlayerFlags flags = PlayerFlags.IsAlive | PlayerFlags.CanJump;
bool isAlive = (flags & PlayerFlags.IsAlive) != 0;
// TIP 13: Guard clauses (early returns)
void Attack()
{
if (!isAlive) return;
if (isStunned) return;
if (!hasWeapon) return;
// Perform attack
PerformAttackLogic();
}
// TIP 14: All/Any patterns with collections
bool allEnemiesDead = enemies.All(e => e.health <= 0);
bool anyEnemyAlive = enemies.Any(e => e.health > 0);
// TIP 15: Bool expression caching
// Recalculate every frame
void Update()
{
if (player.health > 0 && !player.isStunned && player.hasWeapon)
{
// ...
}
}
// Cache if expensive
bool canAttack;
void Update()
{
canAttack = player.health > 0 && !player.isStunned && player.hasWeapon;
if (canAttack)
{
// ...
}
}
// TIP 16: Boolean operators truth table
// AND (&&): true only if BOTH are true
// true && true → true
// true && false → false
// false && true → false
// false && false → false
// OR (||): true if AT LEAST ONE is true
// true || true → true
// true || false → true
// false || true → true
// false || false → false
// NOT (!): inverts the value
// !true → false
// !false → true
// XOR (^): true if EXACTLY ONE is true
// true ^ true → false
// true ^ false → true
// false ^ true → true
// false ^ false → false
02 References
- [01]DOCS — Built-in types and literals.
learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/built-in-types - [02]DOCS — Integral numeric types.
learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/integral-numeric-types
- 01Overview & Integer Typesint · byte · sbyte · short · ushortLIVE
- 02Unsigned & Floating-Point Typesuint · long · ulong · float · doubleLIVE
- 03Decimal, Char, String & Booldecimal · char · string · boolCURRENT