Среда, 28 Июля 2021, 23:51

Приветствую Вас Гость

[ Новые сообщения · Игроделы · Правила · Поиск ]
  • Страница 1 из 1
  • 1
Форум игроделов » Программирование » Программирование .NET » Основы C# + XNA (пишем змейку :))
Основы C# + XNA
_Master_Дата: Понедельник, 20 Февраля 2012, 02:50 | Сообщение # 1
был не раз
Сейчас нет на сайте
Hello Word!

Всем привет, сегодня мы начнем разрабатывать простенькую игрушку с помощью языка программирования C# и технологии XNA. Для примера была выбрана старая добрая змейка. Почему я выбрал именно ее, ответ очень прост – у этой игры предельно простая физика.

Из знаний нам понадобятся начальные навыки в программировании и основы математики. Будем использовать только бесплатные и лицензионные инструменты для разработки, а именно: Microsoft Visual C# 2010 Express, Microsoft Paint и XNA Game Studio 4.0. Студию икспресс как и XNA можно скачать с сайта Майкрософт. Кстати для установки XNA Game Studio 4.0 потребуется именно Visual C# 2010.

Запускаем студию и создаем новый проект, а именно Windows Game (4.0):


После чего IDE сгенерирует уже немало кода. Основной класс нашей игры – это Game1. Вот краткая его структура:
-public Game1() – конструктор;
-protected override void Initialize() – метод в котором можно инициализировать некоторые данные до начала игрового цикла;
-protected override void LoadContent() – здесь мы будем загружать контент игры(спрайты, звуки, шрифты);
-protected override void Update(GameTime gameTime) – этот и следующий методы постоянно вызываются вовремя работы игрового приложения, именно в этом методе будет обновляться физика и логика игры.
-protected override void Draw(GameTime gameTime) – как понятно с названия здесь будет производиться вывод на экран графики;

Для начала открываем Paint и создаем рисунок размерами 32*32 , формата PNG. Это будет сегмент тела нашей змеи. Должно получиться что-то вроде:



Если вы внимательно посмотрите на структуру нашего проекта, то увидите, что это вовсе не проект, а решение(solution), в котором есть 2 проекта. Первый это основной проект нашей игры, а второй – содержит весь контент игры, что можно понять из его названия. В последнем создадим папку и назовем ее Textures, а теперь добавим в эту папку созданный нами рисунок:


Теперь создадим 2 переменные в классе Game1, прямо перед конструктором:
Code
Texture2D snakeTexture;                      
Rectangle snakeRect;

Первая будет содержать наш рисунок. Вторая это прямоугольник, который будет содержать нашу текстуру а так же ее координаты.
Далее в методе LoadContent загрузим рисунок:
Code
snakeTexture = Content.Load<Texture2D>(@"Textures/snake");

Там же инициализируем прямоугольник:
Code
snakeRect = new Rectangle(0, 0, snakeTexture.Width, snakeTexture.Height);

Как видите, координатами его левого верхнего угла будут (0, 0), а высота и ширина равны высоте и ширине текстуры. Стоит отметить, что ось Y направлена сверху в низ, ось X – как обычно, с лева на право.

Перейдем в метод Draw. В нем нам надо вывести на экран рисунок. Для этого вызовем методы spriteBatch.Begin(); и spriteBatch.End(); а между ними метод spriteBatch.Draw(); в который передадим текстуру, прямоугольник который ее содержит и цвет в который хотим подкрасить рисунок(Color.White не изменяет цвет рисунка).
Code
spriteBatch.Begin();
spriteBatch.Draw(snakeTexture, snakeRect, Color.White);
spriteBatch.End();

Компилируем и запускам приложение(f5) и видим наш спрайт:


Hello World написали, можно идти спать.

Двигаем спрайт

Сегодня мы заставим наш спрайт двигаться, причем двигаться туда, куда мы укажем. Для начала изменим изначальную позицию спрайта.
В методе LoadContent заменим эту строчку:
Code
snakeRect = new Rectangle(0, 0, snakeTexture.Width, snakeTexture.Height);

На эту:
Code
snakeRect = new Rectangle(200, 200, snakeTexture.Width, snakeTexture.Height);

Далее создадим две новые переменные, объявим их глобально перед конструктором класса Game1:
Code
int timeElapsed;
int millisecondsPerMove = 400;

Первая переменная будет содержать время, которое прошло с последнего перемещения спрайта, а со второй переменной мы будем сравнивать прошедшее время на каждой итерации игрового цикла. Если timeElapsed станет больше, чем millisecondsPerMove произойдет передвижение спрайта, а так же обнулиться timeElapsed. Все это нужно указать в методе Update:
Code
timeElapsed += gameTime.ElapsedGameTime.Milliseconds;
if (timeElapsed > millisecondsPerMove)
{
     timeElapsed = 0;
     snakeRect.X += snakeTexture.Width;
}

Запустим приложение и увидим, как передвигается наш спрайт. Причем скорость передвижение зависит от значение millisecondsPerMove.

Теперь заставим двигаться объект куда надо.
Для реализации перемещения вверх, вниз, вперед, назад мы можем создать одну переменную целочисленного типа int и передавать ей значения от 1 до 4 в зависимости от нажатой клавиши, можем использовать 4 булевые переменные up, down, right, left. С цифрами легко можно запутаться какому направлению, какая соответствует. С булевыми переменными еще хуже, так как нам нужно, чтоб только одна из них содержала true. Потому лучше всего создать переменную перечисляемого типа.
Вот как это делается:
Code
enum direction { up, down, right, left };

Из этого кода понятно, что переменная типа direction может принимать одно значение из up, down, right, left, это удобно! Теперь создадим такую переменную, и сразу передадим ей значение left.
Code
direction move = direction.left;

Эти две строчки кода добавьте опять же над конструктором нашего класса.

Далее в методе Update добавим следующий код:
Code

KeyboardState kbState = Keyboard.GetState();
if (kbState.IsKeyDown(Keys.Left))
{
    move = direction.left;
}
else if (kbState.IsKeyDown(Keys.Right))
{
    move = direction.right;
}
else if (kbState.IsKeyDown(Keys.Up))
{
    move = direction.up;
}
else if (kbState.IsKeyDown(Keys.Down))
{
    move = direction.down;
}

Здесь мы в начале создаем переменную которая содержит текущее состояние клавиатуры KeyboardState kbState потом с помощью условий определяем направление куда двигаться спрайту.

Теперь немного изменим оставшийся код из метода Update на следующий:
Code

