Главная » Разработка для Windows Phone 7 » Еще одно приложение «Здравствуй, Мир»?

0

Если вы уже устали от приложений «здравствуй, Мир», у меня плохие новости. Но на этот раз мы создадим очень угловатое представление слова «HELLO», используя два разных растровых изображения – вертикальную черту и горизонтальную черту. Буква «Н» будет образована двумя вертикальными и одной горизонтальной чертой. Буква «О» в конце будет выглядеть как прямоугольник.

И по касанию экрана все 15 элементов будут разлетаться в разные стороны и затем опять собираться в слово. Звучит интересно?

Если бы мы создавали приложение FlyAwayHello (Улетающее здравствуй) с нуля, первым шагом мы добавили бы содержимое в каталог Content. На этот раз это был бы не шрифт, а два растровых изображения: HorzBar.png и VertBar.png. Их можно создать прямо в Visual Studio или в Paint. По умолчанию в Paint создается абсолютно белое растровое изображение. Это просто идеальный вариант! Все что мне необходимо – это изменить размер. В Paint зайдем в меню Button (сверху слева под строкой заголовка) и выберем Properties. Зададим ширину 45 пикселов и высоту 5 пикселов. (На самом деле, конкретные значения не особенно важны, приложение обеспечивает некоторую гибкость с этой точки зрения.) Удобнее всего сохранить файл прями в папке Content проекта под именем HorzBar.png. Теперь изменим размер и зададим ширину 5 пикселов и высоту 75 пикселов. Сохраним это изображение в файле под именем VertBar.png.

Несмотря на то что файлы располагаются в соответствующей папке, проект XNA не знает об их существовании. В Visual Studio щелкнем правой кнопкой мыши папку Content и в выпадающем меню выберем Add Existing Item (Добавить существующий элемент). Можно выбрать оба файла PNG и добавить их в проект.

Я собираюсь использовать небольшой класс SpriteInfo (Сведения о спрайте) для отслеживания 15 текстур, необходимых для формирования текста. Если создаете проект с нуля, щелкните правой кнопкой мыши имя проекта, в появившемся меню выберите Add и затем New Item (или выберите Add New Item в главном меню Project). В диалоговом окне выберите Class и задайте для него имя SpriteInfo.cs.

Проект XNA: FlyAwayHello Файл: SpriteInfo.cs (полностью)

using Microsoft.Xna.Framework;

using Microsoft.Xna.Framework.Graphics;

namespace FlyAwayHello {

public class SpriteInfo {

public static float InterpolationFactor { set; get; }

public Texture2D Texture2D { protected set; get; } public Vector2 BasePosition { protected set; get; } public Vector2 PositionOffset { set; get; } public float MaximumRotation { set; get; }

public SpriteInfo(Texture2D texture2D, int x, int y) {

Texture2D = texture2D; BasePosition = new Vector2(x, y);

}

public Vector2 Position {

get {

return BasePosition + InterpolationFactor * PositionOffset;

}

}

public float Rotation {

get {

return InterpolationFactor * MaximumRotation;

}

}

}

}

Конструктор должен сохранять Texture2D вместе с данными позиционирования, которые используются для исходного размещения каждого спрайта и формирования слова «HELLO». Затем в анимации «разлета» приложение задает свойства PositionOffset (Смещение положения) и MaximumRotation (Максимальный разворот при вращении). Свойства Position и Rotation осуществляют вычисления на основании значения статического свойства InterpolationFactor (Коэффициент интерполяции), значения которого лежат в диапазоне от 0 до 1.

Рассмотрим поля класса Game1:

Проект XNA: FlyAwayHello Файл: Game1.cs (фрагмент, демонстрирующий поля)

public class Game1 : Microsoft.Xna.Framework.Game {

static readonly TimeSpan ANIMATION DURATION = TimeSpan.FromSeconds(5); const int CHAR_SPACING = 5;

GraphicsDeviceManager graphics; SpriteBatch spriteBatch; Viewport viewport;

List<SpriteInfo> spriteInfos = new List<SpriteInfo>(); Random rand = new Random(); bool isAnimationGoing; TimeSpan animationStartTime;

}

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

Метод LoadContent загружает два объекта Texture2D с помощью того же универсального метода Load, который использовался в предыдущих приложениях для загрузки SpriteFont. Теперь мы располагаем достаточным объемом сведений для создания и инициализации всех объектов SpriteInfo:

Проект XNA: FlyAwayHello Файл: Game1.cs (фрагмент)

