Навигация
· XNA FAQ
· С чего начать
· Конкурсы
· Обратная связь
· XNA Блоги
Сейчас на сайте
· Гостей: 1

· Пользователей: 0

· Всего пользователей: 3,684
· Новый пользователь: headron
Последние фото
Эх, чуть не проспал закрытие.
Эх, чуть не проспал ...
Альбом: XNA Engine

GB
GB
Альбом: XNA Engine

South Park Coon & Friends
South Park Coon & Fr...
Альбом: XNA Games

Блоги
yavshoke
» XboxOne - интерес...
dampirik
» Push уведомления ...
dampirik
» Реклама,статистик...
Chort
» XNA и StartCoroutine
Chort
» Curve Class
dampirik
» Реклама, статисти...
dampirik
» Увеличение скорос...
dampirik
» Реклама, статисти...
general
» Распаковка DxtCom...
general
» Как работать с XN...
Поддержка
microsoft.com
1gb.ru - Дом для вашего сайта
Статистика посещений:

Записки программиста XNA - разработка GUI
Так уж случилось, что мне пришлось довольно много времени уделить разработке GUI компонент (контролов). Наиболее известные/популярные технологии в .NET это : WinForms — враперы для стандартных контролов Windows OS и новый и красивый WPF (уже не так уж новый, и далеко не такой красивый... вы уж мне поверьте :( ). Но не будем об грустном :), ведь под XNA стандартных контролов нет (хотя есть много готовых и бесплатных решений) и  нам будет надо делать самим. 

Хочу вас сразу застеречь, что написанное является опысом моих решений стоящих перед до мной вопросом и не является окончательной реализацией GUI (уверен найдется что-то что я не учел). 

Введение

Создание Gui элетента делится на две важных для нас части — рендеринг и логика. В свою очередь рендеринг делится на рисование и компоновки элемента, а логика на роботу с вводом, обработка данных и т.д. На схеме показаны только те части о которых пойдет речь.
articles: GUIXNA01.png

1) Layout 
Наиболее сложный вопрос при создании интерфейса — правильная компоновка. Конечно что ее можно сделать вручную задав размеры и позицию каждому элементу, но в динамическом интерфейсе такой вариант неприемлем. В WPFе этим делом занимаются компоновочные элементы (GuiContainer). Реализуется компоновка в два шага 
измерение нужных размеров
расстановка элементов

Сначала компоновочные элементы измеряют свои под-элементы, потом размещают в зависимости от реализированой логики. 

Чтобы сделать первое у элемента есть методы  
public Point Measure(Point point) 
protected virtual Point OnMeasure(Point point)

 


Первый реализует базовую логику работы с пропертями Padding ( отступ изнутри ), Margin ( отступ снаружи ), Width и Height (ширина и высота элемента). Проперти Width и Height имеют тип int? и могут быть не заданными. В таком случае размер считается по контенту.  Второй метод служат для измерения контента элемента (тест, картинка, под-елементы).
Расстановка элементов осуществляется методами 

public void Layout(Rectangle rect)
protected virtual void OnLayout(Rectangle rect)

 


Как и с Measure, первый реализует базовую логику работы Padding и Margin, а также пропертями HAlignment и VAlignment (смещение элемента относительно области расстановки). Второй метод – для расстановки контента.

Пример. В качестве примера рассмотрим реализацию класса StackPanel. Этот контейнер умеет размещать элементы один за другим в горизонтальном или вертикальном порядке (StackPanel.Orientation). В методе OnMeasure нам надо посчитать размер его контента – под-элементов. Пусть Orientation равен Horizontal, тогда ширина равна суме всех ширин элементов, а высота – наибольшая высота элементов. Реализация метода OnLayout похожа, только теперь мы уже размещаем элементы в нужном месте.

Домашнее задание – реализировать WrapPanel, унаследованную от GuiContainer. Этот контейнер должен размещать элементы в ряд (как буквы в текстовом редакторе). Если ряд выходить за границы элемента, мы начинаем новую строку.
articles: GUIXNA02.png