timeElapsed += gameTime.ElapsedGameTime.Milliseconds;
if (timeElapsed > millisecondsPerMove)
{
    timeElapsed = 0;
    if (move == direction.right)
    {
       snakeRect.X += snakeTexture.Width;
    }
    else if (move == direction.left)
    {
       snakeRect.X -= snakeTexture.Width;
    }
    else if (move == direction.up)
    {
       snakeRect.Y -= snakeTexture.Height;
    }
    else if (move == direction.down)
    {
       snakeRect.Y += snakeTexture.Height;
    }
}

Спрайт двигается в зависимости от направления, которое содержится в переменной move.
Запускаем приложение и любуемся.

Весь листинг класса Game1:
Code

public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;

    Texture2D snakeTexture;
    Rectangle snakeRect;
    int timeElapsed;
    int millisecondsPerMove = 400;

    enum direction { up, down, right, left };
    direction move = direction.left;

    public Game1()
    {
       graphics = new GraphicsDeviceManager(this);
       Content.RootDirectory = "Content";
    }

    protected override void Initialize()
    {
       base.Initialize();
    }

    protected override void LoadContent()
    {
       spriteBatch = new SpriteBatch(GraphicsDevice);

       snakeTexture = Content.Load<Texture2D>(@"Textures/snake");
       snakeRect = new Rectangle(200, 200, snakeTexture.Width, snakeTexture.Height);
    }

    protected override void UnloadContent()
    {
    }

    protected override void Update(GameTime gameTime)
    {
       KeyboardState kbState = Keyboard.GetState();
       if (kbState.IsKeyDown(Keys.Left))
       {
          move = direction.left;
       }
       else if (kbState.IsKeyDown(Keys.Right))
       {
          move = direction.right;
       }   
       else if (kbState.IsKeyDown(Keys.Up))
       {
          move = direction.up;
       }
       else if (kbState.IsKeyDown(Keys.Down))
       {
          move = direction.down;
       }

       timeElapsed += gameTime.ElapsedGameTime.Milliseconds;
       if (timeElapsed > millisecondsPerMove)
       {
          timeElapsed = 0;
          if (move == direction.right)
          {
             snakeRect.X += snakeTexture.Width;
          }
          else if (move == direction.left)
          {
             snakeRect.X -= snakeTexture.Width;
          }
          else if (move == direction.up)
          {
             snakeRect.Y -= snakeTexture.Height;
          }
          else if (move == direction.down)
          {
             snakeRect.Y += snakeTexture.Height;
          }
       }

       base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
       GraphicsDevice.Clear(Color.CornflowerBlue);

       spriteBatch.Begin();
       spriteBatch.Draw(snakeTexture, snakeRect, Color.White);
       spriteBatch.End();

       base.Draw(gameTime);
    }
}


ООП

Сегодня мы разберем основы ООП и немного оптимизируем наш код. ООП (объектно-ориентированное программирование) - это один из видов программирования, можно даже сказать доминирующий вид, на данный момент. Основной единицей в ООП есть класс. Грубо говоря, класс - это тип данных, а объект – это переменная этого типа. Особенностью классов есть то, что они содержат методы (аналог функций в структурном программировании), которые обрабатывают данные этого класса. В ООП используются три основных принципа, именуемые: инкапсуляция, полиморфизм и наследование. Расписывать их я не буду, так как это займет много времени и места, потому советую почитать /погуглить самостоятельно. Язык программирования C# является объектно-ориентированным, потому грех не воспользоваться принципами ООП в наших играх.

Перейдем от слов к практике. Какие объекты мы будем использовать в нашей игре? Как минимум – змейка (состоящая с сегментов тела) и яблочко, которое она будет кушать. Теперь подумаем, что будет общее у них? Они оба будут иметь текстуру и прямоугольник содержащий ее. А основное отличие – змейкой будет управлять игрок, а яблоком компьютер. Потому мы создадим родительский класс Sprite, который будет содержать текстуру, прямоугольник и метод Draw (на данный момент так же будет одинаковым и для змейки и для яблока).


Среда разработки сгенерирует вам такой код:
Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace XNA_Lesson3
{
    class Sprite
    {
    }
}

Сразу добавьте эти два пространства имен:
Code
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

Вот те две переменные, про которые я выше упоминал, их объявим сразу после объявления класса Sprite:
Code
public Texture2D texture;
public Rectangle rect;

Теперь напишем конструктор нашего класса:
Code
public Sprite(Texture2D texture, Rectangle rect)
{
    this.texture = texture;
    this.rect = rect;
}

Как видите ему передаются два параметра(все те же текстура и прямоугольник), после чего он заносит их в соответствующие поля.

И последнее, виртуальный метод Draw:
Code
public virtual void Draw(SpriteBatch spriteBatch)
{
    spriteBatch.Draw(texture, rect, Color.White);
}

Не забудьте перед именем класса указать, что он абстрактный - abstract class Sprite{ }

Вот весь код класса Sprite:
Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace XNA_Lesson3
{
    abstract class Sprite
    {
       public Texture2D texture;
       public Rectangle rect;

       public Sprite(Texture2D texture, Rectangle rect)
       {
          this.texture = texture;
          this.rect = rect;
       }

       public virtual void Draw(SpriteBatch spriteBatch)
       {
          spriteBatch.Draw(texture, rect, Color.White);
       }
    }
}


Так как этот класс абстрактный – мы не можем создавать его экземпляры (объекты), но мы создадим дочерние от него классы, которые уже будут использоваться по назначению.

Добавим в проект еще один класс – SnakePart, он пока будет совсем небольшой:
Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace XNA_Lesson3
{
    class SnakePart : Sprite
    {
       public SnakePart(Texture2D texture, Rectangle rect)
          : base(texture, rect)
       {
       }
    }
}

Что тут стоит отметить. Как видите, после имени класса стоит двоеточие, а потом имя родительского класса. Это значит, что данный класс унаследует от родительского все поля, а так же методы. Конструктор здесь принимает так же 2 параметра, но потом их же передает конструктору класса Sprite.

Теперь осталось только использовать все это в основном классе Game1.

Удалим эти поля:
Code
Texture2D texture;
Rectangle rect;

И создадим объект класса SnakePart:
Code
SnakePart snake;

Теперь исправим код в методе LoadContent:
Code
Texture2D snakeTexture = Content.Load<Texture2D>(@"Textures/snake");
//Здесь мы вызываем конструктор класса SnakePart и передаем ему текстуру и прямоугольник
snake = new SnakePart(snakeTexture, new Rectangle(200, 200, snakeTexture.Width, snakeTexture.Height));