protected override void LoadContent() {

spriteBatch = new SpriteBatch(GraphicsDevice); viewport = this.GraphicsDevice.Viewport;

Texture2D horzBar = Content.Load<Texture2D>("HorzBar"); Texture2D vertBar = Content.Load<Texture2D>("VertBar");

int      x = (viewport.Width – 5 * horzBar.Width – 4 * CHAR SPACING) / 2;

int      y = (viewport.Height – vertBar.Height) / 2;

int      xRight = horzBar.Width – vertBar.Width;

int      yMiddle = (vertBar.Height – horzBar.Height) / 2;

int      yBottom = vertBar.Height – horzBar.Height;

// H

spriteInfos.Add(new SpriteInfo(vertBar, x, y)); spriteInfos.Add(new SpriteInfo(vertBar, x + xRight, y)); spriteInfos.Add(new SpriteInfo(horzBar, x, y + yMiddle));

// E

x += horzBar.Width + CHAR SPACING; spriteInfos.Add(new SpriteInfo(vertBar, x, y)); spriteInfos.Add(new SpriteInfo(horzBar, x, y)); spriteInfos.Add(new SpriteInfo(horzBar, x, y + yMiddle)); spriteInfos.Add(new SpriteInfo(horzBar, x, y + yBottom));

// LL

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

x += horzBar.Width + CHAR SPACING; spriteInfos.Add(new SpriteInfo(vertBar, x, y)); spriteInfos.Add(new SpriteInfo(horzBar, x, y + yBottom));

}

// O

x += horzBar.Width + CHAR SPACING; spriteInfos.Add(new SpriteInfo(vertBar, x, y)); spriteInfos.Add(new SpriteInfo(horzBar, x, y)); spriteInfos.Add(new SpriteInfo(horzBar, x, y + yBottom)); spriteInfos.Add(new SpriteInfo(vertBar, x + xRight, y));

}

Метод Update отвечает за продолжение выполнения анимации. Если поле isAnimationGoing (Анимация продолжается) имеет значение false, он проводит проверку на наличие нового касания.

Проект XNA: FlyAwayHello Файл: Game1.cs (фрагмент)

protected override void Update(GameTime gameTime) {

if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this .Exit();

if (isAnimationGoing) {

TimeSpan animationTime = gameTime.TotalGameTime – animationStartTime;

double fractionTime = (double)animationTime.Ticks /

ANIMATION DURATION.Ticks;

if (fractionTime >= 1) {

isAnimationGoing = false; fractionTime = 1;

}

SpriteInfo.InterpolationFactor = (float)Math.Sin(Math.PI * fractionTime);

}

else {

TouchCollection touchCollection = TouchPanel.GetState(); bool atLeastOneTouchPointPressed = false;

foreach (TouchLocation touchLocation in touchCollection) atLeastOneTouchPointPressed |=

touchLocation.State == TouchLocationState.Pressed;

if (atLeastOneTouchPointPressed) {

foreach (SpriteInfo spriteInfo in spriteInfos) {

float r1 = (float)rand.NextDouble() – 0.5f; float r2 = (float)rand.NextDouble() – 0.5f; float r3 = (float)rand.NextDouble();

spriteInfo.PositionOffset = new Vector2(r1 * viewport.Width,

r2 * viewport.Height); spriteInfo.MaximumRotation = 2 * (float)Math.PI * r3;

}

animationStartTime = gameTime.TotalGameTime; isAnimationGoing = true;

}

}

base.Update(gameTime);

}

Когда анимация запускается, ее свойству animationStartTime (Время начала анимации) задается значение свойства TotalGameTime объекта GameTime. При последующих вызовах метод Update сравнивает это значение с новым TotalGameTime и вычисляет коэффициент интерполяции. Свойство InterpolationFactor класса SpriteInfo является статическим, поэтому задается только раз и распространяется на все экземпляры SpriteInfo. Метод Draw перебирает все объекты SpriteInfo, выполняя доступ к их свойствам Position и Rotation:

Проект XNA: FlyAwayHello Файл: Game1.cs (фрагмент)

protected override void Draw(GameTime gameTime) {

GraphicsDevice.Clear(Color.Navy); spriteBatch.Begin();

foreach (SpriteInfo spriteInfo in spriteInfos) {

spriteBatch.Draw(spriteInfo.Texture2D, spriteInfo.Position, null,

Color.Lerp(Color.Blue, Color.Red, SpriteInfo.InterpolationFactor), spriteInfo.Rotation, Vector2.Zero, 1, SpriteEffects.None, 0);

}

spriteBatch.End(); base.Draw(gameTime);

Вызов Draw также использует SpriteInfo.InterpolationFactor для интерполяции между синим и красным при определении цвета элементов слова. Обратите внимание, что структура Color также имеет метод Lerp. Слово отображается синим, но когда разлетается на отдельные элементы, они меняют цвет на красный.

Этот вызов Draw, на самом деле, мог бы быть частью SpriteInfo. SpriteInfo мог бы определять собственный метод Draw с аргументом типа SpriteBatch и затем передавать свои свойства Texture2D, Position и Rotation в метод Draw этого SpriteBatch:

public void Draw(SpriteBatch spriteBatch) {

spriteBatch.Draw(Texture2D, Position, null,

Color.Lerp(Color.Blue, Color.Red, InterpolationFactor), Rotation, Vector2.Zero, 1, SpriteEffects.None, 0);

}

Тогда цикл в перегруженном Draw класса Game1 выглядел бы следующим образом:

foreach (SpriteInfo spriteInfo in spriteInfos) {

spriteInfo.Draw(spriteBatch);

}

Это общепринятая методика, позволяющая сократить количество открытых свойств в SpriteInfo.

Источник: Чарльз Петзольд, Программируем Windows Phone 7, Microsoft Press, © 2011.

По теме:

  • Комментарии