Pull to refresh

Некоторый опыт разработки игры на Unity3D

Reading time 6 min
Views 37K
Не считаю себя опытным программистом и тем более серьезным разработчиком игр. Я человек, который увлекается программированием и у меня немного получается. Статья не претендует называться учебным материалом, или наставлением.

Скриношты для привлечения внимания:
image image

Моральная сторона вопроса

Стоит ли заниматься подобным, не имея серьезных знаний в разработке игр, моделировании, программировании? Ответа на вопрос от меня не последует, он у каждого будет свой. С уверенностью могу сказать точно — я не пожалел. Я попытаюсь кратко и четко выделить и описать узкие места с которыми я столкнулся, проложить карту по граблям.

Unity3d

Unity3d с недавних пор разрешено использовать бесплатно и на всю мощь. Я долго метался между несколькими движками, но c# оказался для меня решающим преимуществом. Кроме того, в Unity3d очень удобный способ построения пользовательского интерфейса. Для тех кто не боится Lua, также могу посоветовать Project Anarchy, или Unreal Engine 4 если не брезгуете визуальным программированием. У меня сложилось впечатление, что Unity3D прекрасно подходит для мобильной разработки. К сожалению у Unity3D слабое русскоязычное комьюнити и нередко приходится лезть на официальный qa.

Архитектура игры

Мной предпринимались попытки применить существующие паттерны, но в итоге все сводилось к «пока пусть здесь полежит». В Unity много компонентов, которые сложно отнести к какому то типу. Скорее всего причиной тому моя неопытность, но в итоге такие скрипты как BotSpawner оставались лежать в корне папки Scripts не имея пристанища.

Сохранения в Unity и база данных

Я использовала встроенные PlayerPrefs и sqlite. Sqlite теперь можно использовать из коробки в бесплатной версии. Для меня были удобны sql таблицы для хранения данных товаров при торговле. Наиболее удобным инструментом для управления таблицами является sqlitebrowser, нам нем я остановился перепробовав конкурентов.

Запись и чтение в PlayerPrefs свилось к подобному:
public class DB : MonoBehaviour {

    public int currentShipBodyState
    {
        get { return PlayerPrefs.GetInt("currentShipBodyState"); }
        set { PlayerPrefs.SetInt("currentShipBodyState", value); }
    }
}

с последующим элегантным чтением и записью:
db.currentShipBodyState = 100;
int shbs = db.currentShipBodyState;


Sqlite потребовал больших телодвижений. Далее приведу пример использования Sqlite в Unity3d пятой версии.
Для использования Sqlite в редакторе достаточно создать каталог StreamingAssets и поместить туда файл базы данных. Для того чтобы работать с базой данных на android устройстве, файл базы нужно поместить в Application.persistentDataPath. В официальной документации характеризуется как общедоступный каталог который не перезаписывается при обновлении программы. Также необходимо создать каталог Plugins и Plugins\Android. В первый поместите sqlite3.dll и System.Data.dll в Plugins\Android поместите libsqlite3.so.

Универсальный калокод для соединения с базой данных:
public IDbConnection connector()
    {

        // если игра запускается на ПК
        if (Application.platform != RuntimePlatform.Android)
        {

            //database.Open(Application.dataPath + "/StreamingAssets/db.sqlite");
            dbconn = (IDbConnection)new SqliteConnection("URI=file:" + Application.dataPath + "/StreamingAssets/db.sqlite");
            dbconn.Open(); //Open connection to the database.


        }
        // android
        else
        {
            // расположение базы данных
            string filepath = Application.persistentDataPath + "/" + "db.sqlite";

            // если базы данных по заданному пути нет, размещаем ее там
            if (!File.Exists(filepath))
            {

                WWW loadDB = new WWW("jar:file://" + Application.dataPath + "!/assets/" + "db.sqlite");
                while (!loadDB.isDone) { }
                File.WriteAllBytes(filepath, loadDB.bytes);
            }

            dbconn = (IDbConnection)new SqliteConnection("URI=file:" + Application.persistentDataPath + "/db.sqlite");
            dbconn.Open(); //Open connection to the database.


        }

        return dbconn;

    }


запросы к базе, это обычные sql запросы:
public List<string> getItemInfoFromIslandByItemName(string item)
    {
        dbconn = connector();
        dbcmd = dbconn.CreateCommand();

        string sqlQuery = "SELECT island_resources.amount, island_resources.weight, island_resources.title, island_resources.price, island_resources.sell_price,ship_resources.amount FROM island_resources INNER JOIN ship_resources ON island_resources.title=ship_resources.title WHERE island_resources.title='" + item + "' AND island_resources.island_id=" + zones_filter(current_zone_name) + " ";
        
        dbcmd.CommandText = sqlQuery;
        reader = dbcmd.ExecuteReader();

        System.Object[] values = new System.Object[reader.FieldCount];
        int fieldCount = reader.GetValues(values);

        List<string> list = new List<string>();

        for (int i = 0; i < fieldCount; i++)
        {

            list.Add( values[i].ToString() );
  
        }
        clean();
        return list;
    }


Моделирование, ландшафт и вода