В методе Update теперь нужно обращаться к прямоугольнику и текстуре, через имя объекта:
Code
timeElapsed += gameTime.ElapsedGameTime.Milliseconds;                  
if (timeElapsed > millisecondsPerMove)
{
    timeElapsed = 0;
    if (move == direction.right)
    {
       snake.rect.X += snake.texture.Width;
    }
    else if (move == direction.left)
    {
       snake.rect.X -= snake.texture.Width;
    }
    else if (move == direction.up)
    {
       snake.rect.Y -= snake.texture.Height;
    }
    else if (move == direction.down)
    {
       snake.rect.Y += snake.texture.Height;
    }
}

Ну и в методе Draw вызовем метод объекта snake:
Code
spriteBatch.Begin();
snake.Draw(spriteBatch);
spriteBatch.End();

Весь листинг класса Game1 со всеми изменениями:
Code
public class Game1 : Microsoft.Xna.Framework.Game
{
     GraphicsDeviceManager graphics;
     SpriteBatch spriteBatch;

     int timeElapsed;
     int millisecondsPerMove = 400;

     SnakePart snake;

     enum direction { up, down, right, left };
     direction move = direction.left;

     public Game1()
     {
         graphics = new GraphicsDeviceManager(this);
         Content.RootDirectory = "Content";
     }

     protected override void Initialize()
     {
         base.Initialize();
     }

     protected override void LoadContent()
     {
         spriteBatch = new SpriteBatch(GraphicsDevice);

         Texture2D snakeTexture = Content.Load<Texture2D>(@"Textures/snake");
         snake = new SnakePart(snakeTexture, new Rectangle(200, 200, snakeTexture.Width, snakeTexture.Height));
     }

     protected override void UnloadContent()
     {
     }

     protected override void Update(GameTime gameTime)
     {
         KeyboardState kbState = Keyboard.GetState();
         if (kbState.IsKeyDown(Keys.Left))
         {
             move = direction.left;
         }
         else if (kbState.IsKeyDown(Keys.Right))
         {
             move = direction.right;
         }
         else if (kbState.IsKeyDown(Keys.Up))
         {
             move = direction.up;
         }
         else if (kbState.IsKeyDown(Keys.Down))
         {
             move = direction.down;
         }

         timeElapsed += gameTime.ElapsedGameTime.Milliseconds;
         if (timeElapsed > millisecondsPerMove)
         {
             timeElapsed = 0;
             if (move == direction.right)
             {
                 snake.rect.X += snake.texture.Width;
             }
             else if (move == direction.left)
             {
                 snake.rect.X -= snake.texture.Width;
             }
             else if (move == direction.up)
             {
                 snake.rect.Y -= snake.texture.Height;
             }
             else if (move == direction.down)
             {
                 snake.rect.Y += snake.texture.Height;
             }
         }
         base.Update(gameTime);
     }

     protected override void Draw(GameTime gameTime)
     {
         GraphicsDevice.Clear(Color.CornflowerBlue);

         spriteBatch.Begin();
         snake.Draw(spriteBatch);
         spriteBatch.End();

         base.Draw(gameTime);
     }
}

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


уроки C# + XNA

Сообщение отредактировал _Master_ - Пятница, 24 Февраля 2012, 04:40
AnotherNoobДата: Понедельник, 20 Февраля 2012, 08:27 | Сообщение # 2
заслуженный участник
Сейчас нет на сайте
Отличный урок, продолжай!

Добавлено (20.02.2012, 08:27)
---------------------------------------------
Отличный урок, продолжай!

FeltikoДата: Понедельник, 20 Февраля 2012, 08:46 | Сообщение # 3
участник
Сейчас нет на сайте
Отлично, как раз учу С# и XNA.
MrNeshДата: Понедельник, 20 Февраля 2012, 09:01 | Сообщение # 4
Воин добра и света
Сейчас нет на сайте
Продолжай!
Какраз начал изучать... wink


Stalker_ShooterДата: Понедельник, 20 Февраля 2012, 20:58 | Сообщение # 5
3D XNA'шник
Сейчас нет на сайте
Дружище, а то, что таких (практически точь в точь) уроков на сайте минимум два, тебя не останавливает? Набивание репутации или постов? Или что?

*Не убегай от снайпера, умрешь уставшим.
*Мои статьи...
_Master_Дата: Суббота, 25 Февраля 2012, 16:56 | Сообщение # 6
был не раз
Сейчас нет на сайте
Quote (Stalker_Shooter)
Дружище, а то, что таких (практически точь в точь) уроков на сайте минимум два, тебя не останавливает? Набивание репутации или постов? Или что?

Я уже не в том возрасте чтоб посты набивать ))
Решил что кому-то это будет полезным, вот и ваяю. Если для тебя это сильно важно, могу бросить эту затею. Короче говоря мне как-то всеравно.

Добавлено (24.02.2012, 04:20)
---------------------------------------------
Хвост змеи

Пора добавить змее хвост! В коде наша змея будет представлена в виде массива объектов SnakePart. Количество элементов этого массива будет не что иное, как длина змеи. Кроме этого мы напишем алгоритм, по которому будет двигаться змея.

Алгоритм движения змеи:
-на каждой итерации игрового цикла (если истекло время задержки) мы будем передвигать сегмент тела змеи (начиная с последнего!) на позицию предыдущего сегмента;
- голову змеи перемещаем в последний момент, в направлении, которое содержится в переменной move;
Голова змеи это первый элемент массива, в нашем случае - snake[0].
Ну что ж приступим!

В классе Game1 вместо одного объекта SnakePart объявим массив этих объектов, так же добавим длину змейки.
Заменим данную строку:
Code
SnakePart snake;

На эту:
Code
int snakeSize;
SnakePart[] snake;

В методе LoadContent нам нужно инициализировать каждый элемент массива. Для этого выделим память под массив соответствующего размера и заполним его нашими объектами. Позиция каждого последующего элемента должна быть смещена вправо, на ширину спрайта.
Было:
Code
snake = new SnakePart(snakeTexture, new Rectangle(200, 200, snakeTexture.Width, snakeTexture.Height));

Стало:
Code
snakeSize = 5;
snake = new SnakePart[snakeSize];
for (int i = 0; i < snake.Length; i++)
{
        int x = 200;
        int y = 200;
        snake[i] = new SnakePart(snakeTexture, new Rectangle(x + i*snakeTexture.Width,      
                       y, snakeTexture.Width, snakeTexture.Height));             
}

Как видите, сначала мы задаем длину змеи, потом создаем массив, далее в цикле заполняем массив. Если посчитать на пальцах, то позиция по X первого (нулевого) элемента будет равна 200, второго = 200 + 32, третьего 200 + 64 и так далее.