2) Drawing
Пожалуй наиболее легкий но и самый не определенный вопрос.Есть много способов для того чтобы отрисовать элемент. Для начала выберем чем же мы будем отрисовывать. Простой вариант — графические примитивы.Это довольно просто и удобно, но создать сложный и красивый интерфейс примитивами почти невозможно. Потому я сразу перехожу к варианту с текстурами и SpriteBatch. Текстурами можно отрисовать почти любой интерфейс, хотя нет такой «вседозволенности». Для отрисовки у нас есть виртуальный метод 
 
public virtual void Draw(SpriteBatch sprite)

 

 
Так как каждый элемент у нас должен иметь свою внешность, стандартной реализации у него нет (кроме компоновочных элементов которые отрисовывают свои под-элементы). А это значит что для каждого элемента вам надо будет самим реализовать такой метод.
Но все же чтобы упростить нам задачу с рисованием, мы добавим новый абстрактный класс — GuiDrawable. Этот объект представляет собой некий примитив, способный отрисоватся в указанную область. У него есть единственный абстрактный метод, 
 
public abstract void Draw(SpriteBatch spriteBatch, Rectangle rect, Color color);

 

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

Класс TextureDrawable демонстрирует роботу простого GuiDrawable. Он просто обрисовывает текстуру в прямоугольник. Но зачем нам еще один класс, если можно просто отрисовать текстуру? Проблема в том, что иногда надо простой текстуры не достаточно. Например если рисовать одной текстурой элементы разных размеров, то они растягиваются и получается не очень красиво. Пример решения данной проблемы класс —  BorderDrawable. Этот класс рисует текстуру таким образом, что растягивается только ее центральная часть, Вертикальные и горизонтальные края  растягиваются только в своем направлении. Углы остаются без изменений в углах прямоугольника. Вы можете реализовать угодную вам реализацию этого класса.
 
Домашнее задание - реализовать класс анимации унаследованный от GuiDrawable. Пусть в классе будет массив текстур, и на каждом  вызове Draw рисуем следующею. Когда прошли все текстуры, начинаем заново.

3) Logic / Input
А собственно вопрос логики мы решать не будем, так как и внешность, так и логика у каждого Gui элемента своя. Для ее реализации у нас есть простой виртуальный метод 
 
public virtual void Update(GameTime gameTime)

 

 
который вы реализуете для каждого елемента по своему (или не реализуете).
Но мы решим вопрос с вводом. Для этого у нас есть компонента InputComponent(для доступа к компоненте, мы добавляем ее в сервисы). Все что он делает — это на каждом апдейте сохраняет текущее и предыдущее состояние мыши и клавиатуры (реализацию геймпада я не делал). Также есть несколько пропертей для проверки состояния мыши. Это все что нам надо. 
Обработка ввода у нас будет осуществляется методами 
 
public virtual void OnMouse\Keyboard <что-то там>(InputComponent input)
 
Единственым вопросом который остался - какой элемент должен оброблять ввод?
 
3a) Mouse
Мышь у нас будет оброблять элемент над которым она находится или элемент который ее захватил (например кнопка). Для реализации первого у каждого элемента есть метод 
 
public virtual GuiElement HitTest(int x, int y)

 

 
который возвращает элемент по которому попал курсор мыши. В базовой реализации, элемент возвращает самого себя. В базовой реализации контейнеров, возвращает также и свои под-элементы.
Реализацией второго варианта есть методы 
 
public void CaptureMouse(GuiElement element)
public void ReleaseMouse(GuiElement element)

 

 
класса GuiManager. Элемент который захватил мышь будет обрабатывать все изменения мыши пока не будет вызван метод  ReleaseMouse.
 
3b) Keyboard
С обработкой клавиатуры все проще, ее обробляет элемент на котором есть фокус. Элемент на котором должен быть фокус задается пропертей 
 