Моделировать учился на ходу, по видео урокам. Корабли (два доступных корабля) плоды моих трудов. Сложнее оказалось текстурировать, на модели ушло не больше 30 минут.
Ландшафт оказался неожиданной проблемой. Встроенная система создания террейна непригодная для мобильных устройств из-за высокого draw calls (dc). В моей игре большая карта и множество островов. Пришлось предпринять немало усилий, для получения качественного ландшафта. Для создания рельефа могу посоветовать GeoControl из которого можно импортировать карту высот в Unity, затем перевести в mesh используя специальный плагин. Полученный mesh нужно оптимизировать в 3d редакторе, я использую Maya. В итоге, количество dc не превышает 100. Мной было перепробовано огромное количество ассетов воды, выбор пал на стандартную воду water4.

AI

Для управления ботами не стал использовать поиск путей. Решил построить «интеллект» на обнаружении препятствия и реакции от системы навигации корабля. Корабль бота стреляем лучами в 3 стороны, при обнаружении препятствия одним из лучей, передается команда «рулю». При обнаружении корабля протагониста (при приближении на минимальную дистанцию), бот получает цель преследования и пытается встать в удобную для выстрела позицию. При обнаружении протагониста боковые лучи передают команду в скрипт отвечающий за стрельбу. Если по близости нет преград, боты живут своей жизнь и путешествуют по «морским путям» от острова к острову.

Внутриигровые покупки Unity3d

Выбор пал на OpenIAB. Невероятно простое решение сложной задачи.
1. В разделе контент для продаж google play создаете товар.
2. Создаете скрипт в котором проделываете несколько простых операций.
Код
        private void Awake()
        {
                // слушаем события до старта
                OpenIABEventManager.billingSupportedEvent += OnBillingSupported;
                OpenIABEventManager.billingNotSupportedEvent += OnBillingNotSupported;
                OpenIABEventManager.queryInventorySucceededEvent += OnQueryInventorySucceeded;
                OpenIABEventManager.queryInventoryFailedEvent += OnQueryInventoryFailed;
                OpenIABEventManager.purchaseSucceededEvent += OnPurchaseSucceded;
                OpenIABEventManager.purchaseFailedEvent += OnPurchaseFailed;
                OpenIABEventManager.consumePurchaseSucceededEvent += OnConsumePurchaseSucceeded;
                OpenIABEventManager.consumePurchaseFailedEvent += OnConsumePurchaseFailed;
                OpenIABEventManager.transactionRestoredEvent += OnTransactionRestored;
                OpenIABEventManager.restoreSucceededEvent += OnRestoreSucceeded;
                OpenIABEventManager.restoreFailedEvent += OnRestoreFailed;
        }

        // инициализируем при старте
        void Start ()
        {
            OpenIAB.mapSku(SKU_COINS_2000, OpenIAB_Android.STORE_GOOGLE, "id созданного вами товара");
            OpenIAB.mapSku(SKU_COINS_5000, OpenIAB_Android.STORE_GOOGLE, "id созданного вами товара");

            var public_key = "ваш ключ из раздела службы и api";

            var options = new OnePF.Options();
            options.storeKeys.Add(OpenIAB_Android.STORE_GOOGLE, public_key);
            options.verifyMode = OptionsVerifyMode.VERIFY_ONLY_KNOWN;
            
            
            OpenIAB.init(options);
        }


 


3. Удобным для вас способом вызываем OpenIAB.purchaseProduct(id созданного вами товара);

Если покупка удалась, вызывается метод OnPurchaseSucceded которым мы прослушивали успешное выполнение операции покупки. Если покупка удалась, товар необходимо употребить, иначе повторная покупка будет не доступна.
private void OnPurchaseSucceded(Purchase purchase)
        {
            OpenIAB.consumeProduct(purchase);
        }
 

При успешном употреблении у нас вызывается OnConsumePurchaseSucceeded, тут можно совершить запись для присвоения товара игроку и пр.

Управление звуками, решение проблемы одновременного воспроизведения нескольких звуков на одном объекте

Создаем пустой объект, на который вешаем данный скрипт:
public class AudioManager : MonoBehaviour {

public Transform target; // задаем объект за которым гоняется "звук"

    // аудио
    public AudioClip cannon_fire_sound_AC;
    // компонент звука 
    private AudioSource cannon_fire_sound_AS;
    // контроллер проигрывания
    public static bool is_cannon_fire;


    void Update ()
    {
        this.transform.position = target.transform.position;
        if (is_cannon_fire)
        {
            cannon_fire_sound_AS = AddAudio(cannon_fire_sound_AC, false, false, 0.5f);
            cannon_fire_sound_AS.Play();
            is_cannon_fire = false;
        }
    }

AudioSource AddAudio(AudioClip clip, bool loop, bool playAwake, float vol)
    {
        var newAudio = gameObject.AddComponent<AudioSource>();
        newAudio.clip = clip;
        newAudio.loop = loop;
        newAudio.playOnAwake = playAwake;
        newAudio.volume = vol;
        newAudio.minDistance = 10;
        return newAudio;
    }


}

Затем из нужного места вызываем AudioManager.is_cannon_fire = true;//не лучшее название для переменной

Admob

Пытаюсь разобраться в тонкостях Admob. Пополнил счет на 500р для тестов и запустил продвижение установив дневной лимит 100р. Через пару часов любуюсь этим:

Честно признаться не разобрался откуда появились такие суммы, скорее всего моя невнимательность к деталям. Но все же надеюсь, это не счет выставленный мне от Admob.

Игра получила название Pirates and traders, доступна Google Play. Естественно требует доработок и развития. Желающие могут потрогать, по поиску на момент написания находится на 29 месте.

Tags:
Hubs:
+12
Comments 4
Comments Comments 4

Articles