Школа создателей компьютерных игр

BannerDrive.ru
[Главная] [С чего начать?] [Дистанционное обучение] [Статьи] [Обучалки] [Книги] [Софт] [Форум] [Ссылки] [О сайте]

Движение курсора клавиатурой и мышью



Подпишитесь на рассылку "Создание компьютерных игр"

Рассылки Subscribe.Ru
Создание компьютерных игр

Рассылка выходит раз в месяц.


Понравился сайт? Узнайте, как помочь сайту.


Рекомендуемые книги


Андре Ламот.
Программирование трехмерных игр для Windows. Советы профессионала по трехмерной графике и растеризации


Андре Ламот.
Программирование игр для Windows. Советы профессионала


Проголосуйте за сайт в рейтинге GameTop!
(нажмите на кнопку рейтинга)

GameTop - рейтинг игровых ресурсов. Портал Rolemancer (www.rolemancer.ru)

Не забывайте, что результаты рейтинга обновляются раз в неделю. Пожалуйста, голосуйте почаще!


Статистика посещаемости

Rambler's Top100

Введение в консольные функции | Все обучалки раздела | Цветной текст

Описание

Объясняется, как управлять символом на экране, используя мышь и клавиатуру.

Программа использует консольные функции для реализации движения символа по экрану с помощью клавиш со стрелками или мыши.

Скачать обучалку (Visual C++ 6)

Работа программы

Исходный код

Файл MoveMan.c

//            - "Talk to me like I'm a 3 year old!" Programming Lessons -                  
//                                                                                         
//            $Author:            Ben Humphrey      digiben@gametutorials.com              
//                                                                                         
//            $Program:            MoveMan                                                 
//                                                                                         
//            $Description:      Using the arrow keys and mouse to move a character around 
//                                                                                         
//            $Date:                  6/20/00                                              
//                                                                                         
// Перевод © 2004 Евгений Казеко
// www.gamecoder.kazeko.com
// evgeniy@kazeko.com


// Мы подключаем наш заголовочный файл, в котором содержатся 
// все остальные заголовки, функции и структуры

#include "MoveMan.h"       


// Создаем переменные HANDLE для ввода и вывода. Мы не помещаем их в main(), потому что
// мы хотим, чтобы эти переменные были глобальные. Это значит, все функции, а не только main, смогут
// использовать их.
HANDLE hInput, hOutput;     

// Определяем действия, выполняемые функцией.
void DrawPlayer(PLAYER Player, int Draw)     
{
      if(Draw)   // Если "Draw" == DRAW (в заголовке определено #define DRAW 1)
      {                                                                        
            // Устанавливаем положение курсора соответствующее положению игрока
            SetConsoleCursorPosition(hOutput, Player.Position);

            // Печатаем на экране символ '@' туда, где находится курсор
            printf("@");                                                
      }
      else     // Если "Draw" == ERASE, значит мы хотим удалить символ
      {                                                                        
            // Мы хотим затереть символ пробелом ' ', поэтому мы устанавливаем курсор на положение игрока.
            SetConsoleCursorPosition(hOutput, Player.Position);
            printf(" ");

            // И теперь, когда мы печатаем пробел, он затрет последний символ '@'.
            // Если мы не будем стирать символ игрока, получится, что как будто бы он оставляет за собой след.
      }
}


/*      Здесь мы создаем функцию MovePlayer(). 
      INPUT_RECORD это наша структура, в которой содержится информация о буфере ввода 
     (о том, что делает пользователь).
      Мы также создаем переменную типа DWORD для совместимости с ReadConsoleInput(). 
     (В переменной будет храниться количество произошедших событий).
      Обратите внимание, что мы помещаем эти переменные в данную функцию, 
      потому что они не будут использоваться другими функциями.
  */