public GuiElement FocusedElement

 

 
класса GuiManager. котором должен быть фокус задается пропертей. Есть несколько способов передачи фокуса : по нажатию какой нибудь клавиши, по нажатию мыши, в результате выполнения некого условия логики GUI. В примере, фокус использует только элемент TextEditBox по нажатию мыши на элементе.
 
Домашнее задание - реализовать элемент TrackBar\ScrolBar унаследованный от GuiElement.  Вам будет нужно добавить две проперти типа GuiDrawable для ползунка и фону. При нажатии на ползунку элемент должен захватить мышь.

articles: GUIXNA03.png

ContentElement 
ContentElement – особый класс элементов, конечный контент которых не определен. Поэтому в качестве контента в нем служит его единственный под-элемент.
 
Пример. Клас ContentButton – реализует элемент «кнопка». Но так как в кнопке может быть текст или рисунок, или то и другое вместе (или еще что-то), то контентом его есть неопределенным. Унаследовал это от  ContentElement в зависимости от случая мы можем поместить в него или элемент текста (TextBlock) или элемент картинки (ImageBlock).
 
GuiManager
Класс GuiManager – компонента которая отрисовывает и апдейтит элементы GUI. Для добавления элементов есть пропертя GuiElement RootElement. Так как это не коллекция, то можно задать только один элемент. В большинстве случаев этим элементом есть компоновочный элемент.
 
GuiManager.Update
В методе void Update(GameTime gameTime) заключена вся логика работы менеджера, поэтому очень важно понять принцип его работы. В этом нам поможет маленькая диаграмма.

articles: GUIXNA04.png


На вопрос – «почему обработка ввода и апдейт элементов идет первым», ответ – «потому что во время апдейта или обработка ввода, возможно надо пересчитать все заново, как например в редакторе текста».
 
Оптимизация           
Возможны несколько вариантов оптимизации, вот два из них :
  • оптимизировать Layout перерасчетом не всего дерева, а только тех ветвей которые надо пересчитать (например если у контейнера установлен размер вручную, то надо пересчитать размер только его под-элементов)
  • оптимизировать прорисовку отрисовав все на текстуру и перерисовывать только по надобности.
 
Заключение
Вот и все, я старался не делать очень много кода (ну не очень получилось :/ ). Хочется верить что это поможет вам в реализации своего полноценного GUI.

С уважением,
Байдалка Владимир ака Chort.


Комментарии
#1 | pax 06.05.2009 22:54:36
А у меня вопрос такой:
Как отрисовать элемент, чтобы оне не выходил за границы его родителя?
#2 | mike 07.05.2009 03:20:47
вопрос к Chort, но выскажу свое мнение - в логику отрисовки каждого из наследников GuiElement нужно добавить установку GraphicsDevice.ScissorRectangle
#3 | pax 07.05.2009 16:36:16
2mike
Спасибо, попробуем! Всеравно свое гуи пишем, если получится что-нибудь стоящее, то выложим. Сейчас пока работает обработка событий курсора (курсора потому, что у XBOX 360 нету мыши), можно перетаскивать контролы, если включена такая опция, да и пока реализована одна простая кнопка Smile но зато события будут работать как в Windows.Forms Smile
#4 | pax 07.05.2009 17:13:28
Вот еще вопрос (у меня xbox-а пока нету, так что я не знаю): Как на XBOX 360 ввод с клавиатуры обрабатывать, и есть ли она на нем...
#5 | mike 07.05.2009 17:18:45
нет там клавы. только GamePad
#6 | pax 07.05.2009 17:20:57
Жаль, а как там ввод организован? Виртуальную клаву делать надо?
И по поводу GraphicsDevice.ScissorRectangle - его можно вызывать внутри SpriteBatch.Begin и SpriteBatch.End?
#7 | mike 07.05.2009 17:26:52
рисуют виртуальную клаву на экране и курсором выбираются кнопки.
GraphicsDevice.ScissorRectangle нужно устанавливать перед вызовом SpriteBatch.Begin, потом рисуешь сам GuiElement, затем цикл по дочерним элементам и для каждого сначала устанавливаешь свой GraphicsDevice.ScissorRectangle а уже потом SpriteBatch.Begin и рисование. и так далее по всей иерархии GuiElement-ов
#8 | general 09.06.2009 12:37:59
про самое главное не рассказал.
как хранить иерархию элементов формы с последующей возможностью быстрого обращения (минуя распаковку из object)
#9 | Chort 30.06.2009 16:22:31
Ух... пропустил все комменты :wow: . Хоть уже и поздно но скажу - чтобы использовать GraphicsDevice.ScissorRectangle внутри SpriteBatch.Begin и SpriteBatch.End надо вызывать SpriteBatch.Begin с параметром SpriteSortMode.Immediate, хотя следующая конструкция будет быстрее
GeSHi: C#
  1.  
  2.  
  3. //...
  4.  
  5. spriteBatch.End();
  6. spriteBatch.Begin(параметры...);
  7.  
  8. //... рисуем что-то
  9.  
  10. bool oldTest = GraphicsDevice.RenderState.ScissorTestEnable;
  11. Rectangle oldRect = GraphicsDevice.ScissorRectangle;
  12.  
  13. context.GraphicsDevice.ScissorRectangle = clipRect;
  14. context.GraphicsDevice.RenderState.ScissorTestEnable = true;
  15.  
  16. spriteBatch.End();
  17. spriteBatch.Begin(параметры...);
  18.  
  19. context.GraphicsDevice.ScissorRectangle = oldRect;
  20. context.GraphicsDevice.RenderState.ScissorTestEnable = oldTest;
  21.  
  22. //...
  23.  