Теперь напишем алгоритм движения змеи. В методе Update изменим этот блок кода:
Code
timeElapsed += gameTime.ElapsedGameTime.Milliseconds;
if (timeElapsed > millisecondsPerMove)
{
        timeElapsed = 0;
        if (move == direction.right)
        {
           snake.rect.X += snake.texture.Width;
        }
        else if (move == direction.left)
        {
           snake.rect.X -= snake.texture.Width;
        }
        else if (move == direction.up)
        {
           snake.rect.Y -= snake.texture.Height;
        }
        else if (move == direction.down)
        {
           snake.rect.Y += snake.texture.Height;
        }
}

На этот:
Code
timeElapsed += gameTime.ElapsedGameTime.Milliseconds;
if (timeElapsed > millisecondsPerMove)
{
        timeElapsed = 0;
        //перемещаем каждый сегмент хвоста змеи на позицию предыдущего сегмента
        int i = snake.Length - 1;
        while (i > 0)
        {
           snake[i].rect = snake[i - 1].rect;
           i--;
        }
        //перемешаем голову змеи
        if (move == direction.right)
        {
           snake[0].rect.X += snake[0].texture.Width;
        }
        else if (move == direction.left)
        {
           snake[0].rect.X -= snake[0].texture.Width;
        }
        else if (move == direction.up)
        {
           snake[0].rect.Y -= snake[0].texture.Height;
        }
        else if (move == direction.down)
        {
           snake[0].rect.Y += snake[0].texture.Height;
        }
}

Что здесь изменилось. Как видите, в цикле мы перемещаем каждый элемент массива на позицию предыдущего. Данный цикл будет работать до того момента пока в переменной i не окажется ноль, то есть мы передвинем все элементы кроме головы змейки. В следующей части данного блока кода мы передвигаем голову змеи в зависимости от направления.
Вот и все, осталось только вывести на экран всю змейку в метода Draw.
Code
protected override void Draw(GameTime gameTime)
{
        GraphicsDevice.Clear(Color.CornflowerBlue);

        spriteBatch.Begin();
        for (int i = 0; i < snake.Length; i++)
        {
          snake[i].Draw(spriteBatch);                     
        }
        spriteBatch.End();

        base.Draw(gameTime);
}

Змейка ползает по экрану:


А теперь представьте, сколько пришлось копировать кода, если бы мы не создали класс SnakePart.
Проект полностю

Яблочко

Сегодня мы добавим в игру яблоко, которое будет кушать змейка. Учитывая, что у нас уже есть готовый абстрактный класс Sprite, это будет совсем не сложно. Нам нужно всего лишь создать класс Apple производный от класса Sprite, и добавить логику поведения яблока.

Вот листинг класса Apple:
Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace XNA_Lesson5
{
       class Apple : Sprite
       {
           public bool isAlive;
           Random rnd = new Random();

           public Apple(Texture2D texture, Rectangle rect)
               : base(texture, rect)
           {
               isAlive = false;
           }

           public void Update()
           {
               if (!isAlive)
               {
                   isAlive = true;
                   int x = rnd.Next(0, Game1.gameScreenWidth);
                   int y = rnd.Next(0, Game1.gameScreenHeight);
                   rect = new Rectangle(x, y, texture.Width, texture.Height);
               }
           }
       }
}

Этот класс унаследовал от родительского класса поля Texture2D texture, Rectangle rect и метод Draw. Кроме этого мы добавили булевую переменную, которая содержит значение живой ли объект или нет. Еще нам понадобится генератор рандомных чисел, который будет использоваться для определения позиции яблока на экране. В конструкторе класса укажем, что объект пока еще «не живой». Далее самое интересное и важное – метод Update. Сразу идет условие – если яблочко «мертвое» оживляем его, определяем рандомно позицию прямоугольника. В дальнейшем этот метод будет немного дополнен.

Более подробно о рандоме. С помощью метода Next мы генерируем число в пределах от нуля до значение переменной Game1.gameScreenWidth которая содержит ширину экрана, аналогично определим Y координату.

Давайте сразу добавим эти две переменные в классе Game1 перед конструктором и сразу же присвоим им значения:
Code
public static int gameScreenWidth = 640;
public static int gameScreenHeight = 480;

Эти поля мы объявили как статические (их можно использовать без создания объекта класса Game1). Из названия понятно, что это ширина и высота игрового экрана.

Ну что ж теперь, когда класс Apple создан – создадим его экземпляр в классе Game1.

Для начала объявим его сразу возле массива Snake:
Code
Apple apple;

Теперь в методе LoadContent нужно загрузить текстуру и инициализировать наш объект. Сначала создайте при помощи Paint рисунок 32*32 с рисунком яблока (или другой еды) и загрузите его в папку Textures.
Теперь можно добавлять следующий код:
Code
Texture2D appleTexture = Content.Load<Texture2D>(@"Textures/apple");
apple = new Apple(appleTexture, new Rectangle(0,0,appleTexture.Width, appleTexture.Height));

Далее осталось добавить в методе Update логику нашего яблочка:
Code
apple.Update();

И не забываем рисовать (метод Draw):
Code
apple.Draw(spriteBatch);

Запустите приложение. Как видите, яблоко появилось на экране, но оно не взаимодействует со змеей. Чтоб исправить это, добавим следующее условие в конце метода Update перед base.Update(gameTime);
Code
if (snake[0].rect.Intersects(apple.rect))
        apple.isAlive = false;

Здесь проверяется, не произошло ли пересечение прямоугольника головы змеи с прямоугольником яблока. Если они пересекаются – умертвляем яблоко. После чего на следующей итерации игрового цикла, в методе Update класса Apple произойдет перемещение яблока на новую позицию.



Вот весь листинг класса Game1:
Code
public class Game1 : Microsoft.Xna.Framework.Game
{
       GraphicsDeviceManager graphics;
       SpriteBatch spriteBatch;

       int timeElapsed;
       int millisecondsPerMove = 400;
       int snakeSize;

       SnakePart[] snake;
       Apple apple;

       enum direction { up, down, right, left };
       direction move = direction.left;

       public static int gameScreenWidth = 640;
       public static int gameScreenHeight = 480;

       public Game1()
       {
           graphics = new GraphicsDeviceManager(this);
           Content.RootDirectory = "Content";

           graphics.PreferredBackBufferWidth = 640;
           graphics.PreferredBackBufferHeight = 480;
       }

       protected override void Initialize()
       {
           base.Initialize();
       }

