Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Создание компьютерных игр


Информационный Канал Subscribe.Ru

Cоздание компьютерных игр.
Рассылка Евгения Казеко.
Выпускается еженедельно по средам.

Выпуск 9. (от 14 мая 2003 года)
Оружейный магазин и многое другое.

--------------------------------------------------------------
* Замечание для новых читателей, особенно для новичков
в программировании - обязательно ознакомьтесь с архивом
рассылки, иначе вам будет непонятно или неинтересно.


Мы наконец-то сделаем это. На этот раз оружейный магазин
нашего игрового мира будет открыт, да и уже знакомый нам
лекарь появится дома. Будут еще кое-какие изменения.
И в результате выпуск будет объемным и достаточно сложным.
Я бы сказал, это будет по-настоящему серьезная консольная
программа. Давайте обо всем по порядку.

Прежде всего хочу в очередной раз поблагодарить всех, кто
пишет и дает ценные замечания или советы. Не всегда конечно
мне удается оперативно среагировать на них, но все они очень
мне помогают. Я решил, что страна должна знать своих героев,
поэтому говорю большое спасибо Рязанцеву Владимиру за
предложение ввести сохранение игры и возможность задавать
параметры (напр. damage) не одним числом, а в диапазоне.
Но все это будет не сегодня - сегодня я опять же благодарю
всех, кто заметил подвисание при вводе буквы вместо цифры, а
также говорю отдельное спасибо MadMan за вопрос о вводе имени,
состоящего больше чем из одного слова. Сейчас я расскажу, как
справиться с этими бедами.

Сперва о подвисании. Стараясь максимально упростить программу,
я забыл о такой функции как rewind. Дело в том, что после
каждого scanf мы должны очистить буфер ввода, иначе в нем
остаются значения, которые и вызывают подвисание программы.
Сделать это можно, вызвав функцию rewind(stdin);
Как это делается, вы увидите в программе.

А теперь о вводе имени из нескольких слов. Дело в том, что
scanf не очень хорошо подходит для строк. Она воспринимает
строку только до первого пробела, все остальное выбрасывается.
Поэтому для ввода имени мы будем использовать функцию gets,
которая воспринимает строку полностью, до нажатия Enter.
В качестве параметра в нее нужно передать переменную, куда
будут записываться вводимые с клавиатуры данные. Опять же,
вы увидите работу функции в программе.

Чем еще сегодня я вас порадую? Усложнилась структура меню,
теперь вы можете выходить в главное меню и вновь возвращаться
в игру, без повторного создания персонажа. И конечно же,
появился оружейный магазин, которого мы так долго ждали.
Пока там всего один предмет, но его можно купить, и он
изменит (увеличит) характеристики персонажа. Единственная
недоработка - предмет нельзя будет снять. Программа и так
получилась большой и сложной, поэтому я решил оставить
это до следующего раза.

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

Текст программы:

-----------------------------------------

#include <stdio.h>


// Сегодня нам понадобится еще одна структура - для предметов,
// которые мы будем покупать в магазине

typedef struct _ITEM
{
        char item_name[20];
        int price, attack_modifier, defence_modifier, magic_modifier;

        // Характеристики предмета - цена и параметры изменения
        // атаки, защиты и магии

} ITEM;


// Определяем структуру с характеристиками игрока

typedef struct _PLAYER
{
        char name[25];
        int player_class;
        int health, gold, attack, defence, magic;

        /*      Герою нужно где-то будет хранить предметы, купленные
                в оружейном магазине, поэтому вводим соответствующие
                переменные. Пока что ограничимся одним предметом. */


        ITEM Wear_item;
        // Предмет на герое



} PLAYER;






// Объявляем прототипы (заголовки) наших функций.
// Зачем они все нужны, вы увидите ниже.

int MainMenu();

void PlayGame(PLAYER *Player);

void CreatePlayer(PLAYER *Player);

void ShowPlayerInfo(PLAYER Player);

void HealerHouse(PLAYER *Player);

void WeaponShop(PLAYER *Player, ITEM *ShopItem);


