Читать книгу «Справочник Жаркова по проектированию и программированию искусственного интеллекта. Том 7: Программирование на Visual C# искусственного интеллекта. Издание 2» онлайн полностью📖 — Валерия Алексеевича Жаркова — MyBook.

5.5. Добавление новых объектов

Продолжаем усложнять игру за счёт добавления в неё новых объектов в виде продуктов питания, например, помидоров (tomatoes) в виде файла tomato.gif, рис. 5.6.


Рис. 5.6.

Помидор.

В начале игры несколько i-х помидоров в виде массива tomatoes[i] должны появиться в верхней части экрана в качестве мишеней (рис. 5.7), которые должны исчезать после попадания в них летающего сыра (рис. 5.8).

Попадание сыра в помидор определяется уже применяемым выше методом IntersectWith.

Исчезновение помидоров выполняется при помощи свойства visible, которому присваивается булево значение false (в коде: tomatoes[i].visible = false;).

Управляя при помощи кнопок Button и мыши перемещением батона хлеба, игрок может отражать сыр вверх таким образом, чтобы уничтожить как можно больше помидоров за меньшее время, набирая при этом очки.

Добавляем в наш проект (из отмеченной выше статьи или из Интернета) файл изображения помидора tomato.gif по стандартной схеме, а именно: в меню Project выбираем Add Existing Item, в этой панели в окне “Files of type” выбираем “All Files”, в центральном окне находим и выделяем имя файла и щёлкаем кнопку Add (или дважды щёлкаем по имени файла). В панели Solution Explorer мы увидим этот файл.

Теперь этот же файл tomato.gif встраиваем в проект в виде ресурса по разработанной выше схеме, а именно: в панели Solution Explorer выделяем появившееся там имя файла, а в панели Properties (для данного файла) в свойстве Build Action (Действие при построении) вместо заданного по умолчанию выбираем значение Embedded Resource (Встроенный ресурс).





Рис. 5.7. Помидоры – мишени. Рис. 5.8. Помидоры исчезают после попадания в них сыра.

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

Листинг 5.4. Переменные и методы для помидоров (tomatoes).

//We declare the object of the System.Drawing.Image class

//for product:

Image tomatoImage;

//Position and state of tomato

struct tomato

{

public Rectangle rectangle;

public bool visible;

}

// Spacing between tomatoes. Set once for the game

int tomatoSpacing = 4;

// Height, at which the tomatoes are drawn. Will change

// as the game progresses. Starts at the top.

int tomatoDrawHeight = 4;

// The number of tomatoes on the screen. Set at the start

// of the game by initialiseTomatoes.

int noOfTomatoes;

// Positions of the tomato targets.

tomato[] tomatoes;

// called once to set up all the tomatoes.

void initialiseTomatoes()

{

noOfTomatoes = (this.ClientSize.Width – tomatoSpacing) /

(tomatoImage.Width + tomatoSpacing);

// create an array to hold the tomato positions

tomatoes = new tomato[noOfTomatoes];

// x coordinate of each potato

int tomatoX = tomatoSpacing / 2;

for (int i = 0; i < tomatoes.Length; i++)

{

tomatoes[i].rectangle =

new Rectangle(tomatoX, tomatoDrawHeight,

tomatoImage.Width, tomatoImage.Height);

tomatoX = tomatoX + tomatoImage.Width + tomatoSpacing;

}

}

// Called to place a row of tomatoes.

private void placeTomatoes()

{

for (int i = 0; i < tomatoes.Length; i++)

{

tomatoes[i].rectangle.Y = tomatoDrawHeight;

tomatoes[i].visible = true;

}

}

Приведённый выше код в теле метода Form1_Paint заменяем на тот, который дан на следующем листинге.

Листинг 5.5. Метод для рисования изображения.

private void Form1_Paint(object sender, PaintEventArgs e)

{

//If it is necessary, we create the new buffer:

if (backBuffer == null)

{

backBuffer = new Bitmap(this.ClientSize.Width,

this.ClientSize.Height);

}

//We create a object of the Graphics class from the buffer:

using (Graphics g = Graphics.FromImage(backBuffer))

{

//We clear the form:

g.Clear(Color.White);

//We draw the image in the backBuffer:

g.DrawImage(cheeseImage, cx, cy);

g.DrawImage(breadImage, bx, by);

for (int i = 0; i < tomatoes.Length; i++)

{

if (tomatoes[i].visible)

{

g.DrawImage(tomatoImage,

tomatoes[i].rectangle.X,

tomatoes[i].rectangle.Y);

}

}

}

//We draw the image on the Form1:

e.Graphics.DrawImage(backBuffer, 0, 0);

} //End of the method Form1_Paint.

