суббота, 15 октября 2011 г.

"GB&W Lite" [wp7][game][release]

Выпустил лайт версию игры GB&W.



Вот видео с мастер классом по прохождению 19го уровня:



игру можно найти тут: 


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

Оказалось, при первой публикации лайт версии, GB&W занимает в памяти более 90 МБ (на сколько не скажу). Очень много анимированной растровой рафики. Лайт версию пришлось немного порезать. Получается для GB&W мне в свое время сделали скидку :).

среда, 13 июля 2011 г.

Компромисс в выборе между классом и структурой

На тему навела вот эта статья "NET 4.0: Class vs Struct или в чём различия между Классом и Структурой" . Давно уже собирался собрать для себя набор иерархии универсальных коллекций, которые не попадают под сборку мусора. Тема в очередной раз стала интересна после моего известного эксперимента с Windows Phone 7. Очень сомневаюсь, что моя статья будет полезна для бизнес-приложений.  Меня больше интересует данная тема для использования в XNA/DirectX и как область применения - игры.

Приглашаю вас обсудить данный вопрос.

И так, условия задачи:
- Нужна коллекция элементов для описания, например, системы частиц (это может быть все что угодно). Такая штука в которой в среднем 60 раз в секунду добавляются и удаляются сотни элементов.
- Изменения в коллекции не должны влиять на сборку мусора.
- Нельзя использовать структуры! Только ссылочные типы.
- Запрещено создавать и удалять классы, за исключением первой инициализации.
- Физическое количество элементов в коллекции должно быть постоянным. Т.к. изменение длинны массива достаточно накладная операция, а количество элементов должно постоянно плавать, то подойдет подобие кеша. Ограничение - разработчику желательно четко знать максимально возможное количество элементов в такой коллекции. А это значит игра должна быть грамотно спроектирована. Хотя лазейку я оставлю.


Набросал вот такой черновик.

Базовый класс для элемента коллекции:

public class TElementConstLength
    {
        private  int indexInCollection = -1;
        internal int IndexInCollection { get { return indexInCollection; } }
        internal void SetIndexInCollection(int i)
        {
            indexInCollection = i;
        }
    }


Базовый класс для коллекции:

public class TCollectionConstLength<T> where T : TElementConstLength, new()
    {
        private T[] elements;

        private int count = 0;
        /// <summary>
        /// Количество живых элементов
        /// </summary>
        public  int Count { get { return count; } }

        private int end = -1;

        /// <summary>
        /// Инициализация колекции
        /// </summary>
        /// <param name="length">Длинна коллекции</param>
        public TCollectionConstLength(int length)
        {
            elements = new T[length];
        }

        /// <summary>
        /// Доступ к элементу по индексу
        /// </summary>
        /// <param name="index">Индекс</param>
        /// <returns>Элемент</returns>
        public T this[int index]
        {
            get { return elements[index]; }
        }

        /// <summary>
        /// Выделение места под новый элемент
        /// </summary>
        /// <returns>Новый элемент</returns>
        public virtual T GetNew() // = Add
        {
            // если список полон, для режима отладки
            if(end == elements.Length - 1)
                Array.Resize(ref elements, elements.Length + 1);
            // 
            end++;
            count++;
            // если не инициализирован элемент
            if(elements[end] == null)
            {
                elements[end] = new T();
                elements[end].SetIndexInCollection(end);
            }
            //
            return elements[end];
        }

        /// <summary>
        /// Очистка списка
        /// </summary>
        public void Clear()
        {
            end = -1;
            count = 0;
        }

        /// <summary>
        /// Удаление элемента по индексу
        /// </summary>
        /// <param name="index">Индекс</param>
        public virtual void Remove(int index)
        {
            // выход за пределы
            if(index < 0 || index > end)
                return;
            if(index < end)
            {
                // рокировка элементов
                T temp = elements[end];
                elements[end] = elements[index];
                elements[index] = temp;
                // рокировка индексов
                int i = elements[end].IndexInCollection;
                elements[end].SetIndexInCollection(elements[index].IndexInCollection);
                elements[index].SetIndexInCollection(i);
            }
            // уменьшаем список
            end--;
            count--;
        }

        /// <summary>
        /// Удаление элемента
        /// </summary>
        /// <param name="e">Элемент</param>
        public virtual void Remove(T e)
        {
            // проверка на соответствие ссылок (защита от тупости)
            if(e != elements[e.IndexInCollection])
                return;
            Remove(e.IndexInCollection);
        }
    }

