Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 28

Nested Structs for Game Entities

Recommended resources

Introduction

Welcome to the next lesson in our journey to master C for game programming. In our last session, we focused on the crucial techniques for passing structs to functions, weighing the performance and safety trade-offs between passing by value and passing by pointer. You learned that for efficiency, especially in performance-sensitive applications like games, passing by pointer is the standard professional practice.

Today, we'll build directly on that foundation. We are moving from simple, flat data structures to more complex, layered ones. This lesson addresses how to implement a game entity using nested structs. You will learn how to embed one struct within another to model "has-a" relationships, a concept fundamental to creating rich and organized game worlds. For instance, a Player has a Weapon, and a Weapon has its own distinct properties. By the end of this lesson, you'll be able to define, initialize, and manipulate these hierarchical data objects, bringing us another step closer to building the complex entities required for your capstone project.


Building Worlds: The "Why" of Nested Structures

At its core, programming is about modeling. We model real-world or imaginary-world concepts using data and logic. A simple struct is great for grouping the immediate properties of an object, like a player's health and mana. But what happens when one of those properties is itself a complex object with its own set of attributes?

This is where nesting comes in. A nested struct allows you to define these "has-a" relationships, creating logical hierarchies in your data. It's how you build complex objects out of simpler, reusable components—much like using LEGO bricks to construct an elaborate model.

The following article explains this concept beautifully, framing it as "building worlds" by defining relationships between data.

Mastering Structures in C : The #1 Best Guide - SyntaxPathways

This short chapter from the "Mastering Structures in C" guide provides the perfect conceptual introduction to why we use nested structs.

Please read Chapter 4: Building Worlds with Nested Structures. Pay attention to the idea that you're not just storing data, but defining relationships between different pieces of data.

As the article shows, this technique allows us to express that an employee is part of a department, or in our case, that a weapon belongs to a player. This creates code that is not only more powerful but also more intuitive and easier to maintain.


The "How": Defining and Accessing Nested Structs

Now that we understand the "why," let's dive into the syntax. There are two primary ways to create nested structures, but one is far more common and aligns better with building modular, scalable game code.

  1. Separate Definition (Preferred Method): You define each struct as a complete, independent type. Then, you include one struct type as a member within another. This is the most flexible and reusable approach.

    // Define a Weapon struct. It can be used anywhere.
    typedef struct {
        char name[30];
        int damage;
    } Weapon;
    
    // Define a Player struct that uses the Weapon type.
    typedef struct {
        char name[30];
        int health;
        Weapon equipped_weapon; // A Weapon struct is nested inside Player
    } Player;
    
  2. Embedded Definition: You define the inner struct directly inside the outer one. This tightly couples the two, making the inner struct type unavailable for use elsewhere.

    typedef struct {
        char name[30];
        int health;
        struct { // The weapon struct is defined inside
            char name[30];
            int damage;
        } equipped_weapon;
    } Player;
    

For our purposes, the separate definition method is superior. It allows us to reuse the Weapon struct for enemies, for items lying on the ground, or in an inventory array, without having to redefine it.

The following video provides a clear, step-by-step guide to both defining a nested structure and accessing its members.

Nested Structure in C Programming Language Tutorial

This tutorial from LearningLad provides an excellent walkthrough of the core syntax using a simple person and address example.

First, watch from the beginning to the definition to see how an address struct is defined separately and then included as a member in the person struct. Next, watch the segment on accessing members. The key takeaway is the "chain" of dot operators used to drill down into the nested struct, like Anil.addr.house_number.

Accessing Members: The Dot and Arrow Chain

As the video demonstrated, you access nested members by chaining the member access operators. The rule you learned in the previous lesson still applies:

  • Use the dot (.) for direct access to a struct.
  • Use the arrow (->) for access via a pointer to a struct.

Let's look at the combinations with our Player example:

Player hero;
Player* hero_ptr = &hero;

// Case 1: Direct access to outer struct
// We use dot, then dot again.
strcpy(hero.equipped_weapon.name, "Broadsword");

// Case 2: Pointer access to outer struct
// We use arrow, then dot.
hero_ptr->health = 100;
hero_ptr->equipped_weapon.damage = 20; // This is the most common pattern

This second case is exactly what you'll use when you pass a pointer to a Player struct into a function, which we know is the most efficient method.

This image shows a simplified C program where an Employee struct contains a nested address. Notice the line strcpy(emp2.add.City, "Manchester");—this is a perfect visual of the chained dot operator syntax in action.