void MovePlayer(PLAYER *Player)                                    
{                                                                              
      INPUT_RECORD InputRecord;                                    
      DWORD Events=0;                                                      

      // Читаем действия пользователя и сохраняем информацию в структуру InputRecord.
      ReadConsoleInput(hInput, &InputRecord, 1, &Events);

      /*      Ниже мы используем переменную "EventType". По этой переменной можно определить, было ли событие
            связано с мышью или с клавиатурой. (Использовали ли мы мышь или клавиатуру).
            MOUSE_EVENT определено где-то в Windows. Мы проверяем, была ли использована мышь.
      */
      if(InputRecord.EventType == MOUSE_EVENT)            
      {                                                                  
            /*      Если мы использовали мышь, проверяем, была ли нажата левая клавиша мыши. Значение 
                  FROM_LEFT_1ST_BUTTON_PRESSED также определено где-то в Windows.
                  В предыдущей обучалке мы использовали "...Event.KeyEvent....". В этот раз мы проверяем
                  информацию о мыши. Внутри структуры "MouseEvent" есть переменные, которые содержат данные
                  о нажатых клавишах мыши, положении мыши, а также была ли при этом нажата клавиша CONTROL.
                  Есть и другие переменные.
                  Проверим, была ли нажата левая клавиша мыши.
            */
            if(InputRecord.Event.MouseEvent.dwButtonState == FROM_LEFT_1ST_BUTTON_PRESSED)
            {                                                                  
                  DrawPlayer(*Player, ERASE);                        
                  
                  /*  Здесь мы вызываем функцию DrawPlayer(), передавая в нее ERASE. Перед тем как нарисовать
                        новый символ, мы хотим удалить старый.
                        Обратите внимание на "*" перед "Player". Поскольку мы уже передали в данную функцию
                        адрес структуры "Player" в памяти, "Player" является указателем.
                        Посмотрите на определение функции DrawPlayer(): DrawPlayer(PLAYER Player, int Draw);
                        Эта функция не запрашивает адрес "Player" (PLAYER Player). Ей нужны данные из структуры,
                        а не ее адрес. Поэтому мы и помещаем впереди указателя символ "*", давая компилятору
                        понять, что мы передаем саму переменную.

                        Если мы нажали левую клавишу мыши, поместим символ игрока туда, где был щелчок мышью.
                        Видите стрелку, указывающую на "Position.X"? Когда у вас указатель на структуру, которой
                        является "Player", и мы хотим получить доступ к ее переменным, мы должны использовать
                        стрелку вместо точки. Это только для указателей. Обратите внимание, что стрелка не
                        нужна для "Position". "Position" это не указатель, "Player" - указатель. Это может
                        показаться сложным, но когда вы больше будете использовать указатели, все станет
                        понятнее.

                        Устанавливаем игрока туда, где была нажата мышь. Положение места, где она была нажата,
                        хранится в InputRecord.Event.MouseEvent.dwMousePosition. 
                        dwMousePosition имеет тип COORD. Поэтому для доступа к значению Х, используем ".X"
                        после dwMousePosition. То же и для Y.
                  */
                        
                  Player->Position.X = InputRecord.Event.MouseEvent.dwMousePosition.X;
                  Player->Position.Y = InputRecord.Event.MouseEvent.dwMousePosition.Y;

                  // Здесь мы рисуем символ на новом положении, 
                  поэтому мы вновь вызываем DrawPlayer() с параметром DRAW.
                  DrawPlayer(*Player, DRAW);                  
            }
      }

      /*      Ниже мы проверяем, была ли нажата клавиша, и берем только нажатие "вниз".
            Чтобы убедиться, что клавиша была только нажата (вниз) а не отжата (вверх),      мы проверяем bKeyDown.
            Это не нужно для Windows 95 и 98, но системы, основанные на NT обрабатывают консольные
            функции по-другому. В Windows 95 и 98 отслеживается только нажатие клавиши, в то время как
            системы NT отслеживают и нажатие и отжатие клавиши. В результате игрок будет двигаться
            каждый раз на 2 позиции, поэтому нам нужно удостовериться, что мы перемещаем игрока только
            при нажатии клавиши (вниз). Мы используем переменную bKeyDown, и проверяем ее на истинность.
      */
      if(InputRecord.EventType == KEY_EVENT && InputRecord.Event.KeyEvent.bKeyDown)
      {                                                                        
            // Проверяем, была ли нажата стрелка вправо.          
            if(InputRecord.Event.KeyEvent.wVirtualKeyCode == VK_RIGHT)
            {                                                                  
                  DrawPlayer(*Player, ERASE);   // Удаляем символ на текущей позиции, передавая в функцию ERASE.
                  Player->Position.X++;      // Увеличиваем значение X, потому что мы двигаемся вправо.
                  DrawPlayer(*Player, DRAW);    // Рисуем символ игрока на новой позиции, передавая DRAW.
            }                                                                  
            // Проверяем, была ли нажата стрелка влево.
            else if(InputRecord.Event.KeyEvent.wVirtualKeyCode == VK_LEFT)
            {                                                                  
                  DrawPlayer(*Player, ERASE);                        
                  Player->Position.X--;   // Уменьшаем значение X, потому что мы двигаемся влево.
                  DrawPlayer(*Player, DRAW);                        
            }                                                                  
            // Проверяем, была ли нажата стрелка вверх.
            else if(InputRecord.Event.KeyEvent.wVirtualKeyCode == VK_UP)
            {                                                                  
                  DrawPlayer(*Player, ERASE);                        
                  Player->Position.Y--;     // Уменьшаем значение Y, так как мы двигаемся вверх.
                  DrawPlayer(*Player, DRAW);                        
            }                                                                  
            // Проверяем, была ли нажата стрелка вниз.
            else if(InputRecord.Event.KeyEvent.wVirtualKeyCode == VK_DOWN)
            {                                                                  
                  DrawPlayer(*Player, ERASE);                        
                  Player->Position.Y++;   // Увеличиваем значение Y, так как мы двигаемся вниз.
                  DrawPlayer(*Player, DRAW);                        
            }                                                                  
      }      
                                                                              
      FlushConsoleInputBuffer(hInput);    // Очищаем буфер ввода.
}