Вот пример для тестов:

using System;

namespace CollectionConstLength
{

    public class ElementTest : TElementConstLength
    {
        private string name;
        public string Name { get { return name; } set { name = value; } }
        public ElementTest() { }
    }

    class Program
    {

        static TCollectionConstLength<ElementTest> item;

        static void Main(string[] args)
        {

            item = new TCollectionConstLength<ElementTest>(5);
            ElementTest et;
            for(int i = 0; i < 5; i++)
            {
                et = item.GetNew();
                et.Name = i.ToString();
            }
            //
            Print();
            //
            item.Remove(item[1]);
            Print();
            //
            Console.ReadKey();
        }

        static void Print()
        {
            Console.WriteLine("//--------------------------------");
            for(int i = 0; i < item.Count; i++)
            {
                Console.WriteLine(
                    string.Format("index - {0}; name - \"{1}\";", 
                        item[i].IndexInCollection, 
                        item[i].Name));
            }
            Console.WriteLine("//--------------------------------");
        }
    }
}


Пример наращивания функционала через наследование:

- Расширенный класс элемента:

public class TUpdate : TElementConstLength
    {
        public virtual void Update() { }
    }

- Расширенный класс коллекции

public class TUpdateCollection<T> : TCollectionConstLength<T> where T : TUpdate, new()
    {
        public TUpdateCollection(int l) : base(l) { }
        public virtual void Update()
        {
            for(int i = 0; i < base.Count; i++)
            {
                base[i].Update();
            }
        }
    }


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

Давайте пообщаемся на эту тему.
Так же буду рад ссылкам на статьи с подобными темами и изысканиями.

суббота, 2 июля 2011 г.

Лето ... Юмор ...


Бред конечно, но когда пытаешься сосредоточиться на работе, а тебе при этом постоянно мешают, еще эта жара - именно такие мысли лезут в голову:

Тенгизи: к сожалению, пока некогда :(( работа... осенью закуплю девайсы и морозными зимними вечерами... :)
Dmitry: да.. да..)))
Dmitry: я вот тут думаю, как бы до морозных вечеров обороты набрать, что бы потом только щепки летели)))
Тенгизи: лето... мозги вообще ничего делать не хотят... :((
Тенгизи: у нас жара вторую неделю... жесть...
Dmitry: ищу стимулы, у нас тоже пекло)
Тенгизи: ну у вас-то еще бы!
Dmitry: )))
Тенгизи: мы-то люди северные, к жаре не привычные
Dmitry: да я лето из за лени ненавижу)))
Тенгизи: хотя я 17 лет в минводах прожил, но тут уже адаптировался... )
Dmitry: у меня такое подозрение, что для того что бы я смог раскрыть все свои способности, по максимуму, и реализовать их - меня нужно запереть на какой ни будь полярной станции и не выпускать.
Dmitry: только с компом и интернетом)))
Dmitry: и раз в неделю привозить еду и женщин)))))))))))
Dmitry: ух ...
Тенгизи: у меня такое же мнение!!! сто пудово! если запереть - реализуемся...
Тенгизи: даже женщин на хрен не возите, только отвлекать будут... потерплю тройку месяцев ))))
Dmitry: раз в неделю можно часок расслабиться)))
Тенгизи: при таком раскладе даже в покер научится играть можно профессионально... ))
Тенгизи: ну.. если часок... но потом чтоб СРАЗУ УВОЗИЛИ! :))
Dmitry: прилетела на вертолете, еды привезла, удовлетворила и улетела)))
Dmitry: идеал женщины)))
Тенгизи: )))))))
Тенгизи: к тому же чтоб глухонемая была ))
Dmitry: не вопрос)))

Приятно осознавать, что ты не один такой :) 

"GB&W" in "Top WP7 Games : June 2011 [ POLL ]"

