Разработка системы крафтинга мобильной игры
Система крафтинга — механика, в которой игрок комбинирует ресурсы или предметы для получения нового. От простого «3 дерева + 2 железа = меч» до многоуровневых деревьев рецептов с вероятностными результатами и временными механиками. Сложность реализации определяется не интерфейсом, а архитектурой данных: рецепты, инвентарь, состояние крафта, серверная валидация.
Архитектура данных рецептов
Рецепты лучше хранить как данные, а не как логику в коде. Это позволяет добавлять новые рецепты без обновления приложения через Remote Config или Addressables:
[Serializable]
public class CraftingRecipe
{
public string recipeId;
public string resultItemId;
public int resultQuantity;
public List<Ingredient> ingredients;
public float craftingTime; // секунды, 0 = мгновенно
public float successRate; // 0.0-1.0, 1.0 = всегда
public string requiredStation; // "forge", "alchemy_table", null = везде
public int requiredLevel;
}
[Serializable]
public class Ingredient
{
public string itemId;
public int quantity;
public bool consumed; // false = инструмент, не расходуется
}
Список рецептов загружается при старте из ScriptableObject или через Addressables.LoadAssetAsync<CraftingDatabase>. Для балансировки без обновления — Remote Config хранит JSON с overrides, который патчит дефолтные значения.
CraftingManager: основная логика
public class CraftingManager : MonoBehaviour
{
public static CraftingManager Instance { get; private set; }
private CraftingDatabase _database;
private InventoryManager _inventory;
public CraftingResult TryCraft(string recipeId, string stationId = null)
{
var recipe = _database.GetRecipe(recipeId);
if (recipe == null) return CraftingResult.InvalidRecipe;
// Проверка станции
if (!string.IsNullOrEmpty(recipe.requiredStation) && recipe.requiredStation != stationId)
return CraftingResult.WrongStation;
// Проверка уровня
if (PlayerData.Level < recipe.requiredLevel)
return CraftingResult.LevelTooLow;
// Проверка наличия ингредиентов
foreach (var ingredient in recipe.ingredients)
{
if (_inventory.GetCount(ingredient.itemId) < ingredient.quantity)
return CraftingResult.MissingIngredients;
}
// Вероятность успеха
bool success = Random.value <= recipe.successRate;
// Списываем только расходуемые ингредиенты
foreach (var ingredient in recipe.ingredients.Where(i => i.consumed))
_inventory.Remove(ingredient.itemId, ingredient.quantity);
if (success)
_inventory.Add(recipe.resultItemId, recipe.resultQuantity);
// Аналитика
GameAnalytics.NewDesignEvent($"Crafting:Result:{recipeId}", success ? 1 : 0);
return success ? CraftingResult.Success : CraftingResult.Failed;
}
}
Крафтинг с таймером
Для механики «запустил крафт, вернись через час»:
public class CraftingSlot
{
public string recipeId;
public DateTime completionTime;
public bool isComplete => DateTime.UtcNow >= completionTime;
}
public void StartCrafting(string recipeId, int slotIndex)
{
var recipe = _database.GetRecipe(recipeId);
_craftingSlots[slotIndex] = new CraftingSlot
{
recipeId = recipeId,
completionTime = DateTime.UtcNow.AddSeconds(recipe.craftingTime)
};
// Сохраняем в PlayerPrefs или Cloud Save
SaveCraftingState();
// Планируем локальный пуш
NotificationManager.ScheduleLocal(
$"Крафтинг завершён: {recipe.resultItemId}",
recipe.craftingTime
);
}
Состояние слотов необходимо персистить — если игрок закрыл приложение, таймер должен продолжаться. Для офлайн-прогрессии используем DateTime.UtcNow при следующем запуске: если completionTime < DateTime.UtcNow — крафт завершён.
Серверная валидация
Для игр с реальной монетизацией крафтинг нельзя полностью доверять клиенту. Классическая схема через PlayFab Cloud Script:
// PlayFab Cloud Script
handlers.CraftItem = function(args) {
var recipeId = args.recipeId;
var recipe = getRecipeFromCatalog(recipeId);
// Проверяем инвентарь на сервере
var inventory = server.GetUserInventory({ PlayFabId: currentPlayerId });
for (var ingredient of recipe.ingredients) {
var count = countItem(inventory, ingredient.itemId);
if (count < ingredient.quantity) {
return { success: false, error: "insufficient_items" };
}
}
// Списываем и начисляем на сервере
for (var ingredient of recipe.ingredients.filter(i => i.consumed)) {
server.RevokeInventoryItems({ ... });
}
server.GrantItemsToUser({ itemIds: [recipe.resultItemId], ... });
return { success: true, resultItemId: recipe.resultItemId };
};
Что входит в работу
- Проектирование схемы данных рецептов и инвентаря
- Реализация
CraftingManagerс логикой проверок и вероятностей - Система крафтинга с таймерами и офлайн-прогрессией
- Локальные пуш-уведомления о завершении крафта
- Серверная валидация через PlayFab / GameSparks (при необходимости)
- UI: рецептбук, слоты крафтинга, прогресс-бар
- Аналитические события для балансировки популярности рецептов
Сроки
Базовый крафтинг (мгновенный, без таймеров): 3–5 дней. Полная система с таймерами, слотами, серверной валидацией и UI: 1,5–3 недели. Стоимость рассчитывается индивидуально.