       protected override void LoadContent()
       {
           spriteBatch = new SpriteBatch(GraphicsDevice);

           Texture2D snakeTexture = Content.Load<Texture2D>(@"Textures/snake");
           Texture2D appleTexture = Content.Load<Texture2D>(@"Textures/apple");
           apple = new Apple(appleTexture, new Rectangle(0, 0, appleTexture.Width, appleTexture.Height));

           snakeSize = 5;
           snake = new SnakePart[snakeSize];
           for (int i = 0; i < snake.Length; i++)
           {
               int x = 200;
               int y = 200;
               snake[i] = new SnakePart(snakeTexture, new Rectangle(x + i * snakeTexture.Width,
                   y, snakeTexture.Width, snakeTexture.Height));
           }
       }

       protected override void UnloadContent()
       {
       }

       protected override void Update(GameTime gameTime)
       {
           apple.Update();

           KeyboardState kbState = Keyboard.GetState();
           if (kbState.IsKeyDown(Keys.Left))
           {
               move = direction.left;
           }
           else if (kbState.IsKeyDown(Keys.Right))
           {
               move = direction.right;
           }
           else if (kbState.IsKeyDown(Keys.Up))
           {
               move = direction.up;
           }
           else if (kbState.IsKeyDown(Keys.Down))
           {
               move = direction.down;
           }

           timeElapsed += gameTime.ElapsedGameTime.Milliseconds;
           if (timeElapsed > millisecondsPerMove)
           {
               timeElapsed = 0;
               //перемещаем каждый сегмент хвоста змеи на позицию предыдущего сегмента
               int i = snake.Length - 1;
               while (i > 0)
               {
                   snake[i].rect = snake[i - 1].rect;
                   i--;
               }
               //перемешаем голову змеи
               if (move == direction.right)
               {
                   snake[0].rect.X += snake[0].texture.Width;
               }
               else if (move == direction.left)
               {
                   snake[0].rect.X -= snake[0].texture.Width;
               }
               else if (move == direction.up)
               {
                   snake[0].rect.Y -= snake[0].texture.Height;
               }
               else if (move == direction.down)
               {
                   snake[0].rect.Y += snake[0].texture.Height;
               }
           }

           if (snake[0].rect.Intersects(apple.rect))
               apple.isAlive = false;

           base.Update(gameTime);
       }

       protected override void Draw(GameTime gameTime)
       {
           GraphicsDevice.Clear(Color.CornflowerBlue);

           spriteBatch.Begin();

           apple.Draw(spriteBatch);
           for (int i = 0; i < snake.Length; i++)
           {
               snake[i].Draw(spriteBatch);

           }
           spriteBatch.End();

           base.Draw(gameTime);
       }
}

На данный момент у нас возникла парочка проблем, которые мы будем решать в дальнейшем:
- код класса Game1 продолжает разрастаться (энтропия блин);
- яблоко иногда частично уходит за пределы экрана;
- яблоко может появиться где угодно, даже поверх змеи – что не правильно.

Проект полностю

Добавлено (25.02.2012, 16:56)
---------------------------------------------
Оптимизация кода

Сегодня поработаем над оптимизацией кода, а так же поправим парочку багов в игре. Какие проблемы есть на данный момент? Во первых, изначальная позиция головы змеи – (200 , 200), и если мы немного поиграем, то увидим, что когда змея подходит к краю экрана - она или частично заходит за него, или между ними остается небольшой промежуток:



Как это исправить? Вспомним, как передвигается наша змея, голова змеи постоянно перемещается на размер своей текстуры по X или Y. Потому, чтоб змея адекватно перемещалась по игровому экрану, нам важно задать ее изначальные координаты, учитывая размер текстуры. Представим, что на экране есть виртуальная сетка с ячейками 32*32(размер нашей текстуры). А теперь зададим изначальную позицию головы змеи ,в какой либо ячейке этой сетки, например центральной.

Для начала узнаем, сколько помещается наших спрайтов на игровом поле по ширине – разделим 640 на 32, получим 20. По ширине в нашей виртуальной сетке будет 20 ячеек. Далее разделим это число пополам получим 10 (середина экрана). Теперь нам надо высчитать, чему равна координата по X данной ячейки, для этого умножаем номер ячейки на размер текстуры по ширине. Аналогично высчитаем координату Y.

В место этого кода:
Code
int x = 200;
int y = 200;

Напишем это, ну и желательно вынести эти переменные за пределы цикла:
Code
int x = (int)((gameScreenWidth / snakeTexture.Width) / 2) * snakeTexture.Width;
int y = (int)((gameScreenHeight / snakeTexture.Height) / 2) * snakeTexture.Height;

Похожие изменения надо сделать и в классе Apple.

Было:
Code
int x = rnd.Next(0, Game1.gameScreenWidth);
int y = rnd.Next(0, Game1.gameScreenHeight);

Стало:
Code
int x = (rnd.Next(0, Game1.gameScreenWidth / texture.Width)) * texture.Width;
int y = (rnd.Next(0, Game1.gameScreenHeight / texture.Height)) * texture.Height;

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



Далее хуже. Мы создадим новый класс Snake, в котором будет содержаться наш массив-змея и вся ее логика. Это сделает наш основной класс Game1 более лаконичным и аккуратным. Рассказывать в деталях тут нечего, в основном это простой копипаст элементов отвечающих за инициализацию и движение змеи с класса Game1 в новый Snake.