Сайт bestwp7games.com предложил голосование за игры для Windows Phone 7 вышедшие в июне. В этом списке на ряду с топовыми играми, имеющими статут xbox live, каким то образом, оказалась и наша игра GB&W. Приглашаю вас присоединиться к нашей группе болельщиков и принять участие в голосовании.

Проголосовать можно тут

Регистрация не нужна. Достаточно выбрать соответствующий пункт и подтвердить кнопкой "vote".

Всем огромное спасибо за отклик и участие!

P.S. Топовым играм, имеющим статус xbox live и команду поддержки, хватает внимания, загрузок и покупок, а вот нам бы все это даже очень не помешало.

понедельник, 13 июня 2011 г.

GB&W - Update v1.4

Это уже 4-е обновление.

Сейчас доступно:
- 40 уровней.
- 14 типов кирпичей
- 14 бонусов

Одна из последних добавленный пакостей это кирпич с кодовым названием "Биомасса". Он не прерывно плодиться с постоянной скоростью, кроме того он подвержен влиянию 2х бонусов:
- катализатор, единовременно ускоряющий рост общего объема биомассы
- заморозка, соответственно временно останавливающий рост биомассы.

Вот новое видео:

среда, 18 мая 2011 г.

GB&W и Конкурс мобильных приложений

С начала главный вопрос на повестке дня).

Сегодня зарегистрировал свой проект в конкурсе мобильных приложений под Windows Phone 7 на сайте конференции DevCon•11. В конкурсе участвует следующее видео:


Ребят у меня к вам просьба - кому симпатичен данный проект, пожалуйста не пожалейте 5 секунд своего времени, проголосуйте за него, для меня важен каждый Ваш голос. Ниже кнопочка для голосования:

Заранее благодарен.

Наконец то завершена работа над первым релизом игры. Почему первым? Потому что в планах добавлять уровни в следующих обновлениях. Сейчас приложение находится на этапе публикации в Marketplace.

суббота, 23 апреля 2011 г.

GB&W - Windows Phone 7 game preview 3

Не пугайтесь обилию выпадающих бонусов. Это один из первых десяти обучающих уровней. Десятка разбита по применению тематических бонусов их комбинаций.
В этом уровне первый раз появляется усиление на пулемет, который в свою очередь превращается в нечто иное :).

Рекомендую смотреть в качестве 720p (сейчас 480p)



Мне часто задают вопрос - как происходит управление в игре и как целиться? Все просто. Кораблик управляется одним касанием и постоянно находится под пальцем. Пулеметы наводятся на ближайшую цель в радиусе поражения. Вторым касанием на кнопки в верхней панели активируется тяжелое вооружение:
ракета I - открыть/закрыть подвеску, для экономии боезапаса. Само наводится и поражает ближайшего бота. При потере цели в процессе подлета (цель уничтожена др. ракетой и т.д.) находит и атакует следующую ближайшую.
ракета II - пуск. Само наводится и поражает/запечатывает ближайшую шахту из которой появляются боты.
ракета III - активация ракеты. Второе нажатие в области экрана помечает область поражения и происходит старт. Так же взрывается от столкновения с кирпичами.

Я потратил какое то время на изменение своего Touch класса. Теперь он понимает второе касание типа Tap при активном первом касании. Т.е. можно не прерываясь контролить кораблик и активировать ракетные бонусы.

среда, 23 марта 2011 г.

"Guns, Balls & Walls" - Windows Phone 7 game preview 2

Делюсь очередным видео. Музыкальное сопровождение отключено по политическим соображениям. Еще нет четкой договоренности с композитором.

понедельник, 7 марта 2011 г.

"Guns, Balls & Walls" - Windows Phone 7 game preview

Игра предназначается для Windows Phone 7 marketplace.
Cуть игры - смешанный коктейль трех направлений:
1) арканоид - основная идея и условия прохождения уровня.
2) аэрохоккей - расширенные правила взаимодействия биты(battleship) и мяча
3) хардкорный 2d шутер - пулеметы, ракеты, враги, кровь, взрывы ... боже дай мне сил остановиться :))).

Отладочный уровень:


Планируется сменное оружие для обоих типов вооружения,
а так же их прокачка. Возможные параметры: дальность, урон, скорострельность. Пока остановимся на стандартных амбициях.

В целом данный проект является экспериментом, в том числе и в 2d.
Очень много времени уходит на рисование, особенно что касается на первый взгляд не заметных деталей таких как кровь на решетках вентиляторов и т.д. . Примерное соотношение времени = (день рисую) / (час программирую). С самим вопросом рисования проблем нет, но не набита рука, плюс, например, если в карандаше я нарисую все что угодно, то ощущение цвета нужно еще нарабатывать. Радует то, что по мере работы прокачка движется. Мега респект создателям графического редактора Paint.net

Windows Phone 7 радует своими возможностями. По предварительным подсчетам, в данном приложении, запас на частицы порядка 4000 штук. Возможно при желании у меня получится и больше. Время покажет.

четверг, 10 февраля 2011 г.

XNA GUI library for Windows & Windows Phone 7. General Presentation Foundation

Вот на таком эпическом названии остановился мой выбор). В основном такая идея была продиктована большой схожестью названий и возможностей элементов библиотеки с технологией Windows Presentation Foundation. Это как бы должно с первых шагов подвести пользователя к мысли что все просто и знакомо в обращении. В библиотеке есть еще места которые можно в перспективе рефакторить для еще большей схожести, но всему свое время. У меня ушло очень много времени на создание сайта на сильверлайте. Именно сильверлайт я выбрал по причине отсутствия необходимости учить для меня что то новое, т.к. я ни когда не работал в области web и это мой первый сайт.

Пересекая очередной финиш, хочу выразить огромную благодарность за помощь, советы и творческие обсуждения своим друзьям:
- Сергей Лутай - блог - твиттер
- Сергей Звездин - блог
- Ильшат Хабибуллин
Спасибо ребят).

Сам сайт продукта http://generalpf.ru

Comments for Vic Gundotra message:"#feb11 Two turkeys do not make an Eagle"

Забавный мужик этот Vic Gundotra. Он является вице-президентом по разработкам в Google. В своем блоге он оставил такое сообщение "#feb11 Two turkeys do not make an Eagle", что по нашему звучит как "11 февраля Из двух индеек не получится Орла". Мало кто сомневается, что речь идет о возможном альянсе Microsoft и Nokia.

Заерзали значит, это хорошо). На уровне вице-президента публичные цирки происходят.

"Кроме мяса, индейки дают много ценного пуха и пера" [Домашняя индейка], а орел?))).

У одной индейки богатый опыт в области мобильных решений, правда ОС не сахар, видимо софт не их занятие. У другой большой потенциал с софтом и возможностями. Есть повод для переживаний).

пятница, 14 января 2011 г.

XNA, GarbageCollector, SynchronizeWithVerticalRetrace, IsFixedTimeStep – используйте правила и будет вам счастье …

И так по порядку. Что значат эти термины:

SynchronizeWithVerticalRetrace – public properties GraphicsDeviceManager. Gets or sets a value that indicates whether to sync to the vertical trace (vsync) when presenting the back buffer (MSDN). Эта штука синхронизирует переключение back buffers с частотой обновления экрана монитора. Зная, как технически происходит обновление изображения на мониторе, можно сказать, что включение данного параметра нужно для предотвращения разрезания изображения при смене кадров.

IsFixedTimeStep - public properties Microsoft.Xna.Framework.Game. Gets or sets a value indicating whether to use fixed time steps (MSDN). Параметр, отвечающий за включение или отключение режима, который отвечает за ограничение частоты вызовов метода Update.

GarbageCollector – системный сборщик мусора. Конкретно нас будет интересовать класс System.GC .