/*      А теперь настало время поговорить о такой важной вещи,
        как указатели. Обратите внимание на изменения в функции
        CreatePlayer - теперь она ничего не возвращает, однако
        у нее появился параметр, да еще и со звездочкой.
        Что все это значит?

        Это значит, что параметр, который нужно будет передать
        в функцию для ее нормальной работы - это указатель.
        Помните, мы говорили о локальных и глобальных переменных?
        Давайте вспомним, как работала функция CreatePlayer в
        прошлый раз (мне следовало бы рассказать об этом подробнее
        еще тогда, но лучше я исправлю эту оплошность сейчас,
        чем не буду этого делать вообще).

        Мы создавали локальную переменную, возвращали ее из
        функции и присваивали переменной Player, определенной
        в функции main(). (Очень надеюсь, что этот момент
        всем понятен - когда функция возвращает какое-либо
        значение, тип которого мы и указываем перед ее именем
        при определении функции, то мы можем запросто присвоить
        это значение любой переменной соответствующего типа.
        Да не просто можем, а так и будем делать, иначе зачем
        вообще что-то возвращать из функции?)

        При этом локальная переменная (Player в CreatePlayer)
        уничтожалась, а глобальная (Player в main) оставалась
        и хранила присвоенные ей значения. Казалось бы, мы могли
        сразу взять эту глобальную переменную, передать ее
        параметром в функцию и изменить как надо? Мы ведь
        передали ее в функцию ShowPlayerInfo.

        Так-то оно так, но важно учесть одну вещь, которая сегодня
        будет "гвоздем программы". В ShowPlayerInfo мы просто
        использовали значения переменной Player. Функция создавала
        в памяти локальную копию этой переменной (сама, без нашего,
        как говорится, вмешательства) и работала с ней, после чего
        эта копия уничтожалась. И если мы будем присваивать внутри
        функции значения, то они будут присваиваться как раз локальной
        копии нашей структуры Player, и будут уничтожены после выхода
        из функции. Значения же самой структуры Player не изменятся.

        Что же делать? Как сказать функции, чтобы она не создавала
        локальную копию, а работала именно с нашей переменной?
        Для этого и используются указатели.

        Вспомните функцию scanf. Для того, чтобы она могла изменить
        значение переменной, записав в нее данные с клавиатуры,
        мы ставили знак &, тем самым говоря, что мы передаем адрес
        переменной в памяти. Этот адрес как раз и является указателем
        на переменную. Таким образом, переменная имеет имя, значение
        и адрес, например, переменная x может иметь значение 1 и
        адрес 0x00fffec2. Нам не нужно знать это число в шеснадцатеричной
        системе счисления, вместо этого мы просто ставим знак &
        перед переменной.

        А зачем же нужна звездочка *? Прежде всего, звездочка в
        параметре означает, что в функцию нужно передать именно
        указатель, а не значение. Звездочка в самой функции (в ее
        описании) будет нужна вот зачем. Дело в том, что адрес-то мы
        передадим, а работать собираемся со значениями. А простое имя
        переменной без звездочки будет означать уже адрес.

        Чтобы окончательно вас не запутать, предлагаю перейти к самой
        функции (и попутно рекомендую посмотреть обучалку по указателям
        на www.gamecoder.nm.ru).

*/




// Описываем функции


// ****************** CREATE PLAYER **************************

// Функция для создания нового персонажа

// Звездочка в параметре означает - функции требуется
// указатель. (Мы передадим его, используя &).
void CreatePlayer(PLAYER *Player)
{

        int pl_class = 0;               // Создадим локальную переменную для scanf


        // Вводим имя персонажа

        printf("\nEnter player's name: ");
        gets(Player->name);

        /*      И вновь большое объяснение. Указатели вещь непростая,
                по крайней мере, требует подробного разбора.
                К тому же у нас тут еще и функция, о которой я упоминал
                в начале выпуска. Ну с ней-то как раз все просто,
                в качестве параметра ей нужно задать переменную, куда
                будет вводиться строка. А вот что же это за стрелочка?

                А стрелочка - это знак, который используется вместо
                звездочки (и вместо точки) при работе со стркутурой.

                Сперва разберем вариант без структуры. Когда мы вызываем
                функцию и передаем в нее указатель, то Player это уже
                не значение переменной! Это ее адрес в памяти.

                К примеру, у нас есть переменная х, с которой работает
                функция. Запись х означает значение переменной, которое
                можно изменять, например х = 0;. Мы передаем в функцию
                адрес переменной, вот так: &x. И теперь, в функции
                запись х означает уже не значение, а адрес! И мы не
                можем написать х = 0;, так как это значит, что мы
                присвоим нулевое значение адресу. Чтобы присвоить значение
                самой переменной, то нужно записать *х = 0;

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

                А теперь про структуру. Для ней вместо звездочки нужно указать
                -> между именем структуры и именем нужной переменной,
                вложенной в эту структуру. Вот и вся разница.
        */


        printf("\n");

        // Определяем класс персонажа
        while ( (Player->player_class < 1) || (Player->player_class > 3) )
        {
                printf("Choose your class (1 - warrior, 2 - mage, 3 - thief): ");

                scanf("%d", &pl_class);
                Player->player_class = pl_class;


                switch(Player->player_class)
                {
                case 1:                                 // Воин
                        Player->health = 40;
                        Player->gold = 20;
                        Player->attack = 3;
                        Player->defence = 3;
                        Player->magic = 1;
                        break;
                case 2:                                 // Маг
                        Player->health = 20;
                        Player->gold = 20;
                        Player->attack = 1;
                        Player->defence = 2;
                        Player->magic = 3;
                        break;
                case 3:                                 // Вор
                        Player->health = 20;
                        Player->gold = 40;
                        Player->attack = 2;
                        Player->defence = 2;
                        Player->magic = 2;
                        break;
                default:
                        printf("Wrong class. Please choose again.\n\n" );
                        break;
                }

                /*      Пара слов о default. Как заметило несколько читателей,
                        и заметило совершенно справедливо, для новичков вовсе
                        не очевидно, что default использовать необязательно,
                        если не нужно выполнять никаких действий, не описанных
                        в case. В этом случае default можно убрать, и все
                        будет выполняться точно так же.
                */


                // Так мы записываем значение в строку
                sprintf(Player->Wear_item.item_name, "%s", "none");

                // Задаем игроку "пустой предмет"

                Player->Wear_item.price = 0;
                Player->Wear_item.attack_modifier = 0;
                Player->Wear_item.defence_modifier = 0;
                Player->Wear_item.magic_modifier = 0;


                // Обязательно очищаем буфер ввода!
                rewind(stdin);

        }       // конец цикла while


} // конец функции CreatePlayer()