Листинг класса Snake:
Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace XNA_Lesson6
{
       class Snake
       {
           Texture2D texture;
              
           int timeElapsed;
           int millisecondsPerMove = 400;
           int snakeSize;

           enum direction { up, down, right, left };
           direction move = direction.left;

           SnakePart[] array;

           public Snake(Texture2D texture)
           {
               this.texture = texture;
           }

           public void Init()
           {
               int x = (int)((Game1.gameScreenWidth / texture.Width) / 2) * texture.Width;
               int y = (int)((Game1.gameScreenHeight / texture.Height) / 2) * texture.Height;

               snakeSize = 5;
               array = new SnakePart[snakeSize];
               for (int i = 0; i < array.Length; i++)
               {
                   array[i] = new SnakePart(texture, new Rectangle(x + i*texture.Width, y,    
                       texture.Width, texture.Height));    
               }

           }

           public void Move(GameTime gameTime)
           {
               KeyboardState kbState = Keyboard.GetState();
               if (kbState.IsKeyDown(Keys.Left)
                   && move != direction.right)
               {
                   move = direction.left;
               }
               else if (kbState.IsKeyDown(Keys.Right)
                   && move != direction.left)
               {
                   move = direction.right;
               }
               else if (kbState.IsKeyDown(Keys.Up)
                   && move != direction.down)
               {
                   move = direction.up;
               }
               else if (kbState.IsKeyDown(Keys.Down)
                   && move != direction.up)
               {
                   move = direction.down;
               }

               timeElapsed += gameTime.ElapsedGameTime.Milliseconds;
               if (timeElapsed > millisecondsPerMove)
               {
                   timeElapsed = 0;
                   //перемещаем каждый сегмент хвоста змеи на позицию предыдущего сегмента
                   int i = array.Length - 1;
                   while (i > 0)
                   {
                       array[i].rect = array[i - 1].rect;
                       i--;
                   }
                   //перемешаем голову змеи
                   if (move == direction.right)
                   {
                       array[0].rect.X += array[0].texture.Width;
                   }
                   else if (move == direction.left)
                   {
                       array[0].rect.X -= array[0].texture.Width;
                   }
                   else if (move == direction.up)
                   {
                       array[0].rect.Y -= array[0].texture.Height;
                   }
                   else if (move == direction.down)
                   {
                       array[0].rect.Y += array[0].texture.Height;
                   }
               }
           }

           public void Collision(Apple apple)
           {
               if (array[0].rect.Intersects(apple.rect))
                   apple.isAlive = false;
           }

           public void Update(GameTime gameTime, Apple apple)
           {
               Move(gameTime);
               Collision(apple);
           }

           public void Draw(SpriteBatch spriteBatch)
           {
               for (int i = 0; i < array.Length; i++)
               {
                   array[i].Draw(spriteBatch);
               }
           }
       }
}

Как видите, конструктор данного класса получает всего один параметр – текстуру.
Методы:
- Init() - инициализация змеи;
- Move(GameTime gameTime) – все что связано с перемещением змеи. Здесь есть небольшое нововведение - невозможность заставить двигаться змейку полностью в противоположном направлении (как бы разворот на 180). Вот:
Code
if (kbState.IsKeyDown(Keys.Left)
      && move != direction.right)
{
      move = direction.left;
}

Теперь, если змея двигается вправо, а вы нажмете кнопку влево – ничего не произойдет;
- Collision(Apple apple) – пока здесь очень немного кода, но в будущем мы добавим в этот метод столкновения с рамками игрового экрана и хвостом змеи;
- Update(GameTime gameTime, Apple apple) – обновление логики змеи, именно здесь вызываются выше описанные 2 метода и производиться проверка на столкновение и управление змейкой на каждой итерации игрового цикла;
- Draw(SpriteBatch spriteBatch) - вывод на экран массива змеи;

Теперь нужно в классе Game1 убрать все лишнее, а так же создать объект класса Snake:
Code
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace XNA_Lesson6
{
       public class Game1 : Microsoft.Xna.Framework.Game
       {
           GraphicsDeviceManager graphics;
           SpriteBatch spriteBatch;

           Apple apple;
           Snake snake;

           public static int gameScreenWidth = 640;
           public static int gameScreenHeight = 480;

           public Game1()
           {
               graphics = new GraphicsDeviceManager(this);
               Content.RootDirectory = "Content";

               graphics.PreferredBackBufferWidth = 640;
               graphics.PreferredBackBufferHeight = 480;
           }

           protected override void Initialize()
           {
               base.Initialize();
           }

           protected override void LoadContent()
           {
               spriteBatch = new SpriteBatch(GraphicsDevice);

               Texture2D appleTexture = Content.Load<Texture2D>(@"Textures/apple");
               apple = new Apple(appleTexture, new Rectangle(0,0,appleTexture.Width, appleTexture.Height));

               snake = new Snake(Content.Load<Texture2D>(@"Textures/snake"));
               snake.Init();
           }

           protected override void UnloadContent()
           {
           }

           protected override void Update(GameTime gameTime)
           {
               apple.Update();
               snake.Update(gameTime, apple);

               base.Update(gameTime);
           }

           protected override void Draw(GameTime gameTime)
           {
               GraphicsDevice.Clear(Color.CornflowerBlue);

               spriteBatch.Begin();
               apple.Draw(spriteBatch);
               snake.Draw(spriteBatch);
               spriteBatch.End();

               base.Draw(gameTime);
           }
       }
}

Так гораздо аккуратней!

Проект полностю


уроки C# + XNA

Сообщение отредактировал _Master_ - Суббота, 25 Февраля 2012, 17:02
ComentДата: Суббота, 25 Февраля 2012, 19:40 | Сообщение # 7
почетный гость
Сейчас нет на сайте
Извините за нубский вопрос - а в С# присутствует оператор switch-case ? А то if-else...else-if чуть режут глаза.
MrIncrofДата: Суббота, 25 Февраля 2012, 20:03 | Сообщение # 8
Lite Programmer
Сейчас нет на сайте
Coment, как я помню - присутствуют.
_Master_Дата: Воскресенье, 26 Февраля 2012, 02:54 | Сообщение # 9
был не раз
Сейчас нет на сайте
Quote (Coment)
Извините за нубский вопрос - а в С# присутствует оператор switch-case ? А то if-else...else-if чуть режут глаза.

Конечно присутствуют. Но как мне кажеться для начинающих if-else - более понятные.

Вот свичькейс конструкция, которой можно заменить код перемещения гловы змеи:
Code
//перемешаем голову змеи
switch (move)
{
           case direction.left:
               array[0].rect.X -= array[0].texture.Width;
               break;
           case direction.right:
               array[0].rect.X += array[0].texture.Width;
               break;
           case direction.up:
               array[0].rect.Y -= array[0].texture.Height;
               break;
           case direction.down:
               array[0].rect.Y += array[0].texture.Height;
           break;
}


Добавлено (26.02.2012, 02:54)
---------------------------------------------
Коллизии

Сегодня мы будем работать в классе Snake, а именно в методе Collision. Нам предстоит сделать проверку пересечения с рамками экрана и хвостом змеи, а так же научить нашу змейку расти. Начнем из самого трудного – рост змеи.
На данный момент, при пересечении прямоугольников головы змеи и яблока происходит попросту перемещение яблока, нам же нужно, чтоб в этот момент змея увеличивалась на один сегмент тела. Вспомним, что змея у нас представлена в виде массива. Чтоб увеличить нашу змею нам надо исполнить следующий алгоритм:

1) создаем временный массив копию текущего массива змеи;
2) увеличиваем размер змеи на 1;
3) создаем массив змеи с новой размерностью;
4) записываем из временного массива информацию в новый массив;
5) инициализируем новый (последний) элемент нового массива.