Как работают SynchronizeWithVerticalRetrace и IsFixedTimeStep. Есть четыре сочетания данных флагов:
1. SynchronizeWithVerticalRetrace = false и IsFixedTimeStep = false
В этом случае графические буферы меняются по мере их рисования, не дожидаясь синхроимпульса, синхронно вызывается метод Update.
Особенности: обычно в приложении не требуется вызывать метод Update с максимально возможной частотой. При SynchronizeWithVerticalRetrace = false мы имеем возможность измерять значение fps превышающее частоту обновления монитора. Активно пользуюсь при отладке приложения.
2. SynchronizeWithVerticalRetrace = false и IsFixedTimeStep = true
В этом случае мы можем дополнительно контролировать частоту вызова методов Update и Draw и устанавливать фиксированный интервал через свойство TargetElapsedTime.
Особенности: сомнительное удовольствие применять данный подход повсеместно. Он подходит, для каких либо исключительных случаев, в которых его применение полностью обоснованно. Не пользуюсь.
3. SynchronizeWithVerticalRetrace = true и IsFixedTimeStep = false
Тут все просто, обновляем, рендерим, ждем синхронизации, меняем back buffers и по новой.
Особенности: невозможно измерить значение fps выше частоты обновления монитора. Пользуюсь.
4. SynchronizeWithVerticalRetrace = true и IsFixedTimeStep = true
Интересный случай. Если время синхронизации меньше IsFixedTimeStep, то ждем следующей синхронизации и только после этого выполняется Update и Draw. Если же время синхронизации больше IsFixedTimeStep, то по факту выполнения синхронизации не медленно выполняется Update и Draw. Эдакий хромающий ослик без одной ноги).
Особенности: теряюсь в догадках где это можно применить, точно не мой профиль). В некоторых случаях при значениях TargetElapsedTime/ vsyncTime = 1.5f (примерно) можно на глаз заметить не равномерное обновления экрана. Не пользуюсь.

Уже теплее. И так, о главном). Если вы написали приложение|игру используя XNA, применили из вышеописанных приемов 1й или 3й и в вашем приложении наблюдаются периодические задержки, а вы к тому же нечайно вспомнили «слова которые нельзя произносить» - Garbage Collector ))). Главное не паникуйте, при этом не обязательно писать в различных форумах Ваше мнение о .net и XNA, крутости и преимуществах C++. Все это от нервов, нервы от не знания, не знание от лени, лень от глупости, но это лечится.

Как пользуется Garbage Collector’ом рядовой «пользователь» Visual Studio? Да ни как. Он полагается на настройки по умолчанию для Garbage Collector’а. Настройки по умолчанию рассчитаны на так называемые бизнес приложения, для динамичной работы с DirectX нужно доработать.

По умолчанию Garbage Collector выполняет освобождение памяти в любом случае не синхронно с работой Update. Мертвый груз копится и освобождается в непредсказуемый, для логики вашего приложения, момент. Самый простой выход – это делать в начале очередного Update:
System.GC.Collect(1);
Где 1 это номер поколения, значение подобрано экспериментально.

С другой стороны может быть не целесообразно перегибать палку и выполнять сборку мусора 60 раз в секунду при включенной синхронизации (если у монитора такая рабочая частота обновления экрана) или более 60 раз при выключенной синхронизации.
По этому можно сделать так:

using System;
using System.ComponentModel;
using Microsoft.Xna.Framework;

namespace GeneralPresentationFoundation.gSystem.gGarbageCollector
{
    public class GarbageCollector
    {

        private BackgroundWorker bw = new BackgroundWorker();

        private int stepNumber = 0;

        private int sleepStep = 1;
        public  int SleepStep { get { return sleepStep; } set { sleepStep = value; } }

        public GarbageCollector()
        {
            Init();
        }

        public GarbageCollector(int sleepStep)
        {
            this.sleepStep = sleepStep;
            Init();
        }

        private void Init()
        {
            bw.DoWork += new DoWorkEventHandler(bw_DoWork);
        }

        public void Update()
        {
            if(!bw.IsBusy)
            {
                stepNumber++;
                if(stepNumber == sleepStep)
                {
                    stepNumber = 0;
                    bw.RunWorkerAsync();
                }
            }
        }

        private void bw_DoWork(Object sender, EventArgs e)
        {
            GC.Collect(1);
        }
    }
}

Возможно не везде будет полезным применение потока, это просто мой случай.
GarbageCollector gc = new GarbageCollector(20);
т.е. при частоте монитора 60 Гц - 3 раза в секунду.