// ****************** SHOW PLAYER INFO ***********************

// Функция для вывода информации об игроке

void ShowPlayerInfo(PLAYER Player)
{
        printf("\n");
        printf("*** PLAYER INFO *** \n");
        printf("Name: %s\n", Player.name);
        switch(Player.player_class)
        {
                case 1:
                        printf("Class: Warrior\n");
                        break;
                case 2:
                        printf("Class: Mage\n");
                        break;
                case 3:
                        printf("Class: Thief\n");
                        break;
                default:
                        break;
        }

        // Обратите внимание на то, как предметы
        // влияют на характеристики персонажа

        printf("Health: %d\n", Player.health);
        printf("Gold: %d\n", Player.gold);
        printf("Attack: %d\n", Player.attack + Player.Wear_item.attack_modifier);
        printf("Defence: %d\n", Player.defence + Player.Wear_item.defence_modifier);
        printf("Magic: %d\n", Player.magic + Player.Wear_item.magic_modifier);
        printf("Item: %s\n", Player.Wear_item.item_name);
        printf("******************** \n");
        printf("\n\n\n");


} // конец ShowPlayerInfo




// ****************** PLAY GAME ************************
// Функция передвижения по игровому миру


void PlayGame(PLAYER *Player)
{
        int selection = 0;

        // Нам понадобится переменная для предмета
        // оружейного магазина (пока что единственного)

        ITEM ShopItem;

        // Присваиваем значения переменным предмета
        // Конечно, не лучший споcоб - делать это здесь,
        // но на первое время сгодится

        sprintf(ShopItem.item_name, "%s", "Sword of Power");
        ShopItem.price = 20;
        ShopItem.attack_modifier = 5;
        ShopItem.defence_modifier = 5;
        ShopItem.magic_modifier = 5;


        // Цикл, реализующий передвижение по игровому миру.
        // Мы находимся на городской площади и имеем
        // возможность посетить лекаря, оружейный магазин,
        // посмотреть наши характеристики или выйти из игры.

        while (selection != 4)  // Пока selection не равно 4
        {
                printf("\n");
                printf("Town Square. You can do the following:\n");
                printf("1 - View Player info\n");
                printf("2 - Enter Healer's House\n");
                printf("3 - Enter Weapon Shop\n");
                printf("4 - Quit to main menu\n\n");
                printf("Choose a number: ");

                scanf("%d", &selection);

                rewind(stdin);


                switch(selection)
                {
                        case 1:
                                ShowPlayerInfo(*Player);
                                break;
                        case 2:
                                // Лекарь на сей раз будет дома
                                HealerHouse(Player);
                                break;
                        case 3:
                                // Магазин наконец-то открылся!
                                WeaponShop(Player, &ShopItem);
                                break;
                        case 4:
                                break;
                        default:
                                printf("Wrong choise\n\n");
                                break;
                }

                                printf("\n\n");

        } // конец цикла while(selection != 4)
}





// ****************** MAIN MENU ************************
// Функция главного меню