void main()       
{                 
      // Объявляем переменную типа PLAYER. Взгляните на MoveMan.h, 
      чтобы увидеть созданную нами структуру.
      PLAYER Player;                                                      
      
      // Инициализируем переменные для буфера ввода и вывода.
      hInput = GetStdHandle(STD_INPUT_HANDLE);            
      hOutput = GetStdHandle(STD_OUTPUT_HANDLE);            

      SetConsoleMode(hInput, ENABLE_PROCESSED_INPUT | ENABLE_MOUSE_INPUT);

      /*      Устанавливаем консольный режим, чтобы был возможет ввод мышью и выход по CONTROL-C.
            Теперь мы сможем использовать мышь. Видите оператор "|" OR? Этот оператор
            производит побитовое объединение двух чисел. Это бинарная операция, которая устанавливает
            нулевые биты в 1.

            Небольшой урок по двоичной системе. (Не ждите, что вы сразу все поймете).
            Число 8 в двоичной системе записывается так: "1000".
            Число 2 в двоичной системе записывается так: "0010".
            Откуда я взял эти числа? Эти числа читаются справа налево.
            0 = "0000" 1 = "0001" 2 = "0010" 4 = "0100" and 8 = "1000"
            Если мы сложим 2 "0010" + 1 "0001" = "0011" -- мы получим 3.

            Вот что делает оператор OR.

                              "8:  "1000"
            OR                  "2:  "0010"
            получаем      "10: "1010"

            Итак, 8 OR 2 становится 10, потому что мы объединяем биты, если на одинаковых позициях стоят 0 и 1.

                              8: "1000"
                        OR      8: "1000"
                        =      8: "1000:

            8 OR 8 так и остается 8, потому что у них у обоих стоит 1 в первой позиции.
            В основном этот оператор сравнивает 2 числа и устанавливает нулевые биты на одинаковых позициях в 1,
            если они равны нулю.
            Не расстраивайтесь, если сразу не все понятно. Посмотрите обучалку "Бинарные операторы".

            Пока что запомните, что в данном случае этот оператор позволяет вводить 2 опции.
            То что делает функция - берет маску, по которой проверяется, какие биты установлены в 1.
      */
      


      Player.Position.X = SCREEN_WIDTH / 2;           // Мы хотим начать в центре экрана.
      Player.Position.Y = SCREEN_HEIGHT / 2;         // Поэтому мы делим ширину и высоту экрана на 2.


      // Вызываем функцию для установки положения курсора согласно данным струкруры Player.
      SetConsoleCursorPosition(hOutput, Player.Position);
      
      ////////////////////////////// Игровой цикл ////////////////////////////////
      
      /*      Создадим бесконечный цикл, для того, чтобы проверять ввод с клавиатуры или мыши.
            Условие while(1) всегда истинно, поэтому цикл не остановится, пока мы не закроем окно или
            не нажмем CONTROL-C.
      */
      while(1)                                                            
      {                                    
            MovePlayer(&Player);      // Вызываем нашу функцию движения игрока.
      }
      /*      Не правда ли, наш игровой цикл хорошо смотрится? Всего один вызов функции.
            Именно так и должен выглядеть игровой цикл. Только вызовы функций. Может быть еще пара
            условных операторов. Идея заключается в том, что этот цикл должен быть простым. Мы
            создаем функции, контролирующие ход игры, и затем вызываем их в игровом цикле.
            Позже вы увидите "while(GameIsRunning)". Переменную "GameIsRunning" устанавливают в 1 (истинно), 
            затем, если пользователь нажмет VK_ESCAPE, переменная устанавливается в 0, что приводит к
            завершению цикла и игры. Пока что мы просто используем CONTROL-C для выхода.
      */
}    // Конец программы.