Добавлено за 0.006 секунд, используя GeSHi 1.0.8.2
Добавить комментарий
Пожалуйста, залогиньтесь для добавления комментария.
Рейтинги
Рейтинг доступен только для пользователей.

Пожалуйста, залогиньтесь или зарегистрируйтесь для голосования.

Отлично! Отлично! 40% [2 Голоса]
Очень хорошо Очень хорошо 40% [2 Голоса]
Хорошо Хорошо 20% [1 Голос]
Удовлетворительно Удовлетворительно 0% [Нет голосов]
Плохо Плохо 0% [Нет голосов]
Авторизация
Логин

Пароль



Вы не зарегистрированы?
Нажмите здесь для регистрации.

Забыли пароль?
Запросите новый здесь.
Мини-чат
Вы должны авторизироваться, чтобы добавить сообщение.

27.08.2014
Я умею немного на asp.net + html и css

22.08.2014
на ASP mvc 3 есть пару проектов. Могу помочь, если нужно. Обидно, если закроется Frown

21.08.2014
я тоже ноль

21.08.2014
Я в вебе только с php занимался да и то на уровне чтоб работало.

21.08.2014
Я в вебе полный ноль…

21.08.2014
Переводить его надо, хоть на ту же азуру. И двиг менять на что-то современное. Если есть веб-разрабы - можем скооперироваться. Один делать не буду.

21.08.2014
не знаю всех нюансов по оплате и все хорошее когда нибудь заканчивается

21.08.2014
А что случилось?

21.08.2014
похоже сайт будет работать до 28го числа

09.08.2014
Апи пока не видел. Но есть приложение в магазине Live Lock Screen BETA, так что думаю скоро будет

08.08.2014
Я про API для Update1. На нем работает это

08.08.2014
А что именно нужно? Чтото и сейчас открыто http://msdn.micro.
...105).aspx

06.08.2014
Кто-нибудь слышал об открытии доступа к Lock Screen Api?

31.07.2014
VPDExpress на базе MVS 2012, ни в какую не ловит исключения. Даже если их сам создаешь. И всех так?

25.07.2014
С днем системного администратора причастных к этой профессии! По случаю - тортик от жены

RSS каналы сайта
XNA - Новости
XNA - Статьи
XNA - Форум
XNA - Галерея
XNA - Файлы
Время загрузки: 0,12 секунд 8,709,585 уникальных посетителей