An example of initializing and accessing members of a nested struct. The `add` member of `emp2` is itself a struct, so its members (`Door_Number`, `City`) are accessed with an additional dot operator.

A Practical Example: Designing a Weapon System

Theory is essential, but seeing these concepts applied in a real game development context is where they truly click. Your interest lies in building games from the ground up, so let's watch a developer do just that. The following video shows the creation of a weapon system in C, using the very techniques we've just discussed.

Weapon System | C Game + Engine From Scratch 18

This video from Dylan Falconer shows a developer implementing a weapon system, providing a practical demonstration of how structs are used to manage game entity data.

First, see how a struct weapon is defined to encapsulate properties like fire rate and recoil. Next, watch how an array of these structs is created to act as a database of all available weapons, with each one being initialized with its unique stats. Finally, observe how the game logic accesses the members of the currently equipped weapon to determine the behavior of a projectile when the player fires.

This video showcases a core pattern in game development:

  • Encapsulation: All data related to a weapon is bundled cleanly inside struct weapon.
  • Data-Driven Design: The game's weapons aren't hard-coded. They exist as data in an array of structs. To add a new weapon, you just add another entry to this array.
  • Composition: A player entity would then simply hold a struct weapon (or a pointer to one) to represent what they have equipped. The player is composed of its base stats plus a weapon.

Let's solidify this with a complete, commented code example that brings together nesting, typedef, and the efficient pass-by-pointer technique from our last lesson.




#include <stdio.h>
#include <string.h>

// Using typedef for clean, reusable types.
// This is the "separate definition" approach.
typedef struct {
    char name[50];
    int damage;
    float attack_speed; // Attacks per second
} Weapon;

typedef struct {
    char name[50];
    int health;
    Weapon equipped_weapon; // The Player "has-a" Weapon
} Player;

// This function takes a const pointer to a Player for maximum
// efficiency and safety, as established in the previous lesson.
void display_player_stats(const Player* p) {
    printf("--- Player Stats ---\n");
    printf("Player: %s\n", p->name);
    printf("Health: %d HP\n", p->health);
    
    printf("--- Equipped Weapon ---\n");
    // Accessing the nested struct's members.
    // p is a pointer, so we use -> to get to equipped_weapon.
    // equipped_weapon is a direct member (not a pointer), so we use .
    printf("   Name: %s\n", p->equipped_weapon.name);
    printf("   Damage: %d\n", p->equipped_weapon.damage);
    printf("   Speed: %.2f attacks/sec\n", p->equipped_weapon.attack_speed);
}

int main() {
    // We can initialize the nested struct directly using C99's designated initializers.
    // This is robust and readable.
    Player hero = {
        .name = "Valerius",
        .health = 100,
        .equipped_weapon = {
            .name = "Blade of the Ancients",
            .damage = 25,
            .attack_speed = 1.1f
        }
    };

    display_player_stats(&hero); // Pass the address of the hero struct

    // Let's change the weapon and see the stats update
    printf("\n--- Picked up a new weapon! ---\n\n");
    
    // We can assign to the entire nested struct at once
    Weapon battle_axe = {"Gorehowl", 40, 0.8f};
    hero.equipped_weapon = battle_axe;

    display_player_stats(&hero);

    return 0;
}

This example is a mini-blueprint for how you will manage entities in your own game. You can define structs for Armor, Potions, and Spells, and then compose them to build a complete character.


Conclusion

In this lesson, you've taken a significant step toward designing complex and realistic game entities. You've learned how to model hierarchical data and "has-a" relationships by nesting one struct inside another. This technique of composition is fundamental to building organized, scalable, and maintainable C programs.

Key Takeaways:

  • Nested structs model relationships: They are the C mechanism for representing that one object is composed of other objects.
  • Separate definitions promote reusability: Defining structs as independent types before composing them is the best practice for modular design.
  • Access is a chain of operators: You drill down into nested data using a sequence of . and -> operators, following the rules for direct vs. pointer access at each level.
  • This is a core game-dev pattern: Encapsulating properties in structs (like Weapon or Armor) and then composing them into a larger entity (like Player) is how complex game objects are built.

In the next lesson, we will introduce enums. You caught a glimpse of them in the weapon system video, where they were used to define categories like WEAPON_TYPE_PISTOL. We will explore how enums provide a clean, readable, and type-safe way to represent discrete states or categories, such as game status (MENU, PLAYING, PAUSED) or item types, further enhancing our ability to structure game logic.