/*      *ПРОВЕРКА ОШИБОК*

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

      Проверить эту программу легко. У вас есть координаты x и y, верно? Итак, мы знаем что
      наше окно имеет ширину 80 и высоту 25. Все что нам нужно сделать - это увеличивать значение х,
      (то есть Position.x), только если х меньше 80, и уменьшать х, если он больше нуля.
      То же и для значения y. Уменьшайте y, когда  он больше 0, и увеличивайте, когда он меньше 25.

      Вы скажете - а что если консольное окно не всегда имеет размер 80 на 25?. Есть хорошая
      функция GetConsoleScreenBufferInfo(). В нее нужно передать буфер вывода (HANDLE hOutput) и
      переменную типа PCONSOLE_SCREEN_BUFFER_INFO (PCONSOLE_SCREEN_BUFFER_INFO pConsoleInfo).
      Это структура, содержащая переменные, в которых хранится информация о консольном окне,
      начиная с атрибутов окна и заканчивая его размерами.


      * Проблемы с WinNT *

      Вам следует помнить, что код для мыши не работает на некоторый операционных системах, таких
      как Windows 2000, до тех пор, пока вы не отключите для окна QuickEdit и Insert Mode. Чтобы 
      сделать это, запустите программу и щелкните на верхнем левом углу окна. Появится меню, 
      зайдите в Свойства. Убедитесь, что у вас выбраны Опции и посмотрите на Редактировать опции.
      Отключите Быстрое Редактирование и Режим Вставки. Таким образом программа не подумает, что
      вы хотите скопировать текст из окна. Затем, нажмите Сохранить и убедитесь, что сохранение
      произойдет для всех заголовков с таким именем. Закройте программу и запустите вновь. Она
      должна заработать.
  */
// © 2001 GameTutorials
// Перевод © 2004 Евгений Казеко
// www.gamecoder.kazeko.com
// evgeniy@kazeko.com

Файл MoveMan.h
#include <windows.h> 
#include <stdio.h> 
#include <stdlib.h>

// Перевод © 2004 Евгений Казеко
// www.gamecoder.kazeko.com
// evgeniy@kazeko.com

#define SCREEN_WIDTH  79     
#define SCREEN_HEIGHT 24      

/*      Мы определили ширину и высоту экрана, для того, чтобы программу было легче читать.
      В обычном DOS окне допускается 80 символов ширины. Так как верхний левый угол имеет координаты (0,0),
      мы вычитаем единицу из ширины и высоты, что дает нам нижний правый угол (79, 24). Нам не пришлось
      бы делать этого, если бы верхний левый угол начинался с (1, 1).
*/


// Эти определения используются для передачи в функцию DrawPlayer(), чтобы указать, рисуем ли мы или стираем.
#define ERASE 0                                                            
#define DRAW  1                  


typedef struct _PLAYER {                                          
      COORD      Position;                                          
      int            Direction;     
} PLAYER;                                          

/*      Мы создали нашу собственную структуру.
      Мы хотим записывать данные об игроке, такие как положение и направление.
      Поэтому мы создаем структуру, содержащую эту информацию.
      Мы могли бы создать отдельные переменные, но когда наша структура станет больше
      (например, можно добавить магию, здоровье, оружие, золото и т.д.), нам не нужно будет
      иметь дело с таким количеством отдельных переменных, и, что более важно, имея
      структуру, мы можем передать в функцию всего одну переменную, содержащую всю информацию
      об игроке, а не 20.

      Позвольте объяснить синтаксис. "typedef" означает определение типа. Помните, как мы
      пишем "int num"? Используя определение типа, мы сможем написать "PLAYER Player". Это наш
      собственный тип переменных.

      Внизу определения вы увидите "} PLAYER". То, что мы помещаем после скобки, является именем
      структуры. Если мы напишем "} PlayerData", нам нужно будет писать "PlayerData Player", чтобы
      объявить переменную в программе (как "int Player").
      Если у нас не стояло бы typedef перед определением структуры, мы должны бы были написать 
      "struct _PLAYER Player". Если мы определяем тип, нам никогда не понадобится "_PLAYER", это нужно
      только для структуры. Если в коде будет ошибка, компилятор сошлется на "_PLAYER", но мы никогда
      не будем ссылаться на это. В основном, определение типа делает код более ясным. Подчеркивание перед
      именем использовать не обязательно, также, как и заглавные буквы. Но большинство пишут именно так.
  */


void DrawPlayer(PLAYER Player, int Draw);            