Добавление новых объектов в игру соответственно усложняет код. В панели Properties (для Form1) на вкладке Events дважды щёлкаем по имени события Load. Появившийся шаблон метода Form1_Load после записи нашего кода принимает следующий вид.

Листинг 5.6. Метод для рисования изображения.

private void Form1_Load(object sender, EventArgs e)

{

//We load into objects of class System.Drawing.Image

//the image files of the set format, added to the project,

//by means of ResourceStream:

cheeseImage =

new Bitmap(myAssembly.GetManifestResourceStream(

myName_of_project + "." + "cheese.JPG"));

breadImage =

new Bitmap(myAssembly.GetManifestResourceStream(

myName_of_project + "." + "bread.JPG"));

//We initialize the rectangles, described around objects:

cheeseRectangle = new Rectangle(cx, cy,

cheeseImage.Width, cheeseImage.Height);

breadRectangle = new Rectangle(bx, by,

breadImage.Width, breadImage.Height);

//We load the tomato:

tomatoImage =

new Bitmap(myAssembly.GetManifestResourceStream(

myName_of_project + "." + "tomato.gif"));

//We initialize an array of tomatoes and rectangles:

initialiseTomatoes();

//We place the tomatoes in an upper part of the screen:

placeTomatoes();

//We turn on the timer:

timer1.Enabled = true;

}

И наконец, вместо приведённого выше метода updatePositions записываем следующий метод, дополненный новым кодом для изменения координат, обнаружения столкновений объектов и уничтожения помидоров.

Листинг 5.7. Метод для изменения координат и обнаружения столкновения объектов.

private void updatePositions()

{

if (goingRight)

{

cx += xSpeed;

}

else

{

cx -= xSpeed;

}

if ((cx + cheeseImage.Width) >= this.Width)

{

goingRight = false;

//At the time of collision, the Beep signal is given:

Microsoft.VisualBasic.Interaction.Beep();

}

if (cx <= 0)

{

goingRight = true;

//At the time of collision, the Beep signal is given:

Microsoft.VisualBasic.Interaction.Beep();

}

if (goingDown)

{

cy += ySpeed;

}

else

{

cy -= ySpeed;

}

//That the cheese did not come for the button3.Location.Y:

if ((cy + cheeseImage.Height) >= button3.Location.Y)

{

goingDown = false;

//At the time of collision, the Beep signal is given:

Microsoft.VisualBasic.Interaction.Beep();

}

if (cy <= 0)

{

goingDown = true;

//At the time of collision, the Beep signal is given:

Microsoft.VisualBasic.Interaction.Beep();

}

//We set to rectangles of coordinate of objects:

cheeseRectangle.X = cx;

cheeseRectangle.Y = cy;

breadRectangle.X = bx;

breadRectangle.Y = by;

//We check the collision of objects

//taking into account the tomatoes:

if (goingDown)

{

// only bounce if the cheese is going down

if (cheeseRectangle.IntersectsWith(breadRectangle))

{

//At the time of collision,

//the Beep signal is given:

Microsoft.VisualBasic.Interaction.Beep();

// we have a collision

bool rightIn = breadRectangle.Contains(

cheeseRectangle.Right,

cheeseRectangle.Bottom);

bool leftIn = breadRectangle.Contains(

cheeseRectangle.Left,

cheeseRectangle.Bottom);

// now deal with the bounce

if (rightIn & leftIn)

{

// bounce up

goingDown = false;

}

else

{

// bounce up

goingDown = false;

// now sort out horizontal bounce

if (rightIn)

{

goingRight = false;

}

if (leftIn)

{

goingRight = true;

}

}

}

}

else

{

// only destroy tomatoes of the cheese is going up

for (int i = 0; i < tomatoes.Length; i++)

{

if (!tomatoes[i].visible)

{

continue;

}

if (cheeseRectangle.IntersectsWith(

tomatoes[i].rectangle))

{

//At the time of collision,

//the Beep signal is given:

Microsoft.VisualBasic.Interaction.Beep();

// hide the tomato

tomatoes[i].visible = false;

// bounce down

goingDown = true;

// only destroy one at a time

break;

}

}

}

} //End of the method updatePositions.