А вот и код:
Code
//кушаем яблоко
if (array[0].rect.Intersects(apple.rect))
{
          apple.isAlive = false;

          SnakePart[] buffer = array;
          snakeSize++;
          array = new SnakePart[snakeSize];

          for (int i = 0; i < buffer.Length; i++)
          {
              array[i] = buffer[i];
          }

          //добавим сегмент тела змеи
          if (array[array.Length - 2].rect.X -
              array[array.Length - 3].rect.X == 0)
          {
              array[array.Length - 1] = new SnakePart(texture,
                  new Rectangle(array[array.Length - 2].rect.X,
                  array[array.Length - 2].rect.Y + texture.Height,
                  texture.Width, texture.Height));
          }
          else if (array[array.Length - 2].rect.Y -
              array[array.Length - 3].rect.Y == 0)
          {
              array[array.Length - 1] = new SnakePart(texture,
                  new Rectangle(array[array.Length - 2].rect.X + texture.Width,
                  array[array.Length - 2].rect.Y, texture.Width, texture.Height));
          }
}

Тут стоит детально разобрать, куда именно мы будем дорисовывать сегмент тела змеи. Тут есть 2 варианты, рассмотрим первый из них:



Если во время поедания яблока последние 2 элемента массива находятся один над вторым, тогда координаты по X их верхнего левого угла равны между собой, а по Y разные. Потому если ми отнимем координаты по X этих 2-ух спрайтов - получим ноль. Тогда новый элемент рисуем с такой же координатой по X, а значение координаты по Y высчитаем, добавив до положения последнего элемента высоту спрайта.

И второй вариант, алгоритм аналогичный:



С этим разобрались.

Теперь нам нужно немного изменить конструктор класса Snake, добавим ссылку на экземпляр основного класса игры:
Code
Game1 game;

public Snake(Texture2D texture, Game1 game)
{
          this.texture = texture;
          this.game = game;
}

Теперь в классе Game1 передайте как второй параметр ключевое слово this:
Code
snake = new Snake(Content.Load<Texture2D>(@"Textures/snake"), this);

Далее в методе Collision добавим проверку на пересечение головы змеи с ее хвостом:
Code
//пересечение с хвостом змейки
for (int i = 1; i < array.Length; i++)
{
          if (array[0].rect.Intersects(array[i].rect))
              game.Exit();
}

Обратите внимание, что в цикле нулевой элемент не проверяется, думаю понятно почему. Если произошло пересечение, вызывается метод Exit – выход из игры. Именно для этого мы изменяли конструктор.

Добавим проверку на пересечение с рамками экрана:
Code
//пересечение с рамками экрана
if (array[0].rect.X < 0 || array[0].rect.Y < 0 ||
          array[0].rect.X + array[0].rect.Width > Game1.gameScreenWidth ||
          array[0].rect.Y + array[0].rect.Height > Game1.gameScreenHeight)
{
          game.Exit();
}

Тут все предельно просто и понятно.

С классом Snake мы закончили. Теперь перейдем к классу Apple. Здесь надо добавить алгоритм, который будет проверять, не пересекается ли оно со змеей, если да, то генерируем новые координаты.

Вот листинг класса Apple:
Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace XNA_Lesson7
{
          class Apple : Sprite
          {
              public bool isAlive;
              bool ok;
              Random rnd = new Random();

              public Apple(Texture2D texture, Rectangle rect)
                  : base(texture, rect)
              {
                  isAlive = false;
              }

              public void Update(SnakePart[] snake)
              {
                  if (!isAlive)
                  {
                      ok = false;
                      while (!ok)
                      {
                          int x = (rnd.Next(0, Game1.gameScreenWidth / texture.Width)) * texture.Width;
                          int y = (rnd.Next(0, Game1.gameScreenHeight / texture.Height)) * texture.Height;
                          rect = new Rectangle(x, y, texture.Width, texture.Height);

                          ok = true;
                          for (int i = 0; i < snake.Length; i++)
                          {
                    if (snake[i].rect.Intersects(rect))
                        ok = false;
                          }
                      }
                      isAlive = true;
                  }
              }
          }
}

Как видите, добавлена новая булевая переменная ok,и пока она содержит false будет повторяться цикл, генерирующий координаты яблока. Обратите, что теперь для работы метода Update нужен массив змеи, потому надо передать его в классе Game1:
Code
apple.Update(snake.array);

Чтоб это работало, нужно сделать массив общедоступным в классе Snake:
Code
public SnakePart[] array;


Весь проект

Score

Сегодня мы добавим в нашу игру очки. Для этого нужно научиться выводить текст на экран. В первую очередь подготовим место куда, будут выводиться игровые очки.
На данный момент размеры игрового экрана (там, где бегает наша змейка) соответствуют размерам области окна, в которой рисуется графика.
Вся игровая логика нашей игры связанная с этими переменными:
Code
public static int gameScreenWidth = 640;
public static int gameScreenHeight = 480;

Изменять их мы не будем, а вот размеры заднего буфера надо изменить.
Было:
Code
public Game1()
{
    graphics = new GraphicsDeviceManager(this);
    Content.RootDirectory = "Content";

    graphics.PreferredBackBufferWidth = 640;
    graphics.PreferredBackBufferHeight = 480;
}

Стало:
Code
public Game1()
{
    graphics = new GraphicsDeviceManager(this);
    Content.RootDirectory = "Content";

    graphics.PreferredBackBufferWidth = 640;
    graphics.PreferredBackBufferHeight = 500;
}

Как видите, мы увеличили место для вывода графики на 20 пикселей по высоте, а игровой экран для змейки остался 640*480.

С помощью пейтна рисуем текстуру размерами 640*20 пикселей. Создаем переменную для нее:
Code
Texture2D panelTexture;

В методе LoadContent загрузим ее с папки Textures(не забудьте добавить файл с текстурой в проект):
Code
panelTexture = Content.Load<Texture2D>(@"Textures/panel");

И выведем на экран в методе Draw, сразу после змеи:
Code
spriteBatch.Draw(panelTexture, new Rectangle(0, gameScreenHeight,  
                 panelTexture.Width, panelTexture.Height), Color.White);

Позиция прямоугольника содержащего нашу текстуру по X равна нулю, по Y значению высоты игрового экрана(480).
Запустив игру, увидим, что наша панелька рисуется там, где надо, а змейка и яблоко не могут на нее залезть.

Теперь осталось загрузить шрифт в игру и вывести очки в соответствующее место. К второму проекту нашего решения (тот что Content) добавьте новый файл, а именно Sprite Font:



Назовите этот файл просто font.
Загружаем его в проект подобно обычной текстуре. Создаем две новые переменные перед конструктором класса Game1:
Code
SpriteFont font;
public int score;

