DEV Community

박준희
박준희

Posted on • Originally published at aicoreutility.com

Implementing a Game Character Evolution System Backend: A 4-Stage System Design

I needed to implement a character set evolution system for my mini-home service. Previously, there was absolutely no functionality for character growth or acquiring new appearances. I felt that adding something like this would enrich the user experience.

Attempts and Pitfalls

At first, I thought about simply adding evolution items to the point shop and having the character's appearance change upon use. However, I ran into unexpectedly complex issues when trying to integrate it with the inventory system.

I had to consider the character's current level, owned items, and evolution stage simultaneously, and I was at a loss for how to manage this data and pass it via API. It was particularly difficult to integrate it naturally without affecting the already existing point shop and inventory systems.

The Cause

The biggest problem was the unclear data linkage between each system. The point shop only managed item purchase history, and the inventory only managed the list of owned items. There was a lack of design on where and how to store and retrieve the new state values for character evolution.

The Solution

Ultimately, I implemented a 4-stage character evolution system by linking it with the point shop and inventory systems. I defined the required items and points for each evolution stage and created logic to update the character's appearance and stats accordingly.

# Example of character evolution logic (simplified)

class CharacterEvolutionService:
    def __init__(self, user_id):
        self.user_id = user_id
        self.inventory_service = InventoryService(user_id)
        self.point_shop_service = PointShopService(user_id)
        self.character_data = self.load_character_data(user_id) # Load current character data

    def load_character_data(self, user_id):
        # In reality, character data would be retrieved from the DB using the user ID
        # Example: {"level": 1, "evolution_stage": 0, "skin_id": "default_skin"}
        return {"level": 1, "evolution_stage": 0, "skin_id": "default_skin"}

    def can_evolve(self, target_stage):
        # Check current evolution stage and required items/points
        required_items = self.get_evolution_requirements(target_stage)
        if not self.inventory_service.has_items(required_items):
            return False

        required_points = self.get_evolution_points(target_stage)
        if self.point_shop_service.get_current_points() < required_points:
            return False

        return True

    def evolve_character(self, target_stage):
        if not self.can_evolve(target_stage):
            raise ValueError("Evolution conditions not met.")

        # Remove required items
        required_items = self.get_evolution_requirements(target_stage)
        self.inventory_service.remove_items(required_items)

        # Deduct points
        required_points = self.get_evolution_points(target_stage)
        self.point_shop_service.deduct_points(required_points)

        # Update character data
        self.character_data["evolution_stage"] = target_stage
        self.character_data["skin_id"] = self.get_skin_id_for_stage(target_stage)
        self.save_character_data(self.user_id, self.character_data)

        return True

    def get_evolution_requirements(self, stage):
        # In reality, this would be retrieved from a config file or DB
        requirements = {
            1: {"item_id": "evolution_stone_1", "quantity": 1},
            2: {"item_id": "evolution_stone_2", "quantity": 2},
            3: {"item_id": "evolution_stone_3", "quantity": 3},
            4: {"item_id": "evolution_stone_4", "quantity": 4}
        }
        return requirements.get(stage, {})

    def get_evolution_points(self, stage):
        # In reality, this would be retrieved from a config file or DB
        points = {1: 100, 2: 200, 3: 300, 4: 400}
        return points.get(stage, 0)

    def get_skin_id_for_stage(self, stage):
        # In reality, this would be retrieved from a config file or DB
        skins = {1: "stage1_skin", 2: "stage2_skin", 3: "stage3_skin", 4: "stage4_skin"}
        return skins.get(stage, "default_skin")

    def save_character_data(self, user_id, data):
        # In reality, character data would be saved to the DB using the user ID
        print(f"Saving character data: {user_id}, {data}")

# --- Mock Services (In a real environment, actual services would be called) ---
class InventoryService:
    def __init__(self, user_id):
        self.user_id = user_id
        self.items = {"evolution_stone_1": 2, "evolution_stone_2": 1} # Example owned items

    def has_items(self, required_items):
        if not required_items: return True
        item_id = required_items["item_id"]
        quantity = required_items["quantity"]
        return self.items.get(item_id, 0) >= quantity

    def remove_items(self, required_items):
        if not required_items: return
        item_id = required_items["item_id"]
        quantity = required_items["quantity"]
        if self.has_items(required_items):
            self.items[item_id] -= quantity
            print(f"Removed {quantity} of {item_id}. Remaining: {self.items[item_id]}")
        else:
            raise ValueError("Insufficient items.")

class PointShopService:
    def __init__(self, user_id):
        self.user_id = user_id
        self.current_points = 500 # Example owned points

    def get_current_points(self):
        return self.current_points

    def deduct_points(self, amount):
        if self.current_points >= amount:
            self.current_points -= amount
            print(f"Deducted {amount} points. Remaining: {self.current_points}")
        else:
            raise ValueError("Insufficient points.")

# --- Example Usage ---
# user_id = "test_user_123"
# evo_service = CharacterEvolutionService(user_id)

# try:
#     print(f"Current evolution stage: {evo_service.character_data['evolution_stage']}")
#     if evo_service.can_evolve(1):
#         print("Stage 1 evolution possible")
#         evo_service.evolve_character(1)
#         print(f"Stage after evolution: {evo_service.character_data['evolution_stage']}, Skin: {evo_service.character_data['skin_id']}")
#     else:
#         print("Stage 1 evolution not possible")
# except ValueError as e:
#     print(f"Error occurred: {e}")
Enter fullscreen mode Exit fullscreen mode

This code checks the user's inventory and point information, and if the conditions are met, it updates the character's evolution stage and changes its appearance. It's designed to clearly manage the required items, points, and applicable skin IDs for each evolution stage.

Results

  • Users can now grow their characters and acquire new appearances in the game.
  • Integration with the existing point shop and inventory systems was achieved smoothly.
  • Through the structured 4-stage evolution system, we can now provide users with a more in-depth gameplay experience.

Takeaways — To Avoid the Same Pitfalls

  • [ ] When implementing new features, clearly design the data linkage with existing systems in advance.
  • [ ] Carefully decide on the data structure for storing and managing character state values (evolution stage, appearance, etc.).
  • [ ] Accurately define all necessary parameters and return values for API calls.
  • [ ] Consider a structure that minimizes dependencies between systems and allows for flexible expansion.

💬 This is part of *Riel** — a full AI product I'm building solo, in public (failures and all). Read more build logs → · See the product →*

Top comments (0)