В режиме выполнения (Build, Build Selection; Debug, Start Without Debugging) несколько i-х помидоров появляются в верхней части экрана в качестве мишеней (рис. 5.7), которые исчезают после попадания в них летающего сыра (рис. 5.8).

Управляя при помощи кнопок Button и мыши перемещением батона хлеба, мы можем отражать сыр вверх таким образом, чтобы уничтожить как можно больше помидоров за меньшее время, набирая при этом очки.

К разработке методики подсчёта очков в игре мы и приступаем.

5.6. Методика подсчёта очков в игре

Игра отличается от любого другого приложения тем, что один или несколько игроков набирают в игре очки, и победителем считается игрок, набравший наибольшее количество очков. А после набора определённого количества очков игра может переходить на более высокие (более сложные) и интересные уровни, после прохождения которых игрок может получить приз, например, в виде изображения какого-нибудь смешного персонажа.

Методика подсчёта очков (score) в игре подразумевает наличие в программе счётчика (scorer) очков и вывода очков на экран (например, методом DrawString) в строке:

g.DrawString(messageString, messageFont, messageBrush,

messageRectangle);

Видно, что в этом методе DrawString мы дожны определить параметры в виде шрифта messageFont, кисти messageBrush и зарезервированного прятоугольника для записи очков messageRectangle, причём в этот прямоугольник летающие объекты не должны залетать. На рис. 5.9 мы получили 20 очков за 2 сбитых помидора, а на 5.10 – 50 очков за 5 сбитых помидоров.

За каждый сбитый помидор мы можем начислить игроку любое количество очков, например, 10 очков в строке:

scoreValue = scoreValue + 10;

Новые очки сразу же выводятся на экран, информируя игрока.




Рис. 5.9. Получили 20 очков за 2 сбитых помидора. Рис. 5.10. Получили 50 очков.

Приступим к программной реализации методики подсчёта очков в игре в нашем базовом учебном проекте.

Сначала мы должны опустить ряд помидоров пониже, чтобы освободить место вверху для записи очков, поэтому вместо 4 записываем ординату, равную, например, 20:

int tomatoDrawHeight = 20;

В любом месте класса Form1 добавляем новые переменные для счётчика очков.

Листинг 5.8. Новые переменные.

// Font for score messages.

Font messageFont = null;

// Rectangle for score display.

Rectangle messageRectangle;

// Height of the score panel.

int scoreHeight = 20;

// Brush used to draw the messages.

SolidBrush messageBrush;

// The string, which is drawn as the user message.

string messageString = "Score : 0";

// Score in a game.

int scoreValue = 0;

Приведённый выше код в теле метода Form1_Paint заменяем на тот, который дан на следующем листинге.

Листинг 5.9. Метод для рисования изображения.

private void Form1_Paint(object sender, PaintEventArgs e)

{

//If the buffer empty, we create the new buffer:

if (backBuffer == null)

{

backBuffer = new Bitmap(this.ClientSize.Width,

this.ClientSize.Height);

}

//We create a object of class Graphics from the buffer:

using (Graphics g = Graphics.FromImage(backBuffer))

{

//We clear the form:

g.Clear(Color.White);

//We draw the images of objects in the backBuffer:

g.DrawImage(cheeseImage, cx, cy);

g.DrawImage(breadImage, bx, by);

for (int i = 0; i < tomatoes.Length; i++)

{

if (tomatoes[i].visible)

{

g.DrawImage(tomatoImage,

tomatoes[i].rectangle.X,

tomatoes[i].rectangle.Y);

}

}

//We write the player's points:

g.DrawString(messageString, messageFont, messageBrush,

messageRectangle);

}

//We draw the image on the Form1:

e.Graphics.DrawImage(backBuffer, 0, 0);

} //End of the method Form1_Paint.

Приведённый выше код в теле метода Form1_Load (для загрузки файлов изображений игровых объектов) заменяем на тот, который дан на следующем листинге.

Листинг 5.10. Метод для загрузки файлов изображений.

private void Form1_Load(object sender, EventArgs e)