Первая это переменная для нашего шрифта, вторая это очки.
В методе LoadContent загрузим шрифт из файла:
Code
font = Content.Load<SpriteFont>(@"font");

Ну и рисуем строку на экране после панели:
Code
spriteBatch.DrawString(font, score.ToString(),  
                 new Vector2(150, gameScreenHeight - 5), Color.Black);

Здесь используется метод DrawString ему передается текстура шрифта, строка, которую надо вывести на экран, ее позиция на экране ну и цвет – черный. Запустив приложение, вы увидите, что возле нашей уродской надписи SCORE постоянно горит нолик.

Теперь добавим в методе Collision класса Snake строчку, которая будет изменять значение очков:
Code
//кушаем яблоко
if (array[0].rect.Intersects(apple.rect))
{
     apple.isAlive = false;
     game.score += 10;   //!!!

     SnakePart[] buffer = array;
     snakeSize++;
     array = new SnakePart[snakeSize];

     for (int i = 0; i < buffer.Length; i++)
     {
         array[i] = buffer[i];
     }

     //добавим сегмент тела змеи
     if (array[array.Length - 2].rect.X -
         array[array.Length - 3].rect.X == 0)
     {
         array[array.Length - 1] = new SnakePart(texture,
             new Rectangle(array[array.Length - 2].rect.X,
             array[array.Length - 2].rect.Y + texture.Height,
             texture.Width, texture.Height));
     }
     else if (array[array.Length - 2].rect.Y -
         array[array.Length - 3].rect.Y == 0)
     {
         array[array.Length - 1] = new SnakePart(texture,
             new Rectangle(array[array.Length - 2].rect.X + texture.Width,
             array[array.Length - 2].rect.Y, texture.Width, texture.Height));
     }
}


Весь проект

Все! В ближайшее время с уроками покончено, надеюсь, эта информация была кому-то полезна. Возможно, в будущем появятся уроки о 3D в XNA, организация меню игры и работа с мышкой.

Всем спасибо за внимание!


уроки C# + XNA

Сообщение отредактировал _Master_ - Воскресенье, 26 Февраля 2012, 17:26
reyzorДата: Суббота, 24 Марта 2012, 17:26 | Сообщение # 10
Проггер в законе
Сейчас нет на сайте
Кому интересна связка C# + XNA заходите на techdays.ru. Сам по этим урокам учился.

Юзаю Unity3d +C#
Мой твиттер
Помог - ставь +, Ответил на вопрос - ставь +.
Black_SnowДата: Суббота, 24 Марта 2012, 19:50 | Сообщение # 11
был не раз
Сейчас нет на сайте
Quote (reyzor)
Кому интересна связка C# + XNA заходите на techdays.ru. Сам по этим урокам учился.

Здесь разве есть уроки по XNA?
reyzorДата: Суббота, 24 Марта 2012, 20:21 | Сообщение # 12
Проггер в законе
Сейчас нет на сайте
Quote (Black_Snow)
Здесь разве есть уроки по XNA?

Да. Это русский сайт майкрософт о их технологиях в виде обзоров и туториалов.


Юзаю Unity3d +C#
Мой твиттер
Помог - ставь +, Ответил на вопрос - ставь +.
_Master_Дата: Суббота, 24 Марта 2012, 20:50 | Сообщение # 13
был не раз
Сейчас нет на сайте
Quote (Black_Snow)
Здесь разве есть уроки по XNA?

да, там есть уроки Ивана Андреева и еще парочка других.


уроки C# + XNA
arthurfokДата: Среда, 01 Августа 2012, 13:44 | Сообщение # 14
частый гость
Сейчас нет на сайте
Quote
Алгоритм движения змеи:
-на каждой итерации игрового цикла (если истекло время задержки) мы будем передвигать сегмент тела змеи (начиная с последнего!) на позицию предыдущего сегмента;
- голову змеи перемещаем в последний момент, в направлении, которое содержится в переменной move;
Голова змеи это первый элемент массива, в нашем случае - snake[0].


А не будет ли проще , если в место массива snake создать List , и при движении (если истекло время задержки) всего на всего удалить самый последний обьект (то есть сегмент хвоста) , и добавить новый обьект с начало list -а , с нужной позицией.

Так будет работать быстрее и без траты лишней времени, имхо smile
MakaralexДата: Воскресенье, 07 Апреля 2013, 16:16 | Сообщение # 15
был не раз
Сейчас нет на сайте
Молодец! Продолжай! Мне стало многое понятно, что не мог понять в других уроках. Никого не слушай, делаешь - делай, если тебе это нравиться.
Скажу ещё что, если хотя бы 1 человек тебя хвалит за проделанную работу - значит работа удалась


hamachi-server.ucoz.ru Игры по сети,по инету,на двоих.
dilovar50Дата: Вторник, 16 Апреля 2013, 09:00 | Сообщение # 16
Construct Classic User
Сейчас нет на сайте
круто, спасибо за эти уроки

Платформер-экшен в Scirra Construct для начинающих , статья состоит из 9 частей. PDF версия статьи(6 частей).
NiscortДата: Воскресенье, 09 Июня 2013, 13:11 | Сообщение # 17
уже был
Сейчас нет на сайте
Поддерживаю. Урок изложен четким русским языком... А не так как читайшь книги и половину не понимешь:) Спасибо за урок!

Толковый выбор приходит с опытом, к которому приводит выбор бестолковый.
NovatorvladДата: Понедельник, 10 Июня 2013, 05:24 | Сообщение # 18
постоянный участник
Сейчас нет на сайте
Niscort, конечно язык понятен, когда все за тебя написано.

Девбложек
NiscortДата: Понедельник, 10 Июня 2013, 11:00 | Сообщение # 19
уже был
Сейчас нет на сайте
Novatorvlad, не спорю но благодаря этому я теперь по лучше знаю что и как необходимо делать. Написал змейку по уроку, затем написал по памяти, Сейчас хочу попробовать реализовать что то свое простенькое. Я так PHP учил и скажу эфективно:). А когда занимаюсь сам по книжкам многово не понимаю:(. А тут все так просто изложено. По моему даже после этого проще книги читать будет. Т.к. уже что то да понимаешь:)

Толковый выбор приходит с опытом, к которому приводит выбор бестолковый.
Andruha93Дата: Воскресенье, 05 Января 2014, 01:32 | Сообщение # 20
частый гость
Сейчас нет на сайте
Народ не забываем повышать репутацию автору !!!
Форум игроделов » Программирование » Программирование .NET » Основы C# + XNA (пишем змейку :))
  • Страница 1 из 1
  • 1
Поиск:

Все права сохранены. GcUp.ru © 2008-2021 Рейтинг