netlib.narod.ru | < Назад | Оглавление | Далее > |
На рис. 10.2 вы уже видели пример простого главного меню, но ваша игра обычно содержит больше, чем просто экран самой игры и меню. Вам обычно нужен экран со сведениями о разработчиках, чтобы стать известным, экран настройки, позволяющий пользователю комфортабельно установить все параметры и поменять разрешение экрана, и всегда хорошо иметь в игре экран помощи, объясняющий основные принципы игры (рис. 10.3).
Рис. 10.3 |
Большинство игровых экранов используют специальную фоновую текстуру, чтобы показать больше информации на экране. На экране выбора миссии вы можете выбрать одну из четырех миссий и затем начать игру. Другие экраны просто отображают некоторую информацию и только экран настройки позволяет пользователю менять что-нибудь. Все игровые экраны возвращаются в главное меню после нажатия Back.
Чтобы автоматически обрабатывать все игровые экраны, вы используете в главном классе игры стек игровых экранов (рис. 10.4). Это позволяет вам использовать еще более сложную систему меню с несколькими уровнями, где вы всегда можете вернуться к предыдущему экрану. Если вы хотите вернуться к главному меню, просто удалите из стека все элементы, кроме самого нижнего. Другой трюк, который я часто использую в демонстрациях, — добавление перед главным меню еще одного игрового экрана, чтобы просто показать рекламный экран «купите теперь эту игру», когда пользователь выходит из игры. Сам класс — это всего несколько строк, и вам надо добавить только одну дополнительную строку к вашему главному классу. Большинство классов игровых экранов также очень просты.
Рис. 10.4 |
Почему стек игровых экранов так хорош? Так вот, все, что вам надо сделать, это наследовать все ваши классы игровых экранов от IGameScreen (рис. 10.5) и тогда вы сможете использовать следующий код для их автоматической визуализации и обработки. Метод Render каждого класса игрового экрана возвращает true, если вы закончили работу с этим экраном и хотите вернуться к предыдущему. После того, как все игровые экраны удалены, вы выходите из игры.
Рис. 10.5 |
// Нет больше игровых экранов? if (gameScreens.Count == 0) { // Тогда выходим Exit(); return; } // if (gameScreens.Count) // Обработка текущего экрана if (gameScreens.Peek().Render()) { // Воспроизведение звука возврата Sound.Play(Sound.Sounds.ScreenBack); gameScreens.Pop(); } // if (gameScreens.Peek)
В качестве очень простого примера класса игрового экрана, вот полный код класса Help из пространства имен GameScreens. Другие классы игровых экранов не намного сложнее, за исключением класса Mission, который обрабатывает саму игру.
/// <summary> /// Помощь /// </summary> class Help : IGameScreen { #region Свойства /// <summary> /// Имя этого игрового экрана /// </summary> /// <returns>Строка</returns> public string Name { get { return "Help"; } // get } // Name #endregion #region Run /// <summary> /// Запуск игрового экрана. Вызывается в каждом кадре /// </summary> /// <param name="game">Форма для доступа к диспетчеру астероидов</param> public bool Run(RocketCommanderGame game) { // Визуализация фона game.RenderMenuBackground(); // Показываем текстуру экрана помощи game.helpScreenTexture.RenderOnScreen( new Rectangle(0, 174 * BaseGame.Height / 768, BaseGame.Width, 510 * BaseGame.Height / 768), new Rectangle(0, 0, 1024, 510)); if (game.RenderMenuButton(MenuButton.Back, new Point(1024 - 210, 768 - 140)) || Input.KeyboardEscapeJustPressed) return true; return true; } // Run(game) } // class Help
Вот так. Выглядит очень просто, не так ли? Пожалуйста, самостоятельно посмотрите классы игровых экранов, если хотите больше узнать о них. В некоторых из них также есть тестовые модули, показывающие поведение и варианты использования каждого игрового экрана.
В этом разделе я хочу, чтобы вы проследовали за всеми этапами разработки внутриигрового UI для игры XNA Shhoter. UI не слишком сложен, но вы все-таки столкнетесь с несколькими проблемами. В нашей стрелялке отображаются следующие элементы:
Время миссии: минуты и секунды, показывающие, как долго вы играете.
Текущий счет: вы получаете очки за стрельбу, убийства и сбор предметов и оружия.
Рекорд, который вы должны побить, чтобы стать номером один.
Ваше текущее здоровье; если потеряете все, вы умрете.
Текущее оружие, из которого вы стреляете, и количество электромагнитных бомб, которые вы можете сбросить.
Рис. 10.6 показывает все эти элементы, объединенные на экране с помощью следующих текстур:
HudTop.png для верхней полосы на экране, показывающей время, счет и рекорд.
HudBottom.png для нижнего двойника, показывающего здоровье и текущее оружие.
GameFont.png игровой шрифт, который вы уже использовали в нескольких играх из этой книги.
NumbersFont.png для чисел из верхней части; выглядит красивее, чем шрифт по умолчанию.
Рис. 10.6 |
Потом реализуем новый класс для отображения цифр из текстуры NumbersFont.png, и благодаря классу Texture и методу RenderOnScreen, добавление внутриигрового UI ничуть не сложно.
Текстура NumbersFont.png обрабатывается в классе NumbersFont и это очень похоже на класс TextureFont из главы 4, но намного проще, поскольку здесь у вас только 11 прямоугольников: 10 для цифр от 0 до 9 и один для двоеточия для отображения времени.
private void RenderHud() { // Визуализируем верхнюю часть дисплея hudTopTexture.RenderOnScreenRelative4To3(0, 0, hudTopTexture.GfxRectangle); // Время BaseGame.NumbersFont.WriteTime(BaseGame.XToRes(73), BaseGame.YToRes(8), (int)Player.gameTimeMs); // Счет BaseGame.NumbersFont.WriteNumberCentered(BaseGame.XToRes(485), BaseGame.YToRes(8), Player.score); // Рекорд BaseGame.NumbersFont.WriteNumberCentered(BaseGame.XToRes(920), BaseGame.YToRes(8), Highscores.TopHighscore); // Визуализируем нижнюю часть дисплея Rectangle bottomHudGfxRect = new Rectangle(0, 24, 1024, 40); hudBottomTexture.RenderOnScreenRelative4To3(0, 768 - 40, bottomHudGfxRect); // Здоровье Rectangle healthGfxRect = new Rectangle(50, 0, 361, 24); hudBottomTexture.RenderOnScreenRelative4To3(50, 768 - 31, new Rectangle(healthGfxRect.X, healthGfxRect.Y, (int)(healthGfxRect.Width * Player.health), healthGfxRect.Height)); // Оружие и бомбы! Rectangle weaponMgGfxRect = new Rectangle(876, 0, 31, 24); Rectangle weaponGattlingGfxRect = new Rectangle(909, 0, 27, 24); Rectangle weaponPlasmaGfxRect = new Rectangle(939, 0, 33, 24); Rectangle weaponRocketsGfxRect = new Rectangle(975, 0, 24, 24); Rectangle weaponEmpGfxRect = new Rectangle(1001, 0, 23, 24); TextureFont.WriteText(BaseGame.XToRes(606), BaseGame.YToRes(768 - 20) - TextureFont.Height / 3, "Weapon: "); // Показываем значок вооружения! Rectangle weaponRect = Player.currentWeapon == Player.WeaponTypes.MG ? weaponMgGfxRect : Player.currentWeapon == Player.WeaponTypes.Gattling ? weaponGattlingGfxRect : Player.currentWeapon == Player.WeaponTypes.Plasma ? weaponPlasmaGfxRect : weaponRocketsGfxRect; hudBottomTexture.RenderOnScreenRelative4To3( 715, 768 - 31, weaponRect); // И название оружия TextureFont.WriteText(BaseGame.XToRes(717+weaponRect.Width), BaseGame.YToRes(768 - 20) - TextureFont.Height / 3, Player.currentWeapon.ToString()); TextureFont.WriteText(BaseGame.XToRes(864), BaseGame.YToRes(768 - 20) - TextureFont.Height / 3, "EMPs: "); // Показываем значки бомб, если они у нас есть for (int num = 0; num < Player.empBombs; num++) hudBottomTexture.RenderOnScreenRelative4To3( 938 + num * 23, 768 - 31, weaponEmpGfxRect); } // RenderHud()
Это решение замечательно работает на PC, но как только вы запустите игру (или тестовый модуль TestHud, который я написал даже раньше, чем метод RenderHud) на Xbox 360, подключенной к телевизору, то увидите, что информационный дисплей виден не полностью, или, еще хуже, вообще почти не виден.
Если вы раньше никогда не разрабатывали игры для консолей, подключаемых к телевизорам, это может стать для вас новой проблемой, поскольку на мониторе PC вы всегда можете использовать 100% видимой области без какой-либо безопасной зоны. Но на большинстве телевизионных экранов вы не видите 100% экрана, а лишь где-то около 90%, и это значит, что примерно 10% по ширине и высоте на границах экрана невидимы (рис. 10.7).
Рис. 10.7 |
Вы можете спросить, почему XNA автоматически не помещает все внутрь этой безопасной области. Дело в том, что это не так просто. Телевизоры получают полный сигнал, и в зависимости от способа подключения и модели телевизора вы видите различные результаты. Вот несколько примеров, с которыми я столкнулся:
PC подключенный к монитору кабелем VGA или DVI позволяет вам видеть 100%.
Xbox 360 подключенная через кабель VGA к монитору PC также позволяет вам видеть 100% (или близко к 100%, если разрешение не является родным).
Xbox 360 подключенная к старым телевизорам через SCART: видимо около 92%.
Xbox 360 подключенная через компонентный кабель к моему новому 24-дюймовому монитору Dell (да HDTV): видимо около 93% – 95% (в зависимости от разрешения).
Некоторые старые телевизоры (согласно документации XNA и советам в сети) имеют безопасную область размером 80% – 90%, но я никогда не видел вариант 80%; это, вероятно, худший вариант.
Как видите, недостаточно просто использовать на Xbox 360 90% экрана и больше не беспокоиться об этом. Результаты на экране могут сильно различаться в зависимости от ситуации, которая не может быть проверена ни вашей игрой, ни XNA Framework. Единственная вещь, в которой можно быть уверенным, это то, что на PC вы будете гарантированно видеть все 100% пикселей экрана, и по этой причине многие игры для PC используют края экрана для отображения элементов UI и другой информации. Если вы взглянете на Xbox 360 (или на любую консольную игру, если на то пошло), вы заметите, что у них часто упрощенный интерфейс, и они никогда не показывают какую-либо информацию на самом краю экрана.
Вы не можете просто поместить информационный дисплей в 90% безопасную область, поскольку если пользователь может видеть большую область, все будет выглядеть некрасиво, ведь у вас нет графики за пределами 90% области, а если пользователь видит меньше, у вас остается та же проблема, что и прежде. Вместо того, чтобы ходить кругами вокруг этой проблемы, вы должны здесь остановиться и переосмыслить задачу. Плохая идея — размещать элементы UI в игре для Xbox 360 как показано на рис. 10.6. Лучшее решение — изменить графику UI и помещать ее в безопасную область, если игра запущена в ситуации, когда пользователь не может видеть 100%, подобной запуску на Xbox 360. Рис. 10.8 показывает, как изменяется графика информационного дисплея, чтобы он работал как на PC, где вы помещаете его к границам экрана, так и на Xbox 360, где он помещен во внутренние 92% (остается видимым с безопасной областью 90%, становится трудным для чтения, если видимо только 85% или меньше, но я никогда не сталкивался с такими наихудшими случаями; у большинства телевизоров безопасная область около 90% – 95%).
Рис. 10.8 |
Рис. 10.9 показывает итоговую раскладку экрана XNA Shooter после прохождения через все, описанные в этой главе, этапы. За подробностями обращайтесь к методу RenderHud из класса Mission. Все разрабатывалось главным образом для широкоэкранных разрешений и чтобы выглядеть хорошо на Xbox 360, но даже с меньшими разрешениями на PC все выглядит хорошо, подобно приведенному снимку экрана. Для тестирования меню и игровых экранов из этой главы запустите пример игрового проекта для этой главы, который по-прежнему основан на проекте XnaGraphicsEngine, начатом вами в главе 5, но теперь у вас в нем есть несколько новых и обновленных классов.
Рис. 10.9 |
Согласно моему опыту есть несколько вещей, которые следует помнить, разрабатывая игры XNA для Xbox 360. Все эти вопросы не имеют большого значения на PC, но могут обойтись вам в дополнительное потраченное время, пока вы не решите их правильно. Одна из основных проблем заключается в том, что если вы разрабатываете вашу игру на PC, а затем тестируете ее на Xbox 360 только в самом конце, многие вещи могут оказаться сделанными неверно (плохая производительность на .NET Compact Framework; элементы UI по краям экрана, которые будут невидимы на некоторых телевизорах; плохая поддержка игрового пульта Xbox 360, являющегося главным устройством ввода для Xbox 360 и т.д.). Если вы заинтересовались, можете прочесть больше об этих вопросах в моем сетевом дневнике.
Тестирование, тестирование и еще раз тестирование. Это наиболее важный совет. Пишите тестовые модули и постоянно проверяйте их работу на вашем PC и на Xbox 360. Я одновременно держу открытыми два проекта (они оба используют одни и те же файлы, но я использую решение PC для разработки, а решение Xbox 360 используется только для развертывания и тестирования). Почти во всех моих классах есть тестовые модули, и я постоянно проверяю их, пока они не будут завершены.
Не используйте циклы foreach, особенно в загруженных циклах визуализации. Это может звучать несколько сумасшедше, поскольку не имеет значения на PC и современные процессоры достаточно быстры, чтобы обрабатывать создание и удаление многих тысяч объектов в каждом кадре, в чем большинство игр даже не нуждается. Но в .NET Compact Framework такие вещи, как циклы foreach, захламляют память, поскольку новый экземпляр перечислителя создается каждый раз, когда вы запускаете цикл foreach. Через некоторое время у вас будет много мертвых объектов, которые следует собрать. Это занимает некоторое время и может заметно замедлить вашу игру. Версия для PC может работать с частотой около 200 кадров, но ваши версии для Xbox 360 застрянут где-то на отметке в 30 – 40 кадров. Избегайте создания новых данных в каждом кадре, избегайте циклов foreach (просто замените их обычными циклами for; зачастую для этого требуется только одна дополнительная строка кода) и вы сможете увеличить производительность на 100% и более.
В Arena Wars (первой разработанной мной игре для .NET) я никогда не создавал какие-либо данные в ходе игры. Все объекты создавались в начале каждой миссии и повторно использовались (это не требовало больших усилий, поскольку принципы игры не позволяли бесконечное количество подразделений; оно почти всегда оставалось примерно одним и тем же, поскольку вы получали назад деньги за погибшие подразделения, чтобы строить новые). Такой подход позволяет ослабить удар по производительности, наносимый сборщиком мусора на медленных машинах в .NET 1.1. В последующих проектах я столько не беспокоился о создании новых объектов, и просто кодировал все самым простым способом, поскольку тестовые модули ведут вас в направлении быстрой разработки решения, которое работает и протестировано, но, возможно, будет не лучшим в других ситуациях, таких как Xbox 360 .NET Compact Framework. Это нормально, поскольку теперь вы можете использовать тестовые модули, чтобы проверить, работают ли другие решения тем способом, который вы от них ожидаете. Для XNA Shooter и XNA Racer (и пары других новых игровых проектов) я снова обеспечиваю, чтобы большинство игровых данных создавалось в начале каждого уровня, а не динамически во время игры. Это делает написание кода значительно проще и позволяет более свободно выполнять рефакторинг, улучшая как общий дизайн кода, так и скорость исполнения. Я советую вам всегда сначала использовать подход «чистого кода» и думать об оптимизации вначале, а не развивать важный код вокруг уродливо оптимизированного в начале проекта кода, которым трудно управлять. Благодаря множеству усовершенствований, которые были проделаны в .NET 2.0 производительность сборщика мусора, повторное использование существующих объектов и обработка исключений теперь намного лучше, а сегодняшние компьютеры намного быстрее.
Безопасная область на телевизоре может создавать проблемы. Воспользуйтесь Google для поиска снимков экрана Xbox 360 и вы заметите, что GUI (графический интерфейс пользователя) выглядит слегка отлично от большинства игр для PC. В играх для PC часто по краям экрана есть элементы UI, показывающие вам подсказки, небольшие кнопки и другие не слишком важные вещи. Если вы будете делать так для игр на Xbox 360, все эти элементы UI на обычных телевизорах могут обрезаться. Для XNA Shooter я должен был переработать все элементы UI, поскольку они не помещались на телевизионном экране и было непрактично помещать их на панель (подобную панели задач Windows), поскольку это выглядит по-разному на PC и некоторых телевизорах. Вместо этого я поместил все элементы UI на плавающие панели, которые подстраиваются в зависимости от того, на какой экран смотрит пользователь.
Важная вещь здесь — разместить важные элементы UI во внутреннем 90% (или 93%, если вы хотите, чтобы они были вплотную к краям) прямоугольнике. Это значит, что вместо использования полного разрешения 1920 × 1080 пикселей, вы используете только 90% от него (1728 × 945). Или просто начинайте визуализировать элементы UI, пропустив примерно 5% экрана (координата X: 96, координата Y: 54). Очевидно, что местоположение этого пикселя зависит от разрешения экрана; просто вычисляйте его в вашем главном классе и затем используйте, когда визуализируете UI.
Чтобы получить дополнительную информацию о .NET Compact Framework и том, как ее лучше использовать для XNA Framework на Xbox 360, прочитайте замечательную статью от команды разработчиков .NET Compact Framework, которую можно найти по адресу http://blogs.msdn.com/netcfteam/archive/2006/12/22/ managed-code-performance-on-xbox-360-for-the-xna-framework-1-0.aspx.
netlib.narod.ru | < Назад | Оглавление | Далее > |