/*      Мы объявили нашу функцию рисования игрока. Мы передаем в нее структуру PLAYER.
      Если бы у нас не было структуры, наша функция выглядела бы вот так:
      "void DrawPlayer(COORD Position, int Direction, int Draw);"
      И представьте, если бы у нас было 20 переменных. Даже хотя в этой функции нам и не нужна переменная
      direction, вы видите как используется структура.

      Вам не обязательно было называть переменную "Player" - вы могли бы назвать ее "PLAYER ChickenLegs"
      или как вы хотите. Это имя используется только внутри функции, оно локально. main не увидит его.
*/

void MovePlayer(PLAYER *Player);

/*      Эта функция будет проверять пользовательский ввод.
      Видите символ * перед "Player"? Это означает, что функции нужен адрес переменной.
      Вспомните, как в функции scanf() мы ставили & в конце последнего параметра.
      Этот символ дает функции адрес переменной в памяти. Если вы передаете что-либо
      в функцию, она создает собственную копию переменной. Указывая функции адрес
      переменной с помощью знака &, мы позволяем ей использовать его, вместо создания
      новой переменной. И тогда, если переменная изменяется внутри функции, она также изменится
      вне функции.
*/


/*      ПРИМЕРЫ СО СТРУКТУРАМИ

      PLAYER Player;                                                

      Мы определили переменную - структуру для игрока. Мы могли бы назвать ее PlayerData или как угодно.
      Обычно я использую маленькие буквы с большой заглавной.
      

      Player.Direction = NORTH;            
      
      Мы установили направление игрока NORTH

      Player.Position.X = 5;                                    
      
      Мы установили координату Х игрока равную 5. Видите? Просто ставим точку после структуры и указываем
      переменную внутри структуры.
            
      Вы не можете сделать следующее:  Player.Position = 5;      
      Это не будет работать. Эта запись говорит, что мы хотим приравнять структуру числу 5. Это бессмыслено.
      
      Вот еще несколько примеров - мы можем проделывать операции сложения и вычитания, как и с обычными
      переменными.
                  
      Увеличиваем координату Х на 1.
        
      Player.Position.X++;      
      
      Уменьшаем координату Y на 1.

      Player.Position.Y--;                                                

      Операторы ниже выполняют то же самое - прибавляют 5 к Х.

      Player.Position.X += 5;                                          
      Player.Position.X = Player.Position.X + 5;            

      Знак "+=" позволяет не вводить повторно "Player.Position.X". Есть также операторы "-=" "/=" "*="
      для вычитания, деления и умножения.
                                                                        
      Со структурами можно делать все то же, что и с переменными. Взгляните:

      if(Player.Position.X > 20)                                    
      {                                                                  
            Выполнить действия...                              
      }




      ПРИМЕРЫ С УКАЗАЛЕТЯМИ


      Создадим тестовую переменную

      int num = 2;

      Передадим переменную в функцию ниже

      ChangeNumber(num);

      Так как мы не поставим * впереди number, функцией будет создана локальная копия этой переменной.

      void ChangeNumber(int number)                        
      {
            number = number + 5                                    
      }                                                                        
                                                                        
      Число 5 было прибавлено к локальной копии, но не к передаваемой в качестве параметра переменной.
      num все еще равняется 2.

      А теперь то же самое с указателем.

      int num = 2;

      ChangeNumber(&num);                                          
      
      Мы передаем переменную в функцию, используя &, чтобы передать функции адрес переменной в памяти.
      
      void ChangeNumber(int *number)                              
      {
            number = number + 5                                          
      }

      Так как мы поставили * перед number, локальная копия не создается, вместо этого используется переменная num.
      И теперь num будет равняться 7.

      В основном мы используем указатели, если мы хотим, чтобы функция могла изменять передаваемые в нее
      переменные. Если нам нужна только информация, которая содержится в переменных, нам не нужен указатель.
      Как например в функции DrawPlayer() нам нужно положение игрока, но мы его не меняем.
      А в функции MovePlayer() мы изменяем положение игрока, поэтому необходимо использовать указатели.
*/

// Перевод © 2004 Евгений Казеко
// www.gamecoder.kazeko.com
// evgeniy@kazeko.com

Скачать обучалку (Visual C++ 6)

Введение в консольные функции | Все обучалки раздела | Цветной текст

[Главная] [С чего начать?] [Дистанционное обучение] [Статьи] [Обучалки] [Книги] [Софт] [Форум] [Ссылки] [О сайте]

Copyright © 2003-2005 Евгений Казеко. Все права защищены. E-mail: evgeniy@kazeko.com

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