Пятница, 19 Апреля 2024, 11:38

Приветствую Вас Гость

[ Новые сообщения · Игроделы · Правила · Поиск ]
  • Страница 1 из 1
  • 1
Форум игроделов » Движки для разработки игр и сложные системы разработки » Unity » Удобный выбор случайного элемента из коллекции по шансу (Всякая C# магия)
Удобный выбор случайного элемента из коллекции по шансу
pixeyeДата: Суббота, 20 Июня 2015, 01:30 | Сообщение # 1
Red Winter Software
Сейчас нет на сайте
Парампа-ПА.

Хочу поделиться удобной штукой. Вещь простая, отчасти описана в одном из официальных туторов Unity, но новички такое могут пропустить.
Речь пойдет о выборе случайного элемента из коллекций. С заданным шансом.

Область применения?

- Сундук из которого должна выпасть вещь из определенного списка. Причем так, чтобы у золотого колечка шанс дропа был очень маленьким.
- Выборка фраз для NPC когда хочется разнообразить его речь, но нужно сделать некоторые фразы редкоиспользуемыми
- Выбор случайного события, эффекта... в общем - вариантов использований масса.

Как видно, коллекции элементов могут быть совершенно разными объектами по логике, но нам надо сделать удобный метод который работал бы с любой коллекцией, иначе нам бы пришлось копировать и переписывать этот метод для каждого отдельного списка вещей, что занудно. Либо ограничиваться выборкой числа шанса и уже потом по этому значению брать из списка вещь. Что тоже ведет к лишнему коду.

Начнем с интерфейса.

Код

public interface IRandom
{
             float itemDropChance
             {
                 get;
                 set;
             }
}


Что такое интерфейс и с чем его едят можно прочесть здесь

Создадим класс с которым будем проводить махинации ; )

Код
public class ItemLoot : IRandom{

public static List<ItemLoot> AllItems = new List<ItemLoot>();

string _nameID;
float  _itemDropChance;

public string nameID{
get {return _nameID;}
set {_nameID=value;}
}

public float itemDropChance{
get {return _itemDropChance;}
set {_itemDropChance = value;}
}

public ItemLoot(string nameID, float itemDropChance){
this.nameID = nameID;
this.itemDropChance = itemDropChance;
AllItems.Add(this);
}

}


После названия класса мы ставим : IRandom , теперь наш ItemLoot класс реализует интерфейс IRandom.
Помимо прочего мы обязательно должны указать в классе ItemLoot определения из IRandom ( в нашем случае itemDropChance )

Дальше идет конструктор, тут все стандартно. Записываем имя вещи и шанс выпадения. Вещи автоматически записываются в коллекцию. Такой подход не всегда удобен, но для примера более чем сойдет.
Сделаем еще один класс для примера.

Код
public class GameEventSpec : IRandom{

public static List<GameEventSpec> AllItems = new List<GameEventSpec>();

string _nameID;
float  _itemDropChance;

public string nameID{
get {return _nameID;}
set {_nameID=value;}
}

public float itemDropChance{
get {return _itemDropChance;}
set {_itemDropChance = value;}
}

public GameEventSpec (string nameID, float itemDropChance){
this.nameID = nameID;
this.itemDropChance = itemDropChance;
AllItems.Add(this);
}

}


Переходим к следующему шагу :
Добавляем статичный класс ExtensionMethods. Я для этого обычно создаю отдельный скрипт и бросаю все туда.

Код
public static class ExtensionMethods
             {

}


В этот класс пишем метод

Код
public static T ReturnRandom<T>(this List<T> items) where T : IRandom
             {
                 System.Random _r = new System.Random();
                 List<float> probs = new List<float>();
                 float total = 0f;
                 for (int i = 0; i < items.Count; i++)
                 {
                     probs.Add(items[i].itemDropChance);
                     total += items[i].itemDropChance;
                 }

                 int index =  probs.Count - 1;

                 float RandomPoint = (float)_r.NextDouble() * total;
                         
                 for (int i = 0; i < probs.Count; i++)
                 {
                     if (RandomPoint < probs[i])
                     {
                         index = i;
                         break;

                     }
                     else
                         RandomPoint -= probs[i];
                 }

                 return items[index];

             }


Этот метод появится в списке методов класса List - теперь мы можем его применять к этим коллекциям. where T : IRandom указывает на то, что аргумент типа должен являться или реализовывать указанный интерфейс.
Для чего это все нужно? Нам нужно обращаться к аргументу типа и забирать значение шанса дропа из переданных объектов. Метод не знает какие объекты мы в него передаем. Это может быть ItemLoot, или еще чтонибудь. Важно, чтобы эти объекты реализовывалм интерфейс IRandom Тогда мы сможем взять itemDropChance

Дальше мы складываем все значения в total. И собираем отдельную коллекцию шансов. Выбираем рандомную точку перемножив сумму шансов на значение от нуля до единицы. Дальше идет обычное сравнение значений по коллекции шансов, что в конечном счете приведет нас к желаемому индексу предмета.

Визуально это хорошо показано в мануале unity3d


Теперь нам остается только вернуть предмет из нашей коллекции по индексу.

Для тестов создадим новый скрипт типа

Код
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Testing : MonoBehaviour{

void Start(){

new ItemLoot ("Apple",25f);
new ItemLoot ("Gun",15f);
new ItemLoot ("Sword",125f);
new ItemLoot ("Bomb",55f);

new GameEventSpec("Darkness", 50f);
new GameEventSpec("Hot", 25f);
new GameEventSpec("Cold", 10f);
new GameEventSpec("Wet", 30f);

          ItemLoot ItemLootSelected = ItemLoot.AllItems.ReturnRandom<ItemLoot >();
Debug.Log(ItemLootSelected.nameID);

GameEventSpec GameEventSelected = GameEventSpec.AllItems.ReturnRandom<GameEventSpec >();
Debug.Log(GameEventSelected.nameID);
}

}


Ну вот, у нас две коллекции объектов разных классов, что реализуют один интерфейс. И мы добавили универсальный метод для получения значения из коллекции. Написано один раз и используется на всем проекте.


ACTORS - мой фреймворк на Unity
Until We Die - игра над которой работаю



Сообщение отредактировал pixeye - Суббота, 20 Июня 2015, 12:07
RangerДата: Воскресенье, 21 Июня 2015, 05:53 | Сообщение # 2
почти ветеран
Сейчас нет на сайте
Спасибо.

Форум игроделов » Движки для разработки игр и сложные системы разработки » Unity » Удобный выбор случайного элемента из коллекции по шансу (Всякая C# магия)
  • Страница 1 из 1
  • 1
Поиск:

Все права сохранены. GcUp.ru © 2008-2024 Рейтинг