{

//We load into objects of the System.Drawing.Image class

//the image files of the set format, added to the project,

//by means of ResourceStream:

cheeseImage =

new Bitmap(myAssembly.GetManifestResourceStream(

myName_of_project + "." + "cheese.JPG"));

breadImage =

new Bitmap(myAssembly.GetManifestResourceStream(

myName_of_project + "." + "bread.JPG"));

//We initialize the rectangles, described around objects:

cheeseRectangle = new Rectangle(cx, cy,

cheeseImage.Width, cheeseImage.Height);

breadRectangle = new Rectangle(bx, by,

breadImage.Width, breadImage.Height);

//We load the image file of a new object:

tomatoImage =

new Bitmap(myAssembly.GetManifestResourceStream(

myName_of_project + "." + "tomato.gif"));

//We initialize an array of new objects and rectangles,

//described around these objects:

initialiseTomatoes();

//We place new objects in an upper part of the screen:

placeTomatoes();

//We create and initialize a font for record of points:

messageFont = new Font(FontFamily.GenericSansSerif, 10,

FontStyle.Regular);

//We reserve a rectangle on the screen

//for record of points:

messageRectangle = new Rectangle(0, 0,

this.ClientSize.Width, scoreHeight);

//We set the color of a brush for record of points:

messageBrush = new SolidBrush(Color.Black);

//We turn on the timer:

timer1.Enabled = true;

} //End of the method Form1_Load.

И наконец, вместо приведённого выше метода updatePositions записываем следующий метод, дополненный новым кодом для изменения координат, обнаружения столкновений объектов, уничтожения помидоров и подсчёта очков.

Листинг 5.11. Метод для изменения координат и обнаружения столкновения объектов.

private void updatePositions()

{

if (goingRight)

{

cx += xSpeed;

}

else

{

cx -= xSpeed;

}

if ((cx + cheeseImage.Width) >= this.Width)

{

goingRight = false;

//At the time of collision, the Beep signal is given:

Microsoft.VisualBasic.Interaction.Beep();

}

if (cx <= 0)

{

goingRight = true;

//At the time of collision, the Beep signal is given:

Microsoft.VisualBasic.Interaction.Beep();

}

if (goingDown)

{

cy += ySpeed;

}

else

{

cy -= ySpeed;

}

//That cheese did not come for the button3.Location.Y:

if ((cy + cheeseImage.Height) >= button3.Location.Y)

{

goingDown = false;

//At the time of collision, the Beep signal is given:

Microsoft.VisualBasic.Interaction.Beep();

}

if (cy <= 0)

{

goingDown = true;

//At the time of collision, the Beep signal is given:

Microsoft.VisualBasic.Interaction.Beep();

}

//We set to rectangles of coordinate of objects:

cheeseRectangle.X = cx;

cheeseRectangle.Y = cy;

breadRectangle.X = bx;

breadRectangle.Y = by;

// check for collisions.

if (goingDown)

{

// only bounce if the cheese is going down

if (cheeseRectangle.IntersectsWith(breadRectangle))

{

//At the time of collision,

//the Beep signal is given:

Microsoft.VisualBasic.Interaction.Beep();

// we have a collision

bool rightIn = breadRectangle.Contains(

cheeseRectangle.Right,

cheeseRectangle.Bottom);

bool leftIn = breadRectangle.Contains(

cheeseRectangle.Left,

cheeseRectangle.Bottom);

// now deal with the bounce

if (rightIn & leftIn)

{

// bounce up

goingDown = false;

}

else

{

// bounce up

goingDown = false;

// now sort out horizontal bounce

if (rightIn)

{

goingRight = false;

}

if (leftIn)

{

goingRight = true;

}

}

}

}

else

{

// only destroy tomatoes of the cheese is going up

for (int i = 0; i < tomatoes.Length; i++)

{

if (!tomatoes[i].visible)

{

continue;

}

if (cheeseRectangle.IntersectsWith(

tomatoes[i].rectangle))

{

//At the time of collision,

//the Beep signal is given:

Microsoft.VisualBasic.Interaction.Beep();

// hide the tomato

tomatoes[i].visible = false;

// bounce down

goingDown = true;

// update the score

scoreValue = scoreValue + 10;

messageString = "Points : " + scoreValue;

// only destroy one at a time

1
...