// Оно тоже изменилось - теперь можно будет продолжать
// игру, если игрок уже создан
int MainMenu()
{
        // Создаем переменную Player, с которой
        // будут работать наши функции

        PLAYER Player;


        int selection = 0;
        int player_exist = 0;

        Player.player_class = 0;

        while (selection != 3)
        {
                printf("\n\n*** GAME MENU *** \n");
                printf("1 - New Game\n");
                printf("2 - Continue Game\n");
                printf("3 - Quit\n");
                printf("***************** \n\n");
                printf("Choose a number: ");

                scanf("%d", &selection);

                rewind(stdin);


                switch(selection)
                {
                case 1: // Новая игра

                        // Здесь мы вызвали нашу функцию для создания игрока.
                        // Обратите внимание! Мы передаем в функцию указатель -
                        // адрес переменной Player в памяти, используя &.
                        CreatePlayer(&Player);
                        ShowPlayerInfo(Player);
                        PlayGame(&Player);
                        player_exist = 1;               // Игрок был создан
                        break;

                case 2: // Продолжить игру
                        if (player_exist)
                                PlayGame(&Player);
                        else
                                printf("No current game exist\n");
                        break;

                case 3:
                        return 0;
                        break;

                }

        }       return 1; // Игра НЕ окончена

}



// ****************** HEALER HOUSE *********************
// Функция "дом лекаря"

void HealerHouse(PLAYER *Player)
{
        int pay = 1;

        printf("\n");
        printf("You enter healer's house. Healer says:\n");
        printf("Hello, %s! I can heal you.\n", Player->name);
        printf("1 health for 1 gold.\n");

        while (pay)
        {
                printf("How much gold do you pay? (0 - exit)\n");
                scanf("%d", &pay);
                rewind(stdin);

                if (Player->gold >= pay)
                {
                        Player->gold = Player->gold - pay;
                        Player->health = Player->health + pay;
                }
                else
                printf("Not enough gold!\n");

                printf("Gold: %d\nHealth: %d\n\n", Player->gold, Player->health);

        }

}




// ****************** WEAPON SHOP **********************
// Функция "оружейный магазин"

void WeaponShop(PLAYER *Player, ITEM *ShopItem)

{

        int buy = 1; // Хотим ли мы купить предмет?


        printf("\n");
        printf("You enter weapon shop. You can buy:\n");


        while (buy)
        {
                printf("%s, price: %d\n", ShopItem->item_name, ShopItem->price);
                printf("1 - buy, 0 - exit\n");
                scanf("%d", &buy);
                rewind(stdin);

                if (buy)
                {
                        if (Player->gold >= ShopItem->price)
                        {
                                // Вычитаем стоимость из наших денег
                                Player->gold = Player->gold - ShopItem->price;
                                // Отдаем предмет игроку
                                Player->Wear_item = *ShopItem;

                                // Обнуляем предмет в магазине
                                sprintf(ShopItem->item_name, "%s", "none");
                                ShopItem->price = 0;
                                ShopItem->attack_modifier = 0;
                                ShopItem->defence_modifier = 0;
                                ShopItem->magic_modifier = 0;
                                printf("Item purchased.\n");
                        }
                                else
                                printf("Not enough gold!\n");
                }

        }

}






// Опыт переписки показывает, что есть один факт, который
// не так уж и очевиден для новичков. Вот он:

// *** Программа начинает выполняться отсюда! ***

void main()
{

        // Эта переменная понадобится для работы игрового цикла
        // и будет определять, окончена ли игра
        int game_not_over = 1;




        // Игровой цикл
        while (game_not_over)
        {
                game_not_over = MainMenu();

        } // конец цикла while(game_not_over)


}       // Конец программы.




-----------------------------------------

Ну что же, мне кажется, что на разбор этой программы
у новичков уйдет никак не меньше недели. Увы, попроще
сделать не получилось. Зато эту программу уже можно
назвать мини-игрой. И обратите внимание на игровой
цикл! Именно таким он и должен быть - простым.

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

Зато, если вы успешно разберетесь с этим выпуском,
то могу вас поздравить - вы будете знать большинство
азов программирования на С. Конечно, еще не разобраны
ни препроцессор, ни файлы, не освещены достаточно
подробно массивы со строками, но писать консольные
программы вы уже сможете.

А я уже подумываю о том, чтобы двигаться дальше.
(Конечно же, после того, как мы завершим более-менее
нашу консольную игру). Пока даже не знаю, в каком
направлении продолжить рассылку - то ли сделать плавный
переход к С++, то ли начать работу с графикой...
В любом случае будет интересно, и, на первых порах,
возможно даже попроще, чем в текущих выпусках.

Так что до встречи и удачи в разборе выпуска!
Как всегда, постараюсь ответить на все ваши вопросы.


--------------------------------------------------------------

Архив рассылки вы найдете по адресам http://subscribe.ru/catalog/comp.games.gamecoder
и http://www.gamecoder.nm.ru/subscribe.htm.


Евгений Казеко.
kazeko@list.ru
www.gamecoder.nm.ru
-----------------------------
Рассылка "Создание компьютерных игр", выпуск 9.
Выпускается еженедельно по средам.


http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу

В избранное