Текст
                    GAME V.
PROGRAMMING
программирпваАие

УДК [004 388.4 004 431(075.4) ББК32 973 26-018я78-1+77.056с.я78-1 СЗЗ Сети, Маниш. СЗЗ Game Programming = Программирование ИГР : [пер. с англ.: учеб, пособие] / Манит Сети. — Second Edition. — Москва: 100 книг, 2007. - 384 с.: ил. + [1] CD-ROM. - ISBN 5-89392-169-0 Агентство СИ? РГБ Эта книга научит вас основным приемам программирования собственных игр. Вы научитесь всему, начиная от создания графических компонентов и их анимации, до включения в игру музыки и звуков. В книге даже рассказано, как включить в игру элементы искусственного интеллекта! Чтобы начать чтение книги, от вас не потребуется никакой стартовой подготовки - все, что вам нужно, вы узнаете прямо из книги. По завершению чтения, вы, с помощью автора, создадите собственную компьютерную игру! 41 обы облегчить вам труд, к книге приложен компакт-диск, в котором вы найдете все программы, необходимые для создания собственной игры, исходные данные для исполнения практических упражнений и множество вещей, полезных для программирования игр - музыку, звуки и высококачественную графику. Научный редактор Перевод Выпускающий редактор Редактор-координатор Дизайн обложки Корректор Верстка А.Г. Жадаев В. В Карпюк, Н.М. Колесникова. В А. Чернела И. Г Колмыкова А.Д. Куксова Борис Клюйко С.Л Крючкова В.Г Баукова Copyright © by Thomson Course Technology Thomson Learning Company All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage or reineval system without written permission from Thomson Course Technology PTR, except for inclusion of brief quotations in e review Russian language edition published by Triumph Publishing (ООО "Издательство Триумф") Copyright © 2006 Все права защищены Никакая часть данной книги не может быть переделана или изменена в какой-либо форме, электронной или механической, включая ксерокопирование, запись на носители информации без разрешения Thomson Course Technology PTR Русскоязычная версия, изданная ООО' Издательстве Триумф" Все права защищены © ООО "Издательство Триумф", 2006 ISBN 5-89392-169-0 © Обложка, верстка и оформление, "100 книг», 2007 ISBN 1 -59200-834-8 (амер.) © ООО «Издательство ТРИУМФ», 2007
Краткое содержание Часть 1. Основы Бейсика .....................19 Главе 1= Нвчинаем работу................... 21 Глава 2. Изучаем Бейсик .......................38 Глава 3. Циклы, функции, массивы и типы .....57 Глава 4. Фактор стиля .........................108 Часть 2. Введение в графические _________средства ..........................117 Глава 5. Начинаем работать с графикой ......119 Глава 6. Переключение страниц и работа с пикселами........................141 Глава 7. Основы программирования изображений ......186 Глава 8. Анимация .................. ......228 Глава 9. Обнаружение конфликтов.............249 Часть 3. Завершение работы .................275 Глава 10. Обработка ввода ................ 277 Глава 11. Звуки и музыка ......................316 Глава 12. Искусственный разум ..............338 Глава 13. Последний рубеж: игра «Захватчики!!!.358 Насть 4. Приложения ........................369 Приложение А. Справочник кодов клавиш ......371 Приложение В. Полезные ссылки...............379 Приложение С. Содержание компакт-диска......381
Содержание Благодарности............................................13 Об авторе ....................................... 14 Введение...........................................15 О чем эта книга? ..................................15 Кто вы? ...........................................16 Вы уже готовы?.....................................17 Часть 1. Основы Бейсика............................19 Глава 1. Начинаем работу...................................21 Краткая история Бейсика.................................... 21 Установка BlitzPlus .....................................22 Разбираемся с IDE....................................... 22 Окна и панели...........................................24 Панели инструментов.....................................24 Меню ................................................... 25 Первая игра: KONG ..........................................25 Компиляция кода.........................................34 Заключение................................................. 36 Главе 2. Изучаем Бейсик....................................38 Здравствуй, мир!...........................................38 Переменные ................................................42 Объявление переменных .................................. 42 Использование переменных................................42 Ввод данных ............................................... 45 Условные операторы.........................................46 Истина и ложь...........................................46 If...Then...............................................47 If...Then... Else.......................................48
Select...Case.............................................50 1огические операторы.........................................51 Оператор NOT.............................................. 52 Команда Goto......................................•.......52 Гекстовая игра-угадайка......................................54 Заключение...................................................56 Глава 3. Циклы, функции, массивы и типы......................57 Циклы .......................................................57 For...Next.... ............................................58 While...Wend............................-.................62 Repeat...Until.......................................... 64 Функции......................................................66 Область действия......................................... 68 Когда использовать функции ......................... 72 Массивы .....................................................73 Многомерные массивы.....................................м..78 Типы ........................................................81 Системы координат .................................. 89 For... Each... Next.................-.....................90 Собираем все вместе: Textanoid! .............................92 Заключение................................................ 106 Глава 4. Фактор стиля.......................................108 Разработка стиля............................................108 Пустое пространство и отступы ...........................109 Комментарии.................................................110 Комментарии перед программой.............................112 Комментарии основной части программы ....................113 Коммента ри и функций ................................-....113 Имена функций и переменных..................................114 Имена.................................................. 114 Формат имен ........................................ ...114
Заключение...............................115 Часть 2. Введение в графические средства.................................117 Глава 5. Начинаем работать с графикой....................................119 Создание графического окна...............................................119 Width и Height........................................................120 Color Depth...........................................................121 [Mode] ...............................................................122 Изображения......................................................... 125 Loadimage................................................. ..........125 Drawlmage.............................................................127 Handle....................................................... .. ..127 Параметры X и Y.......................................................127 [Frame] ..............................................................................................129 Createlmage ..........................................................129 Maskimage ............................................................133 Цвета................................................................ 135 RGB...................................................................135 Color.................................................................137 Cis и CisColor ...................................................... 140 Заключение............................................................. 140 Глава 6. Переключение страниц и работа с пикселами ...................................................141 Переключение страниц ....................................................141 Буферы ..................................................................144 SetBuffer ...............................................................145 SetBuffer buffer.........................................................145 FrontBufferf)................................................ ... .. .. . .................... 146 BackBuffer()......................................... . ..147 Буферы изображения.................................................. 150 Createlmage ..........................................................151 SaveBuffer.......................................................... 156
LoadBufferQ...........................................159 Freelmage.............................................161 Блокировка и разблокировка буферов.......................162 Lock/Unlock...........................................163 ReadPixel( )/ReadPixelFast()..........................164 WritePixel/WritePixelFast.............................167 Применение буферов: программа рисования .................172 Инициализация.........................................174 Главный цикл..........................................176 Функции...............................................178 Заключение...............................................185 Глава 7. Основы программирования изображений ..............................................186 Преобразования ..........................................186 Сдвиг.................................................186 Масштабирование ...................................-..191 Пропорциональность? Что это такое?....................191 Масштабирование форм..................................192 Масштабирование изображений...........................206 Вращение..............................................212 Смешение фонов...........................................221 Команды TileBlock и Tilelmage.........................221 Заключение...............................................227 Глава 8. Анимация........................................228 Использование растровых изображений в анимации...........228 Создание растровых изображений........................236 Отображение движения .................................240 Заключение...............................................248 Глава 9. Обнаружение конфликтов .........................249 Понятие конфликта .......................................249 Ограничивающие окружности ...............................253 Расстояние между точками........................ ....254 Радиусы ..............................................255 Ограничивающие рамки.....................................260 Неполные конфликты пикселов..............................267
Полные конфликты пикселов..................271 Итоги......................................273 Часть 3. Завершение работы.................275 Глава 10. Обработка ввода................................277 Поддержка клавиатуры ...............................„....277 Функция KeyDownQ.................................... 278 Функция KeyHitO.......................................285 Привязка мыши к экрану ..................................295 Отображение курсора мыши..............................295 Что это было? Обработка нажатий кнопок мыши. 299 Функция MouseDownf).................................. 300 Функция MouseHitf)....................................300 Среднее колесико мыши.................................310 Обработка ввода джойстика ...............................313 Итоги....................................................314 Глава 11. Звуки и музыка ................................316 Звук.....................................................316 Слушайте внимательно - воспроизведение звуков.........319 Функция Soundpitch: я - дьявол или бурундук?..........323 Функция SoundVolume ..................................326 Функция SoundPan .....................................328 Музыка...................................................332 Канвлы и функция PlayMusic()..........................332 Смешение с помощью цифровых аудиоканалов..............334 Итоги....................................................337 Глава 12. Искусственный разум............................338 Случайные числа..........................................338 Создание таймера функции MilliSecs()...................-.343 Погоня и маневрирование..................................350 Погоня ...............................................350 Маневри рование.......................................355 Заключение...............................................357
Глава 13. Последний рубеж: игра «Захватчики!!!»..358 Давайте разберемся: планирование игры ...............358 Переменные, функции и типы в игре «Захватчики!!!» ...361 Игра Захватчики!!! ..................................365 Эпилог....................................-..........368 Часть 4. Приложения .................................369 Приложение А. Справочник кодов клавиш.............................371 Приложение В. Полезные ссылки.....................................379 Ссылки на программу Blitz Basic ....................379 Ссылки по общему программированию игр...............380 Приложение С. Содержание компакт-диска............................381 Каталог Source................................... 382 Каталог Art .......... ..........................382 Каталог Sounds................ ««...............382 Каталог Games................................... 382 Каталог Programs .................................383
Благодарности О боже, нужно поблагодарить столько людей! Такое чувство, будто я получаю Оскара: я боюсь, что начнется музыка и меня проводят со сцены. В первую очередь, спасибо Андрэ ЛаМот (Andre LaMoth) за то, что дал мне шанс и вытащил меня на обед. Когда-нибудь я отплачу тем же. Благодарю всех в Premier Press: моих редакторов Дженни Дэвидсон (Jenny Davidson), Кезиа Эндсли (Kezia Endsley). Эми Смит (Emi Smith) и Брэндона Пентикаффа (Brandon Penticuff). Спасибо Адаму Хепворту (Adam Hepworth), который потратил много своего драгоценного времени, читая мой текст и исправляя множество ошибок. Всем моим братьям и сестрам, Рэчи (Rachi), которая предлагала свою помощь, даже находясь за оксаном, Нагине (Nagina), чья любовь и поддержка (и многочисленные телефонные звонки) помогли мне справиться, и Рамиту (Ramit), который дал мне пример для подражания и помогал мне во всем (и за то, что дал мне возможность отсрочить «наказание»). Спасибо за то, что вы здесь и даете мне свою поддержку, когда она нужна мне. Моим маме и папе: я не смот бы сделать это без вас. Я вас очень люблю! Наконец, спасибо Эдгару Л. Ибарра (Edgar L. Ibarra) (Feo) за его художественную работу и Томасу Стенбэку (Thomas Stenback) за работу над музыкой для компакт-диска. Эри Фслвдману (Ari Feldman) зато, что позволил использовать его библиотеку сайтов. А также благодарности за предоставление демоверсий программ для книги: Джейсону Брэйзеру (Jason Braiser) и Эдгару Ибаррэ (Edgar Ibarra) за программу Rockfall и Маркусу «Эйкону» Смиту (Marcus «Eikon» Smith) за программу Galaxiga. И всем остальным, чьи имена я забыл. Спасибо!
Об авторе т Манит Сети (Maneesh Sethi) — ученик средней школы из Калифорнии, собирающийся поступать в Стэндфордский университет в 2006 году. Маниш занимается Web-дизай-ном и разработкой с пятого класса. Он является основателем и ведущим дизайнером компании Standard Design, занимающейся проектированием Web-сайтов. Маниш давал советы по программированию в телепрограмме «Call for Help» на канале TechTV и в конференциях по программированию игр, таких как XGDX. Он является автором книг «Game Programming for Teens (First Edition)» (Программирование игр дня подростков, первое издание) и «Web Design for Teens» (Web-дизайн для подростков), опубликованных Course PTR, а также «How to Succeed as a Lazy Student» (Как преуспеть ленивому студенту). Кроме программирования игр, Маниш любит в них играть (а как же!), занимается спортом — теннисом и баскетболом и, конечно, любит поспать. На сайте www.maneeshsethi.com вы можете узнать о Манише больше, а заодно и о его отмеченных наградами футболках и разработанном им чекле для iPod, сделанном из носка.
Вторая часть посвящена графическом}’ интерфейсу шр. Здесь вы найдете сведения о смене цвета, загрузке и отображении рисунков, создании меняющегося фона и других подобных вещах. В третьей части освещены другие аспекты нршраммирования игр. Здесь читатель может узнать, как считывать данные с клаяиатуры, использовать звук и музыку и познакомиться с искусственным интеллектом. В этой части вы также сделаете то, к чему вас готовили на протяжении всей книги — создадите игру. В четвертую часть включены приложения к книге. Здесь вы найдете коды опроса клавиатуры, список Web-сайтов, на которых вы почерпнете дальнейшие сведения из этой области, и описание содержимого компакт-диска. Кто вы? Я думаю, вы можете «пвегигь на этот вопрос лучше, чем я. Но я могу сказать, каким я вижу читателя. Во-первых, могу предположить, что это либо подросток, интересующийся программированием игр, либо его родитель, заинтересованный в том, чтобы его ребенок изучал программирование игр. Вообще, все, что требуется, — это интерес. Эта книга создавалась не за тем. чтобы быть сложной. Она поможет ввести читателя в мир программирования игр. От вас чребустся очень немного. Все, что вам действительно понадобится, — базовые познания в математике, такие как сложение, вычитание, умножение и деление. Если вы знаете это. вы вполне подготовлены! К тому же, при необходимости вы можете нонроипъ своих родителей помочь вам. В книге встречаются некоторые простейшие алгебраические действия, но это случается редко, и в них можно без труда разобраться. Вам не нужно знать другие языки программирования. Но, конечно, если вы с ними знакомы, вам будет проще. Если вы знаете какие-то другие языки, вы также можете изучать эту книгу — опа учит как языку программирования, так и созданию игр. Если вы родитель или ребенок, который хочет изучать программирование, эта книга — то, что нужно, чтобы начать. Обычное программирование — сложный и утомительный предмет, но программирование игр позволяет создавать то, что приносит веселье. Помогите своему ребенку с программированием, пока он читает эту книгу. Вы оба не только изучите программирование, но, кто знает, может быть, это поможет укрепить ваши отношения? (Мои познания в психологии позволяют предположить такое.)
Вы уже готовы?.. Если вы все еще просматриваете эту книгу в магазине, уже пришло время взять се домой. Книжный магазин будет признателен, если вы ее купите (а я тем более!). Первая часть быстро обучит вас всем хитростям программирования при помощи BlitzPIus Итак, мы начинаем...
Часть 1 Основы Бейсика Глава 1 Начинаем работу.......................21 Глава 2 Изучаем Бейсик........................38 Глава 3 Циклы, функции, массивы и типы........57 Глава 4 Фактор стиля .........................108
Добро пожаловать в потрясающий мир программирования игр! Эта книга покажет вам все углы и закоулки видеоигр и научит вас разрабатывать собственные игры. Программирование игр - обширная тема, и мы постараемся побыстрее пройти скучный материал, чтобы добра! вся до настоящего веселья. Другими словами, начнем прямо сейчас! Самый простой язык для программирования игр (по крайней мере, я так считаю) -Бейсик (BASIC). Акроним BASIC расшифровывается как Beginner's All Purpose Symbolic Instruction Code, что в переводе означает «всецелевой символический код инструкций для начинающих», однако на самом деле это не важно. Бейсик очень прост для написания и понимания, он создан на основе человеческого языка (в качестве команд используются слова, а не просто числа), так что, если вы понимаете английский язык, вы не встретите особых трудностей. При изучении этой книги мы будем использовать программу BlitzPIus, которая позволяет применять модифицированную версию языка программирования Бейсик. Начнем с краткой истории Бейсика - ведь надо же с чего-то начинать.
ГЛАВА 1 Начинаем работу Краткая история Бейсика Язык программирования Бейсик разработан в 1964 г. Джоном Кемепи (John Кетену) и Томасом Курцем (Thomas Kunz) в Дартмутском колледже (Dartmouth College). Он задумывался как очень простой язык для понимания, расшифровки и написания, который должен использоваться для обучения программированию, - как первый шаг на пути к более сложным языкам. В 1970-х гг. два человека - Пол Аллен (Paul Allen) и Билл R-йтс (Bill Gates) - решили разработать язык Бейсик для нового персонального компьютера Альтаир (Altair Personal Computer). Разработчики Альтаира были очень заинтересованы в Бейсике, и Гейтс с Алленом получили на него лицензию. Билл Гейтс и Пол Аллен перенесли Бейсик и на другие тины компьютеров. В 1980 г. кроме Альтаира Бейсик работал на компьютерах Atari, Commodore и Apple. Билл Гейтс разработал операционную систему DOS (Disk Operating Svslem, дисковая операционная система) с интерпретатором языка Бейсик. Это позволило всем пользователям DOS создавать программы на Бейсике. Компания Microsoft, возглавляемая Гейтсом, поняла, насколько популярен Бейсик, и решила создать для него компилятор, не требующий DOS. Так появился QuickBasic, первый независимый компилятор Бейсика. Вскоре после этого Microsoft решила сконцентрировать внимание на графическом интерфейсе и создала среду разработки Visual Basic, позволяющую писать программы с графическим интерфейсом, используя Бейсик в качестве основного языка. BlitzPIus, программа, которую мы будем использовать при изучении этой книги, создана Марком Сибли (Mark Sibly) и предназначена для разработчиков игр. BlitzPIus очень проста в изучении и понимании, т. к. основана на Бейсике, и является хорошим способом изучить программирование игр, не беспокоясь о дополнительном коде, который не имеет почти никакого отношения к самим играм.
Установке BlitzPIus Чтобы поскорее начать создание игр, вам нужно установить BlitzPIus на свой компьютер. Программа BlitzPIus - компилятор, она берет ваш код и делает из него программу, которая может работать на любом компьютере. Однако демоверсия, включенная в компакт-диск, ие имеет компилятора, а имеет только интерпретатор. В отличие от компилятора, интерпретатор не создает исполняемый файл, который можно запускать на любом компьютере, а запускает программу при помощи своего компилятора. Другими словами, созданная вами программа может запускаться только из компилятора на вашем компьютере. Если вы хотите создавать независимые исполняемые файлы, вы можете приобрести полный пакет программ BlitzPIus иа сайте http://www.blitzbasic.com. Кроме того, вы можете скачать новые версии BlitzPIus с Web-сайта поддержки этой книги http://www.maneeshsethi.com. Установщик BlitzPIus показан на рис. 1.1. Рис. 1.1. Установщик BlitzPIus Начнем по порядку. Чтобы установить эту программу, вставьте компакт-диск в привод CD-ROM и запустите BlitzPlusDemo.exe. Установщик спросит, куда вы хотите установить программу. Выберите папку (лучше всего оставить папку но умолчанию) и щелкните на кнопке Start (Начать). Когда установка завершится, щелкните на кнопке ОК и запустите программу. Все готово! Теперь на вашем компьютере установлен интерпретатор языка Бейсик. Демоверсия BlitzPIus имеет одну неприятную особенность - она запускается только 30 раз, а потом блокируется и требует приобрести полную версию. Поэтому я включил в компакт-диск старую демоверсию - файл BlitzBasicDemo.exe. В демоверсии BlitzBasic будет работать большинство кода BlitzPIus, хотя, возможно, придется внести небольшие изменения. BiitzBasic не имеет ограничения по времени и позволит создавать код сколь угодно долго. Разбираемся с IDE Программа BlitzPIus сперва может привести вас в уныние. Программа имеет множество меню и значков, но вы без труда поймете их предназначение. Первое, что вы увидите при запуске программы, - окно документации, показанное на рис. 1.2. Если нужно найти учебное руководство или пример программы, это можно сделать здесь. После того как вы прочитаете все, что вас интересует, откройте новый документ, выбрав команду меню File ♦ New (Файл ♦ Создать) или щелкнув на кнопке New (Создать). Теперь вы увидите то, что называется IDE (рис. 1.3). Акроним IDE расшифровывается как Integrated Development Environment, что в переводе означает «'интегрированная
Окна и панели Главное окно занимает больше всего пространства программы и является самой важной частью BlilzPIus. В этом окне вводится код игры. Ключевые слова и важные части вашей программы будут подсвечиваться, когда вы введете их в этом окне. Если вы хотите получить пример - введите слово End, чтобы экран выглядел так, как показано на рис. 1.4. Вы заметите, что, как только вы ввели слово и нажали Spasebar. оно изменило цвет. Подсветка облегчает чтение и понимание кода программ. Рис. 1.4. Подсвеченный код Взгляните в правую часть экрана. Хотя это не показано на рисунке, единственная видимая панель расположена справа под кнопками funcs (функции), types (тины) и labels (метки), каждая из которых позволяет отобразить на панели различную информацию. Кнопка funcs показывает созданные функции, кнопка types - определенные типы, а кнопка labels - имеющиеся метки. Наверно, сейчас эти описания ни о чем вам не говорят, но к концу книги вы поймете, что они означают. Панели инструментов Основная нацель инструментов (рис. 1.5) - это просто набор значков. Она позволяет быстро выполнять действия и не искать в меню соответствующие команды. В табл. 1.1 вкратце описывается каждый значок в порядке слева направо. 1а»нq вй я в? в Рис. 1.5. Основная панель инструментов Табл. 1.1. Основная панель инструментов Значок Описание New (Создать) Открывает новый пустой документ BlitzPIus Open (Открыть) Позволяет открыть существующий документ Save (Сохранить) Если программа уже была сохранена, выполняет быстрое сохранение открытого документа: если нет, спрашивает имя и расположение на диске файла, в который нужно сохранить документ Close (Закрыть) Закрывает один документ Cut (Вырезать), Сору (Копировать), Paste (Вставить) Команда Cut (Вырезать) сохраняет выделенный текст в буфере обмена и удаляет его с экрана; команда Сору (Копировать) сохраняет выделенный текст в буфере обмена и оставляет его на экране без изменений; команда Paste (Вставить) вставляет сохраненный в буфере обмена тексг в документ
Табл.1.1.(окончание) Значок Описание Find (Найти) Позволяет найти в открытом документе определенное слово или словосочетание Run (Выполнить) Компилирует и запускает открытый документ Pause (Пауза), Continue (Продолжить), Step Over (Пропуск блока). Step Into (Войти в блок). Step Out (Выйти из блока), End (Завершить) Дополнительные средства отладки Ноте (В начало). Back (Назад), Forward (Вперед) Все три команды позволяют войти и переме щаться по документации BlitzPIus. Если окно документации неактивно, кнопки Back (Назад) и Forward (Вперед) будут недоступны Меню Строка меню позволяет использовать всю мощь BlitzPIus. Основная строка меню показана на рис. 1.6. В каждом меню расположено множество полезных команд, самые полезные из которых приведены в табл. 1.2. J File Edit Program Help Рис. 1.6. Меню BlitzPIus Табл. 1.2. Команды меню BlitzPIus Команда Описание Program ♦ Check for errors (Программа ♦ Проверить на ошибки) Позволяет проверить код на ошибки без его компиляции и выполнения Program ♦ Debug Enabled? (Программа * Отладка включена?) Если эта функция включена, программа будет запускаться в маленьком окне (а не занимать весь экран), что упрощает отладку программы Первая игра: KONG Итак, теперь вы сможете увидеть, как выглядит игра. Это будет простой клон игры Pong, которым просто управлять и играть. Идея игры заключается в том, что нужно набрать больше очков, чем противник, отправляя шарик на его сторону стола. Чтобы начать тару, запустите файл demo01-01 .ехе с компакт-диска или скомпилируйте код.
Чтобы самостоятельно скомпилировать код, найдите на компакт-диске файл demoOI-01 - bb, скопируйте его на свой компьютер и откройте в компиляторе BlitzPIus. Чтобы открыть этот файл, щелкните в верхней части окна компилятора, выберите команду меню File ♦ Open (Файл ♦ Открыть). Найдите файл demo01 -01 .bb и нажмите Enter. Код появится в окне компилятора. Чтобы выполнить саму компиляцию, щелкните Program ♦ Run Program (Программа ♦ Запустить), игра скомнилируется и запустится. Если появится окно с просьбой сохранить файл, выберите папку, в которой можно его сохранить, или просто щелкните Cancel (Отмена). Вы только что скомпилировали первую программу! Можете просмотреть код. Хотя сперва он покажется очень запутанным и трудным для понимания, в скором времени вы сможете с легкостью в нем разбираться. В табл. 1.3 перечислены клавиши, используемые в этой программе. Табл. 1.3. Клавиши, используемые в программе KONG Клавиша Действие Up Arrow Перемещение вверх Down Arrow Перемещение вниз Esc Выход из игры P Пауза и снятие паузы Теперь посмотрим сам код. Прочтите его и не беспокойтесь, если чего-то в нем не поймете. Это первая программа, которую вы вцдите. и она не такая уж простая. О том, как написать такой код, вы узнаете из книги. ;6<imo01-01.bb - Игра KONG ;установка графического режима Graphics 800,600 ;подготовка генератора случайных чисел (чтобы сделать случайные числа действительно случайными) SeedRnd(Millisecs()) {создание вторичного буфера SetBuffer BackBufferO {установка дескриптора в центр изображений AutoMidHandle True ;КОНСТАНТЫ {определение кода клавиш
Const UPKEY = 200 ;вверх Const DOWNKEY = 208 ;вниз Const PAUSEKEY =25 ;P Const HUMANSPEED = 7 ;максимальная скорость человека Const COMPUTERSPEED = 6 ;максимальная скорость компьютера ;ТИПЫ ;Тип игрока: и человек, и компьютер Type player Field у,score ;позиция по оси у и очки End туре ;тип шарика Type ball Field x,y,xv,yv End туре {координаты х, у, скорость изменения х и у ;ИЗОБРАЖЕНИЯ {изображение игрока-человека Global playerlimage = Loadimage(“player1.bmp") /изображение игрока-компьютера Global playerZimage = Loadlinage(*player2.bmp") {изображение шарика Global ballimage = Loadlmaget"ball, bmp") .-загрузка изображения шарика {ИНИЦИАЛИЗАЦИЯ ТИПОВ {создание шарика Global ball.ball - New ball {создание игрока-человека Global playerl.player = New player ;создание игрока-компьютера Global player2.player = New player Это раздел объявления. В этой части определяются важные переменные программы, типы и изображения. Не волнуйтесь, все это будет объяснено по мере чтения книги.
После описания начинается инициализация. Инициализация - это процесс установки всего, что будет использовано в программе. В нашем случае здесь устанавливаются начальные значения очков и положение игроков на экране. /ИНИЦИАЛИЗАЦИЯ Text 400,300, "Ready...Set * /секунда ожидания Delay(1000) Text 420,330, "GO!!!" Flip /задержка 1/5 секунды Delay(200) /инициализация уровня InitializeLevel() /установка начальных значений очков playerl\score - ? player2\score = 0 В разделе инициализации устанавливаются некоторые важные для игры переменные, которые нужны для отслеживания количества очков игрока и его положения на экране. После инициализации начинается цикл игры: /ГЛАВНЫЙ ЦИКЛ While Not KeyDown(l) /очистка экрана Cis /рисуем шарик Drawlmage (ballimage,bal1\х,ba11 \у) /рисуем игрока-чеповека Drawlmage (player1image, 60, playerl\y) /рисуем игрока-компьютер Drawlmage (player2image, 740, player2\y) /проверка нажатия клавиши пользователем TestKeyboard() /что должен делать "искусственный интеллект"?
TestAI() ;рисуем очки DrawScore() Flip Delay 20 Wend ; КОНЕЦ ГЛАВНОГО ЦИКЛА Что такое кадр? Скоро вам будет встречаться слово «кадр» (frame), и вы должны знать, что оно означает. Кадр - это экран в любой взятый момент. Игру можно сравнить с анимированным фильмом - и игра, и фильм состоят из набора различных картинок, которые создают анимацию, когда последовательно появляются на экране. Кадры меняются так быстро, что кажется, что объект на экране движется. В среднем игры отображают 30 кадров в секунду, т. е. каждую секунду на экране появляется 30 картинок. Это конец главного цикла. Можно сказать, что главный цикл - это и есть игра. Каждый кадр игры—это один проход главного цикла. Цикл повторяет некоторый код снова и снова, пока не выполняется какое-то условие. В нашем случае таким условием является нажатие клавиши Esc. Обычно главный цикл представлен никлом while, показанным в следующей строке: While Not KeyDown(ESCKEY) После этого игра закончена, теперь мы должны определить функции. Функция вызывается по имени, за которым следуют круглые скобки, например InitializeLevel (). Функции можно сравнить с маленькими помощниками, которые выполняют определенные действия, которые мы котим повторять снова и снова. Если посмотреть на главный цикл, можно увидеть, что большинство функций вызывается оттуда, а некоторые функции вызываются из друшх функций, ;INITIALIZELEVEL() ;установка начальных значений Function InitializeLevel() ;помещаем шарик в центр экрана Ьа11\х = 400 Ьа11\у = 300
;начинаем движение шарика в случайном направлении ball\xv - Rand(2,6) ball\yv = Rand(-8,8) .•размещаем игроков на экране player2\y = 300 playerlAy = 300 End Function Эта функция устанавливает начальные значения для игроков и шарика. Шарик находится в центре экрана и направлен в правую часть экрана (к игроку-компьютеру), с небольшими вариациями в высоте траектории. Игрок-человек находится у левого края экрана, игрок-компьютер - у правого. ;DRAWSCORE() ;вывод очков в правом верхнем углу экрана Function DrawScore() ;вывод очков игрока-человека Text 700,0,"Player 1: " + playerl\score ;вывод очков игрока-компьютера Text 700,30,"Player 2: " + player2\score End Function Это, наверно, самая простая функция в этой программе, поскольку все, что она делает, - выводит очки в верхнем правом углу экрана. ;TESTKEYBOARD ;перемещение игрока вверх и вниз с учетом нажатых клавиш Function TestKeyboard() ;если игрок нажимает клавишу вниз, переметаем его вниз I1 KeyDown(DOWNKEY) playerl\y = playerl\y + HUMANSPEED End [f ;если игрок нажимает клавишу вверх, перемешаем его вверх IE KeyDown(UPKEY) playerl\y = playerl\v - HUMANSPEED End If ;если игрок нажал кнопку паузы, останавливаем игру If KeyHit(PAUSEHIT)
;очищаем экран Cis Text 400,300,“Press '₽' to Unpause Game* Flip ;ждем снятия паузы While Not KeyHit(PAUSEKEY) Wend Endlf End Function Эта функция определяет, какие клавиши нажал пользователь, если вообще нажал. Если вы ничего не поняли, прочтите этот псевдокод: Что такое псевдокод? Солидно звучит, да? Псевдокод - это очень полезная вещь для программирования игр, поскольку позволяет перевести сложные для понимания концепции на человеческий язык. Псевдокод - это код программы, но в простых терминах. Чтобы преобразовать код программы в псевдокод, нужно, в основном, просто заменить каждую строку кода понятными человеку словами. В псевдокоде не отражены все детали настоящего кода, так что, хотя он и позволяет понять смысл кода, не нужно пытаться вставить его в программу. Если (игрок нажимает клавишу вверх} перемещение игрока вверх Если {игрок нажимает клавишу вниз} перемещение игрока вниз Если (игрок нажимает 'Р'} пауза в игре Очень легко понять, не так ли? Вернитесь к настоящему коду и найдите соответствие. Теперь разберемся с функцией TestAI (). ;TESTAI() ;обновляем шарик и очки противника Function TestAI()
,-если шарик выше игрока-компьютера, перемещаем игрока вверх If bal. у player2\y Player2 . playerz у -* COMPUTERSPEED ;если шарик ниже игрока-компьютера, перемещаем игрока вниз Elself ballХу < player2\y playerz Vy • player2\y - COMPUTERSPEED Endlf ;если игрок-человек ударяет шарик, отражаем его и изменяем его скорость и направление If ImagesOverlap (ballimage,ball \x,ball\y, play er linage, 60, playerl\y) ballxxv = -ballXxv » Rand (—4,4) ball'.yv = ballXyv т Rand(-4,4) ;если игрок-компьютер ударяет шарик, отражаем его и изменяем его скорость и направление Elself ImagesOverlap(ballimage.ball\х,ballХу,player2image,740, player2\y) balIXxv ’ -ballXxv т Rand(-4,4) ball - ballkfr-' + Randt-4,4} ,-если шарик ударяется о верхний край, отражаем его вниз Elself ball\y <=0 ballXyv = -ballxyv < Rand(-l.l) ball\xv - ballXxv + Rand{-1,1} ;если шарик ударяется о нижний край, отражаем его вверх Elself ball\y >-600 ballXyv = -ballxyv + Rand(-1,1) ballXxv = ball'.xv + Rand(-l.l) ;если шарик ударяется о левый край, компьютер получает очко Elself ball' х <=0 playerz \ score -- player2 <score + 1 ; компьютер получает очко Text 400,300,"Player 2 Scores!!!" Flip ,-задержка две секунды Delay(2000)
; СбрОС УРОВНЯ InitializeLevsl() ;если шарик ударяется о правый кри человек получает очко Elself ball\x >=800 Playerl\score = playerl s-ti. • . ;человек получает очко Text 400,300,"Player 1 S. !-• s!!!" Flip ;задержка две секунды Delay(2000) ;сброс уровня InitializeLevel() Endlf ;обновляем позицию адрнка на экране ball'x = ball ,х 4 Mall xv ball V • ball у - ball End Function Эту функцию намного сложнее понять. Она изменяет положение шарика с учетом переменных направления и изменяет позицию игрока-комиыотера, учитывая положение шарика. Также, если какая-то из сторон пропустила шарик, увеличиваются очки. Если вы не понимаете, что делает эта функция, вам может помочь псевдокод: Если (шарик выше игрока-компьютер л) перемещение игрока-! пыс.~ « вмядх ИЛИ если (шарик ниже иг. • перемещение и.,эка-л . -лыс- . нил* Если (шарик отражается . । , . - гов< » изменение направления шарика ИЛИ если (шарик отражается игроком-компьютером) изменение направления шарика ИЛИ если (шарик ударяется о верхний при С* изменение направления шарика
ИЛИ если (шарик ударяется о нижний край} изменение направления шарика ИЛИ если (шарик ударяется о левый край} добавление очка компьютеру сброс уровня ИЛИ если (шарик ударяется о правый край) добавление очка человеку сброс уровня Если вы хотите получить более полное представление об этой игре, запустите программу demoOl-01-bb с компакт-диска. Из-за ограничений ширины, некоторые строки кода могут занимать две строки или более. В настоящей игре весь код должен находиться на одной линии, иначе он не будет работать. Приведу пример: Elself ImagesOverlap(ballimage,ball\x,ball\y,player2image,740, player2\y> ,-This tests to see if the ball has collided with player 2's image. Если в компиляторе ввести это с разрывом строки, код не будет работать. Он должен находиться на одной линии, даже если он будет разбит на строки не так, как в книге. На рис. 1.7 и показаны начальный экран и главный экран игры KONG соответственно. Рис. 1.8. Главный экран игры KONG Ready. Set GDI” Рис. 1.7. Начальный экран игры KONG Компиляция кода Скомпилировать код очень легко. Для этого просто откройте файл (demoOI-Ol.bb) с компакт-диска в программе BlitzPIus (или введите код в рабочей области), сохраните
файл, выбрав команду меню File • Save (Файл ♦ Сохранить), а затем выберите команду меню Program * Run Program (Программа ♦ Запустить), как показано на рис. 1.9. Рис. 1.9. Компиляция кода Конечно, полноценной игрой это нельзя назвать. Я не добавлял спецэффекты или звук, поскольку сейчас они не особо важны. Сейчас важно понять, как выглядит код и как его писать. Вы заметите, что предназначение большинства функций можно понять по их названию. Это помогает понять программу. Итак, основными частями игры являются: раздел инициализации; главный цикл; функция завершения программы. Инициализация определяет переменные и функции, которые используются в игре. Описание - часть инициализации, которав нужна для определения переменных, использующихся в программе. Цикл игры - это то, что вы видите на экране. Каждая итерация (проход цикла программой) является кадром игры. Обычно в секунду показывается минимум 30 кадров. На рис. 1.10 представлено схематичное изображение инициализации, цикла игры (также называемого главным циклом) и функции завершения для программы KONG. Последовательность завершения программы - заключительный этап, который выполняется непосредственно перед окончанием игры. Закрываются все открытые файлы, удаляются используемые переменные и завершается работа программы.
initialization Fonts Constants Types images InitializeLevel ()— Game Loop r TestKeyboard ()—' TestA) ()---------1 DrawScore ()— I Shutdown-------- L -----Ball posskxi velocity Player 1 position Player2 position Playerl move up move down Pause Game -Ball move up move down move left move right change droction Player 1 Add 1 score point Player 2 move up move down add 1 score point draw the score Рис. 1.10. Инициализация, цикл игры и завершение программы Конечно, в любой игре есть несколько других важных частей, но их мы рассмотрим, когда это будет необходимо. Сейчас прочтите комментированный код и попытайтесь попять, что все это значит. Если вы все внимательно прочитали, это будет не сложно. Неудачный день Маниша В марте 2004 г. я участвовал в шоу «Gall for Help» (звонок помощи) на TechTV. Я решил продемонстрировать там эту игру, KONG, как очень простую для понимания. Оказалось, это был неудачный выбор. Во время игры в коде рандомизации произошел сбой, и шарик стал ударяться о верхний и нижний края, не двигаясь в других направлениях. Моя игра потерпела крах на глазах у телезрителей! Отрывок этого телешоу вы можете посмотреть на моем сайте tittp:/www.maneeshsethi.com. Но обещайге, что не будете смеяться! Заключение В этой главе было весьма много материала! Мы узнали историю Бейсика, установили BlitzPIus, рассмотрели важные функции этой программы, а также написали, прочитали нашу первую игру и сыграли в нее. Одно важное замечание: не пугайтесь размера или сложности кода этого примера. Эта игра вовсе не сложная, и хотя сейчас вам может показаться, что в ней очень много кода, когда вы завершите изучение книги, вы сможете относительно легко написать что-нибудь подобное.
В этой главе мы прошли следующие темы: • история Бейсика: установка программы BlitzPIus; создание нашей первой игры; компиляция нашей первой игры. Следующая глава посвящена основам Бейсика. Мы рассмотрим основные операторы и операции. Сложностей не предвидится. так что расслабьтесь и получайте удовольствие.
ГЛАВА 2 Изучаем Бейсик В этой главе рассматриваются простые и базрвые аспекты языка Бейсик. Графика здесь почти не упоминается, так что все, что вы создадите, можно будет просмотреть на экране в текстовом формате. То, что вы узнаете из этой главы о программировании на Бейсике, вы можете применять для написания собственных программ. Хотя вы пока нс сможете включать в свои программы график); вы сможете создавать простые текстовые программы. Такая практика поможет закрепить изученный материал, так что будет гораздо проще запомнить его. Последующие тлавы во многом опираются па концепции, описанные здесь, так что убедитесь, что понимаете их, прежде чем будете читать дальше. В этой главе вы узнаете, как использовать переменные, vc ювные операторы и ввод данных. Готовы? Здравствуй, мир! Прежде чем мы пойдем дальше, вы напишете свою первую программу. Те, кто впервые приступают к программированию на любом языке, обычно создают такую программу, поскольку она очень проста. Эта программа просто выводит на экран текст «Hello, World!» (Здравствуй, мир!). Ни графики, нн особых эффектов, просто чистый текст Давайте скомпилируем соответствующий код. Введите текст в компиляторе BlitzPIus или откройте фасы demo02-01 -bb (рис. 2.1). Затем выберите команду меню Program ♦ Run Program (Программа ♦ Запустить) и наблюдайте магическое действо. Если вы решили ввести код в компиляторе, первым делом убедитесь, что рабочая область, в которую вы вводите текст, пуста. В главном окне программы BlitzPIus должен отображаться только код. Если вы не хотите компилировать код, можете запустить эту программу с компакт-диска. На рис. 2.2 показана выполняющаяся программа Hello, World.
;demo02-01.bb - Отображает текст "Hello, World!" Print "Hello, World!" гзадержка пять секунд Delay 5000 Рис. 2.1. Программа Hello World в BlitzPIus Рис. 2.2. Выполнение программы Hello World Хотя эта программа может показаться очень простой, вы только что сделали большой шаг вперед. Вы создали файл, ввели код, скомпилировали его и запустили программу. Поздравтяю! Давайте разберем программу (несмотря на то, что разбирать тут особо нечего). Во-первых, строка ;demo02-01.bb - Отображает текст "Hello, World!"
является комментарием. Комментарий - это любой текст, стоящий после точки с запятой (;). Комментарий заканчивается в конце строки. Он не обязательно должен стоять в отдельной строке, можно поставить его после кода программы. Например, строка Print "Это код" ;а это комментарий. состоит из двух частей: кода и комментария. Комментарии применяются, чтобы помочь понять код. Компилятор не обрабатывает комментарии, он их просто игнорирует. На рис. 2.3 показано, как комментарии выглядят в компиляторе. Рис. 2-3- Комментарии в компиляторе Вы можете спросить: «Если это мой код, зачем мне нужны комментарии, чтобы понимать его? Я его написал, я и так его понимаю!». Такое предположение создает две проблемы: во-первых, вы можете поделиться своим кодом с кем-нибудь еще, и, во-вторых, вы можете забыть, как работает ваша программа, и провести много времени, пытаясь выяснить это. Я неоднократно забывал комментировать свой код, и это не приводило к хорошим результатам. Мне приходилось тратить много времени, чтобы понять код, написанный всего несколькими месяцами ранее. Мораль сей истории такова: всегда комментируйте свой код. Следующая строка кода является частью программы: Print "Hello, World!" Эта строка выводит текстовую строку "Hello, World' " на экран (текстовая строка -это просто набор символов) и переходит на новую строку. Чтобы понять, что я имею в вид}' под новой строкой, добавьте в код еще одну команду Print. Вы увидите, что новый текст появился под старым. Обратите внимание на кавычки, в которые заключен текст "Hello,, World! ". Кавычки необходимы для любого текста. Они указывают программе, что введенные символы
являются набором б}!® или цифр, а не именем переменной. Если убрать кавычки, возникнет ошибка. Если вы введете текст программы в компиляторе, то заметите, что после ее запуска появляется диалог, сообщающий, что «Program has ended» (программа завершена). Такая особенность присуща демоверсии BlitzPIus, в полной версии ее нет. Если хотите избавиться от этого диалога, просто введите End там, где хотите, чтобы программа завершалась. Команда End завершает программу без отображения каких-либо диалогов. Попробуйте вставить End в код demo02-01 -bb. Обычно я объявляю функции, чтобы можно было легко на них ссылаться. Объявление функции описывает любые параметры, принимаемые функцией, и ее имя. Вот объявление функции Print: Print [string$] Обратите внимание на квадратные скобки ([]), в которые заключена переменная strings. Такие скобки указывают, что переменная не обязательна. Если переменная обязательна, но пропущена, возникнет ошибка, и код не скомпилируется Как видите, функция имеет имя Print и параметр [s’ cxng$]. Текстовая строка (string) - это просто набор символов, взятых вместе. Можно сравнить это с предложением. Текстовая строка может быть целым предложением, включая пробелы и знаки препинания. Прежде всего, Print - это функция. Функции - более подробно они будут описаны ниже - могут быть двух типоя: определенные пользователем и определенные компилятором. Функции первого типа создаются программистом (TestKeybOc ir<3 () из главы 1), а функции второго типа встроены в компилятор и могут применяться в любой программе. Пример определенной компилятором функции—функция Print В табл. 2.1 описаны параметры функции Print Табл. 2.1. Пареметры функции Print Параметр Описание strings Текстовая строка, за которой следует переход на новую строю; отображающаяся на экране. Если этот параметр опушен, будет выполнен только переход на новую строку Последняя строка кода вызывает функцию Delay. Delay millisecs% Эта функция делает задержку на указанное количество времени. В этой программе я задал паузу в 5000 миллисекунд, что равняется пяти секундам. Если удалить эту строку кода. программа закончится раньше, чем пользователь успеет прочитать - Hello, World!». Остается один вопрос: для чего нужны знаки доллара и процента в конце параметров функций? Это ведет нас к следующей теме - переменные.
Переменные Переменные необходимы почти для любой программы. «Переменная» означает, что значение может меняться. Например, вы запустили программу, которая хранит в переменной набранные очки. Когда очки изменяются, соответствующая переменная изменяется, чтобы отразить изменения в количестве очков. Объявление переменных С переменными очень просто разобраться, поскольку их можно использовать как обычные числа. Однако, в отличие от чисел, переменные сначала нужно объявить. Когда переменная объявляется, программа уанает о том, что переменная существует и ее можно использовать. В Бейсике есть три типа переменных: целочисленные (integer), с плавающей точкой (floating point) и строки (string). Эти типы описаны в табл. 2.2. Табл. 2.2. Описание типов переменных Тип Описание integer% Целочисленная переменная. Знаки после точки недопустимы float* Переменная с плавающей точкой. Знаки после точки разрешены stringS Текстовая строка При создании переменные по умолчанию считаются целочисленными. Так что указывать знак процента после целочисленных переменных не обязательно. С этого момента в книге они будут опускаться в большинстве случаев. Переменные любого типа определяются аналогично. Нужно просто ввести имя переменной, которую вы хотите определить, добавив символ %. # или $. Например: highscore% = 100 pi# = 3.14159 rnynameS = «Maneesh Sethi» Использование переменных Теперь вы готовы к созданию нескольких программ с использованием переменных, чтобы увидеть некоторые важные особенности последних. ;demo02-02 .bb - Сложение двух любимых чисел
;ПЕРЕМЕННЫЕ favnum -- 314 coolnum = 13 /вывод двух переменных Print "I like " + favnum + "And 1 like ’ t coolnum /вывод суммы переменных Print "These numbers added together are ' + (favnum + coolnum) /задержка 5 секунд Delay 5000 Результат работы этой программы показан на рис. 2.4. Рис. 2.4. Программа demo02-02.bb Так-так, это действительно интересно. Давайте посмотрим. Во-первых, вставлен комментарий, описывающий программ}’. Это очень полезно и должно использоваться во всех программах. Далее я инициализировал две переменные, favnum и coolnum. Затем я вызвал функцию Print. Строковая переменная начинается со статичного текста «I like», а затем отображается favnum. Чтобы вывести на экран favnum, нужно применить оператор объединения (+). Этот оператор соединяет различные строки. В нашем случае это позволяет отобразить переменную favnum. Первое выражение Print завершается выводом текста «And I like» и переменной coolnum. Следующее выражение Print выво,1ит на экран «These numbers added together аге» (Эти числа дают в сумме) и число 327, которое получилось при сложении 314 и 13. Однако, если убрать скобки вокруг favnum и coolnum, получится странный результат: 81413 (рис. 2.5). Причиной столь странного результата является то, что без скобок оператор сложения (+) интерпретируется как оператор объединения из-за контекста, в котором используется. Раз нет скобок, оператор добавляет строку «13» в конец строки «814» и отображает это все как строку, а не как целое число. Единственный способ это исправить - использовать скобки.
Рис. 2.5. Demo02-02.bb, если убрать скобки Boi пример использования только строковых переменных: ;demo02-03.bb - объедини.-:'- : — с stringl$ - "I " string2S = "like • stnng3S = "projii . . r.jl" ;объединяем (трагя conletestri )• • jr чпц!. • ЬГ1 л.*;Э2( * t ищ ;вывод на эк]>1н Print complrte«trl>:u'i Delay 5000 В этой программе создастся несколько отдельных слов, которые объединяются в переменной :ompletuлгпппЁ при помощи оператора объединения. Как видно на рис. 2.6, "I " + "like ” + "programming !" превращаются в "I like programming! ". Рис. 2.6. Программа Demo02-03.bb
Ввод данных Итак, вы поняли, как работать с переменными. Теперь давайте воспользуемся ими, чтобы получить данные от пользователя программы. Функция Input может распознать. какие клавиши нажимает пользователь, например, отвечая на ваш вопрос. Ввод данных обычно организуется через переменные. На рис. 2.7 показан ввод данных в этой программе: ;demo02-04.bb спрашивает у пользователя имя и отображает его ;получение имени пользователя name$ = Inputs("Hi! May I know your name pleasei *j Print ‘Hi “ + nameS + ;задержка пять секунд Delay 5000 Рис. 2.7. Программа demo02-04.bb Первая строка - комментарий, описывающий программу. Вторая строка считывает введенные данные, а третья отобра кает то, что ввел пользователь. Input $ объявляется так: Inputs(prompts) Заметьте, что имя функции, Inputs, имеет в конце знак $. Этот символ показывает тип значения, возвращаемого функцией. Поскольку это строка, функция может возвращать только строки. Это значит, что, если вы попросили пользователя сложить числа, например 2 + 2, возвращенное значение будет «2 + 2», а не 4. Конечно, если пользователь сразу введет 4, то функция возвратит 4. Inputs - имя функции. В табл. 2.3 объясняется, что prompts — это строка, которая отображается на компьютере перед тем, как введенные данные будут считаны. Обычно prompts используется для запроса у пользователя какой-либо информации. Обратите внимание на круглые скобки вокруг prompts. Скобки необходимы; если вы забудете их, программа не скомпилируется. Также заметьте, что нет квадратных скобок,
а это значит, что переменная обязательна. Если вы хотите, чтобы переменная prompts была пустой, воспользуйтесь двумя кавычками. Табл. 2.3. Параметр Input$ (' Параметр Описание prompts Эта строка показывается пользователю перед тем, как он сможет ввести данные В предыдущей программе переменная nameS приравнивается функции Inputs. Когда команда Inputs получает от пользователя ответ, полученные данные сохраняются в переменной name$. Если переменную не указать: Inputs("Hi1 May I know your name please?") полученные от пользователя данные просто уйдут в никуда. Применять Input $ без переменной бессмысленно. Функция Inputs возвращает только строки (именно поэтому после ее имени стоит знак $). Однако, если для получения входных данных используется целочисленная переменная, значение будет интерпретировано, как целое число. Таким образом, вы можете спросить пользователя «Сколько тебе лет?» и использовать для получения значения целочисленную переменную - она будет содержать то. что ввел пользователь. Теперь вы знаете, как получать входные данные Однако приведенные примеры не особо полезны: кому нужна программа, сообщающая пользователю его собственное имя? Поэтому переходим к следующей теме: условным операторам. Условные операторы Условные операторы очень важны для любой программы. Они позволяют вашей программе думать. С ними программа может делать выбор и принимать решения. Прежде чем вы сможете в полной мере понять условные операторы, вы должны разобраться с концепцией истинности и ложности, принятой в BlitzPIus. Истина и ложь Представление об истинности и ложности, принятое в BlitzPIus, отличается от человеческого. Для человека некоторые вещи могут быть частично истинными, но для компьютера любое выражение либо истинно (True), либо ложно (False). Хотя отдельные части выражения мо»ут иметь рааные значения, выражение целиком может быть только истинным или только ложным. Программа BlitzPIus (и компьютер в целом) считает, что ноль - это ложь, а любое другое значение (ненулевое) - истина, хотя обычно для обозначения истинности применяется единица. Это значительно облегчает задачи программирования.
Чтобы Определить, является ли что-то истиной или ложью, нужно воспользоваться логическими операторами и операторами отношения Эти операторы сравнивают одно выражение с другим, чтобы выяснить, выполняется ли указанное отношение, В табл. 2.4 перечислены все логические операторы и операторы отношений. Табл. 2.4. Логические операторы и операторы отношений Оператор Операторы отношений > Больше Больше или равно < Меньше Меньше или равно = Равно о Не равно Логические операторы And И Or Или Not Не Пользуясь табл. 2.4 как руководством, вы можете увидеть, что если, скажем, переменная А равна 14, а переменная В равна 12, то выражение А>В возвратит значение True (истина), поскольку 14 больше 12. If...Then Первым условным оператором, который вы изучите, будет оператор If (если). Это очень простой оператор: If Основная идея оператора If состоит в том, что он позволяет программе делать вы бор. Выражение передается оператору If следующим образом: If выражение истинно Then ;выполняются действия Endlf Как видите, за оператором If следует выражение. Если выражение истинно, выполняется код, заключенный между командами I f и Endl f. Если же оно ложно, ничего не происходит.
;demo02-05.bb - пр< веря< ... - > ли вам лет, чтобы голосовать ;выясняем, скольк пользователю т.»т аде = Input$("How old г * . "I ;если больше ига равно - . ... . --'овать можно If аде >- 18 Then Print 'You are legally allowed Vc vote'• Endlf ;задержка пять секунд Delay (5000) Эта программа просто спрашивает, сколько вам тет, сравнивает со значением 18 и выводит текст " You art 1 еда 1 ly al lowed to vote’*’ (Вам разрешено голосовать!), если вам исполнилось 18. Но ч го, если вы хотите сооб1цить пользователю что-то еще, если ему не исполнилось 18? Как видно на рис. 2.8, эта программа не делает ничего, если пользователю меньше 18 лет. Программа ждет, когда пользователь нажмет любую клавишу, чтобы завершить рабогу. Вы можете не понимать, что делает команда End { f. Она обозначает конец проверки If. .Then. Ко1да программа достигает Enc ;f, она продолжает обычную обработку команд, а не только выполняет команды, koi да выполняются условия, указанные в выражении if. Рис. 2.8. Программа demo02-05.bb If...Then...Else Возможно, вы хотите, чтобы iipoi рамма что-то выполняла и в том случае, когда пользователю меньше 18 лет. Можно переписать программу, добавив еще один оператор If, проверяющий, действительно ли введенное значение меньше 18. Но есть более простой способ: воспользоваться выражением Else. ;demo02-06,bb - проверяет, достаточна ли вам лет, чтобы голосовать ;спрашиваем, сколько пользователю лет
age - Inputs("How old are уоч ; если больше или равно 18 - пусть голос*. - If age >=18 Chen Print "You ar.। . , ;если меньше 18, г-, о совать нельзя Else Print "Sorry, you need f? Ъе a few years older.• Endlf ;задержка пять секунл Delay 5000 На рис. 2.9 показан результат рабогы этой программы. Рис- 2.9. Программа demo02-06.bb В этот раз программа также проверяет возраст пользователя, но, если ему меньше 18, оператор Else позволяет вывести соответствующий текст. Есть еще один полезный способ применения оператора I f _Е1ое. Можно комбинировать два выражения Else if ;demo02-07.bb - дроверяет, достаточно ”и вам пет, чт^бы голосовать age = Inputsf"How old are ycoj *1 If age = 18 Then Print "Yen u-лг. w* vu'x,' Else If age > *R Print "Ycu’ve bi x-г able r.u Ejt q -Л,: . • Else If age < 18 Print "Sorry, yow will have to wait a tew years to vote." Endlf WaitKey
На рис. 2.10 показан результат работы программы. Рис. 2.10. Программа demo02-07.bb Эта программа будет работать только в том случае, если пользователь введет целое число. Если пользователь введет текст, переменная всегда будет равна нулю. Эту проблему можно решить при помощи цикла или оператора Goto (это скоро будет объяснено). В этой программе проверяются все три возможности. Иногда может потребоваться проверить большое число возможностей, а применять для этого I f...Then совсем неудобно. Для этого существует другой условный оператор: Select...Case. Select...Case Этот оператор значительно упрощает работу с большим числом значений. Лучше всего показать это на примере. ;demo02-08.bb - проверка нажатий клавиш х = inputs("Enter 1 to say hi. or 0 to quit. • Select x Case 1 Print "HiJ" Case 0 End Default Print "Huh?" End Select /задержка пять секунд Delay 5000
Эта программа просит пользователя ввести единицу или ноль, после чего либо выводит "Hi! либо завершает работу. Команда default позволяет обрабатывать все остальные команды. Если пользователь ввел не единицу и не ноль, будет выполнен код, определенный командой default. Если вы еще этогоне поняли, обратите внимание, что я выравниваю текст кода так, чтобы его было просто понять. Это значительно облегчает чтение кода, и я очень рекомендую вам делать то же самое. В приведенном примере применять Select-Case не особо необходимо. Поскольку там всего два варианта, можно воспользоваться оператором If...Else. Но в более сложных программах Select...Case становится очень полезным оператором. Кстати, Select...Case объявляется следующим образом: Select переменная Очень просто, не так ли? Логические операторы Логические операторы - основа выражений и условных операторов. Все логические операторы, имеющиеся в BlitzPIus, приведены в табл. 2.5. Там перечислены все условия, при которых логические операторы возвращают истину' и ложь. Табл. 2.5. Логические операторы p 0 PANDQ PORO NOTP 0 0 0 0 1 0 1 0 1 1 1 1 1 1 0 1 0 0 1 0 Оператор AND возвращает значение истины, только когда оба параметры истинны. Оператор OR возвращает значение истины, когда хотя бы один из параметров имеет значение истины. Оператор NOT возвращает значение истины, только если его параметр ложный. Приведем пример оператора AND. ;demo02-09 ,bb - пример использования оператора AND ;выясняем, сколько пользователю лет age = Input$("How old are you? ") ;выясняем, живет ли пользователь в России location - lnput$("Do you live in Russia? (1 For yes, 2 For no) ")
; вводим с т - иват иля т тству«чс/ю возрасту и месту жительства поль- .idli Оператор NOT Операт ор N( 1 немного отличается от остальных двух логических операторов. Вместо двух операндов, он принимает только один. И вместо того, чтобы возвращать значение, основанное на значениях двух операндов, он возвращает значение, противоположное значению переданного ему операнда. Помните, что ложь—это 0, а истина - это 1. Так что оператор NOT может возвращать только единицу или ноль. Если вы запишете М=*Г О вы подучите еданпцу, а если запишете ШТ 1 получите ноль. Команда Goto Перед созданием полноценной m pi>i я хочу представить вам концепцию Goto. Команда Gct^ очень простая, но при ее использовании лснсо допустить ошибку, так что
я рекомендую применять се как можно рьже. Почти всстди, если что-то можно сделать при помощи Goto, это можно сделать и другим способом. Команда Goto работает следующим образом: вы вставляете в код метку, a Goto позволяет перейт и к згой метке (рис. 2.12) Лучшс всего объяснить это еш примере. ;den©02 -10.bb пример и. ->ль ? >вания ( • го .label Print "Hello" selection - Input("Enter . it . > ‘.,i. • : - to repeftt ’Hello’ =-» ") If (selection = .I Goto label Endlf End Результат работы программы показан на рис. 2.13 —►.label Print “Hello” Selection = Input (“Enter 1...’’) If (selection = 1) ------Goto label End Рис. 2.12. Использование команды Goto Рис. 2.13. Программа demo02-10.bb Обратите внимание, что я не включил в эту программу функцию Wai tKey. Поскольку программа повторяется, а затем заканчивается командой End, в функции WaitKey нет необходимости. Как видно на рис. 2.12. при вводе 1 па запрос "Enter 1 . f v ju want me to repeat ’Hello’ " (Веди re 1, если вы хотите. чтобы я повторил «Привет») вызов Goto начинает программу с начала. Это доспиается размещением mcikii .label в начале кода.
Можно переходить при помощи Goto куда угодно, просто перемещая метку . label. Заметьте, что когда вы определяете метку, нужно поставить точку (.) перед ее именем. Но при обращении к метке из команды Goto точка не указывается. Текстовая игра-угадайка Теперь давайте соберем все, что вы изучили в этой /лаве, и создадим вашу первую игру-угадайку. Пользователь вводит число, а вы отвечаете, больше оно или меньше загаданного. Если пользователь угадал число, вы должны сообщить ему об этом. Чтобы игра работала, воспользуемся циклом. Если вы не поймете, как функционирует цикл, подождите до следующем /лавы - там это будет объяснено. Сначала нужно создать раздел инициализации. Он выглядит примерно так: ;demo02-ll.bb - угадай число Print "Welcome to the Guessing Game!" AppTitle "Guessing GameJ* ;установка генератора случайных чисел... не волнуйтесь, потом я это объясню SeedRnd Mi 11iSecs() /выбор числа от 1 до 100 numbertoguess = Rand(1,100) /число попыток numofguesses = С Первая строка после команды Print вызывает функцию AppTitle, которав позволяет изменить имя в панели заголовка, так что программа будет называться не «Blitz Runtime Window», a «Guessing Game!» (Игра-угадайка). Генерация случайных чисел работает следующим образом: переменной numbertoguess присваивается случайное число, подучаемое функцией Rand, которая возвращает число из заданного промежутка - в нашем примере от 1 до 100. В этом разделе выводится текст приветствия, загадывается число и определяются некоторые переменные. Затем начинается цикл, в котором ироверяе/ся, угадал ли пользователь число от 1 до 100. /устанавливаем метку начала цикла .loopbegin /выясняем, какое число ввел пользователь guess = Input$("Guess a number ") /если игрок не угадал число, он угадывает снова If guess > 100 Or guess < 1
Print “Pick a number between 1 and 100, silly’* ;возвращаемся в начало Goto loopbegin Endlf В первой строке этого кода устанавливается метка, позволяющая потом вернуться к началу цикла. Цикл начинается, у игрока запрашивается число и выполняется проверка, входит ли введенное число в диапазон от 1 до 100. Если оно не входит, игрок отправляется в начало цикла. Теперь введем код, проверяющий, угадал ли игрок число. ;увеличиваем счетчик числа попыток numofguesses = numofguesses + 1 ;если введенное число меньше загаданного, отправляем игрока в начало цикла If guess < numbertoguess Then Print "The number was too low." Goto loopbegin ;если введенное число больше загаданного, отправляем игрока в начало цикла Else If guess > numbertoguess Then Print "The number was too high." Goto loopbegin Endlf В псряой строке число использованных попыток игроки увеличивается на единицу. Затем проверяется, угадал ли пользователь число, а если нет, больше оно или меньше. Если пользователь угадал, программа продолжает работу, не отправляя пользователя в начало цикла. Наконец, последняя часть кода: Print "You guessed the number " + numbertoguess + " in " numofguesses * " tries!" /задержка пять секунд Delay 5000 Эту программу можно запустить с компакт-диска, открыв файл demo02-11.bb. На рис. 2.14 показан результат игры.
Рис. 2.14. Игра Guessing Game Заключение Это была непростая глава как для вас, так и для меня. Надеюсь, вы запомнили все, что я вам успел рассказать. Прежде чем вы перейдете к следующей главе, предлагаю вам написать несколько небольших программ, в которых можете применить все, что изучили. Это поможет закрепить полученные знания. В этой главе мы рассмотрели темы: программа Hello, World!; переменные; ввод данных; условные операторы. В следующем главе я расскажу о циклах, функциях, массивах и тинах. Надеюсь, вы готовы!
ГЛАВА 3 Циклы, функции, массивы и типы Мы, наконец, подбираемся к сложному материалу. Эта 1лав,1 посвящена важным и интересным темам - циклам, функциям, массивам и тинам. Все эти понятия являются базовыми для любой компьютерной шры. В данной главе я собираюсь описать каждую из этих тем отдельно, а затем мы создадим простую игру, для чего нам понадобятся все полученные знания. К этому времени вы узнаете, как использовать циклы, функции, массивы и типы. Циклы Цикл-это блок кода, который повторяется скован снова, пока выполняется какое-то условие. Например, Славный цикл игры повторяется, пока трок не выиграет или пока не выйдет из игры. Для создания цикла можно пользоваться командой Goto, которую мы изучили в предыдущей главе. Если помни гс, в программе demo02-10.bb набор команд повторялся, пока пользователь их не останавливал. Циклы работают следующим образом: набор команд повторяется снова, снова и снова, пока не выполнится условие: либо пользователь захочет выйти ил цикла, либо цикл выполнится определенное число раз. На рис. 3.1 изображена схема цикла х = 0 Loop until х -10 Do Whatever -Return to top or exit ;once loop exits, x equals 10, and ;Do whatever has happened 10 times Рис. 3.1. Схематичное изображение цикла Циклы применяются в программах для выполнения многих повторяющихся задач. Например, в гире Космический стрелок птжно использовать цикл (ля проверки попадания каждой пути в корабли противника. Для обновления действии искусственного интс.ыскта ( Ailificidl Intelligence, Al) по управлению кораблями противника так ке будут использоваться циклы.
Есть три типа циклов, и хотя они в определенной степени взаимозаменяемы, каждый из них имеет свои особенности и лучше всего подходит для решения отдельных типов задач. Вот эт и три типа: For...Next; While...Wend; Repeat-Until. For... Next Цикл For...Next выполняет блок кода заданное число раз. Другими словами, вы используете его, когда знаете, сколько раз цикл должен выполняться. Можно применить такой цикл, когда вы хотите, чтобы игрок передвинулся ровно на 10 клеток. Поскольку вы знаете, на сколько клеток игрок должен передвинуться, вы можете сделать так, что каждая итерация (повторение) цикла передвигает его на одну клетку, и выполнить десять итераций. С помощью такого цикла также можно обновлять информацию для набора типов (о типах рассказывается далее в этой главе). Прежде чем мы двинемся дальше, я хочу объяснить принцип итераций. Как вы знаете, цикл обрабатывает некоторый набор команд сверху вниз, а потом возвращается наверх и начинает снова. Итерация завершается, когда весь набор команд обработается один раз. Когдв цикл завершил последнюю команду, но еще не вернулся в начало, он завершил одну итерацию. Когда он возвращается в начало, начинается вторая итерация и т. д. Циклы For...Next всегда используются следующим образом- For переменная = яачальный_яомер То конечный_номер [Step приращение_пага] ;выполнение действий Next Как видите, цикл For...Next начинается с команды For и заканчивается командой Next. Команда То определяет, сколько раз цикл выполняет свои действия. Необязательная команда Step определяет, какое число добавляется к переменной началь-ный_номер после каждой итерации. Если эта команда опущена, переменная началь-ный_номер каждый раз увеличивается на единиц)'. Рассмотрим пример: ;demo03-01.bb - счет от 1 до 10 ;запуск счетчика с начальным значением 1 и выполнение цикла до 10 For counter = 1 То 10 ;выводим значение счетчика Print counter Next
/задержка пять секунд Delay 5000 На рис. 3.2 показан результат выполнения этого кода. Рис. 3.2. Программа demo03-01.bb Эта программа просто выводит на экран чис га от 1 до 10. В первой строке после комментария начинается цикл For...Next. В цикле объявляется счетчик counter, которому присваивается начальное значение 1. Команда То сообщает компилятору, сколько итераций должно быть в цикле. Таким образом, счет будет вестись от 1 до 10. Следующая строка просто выводит значение переменной counter, которое увеличивается на единицу в каждой итерации цикла. Последняя строка запускает цикл снова и увеличивает счетчик counter на 1. Если котите, можете изменить приращение шага цикла. Приращение шага определяет, на какое число увеличивается переменная counter в каждой итерации цикла. По умолчанию приращение происходит па единицу. Чтобы изменить приращение шага, просто добавьте после команды То команду Step: ;<3emo03-02.bb - обратный отсчет с использованием приращения шага :запуск счетчика с начальным значением 5 и выполнение цикла до G с шагом -1.2 For counter# - 5.0 То 0 Step -1.2 .•выводим значение счетчика Print counter Next .•задержка пять секунд Delay 5000 Результат выполнения этого кода показан на рис. 3.3.
Рис. 3.3. Программа demo03-02.bb Тщательно поверяйте свои циклы, чтобы не сделать их бесконечными. Если в приведенном примере вместо шага -1.2 задать шаг 1.2, программа будет постоянно выполнять цикл и никогда не завершится. К счастью, программа BlitzBasic обычно отслеживает такую ошибку и просто пропускает цикл Эта программа может показаться немнию странной, но я написал ее так, чтобы указать на новые возможности. Во-первых, переменная счетчика является переменной с. плавающей точкой (переменкой с десятичными разрядами). Начальное значение равняется 5.0, а конечное 0.0. Приращение шага - 1 2. Такой шаг приводит к тому; что отсчет идет в обратном направлении. Во время первой итерации счетчик имеет значение 5.0, во время второй оно уменьшается до 8.8 ит. д Давайте рассмотрим значения переменных в этом цикле. В табл. 3.1 приведены значения переменной ountpr, прнрашение шага и число, выводимое программой. Поскольку приращение шага имеет отрицательное значение, то оно не прибавляется, а отнимается Табл. 3.1. Значения переменных в программе demo02-O2.bb Итерация Значение -пгег# и выводимое значение Шаг 1 5,0 -1,2 2 3,8 -1,2 3 2,6 -1,2 4 1,4 -1,2 5 0,2 -1,2 Теперь самое время рассказать об усечении дробной части (float trimming). Если вы посмотрите на выходные данные программы demo03-02.bb (рис. 3.3), то заметите, что после точки показано шесть десятичных разрядов (Честь разрядов отображается
по умолчанию. Поскольку значимым является только первый, зачем показывать остальные пять? В этом контексте усечение - это отбрасывание ненужных нулей из «хвоста» числа. Отсечение нулей происходит в два этапа, во первых, нужно преобразовать переменную с плавающей точкой в строку, а затем удалить ненужные разряды. После этого можно отобразить строку. Давайте попробуем: ;demo03-03.bb - отсчет с использованием For counter# = 5.0 То 0.0 Step Print Lefts(Str counter,J| Next Обратите внимание, что в этом примере число 3 используется в качестве переменной длины. Так сделано потому, что число преобразуется в строку, и точка является его частью. После преобразования остается число, точка и один разряд после точки. Эта программа начинается так же, как и предыдущая- создается цикл F - г -N -xt, начинающийся с 5,0 и уменьшающийся па 1,2, пока пе достигнет (1,0. Следующая строка выводит усеченную версию значения счетчика. Давайте разберем эго выражение. Оператор Print выводит каждое значение счетчика с одним знаком после точки. Первое, что он делает, - вызов функции Lett' , которая объявляется следующим образом: Left$ (strings,length, В этом случае в качестве переменной strings использовалось выражение Str counter
Функция Str преобразует целое число или число с плавающей точкой в строку, после чего возвращает полеченное строковое значение. Поскольку возвращается строковое значение, вы можете использовать его в качестве переменной string?. В качестве переменной variable используется число 3, позволяющее оставить число и только один знак после точки. В табл. 3.2 описываются параметры функции Left?. Табл, 3-2. Параметры функции Left? Параметры Описание String? Строка, которую нужно усечь length Количество букв, которое нужно оставить While...Wend Следующий тип цикла - While...Wend. Этот цикл очень похож на цикл For...Next, но обычно применяется для проверки переменного условия. Другими словами, цикл While...Wend используется тогда, когда вы не знаете точно, когда нужно выйти из цикла В играх чаще всего встречаются именно циклы While. Главный цикл (также извести ный как цикл игры) - это цикл, повторяющийся снова и снова, пока игра не окончится. Поскольку нельзя точно сказать, когда игра закончится, цикл While...Wend - отличный выбор. ;demo03~04.bb - ожидание нажатия клавиши Graphics 640,480 Text 0,0, "This program is worthless." Text 0,12,"Press escape to exit." Flip ;ждем, когда пользователь нажмет Escape, чтобы выйти While Not KeyDown(1) Wend End Вы можете заметить в этой программе какие-то странные функции - Flip и Graphics. Чтобы отследить нажатие клавиши - событие KeyDown (), нужно находиться в графическом режиме. Функция Graphics включает этот режим. Это вы изучите во второй части книги, сейчас просто вводите код как есть. На рис. 3.5 показан результат работы этой программы. Эта программа просто отображает текст и просит выполнить действие для выхода. Почти бесполезная трата времени, да? Тем не менее этот пример демонстрирует работу цикла While..,Wend и представляет новую функцию KeyDown () Цикл while..Wend начинается так: While Not KeyDown(1)
Рис. 3.5. Программа demo03-04.bb В этой строке кода определяется, что цикл завершил ся, только когда пользователь нажмет клавишу Esc. И пока пользователь ие нажмет эту клавишу, цикл продолжает работать. Функция KeyDown (), объяв шемая как KeyDown(scancode) проверяет, нажата ли клавиша Esc Число 1 здесь используется как код опроса. Код опроса генерируется нажатием клавиши на клавиатуре. Каждая клавиша имеет собственный код опроса. У клавиши Esc код опроса 1. Полный список кодов опроса см. в приложении А. Функция KeyDown возвращает 1 (истина), если нажата клавиша, и 0 (ложь), если клавиша не нажата. Поскольку мы хотим, чтобы цикл While...Wend повторялся, пока не нажата клавиша, мы инвертируем возвращаемое значение оператором NOT. Таким образом, если игрок не нажимает клавишу Esc, функция KeyDown возвращает 0. Оператор NOT инвертирует его в 1, и цикл While...Wend выполняет следующую итерацию. Теперь самое время представить базовый цикл игры. Этот цикл завершается, только когда пользователь нажимает Esc. Если пользователь проиграл, вызывается функция, завершающая программу. Заметьте, что этот код не будет работать. Он вызывает несуществующие функции (они будут представлены ниже). ;Главный цикл игры While Not KeyDown(l) PerformLogic() Animation() If playerlost Then GameOver() Endlf Wend
Этот цикл игры упрощен, насколько возможно. Ее ш трок не проигрывает или не нажимает клавишу Esc, цикл продолжает итерации. Функция Perf -rr..iogic 1 может обновлять действия искусственного интеллекта, а функция Ап ' Л ion () может выводить на экран и аиимировль изображение. Ехли переменная playerLost получает значение 1 (скорее всего, это делает функция Pez I гг qic), вызывается функция GameOvei i) и игра оканчивается. Всегда старайтесь сделать главный цикл как можно более простым. Он не должен выполнять ненужных операций. Из этой 1лавы вы узнаете, как делегировать операции небольшим и более эффективным функциям. Repeat...Until Последний тип циклов в программе Blitz Basic- Repeat... Until. Такой цикл работает почти так же, как Whil e—Wend, но условие ставится после закрывающего оператора (Until), а нс после открывающего (R> т-at). Не слишком большое различие, да? Использовать цикл этого типа нужно тогда, когда вы точно знаете, что цикл должен выполниться минимум один раз. Это необходимо, косца нужно отображать меню и проверять нажатие клавиш. ;«ЗетпоО3-05.Ьо - Завершение работы щ ; . ™'сп« -,i ке клавиши Esc Graphics 640,4- ij Text 0,0 "Why «ini > open tntx pjcgrari."* Flip /переменная у < :i ••д- ’илт । ..ние - < a y=12 Repeat ;вывод л екста Text 0,y,"Pxe ;секунду ждем Delay ISi.Ui Flip ;смещаем вин педующую строку текста у=у+12 /повторяем, пока пользователь не нажмет Esc Until KeyHitflj Text 0 ,y, "Pr- jram ,, ... Li Flip Результат выполнения этого кода показан на рис. З.б.
Рис. 3.6. Программа demo03-05.bb Эта программа просто выводит на экран текст «Press Esc to exit- (Нажмите клавишу Esc для выхода), пока пользователь не нажмет клавиша Esc. Она представлена двумя основными функциями: Delay и КеуН . t (). Функция Delay приостанавливает выполнение программы па заданное число миллисекунд. Объявление этой функции: De .ay milliseconds где milliseconds - число миллисекунд, на которое приостанавливается программа. В приведенном примере программа приостанавливает работу на одну секувцу (1000 миллисекунд). Вторая функция - KeyHit () КеуН i * (scancode j Параметр scancode - код опроса клавиши, которая может быть нажата. Эта функция определяет, нажата ли клавиша. Если да, то возвращается истина, если нет - ложь. Переменная у определяет расположение текста. В каждой итерации у увеличивается на 12, перемешан текстовую строку вниз. Текст смещается вниз на 12 пикселов, потому что шриф т имеет размер 12. Смешение вниз на 12 пикселов эквивалентно переходу на новую строю; Условие выполнения цикла Repeat.. .Until противоположно условиям циклов и j I e...Wec з и For...Ifext. Вместо того чтобы выполняться, пока условие истинно, цикл at.. .Until выполняется, когда условие ложно. При создании такого цикла убедитесь, что он не бесконечен. Поскольку в программе используется цикл Rep* < . ..Until, текст «Press Esc to exit» появится на экране в любом случае, даже если нажать клавиш} Esc до начала цикла. Если вы будете создавать игры, использующие меню (как большинство ролевых игр), следует применить цикл Repeat. .. Until.
Вам, наверно, интересно, в чем разница между функциями KeyHitо и KeyDown (1. Разница небольшая. Функция KeyDown () определяет, нажата ли клавиша во время проверки, функция KeyHit () определяет, была ли нажата клавиша со времени последнего вызова KeyHit (). Это различие можно увидеть в любой игре. Если используется функция KeyDown (), можно нажать и не отпускать клавишу, а если используется функция к Jit (), нужно нажимать клавишу каждый раз. Итак, я подробно рассказал обо всех трех типах циклов. Надеюсь, теперь вы знаете, как и когда их применять. Сейчас перейдем к очень важной теме: функциям. Функции Функции - неотъемлемая часть любой программы. В тех программах, которые вы уже написали, тоже используются функции, такие как Print и Delay, и вы даже создали свою собственную основную функцию. Этот раздел расскажет, как создавать собственные функции, которые облегчат написание программ. Функции - это небольшие фрагменты кода, которые обычно выполняют одну задачу. Все программы состоят минимум из одной функции: функции main. Хотя эта функция не определяется явно, она все равно существует в программе. Каждая строка кода, написаннав вами до сих пор, за исключением кода из главы 1, входила в функцию main. Эта функция является начальной и конечной точкой каждой программы, написанной в программе Blitz Basic. На рис. 3.7 показан пример функции main в действии. Поскольку эта функция формально никогда не объявляется, я всегда вставляю комментарий там, где она начинается. Советую вам делать так же. Рис. 3.7. Пример функции Функция main вызывает два типа функций: определенные пользователем и определенные программой. Определенные пользователем функции - это функции, написанные программистом, например TestAI () из главы 1. Все эти функции перед использованием должны быть определены. Определенные программой функции определяются компилятором, как, например, функция Print. Все такие функции уже написаны, вам остается только вызвать их с надлежащими параметрами.
Параметры - это сведения, передающиеся функции, чтобы указать ей, что нужно делать. Например, переменная strings может быть параметром функции Print. Эта переменная сообщает функции Print, что именно вы хотите вывести на экран Вы также можете передавать параметры собственным функциям, но для этого их нужно объявить. Если функция вызывается с лишними параметрами, код не скомпилируется. Прежде чем использовать любую функцию, ее нужно объявить. Объявление функции обычно пишется перед кодом функции. Function functionnname([parameter variable,™]) Сложно выглядит? Давайте разберемся. Сначала печатаете Function. Это обязательно для объявления любой функции. Теперь укажите имя функции. Выберите имя, описывающее назначение функции; например, если функция ведет отсчет, назовите ее Count. Затем введите открывающую скобку и добавьте столько параметров, сколько требуется, разделяя их запятыми. И, наконец, введите закрывающую скобку Приведу пример: Function ConvertFtoC (fvalue) Суля по имени, эта функция преобразует значение температуры ио Фаренгейт.’ в значение по Цельсию. Давайте функциям понятные имена. Затем вы вводите сам код функции. Return (5.0/9.0 * ilvalue - 32)) Запомните, что знак * означает умножение, а знак деление. Этот код возвращает преобразованное значение переданной переменной. Возвращаемое значение - это любое число или строка, возвращаемое вызываемой функцией. Например, функция KeyHit ) возвращает либо 0. либо 1. Здесь же возвращается значение по Цельсию, эквивалентное значению по Фаренгейту. И, наконец, нужно завершить функцию. End Function Теперь нужно вызвать эту функцию из главной функции. Print "Welcome to our FtoC converter!" fvalue - Input$("What Fahrenheit value do you wish to convert?'') cvalue = ConvertFtoC(value) Print fvalue + " Fahrenheit • • т cvalue + " Celsius." Этот фрагмент кода является основной частью программы, которая начинается с приветствия и получения значения температуры по Фаренгейту. Затем вызывается функция ConvertFtoC U, сохраняющая значение в переменную cvalue. И, наконец, полученный результат выводится на экран. ;demo03-06.bb - Преобразование значения по Фаренгейту в значение по Цельсию ;ОСНОВНАЯ ЧАСТЬ ПРОГРАММЫ Print "Welcome to our FtoC converter"
;пс .няем i '"value fva • rnpj’. 11’«»!.« чипе du you wish to convert"?! ;пр obp Lvalue I ДеЛЬСИЮ cvalru< - i Miver'-Ft---' ,' ;п-.1водим результат P) int fvalue * “ i - + eve. ‘ Гг Irins." КОНЕЙ О : i • Г! МЫ Fur >n । -M значение ;np. .-и 3B P.e у. IX e з2) На рис. 3.8 показан результат работы этой npoi раммы. Рис. 3.8. Программа demo03-06.bb Вот и все, чю нужно сказать о функциям. Или почти все... Область действия В Blitz Basic существуют две области действия: глобальная и локальная. К глобальным переменным можно обращаться из побои части программы, из любой функции и любой части кода. Локальные переменные действуют только в той функции, в которой определены. Это значит, что переменная, определенная в одной функции, не действует в другой.
Что такое область действия? Довольно трудно поняп., что такое область действия, поэтому я отправился па http://www-dictionary.com и взял оттега определение. «Область действия идентификатора — это область исходного кода программы, в которой он представляет нечто определенное. Область действия обычно начинается там, где идентификатор был объявлен, и заканчивается с окончанием наименьшего вложенного блока (Ъг нхшегкЗ или чело процедуры функции). Во внутреннем блоке идентификатор можно переопределить: в этом случае внешнее объявление не включает («затенено» или «перекрыто») область действия внутреннего объявления». Ну что? Если вы прочли это (могу поспорить, что вы бросили после слов «в которой он представляет нечто определенное»), вы, наверное, только еще больше запутались. Область действия - это диапазон, в котором к переменной можно обращаться Области действия бывают двух типов, которые позволяют программистам создавать программы, в которых присутствуют две или более переменных с одинаковыми именами. Например, в глобальной области (в основной программе) может действовать переменная с именем variab'.ex. и в функции Hi lAmAFunction () тоже будет переменная с именем vari =Ыех. В любой части программы, включая друтие функции, бсдет использоваться переменная variablex с 1лобальной областью действия, по функция HlIAmAFunctlos.O будет использовать собственную переменную с таким же именем. Приведу пример. Заметьте, что этот код не будет работать Он сотткит только для демонстрации областей действия. ;Са11Ме(} - неработающая функция CallMel) Print х Function CallMe() x = 314 End Function Результат выполнения этого кода показан на рис. 3.9. Как видите, эта программа вызывает функцию Са 11Ме , в которой переменой х присваивается значение 314. Затем х выводится на экран, но выводится О! В чем дело? Вы угадали - все дело в области действия. Вызывается функция CallMe (), в которой переменой х присваивается значение 314. Но когда управление возвращается функции main, значение переменой х стирается. И хотя в функции CallMe (} переменная х имеет значение 314, в функции гп< из значение равно 0.
Рис. 3.9. Функция CallMe () не сработала Исправить это можно несколькими способами. Например, можно сделать так: CallMe() Print "х is equal to " л CallMe() Function CallMe() x = 314 Return x End Function В этом примере функция CallMe О возвращает значение х, которое выводится на экран в функции main. Другой способ решения проблемы - использовать глобальную переменную. Глобальные переменные имеют глобальную область действия и доступны из любой части программы. Это означает, что область действия переменной х в функции CallMe () будет такой же, как и у переменной х в функции main. Чтобы создать глобальную переменную, нужно просто поставить команду Global перед объявлением переменной. ;demoO3-07.bb - исправленная функция CallMeО Global х CallMe() Print "х is equal to " * x ;задержка пять секунд Delay 5000 Function CallMe() x = 314 End Function
Результат работы этой программы показан на рис. 3.10. Рис. 3.10. Исправленная функция CallMe, Заметьте, что я написал Global х в функции main, а не в функции CallMe (). Я сделал так потому, что глобальные переменные можно создавать только в функции main. Если вам нужна глобальная область действия, вы должны создать переменную в основной части программы. Кстати, создание переменной без присвоения ей значения называется обьявлением. Присвоение переменной значения называется определением переменной В этот раз мы сделали переменную х глобальной. Затем мы присвоили ей значение 314. Теперь х имеет значение 314 в любой функции, а не только в CallMe (' Переносимый код Переносимость - важная концепция, поскольку в долгосрочной перспективе поможет вам сэкономить много времени. Чтобы можно было назвать что-то переносимым, нужно, чтобы это что-то можно было легко перемещать с места на место. Вспомните об игровых приставках Game Boy, которые вы могли недавно видеть в магазинах Переносимый код легко перемещагь. Переносимый код - это код, который не зависит от глобальных переменных. Это позволяет копировать код функций из одной программы и вставлять в другую. Например, функция преобразования Фаренгейт-Цельсий из программы demo03-06.bb переносима. поскольку вы можете просто скопировать ее код и использовать в других программах, если понадобится. Так как эта фумкцив не использует глобальные переменные, не нужно никакой дополнительной настройки. Если функция берет данные из глобальных переменных, очень сложно перенести код из одной программы в другую, поскольку в разных программах обычно используются разные глобальные переменные. Использование глобальных переменных в играх - обычное явление, но все же старайтесь применять их как можно реже. Во-первых, каждая функция имеет доступ к ним, поэтому можно случайно изменить их значение. Во-вторых, глобальные переменные делают функции менее переносимыми. Если в функции используются только параметры и
локальные переменные, можно просто скопировать ее и перенести в другую программу. Но если задействованы глобальные переменные, придется просмотреть весь код и изменить все ссы iKi I на глобальные переменные, которых нет в повой программе. Хотя сейчас это не каже1ся вам большой проблемой, вы можете изменить это мнение, когда вам придется просматривать функции, чтобы добавить их в новую npoipaMMy. Кстати, можно создать локальную переменную, добавив перед ее объявлением слово 1 cal: l.ri-nl X Если добавить L к переменной х в предыдущей программе х = Л4 то переменная х в функции так же будет равна нулю, т. к. локальная область действия имсе i преимущество перед глобальной. Таким образом, локальная переменная х получает значение 314, тогда как глобальная не изменяется. Между объявлениями ’ оп пер -ценная и переменная нет разницы, если не объявлена 1лоба.п.ная переменная. Дручимн словами, при объявлении локальной переменной директиву Lo . ’ можно опустить (но можно и оставить, если хотите сохранить ясность и стиль). Когда использовать функции Функции необходимы для программирования. Вы знаете, что должны их использовать, но когда это делать? Используйте функции всегда, koi да нужно выполнить какуюто задачу’. Я понимаю, что это очень неясный совет, но у вас должно быть хогя бы несколько функций в каждой программе, кроме самых простых. Основная часть программы должна выполнять как можно меньше работы. Все задачи должны выполняться функциями. Если задачу можно разделить на две или более, создайте для этого несколько функций Можно вызывать функции из других функций. Приведу пример: допустим, вы создаете игру с космическими кораблями, и есть функция, которая выводит изображение на экран. Вы можете создать отдельные функции для вывода на экран различных изображений: одна функция будет рисовать корабли, а другая - нули Если хотите, можете создать разные функции для вывода нуль игрока и противника и разные функции |ля изображения кораблей игрока и противника. В общем, если считаете, что функция будет полезна, создайге ее. Это не потребует кода больше, чем если просто помещать все задачи в функции ma in, и сделает ваш код более переносимым и, конечно, более читаемым.
Массивы Большая проблема программирования - создание большого числа переменных. Подумайте, как можно создать 10 переменных одного типа. Это может выглядеть так: variableO = 314 variable2 = 214 variables • 14 variable4 _ 314 variables = 14 variablefi = j • variable? = 1 ] 4 variableO = 314 VMTiable9 = :Ji He кажется ли это бесполезной трагой времени? Но представьте, что вам нужно создать тысячу переменных Это займет вечность! Рис. 3.11. Массив Как вы могли догадаться, нро|рамма Blitz Basic позволяет избежать этой проблемы. Решением будет использование массивов. Массив - это набор переменных с почти одинаковыми именами. Массия выглядит как обычная переметшая, по в конце имени имеет индекс (число в скобках). Представьте, что массив - это коробка, в которой в ряд стоят банки, и каждая банка содержит число (см. рис. 3.11). В нашем случае в каждой банке храшттся число 314, но вы можете изменять числа. Получись доступ к числу можно через счетчик массива, который выглядит как переменная i?‘i или перецени .я ' Каждая банка не зависит от остальных, но все они упакованы в одну коробку Коробка - что массив, а отдельные банки - переменные массива, в которых хранятся данные. Любая переменная, входящая в массив, может быть записана с кдующим образом-имяпеременной (ивдек-..#, Здесь имяпеременной - имя массива, а индекс# (это всегда целое число, и никогда не строка) - количество неременных в массиве Теперь мы можем создать массив. Воспользуемся переменными из предыдущего примера. Dirr variable ДС» ;Съъявпение масеигг Variable(0) = 314 Variable(1) » 114 Variable(2) 314 Variable(3) 314 Variable(4) = J14
Variable(5) = 314 Variable(6) = 314 Variable(7) = 314 Variable(8) = 314 Variable(9) = 314 Проще не стало? Это длинный способ создания массива. Если воспользоваться циклом For..Hext, можно сделать это гораздо быстрее. Вам может быть интересно, что делает оператор Dim. Буквально это означает «Dimension» (размерность, величина). Этот оператор создает область памяти, которая будет потом использоваться Перед использованием массива, его нужно объявить оператором Dim. ;demoO3-08.bb - присвоение значения 314 десяти переменным Dim variable(10) ;объявление массива For i=0 То 10 variable(i) =314 Print variable(i) Next WaitKey Результат выполнения этого кода показан на рис. 3.12. Рис. 3.12. Программа demo03-08.bb Этот код делает тоже и даже больше - выводит переменные на экран - но он гораздо короче! Массив объявляется так же, как и в предыдущем примере, а затем в цикле For..Uext каждой переменной присваивается значение 314. Просто? Можно посмотреть, как пример с коробкой и банками соответствует этой программе, на рис. 3.13
Обратите внимание, что отсчет переменных начинается с нуля. Компьютеры считают не так, как люди, они начинают с О, а не с 1. Другими словами, десятая переменная массива array (10) - это переменная array(9). Это значит, что когда вы объявляета массив, вы говорите компьютеру, сколько элементов должно быть в массиве, плюс еще один элемент. Но компьютеры начинают отсчет с О, поэтому вы будете обращаться к элементам массива, начиная с О и заканчивая п, где п - число элементов массива. Например, если массив объявлен как array (5), он будет содержать элементы array(0), array(1), array(2), array (3), array (4) и array (5). Так что в этом массиве будут доступны элементы с индексами 0, 1, 2, 3,4 и 5 - не больше и не меньше. Я часто не использую мак-симвльное число элементов и для массива array (5) использовал бы элементы 0-4. Рис. 3.13. Программа demo03-08.bb и пример с коробкой и банками И еще один пример функции. В этой программе создается ряд переменных с увеличением значения каждого из них на единицу. Пользователь может выбирать, что нужно сделать с двумя числами: сложить, вычесть, умножить или разделить. Своего рода мини-калькулятор/ ;demo03-09.bb - позволяет пользователю выполнять математические операции со значениями 0-100 ;ор1 и ор2 - глобальные переменные, поэтому доступны из всех функций ;ор1 содержит первый операнд, ор2 - второй Global cpl Global ор2 Dim array(100) ;0-100 InitializeArray() ;переменная continue имеет значение 1 до тех пор, пока программа работает continue = 1 While continue ;пока пользователь хочет продолжать ;получаем первый операнд opl = Input(“What is the first number? ") ;получаем второй операнд op2 = Input("And the second? *) ;какую операцию хочет выполнить пользователь? operators = Input("Enter +, *, or / ")
; вывод . т] PrintAn-wer ( -..«.j . ' i. ;выясняем. ₽льзователь продолжить continue [При’ '"Enetei 1 to continue or 0 to quit ") /вставка пустой строки Print "" Wend End Это инициализация и главный цикл программы-калькулятора. Программа начинается с создания двух глобальных переменных: <р1 и ор2. Это два числа, которые будут складываться. Например, в выражении 3+14, переменная opl представляет 3, а переменная ор2 представляе i 14. Затем создается массив. Массив имеет 101 элемент, начиная с array (ли заканчивая array (1С' (помните, что массивы начинаются с нулевого элемента). После объявления массива вызывается функция InitializeArray () После этого создается переменная cent mue. Эта переменная определяет, будет ли программа продолжать работе. Пока переменная continue не равна 0, цикл игры продолжает выполняться. Далее начинается главный никл Сначала он псютчает от пользователя переменные opl и pQ. После этого зацрашиваетея переменная operator, определяющая операцию, которую хочет выполнить пользователь (сложение, вычитание, умножение или деление). Затем в цикле вызывается функция rint *, - ?г для вывода результата на экран. Наконец, у пользователя спрашивается, хочет ли он продолжать работу с программой. Есин пользователь продолжает, значение переменной continue остается равным 1 и цикл начинается сначала. Если нет, программа завершает работу. Эта программа содержит две определенные пользователем функции: PrintAnswer () л initialize»,! у 1.1. Рассмотрим каждую из них. ,-эта функция у - г авт » <• - 1ия именным массива Function Im • For 1=ё То it'll array(i/ * btexi End rTKlCtltXl Эта функция просто создас г массив, который будет использоваться в последующих вычислениях. Каждый элемент массива содержит число, соответствующее его индексу: Таким образом. 14-й элемент t array i 1 j равен 13. После инициализации элементов от 0 до 100, они передаются в главный цикл для далыташппс вычислений.
Следующая функция, определенная пользователем. - ;Эта функция выводит на экр^ Q'”4<rT-выдачи hhw Funct ion Pr intAnswer(operators) Print opl + " * + operators + “ " " is equal to " nndAnswer(operatet J End Function Эта функции просто выводит на экран то. что пользователь хочет сделать. Если пользователь хоче г сложить 13 и 31, функция выведет "1j т л is squrt] 44" Вы спросите, откуда взялся ответ. Он нолкчеп при помощи последней определенной пользователем функции: F ’’ ; Эта функция выполняв - вг значениями Function FindAuBwer (operators?, Select operator Case ”+" Return array*opl' < ы r i Case •-* Return array(of . • i j . ; . Case Rt.r 4X.I u.-; dj щ • -CL t;- • Case "/" array -tr-yiupi End Sei' -. t End Function Заметьте, что если переменная oj •. или jZ больше 100 или меньше 0. программа не будет работать.
Кстати, важное замечание. В программе случится сбой, если значение переменной ор2 равно 0, a operator $ предписывает выполнить деление, так как на ноль делить нельзя. Как видите, эта функция начинается с оператора Select. Этот оператор позволяет выбрать действие, соответствующее тому, что выбрал пользователь. Если пользователь хочет умножить числа, функция умножит opl на ор2. Затем возвращенное значение будет выведено на экран функцией PrintAnswer (). Если вы попытаетесь разделить два числа, которые не делятся без остатка, вы получите верный ответ, но без дробной части. Так будет потому, что используются целочисленные переменные. Попробуйте изменить программу так, чтобы использовались переменные с плавающей точкой. На рис. 3.15 и 3.16 показан массив как коробка и проиллюстрировано сложение двух чисел. RowO 0 Row 1 1 Row 2 2 Row 100 100 Рис. 3.15. Массив Многомерные массивы Многомерные массивы схожи с обычными, но имеют больше одного измерения. В сущности, основное отличие состоит в том, что многомерные массивы имеют больше одного индекса. Чтобы представить многомерный массив, можно опять вое пользоваться примером с коробкой. Но вместо одного ряда будет два или больше, как показано на рис. 3.17. Многомерные массивы используются в том случае, когда вам нужен набор наборов переменных. Например, вы можете создать массив для пуль. Вы можете создать двумерный массив и размещать пули, выстрелянные игроком, в одном столбце, а пули противника — в другом (рис. 3.18). Теперь попробуем создать двумерный массив. Эта процедура очень похожа на создание одномерного массива, нужно только добавить в объявлении еще один индекс. Dim bullets(2,100)
Эта команда создает массив пуль, состоящий из двух частей. Первая часть определяет, кто сделал выстрел, а вторая - какая это была пуля. Каждый столбец содержит 100 пуль. Чтобы теперь использовать массив, нужно только добавить переменной второй индекс: bullets(0,23) Эта команда обращается к 24-й пуле игрока. Помните. что компьютер начинает счет с 0, поэтому индекс 28 указывает на 24-й элемент массива. Теперь давайте создадим программу. Эта программа просто создает 25 звездочек (*) и 25 знаков плюс (+). Ничего особенного тут не выполняется, но вы поймете, как можно использовать массивы, когда изучите типы в следующем разделе. На рис. 8.19 массив показан в виде таблицы. ;demo03-10 .ЪЬ - создание 25 звездочек и 25 плюсов ;создаем массив Dim starsplusses$(2,25) ;инициализация массива. Первый столбец будет содержать звездочки, второй плюсы For rows - 0 То 1 For columns - 0 То 24 ;записываем либо «*», либо «+», в зависимости ; от значения, возвращенного функцией FindChar$() starsplusses$(rows,columns) -FindCharS(rows) Next Next 0 0 1 2 3 1 4 5 6 7 2 6 9 10 11 3 97 9В 99 100 100 Рис. 3.17. Простой и многомерный массивы Column О Column! RowO 0, 0 0, 1 Row1 1, о 1, 1 Row 2 2, 0 2, 1 Рис. 3.16. Двумерный массив для пуль Column 0 Column 1 RowO Row1 Row 2 Ж ж ж । + + + Row 23 Row 24 ж ж + + Рис. 3.19. Массив starsplusses$ Этот фрагмент начинается с создания массива scarsplusses$. Так как он имеет индекс (2,25), он будет содержать 50 объектов. Как я получил вто число? Я просто умножил первый индекс на второй: 2*25=50. Далее происходит инициализация массива. Один цикл for запускается из другого. В многомерных массивах обычно используются два цикла for. Первый цикл проходит по первому индексу, второй цикл - по второму; Внешний цикл. For i = 0 То 1, выполняет отсчет от 0 до 1. Второй цикл for считает от 0 до 24. Строка
sUrapx . - .... ; .*в,ийхшш si - Fir.'JChar$ ixwws, определяет, какое значение будет иметь каждый элемент, вызывая функцию FindChar$ (). Определенная пользователем функция Fin jZhar$ () выглядит так: ; ФУНКЦИЯ I _2(DCHA ь.$ (i 1 ;возвращаем * или < Function FindCtjir > 11 If i = О Return Else It । * l Re-urn “+” Endli End Function Если цикл инициализации вызывает эту функцию с номером строки 0, в массив записывается звездочка. Если функция вызывается с номером строки 1, в массив записывается плюс. Массив будет иметь две строки по 25 символов—строка звездочек и строка плюсов. Теперь нужно вывести миссии на лкрал. ;вывод мае ива н„ экран Fez rows = 0 То 1 For column* = 0 То 24 ;выводим каждое значение на экран W. itw starsplujstsS(rows.colunis) Next ;n ie каждой строки символов переход на новую строку Print "" Next ;задержка 5 секунд Delay 5000 Эта функция также содержит два цикла тог, один из которых, запускается из другого. Внешний цикл обрабатывает строки, внутренний - столбцы. Каждый элемент выводится на экран- Koi да цикл доходи г до конца первом строки символов, выполняется переход на новую строку, чтобы можно было вывести вторую строку символов. Здесь встретилась новая функция Write. Эта функция имеет тот же прототип, что и функция Р_ in г -. Write string$ Эти две функции очень похожи. Единственное различие в том, что Write, в отличие от Print, нс переходит на новую строку автоматически. Это очень полезно при выводе на экран содержимого массива, поскольку не нужно выводить каждый элемент на новой строке. На рис. 3.20 показан результат работы программы demo03-10.ЬЬ, если заменить Write на Print.
Рис. 3.20- Программа demo03-10-bb без Wri г ₽ На рис. 3.21 показан результат работы прохраммы demo03-10.ЬЬ. Рис. 3.21. Программа demo03-10-bb Типы Вся эта глава ведет нас к рассмотрению типов, поскольку они являются очень важной и полезной частью языка программирования Blitz Basic. Тин - это просто набор связанных данных. Это звучит почти как определение массива, но типы позволяют назначать каиадой переменней! различные имена и различные типы (строковый, целочисленный и с плавающей точкой) Приведу пример. Представьте, что в вашей игре есть корабль. Компьютер должен знать, куда поместить этот корабль. Допустим, корабль должен иметь координаты 100, 100 (если вы не понимаете, что такое координаты, вы скоро это узнаете). Вы можете назначить координаты так: playerx = 100 playery = 100 Очень просто. А что, если вы хотите добавить счетчик попаданий? Нужна еще одна переменная.
playerhits =• 3 Уже три переменных. Если вы хотите, чтобы корабль мог перемещаться вверх и вниз, понадобится еще две переменных. Итого пять переменных! Unattached Player х Player у Player hits Attached Type player X У hits В таком: случае лучше всего использо- Player x = 0 PlayeAx = 0 вать типы. Это позволит связать все эти Player у = 0 PlayeAy = 0 неорганизованные переменные с одним Player hits = 0 PlayeAhits = 0 именем типа, как показано на рис. 3.22. Создадим тип для корабля: Рис-3 22' Несвязанные и связанные с типом переменные Type Ship Field х,у ;расположение корабля Field hits ;счетчик попаданий End Туре Для создания типа нужно поставить перед его именем ключевое слово Туре, после чего создать поля. Каждое поле - это отдельная переменная, являющаяся частью типа. Переменная может быть целочисленной, строковой или с плавающей точкой. После объявления типа нужно создать переменную этого типа. Эта процедура немного отличается от определения переменной одного из встроенных типов (строковый, целочисленный и тип данных с плавающей точкой - встроенные типы). Создать новую переменную, или так называемый экземпляр, можно следующим образом: player.ship = New ship Выглядит немного странно. Давайте разберемся, что к чему. Первое, что вы видите, -слово player слева отточки. Это слово относится к имени переменной, которую вы создаете. После точки вы видите слово ship. Это тип, который вы хотите назначить переменной. Эта переменная теперь имеет все поля, объявленные в типе ship. Для завершения процедуры мы приравниваем player. ship к New ship. Это позволяет создать новый корабль игрока. Вы можете создать корабль противника, просто изменив имя player на enemy. Создание нового типа почти всегда происходит по следующей схеме: instancename. typename = New typename Теперь, когда все переменные организованы в тип и создан экземпляр этого типа, мы можем установить значения переменных: player\x = 100 player\y = 100 player\hits = 3 Неплохо получилось? Для доступа к переменной используйте следующую конструкцию: instancenameXvariablename Теперь вы можете создавать, определять м обращаться к типам. Давайте на примере посмотрим, как это все работает. Я буду использовать функцию Text, имеклцую объявление
В табл. 3.3 описано, что означает каждый из этих параметров. Функция Text позволяет выводить текст на экран, как и функция Print, но предоставляет возможность выбрать координаты расположения текста. Табл. 3.3. Параметры функции Text Параметры Описание х Координата х текста У Координата у текста strings Отображаемая строка [centerx] Установите true, если хотите, чтобы текст выравнивался по горизонтали | centery) Установите true, если хотиге, чтобы текст выравнивался по вертикали Эта программа использует функцию Text для отображения на экране кораблей противников и счетчика попаданий. Вы также сможете перемещать корабль игрока и уменьшать оставшееся число попаданий. Это очень простая игра. Корабль будет представлен символами < - * -.В табл. 3.4 описаны клавиши, используемые в этой игре. Табл. 3.4. Клавиши, используемые в программе demo03-11 .bb Клавиша Функция Left arrow Перемещение корабля влево Right arrow Перемещение корабля вправо Up arrow Перемещение корабля вверх Down arrow Перемещение корабля вниз Spacebar Уменьшение числа оставшихся попаданий в корабль на одно Esc Выход из игры ;demo03-ll.bb - рисует на экране корабль, который можно перемещать и убивать Graphics 400,300 ;КОНСТАНТЫ Const STARTHITPOINTS
Const SHIPS - Const ESCKEY 1,SPACEBAR = 57.UPKEY - 200, LEFTKEY = 203,DOWNKEY = 208.RIGHTKEY 205 Const STARTX = 200 ..STATRY = 152 Это первая часть программы. Она начинается с перехода в графическим режим. Затем определяются константы. Как вы помните, константы - это переменные, чье значение не меняется на протяжении всей программы. Если вы хотите изменить значение константы -смело летайте это. Изменение отразится на всей про1рамме. Константы, определяющие используемые клавиши (ESCKEY, SPACEBAR и другие), лучше не изменять, т. к. это вызовет некоторые проблемы—вам придется искать соответствующую клавишу. Константы перечислены в табл. 3.5. Табл. 3.5. Константы программы demo03-11.ЬЬ Константа Значение по умолчанию Описание STARTHITPOINT0 3 Определяет, сколько раз вы можете уменьшить число попаданий в корабль, нажав клавиш!' Spacebar, перед окончанием игры SHIP' Символы, представляющие корабль игрока. По скольку мы не используем картинки, это просто текстовая сгрока. Если хотите изменить внешний вид корабля, измените это значение ESCKEY 1 Код опроса клавиши Esc SPACEBAR 57 Код опроса клавиши Spacebar UPKEY 200 Код опроса клавиши Up arrow LEFTKEY 203 Код опроса клавиши Left arrow D( 1Л1КЕУ 208 Код опроса клавиши Down arrow RIGHTKEY 205 Код опроса клавиши Right arrow STARTX 200 Начальная координата х корабля STARTY 150 Начальная координата у корабля Идем дальше. .•ТИПЫ Type Ship Field х.у Field hitpoints
Field shipstrinaS End Type Этот раздел определяет тины, используемые в программе. Здесь определен только тип Ship, объединяющий все переменные, необходимые для отображения корабля на экране. В тдбл. 3 (> перечислены поля i ина fi 1 о. Табл. 3.6. Типы программы demo03-1 l.bb Поле Описание X Координата x корабля. Начальное значение определяется константой J I-RTX У Координата у корабля Начальное значение определяется константой STARTY L.tpoints Число оставшихся возможных попаданий в корабль. Начальное значение определяется константой STARTHITPOINTS t..i| j string$ Внешний вид корабля. Определяется константой SHIP$ Переходим к инициализации программы. ; РАЗДЕЛ ИНИЦИАЛИЗАЦИИ G.obal cont = 1 G..jbal player.ship New ship piayer\x = STARTX player\y = STARTY playerXhitpoints • STARTHiTPOiNI : player\shipstring = SHIP$ В этом разделе определяются все переменные, используемые в программе и поля типа Ship. Первая переменная, соис, определяет, нужно ли продолжать игру. Пока пользователь хочет продолжать, переменная cont имеет значение 1. Строка Global player.ship = New ship соадает экземпляр типа Ship с именем player. Следовательно, все поля типа Ship будут доступны через имя player. В оставшейся части раздела инициализации нолям тина Ship сопоставляются соответствующие константы. Не путайте операторы «•/» и «\» Прямой слэш «/» означает знак деления. Обратный слэш «\» указывает на то, что вы обращаетесь к какой-то части типа. Перейдем к циклу игры. ;Цикл игры While cont = 1
Рис. 3.23. Главный цикл без вызова функции Cis Cis Text playerXx,playerХу, player\shipstring$ Testinput() DrawHUDO Wend ;Конец цикла Цикл игры короткий, как и должно быть. Он начинается с проверки переменной cont- Если ее значение равно 1, игра продолжается; если нет, игра заканчивается. Затем функция Cis очищает экран. Без вызова этой функции на экране будут отображаться следы прежних перемещений корабля (рис. 3.23). После этого в заданной позиции отображается корабль игрока. Затем функция Testinput () проверяет входные данные, а функция DrawHUDO выводит на экран информацию о некоторых значениях, используемых в игре. ;Функция Testinput() изменяет направление движения корабля или число попаданий в корабль Function Testinput О ,-если игрок нажимает стрелку влево, перемещаем корабль влево If KeyHit(LEFTKEY) playerxx = player\x - 3 If playerXx <= 0 player\x - 10 Endlf ;если игрок нажимает стрелку вправо, перемещаем корабль вправо If KeyHit(RIGHTKEY) player\x = player\x + 3 If player\x >= 385 playerxx = 380 Endlf Endlf
,-асли игрок нажимает стрелку вверх, перемещаем корабль вверх If KeyHit(UFKEY) player'у = player ,y 3 If player\y <• 0 player'•/ - » Endlf Endlf ;если игрок нажимает стрелку вниз, перемещаем корабль вниз If KeyHit(DOWNKEY) pleyer\y = player\y + ? If player\y -»= 285 player у • 280 Endlf Endlf ;если игрок нажимает пробел уменьшаем счетчик попаданий на ’ If KeyHit(SPACEBAR) player\hitpoints = player\hitpoints - I If player\hitpoints <=0 cont -- 0 Endlf Endlf ;если игрок нажимает Esc, устанавливаем переменной cont значение 0 и завершаем игру If KeyHit(ESCKEY) cont = С Endlf Функция Test Input () очень длинная, но очень простая. Она просто проверяет, какие клавиши нажимает пользователь, и обновляет переменные на основе этой информации. Начиная сверху, если пользователь нажимает клавишу Left arrow, корабль перемещается на три пиксела элево. Если корабль переместился слишком далеко влево
(за край экрана), корабль возвращас гея вправо. Если пользователь нажимает клавишу Right arrow, корабль смещается вправо. Если корабль ушел слишком далеко вправо, он смещается немного влево. То же самое происходит, если пользователь перемещает корабль вверх или вниз. Если пользователь нажимает Spacebar, число оставшихся попаданий в корабль уменьшается на 1. Затем программа проверяет, не достигло ли это значение пуля. Если достигло, переменной cont присваивается значение 0 и игра завершается. Последняя проверка определяет, нажал ли пользователь клавишу Esc Если это так. переменной cont присваивается значение 0 и игра завершается. ;функция DrawHUDO выводит информацию в верхнем правом углу экрана Function DrawHUDO Texi 260, 10, "X position: " + pl<.-er\x Text 260, 20. "Y position: " playerXy Text 260, । , "Hitpoints * player\hicpoints End Function Последняя функция программы, DrawHOD (), просто выводит в правом верхнем углу экрана информацию о координатах х и у и оставшемся числе попаданий. При запуске этой программы вы можете заметить снижение производительности компьютера. Это может произойти из-за запуска мини-игры без переключения страниц. Не беспокойтесь, я покажу вам, как исправлять эту проблему во второй части книги На рис. 3.24 показано, как работает цикл, а на рис. 3.25 - кадр из программы. Рис. 3.24. Главный цикл игры
Рис. 3-25. Программа demo03-11 -bb Системы координат Ненадолго оставим концепцию типов и поговорим о координатах. Координаты описывают, где на экране находится объект. Они записываются в формате х, у. Например, если объект имеет координаты 314, 18, координата х имеет значение 814, а координата у - 18. На рис. 8.26 показана координатная плоскость. Исходное, или нулевое, значение координат х и у находится в левом верхнем утлу экрана. Координаты х увеличиваются слева напрано, а у — сверх) вни i. Например, если вы хотите найти точку' с координатами 314, 13, переместитесь от начала координат на 314 по зиций вправо и 18 позиций вниз. Каждая позиция является одним пикселом на экране. Пиксел - это наименьшая точка на экране компьютера. Каждый пиксел имеет различный цвет, а собранные вместе, они создают изображение. Чтобы увидеть размер одного пиксела на вашем компьютере, запустите программу demo03-12.bb (рис. 3.27). Маленькая белая точка в центре экрана - это один пиксел. Маленький, правда? Если вы хотите нарисовать объект на экране, вы рисуете его, начиная с определенной позиции пиксела. Обычно такой позицией является левый верхний угол объекта. Так что, если вы хотите вывести текст в определенном месте экрана, нужно указывать верхний левый пиксел текста (рис. 3.28). Если вы выводите текст командой Text, вы также можете выровнять его по центру. Рис. 3.26. Система координат
Рис. 3.27. Один пиксел Text 100,100, "Hello" •(100, 100) Hello Рис. 3.28. Вывод объекта с учетом расположения пикселов For... Each... Next Тины особенно хорошо работают с циклами, Есть даже отдельный вид цикла. который работает только с типами. Это пикт For... Each.. .Next. Цикл Fur Each.. .Next позволяет создавать наборы типов и выполнять действия с ними как с единым целым. Например, используя такой цикл, можно создать набор кораблей противников за один вызов. Возьмем тип Type ship Field х,у Field hitpoints End Type Создадим группу кораблей противника, допустим, 100 штук» SeedRnd MilliSecst) For enemycounter = 0 To 99 ;100 новых кораблей enemy.ship = New ship enemy\x = Rand(1,640) enemy\y = Rand(1,480) enemy'Jiitpointa • 1 Next Мы только что создали 100 различных кораблей противника. Теперь, чтобы проверить их юсе, нужно воспользоваться циклом For... Each... Next. Этот цикл обрабатывает каждый член определенного типа. Это позволяет без труда создавать много копий кораблей про тивников и избавляться от них, когда они больше не нужны. На рис. 3.29 показано, как цикл For_Each__Next использует память. Этот особый цикл
проверяет число попаданий в каждый корабль противника, чтобы убедиться, что он еще не убит. Если убит, программа удаляет этот корабль. Может показаться, что мы создаем тот же корабль много раз. В действительности мы создаем целую группу кораблей с одинаковыми именами. При помощи цикла For...Each...Next вы можете быстро и легко проверять и изменять каждый корабль противника. For enemyships.ship = Each ship —0 1 2 100 | Enemy | | Enemy | [Enemy | | Enemy | Рис. 3.29. Экземпляр enemyships в памяти Fcr enemyships. ship = Each ship If hitpoints <= 0 Delete enemyships Endlf Next Очень просто! Этот фрагмент кода проверяет каждый из кораблей и удаляет их, если счетчик попаданий равен или меньше 0. Можно посмотреть, как цикл For.. .Each.. .Next работает в памяти, на рис. 3.30. For enemyship.ship = Each ship Enemy г и >1 >2 Enemy Enemy ^100 [Enemy Рис. 3.30. Цикл enemyships в памяти Вы можете поинтересоваться, почему программа определяет, равно или меньше нуля значение счетчика попаданий. Корабль удаляется, если значение счетчика уменьшается до 0, как это значение может быть меньше нуля? Причина этого заключается в том, что счетчик может получить значение -1, если в корабль попали дважды за один раз. Поэтому лучше перестраховаться, чем пропустить ошибку. Мораль: всегда проверяйте необычные условия. Можно легко доработать этот цикл, чтобы изменялись значения х и у кораблей противника. Например, если добавить направление х или у, можно заставить корабли перемещаться случайным образом. Вы можете изменить тип следующим образом: Type ship Field х,у Field directionx,directюпу Field hitpoints End Type Затем в цикле инициализации нужно сделать случайными значения направления (положительное значение directionx перемещает корабль вправо, положительное значение directiony перемещает корабль вниз).
enemy\directionx = Rand(-3,3/ enemy\directiony - Rand(-3,3/ И наконец, вы можете добавить в итоговый цикл код, который будет перемещать корабли противника: enemy\х = enemy'х enemy' directionx enemy\y =• enemy\y 1 enemy direction^ Если вы соберете этот код в программу и понаблюдаете за кораблями противника, вы увидите, что коребли оставляют за собой следы. Это происходит потому, что предыдущие изображения не удаляются. Если хотите исправить эту оплошность, просто добавьте команду очистки экрана rise начало цикла игры. Поздравляю, вы создали анимацию! Собираем все вместе: Textanoid! Сейчас, используя все, ч го мы узнали, мы можем создать игру. Это будет прог гая текстовая версия игры Arkanoid, в которой будет использовано все, что мы узнали в этой длинной ыаве. Поскольку это будет текстовая версия, основными командами будут Text и KeyDown. Общий смысл игры - избавиться от всех блоков, ударяя их шаром. Игрок управляет платформой, которая может двигаться влево и вправо. Игрок старается не допустить, чтобы шар ударился о нижнюю часть игрового поля. Каждый раз, когда игрок очищает поле от блоков, он переходит на новый уровень. Теоретически, можно переходить на новые уровни бесконечно, т. к. сложность никогда нс возрастает, но я готов поспорить, что игроку это быстро надоест. Полный исходный код игры можно найти на компакт-диске в файле demo03-13.bb. Начинающему программисту может быть сложно понять эту игру, но я буду помогать при появлении в коде сложных мест. Начнем с определения типов. /ТИПЫ Type paddle /тип игрока Field х,у /координаты End Туре Туре ball Field х,у Field directionx, directтопу End Type Чтобы лучше разобраться с этим, см. рис. 3-31-
Эти типы определяют Mi рока и шар. С координатами х и у все понятно - они определяют позицию каждого объекта на экране, но переменные directi^nx и directiony могут показаться странными. Dx = 5 Dy = 5 Dy Player New Position Заметьте, что я решил не создавать тип для блоков. Я думаю, для этого лучше подойдет массив. Чтобы поупражняться, попробуйте создать для блоков тип. Player Original Position Рис. 3.31. Принцип работы переменных Dil«^ti4>nX и Dir<=r-bi rmV Эти переменные определяют, как будет двигаться шар: переменная direct юпх отвечает за движение вправо и влево, а переменная direr": iony - за движение вверх и вниз. На рис. 3.31 direct юпх перемещает платформу влево, a directiony - вверх. Новое положение платформы будет выше и левее исходного. Затем идет раздел констант: {КОНСТАНТЫ Cnast BLOCKSTRINGS = "ХХХХХХХ" Cor_«t PADDLESTRINGS = "----“ C.ofct BALLSTRINGS = "0* Coast BLOCKROWS = i c-est BLOCKCOLUMNS = 6 Const BLOCKXGAP =85 C^ast BLOCKYGAP = 32 Const BLOCKXORIGIN 16 Cc«st BLOCKYORIGIN = 8 Global BLOCKHEIGHT = FontHeight(j Gobal BLOCKWIDTH = Len(BLOCKSTRINGS) • Fontwidth() G' >bal PADDLEHEIGHT = FontHeight() Gleba1 PADDLEWIDTH = Len(PADDLESTRINGS) * Fontwidth(' Glebal BALLHEIGHT FontHeight() Global BALLWIDTH = Len(BALLSTRINGS) * Fontwidth(i Coast STARTX = 300 Const STARTY = 340 Const ESCKEY = 1,LEFTKEY = 2 3,RI -<?KEY = . Описание констант см. в табл. 3.7. Кстати, функция FontHeight () (которая используется в каждой переменной, определяющей высоту) возвращает высоту в пикселах выбранного шрифта (об изменении шрифта вы узнаете позже) Функция
FontWi<3th(J возвращает ширину одного символа в выбранном шрифте. Функция Len возвращает число символов в строке. На рис. 3.32 показано, что возвращают функции Fontwidth () и Len для произвольной строки. Рис. 3.32. Функции Len и Fontwidth К несчастью, из-за какой-то ошибки в демоверсии программы BlitzPIus функция Fontwidth () не работает. Однако FontHeight {) работает прекрасно. Будем надеяться, в следующей демоверсии зга ошибка будет исправлена. Если появится какой-то способ исправить эту ошибку, я сообщу об этом на своем Web-сайте http://www.maneeshsethi.com Табл- 3.7. Константы программы Textanoid’ Переменная Описание BLOCKSTRING Определяет внешний вид блока PADI )LES—RING Определяет внешний вид платформы BALLSTRING Определяет внешний вид шара BLOCKROWS Число строк в блоке BLC~KCOLOMNS Чисто столбцов в блоке BLOCKXGAP Чис по пикселов между столбцами BLOCKYGAP Число пикселов между строками ELOCKXORIGIN Чисто пикселов от верхнего левого угла окна до первого столбца SLOCKYORIGIN Число пикселов от верхнего левого угла окна до первой строки BLOCKHEIGHT Высота блока
Табл. 3-7. (окончание) Переменная Описание BLOCKWIDTH Ширина блока PADDLEHEIGHT Высота платформы FADDLEWIDTH Ширина платформы BALLHEIGHT Высота шара BALLWIDTH Ширина шара STARTX Начальная координата х игрока STARTY Начальная координата у игрока ESCKEY Код опроса клавиши Esc LEFTKEY Код опроса клавиши Left arrow RIGHTKEY Код опроса клавиши Right arrow Вам может быть интересно, почему height и width - глобальные переменные а не константы. Это так потому, что постоянное значение никогда не может быть переменной. Функция FontHeight () может возвращать различные значения, значит, нужно воспользоваться переменной. Поскольку переменные HEIGHT и WIDTH требуются на протяжении всей программы, я сделвл их глобальными. Далее идет раздел инициализации. ;Инициализация SeedRnd MilliSecs() Glogal score = О Global blockhite = 0 Global level = 1 Dim blocks(BLOCKROWS,BLOCKCOLUMNS) Global ball.ball = New ball Global player.paddle = New paddle N#cLevel() Далайте обсудим этот раздел. Сначала команда SeedRnd устанавливает генератор случайных чисел. Затем создаются переменные score, blockhits и level. Переменная score хранит очки, набранные игроком, blockhits сообщает, сколько раз игрок выполнил удар, a level определяет, на каком уровне игрок находится. Все эти переменные используются в функции DrawHUDf).
Что такое SeedRnd? Вам, наверно, интересно, почему я всегда выполняю команду SeedRi.J Millisecs ( перед использованием (функции id. Дето в гом, что компьютеры не выполняют-случайных действий. Oini созданы для точного выполнения определенных задач и не могтт создавать по-настоящему случайные числа. Поэтому при использовании функции Rand самой по себе, программа будет генерировать одни и те же числа снова и снова. Функция S₽edRnd использус гея для изменения начального числа генератора случайных чисел, чтобы каждый раз получались разные числа. Ml111Seer1' - подходящая функция для установки генератора случайных чисел, поскольку никогда не возвращает одинаковых значений. Команда Dim blocks(BLOCKROWS,BLuCKCOLUMN" создает многомерный массив с именем blocks. Если помните, многомерный массив похож на обычный, по имеет как строки, так и столбцы. .Это отлично подходит для создания блоков. На рис. 3.3S изображены строки и столбцы блока с их индексами. Сто.|бцы нумеруются сверху вниз, а блоки - слева направо. column 0 column 1 column 2 column 3 rowO 001 1011 1021 H row 1 E и H H row 2 20 H s H row3 El 0 s H Рис. 3.33. Строки и столбцы Следующие две переменные - ball и player - создают тар и игрока из соответствующих типов. Наконец, вызов функции NewLevel ( инициализирует уровень. Эта определенная пользователем функция создаст все блоки и устанавливает шар и платформу. Вот определение этой функции: Function NewLevel') For rows = 0 To 3L0CKRCWS
For cols - 0 То BLOCKCOLUMNS 1 blocks(rows,cols) 1 Next Nr ct Re-setLevel t) Enc. Funct i on Первый цикл for перечисляет все строки, а второй - столбцы. Обратите внимание, счет в циклах идет до числа строк и столбцов минус один. Вычитание нужно потому, что элементы массива начинаются с 0. На рис. 3.34 видно, что счетчнк проходит через каждый столбец в первой строке, прежде чем переместится на следующую строку и начнет снова. Во всех двойных циклах for, обрабатывающих блоки, все столбцы в первой строке перечисляются перед переходом па следующую строю'. Каждый из блоков имеет значение 1, что означает, что они выводятся на экран (если они уничтожены, устанавливается значение 0). For rows.. For COlS.: Next Next -->^ЙГ| | 01 | [ 02 | | 03 | | 04 | | 05 | ->™] 0 0 0 0 0 —>~20~[ [21 | р2 | | 23 | | 24 | | 25 | Рис. 3.34. Циклы for Следующая строка вызывает функцию Reset Level (), которая определена так: Function ResetLevein Ы111\х = 320 ЬсЦ\у = 150 ha Ll\directiony = 12 be ll\directionx = Rand(-5,5) player x = STARTX fplayerXy = STARTY ttelay 500 £i.d function
Эта функция устанавливает начальные значения переменным игрока и шара. Шар появляется в центре верхней части экрана, игрок появляется на постоянной начальной позиции. Шар двигается к платформе с шагом 12 пикселов в кадр и случайно смещается вправо или влево. Однако иногда случайность движения шара может вызывать проблемы. Возможно, что directionx будет иметь значение 0, тогда шар будет двигаться только вверх и вниз, без какого-либо смещения вправо и влево. Я оставил в программе эту особенность, чтобы показать проблему со случайными функциями и дать вам возможность поупражняться. Постарайтесь исправить программу так, чтобы переменная directionx никогда не получала значение 0. Это был раздел инициализации. Переходим к циклу игры. While Not KeyDown() Cis DrawHUDO TestInput() DrawBlocks() DrawPaddle() CheckBallO Flip Wend Как видите, цикл не делает почти ничего, кроме вызова других функций. На рис. 3.35 показана схема функций этой программы - какие функции вызывают другие функции и т. д.
I DrawHUD() Рис. 3.36. Функция DrawHUD () Первая вызываемая функция DrawHUD (). На рис. 3.86 можно увидеть, что эта функция просто показывает игроку, на каком уровне он находится, сколько очков имеет и сколько блоков сбил. Function DrawHUD() Text 0,440, "Level: " + level .•вывод уровня Text 0,450,"Score.- ° <- score ; вывод очков Text 0,460,"Block Hits: " + blockhits ;вывод числа сбитых блоков End Function Обратите внимание на координаты. Координата х равна 0, это означает, что текст выводится в левой части экрана, а координаты у равны 440. 450 и 460, что довольно близко к нижнему краю (общая высота окна игры устанавливается функцией Graphics в начале программы и равна 480). Следующей вызывается функция Testinput (). проверяющая, хочет ли игрок переместить платформу или выйти из игры. Function Testinput(j If KeyDown(ESCKEY) ;нажата клавиша Esc End ;выход из игры Elseif KeyDown(LEFTKEY) ;нажата певая стрелка player\x = player\х - 10 ;перемещаем платформу влево Ilself KeyDown(RIGHTKEY) ;нажата правая стрелка player\х - player\х * 10 ;перемещаем платформу вправо Endlf End Function Напомню, что функция KeyDto.-m < scancode1 определяет нажатие клавиш. Здесь эта функция проверяет нажатие Esc, Left arrow и Right arrow. Если игрок нажимает Esc. игра завершается. При нажатии Left arrow или Right arrow платформа движется. Следующая функция - DrawBlocks (). Эта функция проходит в цикле через каждый блок и рисует его, если он равен I. Если блок равен 0 (блок получает значение 0, когда сбит шаром), он не рисуется. function DrawBlocks() х = BLOCKXORIGIN у = BLOCKYORIGIN
;эта переменная создает новый уровень, если не осталось блоков newlevel = О ;для всех строк For rows = 0 То BLOCKROWS -1 ;сбрасываем позицию строк х = BLOCKXORIGIN For cols = 0 То BLOCKCOLUMNS - 1 ;если блок существует, выводим его на экран If (blocks(rows,cols) = 1) Then Text x,у.BLOCKSTRINGS newlevel - newlevel * 1 Endlf ;переход к следующему блоку х = х + BLOCKXGAP Next «•переход к следующему столбцу + BLOCKYGAP Next If newlevel = О level = level + 1 NewLevel() Endlf End Function Возможно, вам трудно с этим разобраться, но я вам помогу. Функция начинается с установки переменным х и у значений BLOCKXORIGIN И BLOCKYORIGIN. На рис. 3.37 показано, как эти переменные определяют, насколько далеко от верхнего левого угла расположен первый блок. Переменная newlevel определяет, остались ли еще несбитые Рис. 3,37. Исходные координаты х и у
блоки. Если найден блок, эта переменная увеличивается на 1. Если в конце функции значение aewlevel равно 0, создается новый уровень. В функции есть два цикла for, проходящие через строки и столбцы блоков (как в функции NewLevel). Между циклами вставлена строка X = BLOCKXORIGIN Эта строка устанавливает переменной х значение BLOCKXORIGIN после того, как проверены все столбцы одной строки. Эта строка необходима; если ее не вставить, программа будет считать, что вторая строка начинается за пределами экрана (рис. 3.88). Рис. 3.38. Функция DrawBlocks () со сбросом значения х и без него Следующие несколько строк проверяют каждый блок: I- (blocks(rows,cols) li liien ;если блок существует Text х,у,BLOCKSTRINGS newlevel = newlevel t- 1 Endlf На рис. 3.39 показано, как проверяется каждый блок. Если текущий блок равен 1, он рисуется; если нет. то не рисуется. Для продолжения игры на текущем уровне должен остаться Рис. 3.39. Проверка блоков
хотя бы один блок; если блоков нет, переменная newlevel не увеличивается и остается равной нулю. И последняя перед командой Next строка в цикле, обрабатывающем столбцы: х = х + BLOCKXGAP Эта строка перемещает переменную х на следующий блок. Константа BLOCKXGAP хранит число пикселов между блоками в строке (иначе говоря - между столбцами). Когда все столбцы в первой строке проверены, цикл переходит на следующую строку. Это выполняется добавлением значения интервала к переменной у: у = у + BLOCKYGAP Аналогично BLOCKXGAP, константа BLOCKYGAP хранит число пикселов между строками. Когда все блоки в строке проверены, координата у перемещается вниз на несколько пикселов, чтобы начать вывод новой строки. В последних строках функции проверяется переменная newlevel, чтобы выяснить, были ли сбиты какие-нибудь блоки. Если не были и переменная newlevel равна О, уровень повышается и вызывается функция NewLevel (). Этот вызов начинает новый уровень и заново рисует все блоки. В цикле игры есть и следующая функция - DrawPaddle (). Эта функция очень проста: Function DrawPaddle() Text player\х.player \у,PADDLESTRINGS End Function Единственное, что делает эта функция, - рисует платформу с заданными координатами. Наконец, в цикле игры вызывается последняя функция - CheckBall (). Function CheckBallО UpdateBallO ,-перемешаем и рисуем шар CheckBallWithPaddle() CheckBallWithBlocks() CheckBallWithWalls() End Function Это самая большая функция программы. Сначала она обновляет позицию шара. Function UpdateBallf) ball\x = ball\x + ball\directionx .-перемещаем шар влево или вправо ball\y = ball\y + ball\directiony .-перемещаем шар вверх или вниз Text ball\x.ball\y .BALLSTRINGS ;рисуем шар End Function Эта функция начинается с перемещения шара, исходя из значений переменных directionx и directiony, а затем рисует шар на экране.
Далее функция CheckBall () вызывает функцию CheckBallWithPaddle () Function CheckBallWithPaddle() If ball\x >= player\x And ball\x <- player\x + PADDLEWIDTH And ball\y + BALLHEIGHT >= player\y And ball\y + BALLHEIGHT <= player\> » PADDLEHEIGHT ball\directiony = -ball\directiony + Rand(-3,3) Endlf End Function Эта функция очень проста. Оператор If определяет, ударился ли шар о платформу. Понять эту проверку может быть трудно, поэтому я объясню. Понять, как работает проверка, вам поможет рис. 3.40. Здесь проверяется, попадает ли координата х шара в промежуток между левым и правым краями платформы. а координата у между нижним и верхним ее краями. Если шар столкнулся с платформой, переменная directiony меняет знак, и шар начинает двигаться вверх, а не вниз. Также увеличивается скорость на значение между -3 и 3 (если опа увеличивается на трицательное значение, шар замедляется). После этого функция CheckBall () вызывает функцию CheckBallWithBlocks (), которая проверяет, сбил ли шар какие-нибудь блоки. Function CheckBallWithBlocks() ,-у - первая строка у = BLOCKYORIGIN For rows = 0 То BLOCKROWS
.устанавливаем х на первый блок столбца х = BLOCKXORIGIN ;дпя каждого столбца блока For cols = 0 То BLOCKCOLUMNS - 1; ;если он существует If blocks(rows.cols) ;если шар сбил блок, удаляем блок If ballXx >= х And ballXx <= x + BLOCKWIDTH And ballXy >= у And ballXy <= у + BLOCKHEIGHT blocks(rows.cols) = 0 ;удаляем блок ballXdirectiony - -rballXdirectiony •* Rand(-2.2) .меняем направление и добавляем случайную величину score = score +75 blockhits = blockhits + 1 ;нельзя сбить более одного блока за раз, :поэтому выходим из функции Return Endlf Endlf ;переходим к следующему столбцу х = х + BLOCKYGAP Next ;переходим к следующей строке у = у + BLOCKYGAP Next End Function
Эта функция может показаться сложной, но она во многом похожа на DrawBlocks (). Первое, что она делает, - установка исходных координат. Затем начинается цикл проверки строк и сбрасывается значение х, как и в функции DrawBlocks. •). Затем в цикле проверки столбцов проверяется существование блока, если блок существует, проверяется, столкнулся ли с ним шар. Если столкнулся, блок удаляется (ему устанавливается значение 0), меняется направление движения шара и его скорость. Наконец, обновляются очки, увеличивается значение переменной blockhits, и функция завершается (поскольку шар ие может сбить два блока за один раз). Последнее, что делает функция CheckBall (). - проверка столкновения шара с краями экрана. Function CheckBallWithWalls() ;если шар ударился о левый край, меняем его направление и скорость If ball\x <=0 ball\directionx = -ball\directionx + Rand(-2,2) ,-если шар ударился о верхний край, меняем его направление и скорость Elself ball\у <=0 ball\directiony = -ball\directiony + Rand(-2,2) ,-если шар ударился о правый край, меняем его направление и скорость Elself ball\x >=640 - BALLWIDTH ball\directionx = -ball\drrectronx + Rand(-2,2) ,-если шар ударился о нижний край, уменьшаем очки за пропуск шара Elself ball\y >=480 score = score - 200 ;сбрасываем уровень ResetLevel() Endlf End Function Если шар ударяется о верхний, левый или правый края, он отскакивает от них. Если он достигает нижнего края (если игрок упустил его), отнимается 200 очков и сбрасывается уровень. Взгляните на рис. 3.41. Это законченная версия игры Textanotd!.
Рис. 3.41. Textanoid! Заключение Это была сложная глава. Мы узнали о циклах, функциях, массивах и типах и создали пераую анимированную игру! Наверно, это одна из самых важных глав книги. Вы познакомились с основой любой программы Blitz Basic и теперь можете создать любую текстовую программу, какую сможете вообразить. Предлагаю сделать перерыв, чтобы усвоить и понять то, что прочитали. Перечитайте непонятные места и просмотрите листинги. Кроме того, прочитайте и постарайтесь понять игру из главы 1. В ней используется все, что вы изучили в этой главе, и добавлено немного графики. Если у вас есть вопросы, обращайтесь на мой сайт www.maneeshsethi.com или пишите на адрес maneesh@maneeshsethi.com Если хотите, могу предложить упражнение. Когда вы играли в последнюю игру, вы могли заметить, что иногда шар двигается только ввера-вниз или замирает до полной остановки. Попробуйте исправить эти недостатки, чтобы шар не замедлялся слишком сильно, не останавливался и не переставал двигаться влево или вправо (подсказка: проверяйте переменную directionx, чтобы шар не двигался только вверх-вниз). В этой главе мы рассмотрели следующее: циклы*. функции;
массивы*. типы; создание игры Textanoid! Эта глава завершена. Немного отдохните и повеселитесь. Я буду ждать вас в любое время, когда вы захотите узнать что-нибудь еще.
ГЛАВА 4 Фактор стиля Я собираюсь сделать эту ыаву короткой и простой, чтобы мы могли перейти к работе с графикой как можно скорее. Вы можете задать вопрос: что такое стиль? Мой компьютерный словарь отвечает па это: характерное, выполненное по определенным правилам оформление. Я считаю, стиль — это не только внешний вид. Стиль — это чувство. На один код я смотрю с презрением, а на другой — с пониманием, просто из-за того, что они вызывают различные ощущения. Но, конечно, чтобы достичь возникновения ощущений, нужно позаботиться о внешнем виде. Стиль компьютерного программирования - это создание кода, который можно понять и прочитать. Кода, который можно видеть каждый день и нс начать его ненавидеть. Стиль - одна из наименее освещенных тем программирования. Эта глава вкратце рассказывает об общих принципах стилей и ведет вас к созданию собственного. Часто можно заметить, что код, который отвратительно выглядит, и работает не лучше. Нелогичный и трудный для понимания стиль обычно приводит к тому, что создается некачественный код. Старайтесь ясно оформлять код, и ваши программы будут лучше работать Разработка стиля У каждого есть собственный стиль программирования, это неизменный факт. Нет двух людей, которые бы работали с кодом одинаково. В сущности, все, что вам нужно для создания собственного стиля, - понять, что вам подходит, и постоянно этому следовать. Правило номер один: будьте последовательны. Начнем с основной составляющей стиля - с пустого пространства Пустым пространством могут быть пробелы, табуляция и переходы на новую строку. В большинстве слу-чаев в начале и конце любой строки может быть сколько угодно пустого пространства. Пустое пространство можно также вставлять между' командами проверки (такими как <<» и «>») и тем, что проверяется (рис. 4.1).
Рис. 4.1. Пустое пространство Пустое пространство и отступы Использование пустого пространства проще всего объяснить на примерах. В первом фрагменте кода, который я пока»)’, используется пустое пространство. Именно на него должен походить ваш код. Сейчас неважно, что делает этот код, важно то. как он выглядит. For х = 0 То 10 If х > 5 Print "х is greater than 50; it's equal to * + x + "." If x >7 Print "Wow, x is really high, it's'+x-r” “ Endlf Else Print "Too bad, x is less or equal to 50." Endlf Next Неплохо выглядит, да? Довольно легко понять, какому оператору I f соответствует какой оператор Endlf, не так ли? Теперь приведу пример кода, в котором совершенно отсутствует пустое пространство. Попробуйте, поймите его! For х - 0 То 10 If х > 5 Print "х is greater than 50; it's equal to • * x + "." If x > 7 Print "Wow, x is really high, it's " +x+ • Endlf Else Print "Too bad, x is less or equal to 50." Endlf Next Этот код гораздо труднее понять. Если вы действительно котите разобраться с его назначением, придется внимательно отслеживать каждый оператор If. Теперь
представьте, что этот фрагмент в 10 или 15 раз длиннее (ведь чем сложнее игра тем больше нужно написать кода) Будет ужасно сложно понять такой код, и это потребует уйму времени. Нужно знать еще кое-что: пустое пространство абсолютно не влияет на результат работы программы. В выходных данных не будет ни лишних пробелов, ни дополнительных строк. На рис. 4.2 и 4.3 показаны результаты работы обеих программ: с пустым пространством в коде и без него. Рис- 4.2. Результат работы программы с пустым пространством в коде Рис. 4.3. Результат работы программы без пустого пространства в коде Комментарии Я уже немного рассказывал о комментариях, но сейчас объясню их подробно. Комментарии, как вы знаете, это текст, который вставляется в код, чтобы объяснить, что вы делаете. В программе код выглядит так: Print "Это выражение." ;а это комментарий
Обратите внимание на точку с запятой перед комментарием. Точка с запятой обязательна, она показывает компилятору, что начался комментарий. В комментариях объясняется, как работает часть программы - либо отдельное выражение, либо целый фрагмент кода. Я пишу по одному комментарию почти для каждой строки программы. Это помогает, поскольку после завершения работы над программой я часто забываю, что я пытался сделать. Через несколько дней или недель я снова берусь за работу, и комментарии объясняют назначение кода. Комментарии применяются не только для описания отдельных строк кода. Обычно я вставляю в начало программы блок комментариев, в котором описывается, для чего нужна эта программа. Часто я использую для этого большое поле, чтобы привлечь внимание к тексту. Вот пример (это начало программы demo04-01.bb, полный листинг см. на компакт-диске): HellcWcrld.bb; Автор - Маниш Сети;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Эта программа выводит на экран текст "Hello World";;;; Входные переменные не требуются; Как видите, этот блок комментариев является вступлением к программе Hello World. Я помещаю подобное поле в начало большинства программ. Тут говорится о том, как называется программа, кто автор, что делает эта программа, и предоставляются какие-то дополнительные сведения. Вы можете вставлять в код программ дополнительные строки. Может быть, вы захотите указать версию программы или дать ссылки на людей, которые помогали в ее создании. Возможно, есть какие-то ограничения («Эта программа не будет работать на Windows ХР») или что-то в этом роде. Дальше идет сам код программы. С комментариями он может выглядеть так: Эта программа сложнее, чем нужно. Нет смысла использовать функции и переменные в такой простой программе. Я сделал это только для того, чтобы показать примеры применения комментариев. ;ПЕРЕМЕННЫЕ ;На экран выводится строка приветствия hellostr$ = "Hello World!" ;КОНЕЦ ОБЪЯВЛЕНИЯ ПЕРЕМЕННЫХ .-ОСНОВНАЯ ЧАСТЬ ПРОГРАММЫ ;Передаем переменную hellostr$ функции Printstring Printstring(hellostr$) ;Ожидаем пять секунд перед завершением работы Delay 5000
;КОНЕЦ ОСНОВНОЙ ЧАСТИ ПРОГРАММЫ ФУНКЦИИ функция PrintString(string$I;;;;;;;;;;;;;;;;;;; Эта функция выводит на экран переменную strngS Параметры: strngS - выводимая на экран строка Funct ion Print String(s tring$) ;Вывод строки на экран Print strng$ End Function ;КОНЕЦ ОБЪЯВЛЕНИЯ ФУНКЦИЙ Результат работы этой программы показан на рис. 4.4. Рассмотрим некоторые комментарии. Рис. 4.4. Hello World! Комментарии перед программой Перед основным кодом программы я вставил несколько разделов с комментариями, которые я называю предирограммными. Сюда обычно входят локальные и глобальные переменные, константы, массивы и все остальное, что вы объявляете перед началом программы. Перед каждой секцией я добавив комментарий, объясняющий, что будет происходить ниже. Например, в программе demo04-01 .bb я создал раздел переменных. В конце объявления я добавил строку, сообщающую о конце секции (КОНЕЦ ОБЪЯВЛЕНИЯ ПЕРЕМЕННЫХ). Я добавил комментарии и для каждой переменной в отдельности, чтобы объяснить, для чего каждая из них служит. Если потом нужно будет выяснить, что за переменная встретилась в коде и каково ее значение, это будет проще сделать, если обратиться к верхней части кода.
Комментарии основной части программы В начале и внутри основной части программы я вставил несколько простых комментариев. В начале стоит комментарий, показывающий место, где начинается основная часть программы. Я гак кс добавил комментарии нос te выражении Комментарии основной часш программы показывают, где начинается и заканчивается основной цик.’1 игры. Такие комментарии я ставлю в начале н в конце цикла While...».'end. Обычно комментарии включаются рядом с вызовом функции, например, как рядом с вы юном I ] int S, rec*., . >qS i в demo04-01 .bb. и описывают эту функцию. Комментарии функций Комментарии должны писат ься в начале каждой функции. Я обычно начинаю определение функций сразу после окончания основной части программы. Соответственно, я вставляю комментарий ;Ф’/НКПНЯ cpa.iv за комментарием ; KOHEU ( НОВНОЙ ЧАСТИ ПРОГРАММЫ. Памятка: различие между объявлением и описанием В этой главе я часто использую термины объявление и описание, поэтому нужно объяснить их различие. Объявление просто ссылается на функцию, говорит о том, что она есть, а в определении она создается Например, Print Str ing t scringS l ~ это объявление функции PnntString. Определением же будет следующий код: Funct 1 гг. г ri я» • । пц • г.г т : I Итак, когда я говорю об объявлении функции, я имею в виду вызов этой функции или ее название. Когда я говорю об определении функции, я имею в виду код этой функции. Перед тем, как определить функцию, я все|да создаю блок, объясняющий ее назначение. В программе demo04-01.ЬЬ комментарии для функции PlIt, *,x.ingistring$) выглядят так: Функция Рг_- :._С1 _ng(Stri Г1 ; ; ; ; ; ; ; ; ; ; ; Эта функция выводит на ...эменную Параметр . str экт . . Как вид иге, в этом блоке сообщается имя функции, ее назначение и параметры. Обязательно добавляйте- подобный блок перед каждой функцией - это значительно облегчит их понимание.
Имена функций и переменных Корректные имена переменных помогут решить множество проблем в программах. Не зная, что делает переменная, можно попасть в затруднение. Придется просматривать программу с самого начала, чтобы понять это. Если давать переменным понятные имена, такой проблемы не возникнет. Имена При объявлении и определении переменных выбирайте имена, которые описывают назначение переменных. Например, при создании программы Hello World я мог назвать переменную как угодно. Я мог выбрать, например, такие имена: i$ row$ howareyou$ _123$ hellostr$ Но, по определенной причине, я этого не сделал. Большинство этих имен не несет никакого смысла. Например, зачем называть строковую переменную howareyou? Конечно, такое имя подходит, если я спрашиваю пользователя, как у него дела. Вы можете поинтересоваться, почему я не выбрал имя hellostr$, ведь для этой программы оно вполне подойдет. Однако в большинстве программ значение переменной из меняется, поэтому, если дать переменной имя, которое точно говорит о ее значении, а не о типе этого значения, это создаст ту же самую проблему, которой надо избежать. Если вы измените программу так, что hellostr$ будет хранить текст «Сегодня мой день рождения», имя hellostr$ потеряет смысл. И может случиться так, что понадобится менять имена всех переменных программы. Формат имен Формат имени вашей переменной зависит от вас. Нет высеченных на камне правил именования функций и переменных. Единственное, что требуется. - формат должен оставаться последовательным. Вот несколько разных способов назвать одну и ту же переменную: hellostr$ Hello_Str$ helloStr$ HelloStr$ Hellostr$ Как видите, все эти переменные похожи, только имена немного различаются.
Для обычных переменных я использую первый вариант: оба слова печатаю строчными символами. Некоторые разделяют слова символом подчеркивания, другие начинают слова с заглавных букв. Функции также можно называть различными способами. Например; Printstring printstring Print_String printString Printstring Printstring Для функций я обычно использую первый вариант: два слова без пробела, оба начинаются с заглавных букв. Вы можете использовать любой способ, но используйте его постоянно. Для констант, глобальных переменных и массивов можете применять другие варианты. Имена констант я печатаю заглавными буквами, вот так: Const CONSTANT = 1 Глобальные переменные я называю так же, как и обычные: globalvar = 10 Многие программисты добавляют а в начало названия глобальной переменной. Я этого нс делаю, но вы можете попробовать, если хотите. В именах массивов я по возможности использую одно слово, которое печатаю строчными буквами: Dim arrey(numofelements) Заключение Надеюсь, вам понравилась эта глава. Я старался дать лучшие объяснения, какие только мог. Эту главу я написал потому, что считаю стиль и ясность очень важной частью любой программы, а также потому, что о стиле очень мало информации. Конечно, ничему, сказанному в этой главе, не нужно следовать в точности. Стиль - это индивидуальная вещь; то, что может понравиться одному, нс понравится другому Попробуйте применить все предложенные стили, чтобы понять, какой из них вам подходит. Единственное, что я строго рекомендую, - ваша программа должна быть простой и понятной. Не нужно использовать сложные команды, если создаете простой фрагмент кода, даже если при этом он станет чуть д линнее. Старайтесь сделать так, чтобы ваш код читался как рассказ - пусть он будет организованным и последовательным. Комментируйте предпрограммный раздел и каждую строку кода, которая того требует. Делайте стиль последовательным; если в имени
одной функции между двумя словами стоит символ подчеркивания, в имени следующей тоже используйте его. Экспериментируйте, и, в конце концов, вы разработаете собственный стиль. В этой главе мы рассмотрели следующее: разработка стиля: комментарии; имена функций и переменных. Мы только что закончили первую часть. Сделайте перерыв, если хотите, или сразу начинайте вторую часть. Мы. наконец, переходим к графике. Я обещаю, это будет весело!
Часть 2 Введение в графические средства Глава 5 Начинаем работать с графикой...........119 Глава 6 Переключение страниц и работа с пикселами....141 Глава 7 Основы программирования изображений....186 Глава 8 Анимация...............................228 Глава 9 Обнаружение конфликтов.................249
ГЛАВА 5 Начинаем работать с графикой Итак, приступим! Сегодня мы узнаем, как использовать в программе графику: Эта глава станет для вас огромным шагом вперед, вы научитесь инициализировать графическое окно и загружать изображения. Вы узнаете, как показывать изображения и перемещать их по экрану. Готовы? Эта глава покажется простой, но она научит вас очень важным вешам! Создание графического окна Г фическое окно немного отличается от текстовых окоп, которые мы использовали до сжх пор. Программы, которые мы создавали раньше, показывали только текст, графические окна могут показывать и изображения. Еще они умеют менять цвет текста Л каждой графической программе BlitzPIus есть строка кода, которая инициализирует кно. Этот процесс создает окно для дальнейшего использования. Чтобы создать гра- (фическое окно, вызовите функцию Graphics. Эта функция объявляется так: 'Graphics width, height, color depth, [model В табл. 5.1 описывается каждый параметр. Табл. 5.1. Параметры функции Graphics Параметр Объяснение width Ширина окна в пиксс-iax height Высота окна в пикселах color depth Е’лбина цвета (количество бит на каждый пиксел) I node] Режим окна: 0 - по умолчанию, 1 - полноэкранный режим, 2 - оконный режим, 3 - оконный режим с масштабированием
Что такое инициализация? Я использую этот термин в данной главе, а вы, может быть, не очень понимаете, что он означает. Инициализировать графическое окно, значит, создать его, так чтобы затем можно было использовать в программе графику Width и Height Давайте поподробнее рассмотрим каждый параметр. Начнем с параметров width и height эти параметры очень важны и образуют только несколько режимов разрешения. Эта режимы перечислены ниже - 640x480; 800x600: 1024x768; - 1280x1024; - 1600x1200. Возможно, вы удивитесь, почему мы используем только эти режимы разрешения, но на эго, конечно, есть причины. Если приложить линейку к монитору и измерить его высо ту и ширину, то ширина всегда будет больше высоты. И, что самое интересное, отношение ширины к высоте всегда одно и то же. Например, ширина моего монитора составляет 14,66 дюймов, а высота -11 дюймов. Если разделить 14,66 на 11, то получится 1,33. Это означает, что ширина монитора в 1,33 раза больше, чем его высота. Эта пропорция одинакова для всех мониторов и большинства телевизоров. Попробуйте сами это проверить! Поскольку ширина монитора больше его высоты, размеры в пикселах на мониторе должны меняться соответствующим образом. Если вы нарисуете квадратную рамку, то на мониторе она будет выглядеть как прямо-уголышк {ее ширина будет больше высоты). Чтобы решить эту проблему, разрешение имеет большее число пикселов в ширину; чем в высоту'. В результате квадрат действительно выгля-дит как квадраг. Посмотрите на рис. 5.1, чтобы увидеть соотношение сторон монитора. Рис. 5.1. Соотношение сторон монитора
Color Depth Обратите внимание, что настройку глубины цвета (параметр color depth) можно применять только в полноэкранном режиме. В оконном режиме глубина цвета вашей игры ограничена глубиной цвета рабочего стола игрока, а в полноэкранном режиме этому параметру можно задать любое значение из табл. 5.2. Чтобы посмотреть глубину цвета рабочего стола, щелкнита правой кнопкой мыши на рабочем столе и выберите пункт Свойства (Properties). Откройте вкладку Настройки (Settings) и проверьте глубину цвета вашего экрана в поле Цветовая палитра (Color Quality). Следующая переменная - это color depth. Она представляет собой глубину цвета, т. е. число цветов, в которые может быть окрашен каждый пиксел (это число определяется числом бит). В табл. 5.2 приведены самые распространенные значения глубины цвета и соответствующее им число цветов. Глубина цвета (бит) Количество цветов 8 256 16 65 536 24 16 777216 82 4 294 967 296 Табл. 5.2 Глубина цвета. Чтобы определить, сколько цветов дает каждое значение глубины цвета, просто возведите двойку в степень глубины цвета. Например, если вы хотите узнать, сколько цветов дает глубина цвета 8, умножьта даойку на саму себя 8 раз (2х2х2х2х2х2х2х2), чтобы получить восьмую степень (2*8). Хотя сегодня в основном используются только эти значения глубины цвета, раньше применяли и другие величины. Например, некоторые очень старые игры работали с глубиной цвета 1, а этот режим дает только два цвета - белый и черный. Убедитась, что вы знаета, какая глубина цвета вам нужна, прежде чем установить ее. Если вы используете, например, глубину цвета 8 бит, а цвета вашей игры требуют хотя бы 16, то они не будут отображаться. Если вы не знаете, какую глубину цвета выбрать, программа BlitzPIus автоматически установит оптимальное значение. Чтобы про1рамма сделала это, просто пропустите этот параметр или установите в 0. Основное правило такое: если вы знаете, какая глубина цвета вам нужна, задайте ее сами, а если нет, разрешите программе сделать это за вас.
[Mode] Последний параметр функции Graphics - это переменная [mode]. Эта переменная может принимать только четыре значения-0,1,2 или 3. Переменная [mode] определяет, как ведет себя окно программы. О - это значение но умолчанию. Если вы не задали значение переменной [mode], оно автоматически устанавливается в 0 и ваша программа открывается в окне в режиме отладки. а не на полный экран, как при обычной работе. Рис. 5.2 показывает разниц}' между полноэкранным и оконным режимами Что такое режим отладки? Я упоминал о режиме отладки. Вам, наверное, интересно, что это такое. Когда вы программируете игру, то часто допускаете разные скрытые ошибки, которые трудно выловить. Отладка позволяет выполнять программу построчно, чтобы сразу можно было понять, где проблема. Чтобы вам было легче находить ошибки, используйте при написании программы функции. Когда основная часть кода находится в разных функциях, искать ошибки гораздо легче, чем когда весь код сосредоточен в одной главной функции. Когда вы программируете или отлаживаете игру, вы работаете в режиме отладки. Так вы видите, какая строка программы выполняется и какое значение принимает каждая переменная. Когда вы закончили про1раммировать, вы выключаете отладку и запускаете игру как обычно. Чтобы включить или выключить режим отладки, установите или сбросьте значок Debug Enabled (Вюиочнть отладку) в меню Program. Табл. 5-3 расшифровывает каждое возможное значение переменной [mode ]. Когда вы выбираете значение 1 (полноэкранный режим), окно вашей игры развернуто на весь экран и никаких других окон или программ не видно. Конечно, другие программы
в это время продолжают работать, просто они скрыты. В таком режиме игра обычно работает быстрее, но зато занимает весь рабочий стол. На рис. 5.4 показан экран игры в таком режиме. Рис. 5.3. Режим отладки Табл. 5.3. Значения переменной [mode] Значение Название режима Описание 0 По умолчанию Работает в оконном режиме при отладке и в полноэкранном при обычном запуске 1 Полноэкранный режим Окно игры развернуто на весь экран - никаких других программ не видно 2 Оконный режим Программа работает в обычном окне 3 Оконный режим с масштабированием Игра работает в обычном окне, но вы можете свернуть его или изменить его размер Если вы задаете переменной [mode] значение 2, ваша игра будет работать в обычном окне. Это значит, что вы можете передвигать окно по экран}; как показано на рис. 5.5, но не можете изменять его размеры. Если переменной [mode] присвоено значение 3, ваша программа работает почти так же. как при значении 2, но вы можете свернуть, развернуть окно или изменить его размер, как вам нужно. Однако за удобство придется платить: в этом режиме игра часто работает гораздо медленнее. Посмотрите на рис. 5.6, как может выглядеть окно с масштабированием.
Рис- 5.5. Программа KONG в режиме окна
Рис. 5.6. Программа KONG в режиме окна с масштабированием Изображения Наконец-то мы добрались до изображений! Из этого раздела вы узнаете, как aai рулить изображение, как растянуть его на весь экран и другие интересные вещи. Вы готовы? Loadimage Первая функция, которая нам понадобится,- Loadlinage. Эта функция загружает в память программы выбранное вами изображение. Вы должны загрузить изображение, прежде чем сможете показать его и использовать в своей программе. Функция Loadlmage объявляется так: Loadlmage[filenames) В табл. 5.4 объясняется каждый параметр. Чтобы загрузить изображение, просто подставьте вместо filenames имя файла с изображением, убедившись, что оно заключено в кавычки, и присвойте это значение переменной, как здесь: Global playerimage = Loadimage["playerimage.bmp")
Табл. 5.4. Параметр функции Loadlmage Параметр Описание filenames Имя файла с изображением Почему .bmp? К сожалению, демоверсия программы BlitzPIus позволяет работать только с изображениями формата bmp. Это означает, что вы не сможете даже открыть в программе некоторые изображения на своем компьютере, если у них другое расширение. Однако существует простой способ решить эту проблему. Просто откройте файл jpeg, gif или png в программе Microsoft Paint или Paint Shop Pro (она есть на компакт-диске). Теперь выберите команду Save As (Сохранить как) и сохраните файл с расширением bmp! Обратите внимание на то, какое значение вы задаете переменной имени файла. Если это только имя файла без пути, то такая схема будет работать, только если файл изображения находится в той же папке, что и игра. Если же файл находится в другом месте, вы должны указать полный путь к нему, как в этом примере: Playerimage = Loadimage("c:\windows\desktop \playerirtiage.bmp") В любом случае, советую вам хранить все изображения в той же папке, где лежит игра. Если вы когда-нибудь решите распространять свою игру, она не будет работать на другом компьютере до тех пор, пока пользователь не положит файлы с изображениями в папку с точно таким же именем, как у вашей папки. Я обычно называю свои переменные, связанные с изображением, так, чтобы сразу было понятно, на что они указывают. Так, первую часть такого имени составляет реальное содержание изображения, например player (Игрок) в p1ayerimage.bmp, а к нем)' я добавляю image. Переменная, которую вы назначаете загружаемому изображению, называется «указатель изображения». Он просто указывает на изображение в памяти, как видно на рис. 5.7. Функция Loadimage [), по умолчанию ищет изображения в той же папке, в которой лежит файл BlitzBasic. Если вы хотите загрузить изображение из другого каталога, вам надо указать полный путь к нем)'. Итак, мы разобрались с функцией Loadimage, пора действительно показать изображение! handle Player image Рис. 5.7. Указатель изображения в памяти
Drawlmage Легко угадать, что делает эта функция: она выводит изображение на экран! В табл. 5.5 объясняется каждый параметр. Давайте начнем с объявления функции. Drawlmage handle,х,у,[frame1 У этой функции много параметров, обсудим сначала переменную handle. Табл. 5.5. Параметры функции Drawlmage Имя Описание handle Указатель изображения X Координата х выводимого изображения У Координата у выводимого изображения (frame] Просто оставьте пока значение 0 Handle С этим параметром вы разберетесь очень легко. Вспомните, каким образом вы загружали изображение: playerimage - Loadlmage("player.bmp") Переменную playerimage и надо задавать в качестве параметра handle. Так что, когда вы задаете параметры для Drawlmage, используйте как параметр handle тот же указатель изображения, что и раньше. Параметры X и Y Параметры х и у работают точно так же, как и обычные координаты х и у в программе BlitzPIus. Когда вы используете функцию Drawlmage, выбранное вами изображение появляется на экране в соответствии с координатами х и у, как показано на рис. 5.8. Переменные х и у - это координаты левого верхнего угла изображения. Однако можно сделать и так, что переменные к и у будут координатами его центра. Очень часто бывает необходимо использовать координаты центра изображения. Например, когда нужно повернуть изображение, хотелось бы, чтобы оно повернулось относительно центра, а не относительно своего левого верхнего угла. Запустив программу deino05-01 .bb, вы можете посмотреть, как выглядит изображение, когда оно поворачивается относительно своего левого верхнего угла. Program Рис. 5.8. Изображение с координатами х, у
Хотя вращение - это сложное действие и мы будем разбирать сто позже, я использовал его, чтобы объяснить, зачем могут понадобиться координаты именно центра изображения. Функция, которая останавливает координаты в центр, называется AutoMidHan^-ii и объявляется так: AutoMidHandle ru= i Zal.se , Что означает знак «|»? Он означает «или». Когда я пишу AutoMidHandle true false, этозначит, чтофункция Av.tQMJ dHapdl? может использовать значение true (Истина) или false (Ложь) Рис. 5-9- Изображение с координатами х, у при установке параметра true для функции AutQMidHandl ₽ центре изображения, вь ботиться о его высоте и Чгобы использовать эту функцию п установить координаты х ну в центр изображения, вызовит е функцию Ai ih ... 11 • с параметром *• х ие, как здесь: AutoMidHar. Ад • true Правда, просто? А чтобы вернуть координаты х и у обратна в левый верхний угол, вызовите функцию с параметром f al.,•;: AutoMidHandl« fa,хе Вообще, полезно использовать функцию AutoMidHandle, потому что гак легче следить, где сочно находятся изображения Когда точка привязки расположена в и можете не гак за-шприпе, как в том случае, когда точка привязки находится в левом верхнем углу Табл. 5.6 содержит описание параметров, а рис. 5.9 показывает, как работает программа demo05-02.bb, в которой используется функция AutoblidH.i.idle. Обратите внимание на то, чем отличаются рис. 5 .8 и 5.9. На рис. 5.8 координат ы х и у находят ся в левом верхнем уыу изображения, а на рис. 5.9 - в его центре Попробуйте запустить программу demo05-02.bb и посмотрите, как изображение поворачивается относительно своего центра, а не верхнеголевого утла, как в счучас с программой demo05-01 .bb Табл- 5.6. Параметры функции А л. >м. аг' ~ л' Имя Описание true Устанавливает координаты х и у в центр изображения false Устанавливает координаты х и у в левый верхний угол изображения Обязатоыю проверьте, что вы установили параметр ttpr®, прежде чем загрузить изображение, иначе функция нс бхдет работать. Кроме того, существует еще функция MiJJkino. i, похожая на функцию AutoMidHaiidle, с той разницей, ч го она мсгацавливает координаты х и у в центр не для всех изображении, а только для того из них. которое вы выберете. Вот как объявляется эта функция:
MidHandle image Координаты последующего изображения будут перемещены в его центр. Используйте эту функцию, если вы хотите изменить положение координат только для одного изображения, а не для всех. [Frame] Этот параметр более сложный. Он позволит вам показывать анимированные изображения. Это слишком рано обсуждать сейчас, но мы доберемся до анимированных изображений очень скоро! Createlmage Эта функция вам понравится. С ее помощью вы сможете создать такое изображение, какое захотите, и работать с ним так же, как с загруженным изображением. Например, вы решили создать изображение со 100 точками, (начала вызовите функцию Createlmage. которая объявляется так: Createlmage(width,height,[frame]) Параметры width и height отвечают за размеры изображения, параметр [frame] используется с анимированными изображениями и пока должен быть установлен в 0. Чтобы создать изображение, вызовите функцию Createlmage примерно так: dotfieldimage _ Createlmage(100,100,0) Теперь у вас есть указатель изображения. Теперь вы должны наполнить поле точками. Ниже показан полный листинг программы demoD5-03.bb, которую вы можете найти на компакт-диске: ; demo05-03.bb;;;;;;;;;;;;;;;;;;;;; ; By Maneesh Sethi;;;;;;;;;;;;; ; ;; ; ; Создает изображение и показывает его!! ; Не требует ввода параметров;;;;; .•ИНИЦИАЛИЗАЦИЯ ;Устанавливаем графику Graphics 800,600 ;Запускаем генератор случайных чисел SeedRnd MilliSecs() S-2940
;КОНСТАНТЫ ;Длина каждого блока Const LENGTH- = 100 ;высота каждого блока Const HEIGHT = 100 ;Количество точек в каждом блоке Const DOTS = 100 ;ВСЕ КОНСТАНТЫ ЗАДАНЫ ;ИЗОБРАЖЕНИЯ ;Создаем переменную dotfield image dotfieldimage = Createlmage(LENGTH,HEIGHT) ;ИЗОБРАЖЕНИЯ ЗАДАНЫ ;Для каждой точки рисуем случайную точку со случайными координатами For loop - 0 То DOTS ;Для каждой звезды ; рисуем только на созданном изображении SetBuffer ImageBuffer(dotfieldimage) ;Рисуем точку Plot Rnd(LENGTH),End(HEIGHT) Next ;Устанавливаем буфер как BackBuffer() SetBuffer BackBuffer() ;ИНИЦИАЛИЗАЦИЯ ЗАКОНЧЕНА ;ГЛАВНЫЙ ЦИКЛ ;Копируем изображение, пока пользователь не нажмет кнопку ESC Cis Tilelmage dotfieldimage Flip
WaitKey ;КОНЕЦ ГЛАВНОГО ЦИКЛА На рис. 5.10 показано поле точек. Рис. 5.10. Поле точек В этой программе появилось несколько новых функций, и сейчас мы их обсудим. Первая из них - ImageBuf f er (). Эта функция похожа на BackBuffer(). Вы узнаете, как с помощью функции BackBuf f er () можно рисовать во вторичном буфере вместо первичного, а потом вы сможете переключать буферы и создавать анимацию. Функция ImageBuf fer () работает похожим образом, но рисует не в буфер, а прямо на изображении. Эта функция объявляется так: ImageBuffer(handle,[frame]) Здесь handle - это указатель нужного изображения, а [ frame ] - рамка для рисования (ус тановите пока этот параметр в 0). Процесс рисования изображения в буфер показан на рис. 5.11. Как видно на рисунке, вызов функции SetBuffer ImageBuffer (dot-fieldimage) позволит вам извлечь изображение из программы и работать с ним. Когда вы закончите, опять вызовите функцию SetBuffer. В этой программе я использовал функцию SetBuffer FrontBufferO, потому что не применялосьпереключение страниц В большинстве игр придется использовать функцию SetBuffer BackBuf fer (). В табл. 5.7 объясняются параметры функции ImageBuf fer. Теперь поговорим о функции Tilelmage (). Она объявляется так: Tilelmage handle, [х] , [у] , [frame]
Рис. 5-11. Действие функций SetBuffer ImageBuffer () Табл. 5.7 Параметры функции ImageBuf f ei Имя Описание handle Указатель нужного 1гзображсния [frame] Рамка для рисования, пока оставьте значение 0 Эта функция работает так: она берет указанное изображение и размножает его ио всему полю. Это похоже на шахматную доску - на ней только два изображения, черное и белое. Но эти два изображения копируются много раз, пока не заполнят всю доску черными и белыми клетками. Посмотрите на рис. 5.12, чтобы лучше представить се- Рис. 5.12. Функция Tileimage
Табл. 5.8. Параметры функции Tilelmage Имя Описание handle Изображение, которое вы хол ите размножить (х] Начальная координата к размноженного изображения, по умолчанию 0 [у] Начальная координата у размноженного изображения, по умолчанию 0 [frame] Рамка для заполнения копиями, по умолчанию 0 Чтобы размножить изображение, просто вызовите функцию Tilelmage с указателем этого изображения. Программа BlitzPIus позаботится обо всем остальном. Из следующих глав вы узнаете, как передвигать<поле размножения вверх и вниз, чтобы имитировать движение. В последней части программы используется функция WaitKey. Эта функция просто приостанавливает выполнение программы до тех пор, пока пользователь не нажмет клавишу. Maskimage Давайте теперь познакомимся с функцией Masklmage (). Она объявляется так: Maskimage handle, red, green, blue Эта функция позволяет вам сделать фон изображения прозрачным. Что я имею в виду? Сейчас объясню. Когда вы рисуете или выводите изображение, у него всегда есть рамка, которая не является его частью. Посмотрите на рис. 5.13. Внешняя часть изображения не используется, и хорошо было бы от нее избавиться. Вы ведь не хотите, чтобы эта рамка портила всю картину, как на рис. 5.14, правда? Рис. 5.13. Изображение без использования маскировки Рис. 5.14. Изображение с рамкой
Так как черный цвет маскируется по умолчанию, рамка у изображения на рис. 5.14 не совсем черная. Я добавил немного синего цвета, чтобы фон не маскировался. Для фона использовался RGB цвет 0,0,10. Вызов функции Maskimage позволит вам избавиться от этой рамки. В табл. 5.9 объясняется каждый параметр функции. Так как для фона используется RGB цвет 0,0,10, обратите внимание на параметры функции Maskimage (). Табл. 5«9 Параметры функции Maskimage Имя Описание handl> Изображение, часть которого вы хотите замаскировать red Значение красного цвета маски green Значение зеленого цвета маски blue Значение синего цвета маски Ниже приведен полный листинг программы. ;demo05-05.bb ;Ву Maneesh Sethi ;Показывает использование маскировки ;Не требует входных параметров ;Инициализация трафики Graphics 640,480 ;Загружаем фон lilliesimage Loadimage("Tillies.bmp") ;Выводим фон Drawlmage 1i11iesimage,0,и ;Загружаем изображение лягушки frogimage = Loadimage("frog.bmp") ;Выравниваем изображение лягушки по центру MidHandle frogimage ;Маскируем изображение лягушки Maskimage frogimage,0,0,10 ;Выводим изображение в центре
• > Drawlmage frogimage,320,240 Flip ;Ждем, пока пользователь нажмет кнопку WaitKey На рис. 5.15 показан результат работы этой программы. Правда, отлично получилось? Кажется, что лягушка - это действительно часть изображения! На компакт-диске вы можете найти программу без маскировки demo05-04.bb и ее аналог с маскировкой - demo05-05.bb .** Ж- Рис. 5.15. Изображение с использованием маскировки Обратите внимание, что величина RGB по умолчанию — это 0.0.0, что обозначает черный цвет. Значит, если ваше изображение имеет черную рамку, она автоматически будет замаскирована. Старайтесь, чтобы все изображения, которые вы используете, имели черный фон, и тогда вам не придется заботиться о маскировке. Может быть, вы обратили внимание на команду Fl ip в конце программы. Дело в том, что по умолчанию программа BlitzPIus помещает изображение во вторичный буфер. Когда вы используете команду Flip, то тем самым выводите изображение из буфера на экран. Мы еще вернемся к этому вопросу в следующих главах. Цвета Прежде чем закончить эту главу, я собираюсь научить вас работать с цветом. Конечно, цвет - это неотъемлемая часть любой программы. А когда вы используете переключение страниц - а этим мы будем заниматься уже в следующей главе, - цвет приобретает еще большее значение. Вам нужно познакомиться еще с несколькими функциями, прежде чем переходить к следующей главе. Это функции Color, Cis и CisColor. И нам еще надо разобраться со значениями RGB. RGB При работе с цветом часто сталкиваешься с понятием RGB цвета. В этом сокращении цвета обозначаются так: R - Red (Красный), G - Green (Зеленый), В - Blue (Синий). Цветовая схема RGB позволяет выбрать любой из 16 миллионов цветов. Как вы думаете, вам хватит такого количества?
Почему 16 миллионов? Когда вы используете значения RGB, то обычно выбираете число от (1 до 255 для каждого из основных цветов (красного, зеленого и синего). Что это нам дает? Очень просто: общее число RGB цветов можно узнать, если возвести 256 в куб, и мы как раз получим 16.7 миллиона цветов, из которых можно выбирать. Когда цвет используется в функциях, то обычно надо задать значение для трех параметров - Red (Красный), Green (Зеленый) и Blue (Синий). Для каждого параметра вы должны выбрать число от 0 до 255 (всего 256 вариантов). Например, если вы установите значение 255 для красного цвета и 0 для зеленого и синего, то получите чистый красный цвет. Черному цвету соответст вует 0,0,0, а белому - 255,255,255. Наверное, вы хотите узнать, как же выбрать эти числа для того цвета, который вам нужен. Существуют два способа. Вы можете угадать эти числа и проверить свою догадку, задав значения параметров Red, Green и Blue. Кроме того, вы можете использовать какую-нибудь программу, например Microsoft Paint. Для запуска программы Microsoft Paint выберите команду Программы ♦ Стандартные • Paint (Programs ♦ Accessories ♦ Paint) из меню Пуск (Start). Вы можете также посмотреть на рис. 5.16, как выглядит программа Microsoft Paint и как ее открыть. На заднем плане рисунка показано окно программы, на переднем - меню Start. V вас это меню может отличаться. Теперь выберите команду меню Палитра ♦ Изменить палитру (Colors ♦ Рис. 5.16. Как открыть программу Microsoft Paint
Edit Colors). В открывшемся диалоге ще .жните мышью на кнопке Определить цвет (Define Custom Colors). На рис. 5.17 изображен диалог Edit Colors с палитрой цветов. Теперь выберите цвет, и внизу вы увидите соответствующие ему значения. Если сразу не получилось, передвиньте ползунок на полосе прокрутки справа и попробуйте выбрать цвет еще раз Теперь вы знаете про RGB все, что вам потребуется, и готовы использовать цвет в своих программах. Рис. 5.17. Выбор цвета в программе Microsoft Paint Color Функция Color может показаться вам забавной. Она определяет цвет по умолчанию для программы. Когда вы рисуете что-нибудь - линии, фигуры или текст (но не когда вы выводите на экран изображения), их цвет будет таким, каким он установлен по умолчанию. Что вы можете делать с помощью этой функции? Например, используйте ее, если вы хотите, чтобы текст был не белым, а какого-нибудь другого цвета. Или, к примеру, вы хотите нарисовать зеленый треугольник. Просто установите зеленый цвет и рисуйте Вы можете изменить цвет в любой момент. Пока вы не вызвали функцию Color, цвет по умолчанию для любой программы BlitzPIus - белый (RGB 255,255,255). Функция объявляется так: Color red,green,blue В табл. 5.10 описываются параметры. Вам просто надо задать параметрам red, green и blue такие значения, которые соответствуют нужному цвету. Табл. 5.10. Параметры функции Color Имя Описание red красный цвет green зеленый цвет clue синий цвет Теперь давайте напишем программу с использованием этой функции. Эта программа будет рисовать эллипсы разного цвета и размера.
/demoO5-06.ЬЬ ;Ву Maneesh Sethi ;Показывает работу функции Color, рисует эллипсы /Не требует входных параметров Graphics 800.600 ;Запускаем генератор случайных чисел SeedRnd (MilliSecs()) Максимальная ширина эллипса Const MAXWIDTH = 200 ;Максимальная высота эллипса Const MAXHEIGHT = 200 ;главный цикл While Not KeyDown(l) ;Очищаем экран Cis /Устанавливаем случайную величину цвета Color Rand(0,255),Rand(0,255),Rand(0,255) /Рисуем случайный овал Oval Rand(0,800),Rand(0,600),Rand(0,MAXWIDTH),Rand(0,MAXHEIGHT), Rand(0,1) /Пауза! Delay E г Flip Wend Правда, здорово? На рис. 5.18 показан результат работы этой программы. Давайте попробуем разобраться. Сначала программа устанавливает графический режим и запускает генератор случайных чисел. Затем она задает максимальную высоту' и ширину эллипса. Вы можете изменить эти значения. Потом программа входит в главный цикл. Сначала программа устанавливает цвет равным случайному значению в строке
Color Rand(0,255].Rand(0,255J,Rand(0,255' В следующей строке программа рисует эллипс случайного цвета. Функция, которая это делает, называется Oval и объявляется так: Oval х,у.width,height [solid] Посмотрите, как в табл. 5.11 объясняются параметры. Табл. 5.11. Параметры функции Oval Параметр Описание X Координата х эллипса У Координата у эллипса width Ширина эллипса в пикселах height Высота эллипса в пикселах [solid] Значение по умолчанию 0; задайте 1, если вы хотите, чтобы эллипс был заполнен цветом. Если задано значение 0, эллипс внутри прозрачный, и видно только его границу Рис. 5.18- Программа demo05-06-bb Ну вот, теперь мы все знаем про функцию Color. На очереди - функции Cis и CisColor.
Cis и CisCoior Мы уже почти добрались до конца главы! Но сначала еще немного теории, которая понадобится нам в следующей главе. Функция cis работает очень просто. Все, что она делает, очищает экран. В следующей главе мы познакомимся с ней поближе. Функция CisColor работает вместе с Cis и позволяет вам изменять цвет фона вашей программы. Функция CisColor объявляется так: CisColor red, green, blue Параметры функции описаны в табл. 5.12. Табл. 5.12 Параметры функции CisColor Имя Описание red красный цвет green зеленый цвет blue синий цвет Функция CisColor изменяет цвет фона. А это значит, что вы можете оставить фон черным (каким он установлен по умолчанию) или изменить его так, как вам нравится. Чтобы использовать эту функцию, задайте параметрам red, green и blue такие значения, которые соответствуют нужному цвету. После вызова функции CisColor вам надо будет вызвать функцию Cis, чтобы очистить экрая и изменить цвет фона. Попробуйте запустить программ}' demo05-07.bb. Программа показывает разные цвета на экраяе, а также советы, которым вам нужно следовать. Попробуйте сейчас! Заключение Итак, теперь вы уже много знаете о графике в видеоиграх. В этой главе мы познакомились с целым рядом функций: Graphics, Loadlmage(), Createlmage(), ImageBuf fer () и Maskimage. Вы найдете множество применений этим функциям в своих играх. В этой клаве мы обсудили следующие темы: создание графического окна; загрузка, вывод и использование изображений; использование цветов. Далее мы будем рассматривать переключение страниц и основы вывода. Следующая глава очень важна, потому что мы займемся анимацией.
ГЛАВА 6 Переключение страниц и работа с пикселами В этой главе мы познакомимся с анимацией и освоим работу с пикселами. Анимация создается с помощью процесса, который называется «переключение страниц». Суть этого процесса состоит в тот, что кадры вашей игры постоянно сменяют друг друга. Мы сможем также рисовать на экране пикселы, которые представляют собой маленькие точки. Вы уже немного знакомы с переключением страниц, мы касались этой темы в предыдущих главах. Переключение страниц использус-гся в большинстве хороших игр, так как этот процесс действительно лежит в основе анимации. Итак, чего же мы ждем? Пора приступать! Переключение страниц Помните, были такие маленькие книжки, если их быстро-быстро перелистывать, то кажется, что изображение движется? В видеоиграх используется похожий процесс. Вы помещаете рисунок вне экрана, в так называемый буфер. Представьте, что буфер - это страница в книге, следующая за той, на которую вы смотрите сейчас. Когда вы перелистываете страницы, изображение на экране сменяется изображением из буфера. Вы можете познакомиться со схемой переключения страниц на рис. 6.1. Рис. 6.1. Переключение страниц
Что такое кадр? Вы уже прочитали несколько глав этой книги и должны знать, что кадр — это одна сцена игры, которан переключается на другие сцены так быстро, что создаете ся иллюзия настоящего движения. В сущности, кадр — это один проход главного цикла. (Напомню на всяким случай, что сделать один проход цикла - это пройти его однократно, или выполнить каждый оператор цикла по одном}’ разу). Обычно цикл выполняется до тех пор, пока пользователь не выйдет из игры. Каждый раз, когда программа проходит цикл, на экране появляется один кадр. Цикл продолжает выполняться, пока игра не кончится. Вам кажется странным, что так можно имитировать движение? Но этот процесс повторяется не меньше тридцати раз в секунду. Представьте, за одну секунду как минимум тридцать разных кадров сменяют друг друга. В книжке одна страница соответствует одном}' кадру. Невозможно рассмотреть каждую отдельную страницу, если быстро перелистывать их, и точно так же дело обстоит с кадрами. Кадры переключаются так быстро, что создается полная иллюзия движения: невозможно разглядеть, когда исчезает один кадр и появляется другой. Мы уже использовали переключение страниц в некоторых играх в этой книге, но я никогда раньше не объяснял, как оно работает. Давайте рассмотрим сейчас пример игры, где не применяется переключение страниц. Вы можете найти эту игру на компакт-диске, она называется ctemo06-01 .bb. ;demo06-01.bb - Плохо анимированный корабль ;Инициализация графики Graphics 800,600 ;Загружаем изображение корабля shipimage = Loadlmage ("ship.bmp") ;Запускаем генератор случайных чисел SeedRnd(Mi11iSecs()) ;Создаем тип ship Type ship Field x.y ;the x and у coords End Type ;создаем корабль ship.ship = New ship ;задаем случайные координаты корабля ship\x = Rand(0,800) ship\y = Rand(0,600)
While Not KeyDown(1) ;Очищаем экран Cis ;передвигаем корабль влево-вправо ship\x = ship\x + Rand(-8,8) ;Передвигаем корабль вверх-вниз ship\y = ship\y + Rand(-8,8) ;Если корабль вышел за пределы экрана, возвращаем его обратно If ship\x < О ship\x = 15 Elself ship\x » 800 ship\x = 790 Elself ship\y < 0 ship\y - 10 Elself ship\y > 600 ship\y = 590 Endlf ;Выводим корабль Drawlinage (ship image, ship\x, ship\y) Wend На рис. 6.2 показаны три кадра из программы demo06-01 .bb. Видите, как корабль исчезает, чтобы потом появиться в другом месте, когда он движется? Это происходит потому, что не используется переключение страниц. Следовательно, нет и анимации. Когда вы запускаете эту программу, то, наверное, сначала видите пустой экран. Попробуйте щелкнуть мышью на каком-нибудь другом окне, а потом опйть на окне программы, и вы увидите корабль. Почему так получается, я объясню вам позже в этой главе. Запомните, что если вы щелкаете мышью на окне другой программы, это называется «изменить фокус» вашего компьютера- Рис. 6.2. Игра без переключения страниц
Переключение страниц работает следующим образом. Следующий кадр находится во вторичном буфере (вне экрана), а потом содержимое первичного и вторичного буфера меняется местами (буферы переключаются), и следующий кадр появляется на экране. Смена кадров происходит постоянно и очень быстро, что позволяет создать полную иллюзию движения. В нашем примере, однако, вторичный буфер не используется. Поэтому, хотя кадры изображают движение корабля, компьютер не может выводить изображение так быстро, чтобы действительно было похоже, что корабль плавно движется. Н, что, приступим? Буферы Я знаю, что мы уже сто раз упоминали буферы, и все же я решил написать отдельный раздел, чтобы вы могли получше разобраться с этим термином. Вам нужно королю понимать его, потому что мы будем часто использовать это понятие. Буфер - это изображение. Каждый кадр вашей игры помещается в буфер, значит, каждый кадр — это изображение (кадр - буфер, буфер = изображение, кадр - изображение). Чтобы создать настоящую анимацию (а значит, чтобы в игру действительно можно было играть), вам придется использовать хотя бы два буфера, хотя в большинстве современных игр применяются три буфера. Буфер бывает «вторичный» (Back Buffer) и «первичный» (Front Buffer). Первичный буфер отображается на экране, а вторичный ~ нет. Представьте себе стоику бумаги Тогда первичный буфер находится на верхнем листе, а вторичный - на листе ниже, как показано на рис. 6.3. FrontBuffer( )• BackBuffer() Page 1 Page 2 Рис. 6.3. Буферы по аналогии со стопкой бумаги Кроме того, существует еще один тип буфера. Он называется «буфер изображения» (Image Buffer). Он похож на любой другой буфер, но в нем обычно находится рисунок, который вы хотите нарисовать сами. Например, вы хотите, чтобы у вас был буфер, в котором вы нарисуете два прямоугольника. Тогда вы создаете буфер изображения, рисуете в нем два прямоугольника и выводите буфер изображения на экран в любой мо мент игры. Особенно приятно, что буфер изображения может быть любого размера, в то время как первичный и вторичный буфер имеют размер, равный размеру экрана.
Буферы обычно используются при переключении страниц. Хотя мы уже познакомились с этим процессом, я еще раз кратко опишу его суть, чтобы обратить ваше внимание на то, как в нем применяются буферы- Пусть v нас есть два буфера: буфер А и буфер Б. Буфер А - первичный, буфер Б—вторичный. Игра запущена, значит, на экране виден буфер А. Потом кадр заканчивается, содержимое первичного и вторичного буферов меняется местами, и прежний кадр. А, теперь не виден. На экране отображае тся буфер Б. Посмотрите на рис. 6.4, чтобы лучше понять этот процесс. Обратите внимание, что вторичный и первичный буфер в действительности всегда остаются па своих местах, а меняется только их содержимое. Рис. 6.4. Буферы А и Б SetBuffer Эта функция очень важна, опа используется во всех программах с переключением страниц. Обычно эта функция появляется в игре сразу же после вызова функции Graphics. Функция «?г объявляется так: SetBuffer uuffex В табл. 6.1 описываются параметры этой функции. Табл. 6.1. Параметры функции SetButtc Параметр Объяснение tuffer Этот параметр определяет, куда помещается изображение Может принимать значения FrontBuf fer (1. BackBuf f er (j или Imagefiuf f er i). Значение по умолчанию - BackBuffer(' Если вы используете переключение страниц, то обычно будете помещать изображение в BackBuffer('. После того как вы инициализировали графику, вам не обязательно задавать значение BackBuffer(), потому что это значение по умолчанию. Но, если хотите, вы можете задать это значение так: SetBuffer BackBuffer(j Правда, просто? Но, прежде чем мы двинемся дальше, вам нужно узнать побольше об этих трех типах буферов: •
FrontBufferO BackBuffer() ImageBuf fer() FrontBufferO Вроде бы, co значениями FrontBuf f er () и BackBuf fer () все понятно, но все же обсудим их. Как мы уже говорили, когда рисуешь на экране, изображение в действительности помещается в буфер. Вопрос в том, какой буфер. В большинстве случаев нужно использовать BackBuf fer (), по той простой причине, что если помещать изображение в первичный буфер (FrontBuf fer ()). игра сильно замедляется. Проблема замедления игры в случае использования FrontBuf fer () остро стояла в более ранней версии программы BlitzPIus (под названием Blitz Basic). Чтобы избежать такого эффекта, в программе Blitz Basic нельзя было помещать изображение напрямую в первичный буфер. В программе BlitzPIus такая проблема уже не стоит, потому что FrontBuf fer () - это просто другое название для BackBuf fer i) в этой версии программы. Легко понять, почему программа начинает работать медленнее. Представьте себе кошку, которая продирается через кусты. Каждый раз, koi да кошка немного продвигается вперед (каждый кадр), фон надо стереть и перерисовать заново, изобразив новую часть кустарника, которую преодолела кошка. Конечно, анимация замедляется. Другое дело, если вы используете BackBuf fer (). Тогда кус гы и кошка спокойно прорисовываются, незаметно для играющего, а потом картинка просто выводится на экран. Вы удивляетесь, кому вообще может прийти в голову использовать Frontbuffer } ? Просто иногда нужно рисовать прямо на экране. В старой программе Blitz Basic можно было использовать FrontBuf fer (), если вам не нужны были два буфера, а нужно было просто рисовать прямо на экране. Такой подход может быть оправдан, когда вы пишете программу типа demo06-02.bb. Но в про>рамме BlitzPIus вы должны использовать два буфера, так что программа будет работать неправильно. Когда вы запускаете программу, она помещает изображение во вторичный буфер (BackBuf fer ()), так что изменения не появятся на экране дотек пор, пока вы не смените фокус экрана на окно другой программы и обратно. Эта программа рисует на экране линии. ;<3emo06-02 .bb Рисует на экране случайные линии ;Инициализируем графику Graphics 800,600,0,.: ;Рисуем только в первичный буфер SeedRnd(MilliSecs()) While (Not KeyDown(l)) ;Устанавливаем случайный цвет
Color Rand(0,255),Rand(0,255),Rand(0,255) ;Рисуем случайную линию Line Rand(0,800), Rand(0,600), Rand(Q,800), Rand(0,600) ;Пауза Delay(25) Wend Поскольку BackBuffer^) используется по умолчанию, то строку SetBuffer BackBuffer(j можно спокойно пропустить, и программа все равно будет помещать изображение во вторичный буфер На рис. 6.5 показано окно программы после переключения фокуса. Рис. 6.5. Программа demo06-02.bb BackBuffer() Параметр BackBuf fer и работает немного ио-другому Когда вы используете функцию SetBuf fer со значением BackBuffer (), все, что вы рисуете, находится вне экрана. Это означает, что ничего из того, что вы только что нарисовали (скажем, случайные линии в нашем примере), на экране не видно. Это основа переключения страниц. Теперь
все, что вы должны сделать, - поменять местами содержимое первичного и вторичного буфера (переключить буферы). Для этого испольлйтс команду Flip. Эта команда прямо так и выгляди г: Flip. Она переключает псранчный и вторичный буферы. Будьте осторожны, когда приманяете команду Flip. Эту команду вы должны расположить недалеко от конца главного цикла программы. В следующем примере, как вы увидите, команда Flip располагается прямо после функции Drawlmage. Почему эти операторы идут именно в таком порядке? Потому что если вы выполните команду Flip до того, как выведете изображение, то программа просто выведет на экран пустой кадр. В начале каждого цикла используется функция Cis, и поэтому изображение космического корабля из предыдущего кадра стирается. Команда Flip выведет на экран содержимое вторичного буфера, а там ничего нет. Конечно, вы можете легко решить эту проблему, если вызовете функцию Drawlmage ближе к началу цикла Давайте вернемся к программе demo06-01.bb, которую мы рассматривали в начале главы - эта та самая программа, где корабль двигался рывками. Эта программа получилась неудачной, по том}' что в ней не использовалось переключение страниц и, значит, не было никакого плавного движения. Чтобы решить эту проблему, вставим команду Flip недалеко от конца цикла. Новая, исправленная версия этой программы называется demo06-03.bb, и вы можете найти се на компакт-диске. Ниже приведен фрагмент программы с циклом игры. While Not KeyDown(1 ;Очищаем ^кран Cis перед витаем корабль влево-вправо ship\x : ship\х + Randi-8,8) ;Передвигаем корабль вверх-вниз ship\y = ship\y + Rand(-8,8) ;Если корабль вышел за пределы экрана, возвращаем его обратно If ship\x < О shij ,4. = 15 Elself ship\x > 800 ship\x - 740 Elself ship\y •. P ship\y =10 Elself ship\y > 600 shij у 590 Endlf
;Выводим корабль Drawlmage (shipii age, shi] 11. .. • . Flip Wend Теперь все работает отлично, правда? На рис. 6.6 показало окно программы. Давайте пройдем по циклу и посмотрим, как он работает л Рис. 6.6. Программа demo06-03.bb Первая строка выглядит так: Cis /Очищаем экран Это очень важная часть программы с переключением страниц. Фактически, эта команда просто стирает с экрана все, что было па нем нарисовано (все содержимое предыдущего кадра). Вы можете подумать, что эго необязательно. Но посмотрите на рис. 6.7, что получится, если не очищать экран. Как видите, без функции Cis космический корабль оставляет слишком много следов. Функция Cis стирает все эти следы перед тем, как появится следующий кадр, так что кажется, что космический корабль действительно летит. На рис. 6.7 показано, как работает программа demo06-03.bb без функции Cis. Ради интереса, вы можете попробовать изменить цвет фона. Для этого воспользуйтесь функцией CisColor, с которой мы познакомились в предыдущей главе. Ну вот, мы разобрались с основами переключения страниц. Теперь давайте займемся буфером изображения.
Рис. 6.7. Программа demo06-03.bb без функции Cis Буферы изображения Буферы изображения очень просто использовать, хотя само понятие - довольно грудное для понимания. Представьте, что где-то в вашей игре есть холст и вы хотите, чтобы играющий мог на этом холсте рисовать. Тогда вы просто используете буфер изображения, играющий рисует на холсте и видит, что он нарисовал. Для рисования не требуется переключение страниц, а буфер изображения будет показан на экране как бы сверху изображения. Представьте, что буфер изображения -это отдельный слой, который расположен выше, чем первичный буфер Рис 6.8 поможет вам разобраться. Буфер изображения часто используется в играх, действие которых разворачивается в космосе. Обычно в такой игре на информационном экране можно
увидеть мини-каргу. Мини-карта представляеч собой уменьшенную версию игрового поля, и на ней можно увидеть, где находятся враги и артефакты. С помощью буфера изображения легко нарисовать такую мини-карту и показать ее в верхней части информационного экрана. Можно использовать буфер изображения двумя разными способами. Во-первых, можно взять уже готовое изображение и добавить его в буфер с помощью команды Loadimage (). Тогда вы сможете рисовать поверв уже имеющегося изображения. Во-вторых, можно вначале создать пустое изображение. Давайте сейчас этим и займемся. Createlmage Когда вы создаете изображение, на самом деле вы создаете пустое изображение. Созданное изображение может быть любого размера; этот размер не ограничивается размером вторичного буфера. Функция Createlmage объявляется так: Createlmage (width,height,[framesj, Почему мы используем скобки? Вы, вероятно, заметили, что некоторые функции пишутся со скобками, а другие - без. Этому' есть очень простое объяснение. Скобки требуются, когда функция возвращает значение. Ко>да функция не возвращает значения, скобки можно писать, а можно и опустить. Например, функция Loadimage (' использует скобки, потому что она возвращает указатель загруженного изображения, как в строке: image = Loadimage (* image, bmp*) В то же время, функция Text скобок не требует: Text 0,0,"No parentheses!" Впрочем, когда вы пишете функцию, лучше всегда использовать скобки, даже если они необязательны. Напоминания: необязательные параметры Напоминаю, что необязательные параметры заключены в квадратные скобки в объявлении функции, и их можно не использовать. Поэтом}', вы можете вообще не задавать им значения в своей программе - у них всегда есть значение по умолчанию. Например, для функции Createlmage () таким параметром является [ f rames ]. Этот параметр имеет значение по умолчанию 0. Вы можете изменить это значение, если хотите, но это необязательно. В табл. 6.2 описываются параметры функции Createlmage (). Эта функция возвращает указатель созданного изображения, как в следующей строке: wallimg = Createlmage(200,200)
Табл. 6.2. Параметры функции Createlmage ( Параметр Описание width Ширина созданного изображения в пикселах height Высота созданного изображения в пикселах [frames] Необязательный параметр. Число кадров в созданном изображении; пропустите пока этот параметр Рис. 6.9. Буферы изображении в памяти Итак, теперь у вас есть пустое изображение. Сейчас оно абсолютно черное. Давайте попробуем на нем что-нибхдь нарисовать. Самое первое, что мы должны сделать, - установить буфер на буфер изображения. Как это сделать? Очень просто! Используйте функцию SetBuffer. SetBuffer ImageBuffer(wallimg) Мы выделили изображение. Давайте нарисуем на нем белый квадрат (RGB-цвет 255. 255,255) Color 255,255,2-Rect 0,0,200,200,1 Давайте разберемся с вызовом функции Rect. Эта функция рисует прямоугольник в соответствии с заданными координатами. Координаты вервнего левого утла прямоугольника задаются с помощью первых двух параметров, а координаты правого нижнего угла соответствуют третьему и четвертому' параметру. Пятый параметр определяет, будет ли закрашена внутренняя часть првмоугольника юта только его контур. Поскольку функция из приведенного фрагмента рисует прямоугольник от (0,0) до (200,200), то, казалось бы, белый прямоуюльник будет расположен от верхнего левого yuia экрана до координат (200,200). Однако все не так просто. Вспомните, когда мы вызвали функцию SetBuffer ImageBuffer (), мы, таким образом, выбрали буфер изображения. И мы рисуем в нем, л он нахо дится вне экрана. Размер буфера изображения мы выбрали равным 200x200 пикселов (помните, мы сами задали такой размер функцией Createlmage ()). А значит, когда мы нарисуем наш белый прямоугольник, буфер изображения полностью окрасится в белый цвет. Если вы еще не разобрались, что происходит, посмотрите на рис. 6.9.
Как вы, может быть, помните из предыдущего раздела, чтобы изменить цвет, которым вы рисуете, надо вызвать функцию lui г. Здесь эта функция устанавливает цвет по умолчанию в белый, и потом функция R»?t использует именно этот цвет. Теперь мы должны выбрать вместо буфера изображения вторичный буфер. Это легко сделать с помощью функции SetBuffer. SetBuffer BackBufferИ Flip Помните, у параметра функции . etBuf f<=r есть три варианта: FrontBuf f er (), BackBuffer () и ImageBuffer ।)? Вспомните также, что если мы выберем FrontBuf f er (), то ничего не произойдет. В большинстве случаев можно просто оставить значение этого параметра но умолчанию и нс менять буфер. Команда Flip выведет изображение на экран. Вы, может быть, не очень поняли, зачем мы переключились обратно на вторичный буфер. Просто, если мы не переключимся, мы.будем продолжать рисовать в буфере изображения. Любой новый текст или рисунок появится в маленьком поле размером 200x200 пикселов. И подумайте, что получится, если мы попробуем вывести буфер изображения сам на себя9 Вы правы - абсолютно ничего! Отлично, теперь мы опять находимся в первичном буфере. Осталось только показать окно буфера изображения. Для этого мы вызовем функцию Drawlmage. Если вы помните, эта функция определялась так: Drawlmage handle,х,у,[framej Посмотрите табл. 5-5 из предыдущей главы, чтобы вспомнить описание параметров. Теперь осталось только задать указатель (wallimg) и величины хиу, как в этой строке: Drawimage wallimg,400,300 Правда, просто? Посмотрите полный текст программы (с комментариями). Вы можете найти эту' программу на компакт-диске под именем demo06-04.bb. ;demo06-04.bb ;Автор Maneesh Sethi ;Показывает работу функции Createlmage ;Не требуется входных параметров ;Устанавливаем графический режим Graphics 800,600 устанавливаем automidhandle в true AutoMidHandle True ;создаем пустое изображение
wallimg = Createlmage(200,200) ;выбираем буфер изображения SetBuffer ImageBuffer(wallimg) «устанавливаем белый цвет Color 255,255,255 ;рисуем прямоугольник от верхнего левого до нижнего правого угла буфера Rect 0.0.200.200 ;переключаемся обратно на вторичный буфер SetBuffer BackBuffer() ;Переключаем изображение на экран Flip ;выводим буфер изображения Drawlmage wallimg,400.300 ;«дем, пока пользователь нажмет какую-нибудь клавишу WaitKey Всегда вставляйте в такст программы много комментариев. Тогда и вам, и другим будет значительно легче понять, что именно вы пытаетась сделать. Если вы забываете вставлять комментарии, потом вам будат сложно разобраться, что же вы написали. Посмотрите еще раз, что было написано в главе 4 о комментариях и о стиле программирования. Выглядит отлично, правда? На рис. 6.10 показал снимок окна программы. Давайте обсудим несколько моментов. Сначала, как обычно, идет инициализация графики. Потом мы вызываем функцию AutoMidHandle с параметром true. Если вы помните, эта функция помешает начало координат в центр изображения, так что вам не надо дечагь это вручную. Обратите внимание, что если бы не эта функция, вам пришлось бы самостоятельно вычислять координаты центра изображения! Попробуйте как-нибудь составить уравнение, которое рассчитает эти координаты. Дальше, мы вызываем функцию SetBuf fer и начинаем рисовать в буфере изображения. Мы устанавливаем белый цвет и рисуем прямоугольник. Потом мы возвращаемся обратно в первичный буфер. Далее, мы помещаем изображение во вторичный буфер, переключаемся и, таким образом, выводим изображение на экран. Функция WaitKey останавливает программу до тех нор, пока пользователь не нажмет какую-нибудь клавишу, так что он может спокойно полюбоваться белым квадратом, прежде чем программа закончит работу.
Рис. 6.10. Программа demo06-04.bb Некоторые из этих шагов могут оказаться трудными для понимания. Посмотрите на рис. 6.11. Как вы видите, в процессе можно выделить три контрольные точки: перед первым вызовом SetBuffer, перед вторым вызовом SetBuffer и в конце программы. В первой точке выбран вторичный буфер, а пусгой буфер изображения не виден. Во второй точке выбран буфер изображения, и в нем нарисован белый квадрат, а вторичный буфер не выбран. В последней точке буфер изображения занесен в выбранный вторичный буфер и затем выведен на экран. Итак, мы прошли целый большой раздел про буферы, но мы еще не закончили. В следующей главе мы познакомимся с еще одним применением буфера изображения. Я подскажу вам- мы займемся смешением фонов. Прежде чем мы продолжим, давайте еще немного позанимаемся бу- ферами. Рис. 6.11. Схема работы программы demo06-04.bb
SaveBuffer Вы когда-нибудь слышали о снимках экрана? Это моментальный снимок с экрана, когда игра работает. Другими словами, вы можете сохранить фотографию, на которой показана игра. Функция SaveBuffer () была придумана специально для этого: вызовите ее, и она сохранит фотографию экрана в данный момент. Определение этой функции немного сложнее, чем вы, может быть, подумали: SaveBuffer (bitter,fLlendreSI В табл. 6.3 вы найдете описание каждого параметра з гой функции. В двух словах, buffer - это буфер, который вы хотите сохранить, a f11 enameS - это имя файла, где будет храниться изображение. Файл обычно имеет расширение .bmp. Табл. 6,3. Параметры функции S т Buffer О Параметр Описание buffer Буфер, который вы хотите сохранить на жестком диске filenames Имя файла в формате .bmp Вы можете задать параметру buffer одно из трех значений: FrontBuffer (), BackBufferО или .mageBuffer(buffer, [frame]). Таким образом, вы можете выбрать, какое изображение вы котите сохранить. Представьте, что буфер изображения - это картина на стене, тогда играющий может сохранить только картину, а не всю стену целиком. Правда, здорово? Например, вы можете добавить строку SaveBuffer (FrontBuffer (),"screenshotl.bmp") до вызова функции Drawimage и строку SaveBuffer (FrontBuffer (), "s<'reenshot2, bmp") после вызова этой функции в программе demo06-04.bb. Посмотрите на рис. 6.12 и 6.13. Видите разницу? Так как буфер еще пустой до вызова функции Drawimage, изображение screenshotl .bmp тоже птстос. А на втором снимке экрана появился белый квадрат. Таким образом, вы видите белый квадрат на черном фоне. Вы заметили, что я использовал значение FrontBuf fer () вместо BackBuffer () ? На самом деле слово FrontBuffer <) - это просто псевдоним для BackBuffer (), так что в дейстаительности оба эти слова обозначают одно и то же. Однако здесь я использовал значение FrontBuf fer (}, потому что это помогает понять, что вы сохраняете все, что показано на экране. Давайте попробуем поиграть с параметрами функции SaveBuffer и посмотрим, как выглядит содержимое буферов в разных точках программы. Итак, даваи-j е вст авим эт}’ функцию в программу. Вы можете найт и эту программу на компакт-диске под названием demo06-05.bb. Эта программа рисует на экране квадраты разного размера. Если вы нажмете клавишу F10, то увидите снимок экрана. 1отовы?
Рис. 6.12. Вызов функции SaveBuf fer до вызова функции Crawlmage Рис- 6.13. Вызов функции SaveBuf fer () после вызова функции Drawlmage jdemo06-05.bb - Иллюстрирует работу функгцщ SaveBufferO Graphics 800,600 ;Запускаем генератор случайных чисел SeedRnd (MilliSecs()}
/68 - это код клавиши Fin Const flOkey - 68 /Номер снимка экрана, целое число, у первого снимка номер . snnum = "1" While Not KeyDown(l) /Устанавливаем случайный цвет Color Rand(0,255),Rand(0,255,,Rand(0,255) /Рисуем случайный прямоугольник Rect Rand(0,800),Rand(0,600),Rand(0,200i.RandlO.200),Rand(0,1) ;Пауза Delay 45 /Если пользователь нажал клавишу F10, делаем снимок экрана If KeyHit(flOkey) SaveBuffer(FrontBuffer(),"screenshot" » multi) • ".bmp") snrmjri snnum • ] /Увеличиваем номер снимка на единицу Endlf Wend Если вы попытаетесь запустить эту программу прямо с компакт-диска, программа выдаст ошибку, кактолько вы попробуете сделать снимок экрана. Это происходит потому, что программа не может сохранить файл на компакт-диске. Если вы хотите посмотреть, как работает программа, сначала скопируйте ее на свой жесткий диск. Если нужно, вы можете загрузить файл программы с сайта http://www.maneeshsethi.com. На рис. 6.14 приведен снимок экрана из программы. Давайте посмотрим, как работает эта программа. Как обычно, начинаем с инициализации. Назначаем константе flOkey значение, равное коду клавиши F10. Присваиваем переменной snnum значение 1: в этой переменной будет храниться номер снимка. Внутри цикла мы вызываем функции Color и Rect, чтобы нарисовать прямоугольник случайного цвет а со случайными координатами. Следующая час гь кода, после оператора If, относится к созданию снимка. Оператор I f определяет, была ли нажата клавиша F10, и если да, программа делает снимок. Первая строка внутри блока операторов If - это вызов функции Saveimage (). Как вы видите, эта функция делает сншиок первичного буфера. Команда string работает
Рис, 6.14. Программа demo06-05.bb так: она создает имя файла screenshotnumber.bmp, где number - это единственная часть имени, которая меняется. В следующей строке переменная snnum увеличивается на единицу, и, таким образом, увеличивается номер в названии файла. Итак, с этим мы разобрались. Со снимками экрана действительно интересно работать, а еще они могут очень пригодиться при отладке. Предположим, вы не знаете наверняка, как выглядит буфер изображения в какой-либо точке ващей программы Тогда вы просто вызываете в нужной точке функцию SaveBuf fer i), сохраняете снимок и смотрите! LoadBuffer() У функции LoadBuffer (' существует множество применений. С помощью этой функции вы можете загрузить изображение, которое вы до этого сохранили, обратно в программу. Конечно, изображение уже должно существовать, хотя и не обязательно, чтобы оно было сохранено в той же самой программе. Функция LoadBufferО похожа на функцию Loadlmage(), но только с одним отличием. Когда вы используете Loadlrrage (), вы создаете указатель изображения, который можете использовать далее в программе, а когда используете LoadBuf f er (), то загружаете изображение прямо в буфер. Благодаря этой особенности, функция LoadBuf fer () обычно используется, чтобы загрузить титульный экран. Кроме того, вы можете применять ее для показа слайдов или для чего-нибудь похожего. Функция LoadBuf fer () определяется так: LoadBuffer (buffer,filenames) Здесь buffer - ло буфер, в который вы собираетесь загрузить изображение, a filenames - имя файла этого изображения. В табл. 6.4 приведен краткий обзор параметров.
Табл. 6-4. Параметры функции iadBuf f • г Параметр Описание buffer Буфор, в который вы хотите загрузить изображение f lename$ Имя файла, в котором находится изображение Давайте напишем программу, которая откроет все снимки экрана, созданные в преды дущей программе (помнит е, там был пример функции SaveBurfer t ‘ со случайными прямоугольниками). Все файлы должны тсжать в той же папке, что и программа. Найдите программу demo06-06.bb на компакт-диске. Вначале, как всегда, инициализация. Когда вы запустите программу, то увидите, что файлы загружаются довольно медленно. Это потому, что программе приходится считывать буфер изображения с диска. Если хотите поупражняться, сначала считайте буферы изображения с диска, а потом начинайте показ слайдов, тогда пауза между слайдами будет меньше Попробуйте! ;demoO6-06,ЬЬ - Показ слайдов Инициализируем графику Graphics 800,600 ;Переменная для номера изображения snnum _ 1 ;Привязываем координаты к центру изображения AutoMidHandle True В этом фрагменте объявляется переменная snnurn, и ей присваивается значение 1, а также проводится инициализация графики. Здесь также задается значение AutoMidHandle true. Дальше начинается главный цикл. Прочитайте эту часть внимательно: ;Загружаем и показываем изображение ;Так как мы загружаем изображение в первичный буфер, ;оно автоматически появляется на экране While (LoadBuffer(FrontBuffer(),"screenshot" + annum + ".bmp") <> 0) ;Выводим имя файла изображения Text 400,ЗСЭ,"Image screenshot" т sruum + ".bmp" ;Переходим к следующему изображению
Программирование игр 161 snnum = annum + 1 WaitKey Переключаем изображение на экран Flip Wend Условие цикла выгладит загадочно, но на самом деле оно несложное. Вспомните, что функция LoadBuf fer (), как и большинство других функций, работающих с изображением, возвращает 0, если изображения нс существует, и 1, ec.ni с ним все в порядке-Так что в этой строке мы просто проверяем, существует ли файл с таким именем, какое указано в параметре функции LoadBuf fer (}. При первом проходе цикла программа пытается вывести на экран изображение из файла screenshotl .bmp. Если ей это удалось, значит, условие цикла не равно 0. и цикл продолжает выполняться и выводить изображения на экран. Если же файла с таким именем не существует, выполнение цикла прерывается. Оставшаяся часть тела цикла самая обычная- Программа показывает текстовое окно с именем файла изображения, потом номер изображения увеличивается на единицу. И, наконец, вызывается команда WaitKey, которая приостанавливает выполнение программы до тех пор, пока пользователь не решит посмотреть следующий слайд. И, наконец, последняя функция. Freelmage Функция Freelmage удаляет любое изображение из памяти. Когда вы загружаете изображение в программу; то привязываете его к имени указателя. Позже вам может понадобиться освободить это имя. Представьте, что у вас есть игра с несколькими уровнями, и в ней участвуют скелеты. Вы загружаете изображение скелета под названием skeletonimage. А когда вы переходите на второй уровень, скелет должен выглядеть уже по-другому. Тогда вам придется освободить имя skeletonimage, чтобы потом привязать к этому имени уже другое изображение. Функция Freelmage объявляется так Freelmage handle В табл. 6.5 описан единственный параметр функции. Команда Freelmage очищает память, занятую под изображение. Заодно в распоряжении программы становится больше памяти, и программа начинает работать быстрее. Табл. 6.5. Параметры функции Freelmage Параметр Описание handle Имя указателя изображения, которое нужно освободить
Использовать функцию Free Image очень легко. Вы просто должны вызвать ее, когда изображение вам больше не нужно. Давайте рассмотрим пример. Предположим. вы загрузили набор изображений: imagel ~ Loadimage ("imagel.bmp") image2 = Loadimage {"image2.bmp") image3 = Loadimage ("image3.bmp") Теперь пусть эти изображения вам больше не нужны, и вы собираетесь их удалить. Скажем, пусть imagel - это титульный экран. Вы делаете что-нибудь тина этого*. ;Показываем титульный экран Drawlmage 0,0,imagel Freelmage imagel Пока все просто. Теперь, если изображения image2 и image3 использовались где-то в программе, не забудьте удалить их тоже: While Not KeyDown(l) Wend Freelmage image3 Freelmage image2 Теперь мы удалили изображения, пусть даже в конце программы. Обратите внимание, что я удалил изображения в порядке, обратном тому, в котором они были загружены. На самом деле, порядок не важен; мне просто так больше нравится. Заметьте, что использовать функцию Freelmage обычно совершенно не обязательно. Если вам больше не понадобится имя указателя этого изображения (или, по-другому, переменной этого изображения) и вас не особенно волнуют скорость и память игры, можете не вызывать эту функцию. Однако ее использование — это признак хорошего стиля программирования, а уборку проводить всегда полезно. Никогда не знаешь - а вдруг твоя программа когда-нибудь сможет работать только потому, что вы вставили эту функцию! Блокировка и разблокировка буферов В этом разделе мы, наконец, исчерпаем все возможности, которые могут предоставить нам буферы. Мы уже коснулись почти всего, кроме блокировки и разблокировки буферов. Когда мы говорим о блокировке буфера, то подразумеваем некую функцию, которая делает буфер недоступным для других частей программы. Казалось бы, это плоко - когда нет доступа к буферу. Однако блокировка сулит нам большую выгоду: с ее помощью мы сможем использовать ряд специальных функций:
ReadPixel() WritePixel ReadPixelFast() WritePixelFast CopyPixelFast CopyPixel С помощью этих функций вы сможете редактировать и копировать любой пиксел в своих буферах. Приступим? Lock/Unlock Функции Lock и Unlock объявляются так: LockBuffer buffer UnlockBuffer buffer Параметры обеих функций описываются в табл. 6.6. Табл. 6.6. Параметры функций Lock < Unlock Параметр Описание buffer Имя буфера, который вы хотите заблокировать или разблокировать, чтобы проводить быстрые операции с пикселами Когда вы применяете эти функции, буферы становятся недоступными для большинства операций. Зато теперь вы можете проводить быстрые операции с пикселами: копирование и правку пикселов в буферах. Функции LockBuffer и UnlockBuffer часто вызываются как в следующем примере: ;Пора блокировать буфер LockBuffer BackBufferO ; Работаем с пикселами UnlockBuffer BackBufferО Вы заметили, что я использовал значение BackBuf fer () для блокировки буфера? Вам может показаться странным, что я заблокировал вторичный буфер, а не первичный. Если бы я выбрал пераичный буфер, то программа стала бы работать гораздо медленнее при проведении операций с пикселами (они не приводились в предыдущем примере). Правда, значение BackBuf fer () не обязательно задавать явно. Если вы не укажете значение переменной buffer, то эта функция заблокирует буфер по умолчанию, который можно задать с помощью функции SetBuf fer. Вызов этой функции не приводился в нашем примере, но предполагается, что он выглядел так: SetBuffer BackBuf fer (' Итак, мы узнали, как блокировать и разблокировать буферы. Теперь посмотрим, как эго можно использовать.
ReadPixel()/ReadPixelFast() Функции ReadPixel () и ReadPixelFast () - это первые функции, которые нам понадобятся, чтобы познакомиться с правкой пикселов. Эти функции считывают цвет одного конкретного пиксела. Обычно вам приходится использовать массив, чтобы хранить эти значения. Напоминание: Массиаы Вы помните, что такое массивы? Массивы позволяют хранить большой объем исходных данных в одной переменной. Одномерный массив объявляется примерно так. Dim sdarray(lOO) /Создаем массив из 100 элементов А многомерные массивы - так: Dim mdarray(100,3) /Создаем массив из 100 строк и 3 столбцов На рис. 6.15 и 6.16 показана разница между одномерными и многомерными массивами. Еще одна важная вещь: с помощью вложенных циклов For ...Next вы можете перебирать все элементы многомерного массива, как в этом примере: For columns = 0 То 3 For rows = 0 То 100 mdarray(rows, columns) - 314 /Присваиваем значение элементу массива Next Next Когда вы будете смотреть рис. 6.15 и 6.16, обратите внимание, что индекс элемента массива никак не связан со значением этого элемента. Как показано на рис. 6.16, индекс элемента массива представляет собой комбинацию из номера строки и номера столбца. Например, элемент, который находится во второй строке и в третьем столбце, имеет индекс (2,3). В то же время значение элемента массива показано на этом рисунке буквой х, чтобы отметить, что это значение может быть любым (любым числом или даже строкой). Кажется, что эти две функции очень похожи. Но есть одно маленькое различие. Во-первых, функция ReadPixelFast () работает быстрее, чем функция ReadPixel (). Почему? В действительности для работы функции ReadPixel () блокировка буфера не обязательна, как для функции ReadPixelFast (). Функция ReadPixelFast () работает только с заблокированным буфером, и это позволяет ей быть более гибкой и работать быстрее. Из двух вариантов: заблокировать буфер и работать с функцией ReadPixelFast () или не блокировать и использовать функцию ReadPixel (), надо всегда выбирать первый. Это относится и ко всем остальным функциям Pixel и PixelFast.
Рис. 6.15. Одномерный массив Columns О 1 2 0 X X X Rows 1 X X X 2 X X X Рис. 6.16. Многомерный массив Если вы решите использовать одну из функций PixelFast и не заблокируете буфер, то совершите большую ошибку! Эти функции могут работать только с заблокированным буфером. Если буфер не заблокировен, программа может даже повредить ваш компьютер. Еще советую вам следить за координатами того пиксела, который вы пытаетесь прочесть. Если координаты находятся за пределами экрана, то вы получите ошибочную информацию. Функции ReadPixel (j и ReadPixelFast () определяются так ReadPixel (х,у, [buffer]) ReadPixelFast (х,у,[buffer]) Убедитесь, что вы не забыли скобки, используя эти команды. Когда функция возв-ращеет значение, вы должны заключить ее параметры в скобки В табл. 6.7 приведено описание параметров. Табл. 6.7. Параметры функций ReadPixel () и ReadPixelFast ( Параметр Описание X Координата х пиксела, который вы считываете У Координата у пиксела, который вы считываете (buffer] Буфер, из которого производится чтение. Возможные значения: FrontBuffer(), BackBuffer() или ImageBuffer(1 Давайте разберем пример, в котором будут применяться эти функции. Следующий фрагмент кода считывает пикселы изображения, начиная от левого верхнего угла.
Dim pixelarray(GraphicsWidth!).GraphiesHeight()) LockBuffer BackBufferf ;Буфер НЕОБХОДИМО ЗАБЛОКИРОВАТЬ For cols = 0 To GraphicsHeight() For rows - 0 To Graphicswidth О pixelarray(rows,cols) = ReadPixelFast (rows,cols) Next Next GraphicsWidthf) и GraphicsHeight!) Вы обратили внимание на две новые функции, GraphicsWidthf) и GraphicsHeight (i ? Эти две функции очень полезные и простые: онн возвращают ширину и высоту экрана. Например, вы инициализировали графику в начале npoi раммы так: Graphics 800,600 Тогда строка х “ Graphicswidth*) присвоит переменной х значение 800, а строка у = GraphicsHeight() присвоит переменной у значение 600. Вам может показаться странным, что мы используем GraphicsWidth () и GraphicsHeight () вместо того, чтобы просто написать «800» и «600»- Причина заключается в том, что вы можете поменять разрешение, когда тестируете программу. Если вы поменяете разрешение, но не поменяете «800» и «600», программа будет работать неверно и может даже повредить ваш компьютер! Помните, что лучше всего писать код таким общим и гибким, каким только возможно. Здорово, правда? Этот фрагмент кода сохраняет каждый пиксел экрана в массиве pixelarray. При выполнении цикла for считывается каждый пиксел первого столбца, потом каждый пиксел второго, и т. д. На рис. 6.17 показана схема этого процесса. Правда, если вы используете только функцию ReadPixelFast(j, то немногого добьетесь. Пора переходить к функциям WritePixel/WritePixelFast!
WritePixei/WritePixelFast Эти функции прямо противоположны функциям ReadPixel () /ReadPixelFast ( . Функции ReadPixel () /ReadPixelFast () и WritePixei/WritePixelFast обычно используются вместе, чтобы копировать и вставлять пикселы. Функции WritePixel и WritePixelFast определяются так: WritePixel x,y,rgb,[buffer] WritePixelFast x,у,rgb,[buffer] Вы заметили, что функции WritePixel не возвращают никакого значения, в отличие от функций ReadPixel () ? И поэтому вы можете не заключать параметры функций WritePixei/WritePixelFast в скобки, хотя скобки не возбраняются. В табл. 6.8 вы найдете описание всех параметров. Мне бы хотелось остановиться подробнее на параметре rgb. Как вы помните, когда мы вызывали функцию ReadPixel () /ReadPixelFast (), то сохраняли все пикселы в массиве. Индекс элемента массива соответствует координатам пиксела, а значение элемента массива -это цвет пиксела. Цвет пиксела и есть параметр rgb, и он хранится у нас в массиве pixelarray. Табл. 6.8. Параметры функций WritePixei/WritePixelFast Параметр Описание X Координатах пиксела, который вы рисуеге У Координата у ииксепа, который вы рисуете rgb Цвет пиксела, который вы рисуете (обычно хранится в массиве) [buffer] Необязательный параметр. Буфер, в котором вы собираетесь рисовать Использовать функции WritePixei/WritePixelFast очень просто. Надо только задать параметры! Посмотрите пример. WritePixelFast 0,0,pixelarray(0,0) Эта строка рисует в левом верхнем углу экрана пиксел, который имел координаты (0,0), когда мы работали с функцией ReadPixel () /ReadPixelFast (). Теперь давайте разберем полный текст программы. Вы можете найти эту программу (с полными комментариями) на компакт-диске под названием demo06-07.bb. ; demo06-07.bb - Пример функций ReadPixelFast/WritePixelFast Graphics 350,350,0,2
Text 0,0, "Press any key to use Readpixel" ;Переключаем текст на экран Flip ;Ждем, пока пользователь что-нибудь предпримет WaitKey ;Загружаем изображение прямоугольника image =Loadimage("rectangle.bmp") ;Помешаем это изображение в буфер Drawlmage image,0,0 Drawlmage image,100,100 ;Переключаем изображение на экран Flip ;Ждем одну секунду Delay (1000) ;Создаем массив, куда будет записан каждый пиксел экрана Dim pixelarray(Graphicswidth(),GraphicsHeight()) ;блокировка буфера ОБЯЗАТЕЛЬНА LockBuffer ;Копируем все пикселы экрана в массив For rows = 0 То Graphicswidth () For cols = 0 То GraphicsHeight() ;Копируем текущий пиксел pixelarray(rows,cols) = ReadPixelFast(rows,cols) Next Next ;Разблокируем буфер UnlockBuffer
Cis Text 0,0,"Press another key to copy pixels backwards" /Переключаем текст на экран Flip /Ждем нажатия клавиши WaitKey /Блокируем буфер для работы функции WritePixelFast LockBuffer /Вызываем функцию WritePixelFast, чтобы перерисовать экран /с использованием информации по цветам, /которую мы получили раньше For rows = 0 То GraphicsWidth() For cols = 0 То GraphicsHeight () /Рисуем текущий пиксел WritePixelFast rows,cols,pixelarray(GraphicsWidth()-rows,cols) Next Next /Переключаем изображение на экран Flip /Разблокируем буфер после окончания /работы функции WritePixelFast UnlockBuffer Text 0,0,"Press a key to exit" /Переключаем текст на экран Flip WaitKey На рис. 6.18 показан снимок окна работы программы
Рис. 6.18. Программа demo06-O7.bb Эта программа довольно сложная. Давайте попробуем разобрать ее по порядку. Вначале идет инициализация графики и загрузка изображений. Далее программа выводит на экран текст, который собирается копировать. После того, как пользователь нажмет клавишу, программа последовательно вызывает функции LockBuffer и ReadPixel()/ReadPixelFast{). LockBuffer For rows = 0 To GraphicsWidth() For cols = 0 To GraphicsHeight ( pixelarray(rows,cols) = ReadPixelFast(rows,cols) Next Next UnlockBuffer Вспомните, что функцию LockBuffer обязательно надо вызвать раньше, чем начнет работать функция ReadPixelFast (). Так же как и все остальные функции для работы с пикселами, функция ReadPixelFast () работает с заблокированным буфером, иначе программа будет выполняться неверно и может завершиться с ошибкой. Функция ReadPixelFast {) работает так же, как в примере, приведенном в одном из разделов выше; она копирует все пикселы одной строки, а потом переходит к следующей строке. Каждый пиксел сохраняется в массиве. Когда функция ReadPixelFast () закончит свою работу, необходимо вызвать функцию UnlockBuf fer, прежде чем двигаться дальше.
Теперь мы переходим к фрагменту, в котором вызывается функция WritePixelFast. LockBuffer For rows - 0 To Graphic sWUth ' For cols = 0 To GraphicsHeight < > WritePixelFast rows cols,pixelarray{GraphicsWidthf)-rows,coIs) Next Next UnlockBuffer Эта часть кода работает так же, как и предыдущий фрагмент Сначала программа блокирует буфер, потом начинаются вложенные циклы for, которые проходят по всем столбцам одной строки, прежде чем перейти к следующей строке. Функция WritePixelFast - это самая интересная часть программы. Давайте рассмотрим ее подробнее. WritePixelFast rows,cols,pixelarray(Graphicswidth()- rows,cols) Я вызвал эту функцию как-то не так. Видите, почему? Я использовал переменные rows и cols в качестве параметров координат х и у. Это, в общем, неправильно, но я хотел привлечь ваше внимание к одному моменту. В данном случае, все будет работать, но обычно это не так. Чаще всего вам надо копировать не весь экран, а только небольшую его часть. Вам придется использовать еще один цикл for, чтобы получить верные значения для х и у, или увеличивать эти значения внутри существующего цикла. Первые два параметра функции легко понять. Функция WritePixelFast рисует пиксел с координатами х,у. Координаты х,у определяются переменными rows и cols, которые задаются в цикле For...Next. Однако третий параметр сложнее. Функция начинает рисовать пиксел в строке с крайней левой позиции, но помещает на это место тот пиксел, который раньше был крайним справа. Таким образом, изображение выводится не таким, каким оно копировалось, а симметричным относительно вертикальной оси. Посмотрите, как задается третий параметр функции. pixelarray(GraphicsWidt h(i- rows,cols) В то же время, вертикальная координата каждого пиксела остается неизменной. Если бы вы хотели, чтобы изображение выводилось симметрично относительно горизонтальной оси, то надо было бы задать параметр так: pixelarray{rows.GraphicsHeight() cols) Правда, здорово? Теперь мы закончили с функциями WritePixel/WritePixelFast и ReadPixel/ReadPixelFast. Существуют еще функции CopyPixel/Copy-PixelFast. Они определяются так: CopyPixel src_x,src_y,src_buffer,dest_x,dest_y,[dest_buffer] CopyPixelFast src_x,src_y,src_buffer,dest_x,dest_y,[dest_buffer] Если вы хотите разобраться с этими функциями, используйте это определение, чтобы написать несколько пробных программ!
Применение буферов: программа рисования Вы много узнали о буферах из этой главы. Теперь пора использовать все знания и написать настоящую полную программу. Это будет программа рисования. Играющий сможет нарисовать что-нибудь в основной части экрана, а программа потом сохранит рисунок. Вы можете найти эту программу под названием demo06-08.bb на компакт-диске. Я советую вам открыть эту программу и заглядывать в текст, когда вы будете читать этот раздел, так вам будет гораздо проще во всем разобраться. Давайте подумаем, что же нам понадобится для этой программы. Прежде всего, программа должна выполнять свое предназначение - дать возможность пользователю рисовать. Предоставим в его распоряжение пять цветов: зеленый (green), красный (red), синий (blue), черный (black) и белый (white). Пользователь сможет сменить цвет, если нажмет клавишу с его номером, Меню выбора цвета будет выглядеть примерно так, как показано на рис. 6.19. Choose your Color 1 Green 2 Red 3 Blue 4 Black 5 White Рис. 6.19. Предполагаемое меню выбора цвета Почему мы используем эскизы? Вы, наверное, обратили внимание, что на последнем рисунке изображен эскиз, а не снимок экрана. Мне бы хотелось воссоздать реальный процесс создания программы или игры. Прежде чем написать первую строку кода, вы должны четко представить себе, что имение вы пытаетесь написать. Хороший способ сделать это - нарисовать эскизы своей программы. Лучше всего нарисовать то, что должно получиться, но и просто описать словами тоже неплохо. Попробуйте сформулировать, что должно происходить, когда пользователь выполняет какое-нибудь действие. Продумайте, как будут выглядеть персонажи и изображения в игре. Теперь надо добавить текст, который сообщит пользователю, какой цвет он выбрал. Кроме того, чтобы было интереснее, давайте создадим индикатор позиции, который будет показывать положение указателя мыши. Мышь? А что это, собственно, такое? Мышь - это такая штука у вас в правой руке, которую вы используете, чтобы передвигать объекты на экране. В этой программе я собираюсь использовать мышь, чтобы интерфейс был более дружественным. Вы еще не знаете, как использовать мышь в программе, но не волнуйтесь. В следующей главе вы узнаета, как работать с вводом с помощью мыши. Давайте расположим индикатор выбора цвета и индикатор позиции рядом с меню выбора цвета. Посмотрите на рис. 6.20, как все будет выглядеть вместе.
Choose your Color 1 Green 2 Red Selected Color Mouse 3 Blue ... x:______ 4 Black y:______ 5 White Рис. 6.20. Полный эскиз информационного экрана Что такое информационный экран? Информационный экран обычно используется в видеоиграх, но иногда этот термин применяют и для описания других программ, таких, как наша. Для большинства программ информационный экран - это просто панель управления. В более общем случае, информационный экран представляет собой часть основного экрана, где выводится статистика и другая информация для пользователя. На рис. 6.21 информационный экран расположен в левом нижнем углу окна. Рис. 6.21. Информационный экран Давайте зарезервируем верхнюю часть окна программы для информационного экрана, а в остальной части окна можно будет рисовать. Теперь мы можем приступать к написанию программы.
Инициализация Как всегда, мы начинаем с хорошо откомментированного первого раздела программы. Сначала вызовем функцию Graphics. /Устанавливаем режим окна для графики Graphics 640,480,0,2 Хочу напомнить вам, что означает каждый из четырех параметров. Первые два задают ширину и высоту окна программы соответственно, третий параметр - это количество цветов в программе, четвертый параметр определяет режим. В нашей программе окно создается с разрешением 640x480 и «гнедом цветов по умолчанию, а работать она будет в режиме окна (значение 2 соответствует режиму окна). После вызова функции Graphics выберем вторичный буфер. Далее, зададим значения констант: ;Вторичный буфер SetBuffer BackBuffer() /Константы для клавиш клавиатуры Const ESCKEY = l.ONEKEY = 2.TWOKEY = J.THREEKEY - 4,FOURKEY = 5, FIVEKEY = 6.F10KEY = 68 /Задаем высоту и ширину буфера Const BUFFERHEIGHT = 480, BUFFERWIDTH = 640 Вы можете изменить высоту и ширину буфера, если хотите. Эти значения определяют размер той области, в которой можно будет рисовать. Только не делайте эту область больше, чем 640x480. Теперь нам надо загрузить все изображения, которые понадобятся в программе. Мы решили, что пользователь сможет выбирать из пяти цветов: зеленого, красного, синего, черного и белого. Значит, мы должны загрузить пять изображений, каждое своего цвета (образцы цветов). Когда пользователь выберет цвет и начнет рисовать, в действительности на экране появится изображение образца этого цвета. /Образцы цветов Global greenimage = Loadimage("greencoloi.bmp") Global redimage = Loadimage("redcolor.bmp") Global blueimage = Loadlmage("bluecolor.bmp") Global blackimage = Loadimage("blackcolor.bmp") Global whiteimage = Loadimage("whitecolor.bmp") На рис. 6.22 показан размер каждого образца цвета (8x8 пикселов). Кроме того, нам придется загрузить изображение указателя мыши. С его помощью пользователь сможет увидеть, где Рис. 6.22. Изображение одного образца цвета
находится указатель (в то время как индикатор позиции х и у просто сообщает координаты). Global mouseimage = Loadimage("mousearrow.bmp") И, наконец, самое важное изображение. Это холст, на котором мы будем рисовать. Я назвал его -буфер рисунка». Буфер рисунка не надо загружать, мы его просто создадим в программе. Посмотрите соответствующую строку: ; Создаем пустое изображение, на котором можно будет рисовать Global picturebuffer = Createlmage(BUFFERWIDTH,BUFFERHEIGHT) Теперь, когда у нас есть буфер рисунка, все, что осталось сделать, - вызвать функцию ImageBuf fer с параметром picturebuffer, чтобы приготовить экран к рисованию. Это мы сделаем немного позже. Сейчас мы переходим к объявлению переменных. У нас есть только одна переменная, определяемая пользователем, это selectedcolor (в ней будет храниться цвет). Следующая строка кода устанавливает зеленый цвет (Green) в качестве цвета по умолчанию. ;Выбираем зеленый в качестве цвета по умолчанию Global selectedcolor - 1 Напомню, что возможны следующие цвета: 1. зеленый: 2. красный; 3. синий; 4. черный: 5. белый. Последняя часть начального раздета называется «Маски». Помните, маскировка позволяет сделать фон изображения прозрачным? В нашей программе мы используем этот прием дважды: Maskimage mouseimage,255,255,255 ;маскирует белый фон значка указателя Maskimage blackimage,255,255,255 /меняет маску, чтобы черный цвет был виден Первая маска относится к изображению указателя мыши. Фон у этого изображения белый, и нам придется замаскировать его, чтобы избавиться от белого прямоугольника вокруг указателя. На рис. 6.23 показано изображение указателя и маска. Вторая маска немного отличается от первой. Помните, мы загружали образец черного цвета? Это изображение черное, и цвет маски по умолчанию - тоже черный, так что я немного изменил маску, чтобы она не была невидимой. Если бы я этого не сделал, то черным цветом нельзя было бы рисовать: ничего не было бы видно. На этом начальный раздел программы закончился, и мы переходим к главному циклу.
Изображение курсора мыши Maskimage 255, 255, 255 Рис. 6.23. Маскировка указателя мыши Главный цикл Обычно я стараюсь сделать главный цикл как можно короче, и эта программа не исключение. Главный цикл включает в себя только несколько вызовов функций, а также выводит буфер рисунка на экран. На рис. 6.24 показана схема отавного цикла. ;ГЛАВНЫЙ ЦИКЛ While Not KeyDown(ESCKEY) ;Очищаем экран Cis ;Выводим информационный экран DrawAllText() /Показываем указатель мыши DrawMouse(> {Проверяем, нажимал ли пользователь клавиши на клавиатуре
TestKeyboardlnput() ;Проверяем, нажимал ли пользователь кнопку мыши TestMouselnput() гВыводим на экран буфер рисунка Drawlmage picturebuffer,0,100 ;переключаем буферы Flip Wend ;КОНЕЦ ГЛАВНОГО ЦИКЛА Как видите, сам но себе главный цикл почти ничего не делает. Все делают функции, которые вызываются внутри него. В табл. 6,9 перечислены все эти функции. Табл. 6.9. Список функций из программы demo06-08.bb Параметр Описание DrawAllText() Эта функция вызывает все функции, которые о гвечают за работу с текстовой областью. Фак шчески, они от вечают за содержимое информационного экрана DrawMouse(} Эта функция изображает указатель мыши в той точке, куда пользователь его передвинул TestKeyboardlnput() Эта функция обрабатывает информацию, вводимую с клавиатуры. Это обычно требуется, кода пользователь хочет сделать снимок экрана или изменить цвет TestMouselnput() Эта функция проверяет, куда по.1ь.юватель передвинул указатель мыши и нажал ли он левую кнопку- мыши (чтобы что-нибудь нарисовать) Кроме функций из этого списка, внутри главного цикла также вызываются стандартные функции, которые используются в большинстве видеоигр. Функция Cis очищает экран, а команда Flip меняет местами содержимое первичного и вторичного буфера. Программа также выводит на экран буфер рисунка; ; выводим на экран буфер рисунка Drawlmage picturebuf fer,0,10 0 Буфер рисунка выводится с координатами 0.100, так что он не пересекается с информационным экраном. Итак, мы разобрались с главным циклом. Пора переходить к описанию функций.
Функции Итак, функции. У нас их довольно много. Сначала вызывается функция под названием DrawAllText (). Эта функция, в свою очередь, вызывает другие команды, которые работают с информационным экраном. Функция DrawAllText () определяется так: Функция DrawAllText() Вызывает функции для работы с информационным экраном Без параметров Function DrawAllText() ;Выводим меню выбора цвета DrawTextlnfо() ;Показываем, какой цвет выбран ColorText() /Показываем индикатор позиции указателя мыши MouseText() End Function Конечно, нам необходимо познакомиться также с функциями DrawTextlnfо О ColorText () и Mouserext (). Первая из них выглядит так: Функция DrawTextlnfо() Выводит меню выбора цвета Без параметров Function DrawTextlnfof) /Показывает, из каких цветов может выбирать пользователь Text 0,0,"Press the number of the color you wish to draw" Text 0,12,"Colors:" Text 0,24,"1. Green" Text 0,36,"2. Red" Text 0,48,"3. Blue" Text 0,60,"4. Black" Text 0,72,"5. White"
Text 0,84,"Press F10 to save image (text WILL NOT be saved)" End Function ,-КОНЕЦ ФУНКЦИИ Функция ColorText () определяется следуюшим образом: Функция ColorText() Определяет, какой цвет выбран, и выводит его название на экран Без параметров Function ColorText() ;Записываем в переменную selectedcolortextS название цвета Select (selectedcolor) Case 1 selectedcolortextS = "Green" Case 2 selectedcolortextS = "Red" Case 3 selectedcolortextS = "Blue" Case 4 selectedcolortextS = "Black" Case 5 selectedcolortextS = "White" End Select ; Выводим выбранный текст Text 240,20,"Selected Color: " + selectedcolortextS End Function И. наконец, функция MouseText () выглядит так: Функция MouseText() Показывает индикатор позиции указателя мыши Без параметров Function MouseText() mousextextS = "Mouse X: + MouseX()
mouseytext$_ - "Mouse Y t Text 540,20,mousext ы.»’ Text 540,40,mousey' End Function + MouseYf) Эти функции кажутся длинными и сложными, но на самом деле не все так плохо Давайте рассмотрим каждую из них более подробно. Начнем с функции DrawTexcIn.ro (i. Эта функция сообщае т пользователю, из каких цветов ок может выбирать. Также она выводит информацию о том, как сохранить рисунок. На рис. 6.25 показан результат работы функции DrawTextlnf о (). Рис. 6.25. Результат работы функции Сравните рис. 6.25 с рис. 6.19. На рис. 6.19 приведен эскиз, который мы сделали вначале. Обратите внимание, что, хотя эти рисунки похожи, все же они отличаются друг от друга. Это важно. Вначале вы обязательно рисуете эскиз. Потом, когда вы работаете над программой, то руководствуетесь этим эскизом, но результат всегда будет выглядеть немного по-другому. Весь текст имеет размер шрифта 12 пунктов (это размер по умолчанию для программы BlitzPIus), так что каждая следующая строчка расположена на 12 пикселов ниже предыдущей. Следующей идет функция С : lorText |). Эта функция показывает, какой цвет выбрал пользователь. Как вы знаете, чтобы выбрать цвет, пользователь должен нажать одну из клавиш с цифрами I, 2, 3, 4 или 5. Нам надо не прост показать номер цвета, а написать его название. Поэтому мы используем условный оператор select. Он присваивает значение переменной s«' ctedco lor text $ в зависимости от того, какую клавишу нажал пользователь. После окончания условного оператора select на экран выводится название цвета. Последняя функция - эго ). Вы, конечно, еще ничего не знаете про мышь, но, в сущности, зга функция просто определяет координаты (х,у) указателя мыши и выводит их на экран. На рис. 6.26 показан весь информационный экран целиком. Вы можете сравнить этот рисунок с более ранним эскизом на рис. 6.20 и посмотреть, чем они отличаются. Рис. 6.26. Информационный экран весь целиком
Итак, с одной функцией мы разобрались. Теперь рассмотрим функцию TestKeyboardlnput (). Эта функция перехватывает ввод с клавиатуры и обрабатывает полученную информацию. Вы, может быть, заметили, что мы пропустили функцию DrawMouse ।). Я решил не разбирать ее здесь, потому что вы еще не умеете обрабатывать движение указателя мыши и нажатие ее кнопок. Впрочем, эта функция довольно простая, и если вы хотите посмотреть ее, просто откройте программу demo06-08.bb с компакт-диска. Когда пользователь запускает нашу программу, у пего не так много возможностей что-нибудь ввести в нее с клавиатуры. Он может только выбрать цвет или нажать клавишу-FI 0, чтобы сохранить рисунок. Цвет можно выбрать с помощью пяти разных клавиш, так что мы должны учесть все варианты в функции Т stKeyboardlnput (). ;функция TestKeyboardlnput() ;Проверяет, нажал ли пользователь клавишу ,,д клавиатуре, ;чтобы изменить цвет или сохранить рисунок ;Без параметров Function TestKeyboardlnput() ;Если пользователь нажал клавишу, установить выбранный цвет If KeyDown(ONEKEY) selectedcolor = Elself KeyDown(TWOKEY) selectedcolor = Elself KeyDown(THREEKEY) selectedcolor = Elself KeyDown(FOURKEY) selectedcolor = 4 Elself KeyDown(FIVEKEY) selectedcolor = 5 Endlf ;Если пользователь нажал клавишу Fl0, сохраняем рисунок If KeyDown(F1CKEY' ;Сохраняем буфер рисунка в файле screenshot.bu^ SaveBu f fer ImageBuf fer(picturebu f fer),“screei ushnt.bmp * Endlf End Function
Если вы попытаетесь использовать имя -1кеу» вместо ONEKEY, программа не будет работать. Почему? Названия переменных могут начинаться только с буквы или символа подчеркивания («_»), но не могут начинаться с цифры или специального символа (#, $ ит. д.). Этот фрагмент кода проверяет, не была ли нажата одна из клавиш выбора цвета. Константы ONEKEY, TWOKEY, THREEKEY, FOURKEY и FIVEKEY были определены в начале программы. Каждое из этих имея соответствует надписи на клавише: ONEKEY - это то же самое, что и «1», TWOKEY - то же, что и «2», и т. д. Последняя строка функции сохраняет рисунок после того, как пользователь нажал клавишу F10: ;Если пользователь нажал клавишу F10, сохраняем рисунок If KeyDown(F10KEY) /Сохраняем буфер рисунка в файле screenshot.bmp SaveBuffer ImageBuffer(picturebuffer),"screenshot.bmp® Сначала этот фрагмент кода проверяет, была ли нажата клавиша F10 (она находится в верхнем ряду клавиатуры). Мы присвоили константе F10KEY значение, равное коду клавиши F10, в начальном разделе программы. Когда пользователь нажимает F10, программа делает следующее: SaveBuffer ImageBuffer(picturebuffer)."screenshot.bmp" Как вы помните, функция SaveBuf fer () сохраняет содержимое буфера, определенного первым параметром, в файл, имя которого задается с помощью второго параметра. Мы определили первый параметр как ImageBuf fer (picturebuffer), следовательно, сохраняться будет только буфер рисунка, а не все окно вместе с информационным экраном. На рис. 6.27 показано, как это происходит. Image buffer FrontBufferf) p|cture —Savin9 >- picture ^-Screenshot.bmp picture butler Рис. 6.27. Использование SaveBufferf) Не пытайтесь сохранить рисунок, если вы запускаете программу непосредственно с компакт-диска. Поскольку текущий каталог будет расположен на диска, а запись надиск невозможна, вы получите сообщение об ошибке. Чтобы этого не происходило, просто скопируйте вначале программу на свой жесткий диск. После того, как рисунок готов, содержимое буфера изображения сохраняется в файле screenshot.bmp. Этот файл будет храниться в том же каталоге, что и программа
Убедитесь, что, прежде чем запускать программу, вы скопировали ее на жесткий диск своего компьютера. Вы можете получить сообщение об ошибке, если попытаетесь сохранить буфер, когда запускаете программу прямо с компакт-диска. Последняя функция, которая у нас осталась, - это TestMouselnput (). Эта функция проверяет, не нажал ли пользователь левую кнопку мыши. Если кнопка была нажата, программа выводит выбранный цвет. Функция TestMouselnput (J выглядит так: ;Функция TestMouselnput() ,-Если пользователь нажал кнопку мыши, рисуем образец цвета ;Без параметров Function TestMouselnput() {Если пользователь нажал левую кнопку мыши, ;выводим образец выбранного цвета If MouseDown(l) {Начинаем вывод только в нужной области экрана SetBuffer(ImageBuffer(picturebuffer)) {Выводим образец выбранного цвета там, где находится указатель мыши Select (selectedcolor) Case 1 Drawimage(greenimage,MouseX(),Mousey() - 100) Case 2 Drawimage(redimage,MouseX(),Mousey() - 100) Case 3 Drawimage (blue image, MouseX () , MouseY () - 100) Case 4 Drawimage (blackimage, MouseX () , MouseY () 100) Case 5 Drawimage(whiteimage,MouseX(),MouseY() 100) End Select End If ; возвращаемся обратно к вторичному буферу
SetBuffer BackBulZ. End Function Первое, что делает эта функция, - проверяет, не была ли нажата левая кнопка мыши. Если кнопка была нажата, программа устанавливает буфер но умолчанию как ImageBuf fer (р^. ireb'if fer1. Таким образом, пользователь может рисовать только в заданной области экрана, а не во всем окне. Инструкция Select определяет цвет по умолчанию и рисует образец изображения этого цвета. Само рисование производится с помощью функции Drawlmaqe: Drawlmage(*in~xge,N<u3eX() .Мс^ле! , 100j Эта функция выводит на экран образец цвета с координатами х и у, причем нужно ввести поправку для координаты у, так как буфер изображения расположен на 100 пикселов ниже верхнего края экрана. Уберите 100», и увидите, что получится! В приведенном примере с функцией Dr aw Image я использовал в качестве указателя изображения значение «* image». Здесь звездочка соответствует одному из пяти цветов- зеленому, красному, синему, черному или белому. Вот мы и закончили разбирать нашу пртрамму. На рис. 6.28 показано окно программы, а на рис. 6.29 - сохраненный рисунок. Обратите внимание, что на втором рисун ке нет информационного экрана. Это потому, чго сохраняется только буфер рисунка ImageBuf fer (p_rturebuf fer 1. а нс все окно программы. Вот вам хорошее упражнение. Измените программу так, чтобы рисунок каждый раз записывался в новый файл. 1огда пользоватс.ш сможет сохранить несколько рисунков. Или измениге программу так, чтобы пользователь мог указать, в какой файл со- хранить рисунок. рис. 6.28. Программа demo06-08.bb
Рис. 6.29. Рисунок, сохраненный из программы demo06-08.bb Заключение Да, это была длинная глава. Я надеюсь, что вы разобрались в темах, которые мы обсудили. В этой главе мы освоили: различные типы буферов; как использовать буферы при переключении страниц; как загружать и сохранять буферы; > как блокировать буферы и разблокировать их; как быстро записать или считать информацию о пикселе; как написать целую программу на основе буферов. Я надеюсь, что эта глава вам пригодится. Она очень важная, поэтому, если вы чего-нибудь не поняли, вернитесь к соответствующему разделу и внимательно перечитайте его, прежде чем двигаться дальше А теперь приготовьтесь! В следующей главе вы окунетесь в сумасшедший мир программирования изображений!
ГЛАВА 7 Основы программирования изображений Добро пожаловать в главу 7! В этой главе вы узнаете, как использовать спецэффекты в ваших изображениях. Они включают в себя вращение, масштабирование и трансляцию (или, в просторечии, сдвиг). Вы также научитесь искусству размножения изображений и смешению фонов, кто и другое действительно круто. Вы, скорее всего, не понимаете, о чем я сейчас говорю, но не беспокойтесь, эти понятия тщательно объясняются в главе. Как бы там ни было, приготовьтесь, потому что мы начинаем! Преобразования Преобразования очень важны в программировании игр. Они используются везде, где у вас есть движение; они изменяют положение изображения или направление. Существуют три типа преобразований: трансляция, масштабирование и вращение. Давайте начнем со сдвигов. Сдвиг Если вы услышите о трансляции текста, вы, вероятно, подумаете о переводе его на другой язык. Верно, но трансляция изображений - это нечто совсем другое! При использовании сдвигов в программировании игр вы берете изображение в текущем положении и затем помещаете его в другое положение, таким образом, сдвиг - это всего лишь удобный способ показа движения! Сдвиг перемещает изображение от одной координаты к другой. После окончания перемещения результат выглядит, примерно как на рис. 7.1.
Сдвиг является очень простой операцией. По существу, вы просто выводите изображение в другой позиции. Например, у вас есть вражеский корабль, который движется из верхнего левого угла экрана к правому нижнем}'углу экрана, и вы хотите, чтобы корабль проходил пять пикселов в секунду. Тогда у вас в программе должен быть раздел инициализации, похожий на приведенный ниже: shipx = О shipy = О Памятка: координатное положение Все мы забываем что-то с течением времени, не правда ли? Вы, возможно, забыли, как используются координаты; а поскольку они чрезвычайно важны в этой главе, то хорошо было бы их повторить. Система координат имеет две оси: ось х и ось у. Ось - именованная числовая прямая. Две оси на компьютерном экране выглядят, как на рис. 7.2. Как вы, вероятно, заметили, здесь есть штриховые метки с числами перед ними. Конечно, эти черточки невидимы на компьютерном экране. Как видно, нулевое деление обеих осей х и у находится в верхней левой точке экрана. Если вы переместитесь вправо или вниз монитора, оси х и у получат приращение. Если вы меняете приращение вдоль обеих осей, вы можете установить точку на экране туда, куца захотите- Рис. 7.3 демонстрирует точку, которая сдвинута на 10 пикселов вправо по оси х и на 16 позиций вниз по оси у. Очевидно, должен быть лучший способ описать эту точку, чем «10 пикселов вправо по оси х и 16 пикселов вниз по оси у». Этот способ - размещение внутри круглых скобок; поставьте сначала координату х, затем запятую и затем координату у. Таким образом, точка на рис. 7.3 - это (10,16). «10» означает 10 пикселов по оси х, и «16» означает 16 пикселов по оси у. Операторы этого раздела размещают корабль в левом верхнем углу экрана. Теперь начнем игровой цикл. Поскольку мы передвигаем корабль диагонально вниз на пять пикселов за секунду, мы должны обновлять положение корабля с помощью кода следующего типа: While Not KeyDown (1) Cis Drawlmage shipimage,shipx,shipy shipx = shipx » 7 shipy = shipy 4 5 Flip Wend
Посмотрим, что это такое. Мы начинаем с цикла точно так же, как начинается любая другая игра. Сначала очищаем экран, чтобы можно было использовать постраничное отображение. Затем мы выводим изображение корабля. Заметьте, что у вас есть выбор, куда поместить вашу команду Drawlmage. Я выбрал размещение команды в начале цик-.ы так, чтобы вы могли видеть изображение судна в точке 0,0, но вы можете поместить команду в конец цикла. Команда Drew,image выводит изображение судна в соответствии с набором координат х и у. Когда цикл выполняется в первый раз, обе переменные shipx и shipy равны 0, ио их значения изменяются в следующей строке программы. Эта строка добавляет 7 к shipx и 5 к shipy. Поскольку добавления происходят в каждом кадре, судно перемещается на семь пикселов вправо и на пять пикселов вниз в каждом кадре. Рис. 7.4 можег помочь прояснить координатные позиции д ля вас - это таблица значений и номеров кадра. Уравнение, написанное рядом с таблицей на рисунке. позволяет вам определять позицию х и у, подставляя номер кадра. Конечно, как только значения х и у выходят за пределы экрана, изображение уже не может быть видимым, но координаты изображения все еще обновляются. Оставшаяся часть главного цикла является командой Flip, которая работает с отображением сгранп-цы. Пожалуйста, вернитесь к главе 6 для повторения пройденного материала. если вы не понимаете, что делает команда Flip. Вся программа доступна на компакт-диске под именем demo07-01.bb. Рис. 7.5. по- называет окно программы. Increasing X —► origin (0,0) S 15 25 35 45 55,55,75,85 85^ 5 15 25 х 35- а «• | 55 I 65 *75 85 85 Рис. 7.2. Система координат origin (0,0' 2 4 6 8 10 12 14 16 18 20 2 6 6 10 12 14 16 • (10.16) 18 20 Рис. 7.3. Точка в системе координат Frame # 1 0 0 2 5 5 3 10 10 4 15 15 5 20 20 Equation х=5- framecount - 5 у=5- f ramecount - S Рис. 7.4. Таблица значений координат
Л Рис. 7.5. Программа demo07-01 .bb Давайте быстро уясним уравнение для сдвига. Эта формула очень проста, но таким же простым преобразованием и являет ся сдвиг! Х[1[ = X + dx у[1] = У + dy Символ <3 в обозначениях величин dx и dy имеет обоснование; это не просто случайная буква. В греческом языке буква дельта, которая обозначается кек треугольник (Л), подразумевает “приращение». Если вы «читаете» переменную, вы можете видеть, что dx и dy означают «приращение х» и «приращение у». Что это означает? Так, если вы введете подходящие координаты (в данном случае значения х и/или у) и прибавите число, на величину которого вы хотели бы сделать сдвиг (это число dx или dy может быть отрицательным или положительным), то получите новое координатное положение для переменной. Например, в строках кода: shipx = ehipx г 7 shipy = shipy + 5 величины shipx и shipy - это х [ 1, и у i - соответственно. Величины shipx и shipy - это х и у, а 7 - это dx и 5 - эго dy. Все эти переменные занимают место кон стант в предыдущем уравнении. Заметьте, что переменные х [ 1 ] иу [ 1 ] -то же самое, что и переменные х и у. Это не столь важно, поскольку вы обновляете переменные! для передвижения изображения. Вы готовы написать функцию Translate? Функция Translate() смещает любую точку, переданную ей в виде параметров. Начнем с объявления функции. Нам понадобится входная координата и переменная преобразования («d»). Таким образом, функция должна выглядеть примерно так Function Translate(x,dx) Туг все понятно, не правда ли? И, конечно, тело функции должно быть таким же простым:
Return х + dx Круто, да? Табл. 7-1 описывает каждый из параметров этой функции. Табл- 7.1. Параметры функции Translate О Параметр Описание X Координата, по которой вы хотите сделать сдвиг dx Приращение, на которое сдвигается переменная х Давайте перепишем главный пикл с помощью новой функции. While Not KeyDown(l) Cis Drawlmage shipimage.shipx,shipy shipx - Translate(shipx,7) shipy = Translate(shipy,5) Flip Wend Function Translate(x,dx) Return x + dx End Function Ай да мы! Мы теперь имеем рабочую функцию сдвига. Хотя это может показаться тривиальным, вероятно, намного проще понять строку shipx = Translate(shipx,5) вместо shipx = shipx + 5 Вы не согласны? Обратите внимание, что функция Translate () не использует глобальные переменные, что делает ее в высшей степени мобильной, поскольку она может теперь использоваться в любой другой программе. Копируйте код. и вы сможете использовать Translated столько, сколько вы хотите. Между прочим, программа, использующая функцию Translate (), доступна на компакт-диске под именем demoO7-O2.bb. Если вам нужна помощь для понимания главного цикла, см. рис. 7.6. While... —>• х= Translate(x.dx) -> y=/rranslate(y,dy) Function Translate^,dx) — Return x + dx End Function Рис. 7.6. Главный цикл
Хорошо, теперь, когда мы узнали о трансляции, давайте вникать дальше, рассмотрим масштабирование Масштабирование Когда вы масштабируете объект, вы делаете объект большим, или меньшим, или того же самого размера, если вы действительно этого хотите. Масштабирование означает создание чего-то меньшего или большего размера, но обычно сохраняющего те же самые пропорции. Пропорциональность, тем не менее, не обязательна. В отличне от сдвига вы не можете масштабировать точку. Это потому, что точка есть точка - вы не можете делать точку различных размеров. Давайте узнаем, что такое пропорциональность и как она используется. Пропорциональность? Что это такое? Пропорция - это соотношение, записываемое в виде дроби. Например, соотношение объекта к другому' объекту, который в два раза больше, - 1:2 или 1/2. Если бы другой объект был в три раза больше, то и соотношение было бы 1:3 (и дробь была бы 1/3). Если вы смотрите на дробь, обратите внимание, что 1/3 то же самое, что и 1, деленная наЗ. Это интересно: меньший объект («1» в 15) равен 1/3 размера большего объекта. Если вы перевернете верхнюю и нижнюю части дроби, вы получаете дробь 3/1, где размер большего объекта сравнен с размером меньшего объекта (больший объект в три раза больше меньшего объекта). Взгляните на рис. 7.7. На этом рисунке слева вы можете видеть изображение человека стандартного размера. Изображение справа - тот же самый человек, но масштабированный. Его размер -1/5 размера исходного человека (большой человек относится к маленькому' как 5:1). Вы можете также использовать соотношения и дроби, когда объект становится меньшим. Скажем, у вас есть объект А и объект В. Объект В в пять раз меньше объекта А. Отношение в этом примере 5:1 или дробь 5/1 (или просто 5: любое число, разделенное на I, - это то же самое число). Как видно, объект А, который является «5» в отношении 5:1, в пять раз больше чем объект В. Если вы переворачиваете дробь 5/1, вы получаете 1/5, котораз является размером объекта В по сравнению с размером объекта А. Рис. 7.7. Человек и его копия, масштабированная в отношении 1/5 Пропорция может быть представлена как отношение или дробь. Вы можете также использовать доли - проценты, поделенные на 100. При использовании программы
BlitzPIus вы обычно применяете доли. Другими словами, котда вы хотите масштабировать размер объекта, вы умножаете его па число долей. Например, вы хотите сделать нечто в четыре раза больше, чем оно есть на самом деле. Просто умножьте каждую координату на 4. Обратившись к рис. 7.8, вы можете видеть блок с координатами 0,0,0,5, 5,0, и 5,5. При умножении каждой координаты па 4, так что 0,0 остается 0,0; 0,5 становится 0,20; 5,0 становится 20,0; и 5,5 становится 20,20, блок становится в четыре раза больше. А если вы хотите сделать объект приблизительно равным 5/8 от большего объекта? Все, что вы должны сделать, - взять калькулятор и разделить 5 на 8. Поскольку 5/8 = 0,625, коэффициент умножения будет 0,625. Таким образом, теперь у пас есть основная схема для нашего уравнения масштабирования. Уравнение масштабирования очень походит на уравнение сдвига: х[1] - л * я-; УН] « У • зу Обратите внимание на различия между сдвигом и масштабированием. При сдвиге вы прибавляете переменную d к текущей величине х: а при масштабировании вы умножаете переметшую » па текущую величин)' х. При масштабировании объекта вы должны использовать доли, а не проценты. Если вы хотите сделать один объект размером 50% от предыдущего, не умножайте на 50. Ваш новый объект будет в 50 раз больше старого! Вместо этого думайте об этом, как о дроби. Вы хотите сделать новый объект, равным 1/2 от предыдущего. Если вы разделите 1 на 2. то получите 0,5. Умножьте объект на 0,5, и ваш объект уменьшится на 50%. Масштабирование форм Масшт абирование форм относительно простое дело. Лишь умножьте крайнюю координату на масштабирующий множитель, и готово! Следующий шаг - масштабирование различных видов форм, таких как прямоугольники и треугольники. Готовы начать?
Программирование игр 193 Масштабирование прямоугольников Давайте напишем несколько программ, использующих масштабирование форм Первая иршрамма рисует прямоугольник, ждет, пока пользователь выберет масштаб, и выводит новый прямоугольник с новым размерим. На компакт-диске этот файл назван demo07-03=bb- Мы начинаем с вызова 1рафического режима. После этого инициализируем переменные: ;J.smo - Г..- т г:.,-гт".': - вание Graphics бис ,.1 , .-ПЕРЕМЕННЫЕ ;Создаем переменные, описывающие прямоугольник re:tbeginx = 25 ;Координата х верхнего левого угла г, ctbeginy = i’i Координата у верхнего левого угла rtfCtwidth 13С .Относительная координат3 х нижней правой точки tectheight = _iF Относительная координата у нижней правой точки Все, что происходит, - создание нескольких переменных. Я сдвинул блок от точки 0,0 так, чтобы вы могли видеть масштабирование более ясно, be in вы сочтете нужным, поменяйте любую из этих переменных. Затем переходим к главному разделу кода. Первая часть имеет дело с первым прямоугольником. .-Г.1АВНЫЙ РАЗДЕЛ ; 5еждаем'~ч, что тек'-” вывод.- * *ц<1НсЯ части экрана Text I .7 , •' • : , : ,icy« । первый прямо. . - . [ненным .-Показывай j ый прч» ьник Мы начинаем с вызова функции Text. Это вызывает появление текста «This is our firs*: iec*Lang±e» в шькней части экрана, поэтому текст не пересекается с прямоугольниками. Затем мы вызываем функцию Rent. Эта функция, определенная в компиляторе, рисует прямоугольник от начальных координат (г«- tbeginx и rectbeqiny] к конечным координатам (r₽ctwidth и г«*с»-.Ъо . чЫ). Смотрите параметры функции Rect в табл. 7.2. Значение параметра 0 в кон tie оставляет прямоугольник незаполненным. Вы можете установить параметр равным i, если хотите заполнить прямоугольник, но если вы сделаете так, этобгдет выглядеть довольно безобразно. Так почему я вставил команд}- £ 1именно в этом месте? Помните, в программе BlitzPIus все выводится в буфер памяти BackBuf fei |). Поскольку- мы хотим увидеть только что нарисованный прямоугольник, мы должны использовать команду Flip, чтобы показать прямоугольник на экране.
Табл. 7.2. Параметры функции Rect Параметр Описание X Координата х левой вершины прямоугольника У Координата у правой вершины прямоугольника width Ширина прямоугольника в пикселах T-ight Высота прямоугольника в пикселах solid Если установлен равным 0, прямоугольник не заполнен; если установлен равным 1, прямоугольник заполнен Затем следует определение масштабного коэффициента. ;Запрашиваем у пользователя значение масштабного коэффициента sxy# = Input ("What would you like the scaling factor to be? (Ex: 50$ =.5>? -> *} Этот оператор спрашивает пользователей на какую величину они хотели бы масштабировать координаты х и у. В этой программе и х, и у масштабируются одной и той же величиной. Если нужно, перепишите эту программу так, чтобы пользователи могли масштабировать х и у различными величинами. При использовании функции Input (} появится окно, запрашивающее информацию. Оно запрашивает информацию не прямо на экране, а во внешнем окне. Из-за этого при использовании функции Input () вы не должны использовать развернутое окно. Вместо этого используйте маленькое окно, не превышающее размер рабочего стола. Мы не затрагивали типы переменных в течение длительного времени. В том случае, если вы забыли, напомним: если знак # добавлен в конец имени переменной, переменная имеет тип числа с плавающей точкой. Если переменная имеет тип числа с плавающей точкой, переменная может содержать десятичные разряды после десятичной точки. Другими словами, хух может быть 314, тогда как хух# может быть 314,13. Если вы попробуете присвоить переменной, отличной от числа с плавающей точкой (например, целого типа), десятичное число, то десятичная часть будет усечена. Например, число 1,9 станет 1, потому что часть 0.9 была усечена или удалена. Будьте внимательны при выполнении таких действий, потому что, если вы усекаете десятичные числа, вы теряете информацию. И если вы, тем не менее, намереваетесь присвоить десятичное число, это может быть действительно плохой затеей. Например, если бы вы вычисляли налог с величиной ставки 0,0В и избавились бы от десятичной части, вы пришли бы к нелоговой стевке 0! В этой программе пользователь может умножить переменную на любое число, вроде 1,5, 0,3 ит. д. Нет ничего хорошего втом, если десятичное число будет усечено, потому что новая переменная, вероятнее всего, выйдет со значением 1 или 0. Какой скучной была бы программа, если новый прямоугольник удалялся или сохранялся с тем же самым размером!
Коэффициент масштабирования сохранен в переменной sxy#. Эта переменная используется в следующем разделе кода. ;Умножаем ширину и высоту на коэффициент масштабирования rectwidth = rectwidth * sxy# rectheight = xectheight * sxy# ; Показываем последний ввод информации пользователем Flip Чтобы масштабировать новый объект, вы должны умножить каждую координату на коэффициент масштабирования, который был определен с помощью пользовательского ввода в предыдущем разделе кода. Здесь оба значения х и у умножены на коэффициент масштабирования, чтобы прямоугольник стал настолько большим, насколько кочет пользователь. Конечный раздел кода рисует второй прямоугольник и осуществляет выход из программы. ; Рисуем новый прямоугольник Rect rectbeginx, rectbeginy, rectwidth, rectheight, 0 Prine "Press any key to exit." .Ожидаем от пользователя нажатия клавиши перед выходом WaitKey Заметьте, что функция WaitKey будет работать только тогда, когда активна текущая программа, рисующая прямоугольник, но не тогда, когда активно окно ввода. Первая строка здесь рисует новый прямоугольник с преобразованными координатами Так как начальные значения координат х и у остаются теми же, прямоугольник рисуется поверд старого. Две конечные строки запрашивают у пользователя нажатие любой клавший. Как только пользователь нажмет клавиш)-, программа завершится. Рис. 7.9 показывает кадр окна программы. Эта программа обучает многим полезным понятиям. Попробуйте следующее: измените код для работы с эллипсами, используя функцию Oval. Вы должны были заметить нечто странное при использовании масштабирующего множите |я. При изменении в 2 раза, как на рис. 7.10, первоначальный прямоугольник имеет размер в 1/4 размера нового прямоугольника, хотя вы могли ожидать размер в 1/2. Причина в том, что каждая координата изменена в 2 раза, а не прямоугольник целиком. Поэтому новый прямоугольник в действительности в четыре раза больше размера исходного прямоугольника.
Рис. 7.9. Программа demo07-03.bb Рис. 7.10. Программа demo07-03.bb с масштабным коэффициентом 2
Масштабирование треугольников Мы можем теперь перейти к чему-то чуть более сложному — масштабированию треугольника. В отличие от прямоугольника, для треугольников нет функции. Мы должны рисовать каждую линию вручную. Прежде чем мы сможем начать обсуждение, как масштабировать треугольник, мы должны понять различие между локальными и глобальными координатами. Есть огромное различие между глобальными и локальными координатами. Локальная координата очень похожа на локальную переменную, является видимой только из объекта, который рисуется. Глобальные координаты, с другой стороны, являются одинаковыми для всех объектов. Возможно, аналогия поможет понять различие. Возьмите человека; например, давайте возьмем вас. Вы - человек. Существует много людей. Но вы - центр всего, что вы можете видеть. Для вас все вращается вокру; вас. Следовательно, ваши локальные координаты проходят от вашей вершины к основанию. Однако помните, что эти понятия справедливы также для каждого другого человека. Каждая личность имеет свои собственные шкальные координаты. Теперь вообразите космический корабль, наблюдающий Землю с неба. Для пришельцев люди находятся всюду Каждая личность не является центральной для космического корабля; наоборот, Земля выглядит как целое. Таким образом, для пришельцев Земля - координатная плоскость (это, на самом деле, не плоскость, но не берите это в голову). Где бы вы ни были размещены в некоторой координатной позиции (возможно, 18.14), это изменится, если вы сделаете шаг в другую область. Широта и долгота выполняют те же самые действия, что и глобальные координаты, - вы можете указывать точно некоторое положение где-либо в мире, указывая координаты долготы и широты. Помотрите на рис. 7.11, который показывает карту- мира с двумя людьми, человеком А и человеком В. Человек А и человек В, каждый в отдельности полагает, что он - центр мира; т. е. каждый думает о себе, как о размещенном в точке 0,0. Однако космический корабль, который наблюдает людей (вы теперь- космический корабль), видит их в двух совершенно различных координатных позициях, указанных значениями широты и долготы. Рис. 7.11. Космический корабль и мир
Когда вы передвигаетесь, ваши глобальные координаты изменяются. Однако ваши локальные координаты остаются теми же. Ваша точка зрения не меняется, и, следовательно, ваши локальные координаты остаются вместе с вами неизменными независимо от того, куда вы идете. С объектами в программе BlitzPIus эта аналогия работает чрезвычайно хорошо. Для треугольника, который мы используем в следующей программе, начало отсчета находится в точке 0,0. Глобальные координаты объекта существуют, где бы он ни отобразился ва экране. Обратившись к рис. 7.12, вы можете видеть, что локальные координаты объекта начинаются в верхнем левом углу и заканчиваются в нижнем правом углу. Рис. 7.12. Глобальные и локальные координаты Теперь, когда мы понимаем, что такое локальные и глобальные координаты, вникнем в эту программу. Сначала устанавливаем графический режим Graphics 800,600,0,2 Теперь мы собираемся создать тип «точка» с именем point. Тип point будет содержать два поля: собственные координаты х и у. Type point Field х,у End Type Мы должны иметь три точки для этого треугольника: по одной для каждой вершины. Вершина-это точка, где линия меняет направление - в случае треугольника имеются три вершины, одна у каждого угла. pointl.point = New point point2.point = New point point3.point = New point Точки pointl, point2 и point3 - три различные вершины треугольника. На рис. 7.13 вы можете видеть, что начальная точка pointl находится в верхней точке или вершине треугольника. а точки points и point! Рис. 7.13. Точки pointl, point2 и point!
следуют в направлении по часовой стрелке. Линия, которая начинается в точке pointl, простирается до точки point?, линия из точки point? простирается до точки point 3, и линия из точки point3 простирается до точки pointl. Затем, мы должны определить локальные координаты для нашего первого треугольника. В программе demo07-04.bb вершины определены следующим образом: ;Эти переменные определяют каждую вершину и выражены в локальных координатах pointl\х = О point1\у = -100 point?\х = 100 point?\у = 100 point3\x = -100 point3\y = 100 Эти точки сосредоточены вокруг точки 0,0. Заметьте, что все эти координаты локальные: очевидно, у вас никогда не будет отрицательных значений для глобальных координат. Рис. 7.14 показывает координаты каждой точки в треугольнике. Как видно, точка начала координат, 0,0, находится точно в центре треугольника. Рис. 7.14. Локальные координаты Как отмечено ранее, чтобы получить глобальные координаты, мы прибавим постоянное значение к каждой лока.1ьной координате треугольника. Раздел констант этой программы содержит две переменные. .КОНСТАНТЫ «Глобальные указатели, которые прибавляются к каждой локальной .координате для помещения ее на экране Const xs = 400 Const ys = 300 Я выбрал эти два числа, потому что они центрируют треугольник на экране. Заметьте, что программа имеет разрешение 800 на 600 пикселов (эти числа определены в инициализации графического режима), а 800/2=400, и 600/2=300. Чтобы добиться правильных глобальных координат, переменная x s прибавляется к каждой координате х, а переменная ys прибавляется к каждой координате у. Теперь, когда все наши начальные значения определены и переменные созданы, перейдем к программе. Программа начинается с этих двух строк: Locate 0,700 Print "This is our first triangle."
Как вы, возможно, знаете, команда Locate размещает все команды Print в нижней части экрана. Затем оператор Print выводит на экране надпись «This is our first triangle.» Затем мы рисуем первый треугольник Это выполняется с помощью функции Line, которая чертит линию от одной координатной позиции до другой. Функция Line объявлена таким образом: Line x,y,xl,yl Табл. 7.3 объясняет каждый параметр в отдельности. Табл. 7.3. Параметры функции Line Параметр Описание X Начальная координата х У Начальная координата у х! Конечная координата х У1 Конечная координата у В сущности, функция Line рисует прямую линию от координат х, у к координатам xl, у 1 В этой программе есть три вызова функции Line для каждого треугольника. ;Рисуем первый тр .угсльник Line pointl\x+xs, pain*'Лу+ys , . nt2 'х-гк , point2‘ f+ys Line point2\x+xs, poincixyrys, pc int' л+xs, point3'y+yo Line poincj\x+xs, point 3 \y+', pointl x+xs, pointl'y+ys Как видно, при каждом вызове функция Line чертит линию от одной вершины к другой. При внимательном рассмотрении можно видеть, что величина xs добавлена к каждой координате х и величина ys добавлена к каждой координате у. Эти числа прибавлены к локальным координатам треугольников для передвижения их на экране так, чтобы их было видно в программе. Рис. 7.15 демонстрирует локальные и глобальные координаты треугольника. Теперь, когда мы нарисовали исходный треугольник, давайте выясним, какой масштабный коэффициент желает иметь пользователь. Это выполняется с помощью вызова функции Input Flip ;Получаем масштабный коэффициент пользователя sxy# = Input ("What y?u like th* * лI i t j fac • c co be? (Exs50% - .5)? => " Flip Пользователь теперь имеет возможность ввести масштабный коэффициент, который сохраняется в переменной sxy#. Заметьте, что эта переменная имеет тип числа с плавающей точкой, чгр обозначено символом #.
Вообще, при использовании функции Inj it') вам нужно поместить команду Flip до и после вызова функции 1г. Рис. 7.15- Локальные и глобальные координаты рассматриваемого треугольника После того, как пользователь выбрал масштабный коэффициент, мы масштабируем каждую точку. Следующие строки выполняют действия масштабирования. ,-Умножаем все KCOj •• - point 1Ax “p in-! х • кху* pointl\y pclntl'-y * аку* point, v points . ' - points ,'z - у * «ху» point "i •> = p - • - point> у = print' ' ! * ux, t Довольно просто, да? Весь этот блок кода умножил позиции х и у каждой вершины на величину г.ху#- Хорошо, теперь мы можем нарисовать новый треугольник. Так как мы хотим, чтобы новый объект был хорошо виден, мы должны изменить цвет линий. Это легко выполняется с использованием функции Со.or ;Меняем _гандс-ртм1/' и?'317’ я? четеи| Color С ,254,-: Это делает все последующие резучьтаты вызова команд I 1 пе зеленого цвета. Теперь все, что мы должны сделать, - нарисовать новый треугольник. Это выполняется вызовом функции Line для каждой точки, как мы делали для исходного треугольника.
;Рисуем окончательный треугольник (с отмасштабированными координатами) зеленым цветом Line pointl\x * xs,pointl\y + ys,point2\x + xs,point2\y «• ys Line point2\x + xs,point2\y т ys,point3\x + xs,point3\y « ys Line point3\x + xs,point3\y + ys,pointl\x + xs,pointl\y * ys Превосходно! Программа нарисовала линии, соединяющие каждую вершину, и. следовательно, изобразила новый треугольник. Теперь все, что мы делаем, - завершаем программу. Print "Press any key to exit." {Ожидаем от пользователя нажатия клавиши перед выходом WaitKey Эти строки программы предлагают пользователю нажать любую клавишу', и затем программа вдет нажатия клавиши пользователем перед выходом. Это - завершенная программа. Рис. 7.16 и 7.17 демонстрируют программу с масштабными коэффициентами 2 и 0,5. Вы не сможете увидеть, что новые линии нарисованы зеленым цветом на рисунках, но вы можете рассмотреть программу на компакт-диске, чтобы увидеть новые треугольники, нарисованные зеленым цветом. Если заметили, новый треугольник центрирован относительно первоначального треугольника. Но если вы не хотите сохранять центрирование? Все, что вы должны сделать, - изменить локальные координаты. Рис. 7.16. Программа demo07-04.bb с масштабным коэффициентом 2
Рис. 7.17. Программа demo07-04.bb с масштабным коэффициентом 0,5 Хотите увидеть нечто крутое? Когда у вас запрашивают значение sxy#, введите отрицательное число. Отобразится новый треугольник. Траугольнику соответствует рис. 7.18; на нем показана работа программы со значением sxy#, равным -1 Рис. 7.19 показывает программу demoO7-O5.bb. Как видно, треугольник увеличивается но направлению вниз, но не остается отцентрированным. Программа demo07-O5.bb почти точно такая же, как demoO7-O4.bb, за исключением измененных начальных переменных. Переменные теперь инициализированы с различными значениями. БЕРЕМЕННЫЕ ;Эти переменные определяют каждую вершину и выражены в локальных координатах point1\х = О pointl\y = О point2\x = 100 point2\y = 100 points\х = -100 point3\y = 100
Рис- 7.18. Программа demo07-04.bb с масштабным коэффициентом -1 Рис. 7.19. Программа demo07-05.bb
Большое различие здесь в том, что переменная point1\у была изменена на 0 в отличие от своего значения в demo07-04.bb. Поскольку любое число, умноженное на 0, равняется 0, когда величина sxy# умножается в строке: jsintl\х pointl(л * окуй величина point1\к вешда будет раяна 0 Поскольку переменная не изменяется, точка point 1 останется в той же самой позиции в течение всего времени работы программы. Таким образом, треугольник будет расти сверху вниз. Выходит довольно мило, вы нс- находите? Кстати, если вы хотите, чтобы треугольник рос вверх, измените у нижних точек значения на 0. Программа demo07-06.bb показывает треугольник, растущий вверх. Переменные быти немного изменены; теперь они выглядят так: ;ПЕРЕМЕННЫЕ ;Эти переменные определяют каждую вершину и выражены в локальных координатах pointl\x = Э point1- -10U point2\x = L00 point2\у = 0 point3\x = -’*?? paint3\y - Как вы можете видеть, две нижние гочкн расположены на нулевом уровне Теперь, ко|да треугольник масштабируется на 2, то растет вверх, как на рис. 7.20. Рис. 7.20. Программа demo07-06.bb
Одна вещь, которую вы должны знать о предыдущей программе: поскольку изменение значений координаты у для двух нижних точек перемещает рисунок немного вверх, я немного изменил переменную ys. Раздел констант теперь выглядит так: ;КОНСТАНТЫ ;Глобальные указатели, которые прибавляются к каждой локальной ;координате для помещения ее на экране Const xs = 400 Const ys = 400 Значение переменной ys в программе demo07-06.bb было изменено с 300 на 400, чтобы компенсировать различие в 100 пикселов между исходным треугольником в программе demo07-04.bb и новым треугольником в программе demo07-06.bb. Теперь давайте создадим функцию масштабирования. Function Scale(x,sx) Return x * sx End Function Табл. 7.4 рассматривает каждый параметр. Если вы хотите масштабировать координату х точки point 1, вызовите функцию таким образом: Scale (point1\х,sxy#) предполагая, что sxy# является масштабным коэффициентом. Табл. 7.4. Параметры функции Scale!) Параметр Описание X Значение, которое вы хотите масштабировать S Масштабирующий множитель Масштабирование изображений Программа BlitzPIus делает масштабирование изображений предельно простым, предоставляя функцию Scalelmage. Функция Scaleimage определяется таким образом: Scalelmage image,xscale#,уscale# Табл. 7.5. Параметры функции Scalelmage Параметр Описание image Имя изображения, которое вы хотите масштабировать xscale# Величина, на которую вы хотите масштабировагь ось х, (1,0=100%) yocale# Величина, на которую вы хотите масштабировать ось у, (1,0=1004)
В качестве примера масштабируем изображение косми- |К.Я| ческого корабля. Вначале космический корабль выгля- '*71 дит, как на рис. 7.21. рис 7 21 Программа demo07-07.bb довольно коротка, поэтому я Исходное изображение собираюсь показать ее полностью и объяснить по окон- космического корабля чании. ;demo07-07.bb - Демонстрирует использование функции Scalelmage Graphics 800,600,0,2 ;Устанавливаем значение параметра autontidhandie равным "истинно® AutoMidHandle True ;ИЗОБРАЖЕНИЯ ;Загружаем изображение космического корабля, который будет нарисован на экране spaceshipimage - Loadlmage("spaceship.bmp") ;Выводим космический корабль точно в центре экрана Drawlmage spaceshipimage,400,300 Flip ;Выясняем, какие масштабные коэффициенты хочет применить пользователь для координат х и у xs# = Input("What would you Like the x scaling value to be? ' ys# = Input("What would you like the у scaling value to be? *1 Flip ;Готовим экран для отмасштабированного космического корабля, очищая его Cis ;Масштабируем изображение Scalelmage spaceshipimage,xs #,ys # ;Выводим новый отмасштабированный космический корабль Drawlinage spaceshipimage ,400,300 Print "This is your updated image" Print "Press any key to exit" ;Ждем от пользователя нажатия клавиши перед выходом WaitKey
Рис. 7.22 показывает окно программы. Первое, что делает программа, - инициализирует графический режим и устанавливает для переменной AutoMidHandle значение «истинно», поэтому изображении центрируются. Затем программа загружает изображение космического корабля н выводит изображение на экран. Используя функцию Input, программа узнает масштабирующие коэффициенты. Затем программа очищает экран при подготовке нового изображения. ;Масштабируем изображение ScaleImage spaceshipimage,xs#,уs# ;Выводим новый отмасштабированный космический корабль Drawimage spaceshipimage,400,100 Только что отмасштабированный космический корабль выводится в центре экрана после масштабирования с помощью функции Scalelmage, которая использует масштабные коэффициенты, предоставленные ранее пользователем. Эти две строки масштабируют и выводя! новое изображение. Программа заканчивает свою работу предложением нажать любую клавишу. После нажатия клавиши программа завершается. Заметьте, если вы задаете размер изображения больше 100 процентов, оно выглядит расплывчато. Причина в том, что функция масштабирования растягивает изображение и делает квддый пиксел немного больше.
Можно также использовать функцию Sщ.® для многоугольников типа треугольников. Нужно лишь только сделать несколько вызовов функций Croat elir»ge i j и ImageBuf fer () Перепишем программу demo07-04.bb, используя функцию Scalelmsцv. Очевидно, сначала мы инициализируем графический режим. Затем установим для nUtoMidHandle значение «истинно», так что изображения в программе будут отцентрированы. Далее мы можем инициализировать программные переменные, такие как начальные координаты и тип «точка». Теперь чуть изменим программу. Мы должны вызвать функцию Си. г-. . для получения экземпляра графического изображения для масштабирования. Этот вызов сделает следующую хитросты image - CriAiceIma-|3t .ntZ-х ; шел . с) , int / - . 1\у)) Посколькумы знаем, что точка point J является крайней вершиной слева, точка i >int2 является крайней внизу и справа и точка point 1 - верхняя, мы вычитаем наибольшее и наименьшее значения для получения ширины и высоты нашего изображения. Ifenepb нам нужно вызвать команду Set Bu f дня установки активного буфера для указателя image, тогда можно будет рисовать прямо в нем. SetBuffer ImageBuf 1 »r (iina' Теперь продолжаем с помощью команд L *. Затем мы должны вернуться к функции FrontBuf fer (), вызвав команду SetBuf£ег снова. SetBuffer FrontBuffer(image) Затем используем функцию Drawlmac• j для отображения .первоначального треугольника. Drawlmage image,400,300 Используя команду Input, получим масштабный коэффициент и вызовем функцию Scalelmage следующим образом: Sealelmage image,sxyft,sxyft Затем вызовем Drawimage image,4QS,130 И программа сделана! Рис. 7.23 показывает работу программы demo07-08.bb. Ниже приведен полный исходный текст программы. Ио окончании произведем обзор. ;demo07-08,ЬЬ Демонстриохгет 1 гштабирование ' помощью функции Scalelmage Graphics 800,600,0,? {Убедитесь, что значение параметра {AutoMidHandle равно "истинно" AutoMidHandle True {СТРУКТУРЫ
Рис. 7.23. Программа demo07-O8.bb ;Структура point описывает одну точку в системе координат Type point Field х,у End Type ;Создаем три вершины pointl.point - New point point2.point - New point point3.point = New point ;ПЕРЕМЕННЫЕ ;Эти переменные даны в локальных координатах и описывают ;положения вершин pointl\х = 100 point1\у = 0 point2\x = 200 point2\y = 200 point3\x - 0 point3\y == 200
.•Создаем буфер с надлежащими высотой и шириной image = Createlmage((point2\x - point3\x) + l,(point2\y - pointl\y) + 1) ;ГЛАВНЫЙ РАЗДЕЛ Print "This is our first triangle." ;Устанавливаем буфер по умолчанию для созданного нами изображения, чтобы мы могли рисовать треугольник прямо в нем SetBuffer ImageBuffer(image) ;Рисуем треугольник в новом буфере Line ро int1 \х,point1\у,point2\х,point 2\у Line point2\х,point2\у,point3\х,point 3\у Line pointj\х,point3\у,pointl\х,pointl\у SetBuffer BackBuffer{) ;Выводим изображение в центре экрана Drawlmage image,400,300 Flip ; Получаем масштабный коэффициент sxy# = Input ("What would you Like the scaling factor to be? (Ex: 50% = .5) ? => ") ;Отображаем масштабный коэффициент Flip ;Масштабируем изображение, используя этот масштабный коэффициент Scalelmage image,sxy#,sxy# ;Выводим новое изображение Drawlmage image,400,300 Print "Press any key to exit." ; Ожидаем нажатия клавиши перед выходом WaitKey Хочу отметить несколько моментов в этой программе. Во-первых, я убрал константы xs и у s. Поскольку треугольник рисуется в буфере изображений, а изображение выводится в центре, то нет нужды преобразовывать локальные координаты в глобальные.
Во-вторых, заметьте, что я прибавил 100 к каждой переменной point\х и point\у Это необходимо для удаления отрицательных координат. Отрицательные координаты были бы нарисованы вне буфера, а нам нужно все поместить в нем, поэтому мы просто избавляемся от отрицательных значений Втретьих, заметьте, что я прибавил 1 к размеру буфера при вызове функции Createlmage. Эта прибавка была сделана, чтобы вывести в буфер все изображение. Обычно имеет смысл дать маленький припуск (здесь 1 пиксел) для уверенности, что все изображение появится полностью. Наконец, вы заметите, что отмасштабированное растровое изображение расплывчато. Это случается при масштабировании растровых изображений, поскольку они имеют лишь ограниченное количество информации, и, когда вы пытаетесь растянуть изображение, компьютер должен пополнять информацию для заполнения пустот, чтобы сделать изображение большим. Компьютер делает это путем усреднения пикселов в растровом изображении и последующим расчетом того, что должно быть между ними. Результатом этого заполнения являются нечеткие изображения, потому что компьютер должен догадываться, как должно выглядеть новое большое изображение. Хорошо, уже довольно много сказано о масштабировании. Теперь можно перейти к действительно крутому предмету: вращению. Вращение Пока вы научились двум из трех типов преобразований. Вращение - заключительный тип, который вы изучите. Вращение обычно является чрезвычайно тяжелым в реализации, но программа BlitzPIus значительно его обличает. Как и для масштабирования, программа BlitzPIus предоставляет функцию для вращения - Rotatelmage. Функция Rotatelmage определяется так: P'-’tatelmage image, value# Табл. 7.6 рассматривает отдельные параметры. Как можно видеть, параметр вращает данное изображение по часовой стрелке. Табл. 7.6. Параметры функции Rotatelmage Параметр Описание Image Имя изображения, которое вы хотите вращать value# Сумма градусов между 0 и 360, на величину которой вы хотите вращать изображение по часовой стрелке Взгляните на рис. 7.24 и 7.25. Они демонстрируют направления по часовой стрелке и против часовой стрелки соответственно. О да, часы вращаются по часовой стрелке.
Рис. 7.25. Против часовой стрелки Рис. 7.24. По часовой стрелке Значение может быть равным любому числу между 0 и 360. В круге содержится 360 градусов. Обратитесь к рис. 7.26. чтобы увидеть градусы в круге. Как вы видите, вращение изображения является довольно простым делом. Вращение фигур является чрезвычайно трудным и требует сложной математики, поэтому в большинстве случаев хорошей идеей является использовать функции CreateImage! | и iitiageBul |,как это сделано в нро1раммс demo07-08.bb. Использование функции "reatelmage () позволяет вам превратить вашу фигуру в изображение, которое упрощает выпо.т нение алгоритмов вращения Рис. 7.26. Градусы в круге Давайте напишем программу, которая вращает изображение. Эта про1рамма загружает изображение с жесткого диска, спрашивает у пользователей, на сколько градусов они хо тят повернуть изображение, и совершает действие. Ниже приведен раздел, осуществляющий вращение в коде программы demo07-0S.bb. ; Выводим исходное изображение Dtawlmage shipimage/.99 ,?99 Flip ; ведением значение угла вращения ir:rationvalue# = Input । “How many ijojJC ..ike *"o rotate the image? Flip
;Вращаем изображение Rotatelmage shipimage, rotationvalue# Print «Your new image is now drawn on the screen» ;Выводим новое и повернутое изображение на экран Drawlmage shipimage,440,300 Этот фрагмент выводит изображение shipimage, которое было загружено ранее в программе, в центре экрана. Затем программа узнает у пользователя значение rotationvalue# и вращает изображение, используя команду Rotatelmage shipimage,rotationvalue# Эта строка вызывает вращение изображения shipimage на количество градусов, введенное в переменную rotationvalue#. Затем программа выводит новое изображение справа от исходного. Такова программа demo07-09.bb. Рис. 7.27 показывает кадр из программы. Рис. 7.27. Программа demo07-09.bb Запрос у пользователя значения утла вращения - это хорошо, но как насчет вращения в реальном времени? Вращение в реальном времени позволяет вам поворачивать изображение в любой момент. Этот эффект использован в играх типа Asteroid, где космический корабль вращается на экране.
Думаете, исполнение в режиме реального времени такое же простое дело, как ожидание пользовательского нажатия кнопки и вызов функции Rotatelmage? Отнюдь. Если вы сделаете это, ваша программа будет выполняться чрезвычайно медленно. Вам нужно предварительно загрузить ваши изображения, чтобы позволить программе выполняться на полной скорости. Прежде чем мы начнем заниматься предварительной загрузкой, посмотрим на что будет похожа программа вращения без нее. Ниже приведен гчавныйцикл программы demo07-10.bb. Прочитайте это и постарайтесь понять. Далее следует объяснение ;ГЛАВНЫЙ ЦИКЛ While Not KeyDown(l) ; Очищаем экран Cis ;1»бавляем текст Text 10,0, "Press Left to rotate counter-clockwise and right to rotate clockwise." Text 10,20,"Press Esc to exit." ;Если пользователь нажал клавишу "влево", вращаем на четыре градуса влево, если он нажал клавишу "вправо", вращаем на четыре градуса вправо If KeyDown (203) Rotatelmage shipimage, -4 Elself KeyDown (205) Rotatelmage shipimage,4 Endlf ; Выводим корабль Drawlmage shipimage,400,300 Flip wend .КОНЕЦ ГЛАВНОГО ЦИКЛА Как видно по командам Flip и Cis, эта программа использует постраничное отображение. Оставшаяся часть программы довольно очевидна. Если пользователь нажимает стрелку влево (код символа 203), корабль поворачивается против часовой стрелки на четыре градуса. Если пользователь нажимает стрелку вправо (код символа 205), корабль поворачивается но часовой стрелке на четыре градуса. Выдумаете, эта программа будет работать правильно? К сожалению, нет. Так как вращение забирает уйму производительности процессора, программа работает медленно. Чтобы урегулировать эту проблему, мы используем технику', называемую предварительной загрузкой.
Предварительная загрузка представляет некоторую сложность для понимания, но позвольте мне вас провести через эти сложности. Первое, что вам нужно сделать, - решить. сколько кадров вам надо. С большим количеством кадров вы получаете чуть лучшую анимацию. Чем большее количество поворотов имеет изображение, тем меньше различие в градусах между отдельными кадрами. Однако с большим количеством кадров ваша программа занимает больше места в памяти. Программа так же может работать немного медление^. Я обычно выбираю количество кадров 16 - это обеспечивает приличные результаты, но не занимает так много места в памяти, как большее количество кадров. Рис. 7.28 показывает каждое из 16 положении корабля. Что такое предварительная загрузка? Предварительная загрузка является сложным для понимания понятием для начинающих программистов. Главная идея, лежащая в основе предварительной загрузки, состоит в том, что отображение в программе изображения, которое некогда было изменено и сохранено, является более быстрым, чем изменение изображения каждым раз, когда изменение необходимо. Предварительная загрузка делает следующее: в начале программы вы создаете изображения, которые вам понадобятся в программе позже, и сохраняете их в массиве, который может быть вызван в любое время. Когда вам нужно будет позже показать сохраненные изображения, вы лишь выведите необходимое изображение из массива, созданного вами ранее. Думайте об этом, как о выполнении вашего домашнего задания. Процесс будет значительно облегчен, если вы вытащите все ваши школьные принадлежности сразу, как только начнете, доставать книгу или карандаш только тогда, когда они нужны вам. Ваша домашняя работа будег сделана в любом случае. Но работа может быть выполнена быстрее, если вы «предварительно загрузили» все ваши принадлежности. Конечно, эта аналогия работает только, если вы действительно делаете вашу домашнюю работу. (Более подробно читайте, как избежать неприятностей с невыполнением ваших домашних работ, в моей другой книге «Как преуспеть в качестве ленивого студента». См. в Интернете сайт www.maneeshsethi.com.) В приведенной ниже программе вращения я поворачиваю изображение столько раз, сколько хочу иметь кадров. Когда пользователь нажимает клавишу влево или вправо, отображается следующий кадр Между прочим, когда я использую кадр в этом разделе, я обращаюсь к кадрам изображения, а не к итерациям главного цикла. В любом случае, примемся за предварительную загрузку. Первое, что мы сделаем, -создадим константу для хранения суммы положений (кадров), которая нам нужна для нашего изображения. Это может выглядеть приблизительно так. Const rotations = Lfe Далее мы создаем массив, содержащий все наши кадры. Dim imagearrai i rotations)
Как можно видеть, этот массив будет хранить 16 изображений: по одному для каждого вращения. Массив фактически хранит 17 элементов, мы даем ему один дополнительный элемент для буфера. Не забудьте, что каждый массив начинается с 0. поэтому в 16 кадрак будут вращения от 0 до 15. Теперь идет довольно сложная часть. Мы должны загрузи »ь кадры в массив. Это может быть достигнуто с помощью цикла For...Next. For frame = 0 То rotations - Ич % Ч * * * * * * fc. * * » » * Рис. 7.28. 16 положений корабля Imagearray(frame> = CopyImage (shipimage) Rotateimage imagearray(frame),frame *360/rotat ions 1,'ext Что скажете? Пройдем в этом цикле строку за строкой. Этот отде льный цикл выполняется 16 раз. Цикл начинается с 0 и считает до rotations - 1. Здесь rotations - 1 равняется 15 (16 - 1 = 15). Теперь мы переходим к фактическому копированию. Каждый кадр массива имеет переменную shipimage, копию реального изображения. Это выполняется функцией Copy Image (). Следующая строка создает вращение для каждого кадра и выглядит так: Rotateimage imagearray(frame),frame " 360 f rotations Если помните, первый параметр функции Rotate Image поясняет, какой объект будет вращаться, второй параметр определяет, на сколько объект будет повернут. Здесь вращается изображение imagearray (frame). Этот кадр - один из icx, что были толью» что скопированы в массив. Величину поворота понять немного сложнее. Номер кадра умножается на сумму градусов в круге (360) и затем делится на общее число вращений. Табл. 7.7. Угол вращения в градусах для 16 поворотов по полному кругу Кадр Номер индекса Градусы вращения 1 0 0 2 1 22,5 3 2 45 4 3 67,5 5 4 90 6 5 112.5 7 6 135 8 7 157.5 9 8 180
Табл- 7.7. (окончание) Кадр Номер индекса Градусы вращения 10 9 202,5 11 10 225 12 11 247.5 13 12 270 14 13 292,5 15 14 315 16 15 337,5 Заметьте, что изображение никогда не поворачивается на 360 градусов. Вращение изображения на 360 градусов - это то же самое, что и вращение на 0 градусов, это поворот на полный круг. Поэтому заключительный кадр вращает изображение чуть меньше, чем на полные 360 градусов. Позвольте теперь показать полный исходный код для программы вращения. Мы рассмотрим в деталях главный цикл сразу после этого. ,• demoO?-11-ЪЬ - Демонстрирует предварительную загрузку и вращение в режиме реального времени Graphics 800,600 .-Настраиваем параметр AutoMidHandle и буфер BackBuffer () AutoMidHandle True SetBuffer BackBuffer(j .ИЗОБРАЖЕНИЯ ;Загружаем изображение космического корабля, который будет вращаться shipimage = Loadimage ("spaceship.bmp") ;КОНСТАНТЫ ;Сколько всего поворотов вы хотите? Const rotations = 16 ;Создаем массив вращений Dim imagearray(rotations)
«Для всех желаемых положений копируем изображение космического корабля и вращаем его на нужное число градусов For frame = 0 То rotations - 1 imagearray (frame) = CopyImage (shipimage) RotateImage imagearray(frame),frame * 360 / rotations Next ;Начнем с кадра 0 (корабль направлен вверх) frame = 0 {ГЛАВНЫЙ ЦИКЛ mile Not KeyDown(1) {Очищаем экран Cis {Добавляем текст Print "Press Left to rotate counter-clockwise and right to rotate clockwise." Print "Press Esc to exit " {Вращаем корабль влево, если пользователь нажал клавишу "влево" If KeyDown (203) {Уменьшаем номер кадра на 1 (таким образом, вращая его влево) frame = frame - ] ,-Если счетчик кадров меньше 0, присваиваем ему максимальное значение номера элемента массива If frame 0 frame ~ rotations - 1 Endlf {Вращаем корабль вправо, если пользователь нажал клавишу "вправо" Elself KeyDown (205) {Увеличиваем номер кадра на 1 (таким образом, вращая его вправо) frame = frame + 1 {Если номер кадра стал слишком большим, устанавливаем ему значение 0
If frame rot - EJ.dlf Endlf ; Выводим т».ку _ик Drawlmage ii arr Flip ;Ожидаем мгновение Delay 50 Wend Красота, не правда ли? Рис. 7.23 - это кадр из программы. Как обычно, начало программы устанавливает графический режим и инициализирует массив вращения. Затем программа печатает вводный текст для пользователя и устанавливает переменную frame в исходное значение 0, поэтомх корабль направлен вверх. Главный цикл проверяет работу двух клавиш - «стрелка влево» и «стрелка вправо». Если пользователь нажимает стрелку влево, количество кадров уменьшается на один, а если нажата стрелка вправо, количество кадров возрастает на один. Если значение переменной frame равно () и нажата клавиша влево, значение frame становится равным rotations-1, а если значение г га- з равняется максимальному числу вращений и нажата клавиша вправо, значение f ram*= становится равным 0. Рис. 7.29. Программа demo07-1 l.bb
Убедитесь сейчас, что вы поняли темы вращения и предварительной загрузки. Если вам все еще немного неудобно работать с этим материалом, пожалуйста, опять перечитайте раздел. Глава сейчас переходит к теме эффекта смешения фонов. Подумайте, с такими крутыми словами, как смешение фонов, как это может не быть забавным? Смешение фонов Смешение фонов - это очень интересная тема, и мы собираемся нырнуть прямо в нее. Используя смешение фонов, вы можете создать эффект движения через трехмерное пространство с фиксированной точки зрения. Вы можете думать о смешении фонов как о прокрутке, если хотите; в сущности, вы прокручиваете два или более фонов в одно и то же время для имитации движения Что такое смешение фонов? Помните, как вы в последний раз были в машине на автостраде? Когда вы смотрели в окно (полагаем, что вы не играли в видеогпру), вы могли замегить, что объекты, которые были ближе к вам, двигались быстрее, чем объекты, которые были дальше? Дорожные знаки, которые стоят вдоль дороги, проносятся мимо, в то время как деревья далеко в горах движутся намного медленнее. Смешение фонов создаёт этот эффект в играх: одна часть фона движется быстрее, чем другая часть, основываясь на расстоянии от точки зрения игрока. Прежде чем мы сможем реально начать создавать смешение фонов, нужно детально освоить две команды программы BlitzPIus: TileBlock и Tile Image. Команды TileBlock и Tilelmage Поскольку эффекты смешения фонов начинаются с понятия фона, мы должны прежде всего, научиться создавать фоны. Проще сказать, чем еде чать, правда? К счастью, программа BlitzPIus предоставляет две функции для управления фонами: TileBlock и! :lelmage. Вы, вероятно, должны знать, что такое размножение фонового элемента, или «выкладывание мозаикой», поскольку это делают обе функции TileBl<?k и Tilelmage. Размножение фонового элемента берет единичное изображение и покрывает фон вашей программы равномерными узорами. Точно как кафельные плитки: каждая плитка точно такая же, как и следующая. Обе функции TileBlock и Ti lelmage имеют одинаковые определения. TileBlock image,[х,у,frames] Tilelmage image, I x, у, frames ]
Табл. 7.8 описывает каждый параметр. Как можно видеть, единственный обязательный параметр - это image (изображение, которым вы хотите замостить фон). Параметры х и у перемещают начальную точку повторяющихся элементов в положение, отличное от точки 0,0 по умолчанию. Параметр frames используется при анимации, которая будет обсуждаться в следующей главе. Табл. 7.8. Параметры функций TileBlock и Tilelmage Параметр Описание image Имя изображения, которым вы хотите замостить фон [х] Необязательный параметр; начальная координата х для процедуры покрытия фона [у] Необязательный параметр; начальная координата у для процедуры покрытия фона (frames] Необязательный параметр; позволяет вам использовать кадры в анимации Есть небольшая разница между функциями TileBlock и Tilelmage. При использовании функции TileBlock вся прозрачность и маскировка на вашем изображении игнорируется. Следовательно, вы не можете нарисовать перекрывающиеся фоны, используя функцию Ti leBlock. Конечно, из-за игнорирования прозрачности функция TileBlock работает чуть быстрее. Главным образом, тем не менее, мы будем использовать функцию Tilelmage. Употребление функций TileBlock и Tilelmage действительно является легким делом. Вызывайте функцию, которую хотите использовать, с изображением, которым вы хотите замостить фон. Для нашей новой демонстрационной программы используем изображение на рис. 7.30. Следующая программа называется demo07-12.bb. В программе есть всего лишь четыре вызова - один инициализирует графический режим, один загружает фоновое изображение, один накладывает изображение, используя функцию TileBlock. Последним вызовом в программе является вызов функции WaitKey, чтобы пользователь мог видеть программу перед закрытием. Рис. 7.31 показывает пример кадра программы. Вызов функции TileBlock очень прост. Рис. 7.30- Элемент фона ;Замостим фон изображением TileBlock backgroundimage Как вы, вероятно, догадались, в переменную backgroundimage изображение было загружено предварительно.
Рис. 7.31. Программа demoO7-12.bb Теперь, когда вы размножили изображение, вам нужно понять, как прокручивать его вверх и вниз. Прокрутка вызывает появление движения в игре; поэтому будет казаться, будто вы действительно летаете в космосе. Нижеприведенная программа, demoO7-13.bb, расположена на компакт-диске. Программа начинается, как это обычно делается, с инициализации графического режима и всякой всячины. Инициализация также создает переменную scrolly, которая используется в команде Tile Image. Затем загружаем фон. такой же. как изображение на рис. 7.27. Теперь войдем в главный цикл. ; ГЛАВНЫЙ ЦИКЛ While Wot Key Down (1’ ;3амостим фон при значении координаты у как у параметра scrolly 1 eBlock backgroundimage,0,scrolly :Чуть прокрутим фон, увеличив параметр scrolly на 1 trolly = scrolly + 1 ,-Если значение параметра scrolly стало чересчур большим, сбрасываем его в ноль -< scrolly >- ImageHeight(backgroundimage) scrolly = О tosif Hip
Wend ;КОНЕИ ГЛЛЬч1 • । I - • Цикл начинается так, как вы, вероятно, и ожидаете. Первая строка внутри цикла - команда TiieBiot к. Эта строка помещает фоновое изображение, но она включает не обязательный параметр то 1 у для у. Поскольку переменная s. - - э 11у возрастает на 1 для каждого кадра в следующей строке кода, изображение располагается чуть-чуть выше в каждом кадре. Эю г эффект наложения создаст аффект прокрутки. Последняя важная строка в главном цикле - оператор If — обнуляет переменную scrony, когда программа прокрутила изображение полностью. Другими словами, если изображение backgrovndimeae имеет в высоту G4 пиксела, через каждые 64 кадра оно будет повторяться. На т от случай, если вы хотите знать, функция >пс возвращает высоту дан- ного изображения в пикселах. Заметьте, что в главном цикле нет команды С1«. Поскольку вы накладываете фон, очистка экрана бесполезна, так как команда lileBl - ck пишет поверх всего, что было до того. Если вы используете команду Ti . almage, которая сохраняет прозрачность изображения, вам нужно использовать команду Г1.?. Рис. 7.32 демонстрирует три кадра из программы demo07-13.bb с интервалом в пять кадров между ними. Как можно видеть, каждый кадр меняется очень слабо. Рис. 7.32. Фон через интервалы в пять кадров Последнее, что мы должны сделать, - прокрутить два изображения сразу. Два изображения создадут эффект расстояния, так как некоторые звезды появятся ближе из-за более быстрой прокрутки, а друг не появятся дальше нз-за более медленной прокрутки. Вдобавок, ближние звезды ярче. рис. 7.33 показывает оба звездных изображения. Рис. 7.33. Ближние звёзды слева и удаленные звёзды справа
Ниже следует полная программа demo07-14.bb. Как можно видеть, мы загрузили два изображения и прокрутили их. .-uamoO ' 14.bb - Программа смешения фонов □raphicr 800.600 ; 'танавливаем значение параметра AutoMidhandle равным "истинно" •t выводим г ; п Суфер Mic'HanlJc Р; цм S* cuff. iktafier<) ; ИЗОБРАЖЕНИЯ ,Ечижний (« . i: покручиваемый фон Ь.; -kgro’Xi ifcnt я<т dImage (" star 2.. bmp ") ; Дальний мрдпрчн^ прокручиваемый фон b<»:kgrou«,-j. TMjefai loadlmage ("starsfarther.bmp") ;( ?здаем н<i•ценную для отслеживания прокрутки si jolly . ф ;1ТАВНЬЙ ЦИКЛ tifllle Not KeyDown(l) .-Очищаем экран /вкладываем оба фона с присущими им скоростями -- Lelmage backgroundimagefar,0,scrolly ! U elmage backgroundimageclose,0,scrol ly *2 :Eсращиваем переменную прокрутки scrolly scrnlly = scrolly + 1 ;C‘ 1сываем значение переменной прокрутки в 0, если оно становится тишком большим П scrolly >- ImageHeignc(backgroundimagecloseJ scrolly = 0 ftrdlf JJip
Wend ;КОНЕЦ ГЛАВНОГО ЦИКЛА Главное отличие при сравнении этой программы с предыдущей - это загрузка и наложение. Вместо загрузки одного изображения эта программа загружает два: backgroundimageclose и backgroundimage far. Команда Trlelmage размещает оба изображения, но эторому изображению предписано прокручиваться вдвое быстрее. Таким образом, этот прием дает ощущение удаленности. Рис. 7.34 показывает кадр из этой программы. Рис- 7.34. Программа demo07-14.bb Заметьте, что я нарисовал ближние звезды после прорисовки удаленных звезд. Это важный момент - если бы я нарисовал ближние звезды первыми, они появились бы под дальними звездами. Это разрушило бы эффект смешения фонов. Ну вот, это то, что касается смешения изображений. Если хотите нечто забавное, попробуйте добавить еще одно изображение в смешение. Сможете сделать это? В качестве окончательной программы главы, demo07-15.bb, я просто взял программу KONG из главы 1 и добавил смешанное звездное поле на фон. Это то же самое, что обычная программа KONG, но теперь все происходит в космосе. Рис. 7.35 показывает работающую новую программу KONG.
Рис. 7.35. Программа demo07-15.bb Заключение Итак, мы, наконец, завершили программирование изображений. Круто, да? Вот список основных тем, раскрытых в этой главе: сдвиг; масштабирование; пропорции; масштабирование изображений; масштабирование форм; вращение; смешение фонов. Эта глава является ступенью к следующей главе: Анимация. Приготовьтесь, потому что вы собираетесь освоить, как загружать и показывать множественные изображения для анимации объекта, делая объект движущимся, идущим, бегущим, прыгающим, взрывающимся - каким вам захочется!
ГЛАВА 8 О* Анимация Вы помните все те параметры с именем [ frame 1, которые мы оставили равными О? Так вот, эти параметры станут очень полезными, как только вы поймете, для чего они там существуют, и этому вы научитесь в этой главе. Скоро вы освоите сладкое искусство анимации. Как вы знаете, каждый шаг главного цикла воспроизводит один кадр изображения текущей игры. Когда кадры изображения выводятся в быстрой последовательности, кажется, что изображения на экране движутся плавно. С помощью ввда анимации, представленного в этой главе, вы научитесь имитировать движение на экране, например вы сможете анимировать персонаж и создать ощущение, что он идет. Давайте проникнем прямо в суть главы. Первейшим делом нам нужно рассмотреть растровые изображения еще раз. «Опять!» - можете сказать вы, но на этот раз мы используем их другим способом. Использование растровых изображений в анимации Пока мы широко использовали одиночные растровые изображения на протяжении всей книги. Одиночное растровое изображение содержит только один снимок одного неподвижного графического образа. Однако изображение, поддерживающее наборы кадров, содержит многочисленные образы, которые обычно связаны друг с другом. Возьмите, например, рис. 8.1. Как можно видеть, это одиночное изображение.
Теперь поместим рисунок этого мальчика в программу. rof«-01.bb - Движущееся статичное изображение Gi « «.hies 800,600 ; гиздаем буфер по умолчанию и присваиваем параметру ?mtomidhandie значение "истина" & •tBuffer BackBuffer () ’.atoMidHandle True ;ИЗОБРАЖЕНИЕ ; J мгружаем изображение, которое будет выведено на экране playerimage = Loadlmage("staticboy.bmp") ,-ТИПЫ ,<*ги типы описывают координатную позицию игрока Т'/^е player Field х,у e=rt туре «•Создаем игрока player .player = New player «•Устанавливаем начальные значения для игрока [.JayerXr - 400 ji<ayer\y • /ГЛАВНЫЙ ЦИКЛ Кд е Not KeyDown(l) -.Пока пользователь не нажмет клавишу Esc ;Пчищаем экран С1-. ; П« чатаем текст ‘text 0,0,"X Coordinate: " + player\x Text 0,12,"Y Coordinate: " + player\y
;Если пользователь нажимает клавишу ’влево®, ;двигаем растр влево If KeyDown (203) player\x = player ,x - 5 Endlf ;Если пользователь нажимает клавишу "вправо". /двигаем растр вправо If KeyDown(205) playerXx = playerXx н 5 Endlf /Если пользователь нажимает клавишу "вверх", /двигаем растр вверх If KeyDown (200) playerXy = player\y - ' Endlf /Если пользователь нажимает клавишу "вниз", /двигаем растр вниз If KeyDown (208) player'y = playei у Endlf /Выводим игрока на экран Drawimage playerimage,player\x,playerXy Flip /Немного замедлим программу Delay 50 Wend /КОНЕЦ ГЛАВНОГО ЦИКЛА Эта программа загружает изображение и показывает его на экране. Координаты изменяются на основании нажатий клавиш игроками: если они нажимают клавиши Up. Down, Left или Right мальчик движется соответственно. Рис. 8.2 показывает кадр программы.
Даже если эта программа работает гладко, се результаты выглядят очень скучно. Все, что вы видите, - это перемещаемое изображение - мальчик даже не двигает ногами. Изображение кажется плывущим. Для решения этой проблемы мы собираемся заставить изображение казаться идущим. Чтобы сделать это, мы используем изображение с восьмью кадрами. Рис. 8.3 показывает изображение. Как видите, каждый кадр слабо отличается от предыдущего. Когда мы соберем эти кадры вместе с помощью главного цикла, мы создадим эффект анимации. Некоторые важные части программы должны измениться. Сначала мы должны загрузить изображение. Загрузка оживленного изображения несколько сличается пт загрузки статического изображения. Наиболее очевидное изменение состоит в том, что мы используем функцию LoadAnimlmage () вместо Load Image (). Функция определяется так: l.>adAnimlmage (filenames, width, height, first, count) Здесь чуть больше параметров, чем у функции Loadlmage ). Первый параметр filenames действует так же, как параметр с тем же именем в функции Loadimage (). Параметр filenames является именем файта с изображением, которое вы хотите загрузить. Следующие два параметра width и height являются шириной и высотой каждого кадра. Например, на рис. 8.3 размеры каждою кадра 71x95 пикселов. Рис. В.2. Программа demo08-01.bb Рис. В.З. Кадры с «идущим» изображением
Заметьте, что значения ширины и высоты каждого кадра точно те же, что и в предыдущем примере. Убедитесь, что все кадры изображения одинаковы; иначе, ваша программа не будет работать Параметр f irst сообщает, с какого кадра вы хотите начать загрузку. Почти всегда вам нужно будет начинать с первого кадра, поэтомуустановмте это значение равным 0, потому что в компьютерных языках счет начинается с 0. Реже у вас может возникнуть желание загрузить изображения, начиная с более позднего кадра, чем первый. Если дело обстоит так, вы используете другое значение для параметра first. Последний параметр count информирует функцию LoadAnimlmage о том, сколько всего кадров вы загружаете. Табл. 8.1 перечисляет все параметры функции LoadAnimlmage. Теперь мы можем загружать наше оживляемое изображение, используя функцию playerimage = LoadAnimlmage("animatedboy.bmp",95,71,0,8) Табл. 8.1 Параметры функции LoadAnimlmage Параметр Описание filenames Имя файла с изображением, которое вы хотите загрузить width Ширина каждого кадра в пиксела height Высота каждого кадра в пиксела first Номер кадра, с которого вы х<лите начать count Общее число кадров, которое вы хотите загрузить Прекрасно, загрузка теперь выглядит хорошо. В нижеприведенной программе demo08-02.bb мы создадим тип с координатами игрока х и у. Нам также может потребоваться еще одна дополните чьная переменная в типе - frame. Переменная frame сообщает npoi рамме, какой кадр должен быть нарисован в определенное время. Ниже полностью приведена секция инициализации новой программы demo08-02.bb. ;demo08-02.bb - Движущееся анимированное изображение Graphics 800,600 ;Настраиваем буфер BackBuffer(, и параметр AutoMidHandle SetBuffer BackBuffer!) AutoMidHandle True ,-ИЗОБРАЖЕНИЯ ,-Загружаем анимированное изображение мальчика playerimage = LoadAnimlmage("animatedboy.bmp".95,71,0,8) гТИПЫ
; Загружаем тип игрока туре player Field х,у ;Координаты к и у Field frame ,-Кадр для рисования End Туре ; Создаем игрока player.player = New player ;Даем игроку начальные значения player\х - 400 player\у = 300 player\frame = 0 Мы поменяли вызов загрузки для работы с анимированным изображением. Также тип player теперь включает поле frame, которое инициализировано значением 0. Теперь мы перейдем к главному циклу. Чтобы создать движение изображения, мы должны прибавлять кадр всякий раз, когда нажата клавиша. Следовательно, мы добавляем строку player\frame = player\frame * 1 для проверки клавиш, двигающих игрока вверх и вправо, и добавляем player\frame = player\fran® - 1 для проверки клавиш, двигающих игрока вниз и влево. Другими словами, всякий раз, когда игрок нажимает кнопку; изображение переходит к следующему кадру, и при этом кажется, что мальчик идет. Конечно, из-за того, что есть только восемь кадров, мы должны убедиться, что значение переменной player\ frame никогда не заходит дальше 7 - не забудьте, что счет кадров в поле frame начинается с 0. Мы также должны удостовериться в том, что, если пользователь заходит ниже нулевого кадра, управление передается на 7-й кадр так, чтобы анимированные изображения самостоятельно перенастраивались и продолжали работать. Это выполнено с помощью этого блока кода: If player\frame > 7 player\frame = 0 Elself player\frame < 0 player\frame = 7 Endlf Ниже приведен полный исходный текст для главного цикла ; ГЛАВНЫЙ ЦИКЛ While Not KeyDown(l)
;Очищаем экран Cis .•Размещаем текст в верхнем левом углу экрана Text 0,0, "X Coordinate: • »• player\x Text 0,12,"Y Coordinates • • player\у ;Если пользователь нажал клавишу "влево", двигаем игрока ;влево и уменьшаем номер кадра на 1 If KeyDown (203) player\х - player\x - Е player'frame - player\frame - 1 Endlf ;Если пользователь нажал клавишу "вправо". двигаем игрока вправо и увеличиваем номер кадра на 1 If KeyDown(205) player\х = player\x + 5 player\frame ~ player\frame 1 Endlf ;Если пользователь нажал клавишу "вверх", двигаем игрока вверх и увеличиваем номер кадра на 1 If KeyDown (2<jQ i player\y = player\y -5 player‘.frame - player\frame + ] Endlf ;Если пользователь нажал клавишу "вниз", двигаем игрока вниз и уменьшаем номер кадра на 1 If KeyDown (208) player\y = player\у + 5 player .frame = player\frame - 1 Endlf .-Если номер кадра становится чересчур большим, ;сбрасываем его значение в 0
If player\frame > ' player\frame = О ;Если номер кадра становится чересчур малым, переустанавливаем его значение в 7 Elself player\frame < О player\frame = 7 Endlf ;Выводим игрока в правильном положении ;и в правильном кадре Drawlmage playerimage,player\х,player\у,player\frame ;Делаем небольшую паузу Delay 100 Flip Wend ;КОНЕЦ ГЛАВНОГО ЦИКЛА Этот код и содержится в программе. На рис. R.4 представлен кадр из этой программы Есть один момент, который я хочу отметить в цикле. Видите команд}7 Drawlmage? В ней есть дополнительный параметр, с которым мы еще нс встречались. Рис. 8.4. Программа demo08-02.bb
Как мы помним, объявление функции Drawlinage следующее: Drawltnage handle,х,у, [frame] Мы до сих пор не использовали последний параметр. Параметр [ frame] позволяет нам изменить номер отображаемого кадра, как мы делали в предыдущей программе. Круто, правда? Создание растровых изображений Теперь, когда вы знаете, как загрузить растровые изображения, вы. возможно, хотите узнать, как создавать их. Прежде всего решите, как будет выглядеть анимированное изображение. Обычно каждый кадр выглядит почти так же, как соседние, но только с одним или двумя мелкими отличиями. Посмотрите на рис. 8.5. Это изображение, как вы можете видеть, является прямоугольником. Допустим, мы хотим анимировать этот прямоугольник. Сейчас мы хотим оживить это изображение. Давайте повернем его на 45 градусов (1/8 полного оборота). Результат представлен на рис. 8.6. Как вы можете видеть, этот прямоугольник был слегка повернут набок. Теперь, чтобы соединить их в растре, мы должны использовать наш любимый графический редактор (я использую программу Paint Shop Pro. которая включена в состав компакт-диска). Я создал оба изображения и соединил их в одном отдельном. Заключительное изображение показано на рис. 8.7. Рис. 8.6. Второй кадр анимации прямоугольника Рис. 8.5. Прямоугольник перед анимацией Убедитесь в том, что вы поместили кадры бок о бок. совершенно без промежутков между ними. Если вы случайно добавите промежуток, кадры станут искаженными, и выполнение вашей анимации прекратится с сообщением об ошибке «Мало кадров в изображении». Если ваши кадры перекрываются, программа покажет некоторую часть кадра два в кадре один, часть кадра три в кадре два, ит.д. Рис. 8.7. Двухкадровое изображение А сейчас нюанс: ширина и высота каждого кадра должны соответствовать ширине и высоте наибольшего кадра. На рис. 8.7 каждый кадр имеет размер 250 па 250 пикселов, но лишь потом-,; что этого размера требует больший кадр (кадр 2). Взгляните - видите первый кадр? Вокруг него есть много черного пространства Первый кадр по размеру ближе к 200x200, но достигает больших размеров из-за следующего повернутого кадра.
Теперь, когда изображение у нас приготовлено, нужно для него написать программу. Следующий листинг взят из программы demo08-03.bb. Начнем с инициализации. ;demo08-03.bb - Демонстрирует вращение прямоугольника Graphics 800,600 ,-Устанавливаем указатель изображения в центре AutoMidHandle True /Загружаем анимированные прямоугольники rectanglesimage = LoadAnimlmage("гectangles.bmp".250,250,0,2) /Создаем переменную, подсчитывающую, сколько произошло вращений rotationcount = 0 Очевидно, этот раздел лишь настраивает графический режим и загружает изображение. Удостоверьтесь, что вы обращаете внимание на то, что команда LoadAnimlmage () заявляет о наличии в изображении rectanglesimage двух кадров, каждый размером 250x250 пикселов. Создана также переменная rotation-count, чтобы считать, сколько раз происходит вращение. Теперь перейдем к важнейшей части этой программы. /ГЛАВНЫЙ ЦИКЛ While Not KeyDown(1) /Очищаем экран Cis /Печатаем количество вращений Text 0,0,"Number of Rotations: " + rotationcount /Рисуем изображение прямоугольника в соответствующем кадре Drawlmage rectanglesimage,400,300,rotationcount Mod 2 /Увеличиваем счетчик вращений на 1 rotationcount = rotationcount + 1 /Делаем небольшую паузу Delay 100 Flip Wend /КОНЕЦ ГЛАВНОГО ЦИКЛА
На рис. 8.8 показан кадр программы. Рис. 8.8. Программа demo08-03.bb Итак, давайте начнем сверху. Как обычно, команда Cis очищает экран, поэтому вращение не оставляет полос. Рис. 8.9 показывает, на что будет похожа программа, если вы удгыите команду Cis. Рис. 8.9. Результат удаления команды Cis из программы demoOB-ОЗ.ЬЬ
Команда Text отображает, сколько вращений произошло в программе, используя назначение переменной rotationcount - Затем программа выводит текущее изображение. Параметры здесь довольно понятны за исключением последнего. Как вы знае те, номер последнего кадра определяется параметром [ frame]. Мы хотим, чтобы программа чередовала значения 1 и 2 для параметра [ frame], и для этого используем оператор Mod. Как вы уже давно знаете, оператор Mod возвращает остаток от деления первого операнда на второй. Другими словами, 1 Mod 2 возвращает 1, поскольку 1, деленное на 2. дает остаток 1, и 2 Mod 2 возвращает 0, поскольку 2, деленное иа 2 дает остаток 0. Табл. 8.2 показывает возвращаемые значения оператора Mod для чисел 1-10, деленных на 2. Другими словами, в зависимости от значения переменной rotationcount (четное или нечетное) iipoi рамма отобразит первый или второй кадр. Если бы вы хотели расширить изображение до трех кадров, вы должны были бы сделать параметр [ frame] равным rotationcount Mod 3. Табл. 8.2. Результаты операции Mod Первый операнд второй операнд Результат 1 2 1 2 2 0 3 2 1 4 2 0 5 2 1 6 2 0 7 2 1 8 2 0 9 2 1 10 2 0 Следующие две строки кода обновляют значение счетчика вращений и вызывают паузу в программе на 100 миллисекунд соответственно. Если вы уберете команду Delay, программа отработает так быстро, что вы не сможете увидеть смену кадров! Прекрасно, вот так вы создаете растровое изображение. Теперь продвинемся чуть дальше - к вращению.
Отображение движения Если вы помните предыдущую главу' вы знаете, как программа Blitz Basic создает все на ши вращения для нас. Однако это не всегда работает. Когда-нибудь вы решите повысить яркость или поместить подсветку в одной области изображения, но захотите, чтобы подсветка не вращалась. В других случаях, вы можете захотеть иметь изображение, «идущее» в многочисленных направлениях. Первое, что нам тжно сделать, - создать растр. Этот пример начинается с изображения на рис. 8.10. Итак, сейчас, когда у нас есть основа, нам также нужно иметь анимацию. Поскольку изображение собирается не вращаться, а скорее полностью меняться, программа нс может сделать работу за нас. Рис. 8.11 показывает некоторые из кадров движения. Превосходно, не правда ли? Теперь мы собираемся соединить изображения в одно растровое, которое будет использоваться в прщрамме, рис. 8.12. Обратите внимание на то, что растр разбит на четыре раздела: один раздел содержит анимацию для движения влево, другой для движения вверх, еще один для перемещения право и последний, чтобы дви- Рис. 8.10, Изображение перед движением Рис. 8.12. Загружаемое игровое изображение Рис. 8.11. Кадры движения гаться вниз. Итак, сейчас у нас есть готовое изображение, нужно заняться программой. Прежде всего, начнем с существующего кода для программы demo08-04.bb. Как обычно, вначале создайте буфер и установите графический режим. ;demo08-04.bb - Демонстрирует спрайтовое движение Graphics 800,600 ;Настраиваем буфер BackBuffer!) и параметр AutoMidHandle SetBuffer BackBuffer!) AutoMidHandle True После этого вписываем константы, которые будут использоваться в прей рамме ;КОНСТАНТЫ ;Эти константы описывают направления ориентации игрока в начале движения Const DIRECTIONLEFT = 1 ;При направлении влево Const DIRECTIONUP = 2 ;При направлении вверх Const DIRECTIONRIGHT = 3 ;При направлении вправо Const DIRECTIOJflDOWN = 4 ?При направлении вниз
,-Эти константы определяют, сколько пикселов приходится при движении на кадр nst MOVEX = 5 :Сколько пикселов приходит я при движении г-ево/вправо на кадр? Const MOVEY - 5 ; Сколько пикселов приходится при движении i-ьерх/вниз на кадр? ;то ключевые константы кода nst LEFTKEY = 203,U₽KEY = 200,RIGHTKEY = 205,DOWNKEY = 208 Эти константы используются на всем протяжении программы и очень полезны. В основном, константы DIRECTION* позволяют игрокам иметь различные значения в зависимое ги от направления их движения. Например, если пользователи направляют ввера, их значение направления будет 2, если они направляют вправо, их значение направления будет 8. Параметры MOVE* определяют число пикселов, на которое игрок будет перемещен за один кадр. Не бойтесь изменять их, если хотите. Наконец, параметры *KEY дают коды для клавиш Left, Up, Right или Down. Табл. 8.3 объединяет все эти параметры. Табл. 8.3. Константы программы demo08-04.bb Константа Значение Описание DIRECTIONLEFT 1 Значение направления для отработки движения игрока влево r’TRECTIONUP 2 Значение направления для отработки движения hi рока вверх DIRECTIONRIGH'l 3 Значение направления для отработки движения игрока вправо DIRECTIONDOWN 4 Значение направления для отработки движения игрока вниз MOVEX 5 Число пикселов, на которое игрок может двигаться влево или вправо за один кадр MO EY 5 Число пикселов, на которое игрок может двигаться вверх или вниз за один кадр LEFTKEY 208 Код клавиши Left I’PKEY 200 Код клавиши Up RI'JiTKEY 205 Код клавиши Right I OWNKEY 208 Код клавиши Down
Прекрасно, теперь переходим к типу «игрок». {ТИПЫ ;Тип игрока player, используемый для персонажа на экране Type player Field х,у ;Координатное положение Field direction {Направление ориентации (одна из констант DIRECTIONXXX) Field frame ;Кадр для рисования Field image {Изображение для рисования End Туре Поля х и у показывают координатное положение игрока, поле direction показывает, в каком направлении повернут игрок, и поле frame выбирает, который кадр изображения игрока рисуется. Поле image сообщает программе, какой графический образ загружается и анимируется. Табл. 8,4 объясняет каждый параметр. Табл. 8.4. Поля типа в программе demo08-04.bb Поле Описание X Координата х положения игрока У Координата у положения игрока direction Направление, в котором игрок повернут, на основании значений констант DIRECTION* frame Номер кадра изображения игрока, которое выводится image Изображение, которое будет загружено и анимировано Теперь нам нужно настроить тип «игрок». {Создаем игрока player.player = New player {Даем переменным игрока стартовые значения player\x = 400 player\y = 300 player\direction = DIRECTIONLEFT player\frame = 0 {Загружаем изображение игрока player\image - LoadAnimlmage("monkeyanim.bmp",48,40,0,8) Как обычно при создании типа, вы должны создать экземпляр типа с помощью вызова команды New. Здесь мы создаем переменную player, основанную на типе player.
Затем присваиваем фактические значения пилей. Игрок начинает существование непосредственно в центре экрана (400, 300). Затем я решил начать вести игрока влево, поэтому значение параметра player \direction установлено в DIRECTIONLEFT. Номер кадра затем установлен равным 0, поэтому игрок начнет двигаться в правильном направлении из правильной отправной точки. Обратите внимание, что сначала в программе мы устанавливаем значение параметра AutoMidHandle равным «истина». Это позволяет объекту быть центрированным и отображенным правильно. Обратите внимание, что я сделал это прямо перед нижеследующей командой LoadAnimlmage (). Функция LoadAnimlmage () загружает картинку игрока с присущими параметрами: каждый кадр размером 48x40, и есть восемь кадров (начиная с 0 и заканчивая 7). Итак, теперь, когда это завершено, мы переходим к фактическому цингу. В этой точке игрок ориентирован влево и отображается кадр 0. В игровом цикле мы хотим, чтобы игрок мог перемещаться по изображению. Во-первых, начнем цикл с некоторой настройкой. .-ГЛАВНЫЙ ЦИКЛ While Not KeyDown(1) .•Очищаем экран Cis ; Печатаем информацию об игроке Text 0.0."Player X: " г player\x Text 0,12,"Player Y: " + playerXy Text о.24,"Player Direction; " + playerXdirection Text 0,36,"Frame: " + player\frame Эти строки показывают значения всех полей типа «игрок» (за исключением поля image, конечно). Теперь я хочу вас на мгновение остановить. Следующая часть кода предполагается тяжелой для понимания, поэтому я собираюсь показать вам только одну часть и объяснить ее до показа остального. Сейчас мы должны позволить пользователю изменить направление персонажа на экране. Чтобы сделать это, мы сначала проверяем, какая клавиша бьыа нажата. If KeyDown (LEFTKEY) Следующие строки программы отработают, только когда пользователь нажмет клавишу «стрелка влево». Теперь вы действительно должны переместить пользователя влево, изменяя его координату х. playerXx = playerXx - MOVEX
Как и следовало ожидать, это перемещает пользователя немного влево. Затем мы изменяем направление ориентации пользователя. playerXdirection = DIF bCTIONLEFT Именно это сообщает компьютеру, что игрок направлен лицом влево. Следующая строка, вероятно, самая сложная для понимания. Она вычисляет номер кадра, который отображается, основываясь на направлении, в котором повернут игрок. player\frame (player\ frame + 3 } Mod (2) + (2 * (playerXdirection)-2) Вот так так! Это - большая математическая проблема. Позвольте мне показать вам, что происходит. 1. Переменная player\frame увеличивается на 1. В этом примере переменная player frame, которая начала программу со значением 0, теперь равна 1. 2. Переменная player' frame делится на 2, и возвращается остаток, используя функцию Mod. В этом примере, переменная playerX frame, которая равна 1 делится на 2. Поскольку 1/2 дает остаток 1, (player\frame + 1 ) Mod (2) возвращает 1 3, Число 2, умноженное на значение направления игрока, за минусом 2 прибавляется к значению кадра. Это выражение дает соответствующее значение номера кадра в зависимости от направления игрока. В этом примере, выражение 2 * player\direction (которое равно 1) - 2 = 0, что прибавляется к переменной player\f rame (которая, согласно шагу 2, раяна 1), Таким образом, переменная player \ frame равна 1. Хотелось бы надеяться, что большую часть из этого не тяжело постичь, за исключением выражения 2 * playerXdirection - 2. По существу, думайте об этом у равнении, как об аналогии с глобальными и локальными координатами. Если вы помните, с помощью глобальных и локальных координат вы находите позицию чего-либо в его собственном локальном пространстве и прибавляете это к позиции на экране. То же самое имеет место здесь; вы различаете кадр (0 или 1, локальные координаты) и прибавляете это к выражению 2 * player \direction - 2 (от 0 до 7, глобальные координаты). Табл, 8,5 перечисляет все возможные значения для переменной player X frame, завершав значением выражения 2 * playerXdirection - 2 для этого кадра. Убедитесь, что вы понимаете, что выражение 2 * playerXdirection - 2 работает только потому, что есть два кадра для каждого направления. Если было бы три кадра для каждой анимации (с общим количеством 12 кадров, если направлений остается все еще только четыре), уравнение было бы 3 * playerXdirection - 3. Если бы было пять кадров на одно направление, выражение было бы 5 * playerXdirection - 5, ит. д.
Табл. 8.5. Значения для каждого кадра Номер кадра Направление 2 * p1ayer\direction - 2 0 1 0 1 1 1 2 2 2 3 9 3 4 з 4 э 3 5 6 4 6 7 4 7 Теперь, когда вы (будем надеяться) поняли, как мы находим кадр с шроком, по крайней мере, когда он движется влево, позвольте мне показать весь игровой цикл, ;] '1АВНЫЙ ЦИКЛ While Not KeyDownfl, ;(нищаем экран ; Поедаем текст в верхнем левом углу Lcfaate 0,0 ;Выводим информацию об игроке Text 0,0,"Player X: " + player\х Text 0,12,"Player Y: " + player\y Text 0,24,"Player Direction,: - + player\direction Text 0,36,"Frame: " + player\frame ,-Если пользователь нажимает клавишу «влево», двигаем игрока :влево и находим правильное направление и кадр 11 KeyDown(LEFTKEY) player х player\x - MOVEX ;двигаем его влево playerxdirection = DIRECTIONLEFT ;поворачиваем его лицом влево playerxframe = (playerXframe + 1) Mod (2) + (2 * (playerXdirection) - 2) ;находим кадр
;Если пользователь нажимает клавишу "вверх", двигаем игрока ;вверх и находим правильное направление и кадр Elself KeyDown(UPKEY) player\y = playerXy - MOVEY ;двигаем его вверх playerXdirection - DIRECTIONUP ;поворачиваем его лицом вверх playerXframe = (playerXframe + 1) Mod (2) » (2 * (playerXdirection) - 2) ;находим кадр ;Если пользователь нажимает клавишу "вправо", двигаем игрока вправо и находим правильное направление и кадр Elself KeyDown(RIGHTKEY) playerXx = playerXx ♦ MOVEX ;двигаем его вправо playerXdirection = DIRECTIONRIGHT ;поворачиваем его лицом вправо player\frame = (playerXframe ♦ 1) Mod (2) + (2 • (playerXdirection) - 2) ;находим кадр ;Если пользователь нажимает клавишу "вниз", двигаем игрока вниз находим правильное направление и кадр Elself KeyDown(DOWNKEY) playerXy = playerXy + MOVEY ;двигаем его вниз playerXdirection = DIRECTIONDOWN ;поворачиваем его лицом вниз player\frame = (playerXframe + 1) Mod (2) + (2 • (player\direction) - 2) ; находим кадр Endlf ;Выводим игрока в правильном положении и в правильном кадре Drawimage playerXimage,playerXx,playerXy.playerXframe ;Ждем доли секунды Delay 50 Flip Wend .•КОНЕЦ ГЛАВНОГО ЦИКЛА Круто, правда? Рис, 8.13 показывает снимок окна этой программы.
Рис. 8.13. Программа demo08-04.bb Заключительные детали программы действуют так, как вы от них ожидаете. Если вы нажимаете клавишу «стрелка вправо», игрок передвигается на пять пикселов вправо, как показано следующей строкой кода. player\х = playerxx + MOVEX То же самое, только с переменной у, происходит, если пользователь нажимает клавиши Up или Down В конце программы изображение выводится на экран с помощью команды Drawimage. Drawlmage player\image,player\x,player\y,player\£ rame Эта строка выводит выбранный кадр (player\frame) изображения игрока (player \ image) с координатами игрока х и у (playerXx . player \у). Программа завершается, задерживаясь на 50 миллисекунд. Без паузы анимация происходит очень быстро - иногда так быстро, что практически невозможно увидеть существующее движение! Вот и все о программе demo08-04.bb. Только лишь для забавы я написал программу demo08-05.bb. Она точно такая же, как demo08-04.bb, по на сей раз игрок ходит по траве вместо пустоты. Рис. 8.14 показывает траву, которой вымощен фон. Рис. 8.14. Элемент травяного фона
Рис. 8.15 показывает снимок окна программы dema08-05.bb. Рис. 8.15. Программа demo08-05.bb Эта глава почти завершена, поэтому давайте повторим для запоминания некоторые самые важные моменты при создании растровых изображений. убедитесь, что каждый кадр вашего растра имеет один и тот же размер; убедитесь, что все растры выстроены в линию непосредственно рядом друг с другом. Помните также, что понять анимации легче, если вы выстроите растры в линию. Например, в программе demo08-04.bb (а также в demo08-05.bb) я создал четыре набора из двух анимаций. То же самое было бы сделано в другом случае для вращений. Например, скажем, вы бы вращали корабль 12 раз. Поместите первые чет ыре поворота (от направления вверх до направления вправо) в одну строку, следующие четыре поворота (от направления вправо до направления вниз) в другую строку и т. д. Заключение Итак, мы сделали это! Это - конец этой главы. В этой глаяе вы освоили следующие понятия:Ы использование растровых изображений в анимации; создание растровых изображений; показ движения. Вы готовы к следующей главе? Мы пододвигаемся к теме обнаружения конфликтных ситуаций. Здорово!
ГЛАВА 9 Обнаружение конфликтов Вы приближаетесь к концу второй части книги. В этой главе рассматривается искусство обнаружения конфликтов. Обнаружение конфликтов позволяет вашей программе определить, был ли объект на вашем экране задет другим объектом, и выполняет действия на основе проверки. Например, если вы сделали космический истребитель и хотите определить, попадает ли ракета во вражеское судно, вы должны использовать проверку на пересечение. Если корабль был поражен, вы могли бы уменьшить его очки или уничтожить корабль совсем. Есть несколько способов обнаружишь конфликт, и сейчас мы собираемся тщательно в них разобраться. Мы можем использовать ограничивающие рамки, как прямоугольные, так и круглые, а также полные точечные пересечения. Давайте начнем с проверки пересечения с отдельной точкой. Понятие конфликта Прежде, чем мы узнаем, как контролировать конфликты (столкновения) объектов -изображений, форм и т. п„ - детально рассмотрим основные виды конфликтов пикселов. Чтобы определить, был ли конфликт пикселов, вы просто проверяете интересующий вас пиксел и убеждаетесь, что значения его координаг х и у не г «впадают с координатами объекта, который вы проверяете. См. рис. 9.1 в качестве примера. Нет конфликта Конфликт | рстолкновение Рис. 9.1. Различие между конфликтом и отсутствием конфликта
В приведенной ниже программе demo09-01 .bb мы позволим пользователю контролировать отдельный пиксел, который можно двигать вверх, вниз, влево или вправо. Если пиксел ударится о стенку, а стенкой будет край экрана, положение пиксела будет восстановлено, а счетчик столкновений увеличен. Ниже приведен исходный код программы demo09-0l .bb: ;demo09-01.bb - Демонстрирует конфликты пикселов Graphics 400,300 .Создаем переменные, описывающие координатное положение точки Global к = 200 Global у - 150 Cis ;Эта переменная содержит число происшедших столкновений collisions = 0 ;КОНСТАНТЫ ;Это ключевые константы кода Const UPKEY = 200,DOWNKEY = 208,LEFTKEY = 203,RIGHTKEY = 205 ;ГЛАВНЫЙ ЦИКЛ While Not KeyDown (1} ;Выводим вводную информацию Text 0,0,"Press the arrow keys to move the pixel around • {Выводим число столкновений Text 0,12, "Collisions: " * collisions ;Перемещаем игрока в зависимости от нажатой пользователем клавиши If KeyDown(UPKEY) у - у - 5 Elself KeyDown(DOWNKEY) у = у Г 5 Elself KeyDown(LEFTKEY) x = x - 5 Elself KeyDown(RIGHTKEY)
х = х + 5 Endlf {Вызываем функцию CheckForCollieions и определяем, были ли столкновения collisions = CheckForCollisions(collisions) ;Рисуем точку на экране Plot х,у {Ожидаем долю секунды Delay 100 Flip Wend {КОНЕЦ ГЛАВНОГО ЦИКЛА •ФУНКЦИИ {Функция CheckForCollisions(collisions) - возвращает общее число столкновений, проверяет наличие новых конфликтов {collisions: количество столкновений в момент вызова функции Funct ion CheckForCol1isions(col1is ions) {Если точка - вне экрана, сообщаем о конфликте If х <= 0 Or х >= 400 Or у <- 0 Or у >= 300 collisions = collisions + 1 {наращиваем счетчик collisions Cis {Очищаем экран Text 100,150."A Collision Has Occurred* Flip Delay 1000 {ждем секунду Cis {Снова очищаем экран Flip Cis х = 200 {восстанавливаем значение х у = 150 {восстанавливаем значение у
Endlf ; Возвращаем чис>.л конфликтов Return collisions Cis End Function Принцип работы этой программы должен быть вам уже знаком. Программа начинает с установки графического режима и создания переменных х, у и collisions. Затем программа входит в главный цикл. Заметьте, х и у являются глобальными переменными, a collisions - не является Этот факт будет важен позже в программе. Внутри главного цикла программа определяет, были ли нажаты какие-либо клавиши со стрелками. Если да, программа увеличивает на 1 значения переменных х и у соответ ствснно. Программа отображает также количество столкновений в верхней части экрана. Перед концом цикла программа вызывает функцию CheckForCollisions (). Функция содержит переменную collision в качестве параметра. Функция также устанав ливаст значение переменной collision равным возвращаемому значении». Табл. 9.1 уточняет параметр. Табл. 9.1. Параметр функции .-kForCollisions (} Параметр Описание collisions Число столкновений, которые произошли до сих пор в программе. Функция также возвращает чис то конфликтов Далее рассмотрим функцию CheckForCollisions I. Первая и сложнейшая для понимания часть функции - это проверка. Проверка выгладит так: If х <= 0 Or х >= 400 Or у < 0 Or у >= 300 В ходе этой проверки определяется. что проверка для координаты х относится к правой и левой границам экрана, а проверка для координаты у отно- х=-400 сится к верхней и нижней границам. ~ Теперь, если программа находит, что точка пересекла одну из границ, начинается процедура восстановления ис- -----------уаЗОО----------- ходпых значений. Сначала прибавляется 1 к значению переменной col- ----------------------------------------- lisions, что наращивает счетчик Рис. 9.2. Проверка столкновения с границей
конфликтов на 1. Затем отображается надпись «А Collision Has Occurred» (Произошло столкновение) на экране. Затем значения координат х и у возвращаются к исходным значениям. Независимо от того, произошло столкновение или нет, функция возвращает значение параметра collisions в главный цикл. Если конфликтов не было, значение иараметра collisions останется неизменным; если столкновение было, значение параметра col li s ions возрастет на 1. Оставшаяся часть главного цикла рисует пиксел на экране и делает паузу в работе программы на 1, '10 секунды. Это все, что касается программы demo09-01.bb. На рис. 9.3 представлено окно программы. Рис. 9.3. Программа demo09-01.bb Ограничивающие окружности Теперь, когда мы знаем, как проверять отдельные точки на. i фсдмст конфликтов с другими объектами, нам нужно освоить, как проверять конфликты между объектами. Объектами могут быть формы, изображения и т. п. Существуй несколько способов проверки конфликта форм. Ограничивающие окружности - это первый метод. По существу, метод включает в себя размещение невидимых окружностей вокруг проверяемых нами объектов. Если крути перекрываются, произошел конфликт. См. рис. 9.4 в качестве примера. Если вы посмотрите на рис. 9.4 внимательно, вы заметите, что объекты в действительности не сталкиваются - это делают только ограничивающие объекты окружности. Это обычно не столь важно; поскольку объекты так близки друг к другу, что это выгладит так, как будто было столкновение. Прежде чем я смогу показать вам, как это работает, вы должны понять две концепции: расстояние межд) точками и радиус круга. Рис. 9.4. Перекрывающиеся круги
Расстояние между точками Если мы используем ограничивающие окружности, мы должны сравнить расстояния между точками. Для нахождения расстояния между'двумя точками мы можем использовать математическое уравнение. distance = sqrt((x2 - xl) ' 2 + (у2 - yl) ' 2)) Приходилось ли вам ранее видеть символ ~? Это означает «в степени». В этом случае '2 означает, что вы возводите число во вторую степень или берете число в квадрате. Взять квадрат значения означает умножить значение на само себя. Другими словами, 10'2 читается как “10 в квадрате» и равно 10x10 или 100. Как это прочесть? Чтобы найти расстояние между' двумя точками, вы берете разность координаты х второй точки и первой точки и разность координаты у второй точки и первой точки. Затем вы возводите в квадрат каждое число - умножаете каждое из значений на себя - и складываете результаты вместе. Наконец, вы берете квадратный корень из конечного числа. Рис. 9.5 показывает, как вы могли бы оценить расстояние между двумя различными точками. Нелегко понять, да? Ничего, не беспокойтесь по этому поводу. Я написал приведенную ниже функцию Distance () как раз для вас. Function Distance(xl,yl,х2,у2) Рис. 9.5. Пример вычисления расстояния dx = х2 - xl dy = у2 - yl Return sqrt((dx * dx) + (dy • dy)) End Function Помните, как в главе 7 «Основы программирования изображений» вы читали о приращение значений? Если да, то вы должны также вспомнить, что мы говорили об изменении величин как о дельте. Вот что символ «d» означает в переменных dx и dy в функции Distance (). Дельта означает «приращение» - здесь это приращение х и у. Здесь вводятся одно или два новых понятия. Рассмотрим эти понятия подробнее. Прежде всего, обратите внимание, что я вычислил, чему равны значения выражений х2 — xl и yz -yl перед непосредственным нахождением расстояния. Это очень облегчает чтение кода. Если бы я пренебрег предварительным вычислением этих чисел, оператор возвращения значения функции выглядел бы приблизительно так. Return sqrt(.((x2 - xl)*(x2 xl)) + t (у2 — yl) * (у2 - yl)))
Гораздо хуже, правда? Нахождение значений сделало мой код значительно легче для чтения и понимания. Обратите также внимание на функцию sqrt (). Она возвращает квадратный корень предоставленного в качестве параметра числа. Квадратный корень - это число, которое при умножении на себя, дает данное число. Ха! По существу, если вы умножаете квадратный корень из числа на себя, вы получаете это же число. Например, квадратный корень из 4 равен 2. Вы можете проверить это, умножив число 2 на себя. Поскольку 2x2 - 4, число 2 является квадратным корнем из 4 (-2 тоже корень, но это уже совсем другая тема). Поиск квадратного корня вручную является невероятно сложной процедурой. Именно поэтому программа BlitzPIus предоставляет вам функцию sqrt (). Ниже следует объявление для функции sqrt (). Sqrt (value#) Табл. 9.2 описывает параметр функции sqrt (). Табл. 9.2. Параметр функции Sqrt () Параметр Описание value# Число, квадратный корень которого вы хотите получить Вот и все, что касается нахождения расстояния между двумя точками. Для справки ниже следует объявление функции Distance (). Function Distance(xl,yl,x2,y2) Табл. 9.3. Параметры функции Distance () Параметр Описание Х1 Координата х первой оцениваемой точки у1 Координата у первой оцениваемой точки х2 Координата х второй оцениваемой точки У2 Координата у второй оцениваемой точки Итак, сейчас мы переходим к нахождению радиуса круга. Радиусы У вас болит голова от раздела о нахождении расстояний? Ничего, ие волнуйтесь, эта часть гораздо проще. Прежде всего, радиус окружности равен 1 /2 диаметра окружности. В круге существует только одна точка непосредственно в центре. Начинал с этого места, я буду называть эту точку' «центром окружности». Прекрасно, в любом случае диаметр окружности - расстояние от любой точки на окружности до другой точки на окружности, если оно проходит через центр окружности. Что я имею ввиду? См. рнс. 9.6.
Заметьте, что диаметр, показанный на рис. 9.6, -только один из многих. Действительно, можете ли вы прикинуть, сколько диаметров находится в круге? Если вы предположили 1, то неправильно. 360? Нет, снова неправильно. В круге фактически бесконечное число диаметров. Это верно: существует бесконечное число диаметров в круге. Однако все они должны простираться от крав до края и проходить через центр круга, поэтому'длины всех диаметров равны. В любом случае, возвращаясь к радиусам, радиус окружности составляет 1 '2 диаметра. Вы моьчи бы подумать, что это — то же самое, что и расстояние от «центра окружности» до самой окружности, не так ли? Вы совершенно правы! Рис. 9.7 показывает радиус окружности. Убедитесь, что вы понимаете, что любая точка на окружности имеет точно такое же расстояние до центра окружности, что и любая другая точка на окружности. Пгговы к крутому словечку? Каждая точка на окружности яаляется равноотстоящей, или эквидистантной, от центра окружности. Итак, мы готовы идти дальше. Как мы можем найти радиус объекта? Это - большая проблема, так что давайте выясним, как это делается. Мы собираемся использовать код для вычисления. Сначала мы лшружаем объект - скажем, изображение. imagehandle = Loadlmage("image.bmp") Рис. 9.6. Диаметр окружности Рис. 9.7. Радиус окружности Не так уж сложно, да? Теперь мы должны найти радиус. Прежде чем мы сможем это сделать, мы должны детально освоить две очень важные функции - Imagewidth () иImageHeight(). Эти две функции возвращают ширину и высоту в пикселах изображения, соответствующего указателю. Ниже следуют объявления функций. ImageWidth(imagehand1е) ImageHeight(imagehandle) Табл. 9 4 и 9.5 описывают параметры функций. Табл. 9.4 Параметр функции ImageWidth () Параметр Описание Imagehand Указатель изображения, чья ширина в пикселах возвращается функцией
Табл, 9.5. Параметр функции ImageHeight () Параметр Описание imagehandle Указатель изображения, чья высота в пикселах возвращается функцией Так или иначе, возвращаясь к нашему коду, мы должны найти радиус нашего изображения. Давайте определим радиус изображения как расстояние от центра изображения до крайних точек изображения. Убедитесь, что вы понимаете, что каждое растровое изображение прямоугольное, а не круглое по природе. Следовательно, проверка радиальных столкновений не будет совершенно точной. Вы можете решить, что достаточно взять результат функций ImageHeight () или Imagewidth () и разделить на два, чтобы получить радиус. Однако это плохая идея. Поскольку изображения имеют не квадратную форму, а скорее прямоугольную, учет только ширины или только высоты может дать вам неточный радиус. Что мы собираемся сделать: взят ь среднее арифметическое половины высоты и половины ширины изображения. Давайте напишем для этого функцию FindRadius (). Function FindRadius(imagehandle) Return ((Imagewidth(imagehandle)/2) • ImageHeight(imagehandle) /2) /2) End Function Эта функция возвращает приблизительный радиус изображения, указатель которого задается как параметр. Табл. 9.6 описывает параметр. Табл. 9.6. Параметр функции FindRadius() Параметр Описание imagehandle Указатель изображения, чей приблизительный радиус возвращается функцией Итак, теперь нам нужно узнать, как проверять изображение, предоставленное функции FindRadius (), па предмет столкновения с другим объектом. Все, что мы делаем, - проверяем, является ли расстояние от изображения до точки меньшим, чем радиус. Приведенная ниже программа demoO9-O2.bb демонстрирует, как это делается. Программа длинная, поэтому я не хочу приводить в книге весь листинг. Позвольте показать вам хотя бы некоторые крутые места. Мы еще не обсуждали использование ключевого слова Each в циклах For. Рассмотрим. как оно работает. Прежде всего, мы должны создать тип. В этой программе мы использовали тни для каждой точки. Тип определен следующим образом. ;Тип "точка" определяет каждый объект, который может быть пересечен кораблем
Type point Field х,у ;координаты корабля х и у End Туре Теперь мы хотим создать множество таких точек. Это выполняется циклом For-JEach. ;Создаем новые точки в количестве, равном константе NUMBEROFOBJECTS, со случайными координатами х и у For counter = 0 То NUMBEROFOBJECTS point.point = New point point\x = Rand (0,800) point\y = Rand (0,600) Next Этот цикл создает точки в количестве, равном константе NUMBEROFOBJECTS, и дает им всем случайные значения координат х и у. Если вы интересуетесь, что означает константа NUMBEROFOBJECTS, см. табл. 9.7. Табл- 9.7. Константы программы demo09-02.bb Константа Значение Описание NUMBEROFOBJECTS 50 Количество точек, которые могут быть задеты кораблем игрока LEFTKEY 203 Код клавиши Left UPKEY 200 Код клавиши Up RIGHTKEY 205 Код клавиши Right DOWNEEY 208 Код клавиши Down MOVEX э Число пикселов, на которое игрок может сдвинуться влево или вправо за один кадр MOVEY 5 Число пикселов, на которое игрок может сдвинуться вверх или вниз за один кадр Звучит неплохо? Хорошо. Теперь, когда мы создали каждый из объектов, нам также нужно знать, как удалить все объекты. Мы стираем объекты, когда переустанавливаем уровень. ,-Удаляем каждую точку на экране For point.point = Each point Delete point Next Этот код удаляет все точки, созданные ранее.
Кстати, если вы не помните, как работает цикл For...Each, см. главу 3 «Циклы, функции, массивы и типы» для повторения Итак, следующая тема, к которой я хочу перейти, - функция Tested lisions (). Эта функция проверяет все объекты на экране, чтобы определить, задевает ли их корабль. .ФУНКЦИЯ Testcollisions() - Проверяет объекты и корабль на пересечение ;Вез входных параметров ,• Возвращает 1, если было пересечение, 0, если не было Function Testcollisions() ;Проверяем каждый объект, чтобы определить, находится ли он в пределах радиуса корабля. Если да, возвращаем результат, сообщающий о пересечении. For point.point = Each point If Distance(player\x,playerXy,point\x,point\y) < player\radius Return 1 Endlf Next ;Если пересечений не было, возвращаем О Return 0 ;Пересечений не было End Function Довольно неплохо, да? Функция проверяет каждую точку, чтобы определить, находится ли точка внутри радиуса корабля. Если да, возвращается 1. Если было столкновение, в главном цикле переустанавливается уровень, и число конфликтов возрастает на единицу, В этой программе определено довольно много фунций и табл. 9.8 перечисляет все. Табл. 9.8. Функции программы demo09-02.bb Параметр Описание TesetLevel(} Удаляет и восстанавливает все объекты; восстанавливает начальные координаты игрока Г»stCollisions() Проверяет все объекты, чтобы увидеть, сталкивались ли они с космическим кораблем, и возвращает 1, если столкновение имело место TestKeys ( Проверяет клавиатуру, чтобы определить, была ли нажата какая-либо клавиша Distal. се ।) Находит расстояние между двумя точками FindRadius() Находит радиус изображения Вот примерно все, что касается этого раздела кода. Далее мы переходим к ограничивающим рамкам. Кстати, рис. 9.8 показывает снимок окна программы.
Рис. 9.8. Программа demo09-02.bb Ограничивающие рамки Итак, теперь, когда мы усвоили, как использовать ограничивающие окружности, усвоим, как использовать ограничивающие рамки. Ограничивающие рамки - точно то же. что и ограничивающие окружности, за исключением того, что вместо сравнения перекрывающихся окружностей сравниваются перекрывающиеся прямоугольники. Смотрите рис. 9.9 как пример ограничивающего прямоугольника. Если вы посмотрите на рис. 9.10, то заметите, что конфликт происходит не вссща, даже если о нем сообщается. Это обычно не столь важно, потому что все-гйки объекты довольно близки друг к другу. В отличие от использования oi раничива-ющих окружностей программа BlitzPIus предоставляет метод проверки конфликтов, используя ограничивающие рамки. Вы прочитаете об этом через минуту' после того, как я покажу вам, как это делать вручную. Мы должны использовать функции ImageHeight () и ImageWidth () снова, но на сей раз другим способом. В качестве ограничивающей рамки д ля прямоугольника берется внешний край изображения. См. рис. 9.11 в качестве примеря. Как мы теперь перейдем к определению этой ограничивающей рамки? Прежде всего, помните, что, когда мы используем изображения, точка обработки находится непосредственно в центре изображения. Это определено функцией AutoMidHandle.
Поскольку точка обработки находится в центре, мы должны определить верхний левый и нижний правый углы, чтобы найти ограничивающую рамку. Если бы точка привязки была установлена в верхнюю левую точку изображения, проблема решалась бы легко. Мы начали бы, расположив точку привязки в верхнем угщ Сумма координаты х точки привязки и значения функции Imagewidth!) дала бы координату х нижнего правого угла, а координата у нижнего правого угла была бы равна сумме координаты у точ ки привязки и значения функции ImageHeight!). Рассмотрите рис. 9.12 в качестве примера того, как это работает с изображением, которое имеет 32 пиксе- ла в ширину и 32 пиксела в высоту. Хорошо, теперь к делу. Поскольку точка привязки находится не в верхнем левом углу, мы должны использовать другую формул}'. 11о существу, ограничивающая рамка будет иметь такие координаты верхнего левого угла; -.'2 * ImageWidth(), -1/2 * ImageHeight!) И такие для нижнего правого угла: 1/2 * ImageWidth!), 1/2 * ImageHeight!) Рис. 9.10. Неполный конфликт Рис. 9.11. Ограничивающая рамка Эго иллюстрирует рис. 9.13. MldhandfefO.O) ImageWHthf 1 = 32 Граница ограничивающей рамки (ImsoeWidtht). кпадаНе^Ц)) <32 32) Рис. 9.12. Ограничивающая рамка с точкой привязки в верхнем левом углу
Рис. 9.13. Ограничивающая рамка с точкой привязки в центра Как это работает? Функция AutoMidHandle устанавливает точку привязки непосредственно в центре прямоугольника изображения. Мы должны передвинуть точку привязки к верхнему левому углу изображения так, чтобы мы смогли нарисовать прямоугольник вокруг растра. Поскольку точка привязки расположена непосредственно в центре, мы должны переместить ее на 1 /2 высоты прямоугольника вверх и на 1/2 ши рины прямоугольника влево. Это позволяет вам задавать прямоугольник от верхнего левого угла. Итак, теперь, до того как мы создадим нашу программу обнаружения конфликтов, напишем программу, которая демонстрирует технику ограничивающих рамок. Приведенная ниже программа demo09-03.bb рисует прямоугольник ограничивающей рамки вокруг изображения космического корабля. ;demo09-03.bb - Рисует ограничивающую рамку Graphics 800,600 ;Устанавливаем буфер по умолчанию и функцию automidhandle равной "истинно" SetBuffer BackBufferl) AutoMidHandle True ;ИЗОБРАЖЕНИЯ ;Загружаем изображение корабля Global shipimage = Loadlmage("ship.bmp") ;Задаем кораблю координаты по умолчанию Global х = 400 Global у = 300
;КОНСТАНТЫ {Ключевые константы кода Const UPKEY = 2ОО,DOWNKEY = 208,LEFTKEY = 203,RIGHTKEY = 205 ;Эти константы описывают, на сколько пикселов происходит перемещение за один кадр Const MOVEX = 5 Const MOVEY = 5 .-ГЛАВНЫЙ ЦИКЛ While Not KeyDown(1) {Очищаем экрана Cis {Выясняем, были ли нажаты какие-либо важные клавиши на клавиатуре TestKeys() {Рисуем ограничивающий прямоугольник вокруг игрока DrawPlayerRect() {Выводим изображение корабля Drawlmage shipimage,х,у Flip {Замедляем отображение Delay 20 Wend {КОНЕЦ ГЛАВНОГО ЦИКЛА {ФУНКЦИЯ DrawPlayerRect() - Рисует ограничивающий прямоугольник Function DrawPlayerRect() {Находим ширину изображения iw = Imagewidth(shipimage) {Находим координаты верхнего левого угла xl# = ((-Imagewidth(shipimage) /2) А х) yl# = ((-ImageHeight(shipimage)/2) + у) ; Рисуем всю ограничивающую рамку
Rect xl#,yl#.Imagewidth(shipimage),ImageHeight(shipimage),0 End Function ;ФУНКЦИЯ TestKeysO - Проверяет все клавиши, чтобы определить, были ли они нажаты Function TestKeysO ;Если нажата клавиша "вверх", передвигаем игрока вверх If KeyDown(UPKEY) у = у - MOVEY Endlf .-Если нажата клавиша "вниз", передвигаем игрока вниз If KeyDown(DOWNKEY) ; Если нажата клавиша "вниз" у = у + MOVEY Endlf ;Если нажата клавиша "влево", передвигаем игрока влево If KeyDown(LEFTKEY) х т х - MOVEX Endlf ;Если нажата клавиша "вправо". передвигаем игрока вправо If KeyDown(RIGHTKEY) х = х + MOVEX Endlf End Function Для меня наиболее трудная для понимания вещь - функция DrawPlayerRect (). Функция DrawPlayerRect <) рисует ограничивающую рямку вокруг изображения игрока. Если вы помните, я сказал, что ограничивающая рамка простирается из точки с коодинатами (-1/2 * ImageWidth (), 12 * ImageHeight ()) до точки с координатами (1/2 * Imagewidth (), 12 * ImageHeight ()). Однако функция DrawPlayerRect () создает ограничивающую рамку совсем иначе. ;ФУНКЦИЯ DrawPlayerRect() - Рисует ограничивающий прямоугольник Function DrawPlayerRect(1 ,-Находим ширину изображения iw = ImageWidth (ship image) .•находим координаты верхнего левого угла xl# = ((-ImageWidth(shipimage) /2) + х)
yl# = ((-ImageHeight(shipimageI/21 - у) ;Рисуем всю ограничивающую рамку Rect xl#,у1#,ImageWidth(shipimage),ImageHeigL .hipimage).0 End Function Прежде всего, возьмем переменную xl#. Как можно видеть, вместо 1/2 * ImageWidth(). значение равно ImageWidtl Однако выражения 1/2 * Imagewidth () и ImageWidth (' 2 эквивалентны. Умножение чего-либо на 1/2 - это то же самое, что и деление на 2. Следовательно, выражение 1 z * ImageWidth () -это то же самое, что и ImageWidth () . 2. Обратите также внимание, что я прибавил координату х к прямоугольнику при нахождении значения xl#. Это размещает ограничивающую рамку в реальном пространстве игрока - если бы я забыл добавить координату прямоугольник начался бы в верхнем левом углу экрана. Это подобно глобальным и локальным координатам. Нахождение ограничивающей рамки - это нахождение локальных координат, но добавление надлежащего значения х дает преобразование в правильные глобальные координаты. Мы делаем ту же самую операцию с переменной yl#. Последнее: вызов функции Rect может слегка сбить с толку. Позвольте мне помочь вам понять это, подробно изложив объявление функции Rect. Rect х,у,width,height,solid Помните это? В любом случае, как вы знаете, прямоугольник начинается с точки х, у. Мы уже поняли, что представляют собой координаты х и у в предыдущих двух переменных xl# и у!#. Теперь нам нужно определить щиршп’ и высоту прямоугольника. Ширина и высота прямоугольника - это ширина и высота изображения. Они находятся с иегюльзовзнием функций ImageWidth () и ImageHeight; 1 Конечно, мы не хотим, чтобы прямоугольник был закрашен - это сделает вид корабля безобразным! Поэтому мы устанавливаем параметр равным 0, что оставляет прямоугольник незаполненным. На рис. 9.14 показано окно программы. Рис. 9.14. Программа demo09-03.bb
Между прочим, есть гораздо более простой способ захватить образ за верхний левый угол, чем использование функций ImageWidth!) и ImageHeight!). Программа BlitzPIus предоставляет функцию с именем Handlelmage, которая позволяет вам выбрать, 1де на изображении вы хотите разместить ваш указатель - точку захвата. Функция Handlelmage объявляется таким образом. Handlelmage image,х,у Для установки точки захвата в верхний левый угол вам нужно лишь вызвать функцию Handlelmage, как показано ниже, Handlelmage shipimage,0,0 Прекрасно, теперь мы готовы определить, было ли столкновение объекта с космическим кораблем, используя ограничивающие рамки. Программа demo09-O4.bb такая же, как и demo09-02.bb, за исключением того, что в ней не используются ограничи вающие окружности. Давайте перейдем к изменениям в программе. Прежде всего, тип «игрок» изменен из такого вида: ;Тип игрока является космическим кораблем на экране iype player Field х,у ;координаты игрока х и у Field collisions ;количество происшедших конфликтов Field radius радиус изображения игрока Field image ;изображение игрока End Туре к такому: ,-Зтот тип содержит данные игрока Type player Field xry ;координаты игрока х и у Field collisions {количество происшедших конфликтов Field image {изображение игрока End Туре Да, правильно. Мы избавились от поля radius! Во всяком случае, возвращаясь к программе, мы изменили функцию Testcollisions совсем немного. .-ФУНКЦИЯ Testcollisions () - Проверяет объекты и корабль на предмет конфликтов ;Без входных параметров {Возвращает 1, если был конфликт. С, если не было Function Testcollisions() {Проверяем каждую точку, чтобы видеть, {находится ли точка в пределах радиуса игрока For point.point s= Each point
; Определяем ограничивающую рамку для игрока xl = -ImageWidth(player\image)/2 + playerXx х? ~ Imagewidth(playerXimage)/2 * playerXx yl - -ImageHeight(playerXimage)/2 * playerXy yi = ImageHeight(playerXimage)/2 + playerXy ;Если точка находится в пределах радиуса столкновения, возвращаем 1 If (point\х > xl' And (point'x t x2) And (point\y > yl) And (point \y < y2) Return 1 End If Next ; Переходим к следующей точке {Столкновений не было, если функция выполняется здесь, поэтому возвращаем С Return О End Function {КОНЕЦ ФУНКЦИИ Testcollisions() Как вы можете видеть, функция начинается поиском ряамеря прямоугольника. Вспомните, что ограничивающий прямоугольник простирается от точки с координатами (ImageWidth()/2 + х, -ImageHeight ()/2 + у) до точки с координатами (ImageWidth()/2 + х, ImageHeight ()/2 + у). Оператор If определяет, попадает ли какая-нибудь из точек внутрь ограничивающей рамки, и, если да, то сообщает ся о конфликте. И еще одно важное изменение. Функции FindRadius () и Distance () были изменены к следующему виду: Да! Эти функции больше не нужны, поэтому они были стерты. Неполные конфликты пикселов Вы могли обратить внимание на одну вещь: эти программы проверяют корабль относительно отдельных точек. А что, если мы хотим проверить изображение относительно другого изображения? Скажем, ядро относите чьио судна или крушение ракеты? Программа BlitzPIus обеспечивает превосходный способ это сделать. Обратите внимание, что этот раздел назван «'Неполные конфликты пикселов». Другими словами, конфликт может не быть безупречным из-за своей неполноты. Следующий раздел раскрывает тему' полных конфликтов пикселов. Существует функция, предоставляемая программой BlitzPIus, с именем ImagesOverlap () Функция определяется следующим образом:
ImagesOverlap (imagel,xl,yl,image2,х2,у2) Табл. 9.9 объясняет все параметры. Табл. 9.9. Параметры функции ImagesOverlap () Параметр Описание imagel Указатель первого изображения, которое вы хотите проверить на предмет конфликта xl Координата х первого изображения yl Координата у первого изображения image2 Указатель второго изображения. которое вы хотите проверить на предмет конфликта x2 Координата х второго изображения y2 Координата у второго изображения Теперь давайте напишем программу, использующую эту функцию. Приведенная ниже программа cJemoO9-O5.bb позволяет вам управлять кораблем. Если вы натолкнетесь на движущийся случайным образом корабль, который уже есть па экране, произойдет конфликт. Программу довольно легко понять, поэтому я собираюсь перечислить лишь некоторые важные части. Ниже приведены типы из программы и начальные значения для этих типов, .-типы ;Вражеский корабль - случайно движущийся объект на экране Type enemyship Field х,у ;Координаты х и у Field xv,yv ;Скорость Field image ;Изображение End Type ;Тип playership определяет игрока Type playership Field x,y ;Координатное положение x и у Field collisions ;Количество столкновений Field image ;Изображение игрока End Type ;Создаем врага и присваиваем его переменным значения по умолчанию Global enemy-enemyship = New enemyship enemy\x = 400
enemy\у = 200 enemy\xv = Rand(-5,5 J enemy \yv = Rand(-5,5) enemy\image = Loadimage(“enemyship.bmp") ;€оздаем игрока и присваиваем его переменным стандартные и случайные значения Global player.playership = New playership player\x = 400 player\y = 400 player\collisions = 0 player\image = Loadimage("ship.bmp") Единственное главное различие между игроком и врагом - то, что враг имеет поля скорости xv и у V. Эти значения скорости прибавляются к координатам к и у врага в каждом кадре и служат для оценки движения. Ниже приведен главный цикл. Обратите внимание, как мало в нем фактически делается. {ГЛАВНЫЙ ЦИКЛ While Not KeyDown(1) {Очищаем экран Cis {Убеждаемся, что текст появляется в верхнем левом углу Text 0,0,“Collisions: + player4collisions .-Определяем, натолкнулся ли враг па стенку TestEnemyCollisions() {Проверяем клавиатуру TestKeys() {Если игрок и враг перекрываются, наращиваем счетчик collisions и переустанавливаем игр <ка и врага в исходное положение If(ImagesOverlap(playei ige,playe player’ ;,ene y\i, age,enemy\ x,enemy\y)) playei .collisions playei . •i ^iops • player\x = 400 player\y = 4 10 enemy\x = 400 enemy\y _ 200 enemy\xv = Rand(-5,5) enemy\yv = Rand(-5,5)
Endlf ;Передвигаем врага enemyХх = enemy\х + enemy\xv enemy Ху = enemyХу + enemy \yv ;Выводим игрока и врага Drawlmage enemy\image,enemyХх,enemy Xy Drawimage playerX image,player\x,playerXy Flip ;Замедляем выполнение Delay 20 Wend ;КОНЕЦ ГЛАВНОГО ЦИКЛА Главный цикл делает следующее: выводит число конфликтов, вызывает функции TestEnemyKeys () и TestKeys (), определяет, произошли ли какие-либо конфликты, передвигает врага и выводит два корабля. Единственная строка, представляющая интерес, - проверка. If(ImagesOvelap(playerX image,playerXx.playerХу,enemyX image.enemyXx, enemy Xy)) Строка определяет, было ли пересечение изображений игрока и врага. Рис. 9.15 показывает окно программы. Рис. 9.15. Программа demo09-05.bb Вот примерно то, что касается неполных точечных столкновений. Далее вы узнаете, как определить, произошли ли столкновения фактически, используя полные точеч- ные столкновения..
Полные конфликты пикселов До сих пор мы выполняли все наши проверки конфликтов с помощью вычислений неполных конфликтов пикселов. Вы понимаете различие между неполными конфликтами пикселов и полными? Позвольте мне помочь вам понять. При использовании неполных конфликтов пикселов, каждое столкновение, которое происходит, является приблизительным. Это означает, что, в то время как столкновения фактически не произошло, обычно оно близко настолько, что это похоже на то, как будто оно было. Это создает иногда проблему, когда очевидно, что конфликта не должно было случиться. Обратившись к рис. 9.16, вы можете видеть, что столкновение ограничивающих рамок может быть чрезвычайно неточным. На этом рисунке ограничивающие рамки немного пересеклись, однако фактические объекты все еще очень далеки друг от друга. Тем не менее программа BlitzPIus обеспечивает очень легкий способ решения этой проблемы. Просто используем функцию Image sCo 11 ide () вместо ImagesOverlap(). Функция ImagesCollide () объявляется следующим образом: ImagesCollide (imagel, xl ,yl,f ramel,image2,x2 ,y2,frame2) Табл. 9.10 приводит параметры функции ImagesCollide Табл. 9.10. Параметры функции ImagesCollide () Параметр Описание iragel Указатель первого изображения, которое вы хотите проверить на предмет столкновения xl Координата х первого изображения yi Координата у первого изображения framel Кадр первого изображения, который вы хотите проверить, - кроме случая использования анимации, установите равным 0 image2 Указатель второго изображения, которое вы хотите проверить на предмет столкновения x2 Координата х второго изображения y2 Координата у второго изображения frame2 Кадр второго изображения, который вы хотите проверить. - кроме случая использования анимации, установите равным 0 Функция ImagesCollide () проверяет все непрозрачные пикселы первого изображения на конфликты с непрозрачными пикселами второго изображения. В случае конфликта выводится сообщение об этом. Однако такая проверка означает, что при
использовании функции ImagesCollide () вместо ImagesOverlap () ваша программа будет работать немного медленнее. Конфликт при использовании функции ImagesCollide () будет таким, как показан на рис. 9.17. Рис. 9.16. Проблемы с неполными Рис. 9.17. Использование функции точечным и столкновениями ImagesCo 1 li de (, Я переписал программу demo09-05.bb, чтобы сделать программу demo09-06 bb. Единственное сделанное мною изменение было в главном цикле. Я изменил /Если игрок и враг пересекаются, наращиваем переменную collisions и переустанавливаем игрока и врага If(ImagesOverlap(playerXimage,playerXx,player\y,enemyXimage,enemy, x,enemy\y)) playerXcollisions = playerXcollisions + 1 playerXx = 400 playerXy = 400 enemyXx = 400 enemy\y = 200 Endlf на «•Если игрок и враг конфликтуют, наращиваем переменную collisions и переустанавливаем игрока и врага If(ImagesCollide(player\image,playerXx,playerХу,enemy\image,enemyX x.enemyXy)) playerXcollisions - player\collisions + 1 playerxx = 400 player\y = 400
enemy\х = 40и enemy\у - Endlf Запустите программ)' и посмотрите, замечаете ли вы различия. Рис. 9.18 показывает кадр программы. Перед тем как мы закончим эту главу, я хочу лишь предостеречь вас от злоупотребления функцией ImaqesCc ide (). При чрезмерном использовании функции ImagesCollide (), ваша программа может испытать резкое замедление. Если вы не уверены, что это необходимо, в большинстве с 1учаев лучше придерживаться функции ImagesOverlap।. Рис. 9.18. Программа demo09-06.bb Итоги Здорово! Мы сейчас достигли конца главы 9, а также конца второй части книги. В этой главе вы узнали, как использовать несколько типов методов обнаружения конфликтов. Не забывайте эти методы, потому что они будут полезны в ваших программах! Бэтой главе мы раскрыли такие понятия: основы конфликтов; ограничивающие окружности: расстояние между- точками, ограничивающие рамки; неполные конфликты пикселов: полные конфликты пикселов. В следующем разделе мы узнаем о других возможностях программы BlitzPIus и будем прогрессировать в направлении нашей собственной заключительной игры. Круто, не правда ли?
Часть 3 Завершение работы Глава 10 Обработка ввода.........................277 Глава 11 Звуки и музыка..........................316 Глава 12 Искусственный разум.....................338 Глава 13 Последний рубеж: игра «Захватчики!!!»...358
ГЛАВА 1О Обработка ввода Наконец мы находимся в последней части книги! Кслда вы закончите эту часть, вы узнаете все, что нужно для создания игр. Итак, давайте начнем с поддержки интерактивности. Конечно, любая шра требует поддержки ввода. Иначе это нс игра; вместо этого получится фильм. Ишяда вы можС1с захотеть включить фильмы и другие раздеты игры, которые не воспринимают ввод пользователя, в вашу шрн например части игры, которые объясняют основную сюжетную линию без какого-тибо реального игрового действия. Основная часть вашей игры, однако, будет зависеть от ввода пользователя. Несмотря на то, что существует целый ряд способов, которыми игрок может взаимодействовать с игрой (используя игровые панели, гоночные рули и т. п.}. программа BlitzPIus сводит все варианты к трем основным: мышь, клавиатура и джойстик. Эта глава подробно описывает первые два устройства и знакомит с третьим. Устройство первое: клавиатура! Поддержка клавиатуры Вы используете клавиатуру всякий раз. когда используете ваш компьютер; да что там говорить, я использую ее, чтобы вывести эти слова прамо сейчас. Именно поэтому клавиатура предполагается в качестве стандартного источника ввода для большинства создаваемых вами игр. С ее помощью мы лучше решим сложную задачу, когда нужно выяснить, что хочет сделать игрок. До сих пор мы читали об ограниченном назначении клавиатуры: вы знаете, как установить, нажали ли пользователи клавишу Esc и некоторые другие клавиши. Следующие разделы пересматривают то, что мы знаем, и затем обучают вас чуть большему. Итак, давайте начнем с повторения функций KeyDown () и KeyHit ().
Функция KeyDown() Мы использовали эту функцию на протяжении книги, поэтому вы, скорее всего, уже знаете, что она делает. Прежде всего повторим объявление функции KeyDown (). KeyDown (scancode) Вспоминаете, да? Так или иначе, функция KeyDown!) делает проверку клавиатуры, чтобы определить, был ли введен код опроса (скан-код). Если вы не помните, позвольте мне еще раз дать определение кода опроса. Код опроса - это код, который представляет некоторую клавишу. Каждая клавиша на вашей клавиатуре представлена определенным кодом опроса. Между прочим, я мог бы время от времени использовать слова «код клавиши». Код клавиши - синоним для кода опроса - оба названия подразумевают одно и тоже. Если клавиша была нажата, функция KeyDown () возвращает 1. Если клавиша не была нажата, функция KeyDown () возвращает 0. Табл. 10.1 объясняет параметр функции KeyDown!). Табл. 10.1. Параметры функции KeyDown() Параметр Описание scancode Проверяет, была ли нажата клавиша, представленная кодом опроса Есть многочисленные коды опроса, которые встроены в программу BlitzPIus, и все они перечислены в приложении А. Однако я решил перечислить несколько из них прямо здесь, в табл. 10.2. Табл. Ю.2. Важные коды клавиш Клавиша Код опроса Esc 1 ## 1-9 2-10 #0 11 Enter 28 Левая клавиша Ctrl 29 Левая клавиша Shift 42 Spasebar 57 F10 68 Up 200 Left 203
Табл. 10.2. (окончание) Клавиша Код опроса Right 205 Down 208 Как вы, вероятно, обратили внимание, буквы на клавиатуре не были упомянуты в таблице. К сожалению, коды опроса для букв, которые поддерживает программа BlitzPIus, несколько разрозненны, а так как этих букв 26, таблица могла бы стать довольно длинной. Так или иначе, достаточно обратиться к приложению А, и вы найдете список всех кодов опроса, которые когда-либо могут понадобиться. Итак, как мы используем эту информацию? Ну, обычно мы проверяем функцию KeyDown () с помощью оператора If. Например, если бы мы хотели определить, нажимал ли пользователь клавишу Spasebar, мы должны были бы написать примерно следующее. If KeyDown(28) ;Делаем что-нибудь Endif Давайте рассмотрим подробнее этот код. Как мы знаем, оператор If выполняет следующие за ним действия, если выражение, которое оператор проверяет, истинно. Если вы помните, на компью терном языке 1 равно значению «истинно», а 0 равно значению «ложно». Функция KeyDown () возвращает I, если на клавиатуре нажата клавиша, определенная своим кодом опроса. Следовательно, операторы внутри блока If.. .Endif выполняются тогда и только тогда, когда пользователь нажал клавишу Spasebar. Давайте напишем программу на эту тему. Приведенная ниже программа damol 0-01 .bb передвигает фон открытого космоса, если пользователь нажимает клавиши Up, Left, Right, Down. ;demol0-01.bb - Демонстрирует использование функции KeyDown() {Инициализируем графический режим Graphics 800,600 ;Загружаем изображение фона backgroundimage ~ Loadlmage(“stars.bmp"I {КОНСТАНТЫ {Следующие константы используются для проверки нажатий клавиш Const ESCKEY = 1, UPKEY = 200, LEFTKEY = 203, RIGHTKEY = 205, DOWNKEY = 208 .-Переменные scrollx и scrolly определяют на сколько изображение
должно быть передвинуто scrollx = О scrolly = О ;ГЛАВНЫЙ ЦИКЛ While Not KeyDown(ESCKEY) ;Если пользователь нажимает клавишу "вверх", мы прокрутим фон вверх If KeyDown(UPKEY) scrolly = scrolly - 5 ;прокручиваем фон на 5 пикселов вверх Endlf ;Конец проверки клавиши "вверх" ;Если пользователь нажимает клавишу "влево", мы прокрутим фон влево If KeyDown(LEFTKEY) scrollx = scrollx - 5 ;прокручиваем фон на 5 пикселов влево Endlf ;Конец проверки клавиши "влево" ;Если пользователь нажимает клавишу "вправо", мы прокрутим фон вправо If KeyDown(RIGHTKEYJ scrollx = scrollx * 5 ;прокручиваем фон на 5 пикселов вправо Endlf ;Конец проверки клавиши "вправо" ;Если пользователь нажимает клавишу "вниз", мы прокрутим фон вниз If KeyDown(DOWNKEY) scrolly = scrolly + 5 ;прокручиваем фон на 5 пикселов вниз Endlf ;Конец проверки клавиши "вниз" ;Размножаем фоновое изображение на экране так, чтобы оно было похоже на реальный открытый космос Т±1eBlock backgroundimage,scrollx,scrolly ;Ждем долю секунды Delay 35 Flip Wend ;КОНЕЦ ГЛАВНОГО ЦИКЛА
Эта программа довольно хорошо демонстрирует понятие функции KeyDown () - Начнем с начала. Я создал раздел констант, описывающий клавиши, которые будут использоваться на протяжении программы. Константы, как вы, возможно, помните, являются переменными, чьи значения не могут быть изменены; следовательно, они полностью подходят для хранения значений кодов опроса. Так как код опроса клавиши никогда не меняется, вы должны всегда создавать для ваших клавиш константы. Поверьте мне: они помогут вам во многих случаях. Во-первых, вы будете знать, какие клавиши используются на протяжении программы, лишь взглянув на раздел констант. Во-вторых, следующий код: If KeyDown(DOWNKEY) ;Передвигаем игрока вниз End if понять значительно легче, чем этот If KeyDown(208) ;Передвигаем игрока вниз Endif Вы также не должны помнить коды опроса для каждой клавиши, используемой в вашей программе. Слушайте внимательно: хороший совет для запоминания - если есть способ сделать кое-что проще, сделайте так. Работать тяжело, а запоминание являет ся работой. Использование констант позволяет вам забыть об индивидуальном коде и помнить только клавишу, которую вы проверяете. Так или иначе, возвратимся к код}'. Мы переходим к главному циклу'. Как вы можете видеть, главный цикл работает лишь до тех пор. пока не нажата клавиша Esc. что видно из этой строки программы. While Not KeyDown(ESCKEY) Подобно оператору I f, оператор Whi Le работает до тех пор, пока следующие за ним утверждения истинны? или равны 1. Поскольку функция KeyDown () возвращает 0, если не нажата никакая клавиша, а оператор Not преображает 0 в 1 и 1 в 0, выражение Not KeyDown (ESCKEY) равняется 1 (истинно) до тех пор, пока не нажата клавиша. Следовательно, главный цикл выполняется только до тех пор, пока не нажата клавиша Esc Затем программа переходит к фактической проверке клавиш. Ниже приведена проверка для клавиши Up. ;Если пользователь нажал "вверх", мы прокрутим фон вверх If KeyDown(UPKEY) scrolly = scrolly - 5 ; прокручиваем фон на 5 пикселов вверх Endlf ;Конец проверки клавиши "вверх" Здесь оператор выполняется, пока нажата клавиша Up. Оператор изменяет значение переменной scrolly, и фон немного прокручивается вверх. Предыдущая проверка повторяется еще три раза для всех четырех клавиш со стрелками: Up, Down, Left и Right. На рис. 10.1 показано окно программы.
Рис. 10.1. Программа demol 0-01.bb Обратите внимание, когда карта прокручивается влево, кажется, будто вы движетесь вправо, и наоборот. То же самое происходит, когда вы прокручиваете вверх. Крутой эффект, вы не находите? Хорошо, я думаю, что вы добрались до сути. Однако я хочу подробно рассмотреть одну проблему' с использованием функции KeyDown ( Иногда, когда вы набираете что-нибудь на вашей клавиатуре, функция KeyDown' 1 полагает, что вы удержали клавиш) нажатой дольше, чем в течение одного кадра. Это случается потому, что игровой цикл выполняет итерации чрезвычайно быстро, и вы можете удерживать клавишу нажатой дольше, чем требуется для одного кадра. Конечно, вы хотите, чтобы это происходило в некоторых играх, в особенности с движением. Когда вы выполняете действие, подобное перемещению космического корабля по экрану, вы хотите, чтобы игрок moi просто держать клавишу нажатой для перемещения персонажа. Однако время от времени у вас будут с 1учаи, когда вы не хотите, чтобы пользователи могли удерживать клавиши нажатыми дольше, чем для одного кадра. Рассмотрим в качестве примера следующее: когда вы создаете игру, вы обычно хотите, чтобы игрок был способен выйти из игры, нажав клавишу Esc. Теперь, возможно, вы хотите показать кое-что на экране прежде, чем игра фактически закроется, например вы выведете на экране фразу «Нажмите любую клавишу для выхода». Затем программа ждет нажатия клавиши, используя функцию WaitKey, которая приостанавливает программу до тех пор, пока не будет нажата клавиша. Функция WaitKey не имеет никаких параметров; она только приостанавливает выполнение программы. Тем не менее есть проблема: когда игрок нажимает клавишу Esc, нажатие клавиши передается оператору WaitKey, и программа немедленно завершается. Вы должны найти способ немедленно прекратить выполнение программы после отпускания клавиши. Существует одни простой способ это сделать.
Что мы должны сделать - очистить память компьютера от информации о том, какие клавиши были нажаты. Это заставит компьютер забыть о любых ранее удерживаемых клавишах. Чтобы выполнять это действие, используем функцию FlushKeys. Объявление функции FlushKeys чрезвычайно просто: FlushKeys Параметров у функции нет - просто вызовите саму функцию. В любом случае, вызывая функцию FlushKeys, вы очищаете буфер ввода. Следовательно, информация о любой клавише, нажатой прежде, стирается. Давайте рассмотрим различие в программе-примере. Приведенная ниже демонстрационная программа demol 0-02.ЬЬ показывает, что случается, если вы не используете функцию FlushKeys. ;demol0-02,bb - Демонстрирует проблему, вызванную отсутствием функции FlushKeys Graphics 800,600 ,-Создаем изображение фона, которое будет прокручиваться backgroundimage = Loadlmage ("stars.bmp") ;Создаем переменную, которая определяет, на сколько прокручен фон scrolly = 0 ; ГЛАВНЫЙ ЦИКЛ While Not KeyDown(1) ;Прокручиваем фон немного, наращивая переменную scrolly scrolly = scrolly + 1 ;Размножаем фоновое изображение TileBlock backgroundimage,0,scrol ly ;Восстанавливаем исходное значение переменной scrolly, если число становится слишком большим If scrolly > ImageHeight(backgroundimage) scrolly = 0 Endlf ;Выводим необходимый текст Locate 0,0 ;Размещаем текст в верхнем левом углу экрана
Print "When you want to c-uii , pit Es ’ Print "Hopefully, a №>ssag₽ sf-At-i , appear you hit Esc. ;Задерживаем выполнение пр> "раммы на п лю секунды Delay 25 Flip Wend ;КОНЕЦ ГЛАВНОГО ЦИКЛА Text 0,24,"Quitting...' Flip Delay 1000 Flip На рис. 10.2 показано окно программы. Рис. 10.2. Программа demo 10-02.bb Если вы решили открыть код программы в компиляторе системы BlitzPIus и выбрали пункт меню Program ♦ Run Program (Программа ♦ Выполнить программу), вы заметите нечто странное. Вопреки тому, о чем мы здесь говорили, утверждение «Нажмите любую клавишу для выхода» будет показано на экране. Это происходит из-за диалога, который появляется, когда вы запускаете программу вне вашего компилятора. Если вы хотите видеть то, что случилось бы, если бы вы компилировали программу, используя полную версию системы BlitzPIus, добавьте команду End непосредственно после команды WaitKey
Выглядит хорошо, да? Попробуйте запустить исполняемый файл demo10-02.exe с компакт-диска. Файн demo10-03.bb - это та же самая программа с кораблем, нарисованным в центре экрана. Вы не можете двигать корабль, но он действительно хорошо выглядит! Рис. 10.3 показывает окно программы. Давайте перейдем к следующей функции ввода с клавиатуры: KeyHit (). Рис. 10.3. Программа demo 10-03.bb Функция KeyHit() Это последняя функция для ввода с клавиатуры, которую мы подробно рассмотрим. Функция KeyHit () действует весьма похоже на KeyDown (), кроме маленького, но важного различия. Принимая во внимание, что функция KeyDown () позволяетигрокуудер-живать клавишу, функция KeyHit () позволяет игроку нажимать клавишу только кратковременно. Возьмите в качестве примера программу demol0-04.bb. Эта программа выводит космический корабль на фоне, замощенном космическим рисунком. Это позволяет игроку перемещать космический корабль, используя функцию KeyHit (). Рис. 10.4 - снимок окна этой программы. В программе demo 10-04.bb вы найдете команду KeyHit (), помещенную в выражение оператора If. Ниже приведен исходный код программы, использующей функцию KeyHit(). ;Если пользователь нажимает клавишу "вверх", перемещаем игрока вверх If KeyHit(UPKEY) ;перемещаем игрока на 5 пикселов вверх
Ж Рис. 10.4. Программа demo10-04.bb Endlf ,-Если пользователь нажимает клавишу "влево" перемещаем игрока влево If KeyHit I LEFTKEY) x = перемещаем игрока на 5 пикселов влево Endlf ; Если пользователь нажимает клавишу "вправо", перемещаем игрока вправо If KeyHit(RIGHTKEY) х = х + 5 перемещаем игрока на 5 пикселов вправо Endlf ; Если пользователь нажимает клавишу "вниз*, перемещаем игрока вниз If KeyHit(DOWNKEY) у = у + 5 i перемещаем игрока на 5 пикселов вниз Endlf Между прочим, объявление функции KeyHit () точно совпадает с объявлением функции KeyDown. Табл. 10.3 рассматривает параметры функции KeyHit (). Если вы запустите программу, вы заметите, что можете двигать игроком, только нажимая клавиши со стрелками многократно. Чаще всего вы позволяете игроку передвигаться, удерживая нажатыми клавиши курсора, но иногда вы можете позволить игроку делать что-нибудь, только нажимая клавишу много раз.
Табл. 10.3. Параметры функции KeyHit () Параметр Описание scancode Код клавиши, которую вы хотите проверить при вводе Например, возьмем игру' с космической симуляцией. Мы хотим дать игроку возможность передвигаться по экрану и стрелять снарядами. Чтобы сделать это, мы позволим игроку удерживать клавиши курсора для движения, но, чтобы произвести выстрел, он должен нажать клавишу' Spasebar. Ниже приведен раздел инициализации из программы demol 0-05.bb. ;demo10-05.bb - Космический симулятор с функцией KeyHit() Graphics 800,600 {Присваиваем функции automidhandle значение "истинно" AutoMidHandle True ;Настраиваем буфер SetBuffer BackBufferО {ТИПЫ ;Тип Bullet = содержит информацию для каждого снаряда туре bullet Field х,у {координаты снаряда End Туре {Тип Player - содержит информацию о самом игроке туре player Field х,у /координаты игрока End туре {Создаем игрока и инициализируем поля Global player.player = New player player\x = 400 player\y = 500
;КОНСТАНТЫ ;Следующие константы используется для проверки нажатий клавиш Const ESCKEY 1. UPKEY 200, LEFTKEY • EIGHTKEY - 2QF DOWNKEY = 2’08 . J'PACl bXF 57 ;ИЗОБРАЖЕНИЯ playerimage • Loadlma • ! . ,bmp") Global bulletimage = Loadimage*"bulLet.„jip") backgroundimage - Loadimage("starб.bmp") ;Создаем переменную для счетчика прокрутки scrolly = 0 Раздел инициализации уже хорошо знаком вам. Раздел начипаехся с установки графического режима и функции AutoMidHandle со значением «истинно». После этого устанавливается буфер. Затем создаются типы, которые используются в программе. Первый тин - тип для снаряда bullet-. Каждый создаваемый снаряд использует этот тип. Следующий тип - тип игрока pl aver. Оба гипа bullet и player имеют сходные поля: х и у. Как вы, вероятно, догадались, ноля х и у определяют координаты для положений игрока и снаряда После создания типов программа инициализирует тип игрока. Конечно, есть только один игрок, поэтому единственный игрок и создается. Начальные координаты игрока равны 400,500, что вводит игрока в игру приблизительно в центре нижней части экрана. Следующие два раздела описываю! коне гаш ы п изображения. Константы - это коды для каждой из клавиш, используемых в программе. Программа загружает три изображения: playerimage, bulletimage я backgrrundxmage. Обратите внимание, что переменная bul let’mage - глобальная, подра!умевастся, что она используется не только в главной функции, но и в других Заключительный раздел инициализации создает переменную scrol у. Этот счетчик определяет, как далеко должен прокр* гпться фон в любой момент времени. Далее рассмотрим главный цикл. ;ГЛАВНЫЙ ЦИКЛ While Not KeyDown(ESCKEY) ;Наращиваем переменную прокрутки scrolly = scrolly + 1 ;Покрываем фон плиткой из фоновых элементов
TileBlock backgroundimage, 0,scrol ly ;Восстанавливаем исходное значение переменной прокрутки, если оно чересчур возрастает If scrolly > ImageHeight (backgroundimage) scrolly = C Srflf ; Проверяем ввод с клавиатуры TestKeys i1 ;Обновляем (передвигаем) каждый снаряд Ul rJateBullets () ;Выводим игрока Drawimage playerimage,player\х,playerXy ; Переключаем первичный и вторичный буферы -lip >nd ; КОНЕЦ ГЛАВНОГО ЦИКЛА Главный цикл начинается с заполнения фона. Главный цикл наращивает переменную scrolly и затем покрывает фон. Когда значение переменной scrolly слишком возрастает, оно восстанавливается в 0. Посте этого программа вызывает две определенные пользователем функции: TestKeysO и UpdateBullets (). Первая функция проверяет клавиатуру, чтобы определить, произошел ли какой-нибудь ввод, а вторая функция передвигает и обновляет на экране каждый снаряд. Главный цикл завершается рисованием корабля игрока и его текущего положения. Затем с помощью команды Flip переключаются первичный и вторичный буферы. Оставшаяся часть программы предоставляет листинг для двух пользовательских функции: TestKeys () HUpdateBuiiecs ' ’. Ниже приведен исходный код для функции TestKeys(). ; ФУНКЦИИ ;Функция TestKeys(j - Проверяет, какие клавиши были нажаты пользователем 10-2940
Function TestKeysO ;Если пользователь нажал клавишу "вверх", мы передвигаем игрока на 5 пикселов вверх If KeyDown(UPKEY) player \у = player \y - 5 ;передвигаем игрока на 5 пикселов вверх Endlf ;Если пользователь нажал клавишу "влево", мы передвигаем игрока на 5 пикселов влево If KeyDown(LEFTKEY) player\x = playerxx - 5 ; передвигаем игрока на 5 пикселов влево Endlf .•Если пользователь нажал клавишу "вправо", мы передвигаем игрока на 5 пикселов вправо If KeyDown(RIGHTKEY) player\x = playerxx + 5 ; передвигаем игрока на 5 пикселов вправо Endlf ;Если пользователь нажал клавишу "вниз", мы передвигаем игрока на 5 пикселов вниз If KeyDown(DOWNKEY) playerXy = player\y < 5 ,-передвигаем игрока на 5 пикселов вниз Endlf ,-Если пользователь нажал клавишу "пробел", мы создадим ;новый снаряд в текущей позиции игрока If KeyHit(SPACEBAR) bullet-bullet = New bullet bullet\x = player\x bullet\y = player\y Endlf End Function
Несмотря на то, что исходный код функции TestKeys () не является коротким, понять его довольно просто. Функция проверяет, была ли нажата какая-либо клавиша, и, если да, то что-либо меняется в программе. Табл. 10.4 объясняет, что делается при нажатии каждой клавиши. Табл. 10.4. Клавиши, используемые в программе demo 10-05.bb Клавиша Функция Up arrow Передви! аст игрока на пять пикселов вверх Left arrow Передвигает игрока на пять пикселов влево Right arrow Передвигает игрока на пять пикселов вправо Down arrow Передвигает игрока на пять пикселов вниз Spasebar Создаёт новый снаряд для вывода на экран Как вы можете видеть, клавиши курсора делают то, что вы от них и ожидаете. Единственная новая клавиша - Spasebar. Когда игрок нажимает клавишу Spasebar, создается новый снаряд. Код, который выполняет это действие, приведен ниже. If KeyHit(SPACEBAR) bullet.bullet - New bullet Заметьте, что главная функция использует функцию KeyHit () вместо KeyDown (> для создания новых снарядов. Это предотвращает игрока от удерживания клавиши Spasebar и быстрого создания сотен снарядов. Рис. 10.5 показывает, что случится, ес-чи вы замените функцию KeyDown (' функцией KeyHit I Рис. 10-5. Замена функции KeyDown () функцией KeyHit ()
Создавая новый снаряд, программа добавляет новый экземпляр типа снаряд. Если вы помните, при создании множества элементов одного и того же типа, самый последний становится активным. Таким образом, следующие строки bulletХх = playerXx bulletXy = playerXy относятся лишь к самому последнему снаряд}' (тому, который был создан несколько миллисекунд назад). Новый снаряд создается в текущей позиции игрока. Прекрасно, следующая и заключительная функция модифицирует каждый снаряд. Ниже приведен исходный код для функции Updat eBu 1 lets () ;Функция UpdateBulletsf) - Передвигает каждый снаряд по экрану Function UpdateBullets() ;Каждый снаряд передвигаем вверх на 5 пикселов. ;Если снаряд выходит за пределы экрана, удаляем его, в противном случае выводим снаряд For bullet.bullet = Each bullet bulletXy = bulletXy - 5 ;Двигаем снаряд вверх ;Если снаряд выходит за пределы экрана, удаляем его, в противном случае выводим снаряд If bulletXy < О Delete bullet Else Drawlmage bulletimage, bulletXx, bulletXy ;Выводим снаряд Endlf Next ;Переходим к следующему снаряду End Function Функция начинается с цикла For...Each, который проверяет каждый созданный снаряд- Функция перемещает каждый существующий снаряд на пять пикселов вверх. Затем программа определяет, не вышел ли снаряд за пределы экрана. Если да, то снаряд удаляется. Если нет, то снаряд выводится на экране. Функция завершается переходом к новому снаряду и возвратом в главную функцию после обработки каждого снаряда.
Вот примерно то, что касается программы demolO-05-bb. Приведенный на рис. 10.6 снимок был взят из этой программы. Рис- 10.6. Программа demo10-05.bb Кстати, попробуйте поменять функцию KeyHit () на функцию KeyDown () внутри функции TestKeys (). Серьезно, это может обеспечить вам забавное времяпрепровождение - особенно тем, кому нравятся яркие и быстро двигающиеся объекты (как и мне!) Прежде чем мы перейдем к теме ввода с помощью мыши, я хочу объяснить одну вещь, касающуюся функции KeyHit (). Функция KeyHit () предоставляет результирующее значение. Функция возвращает количество пользовательских нажатий клавиш со времени последнего своего вызова няи с начала программы, если ранее не было вызовов функции KeyHit (). Приведенная ниже демонстрационная программа demo1D-06.bb показывает, что эта функция может делать. ;demol0-06.bb - Демонстрирует возвращаемое значение функции KeyHit() Настраиваем графический режим и, чтобы можно было читать текст, выводим текст в окне Graphics 800,600,0,2 ;Начинаем вводный текст Text 0,0,"You know what's cool? Game Programming." Text 0,12,"Although Maneesh ain't that uncool, either." Flip .•Продолжаем текст
Text 0,36, "Anyway, press Esc as many times as you can in the Next 5 seconds." Text 0,48. "At the end of the program, the number of times will be printed." Flip numberofhits=0 ;Даем пользователю 5 секунд для нажатий клавиши Esc столько раз. сколько возможно timerbegin - MilliSecs() While timerbegin > MilliSecs() - 5000 If KeyHit(1) numberofhits = numberofhits *• 1 Endlf Wend ;Выводим количество нажатий клавиши Esc Text 0,60,"Esc was hit " + numberofhits * " times." Text 0,72,"You gotta love KeyHit(I huh?" Flip Задерживаемся на секунды, чтобы пользователь смог увидеть заключительный текст Delay 5000 Главная часть программы настраивает переменную numberofhits на подсчет значений KeyHit (1) и добавляет значение функции к значению numberofhits - с начала работы программы прибавляется 1 к значению numberofhits каждый раз, когда пользователь нажимает клавишу Esc. Рис. 10.7 является снимком, взятым из программы demol 0-06. bb. Итак, зто все относительно ввода с клавиатуры. Сейчас мы переходим к вводу с помощью мыши.
Рис. 10-7. Программа demol 0-06.bb Привязка мыши к экрану Поддержка мыши намного проще, чем поддержка клавиатуры. Достаточно взгляда на эти устройства: в сравнении со 105 клавишами на вашей клавиатуре на мыши есть только две или три кнопки (ну, может и больше, в зависимости от модели). Следовательно, вы должны проверять ввод максимум трех кнопок. Однако при использовании мыши вы также должны проверять координатное положение на экране В отличие от клавиатуры, которая является вездесущей, мышь существует только в некотором положении на экране. Перемещая мышь, вы перемещаете ее указатель на вашем экране (обычно обозначаемый стрелкой) и переходите туда, куда указывает ваша мышь. Это позволяет вам выбирать что-нибудь на экране, перемещая вашу мышь к расположению объекта и нажимая кнопки мыши. К сожалению, программа BlitzPIus не предоставляет указателя мыши, который позволяет вам видеть, где находится ваша мышь в текущий момент времени. Существует простой способ обойти эту проблему, и мы разберем его в следующем разделе. Отображение курсора мыши Большую часть времени работы на вашем компьютере вы используете мышь, чтобы выполнять любые действия, которые вы хотите. Рис. 10.8 показывает пример стандартного курсора мыши. Рис. 10.8. Указатель мыши
Программа BlitzPIus не имеет поддержки отображения курсоров мыши. Очевидно, для работы нам нужен курсор мыши; иначе любая программа, которая использует мышь, будет действовать примерно как программа из файла demol 0-07.bb. Рис. 10.9 показывает кадр этой программы. Рис. 10.9. Программа demo10-07.bb Если вам случится управлять вашей программой в оконном режиме (вызывая функцию Graphics ххх.ууу, zzz,2), появится стандартный указатель мыши, тот, что вы видите при использовании вашего компьютера. Довольно глупо, да? Вы не представляете, где находится мышь, поэтому программа в значительной степени бесполезна Что мы должны сделать - найти способ рисовать указатель мыши. Что мы собираемся сделать - нарисовать изображение, содержащее графическое изображение указателя мыши, в точке с координатами х иу. С чего начнем? Сначала мы сделаем изображение, которое мы хотим использовать для указателя мыши. Этот пример использует указатель, показанный на рис. 10.10. Рис. 10.10. Пример указателя мыши Я сделал указатель белым, поэтому он может быть виден на черном фоне, часто используемом в играх. Я сделал это с помощью программы Paint Shop Pro, которую вы можете найти на компакт-диске. Теперь, когда мы имеем изображение указателя мыши, все, что мы должны сделать, -вывести его в положении указателя мыши. Прежде чем мы сможем делать это, нам нужно знать, как найти текущую позицию мыши. Но, прежде чем мы сможем делать это, мы должны знать одну вещь.
При использовании изображений мыши важно, чтобы вы никогда не устанавливали точку привязки в центре; вместо этого она должна быть в верхнем левом углу изображения, потому что вам нужно, чтобы пользователь выбирал что-нибудь кончиком курсора мыши, а не центром. Задание точки привязки с использованием функции AutoMidHandle было бы затруднительным, поскольку функция устанавливает каждый разточку привязки в центр автоматически. Хитрость здесь состоит в использовании функции Handlelmage и передаче ей координат 0,0 в качестве параметров. Например, если бы у вас было изображение указателя мыши с именем mouseimage, вы должны были бы вызвать функцию Handlelmage примерно так. Handlelmage mouseimage,0,0 Между прочим, если вы не знаете, функция Handlelmage устанавливает точку привязки изображения в положение, переданное функции через координаты х и у в качестве параметров. Итак, теперь мы должны установить координатную позицию мыши. К счастью, программа BlitzPIus предоставляет две функции для этой цели. Эти две функции называются MouseX() и MouseY (). Объявления приведены. MouseX() MouseY() Что может быть проще? Во всяком случае, каждая функция возвращает координатную позицию мыши: MouseX() возвращает координату х мыши, a MouseY() возвращает координату у мыши. Хорошо, теперь давайте включим все это в программу. Ниже приведен листинг программы demo10-08.bb. К проверке мыши я добавил прокрутку фона, потому что прокрутка фонов - это круто. ;demol0-08.bb - Демонстрирует рисование указателя мыши Graphics 640,480 ;Настраиваем поверхность рисования по умолчанию во вторичном буфере SetBuffer BackBuffer() .•ИЗОБРАЖЕНИЯ ;Загружаем фон и изображение указателя мыши backgroundimage = Loadimage("stars.bmp") mouseimage = LoadImage("mouse image.bmp"I ;Устанавливаем точку привязки в верхний левый угол для изображения mouseimage Handlelmage mouseimage,0,0 ;Создаем счетчик для прокрутки фона scrolly = 0
;ГЛАВНЫЙ ЦИКЛ While Not KeyDown(l) ;Немного прокручиваем фон, наращивая счетчик scrolLy scrolly = scrolly + 1 ;Закладываем фон плиткой из элементов фона TileBlock backgroundimage.0.scrollv восстанавливаем исходное значение счетчика scrolly, если число слишком возрастает If scrolly > ImageHeight(backgroundimage) scrolly = 0 Endlf {Выводим текст Text 0,0,"Mouse is easier to find now, huh" {Выводим координаты X и Y Text 0,12,"MouseX: " + MouseX() Text 0,24,"MouseY: * + MouseY() {Выводим изображение курсора мыши Drawimage mouseimage,MouseX(),MouseY() {Замедляем выполнение Delay 20 {Переключаем первичный и вторичный буферы Flip Wend {КОНЕЦ ГЛАВНОГО ЦИКЛА Не так уж страшно, да’ Строка кода, содержащая вызов функции Drawimage, выводит курспр в точке текущего положения мыши, которое передается функциями MouseX () и MouseY (). На рис. 10.11 показано окно программы. Мы достигли превосходного результата! Теперь мы знаем, как отслеживать положение указателя мыши и выводить объект, представляющий его позицию. MouseX () и MouseY () - две важные функции для определения положения мыши. Мы также освоили, как изменить изображение указателя мыши г помощью функции Handlelmage. Далее мы собираемся узнать, как отслеживать нажатия кнопок мыши.
Рис. 10.11. Программа demolO-Oe.bb Что это было? Обработка нажатий кнопок мыши Подобно клавиатуре, мышь также имеет кнопки, которыми вы можете щелкать на объектах экрана. Мышь можно использовать для многих действий во всех ваших играх. Например, у вас есть игра с боковой прокруткой, подобная Super Mario Brothers. Вы могли бы позволить игрок}' двигаться по экрану, щелкая левой кнопкой мыши, подскакивать, используя правую кнопку. Рис. 10.12 показывает типичную мышь. Обратите внимание, что мышь имеет колесико. Многие мыши имеют колесики, и мы узнаем, как использовать его так же, как другие две кнопки мыши. В любом случае, обратимся к функциям.
Функция MouseDown() Программа BlitzPIus предлагает функции для ввода с помощью мыши, которые подобны функциям ввода с клавиатуры. Функция, о которой мы узнаем в этом разделе, MouseDown (), действует точно так же, как ее двойник для клавиатуры KeyDown (). Функция MouseDown () объявляется следующим образом. MouseDown(button) Параметр button - кнопка, которую вы проверяете: левая кнопка мыши, правая кнопка мыши или средняя кнопка мыши. Табл. 10.5 перечне ыет все возможные значения для параметра button. Табл. 10.5. Варианты кнопок для функции Moi*seD<i?n Кнопка Код кнопки Левая кнопка мыши 1 Правая кнопка мыши 2 Средняя кнопка мыши 3 Легко, да? Как мы можем использовать функцию MouseDown () ? Вот, например, для определения, щелкал ли пользователь левой кнопкой мыши, вы должны сделать примерно следующее. If MouseDown(1) ;производим действия Endif Все, что вы хотите, чтобы случилось, когда нажата левая кнопка мыши, размещается между операторами If и Endif. Функция MouseDown (} возвращает значение «истинно» (1), если кнопка мыши была нажата, и «ложно» (0), если кнопка мыши не была нажата. Я пренебрег написанием программы-примера для функции MouseDown () прямо сейчас; вместо этого я буду использовать эту функцию в типовой программе со следующей функцией: MouseHit (). Функция MouseHit() Держу пари, вы можете допщаться о различии между этой функцией и функцией MouseDown(), да? Тогда как при использовании функции MouseDown)) вы можете удерживать кнопку нажатой, при использовании функции MouseHit () вы должны щелкать кнопкой мыши много раз, вызывая действие. Это различие - такое же. как в KeyDown () в сравнении с KeyHit (), функциях ввода с клавиатуры. Точно так же, как функция KeyHit (), функция MouseHit () записыва ет число раз, которое вы щелкнули кнопкой.
Функция MouseHit () объявляется следующим образом: MouseHit(button) Кнопка может быть любой из списка в табл. 10.5. Так или иначе, давайте перепишем программу demol 0-05bb чтобы использовать ввод с помощью мыши. Вместо того чтобы использовать клавиши для перемещения корабля игрока, корабль размещен в точке с координатами курсора мыши. В этой программе мы не нуждаемся в курсоре мыши, потому что корабль служит признаком позиции мыши. Было изменено также назначение кнопок мыши. При нажатии левой кнопки мыши все еще создается снаряд, а удерживание правой кнопки мыши создает лазер. Попробуйте. Большая часть программы изменилась, поэтому я собираюсь копировать исходный текст раздел за разделом. Начинаем с раздела инициализации из файла demo 10-09. bb. ;demol0-09.bb - Космический симулятор с функциями MouseDown() и KeyDown() Graphics 800,600 ;Задание функции Automidhandle параметра "истинно" AutoMidHandle True ;Настройка буфера SetBuffer BackBuffer() ;ТИПЫ ;Тип снаряда bullet - содержит информацию о каждом снаряде туре bullet Field х, у ;координаты снаряда Field bullettype ;тип оружия LASER или NORMALBULLET (см.константы) End Type ;Тип игрока player - содержит информацию о текущем игроке Type player Field х,у /координаты игрока End.Туре
;Создаем экземпляр игрока и инициализируем поля Global player.player = New player player\x = 400 player\y = 500 ;КОНСТАНТЫ ;Следующие константы используются для проверки нажатий клавиш (мыши и клавиатуры) Const ESCKEY = L, LEFTMOUSEBUTTON = 1, RIGHTMOUSEBUTTON = 2 ;Следующие константы используются для оружия ;константа BULLET для обычного снаряда, константа LASER для лазера Const NORMALBULLET = 1. LASER = 2 ;ИЗОБРАЖЕНИЯ playerimage = Loadimage("ship.bmp") Global bulletimage = Loadimage("bullet.bmp"' Global laserimage = Loadimage("laser.bmp") backgroundimage = Loadimage("stars.bmp") Handlelmage laserimage, Imagewidth(laserimage)/2, ImageHeight(laserimage) ;ПЕРЕМЕННЫЕ ;Создаем счетчик для прокрутки scrolly = 0 ;Количество нажатий левой и правой кнопок мыши Global leftmouseclicks = 0 Global rightmouseclicks = 0 Хорошо, давайте подробно разберем этот раздел. Программа начинается так же. как программа demo 10-09.bb, с установки графического режима, создания вторичного буфера и задания функции AutoMidHandle параметра «истинно». В следующей часта немного изменились типы.
Описание типа снаряда похоже на приведенный ниже фрагмент из программы demo10-05.bb: Type bullet Field х.у ;координаты снаряда End Туре Обратите внимание на новое поле: bullet type. Этот тип определяет, является оружие обычным снарядом или лазером. Мы присваиваем значение этому полю во время создания снаряда в зависимости от того, щелкает игрок левой или правой кнопкой мыши. Следующее важное изменение в программе находится в разделе констант. Как вы можете видеть, мы удалили константы для всех кодов клавиш кроме Esc. Константа Esc остается, поскольку мы используем ее, чтобы определить, вышла ли программа из главного цикла. В секции присвоения кодов кнопок мы создали набор новых констант. Первые две новые константы - LEFTMOUSEBUTTON и RIGHTMOUSEBUTTON. Позже в программе эти две константы используются в проверках функций MouseHit () и MouseDown () - они сообщают программе, какими кнопками мыши щелкали. Другие две константы NORMALBULLET и LASER используются в поле bullettype типа оружия. Если значение поля bullet type равно NORMALBULLET, снаряды - обычные боеприпасы. Если значение поля bullettype -LASER, то оружие - лазер. Мы загрузили новое изображение laserimage, которое является изображением каждого лазерного луча, выпущенного при щелчке Рис. 10.13. правой кнопкой мыши. Рис. 10.13 показывает, как выглядит лазер. Изображение Это прямая линия, выходящая из середины корабля. лазера Лазерный луч очень длинный, поэтому он простирается от одного края экрана к другому. Таким образом, длина лазерного луча - это высота экрава. Затем мы устанавливаем точку привязки в центр основания изображения строкой кода Handlelmage laserimage, ImageWidth(laserimage)/2, ImageHeight(laser image) Эта строка, возможно, немного сложна для понимания, поэтому рассмотрим ее подробно. Прежде всего, мы должны знать, что делает функция Handlelmage. Функция Handlelmage позволяет вам выбрать место для точки привязки. Параметр AutoMidHandle автоматически назначает расположение точки привязки любого изображения непосредственно в центре. Что делает точка привязки? Когда вы перемещаете изображение, оно перемещается с помощью захвата его точки привязки. Представьте себе поднятие игральной карты. Если вы поднимаете карту точно за центр, обратите внимание, что карта расширяется во всех направлениях от вашего пальца. Это действие, выполняемое функцией AutoMidHandle. См. рис. 10.14 в качестве примера.
Как видно на рис. 10.14, точка привязки, на которую указывает ваш курсор, находится непосредственно в центре карты. Точки карты простираются во всех направлениях от центральном точки. Когда вы перемещаете вашу руку, карта остается захваченной за центр, и таким образом края карты все так же отстоят от точки 0,0. Однако при использовании изображения лазера мы хотим, чтобы была привязка к основанию изображения. Мы сделаем лазер простирающимся от передней стороны космического корабля игрока к верхнему краю экрана, и, поскольку луч должен исходить непосредственно от игрока, мы должны установить маркер на нижний край лазера. Рис .10.15 показывает, как примерно выглядела бы установка маркера на нижний край игральной карты. Handling Point (0,0) Handling Point (0,0) Рис. 10.14. Точка привязки в центре игральной карты Рис. 10-15- Точка привязки внизу карты Функция Handlelmage позволяет вам устанавливать маркер изображения лазера в точку с теми координатами, которые вы хотите задать. В строке Handlelmage laserimage, ImageWidth(laserimage)/2. ImageHeight(laserimage) точка привязки установлена в точку с координатами Imagewidth (laserimage) /2, ImageHeight (laserimage). Что это означает? Координата х точки привязки равна ImageWidth (laserimage) /2. Это половина ширины изображения, что соответствует центру изображения. Координата у, равная ImageHeight(laserimage), помещает точку привязки внизу изображения- См. рис. 10.16 для справки. Итак, надеюсь, мы теперь понимаем, что такое точка привязки изображения лазера Переходя к разделу инициализации программы, мы добрались до двух новых переменных leftmouseclicks и rightmouseclicks. Эти две переменные записывают, сколько раз щелкала каждая из соответствующих им кнопок.
Загсм - главный цикл. Просмотрите код. ;ГЛАВНЫЙ ЦИКЛ While Not KeyDown(ESCKEY) /Наращиваем переменную прокрутки scrolly = scrolly * 1 ;Замостим фон TileBlock backgroundimage, 0, scrolly /Выводим текст Text 0,0, "Playei X: * + MouseX <' Text 0,12,"Player • • + MouseY() Text 0,24,"Number E times left mo :se button was nit: • • 1ef tmouseclicks Text 0,36,"Number or times right mouse button was hit: • * rightmouseclicks /Восстанавливаем исходное значение переменной прокрутки, когда оно слишком возрастает If scrolly > ImageHeight(backgroundimage) scrolly = 0
Endlf ;Проверяем кнопки мыши TestMouse() ;Обновляем (передвигаем) каждый снаряд UpdateBullets() ;Выводим игрока Drawlinage player image, player\х,player \y ;Переключаем первичный и вторичный буферы Flip Wend ;КОНЕЦ ГЛАВНОГО ЦИКЛА Этот цикл почти точно такой же, как в программе demol0-05.bb, за исключением двух изменений. Мы добавили немного текста на экран, который сообщает игрокам их положение и количество щелчков левой и правой кнопками мыши. Второе изменение - замена имен, функция TestKeys () была заменена на функцию TestMouse (), которую мы исследуем далее ;ФУНКЦИИ ;Функция TestMouse() - Проверяет, какие кнопки мыши были нажаты и где находится игрок Function TestMouse() ;Устанавливаем игрока в положение курсора мыши player\x = MouseX() player\y = MouseYО ;Если игрок щелкнул левой кнопкой мыши, создаем снаряд If MouseHi t(LEFTMOUSEBUTTON) bullet.bullet = New bullet ;создаем снаряд bullet\x = playerXx ;помещаем снаряд в координату х игрока bulletXy = player\у ;помещаем снаряд в координату у игрока
bullet\bullet type = NORMALBULLET ;делаем выстрел обычным ;наращиваем счетчик щелчков левой кнопкой мыши leftmouseclicks » leftmouseclicks т 1 Endlf ; Если игрок щелкнул правой кнопкой мыши, создаем лазер If MouseDown(RIGHTMOUSEBUTTON) bullet.bullet = New bullet ;создаем лазер bullet\x = player\x ;помещаем лазер в координату х игрока bullet\у = player\y ;помещаем лазер в координату у игрока bullet\bullettype = LASER ;делаем выстрел лазером ;прибавляем сумму щелчков правой кнопкой мыши со времени последнего кадра rightmouseclicks = rightmouseclicks + MouseHit(RIGHTMOUSEBUTTON) Endlf End Function Ну что, большое различие? Эта функция претерпела серьезную перестройку, потому что вы больше не используете клавиатуру. Функция иачннается с назначения координатам корабля значений координат мыши с помощью функций MouseX{) и MouseY (). Если вы помните, функции MouseX () и MouseY (I возвращают координаты мыши в данный момент времени. После этого мы определяем, щелкнул ли игрок какой-либо кнопкой мыши. Если так, программа создает снаряд. Проверки для левой и правой кнопок мыши начинаются одинаковым образом: новый снаряд создается с координатами игрока. Это показывает снаряд непосредственно возле игрока, создавая иллюзию, что корабль игрока действительно выпустил заряд. Следующая строка отмечает различие между проверкой левой кнопки мыши и проверкой правой кнопки мыши. Если игрок щелкнул левой кнопкой мыши, тип оружия устанавливается в значение NORMALBULLET, тогда как, если игрок щелкал правой кнопкой мыши, тип оружия устанавливается в значение LASER. Последний раздел каждой проверки увеличивает счетчик щелчков левой кнопкой мыши или счетчик щелчков правой кнопкой мыши, в зависимости от того, какой кнопкой щелкали. Вы можете видеть, что действия, предпринятые, чтобы увеличить счетчик, различны в каждом испытании, и если вам нужна помощь для понимания почему, см. сопроводительное примечание.
Посмотрите на конец каждой проверки кнопок мыши, как для левой кнопки, так и для правой кнопки: вы обратили внимание, насколько они различны? Обе строки кода наращивают свои счетчики, кот орые уточняют, сколько раз были выпущены лазерные лучи или снаряды, однако они делают это различным образом. Первая проверка MouseHit (LEFTMOUSEBUTTON) прибавляет 1 непосредственно к счетчику щелчков левой кнопкой мыши, в то время как вторая проверка MouseDown(Rightmousebutton) прибавляет результат функции MouseHit (RIGHTMOUSEBUTTON) к счетчику щелчков правой кнопкой. Почему мы не можем просто добавить единицу к счетчику щелчков правой кнопкой мыши? Так, в первой проверке мы использовали функцию MouseHit () для определения, был ли щелчок левой кнопкой мыши Если вы помните, функция MouseHit () всегда возвращает 1, если был однократный щелчок кнопкой (здесь левой кнопкой мыши). Поскольку мы вызываем функцию MouseHit (), мы знаем наверняка, что был одиночный щелчок кнопкой, поэтому мы прибавляем 1 к счетчику. С другой стороны, мы используем функцию MouseDown (} для проверки правой кнопки мыши. Функция MouseDown возвращает 1 до тех пор, пока кнопка удерживается нажатой, а не только при одиночном нажатии кнопки (как эго делает функция MouseHit ()). Другими словами, новый снаряд может быть создан, даже если кнопка не была отпущена и затем не была нажета снова новый снаряд создается именно потому, что кнопка удерживается нажатой. Вследствие этого факта, мы прибавляем значение MouseHit (rightmousebutton) к счетчику, который будет возрастать на единицу, если правая кнопка отпущена и затем опять нажата, и на ноль, если она просто удерживается нажатой. Итак, переходим к последней функции: UpdateBullets () - ;Функция UpdateBullets() - Перемещает каждый снаряд по экрану Function UpdateBullets() Каждый снаряд перемещаем на 5 пикселов вверх. Если снаряд ;выходит за пределы экрана, удаляем его, иначе выводим его For bullet.bullet = Each bullet ;Если снаряд выходит за пределы экрана, удаляем его, ;иначе выводим его на экран. ;Выводим изображение лазера или снаряда, в зависимости от выбранного типа If bulletXy < п Delete bullet Elself bullet bullettype = NORMALBULLET bulletXy = bullet- 5 /Двигаем снаряд вверх Drawimage bulletimage,bullet\x,bulletXy ;Выводим снаряд Elself bullet\bullettype = LASER If playerXx r> bullet\x Delete bullet Else
Drawlmage laserimage,bullet\x,bullet \y ;Выводим лазер Endlf Endlf Next ;переходим к следуют-иу снаряду End Function Сначала эта функция определяет, находится ли снаряд на экране или он вышел за пределы экрана. Если координата у снаряда меньше 0, снаряд выходит за пределы экрана. Когда это случается, снаряд удаляется с помощью функции Delete. Если снаряд не был удален, функция проверяет тип оружия, используя переменную I .llet .bullettype. Если оружие - обычный снаряд, он перемещается на пять пикселов вверх и выводится в точке с надлежащими координатами. Если оружие - лазер, программа должна сделать еще несколько проверок. Поскольку лазер везде следует за игроком и простирается до края экрана, мы не хотим, чтобы координата х игрока была отличной от координаты х лазера. Поэтому мы сравниваем координату' х игрока с координатой х лазера, используя операт ор <> (не равно). Если значения переменных player х и bullet\х не равны друг другу, лазер удаляется. Если координата х игрока и координата х лазера равны, лазер выводится на экране. Вот и все, что касается программы demo 10-09. bb. На рис. 10.17 показано окно программы. Рис. 10.17. Программа demol 0-09. bb
Хорошо, я нздеюсь, что вы теперь поняли основы ввода мышью. Прежде чем мы перейдем к вводу джойстиком, я хочу детально рассмотреть среднее колесико мыши. Среднее колесико мыши Как вы знаете, многие мыши имеют среднюю кнопку в дополнение к обычным правой и левой кнопкам. Часто средняя кнопка мыши - прокручивающееся колесико, которое может использоваться в программах типа Internet Explorer для прокрутки вверх и вниз. Программа BlitzPIus обеспечивает поддержку для среднего колесика мыши, как для щелчка, так и для прокрутки. Вы уже знаете, как определить, был ли щелчок средней кнопкой мыши. Чтобы сделать это, лишь вызовите функцию MouseDown (I со значением параметра 3. Вы должны были бы написать в вашей программе примерно следующее: If MouseHit(3) ;выполняем действия Endlf Не так уж и сложно, да? Определить, было ли колесико мыши прокручено, почти так же просто. Помните начало раздела о вводе мышью, где мы использовали две функции MouseX (' и MouseY () ? Если вы помните, функции MouseX () и MouseY () давали координатное положение мыши. Программа BlitzPIus предлагает функцию MouseZ (), которая проверяет колесико мыши, чтобы определить, прокрутилось ли оно. Программа BlitzPIus на компакт-диске не позволяет попользовать функцию MouseZ (). Это очень неудачно, потому что вы не можете определить, было ли колесико мыши прокручено в вашей демонстрационной программе. Однако полная версия программы BlitzPIus действительно позволяет использовать функцию MouseZ (). Вы можете купить полную версию на сайте http://www.blitzbaslc.com. Программа demol 0-10.bb не будет работать, если вы попробуете компилировать ее в среде BlitzPIus. Вы получите ошибку «Function not Found» (Функция не найдена). Исполняемый файл, однако работает, поэтому используйте его. Эта программа показывает вам, как вы можете использовать функцию MouseZ (), если вы купили полную версию программу BlitzPIus. Значение функции MouseZ () равно 0, когда начинается ваша программа. Если вы прокручиваете колесико мыши в направлении от себя (вверх), значение функции MouseZ () возрастает. Если вы прокручиваете колесико мыши к себе (вниз), значение функции MouseZ () убывает. Рис. 10.18 иллюстрирует, что означает прокрутка мыши «вверх» и «вниз». Так или иначе, чтобы проверить, для чего нужна функция MouseZ (), просто вызовите ее. Она не требует никаких параметров. Поэтому исследуем программу demo 10-10.bb в качестве примера. Эта программа прокручивает фон на 20 пикселов, когда вы поворачиваете колесико мыши.
/demolO-lO.bb - Демонстрирует использование функции MouseZ() ;Настраиваем графический режим и буфер Graphics 800,600 SetBuffer BackBuffer() /Загружаем изображения backgroundimage = Loadimage("stars.bmp"' Рис. 10.18. Прокручивание колесика мыши shipimage = Loadimage ("ship.bmp") /ГЛАВНЫЙ ЦИКЛ While Not KeyDown(l) /Прокручиваем фон на 20 пикселов при каждом повороте колеса мыши scrolly = MouseZО * 20 /Заполняем фон TileBlock backgroundimage,0,scrol ly /Выводим игрока Drawimage ship image,MouseX(),MouseY() Flip Wend /КОНЕЦ ГЛАВНОГО ЦИКЛА Обратите внимание на то, что, к сожалению, демонстрационная версия не поддерживает функцию MouseZ (), вероятно из-за некоторой ошибки в коде. Однако реальная программа действительно поддерживает функцию MouseZ (). Если вы попробуете откомпилировать этот код в демонстрационной версии программы BlitzPIus, он не будет работать правильно, но в полной версии он будет работать. Как вы можете видеть, программа устанавливает для переменной sc i oily /начеиие MouseZ () * 20. Умножение MouseZ () на 20 заставляет переменную прокрутки
изменяться на 20 пикселов вместе с каждым движением колеса мыши. Это означает, что фон прокрутится быстрее и легче. Попробуйте изменить 20 па друтое число и посмотрите, что произойдет. Если вы уменьшите 20, вы заметите, что фон прокручивается намного медленнее, тогда как увеличение делает фоновую прокрутку быстрее. Приведенный ниже рис. 10.19 показывает кадр программы demolO-10.bb Рис. 10.19. Программа demolO-10. bb Прежде чем мы перейдем к следующему разделу, рассмотрим несколько разных функций мыши, которые вы должны знать. Программа BlitzPIus предоставляет функцию FlushMousel), подобную функции FlushKeyst). Эта функция очищает память компьютера от информации о нажатых кнопках мыши. Функция WaxtMouse() - еще одна функция, которая имеет двойника для клавиатуры — WaitKeys (' Эта функция ждет нажатия кнопки мыши игроком перед возобновлением выполнения программы. Функция GetMouse () может использоваться, если вы хотите получить кнопку, которую игрок нажимал, но нс знаете, какая это будет кнопка. Функция GetMouse () не имеет никаких параме тров, но, если кнопка нажата, функция возвращает номер нажатой кнопки. Поэтому; если была нажата левая кнопка мыши, возвращается 1, если была нажата правая кнопка мыши, возвращается 2; и если было нажато колесико мыши, возвращается 3. Наконец, программа BlitzPIus предоставляет функцию MoveMouseO. Эта функция объявляется следующим образом: MoveMouse х.у
Эта функция перемещает мышь в координаты, которые вы передадите функции в качестве параметров. Поэтому, если вы набрали MoveMouse (0,0), мышь перемещается в верхний левый угол экрана. Вот это в основном все, что касается ввода мышью. Следующая тема: ввод информации с помощью джойстика. Обработка ввода джойстика Как вы знаете, мыши и клавиатуры - самые обычные устройства ввода данных для игр. Однако есть много других устройств, которые используются, только гораздо реже. Игровые клавиатуры и джойстики используются для многих полетных и гоночных видеоигр. Программа BlitzPIus обеспечивает поддержку джойстиков, оченьпохожую на поддержку клавиатур и мышей. Однако я не хочу переходить к теме джойстиков полностью, потому что тема является большой и нечасто используемой. Тем не менее, если вы хотите узнать о джойстиках, я составил список (см. табл. 10.6) самых полезных функций, их параметров и возвращаемых ими значений. Табл. 10.6. Функции для джойстика Функция Описание JoyType([port. Эта функция определяет, связан ли джойстик в настоящее время с данным портом. Функция возвращает 0, если нет никакого джойстика, 1, если джойстик является цифровым, и 2. если джойстик - аналоговый JcyHit(button, [port]) Эта функция возвращае т количество нажатий указанной кнопки джойстика GetJoy([port] Эта функция возвращает номер кнопки, которая была нажата WaitJoy([port] Эта функция останавливает выполнение программы, пока не будет нажата кнопка джойстика JoyX#([port] Эта функция возвращает координату х джойстика. Возвращаемое значение может располагаться в интервале от -1 (крайнее левое) до 1 (крайнее правое), с 0, находящимся непосредственно в центре JoyY#([port] ' Эта функция возвращает координату у джойстика. Возвращаемое значение может располагаться в интервале от -1 (крайнее верхнее) до 1 (крайнее нижнее), с 0, находящимся непосредственно в центре
Табл. Ю.6. (окончание) Функция Описание JoyZ#([port]) Эта функция возвращает значение на оси z джойстика. Обычно, ось г - это кнопка. Значение может располагаться в диапазоне от Одо 1 (не достигая максимального значения) JoyU#([port] В зависимости от джойстика эта функция может использоваться, чтобы обнаружить колесико, спусковой крючок или кнопку; называется u-осыо. Эта функция возвращает значение между -1 и 1 JoyV#([port]) В зависимости от джойстика эта функция может использоваться, чтобы обнаружить колесико, спусковой крючок или кнопку, которые являются отличными от тех устройств, что связаны с функцией JoyU#; это называется v-осью. Эта функция возвращает значение между -1 и 1 FlushJoy Эта функция сбрасывает все команды из очереди джойстика в значительной степени подобно функции FlushKeys () Вы, вероятно, обратили внимание, что во всех функциях используются только два параметра. Параметр [port ] используется только, если есть более одного джойстика, связанного с компьютером. Порт - номер разъема на вашем компьютере, к которому подключен джойстик. Почти всегда оставляйте место параметра [port] пустым. Параметр but t on относится к кнопкам на вашем джойстике. Первая кнопка джойстика имеет номер один, а последняя кнопка зависит от модели контроллера. Обычно, бывает не менее трех киопок. Вы можете вызвать функцию JoyX# (а также JoyY#, Z#, U# и V#) примерно так: returnvalue = JoyX#() Переменная returnvalue будет теперь содержать значение между -1 и 1. Итоги Да, глава была длинной. Хотелось бы надеяться, вы теперь понимаете основы ввода. Ввод посредством мыши, клавиатуры и джойстика используется во всех играх, и крайне важно, чтобы у вас было твердое понимание этих концепций. Эта глава охватила следующие понятия: обеспечения ввода с клавиатуры с помощью функций KeyDown () и KeyHit (); отображение курсора мыши; обработка нажатий кнопок мыши с помощью функций MouseDown () и MouseHit();
использование среднего колесика мыши; обработка ввода джойстика Даже при том, что программа BlitzPIus обеспечивает вас большим количеством функций (я даже не буду их повторять), только немногие абсолютно необходимы. Почти наверняка вы будете использовать функции KeyDown () или MouseDown () в ваших программах. Функции типа FlushKeyS О — только иногда. Так или иначе, приготовьтесь к следующему уроку. Мы переходим к кое-чему действительно забавному: звуку. Вы узнаете, как использовать звук в ваших играх, чтобы создать эффект действия!
ГЛАВА 1 1 Звуки и музыка Если вы играли в какие-либо игры в последнее время, вы знаете разницу; которую может создавать звук. Звук не только создает ощущение участия в игре, но и дает возможность понять, что происходит вокруг участника. Более новые игры даже поддерживают многоканальный зщ к, что позволяет слышать звук более чем в одном направлении. Для поддержки многоканального звука необходимо более двух звуковых колонок (многие системы имеют целых пять, а инсида даже более, расположенных вокруг игрока). Все эти звуковые колонки, или каналы, могут издавать различные звуки в одно и то же время. Вы можете слышать кого-то, приближающегося из-за вашей спины (ил колонок, которые расположены позади вас), однако, ко1да вы поворачиваетесь вокруг, чтобы столкнуться с ним лицом к лицу, шаги доносятся из ваших передних колонок! Программа BlitzPIus обеспечивает хорошую поддержку для звука и музыки, и эта глава научит вас, как использовать и то и другое. Сначала звук! Звук Во введении я сказал, что в этой главе мы будем осваивать и звуки, и музыку, и вы могли бы подумать, что это одно и то же. Нет! Программа BlitzPIus относится к звукам и музыке, как к двум различным сущностям. В отличие от музыки, звук проигрывается динамически. Что это означает? Игра не проигрывает звуковой файл снова и снова. Напротив, звук проигрывается только в определенное время. Возьмем, например, огнестрельное оружие. Вы не желаете, чтобы выстрел звучал снова и снова; вы хотите. чтобы звук выстрела был только тогда, когда оружие действительно стреляет. Для этой книги мы будем использовать формат .WAV для звуковых файлов. Вы можете спросить, что такое формат файла .WAV' Этот файловый формат представляет звук на компьютере. Этот звуковой формат не искажает качество звука подобно некоторым другим форматам файла, которые мы обсудим позже, поэтому звук
из файла .WAV «чище» Я поместил ряд бесплатных звуковых файлов на компакт-диске, больше всего в формате .WAV, которые вы можете использовать в ваших программах. Кстати, WAV расшифровывается как «WAVeform audio file» (Волновой аудиофайл). Во всяком случае, давайте начнем. Сначала мы должны узнать, как загрузить звуки. Загрузка звуков Помните изображения? Было чрезвычайно просто загрузить их, правда? Мы только использовали функцию Loadlmage (). Программа BlitzPIus делает загрузку звуков такой же простой: мы используем функцию LoadSound ( Функция LoadSound () объявляется следующим образом LoadSound (filenameS) Табл. 11.1 описывает параметр для функции LoadSound () . Табл. 11.1. Параметр функции LoadSound() Параметр Описание filename? Имя звукового файла, который вы хотит е загрузить Вы загружаете звуки точно также, как изображения. soundfile = LoadSound("soundfile.wav") Замените имена soundfile и soundfile.wav па название переменной и звуковой файл, который вы хотите загрузить в вашу программу. С этого места я использую определенный стиль для названий моих звуковых файлов. Всякий раз, когда я загружаю их, я вызываю переменную, которая содержит имя файла xxxxxsound.wav, где ххххх описывает звук. Например, чтобы загрузить звук лазера, я вызвал бы звук lasersound и загрузил бы его следующим образом: laserso’und = LoadSound Пэдег чау'. Между прочим, есть кое-что еще, что вам нужно узнать. Имя переменной, которая содержит звук (в предыдущем примере soundf lie) называется указателем. Почему это называется указателем? Как правило, вы используете указатель переменной как адрес или ссылку на объект. Поэтому он помогает вам обратиться к чему-нибудь - в этом случае к звуковому файлу. Когда вы хотите произвести какие-либо действия со звуковым файлом, для доступа к нему необходим указатель; это похоже на ключ к замку. Ну, хорошо, я надеюсь, что до сих пор загрузка звуков вам понятна. Прежде чем я буду двигаться дальше, я хочу уточнить одну маленькую функцию. Эта функция -FreeSound. Функция FreeSound удаляе т звук из памяти. После удаления звука вы можете использовать LoadSound () и загружать другой звуковой файл с тем же самым указателем:. Ниже приведено объявление для функции FreeSound. FreeSound sound_variable Как вы уже догадались, табл. 11.2 разъясняет параметр.
Табл. 11.2. Параметр функции FreeSound Параметр Описание md_vaz-fable Указатель звукового файла, который вы хотите удалить из памяти Вы могли бы использовать эту функцию в игре с многочисленными уровнями, возможно, потому что вы используете различные звуки при переходе от одного уровня к следующему. Уда_шв звук, вы свободно можете загрузить другой звук с тем же самым указателем, который может затем использоваться вместо прежнего звука. Давайте рассмотрим еще один пример того, как вы могли бы использовать функцию FreeSound. Скажем, вы пишете игру, которая использует пистолет. Возможно, звук, который воспроизводится, когда пистолет стреляет, вы назвали playergunsound. Теперь вообразите, что игрок получает глушитель, который может размещаться на пистолете. Выстрел из пистолета с глушите гем, конечно, звучит совсем по-другому, чем выстрел из пистолета без глушителя. Самый простой способ сделать это состоит в том, чтобы удалить старый громкий звук из памяти и загрузить приглушенный звук с тем же указателем. Тем самым игра продолжит проигрывать звук, который загружен в переменную playergunsound, даже если звуковой файл изменился. Взгляните на рис. 11.1. В первом кадре переменная player gunsound содержит звук gunshot .wav. Этот звуковой файл и есть звук обычного выстрела. Во втором кадре была вызвана функция FreeSound, и переменная playergunsound не содержит ничего. В грстьем и заключительном кадре функция LoadSound () была вызвана снова, и переменная playergunsound теперь содержит звук silencer .wav. I К Ш gunshot wav FreeSoundj) LoadSound!), sllencer.wav ptayergtrsouxi ptayeigirisomd Ftayeri-^nsourid Рис. 11.6. Каналы и панорамирование Попробуем включить это в код программы. Ваша программа могла бы выглядеть примерно так. ;Загружаем начальный звук для обычного выстрела Global playergunsound = LoadSound ("gunshot.wav") iНачинаем ГЛАВНЫЙ ЦИКЛ While Not KeyDown(1) If GunshotOccurredO PlayGunshotSound() Endif
If SilencerAttachedO SwitchSoundFiles() Endif Wend ;КОНЕЦ ГЛАВНОГО ЦИКЛА ;Функция SwitchSoundFilesО переключает файл со звуком обычного выстрела ;на приглушенный звук выстрела Function SwitchSoundFiles() FreeSound playergunsound playergunsound _ loadsound("silencer. wav") End Function Этот пример кода, конечно, не будет работать, если вы скопируете его прямо в среду программы BlitzPIus. Большинство используемых функций являются пользовательскими, которые мы еще не создали, но вы можете, вероятно, угадать, что они делают. Функция GunShotOccurred () возвратила бы значение «истинно», если бы выстрел произошел; функция PlayGunshotSound () озвучила бы файл playergunsound; функция SilencerAttachedO возвратила бы значение «истинно», если бы глушитель был присоединен к пистолету; и функция SwitchSoundFiles (), которая также определяется пользователем, удаляет предыдущий звук выстрела и присоединяет новый приглушенный звук выстрела. С помощью этого приема вы можете заменять звуковые файлы, ие нарушая главный раздел вашей игры Прекрасно, я надеюсь, что вы понимаете это. Давайте перейдем к следующему разделу. который научит вас. как фактически проигрывать звуки. Слушайте внимательно - воспроизведение звуков Вы уже забрались довольно далеко в главу и, вероятно, хотите узнать, как фактически слушать те красивые звуки, которые вы только что научились загружать. Программа BlitzPIus обеспечивает нас простым методом запуска звуков. Эту функцию называют PlaySound. Предсказуемо, не так ли? Она объявляется следующим образом. PlaySound sound_variable Сделаем предположение наугад насчет параметра sound_variable. Да, вы правы: sound_variable - это указатель звукового файла, который вы загрузили, используя функцию LoadSound (). Другими словами, вы можете загрузить файл звукозаписи следующим образом: explosionsound = LoadSound("explosion, wav") Затем вы проигрываете его следующим образом:
PlaySound explosionsound С ума сойти, как сложно, да? Так или иначе, табл 11.3 описывает параметр функции PlaySound. Хорошо, дзвайте ицямвдтм эту функцию в типовой программе. Начнем с прокрутки фона. Почему? Потому что это делать просто и всегда забавно. Затем мы можем добавить привлекательный космический корабль способом, который мы используем довольно часто. Затем - это забавный момент - мы позволяем и> року стрелять снарядами, используя клавишу Spasebar. Выстрел снарядом создает звук выстрела. Давайте также добавим вражеский космический корабль. Этот враг похож на меня: он перемещается случайным образом, все>да не предсказуем и не имеет каких-либо возможностей самообороны. Во всяком случае, когда снаряд поражает эту сумасшедшую мини-копию меня, проигрывается звук взрыва и враг уничтожается. Затем его корабль восстанавливается. Табл. 11.3. Параметр функции PlaySc „id Параметр Описание sound_var iat-4 е Указатель звукового файла, 3ai ружаемого функцией LoadSound(), который вы хотите озвучить Да, я хотел сделать эту программу немного причудливой, поэтому я использовал свое изображение для вражеского судна. Хорошо, теперь смотрим На всякий случаи табл. 11.4 объясняет назначение клавиш, используемых в программе Табл. 11.4. Клавиши для программы demo! 1 -01 .bb Клавиша Действие Esc Выход из игры Up arrow Передвигает корабль вверх Down arrow Передвгпаег корабль вниз Right arrow Передвигает корабль вправо Left arrow Передвигает корабль влево Spasebar Выпускает снаряд Я собирался показать исходный код программы целиком, но понял, что он займет около пяти страниц, поэтому я буду i юказыва гь только важные части. Первый фра1 мент кода, который я покажу вам, взят из раздела инициализации программы demol 1-01 .bb ;ЗВУКИ ;Загружаем звук, который воспроизводится, когда игрок выпускает снаряд Global bulletsound = Loadsound("zing.wav") ;За]фуЩ1ем звук, который воспроизводится, когда игрок уничтожает врага
Global explosionsound - LoadSound("explode.wav"j Уверен, вы можете догадаться, что этот фрагмент делает! Этот код загружает оба звука, которые используются в программе Давайте перейдем к использованию этих загруженных звуков. Следующий код - функция UpdateBullets(). ;Функция UpdateBullets() - Перемещает и проверяет каждый снаряд на предмет конфликта с другими объектами Function UpdateBullets() ;Цикл по всем снарядам For bullets.bullet = Each bullet ;Обновим положение снаряда, перемещая его на 5 пикселов вверх bullets\у = bullets\y - 5 ;Выводим снаряд в присущих ему координатах Drawlmage bul lets \ image,bullets\х,bu11et s\у ;Если снаряд попал во врага, проигрываем звук взрыва и переустанавливаем уровень I£ ImagesOverlap(enemy\image,enemy\х,enemy\у,bullets\image, bullets\x,bullets\y) PlaySound explosionsound ;проигрываем звук взрыва Cis Text 260,300,"You destroyed the enemy! How could you?" Flip Delay 4000 ResetLevelO ;Восстанавливаем все переменные уровня Return . Возвращаемся в главный цикл Endlf ;Если снаряд вышел за экран, удаляем его If bullets\y < 0 Delete bullets Endlf Next ;Переходим к следующему снаряду End Function Как вы, вероятно, и предполагали, эта функция обновляет все снаряды на экране. Она начинается с перемещения каждого снаряда вверх на пять пикселов и затем выводит снаряд. Потом проверяй! снаряд на столкновение. Проверка столкновения использует функцию ImagesOverlap (). Как вы моти бы иомнить, функция ImagesOverlap() проверяет два изображения, здесь enemy и bullets, чтобы видеть, пересеклись ли они друг с другом. Если да, программа запускает звук взрыва, используя команду Play Sound. 11-2840
PlaySound explosionsound ;проигрываем звук взрыва Остальная часть функции очищает экран и отображает некоторый текст. Затем она переустанавливает уровень и. используя команду Return, возвращается назад к главному циклу. Вы могли бы задаться вопросом, почему я использовал команду Return, чтобы возвратиться к главному циклу, вместо того, чтобы лишь позволить функции завершиться, выполнив ее команды. Вот почему: в пределах функции ResetLevel I), которая вызывается непосредственно перед командой Return, все снаряды удалены. Включая снаряд, который только что обрабатывался. Поскольку снаряда больше не существует, как мы могли бы выполнить действия следующей строки, которая проверяет, ушел ли снаряд за пределы экрана? Нет никакого способа, поэтому, чтобы исправить эту ситуацию, мы просто возвращаемся назад к главному циклу и начинаем на пустом месте. Последняя часть функции только проверяет, не ушел ли снаряд за пределы экрана. Если да, то снаряд удаляется. Функция PlaySound используется в программе еще ряз. Следующий блок является отрывком из функции TestKeys (). ;Создаем новый снаряд, если нажата клавиша "пробел" If KeyHit(SPACEBAR) bullets.bullet = New bullet .-Создаем снаряд bullets\x - player\x ;Назначаем снаряду координату x игрока bullets\y = player\y :Назначаем снаряду координату у игрока bul lets\image = Loadimage ("bullet .bmp") .-Загружаем изображение снаряда ;Проигрываем звук выстрела PlaySound bulletsound Endlf Что при этом делается? Действительно, код начинает с проверки, нажал ли игрок Spasebar. Если нажал, то программа создает новый снаряд. Затем программа назначает стартовым координатам снаряда стартовые координаты игрока. Потом загружается изображение снаряда. Блок заканчивается воспроизведением звука bulletsound. Этот звук создается каждый раз при создании нового снаряда. Вот и все, что касается этой безумной программы. Рис. 11.2 показывает программу во всей ее красе. Ничего себе, как плохо я выгляжу на этой фотографии. Вы устанете искать мое хорошее изображение за последние несколько лет! Итак, теперь мы знаем, как воспроизводить звук. Люди, мы еще не закончили! Прог рамма BlitzPIus обеспечивает нас некоторыми действительно крутыми инструментальными средствами, которые делают звуки намного более забавными для использования.
Рис. 11.2. Программа demol 1 -01 .bb Программа BlitzPIus дает нам три функции: SoundPitch, Soundvolume и SoundPan. Эти три функции могут использоваться вместе с каждым из ваших звуковых файлов, чтобы произвести некоторые совершенно сладкозвучные эффекты. Давайте подробно рассмотрим каждую из функций по порядку, начиная с функции SoundPitch. Функция SoundPitch: я - дьявол или бурундук? Что такое высота звука? В сущности, высота звука определяет, как высока или низка частота звука. Возьмите парня в вашей школе, который оставался на второй год шесть или семь лет. Он, вероятно, имеет очень низкий голос. Теперь возьмите ребенка, который выглядит всего лишь лет на шесть. Этот ребенок имеет голос бурундука! Его голос имеет очень высокий тон. Высота звука измеряется в герцах. Шкала герц идет от 0 до бесконечности, но для людей звук с высотой в 44 000 Гц - предельный, который можно услышать. На самом деле, 22 000 Гц - это максимум, но в компьютерах мы должны испо^шзовать удвоенный максимум для воспроизведения звука. Это большая шкала, не так ли? Чем меньше число, тем ниже звук. Обычно, если звук замедляется, то высота становится меньшей, а если звук ускоряется, то высота возрастает. Как вы могли бы использовать это в программе? Использование значений герц в программе BlitzPIus значительно отличается от использования значений герц в реальной жизни. Значения, которые я даю, приписаны звукам в программе BlitzPIus, а не звукам в действительности. Подумайте о ком-то с действительно низким, грубым голосом. Он имел бы среднее значение высоты звука приблизительно 8 600 Гц. Теперь возьмите мальчика, подходящего по возрасту для третьего класса, с высоким голосом. Он, вероятно, имеет среднюю высоту звука 44 000 Гц.
Когда вы загружаете звук в ваши программы, вы не знаете значения его высоты в герцах. Для замены числа герц на выбранное вами значение вы используете функцию Soundpitch. Функция SoundPitch объявляется следующим образом: ScundPitch sound_variable,hertz Табл. 11.5 уточняет все параметры функции SoundPitch. Табл. 11 -5- Параметры функции SoundPitch Параметр Описание sound__variable Имя звукового файла, который вы хотите изменить hertz Число герц, которое вы хотите установить для звука sound_variable(менаду 0 и 44000) Как бы там ни было, давайте напишем программу. Эта программа позволит вам создать звук взрыва. Если вы нажмете клавишу Spasebar. проигрывается звук взрыва, а, нажимая клавиши Up или Down, вы меняете значение частоты звука на 1000 Гц. Мы также установим вначале значение переменной равным 22 000 Гц. Таким образом, у нас есть исходная точка д ля проверок. Ниже следует исходный код программы demol 1 -O2.bb. ;demoll-02.bb - Демонстрирует работу функции SoundPitch Graphics 800,600 ;Обеспечиваем использование вторичного буфера и задаем функции automidhandle параметр "истинно* SetBuffer BackBuffer() AutoMidHandle True ;ИЗОБРАЖЕНИЯ ;Загружаем изображение корабля игрока playerimage = Loadlmage("spaceship.bmp") ;ЗВУКИ ;Загружаем звук снаряда explosionsound - LoadSound("explode.wav") ;КОНСТАНТЫ ;Следующие константы, использованные для кодов клавиш Const ESCKEY = 1,SPACEBAR = 57,UPKEY - 200,DOWNKEY =208 ;Создаем переменную для частоты звука hertz = 22000 ;Обеспечиваем значение частоты звука для снаряда, равное начальному значению переменной частоты
SoundPi tch explosionsound,hert z ; ГЛАВНЫЙ ЦИКЛ While Not KeyDown(1) ;Очищаем экран Cis ; Обеспечиваем вывод текста в верхнем левом углу Locate 0,0 Print "Current Hertz Value: " + hertz ;Воспроизводим звук взрыва, если пользователь нажимает клавишу "пробел" If KeyHit(SPACEBAR) PlaySound explosionsound Endlf ;Если нажата клавиша "вверх", наращиваем частотную переменную If KeyHit(UPKEY) hertz = hertz + 1000 Endlf ;Если нажата клавиша "вниз", уменьшаем частотную переменную If KeyHit(DOWNKEY) hertz = hertz - 1000 Endlf ;Присваиваем звуку взрыва высоту, равную значению частотной переменной SoundPitch explos ionsound,hert z ; Выводим игрока Drawlmage playerimage,MouseX(),MouseY() Flip Wend ;КОНЕЦ ГЛАВНОГО ЦИКЛА Круто, ла? Программа позволяет игрок}' изменять частоту звука, нажимая клавиши Up или Down, а также синхронизирует частоту звука взрыва со значением переменной частоты, используя функцию SoundPitch. SoundPitch explos ionsound,hert z
На рис. 11.3 показан кадр программы demo! 1 -02.bb. Рис. 11.3. Программа demol 1 -02-bb Прекрасно, вот все, что касается высоты звука. Теперь давайте узнаем, как использовать функцию SoundVol ume. Функция SoundVolume Бьюсь об заклад, вы можете до! адаться, что делает функция SoundVolume. П роще говоря, изменение громкости звука регулирует, насколько громким или тихим является звук. Функция Soundvolume используется весьма сходно с функцией SoundPitch. Вот ее объявление: SoundVolume sound_variable,volume# Параметр sound_variable - указатель звукового файла, громкость которого вы хотите изменить. Параметр volume# является переменной типа числа с плавающей точкой и значением в диапазоне от 0 до 1,000. Чем ближе к 1 значение параметра volume#, тем громче звук. В табл. 11.6 приведены параметры функции. Табл. 11 -6. Параметры функции SoundVolume Параметр Описание sound_variable Указатель звукового файла, с которым вы хотите работать h’.'Xt i Громкость, которую вы хотите установить для звука sound_variable. Может изменяться в пределах от 0 до 1 Круто? Создадим программу. Программа demol 1 -03.bb выводит движущийся случайным образом корабль и проигрывает звук лазера каждый раз, когда вы нажимаете
клавишу Spasebar. Чем дальше вражеский корабль от вашего корабля, тем тише звук. Если корабль действительно близко, звук проигрывается громко. Эта программа довольно длинная, поэтому я собираюсь показать лишь два фрагмента из нее. Первая часть является отрывком из пользовательской функции FindCurrentVolume(). ;Функция FindCorrectVolume - Присваивает переменной volume# правильное значение в зависимости от расстояния от игрока до врага Function FindCorrectVolume() ;Находим расстояние между игроком и врагом diet = Distance(player\x,player\y,enemy\x,enemy\y) ,-Назначаем значение громкости переменной volume# в зависимости от величины расстояния ;Чем больше расстояние, тем тише звук If dist < 100 volume# = 1.000 Elself dist < 200 volume# = .700 Elself dist < 300 volume# = .400 Elself dist < 400 volume# _ .1000 Else volume# = 0.000 Endlf Первое, что делает эта функция - находит расстояние между' врагом и игроком, используя функцию Distance (}, которую мы записали в главе 9 «Обнаружение конфликтов». (Я скопировал исходный код из функции Distance (} в эту программу.) Затем она присваивает переменной volume# значение, зависящее от величины переменной dist. Если значение переменной dist выше, космический корабль находится дальше; поэтому звук должен быть более тихим. Блок операторов If ... Elself ... Else определяет, насколько громким должен быть звук. Следующая и последняя часть программы demo 11-03-bb фактически использует функцию Soundvolume. ;Создаем новый снаряд, если нажата клавиша "пробел" If KeyHit(SPACEBAR) /Определяем, какая должна быть громкость volume#
FindCorrectVolume() ;Назначаем для звука bulletsound, громкость volume# Soundvolume bulletsound,volume# ;Озвучиваем снаряд PlaySound bulletsound Endlf Это т код выполняется, когда вы нажимаете клавишу Spasebar. Он вызывает функцию FindCorrectVolume (), которая присваивает переменной volume# ее правильное значение. Затем код регулирует громкость звука bullet sound в зависимости от значения переменной volume#. И, наконец, блок запускает файл звукозаписи для снаряда. На рис. 11.4 показан кадр программы demo11-03.bb. Прекрасно, вот все, что касается функции SoundVblume. Есть лишь еще одна функция, которую мы изучим прежде, чем перейдем к воспроизведению музыки! Функция SoundPan Хорошие вещи всегда входят в тройки, да? Есть три основные вцдеоигровые консоли, три месяца лета и три бутылки пепси на моем столе, которые поддерживают меня допоздна. Действительно, есть также три функции, редактирующие звук. Мы уже обсудили две из них - функции SoundPitch () и Soundvolume (). Функция SoundPan -третья и последняя.
Функция SoundPan предлагает очень крутой эффект: она позволяет вам создавать иллюзию движущегося звука, позволяя вам выбрать, из какого динамика исходит звук. У вас может быть программа со звуком, играющим из левого динамика, правого динамика или из обоих. Это позволяет вам создать у игроков ощущение, будто звуки действительно двигаются вокруг них. Функция SoundPan объявляется следующим образом: SoundPan sound_variable,pan# А сейчас - крутая часть: так как параметр панорамирования (баланса) звука pan# является переменной типа числа с плавающей точкой, вы можеге, например, иметь звук, смещенный немного влево, но все еще слабо звучащий справа. Что я имею в виду'? Ну, если бы вы установили значение панорамирования, равное -0.75. звук исходил бы на 75% из левого динамика и на 25% из правого динамика. Параметр sound_variable, как вы, вероятно, знаете, является указателем звукового файла, который вы хотите редактировать. Параметр pan# содержит значение, которым вы хотите панорамировать звук. Значение параметра pan# может быть между -1,000 и 1,000; если число отрицательное, звук будет исходить преимущественно из левого динамика, а если оно положительное, он будет проигрываться главным образом из правого динамика. В табл. 11.7 перечислены параметры функции SoundPan. Табл. 11.7. Параметры функции SoundPan Параметр Описание sound_variable Указатель звукового файла, который вы хотите панорамировать pan# Число в пределах от-1,000до 1,000, которым вы хотите панорамировать зйук Круто? Давайте напишем программу. Она будет простой: вражеское судно перемещает ся влево и вправо. Когда игрок нажимает клавишу Spasebar. запускается звук. Если враг находится слева от игрока, звук играет полностью из левого динамика. Если враг - справа от игрока, звук запускается из правого динамика. Если враг - непосредственно перед игроком, звук запускается из обоих динамиков. Ниже приведен главный цикл, который взят из программы demol 1 -О4.ьь. ;ГЛАВНЫЙ ЦИКЛ While Not KeyDown(ESCKEY) ;Замостим фон TileBlock backgroundimage,0,scrolly ;Наращиваем переменную прокрутки scrolly = scrolly + 1 If scrolly > ImageHeight(backgroundimage)
scrolly = 0 Endlf ;Выводим весь текст в верхнем левом углу Locate 0,0 Print "Panning variable*. “ + pan# ?Устанавливаем координаты игрока playerXx - MouseX() playerXy = MouseYО ;если враг слева от игрока, делаем звук исходящим из левого динамика If enemy\х < playerXx pan# - -1.000 ;если враг справа от игрока, делаем звук исходящим из правого динамика Elself enemyХх > playerXx pan# = 1.000 гесли враг перед игроком Else pan# = 0 Endlf ;Панорамируем звук SoundPan bulletsound,pan# ;Если пользователь нажимает клавишу "пробел", воспроизводим звук If KeyHit(SPACEBAR) PlaySound bulletsound Endlf ;Передвигаем врага в соответствии с его скоростью enemyХх = enemyХх + enemy\xv ;Если враг выходит за экран, обращаем его скорость в противоположную сторону If enemyХх < 0 Or enemy\х > В00 endmyXxv = - enemyXxv
Endlf ;Выводим игрока и врага Drawlmage playerX image, player\x, player\y Drawlmage enemy\ image,enemy\x,enemy\y Flip Wend He так уж и плохо, а? Главная часть кода находит, какой должна быть переменная панорамирования pan#. Переменная pan# используется как параметр для функции SoundPan и определяет, как далеко налево или направо должен направляться звук. Для определения значения переменной pan# мы используем следующий блок кода. ;Если враг слева от игрока, делаем звук исходящим из левого динамика If enemy\x < player\x pan# = -1.000 ;Если враг справа от игрока, делаем звук исходящим из правого динамика Elself enemy\x > playerXx pan# = 1.000 ;Если враг перед игроком Else pan# = 0 Endlf Этот код присваивает переменной pan# значение -1 (левый динамик), если враг находится слева от игрока, значение 1, если игрок - справа, и значение 0, если он - непосредственно перед игроком. Последняя часть главного цикла, которую я хочу показать вам, наконец использует функцию SoundPan. ;Pan the sound SoundPan bulletsound,pan# Довольно круто, да? Эта функция согласовывает звук снаряда с переменной панорамирования pan#. На рис. 11.5 показан кадр программы. Между прочим, эта программа работает не с любыми динамиками, поэтому, если кажется, что она балансирует звук неправильно, то это может быть только из-за аппаратных характеристик ваших динамиков. Ха! Вот то, что касается звуков! Теперь мы добрались до использования музыки в наших играх.
Рис. 11.5. Программа demol 1-04.bb Музыка Вы можете быть от этого далеки, но вряд ли вы совсем нс понимаете различие между музыкой и звуком, но крайней мере, на компьютере, Дело в гом, что пока вы использовали звук, чтобы воспроизвести звуки взрывов и орудийный огонь. Вы можете видеть, что эти звуки проигрываются только во время фактического взрыва или в тот момент, когда снаряд фактически выпущен. Музыка, однако, проигрывается на заднем плане в то время, как выполняется ваша И1ра. Следовательно, опа намного легче для использования, потому что вы можете запустить ее в начале ш ры и не волноваться относительно музыки в дальнейшем. Для музыкальных примеров в этой книге я исмользую формат .MP3. Программа BlitzPIus также позволяет вам использовать форматы .WAV. .OGG, .XMS и .MID для ваших игр, по я не буду охватывать их. чтобы упростить дело. Это все специальные форматы, которые похожи на формат .WAV, но имеют маленькие различия, которые здесь объясняться не будут. Имеется много другой информации, если вы хотите ознакомиться с другими форматами. Особая благодарность Томасу Стснбэку (Thomas Stenback) и всем участникам группы -Ini crim Nation» за разрешение использовать их музыку на компакт-диске. Вы можете посетить сайт группы «Inlcrim Nation», сочинившей музыку; содержащуюся на компакт-диске, http://www.interimnation.com. Чтобы понять, как использовать музыку в программе BlitzPIus, мы сначала должны обсудить каналы. Каналы и функция PlayMusicf) Что такое канал? Ну, вообразите, что у вас есть брат, который с вами разговаривает. В этой точке единственный, кого вы можете слышать, - ваш брат. Теперь вообразите.
что телефон, размещенный возле вас, начинает звонить. Внезапно вы можете услышать два источника звука сразу, правильно? Так вот, в этот момент существуют два играющих канала: канал брата и канал звонящего телефона. Теперь крутая вещь относительно каналов - вы можете редактировать каждый канал независимо. Чтоэто означает? Ну, например, скажем, ваш брат, который в настоящее время находится на канале брата, начинает вам шептать. Громкость этого голоса уменьшается. Используя каналы, вы можете изменять громкость одного канала, оставляя другой неизменным. Если вы взглянете на рис. 11.6, вы можете видеть при- мер того, как могли бы работать Левый . Централь . J) т .П точка Правый каналы. Имеются два блока, оба испускающие звуки. Одни - справки один -слева; таким образом, используя волшебство каналов, Канал - Канал тот, что слева, направляет звук __________________________________________ далеко налево, а тот. что справа, Рис 11 ". Каналы „ панорамирование направляет направо. Все, что нам теперь нужно, - узнать, как получить контроль над каналом. В отличие от указателей, которые вы используете для загрузки звуков, вы должны проигрывать звук, чтобы получить доступ к каналу. Самый обычный способ получить канал состоит в том, чтобы использовать функцию PlayMusic (). Эта функция объявляется следующим образом: PlayMusic (filenames) При воспроизведении музыки вам не нужно сначала загружать звук. Вы только лишь вызываете функцию PlayMusic () с соответствующим именем файла, и ваш звук готов! Скажем, вы котели бы загрузить песню в стиле «техно»- с названием technosong.mp3. Вот что вы должны сделать: technosong = PlayMusic("technosong.mp3") Как вы можете видеть, программа использует функцию PlayMusic() и передает песню в переменную канала. Эта переменная может позже использоваться для редактирования звука. Обратите внимание, что эта строка программы фактически проигрывает музыку Это означает, что в момент использования этой строки программы начнет играть музыка из файла technosong.mp3. Если вы хотите загрузить звук перед использованием звукового файла в программе, используйте функции LoadSound () /PlaySound(). Между прочим, функция play Sound () тоже возвращает переменную канала, которую вы можете использовать так же, как переменные канала для файлов музыки. Хорошо, теперь, когда мы знаем, как загрузить музыкальный файл, давайте выясним, что мы можем делать с этими каналами. Кстати, табл. 11.8 описывает параметр функции PlayMusic().
Табл. 11.8. Параметр функции PlayMusic (' Параметр Описание f ilename# Полный путь и имя файла, который вы хоппе проигрывать Смешение с помощью цифровых аудиоканалов Предыдущий раздел показал вам, как проигрывать файлы музыки и загружать каналы, а этот раздел научит вас, как их использовать. Ниже приведен список всех функций и их объяалений, которые могут использоваться в работе с каналами. StopChannel channel_handle Pausechannel channel_handle ResumeChannel channel_handle Channelvolume channel_handle,volume# ChannelPan channel_handle,pan# Channelpitch channel_handle,hertz Их совсем немного, и они очень просты для понимания. Большинство из них даже не требует параметров кроме обязательной переменной канала, а если требует, то имею щиеся требования не жестки. Во всяком случае, позвольте мне помочь вам понять, что эти функции делают. Первая половина списка (функции StopChannel, Pausechannel и ResumeChannel) может быть выделена в одну группу, а вторая половина (функции Channelvolume, ChannelPan и Channel Pitch) может быть выделена в другую группу. Все функции внутри каждой группы связаны. Функции первой группы работают очень похоже на кнопки остановки, паузы и продолжения на проигрывателе компакт-дисков. Функция StopChannel* немедленно останавливает песию. Песня прекращается и может быть перезапущена только сначала. Функции Pausechannel и ResumeChannel, однако, позволяют вам приостановить и начать проигрывать музыкальный файл с любого места внутри песни. Функция Pausechannel приостанавливает песию, а функция ResumeChannel подхватывает песню из того же самого места, где она была остановлена. Вы можете использовать эти функции в многочисленных ситуациях. Скажем, у вас есть игра с монстром-пришельцем, появляющимся в конце. Музыка играет на заднем плане, и, когда вы добираетесь до монстра, вы хотите, чтобы музыка прекратила играть в то время, как монстр говорит или что-нибудь делает. Что вы делаете: вызываете функцию Pausechannel сразу, как только монстр появляется на экране, и, после того как он заканчивает свою речь, или видео, или что-нибудь еще, вы вызываете функцию ResumeChannel, чтобы начать музыку прямо из исходной точки (на которой перед этим остановились). На всякий случай табл. 11.9 объясняет параметр для функций StopChannel, PauseChannelиResumeChannel.
Табл. 11.9. Параметр функций Stoe-^Pause/ResumeChannel Параметр Описание channel_handle Канал, который вы хотите остановить/приостановить/продолжить Хорошо, круто. Теперь - вторая группа, которая состоит из функций Channelvolume, ChannelPan и Channelpitch. Вы помните функции Soundvolume, SoundPan и SoundPitch? Так вот эти функции работают тем же самым образом. Функция Channelvolume регулирует громкость музыки, играющей из данного канала. Значение переменной volume# может быть где-нибудь между 0 и 1,000, со авачениеы 0, соответствующим самому тихому звуку, и значением 1,000 - самому громкому звуку. Функция ChannelPan позволяет вам регулировать направление звука, исходящего из канала. Значение параметра может быть в диапазоне от -1,00 до 1,00. Значение -1,00 соответствует крайнему левому направлению, значение 1,00 - это крайнее правое и, конечно, значение 0,00 находится непосредственно в центре. Последняя функция, которая может использоваться для музыки, - функция Channelpitch. Функция Channelpitch использует значение частоты в герцах между 0 и 44 000, где значение 44 000 является наиболее высоким звуком, а значение 0 является наиболее низким звуком. Фактически 0 означает отсутствие звука! Крутое свойство этих функций состоит в том, что каждое изменение, которое вы применяете к ним, происходит в реальном времени - вы не должны переигрывать музыку каждый раз, когда вы хотите услышать изменения. Например, если бы у вас была программа с музыкой, играющей гпраяа, и музыкой, играющей слева, и игрок поворачивал бы своего персонажа приблизительно на 180 градусов, все, что вы должны сделать, - вызвать функцию ChannelPan, и дело выполнено, вместо того, чтобы запускать звук снова. Табл. 11.10 перечисляет параметры для функций Channel Volume, ChannelPan и ChannelPitch. Табл. 11.10. Параметры функций ChannelVolun -/Pan/Pitch Параметр Описание channel-handle Канал, содержащий звук, который вы хотите редактировать volume# Значение громкости (между 0 и 1.00). которое вы хотите назначить для канала pan# Значение панорамирования (между -1,00 и 1,00), которое вы хотите назначить для канала hertz Значение частоты в герцах (между 0 и 44 000), которое вы хотите назначить для канала
Итак, я включил в книгу программ)' demo 11-05.bb, которая позволяет вам экспериментировать с музыкальным файлом. Вы можете изменять громкость его звучания, панорамировать его и изменять высоту его звучания. Вы можете также сделать паузу, остановить и возобновить песню. Табл. 11.11 уточняет назначения всех клавиш для программы demol 1 -05.bb. Табл. 11.11. Назначение клавиш в программе demol 1 -5.bb Клавиша Действие Up Arrow Увеличивает высоту звука на 100 Гц Down Arrow Уменьшает высоту звука па 100 Гц Right Arrow Направляет звук налево на -0,1 Left Arrow Направляет звук направо на 0,1 A Повышает громкость на 0,1 Z 11снижает громкость на 0,1 P Делает паузу в звучании R Возобновляет звук S Останавливает звук Исходный код для программы demol 1 -05.bb очень длинный, поэтому он не включен в книгу. Не поленитесь проверить его на компакт-диске. Между прочим, послушайте песню, включенную в демонстрационную программу- она очень классная. На рис. 11.7 показан кадр программы demol l-05-bb. Рис. 11.7. Программа demo11 -05.bb
Есть еще несколько функций, о которых вам, возможно, следует знать немного больше. Первая функция ChannelPlaying() проверяет, играет ли канал в данный момент. Если файл музыки проигрывается, функция Channel Playing () возвращает 1; если он не играет, функция ChannelPlaying () возвращает 0. Вы могли бы использовать эту функцию, если хотите, чтобы ваш музыкальный фон запускался больше чем один раз, сразу мосле завершения. Это называют зацикливанием, которое является проигрыванием одного и того же файла музыки много раз без перерыва. Как вы могли бы это сделать? Возможно, примерно так: coolsong = PlayMusic("song.mp3") ;ГЛАВНЫЙ ЦИКЛ While Not KeyDown(1) If Not ChannelPlaying(coolsong) coolsong = PlayMusic ("song.mp3") Endif Файлы, содержащие звуки, зациклитьироще. Программа BlitzPIus предоставляет функцию LoopSound, которую вы можете использовать для многократного запуска звукового файла. Функция LoopSound объявляется следующим образом: LoopSound sound_variable Все, что вы делаете, - передаете указатель звукового файла этой функции в качестве параметра. После того как вы сделаете это, вызывайте функцию Play Sound с указателем звука в качестве параметра, и файл будет проигрываться многократно. Итоги Программа BlitzPIus действительно облегчает использование звука и музыки в играх, а звук и музыка действительно делают игру совершенно иной. Они обеспечивают подходящие настроение и окружение для игры. Используя музыкальный фон, вы можете дать игроку ощущение лихорадочной миссии-экшн или неспешной миссии-квеста. Главные темы, которые мы охватили: загрузка звуков; воспроизведение звуков; использование функции PlayMusic () и каналов; редактирование каналов; Между прочим, на компакт-диске содержится много звуков и музыкальных файлов. Все музыкальные файлы предоставил Томас Стенбэк, и звучат они великолепно. В следующей глаяе мы собираемся раскрыть тему искусственного интеллекта. Вы узнаете, как заставить компьютеры думать и действовать!
ГЛАВА 1 2 Искусственный разум Так как скоро конец книги, мы добрались до самой сути программирования компьютерных игр. В отличие от некоторых программ, установленных на компьютерах, игры требуют от компьютера способности мыслить самостоятельно! Ну может быть, и не думать, но действовать так, как будто это происходит на самом деле. В играх, где противником управляет компьютер, он должен превосходи гь трока, которым руководит человек. Эта глава вооружит вас необходимыми инструментами для создания противника, ведущего себя, как человек. Создание искусственного разума может показаться крайне сложным и запутанным, поэтому' данная глава выступает в качестве краткого введения в наиболее простые способы его реализации. Существует множество еще более интересных тем, чем те, с которыми я вас познакомлю, и если вы хотите узнать подробнее о некоторых из них, предлагаю поискать статьи этого направления в других книгах. Я думаю, вам не терпится уже начать, поэтому завершаю свое вступление. Я имею в виду - сейчас. Серьезно, вступление заканчивается. Я не шучу. Случайные числа В первой части создания искусственного интеллекта мы узнаем, как пользоваться случайными переменными в программах. Использование случайных переменных в программах в действительности ие совсем разумно, но это лишь первая ступенька, верно?
Случайные переменные: действительно ли случайные? Немного интересной информации о случайных числах в компьютере: нахождение случайного числа является практически невыполнимой задачей. Компьютеры только принимают вводимую информацию, обрабатывают ее и выдают результат, но, в целом, они не создают произвольных чисел. Следовательно, компьютеры могут лишь производить так называемые псевдослучайные числа. Что такое псевдослучайное число? Это такое число, которое хотя и может казаться произвольным, в действительности таковым не является. Например, если вы напечатаете десять миллионов произвольных чисел, все равно не сможете обнаружить некоторые повторяющиеся образцы, равномерно расположенные между числами. Однако, если проделать это на компьютере, можно заметить эти закономерности и некоторую неровность в числах. Но, в целом, псевдослучайные числа довольно близки к случайным! Для генерации случайных чисел необходимо написать две функции. Первая из них называется SeedRnd и объявляется так: SeedRnd seed Что значит параметр seed? Функция SeedRnd действует так: она вводит в компьютер число, которое впоследствии будет использоваться для создания псевдослучайных чисел. Нам нужно сделать значение seed равным числу, которое постоянно изменяется при каждом запуске программы, в противном случае «случайные» числа всегда будут теми же. Вторая функция MilliSecs () изменяется при каждом запуске программы. Она возвращает количество миллисекунд в системном таймере (с момента последнего запуска компьютера). Поскольку время в системном таймере меняется постоянно, функция Mi 11 iSecs () - хорошая альтернатива значению seed в функции SeedRnd. В начале программы, использующей случайные переменные, вызовем функцию SeedRnd следующим образом: SeedRnd MilliSecs() Классно, а? После этой нелегкой задачи продолжим использовать случайные числа. Заметьте, что функция SeedRnd на самом деле не выполняет заметных действий, она просто подготавливает программу к дальнейшему использованию в ней случайных переменных Теперь, когда мы установили генератор случайных чисел, вызвав функцию SeedRnd, можно приступать непосредственно к получению тех самых случайных чисел. Итак, существуют две функции, которыми снабжена программа Blitz Basic: функции Rnd и Rand. Обе эти функции имеют похожие объявления. Rand ([start],end) Rnd (start #,end#) Как вы заметили, они почти одинаковы. Разница заключается только в параметрах, о которых я сейчас подробно расскажу.
Названия параметров функций Rand() и Rnd() похожи. Параметр start I start# означает минимально допустимое значение случайного числа, а параметр end I end# -наибольшее возможное значение случайного числа. Это объяснение, вероятно, покажется довольно сложным для восприятия, поэтому попробую рассказать понятнее. При использовании одной функции случайных чисел вы обычно задаете ей два параметра. Например, можете сделать что-либо подобное: randomvalue = Rand (100,200) Вы сообщаете фрикции Rand () параметры 100 и 200, поэтому случайное значение (randomvalue) будет содержаться в пределах от 100 до 200. Можете легко изменить приведенные параметры и увидите, что я имею в виду. Если бы вы указали в предыдущих настройках значение 50 вместо 100, то значение randomvalue сейчас заключалось бы в интервале от 50 до 100. Так, теперь о другом. Вы могли заметить, что параметр [start] функции Rand() является необязательным (заключенным в скобки). Поскольку он дополнительный, можно снабжать функцию Rand только одним параметром. Если вы пренебрежете включением [start ], программа Blitz Basic предположит, что вы хотите значение этого параметра указать равным 1. Следовательно, вызов функции Rand() таким образом Rand(205) возвратит случайное число в пределах от 1 до 205. Обе функции, Rand () и Rnd (), имеют похожие параметры с той лишь разницей, что в функции Rand () могут содержаться целые числа, а в функции Rnd () числа с плавающими точками. Вспомните, целым называется число без точки (например, 314), тогда как переменная с точкой содержит десятичные доли (например, 314.13, где число после точки является десятичной частью) Тот факт, что функция Rnd() позволяет задавать в качестве параметра число с плавающей точкой, означает возможность создания случайных переменных, содержащих десятичные доли чисел. Если вы напишете функцию Rnd () следующим образом: случайное число = Rand (1.000.14.000) функция находит случайное число Rand возвращает 2.113 I Rand удаляет дробную часть I случайное число устанавливается равным целому, 2 Rnd (1.000,14.000) она возвратит число в промежутке от 1,000 до 14,000. Нвпрпмер, она может выдать такое число, как 3,133, или какое-либо подобное. Если же вы решите написать функцию Rand () таким же образом, помните, что она возвращает только целые числа, например 4 или 9. На рис. 12.1 показано, что произойдет, когда вы вызовете функцию Rand(} с числами с плавающей точкой в качестве пара метров. Как видите, если даже вы укажете функцию Rand(), содержащую десятичные дробные числа в качестве параметров, она все равно вернет целое число. Это делается для нахождения случайного числа и удаления его дробной части. Рис. 12.1 . использование в функции Rand(] чисел с плавающей точкой
Хорошо, просто великолепно. Надеюсь, вы поняли, как определяются случайные числа. Теперь попробуем применить это в программе Представленный ниже код взят из исходного файла demo12-01 .bb ;demol2-01.bb - Демонстрирует случайные переменные Graphics 600,600 ;Зададим точку привязки и вторичный»буфер AutoMidHandle True SetBuffer BackBufferO ;Убедимся, что запущен генератор случайных чисел SeedRnd MilliSecs() ;Теперь загружаем изображение, которое хотим использовать Flyimage - LoadAnimImage("fly.brnp",64,64,0,4) ;Задаем начальное значение переменной frame frame - О ; Задаем значения х и у мухи flyx = 400 flyy - 300 ;ГЛАВНЫЙ ЦИКЛ While Not KeyDown(l) ;Очищаем экран Cis Text 0,0,"Fly X: " + flyx Text 0,20, "Fly Y: " + flyy ;Перемещаем муху на произвольное расстояние flyx = flyx + Rand(-15,15) flyy = flyy + Rand(-15,15) ;Выводим муху на экран Drawlmage flyimage,flyx,flyy,frame ;Увеличиваем значение переменной frame frame — frame + 1
;Если значение frame слишком велико или мало переустанавливаем его If frame > 3 frame = О Elself frame < О frame - 3 Endlf ;Переключаем буферы Flip ;Делаем небольшую паузу Delay 25 Wend ;КОНЕЦ ГЛАВНОГО ЦИКЛА На рис. 12.2 показан кадр программы demo12-01 -bb Рис. 12.2. Вид программы demo12-01.bb Получившаяся у пас программа, без сомнений, хороша, хотя в ней пока и не все гладко, не так ли? Полет мухи очень прерывистый и выгладит ужасно, когда она двигается на экране. Причина этого заключается в том, что переменные х и у мухи обновляются в каждом кадре, т. е. ее положение изменяется значительно чаще, чем 30 раз в секунду (обычно в таких играх смена кадров происходит около 30 раз в секунду). Давайте переделаем программу, только вместо изменения координат мухи 30 раз в секунду мы выполним это только каждые несколько секунд. Как это делается? Так, прежде всего мы до.ькны познакомиться с тем, как создавать таймер, использующий функцию Millisecs().
Создание таймера функции MilliSecs() Вы, вероятно, заметили частое использование функции MilliSecs () - мы пользовались ею, чтобы сделать генератор случайных чисел с командой SeedRnd. Как вы помните, причина, по которой мы использовали миллисекунды в создании генератора случайных чисел, заключается в том, что функция MilliSecs () никогда не повторяет число дважды. Так, но, если число никогда не повторяется дважды, как можно применить это при создании таймера? Значение функции MilliSecs() увеличивается с каждой миллисекундой, пока компьютер работает. Например, если значение MilliSecs{) сейчас равно 100123, то в следующую миллисекунду оно будет равным 100124. Миллисекунда равна одной тысячной доле секунды (другими словами, в секунде содержится 1000 миллисекунд), следовательно, 101123 произойдет точно на секунду позднее, чем 100123. Теперь, пользуясь функцией MilliSecs!), попробуем создать таймер. Хотя функцию MilliSecs () нельзя вызвать в точно такое же время - это не беда. Необходимо создать в начальный момент переменную, которая содержала бы функцию MilliSecs (). Затем мы проверим этой функцией каждый кадр, пока ее значение не будет равно или больше, чем стартовая переменная, которую мы создали вначале, плюс количество времени, после которого мы котим остановить таймер. Итак, напишем код таймера- Следующий отрывок кода показывает, как работает трехсекундный таймер. ,-Создадим начальную переменную таймера, содержащую значение, с которого нужно начинать отсчет timerbegin - MilliSecs!) ;Здесь мы можем начать основной цикл ;Проверяем, когда текущее число функции MilliSecs() будет равно начальной переменной + 3 секунды If MilliSecsO >= timerbegin + 3000 ;Делаем что-нибудь Endlf Если вы просто скопировали этот код в программу, он может работать не совсем корректно. Программа может никогда не кончиться! Причина кроется в том, что таймер будет переустанавливаться каждый кадр, потому что существует строка timerbegin - MilliSecs О, астрока If MilliSecs() >= timerbegin + 3000 не будет отстоять более чем на несколько миллисекунд от точки отсчета. Поэтому для правильной работы программы необходимо разделить запуск таймара и его проверку. Если вам нужно использовать таймер только один раз, достаточно лишь указать его инициализацию в начале программы и вставить тестирование в основной цикл. Идем дальше от строки к строке. Сначала мы создадим таймер.
;Создадим таймер, содержащий значение точки отсчета timerbegin = MilliSecs() Это действие создаст таймер со значением, равным значению функции MilliSecs () на момент создания таймера. Затем мы должны проверить таймер, чтобы определить, достаточна ли его продолжительность. ;Проверяем, равно пи текущее значение функции MilliSecs() точке отсчета + 3 секунды If MilliSecs О >= timerbegin + 3000 Как это делается? Функция сравнивает текущее значение функции MilliSecs () с точкой отсчета плюс 3 секунды (3000 миллисекунд). Как вы помните, точка отсчета равна значению функции MilliSecs () на момент создания таймера. Поскольку зна чение функции MilliSecs {) увеличивается каждую миллисекунду работы компьютера, проверка будет истинной через три секунды после создания таймера. «Замечательно!» - сказал я сам себе. Приведем полный список команд файла demo12-02.bb. ;demol2-01.bb - Демонстрирует случайные переменные Graphics 800, 600 ; Задание точки привязки и вторичного буфера AutoMidHandle True SetBuffer BackBuffer() ;Создаем генератор случайных чисел SeedRnd MilliSecs() ;КОНСТАНТЫ ;Эта константа определяет, сколько пройдет времени прежде, чем муха изменит направление Const CHANGEDIRECTIONS = 1500 Направление движения мухи изменяется каждые 1,5 секунды ;Тип мухи Type fly Field х,у ;Координатная позиция Field xv,уv ;Скорость мухи Field image ;Изображение мухи End Туре
;Создадим муху fly.fly - New fly ;Полет мухи начинается в центре экрана fly\x = 400 fly\y = 300 ;Задаем мухе произвольную скорость fly\xv = Rand(-15,15) fly\yv = Rand(-15,15) ;Теперь загрузим изображение мухи fly\image = LoadAnimlmage ("fly.bmp",64,64,0,4) :Задаем значение начального кадра frame = 0 :Создадим точку отсчета timerbegin = MilliSecs() ;Создаем переменную, которая не позволяет таймеру переустанавливаться t interactive = 1 ;ГЛАВНЫЙ ЦИКЛ While Not KeyDown(1) ;Очищаем экран Cis Text 0,0."Fly X: " + flyx Text 0.20."Fly Y: " + fly Text 0,40," Текущее время, остающееся на таймере: * + (CHANGEDIRECTIONS - MilliSecs() * timerbegin) ;Если время таймера вышло, обновляем скорость мухи If MilliSecs() >= timerbegin + CHANGEDIRECTIONS
;Перемещаем муху произвольным образом flyXxv = flyXxv + Rand(-10,10) flyxyv = flyXyv т Rand(-10,10) ;Выключаем таймер timeractive = 0 Endlf ;Если таймер не активен, переустанавливаем его If timeractive = 0 timerbegin = MilliSecs() timeractive = 1 Endlf ;Перемещаем муху fly\x = fly\x + flyXxv flyXy - flyXy + fly\yv ;Проверяем, задевает ли муха стены If flyXx <= 0 Or flyXx > 800 flyXxv = ?fly\xv Endlf If flyXy <= 0 Or flyXy >= 600 flyXyv = ?fly\yv Endlf ;Выводим муху на экран Drawlmage flyX image,fly\x,fly Xy,frame ;Увеличиваем кадр frame = frame + 1 ;Если номер кадра получится слишком большим или маленьким, переустанавливаем его If frame > 3 frame - 0 Elself frame < 0 frame
Endlf ;Переключаем буферы Flip ;Делаем небольшую паузу Delay 75 Wend ;КОНЕЦ ГЛАВНОГО ЦИКЛА Я сделал небольшие изменения программы в файле demol 2-01 .bb, о которых расскажу сейчас. Прежде всего, я создал постоянную, которая определяет, как долго длится пауза между изменениями в скорости и направлении корабля. Эта постоянная называется CHANGEDIRECTIONS. Следующая часть программы, которую я изменил, - сама муха. Я создал тип мухи и установил начальные значения его переменных. Следующий отрывок исходного кода, который создает и инициализирует муху. ;Тип мухи Type fly Field х,у Field xv,уv Field image ;Координатная позиция ;Скорость мухи ;Изображение мухи End Туре ;Создаем муху fly.fly = New fly ; Полет мухи начинается в центре экрана fly\x = 400 fly\y = 300 ;Задаем мухе произвольную скорость fly\xv = Rand(-15,15) fly\yv = Rand(-15,15) ;Загружаем изображение мухи fly\image = LoadAnimlmage("fly.bmp®,64,64,0,4) Как вы видите, тип мухи намного облегчает задание всех переменных, которые к ней относятся. Мы начинаем с того, что помещаем муху в центре карты, указав ей произвольные координаты и зах рузив ее изображение в предыдущем разделе
Следующий отрывок, который я изменил, находится перед главным циклом. Я добавил часть кода, который создаст таймер. ;Создаем точку отсчета timerbegin = MilliSecs() ;Создаем переменную, которая не позволяет таймеру восстановиться t interactive = 1 Мы познакомились с тем, для чего нужна точка отсчета (переменная timerbegin), а теперь узнаем, как применяется переменная timeractive . Ее значение равно 1, когда таймер работает правильно, но, если время, указанное в нем, заканчивается, переменная устанавливается в 0. Затем таймер переустанавливается, и переменная timeractive снова становится равной 1. Перейдем дальше к главному циклу. Пропустим обычный процесс очистки заднего фона и вывода соответствующей информации на экран. В этой программе на экране отображаются координаты х и у, а также время, оставшееся на таймере. Следующая часть кода If...Endlf выполняет работу таймера. ;Когда время на таймере заканчивается, скорости полета мухи обновляются If MilliSecs{) >= timerbegin + CHANGEDIRECTIONS /Перемещаем муху произвольным образом fly\xv = fly\xv + Rand(-5,5) fly\yv = fly\yv + Rand(-5,5) /Выключаем таймер timeractive = 0 Endlf Следующий блок начинается с проверки, определяющей, закончилось ли время таймера. Это делается сраянением текущего значения функции Mi 11 i Sec s () со значением MilliSecs (), когда таймер был запущен, плюс величина счетчика (CHANGEDIRECTIONS). Если проверка возвращает истину, значит, время таймера вышло. Когда время таймера заканчивается, мтае сообщаются новые случайные значения xv и у v скоростей, которые позволяют ей двигаться в других направлениях с различной скоростью. Затем переменная активации таймера устанавливается наб. Это означает, что таймер становится непригодным и требует восстановления. Код, который его переустанавливает, располагается непосредственно после предыдущего кода и похож на этот: /Если таймер неактивен, переустанавливаем его If timeractive = 0 timer begin = MilliSecs() timeractive = 1
Этот отрывок кода устанавливает начальную точку таймера в текущее значение функции MilliSecs (). Поскольку таймер больше не нуждается в переустановке (по крайней мере, до тех пор, пока не закончится новое время таймера), значение активации таймера устанавливается на 1. Два других изменения из первой программы перенесены во вторую программу этой главы. Первое из них определяет, задевает ли полет мухи стены. Вот его код: ;Проверяем, задевает ли муха стены If fly\x <= 0 Or fly\y > 800 fly\xv = ?fly\xv Endlf If fly\y <= 0 Or fly\y >= 600 fly\yv = ?fly\yv Endlf Этот код следит, чтобы муха не выходила за пределы экрана, а если это случится, то он задает ей обратное направление. Другим новшеством является добавление в программу значений скорости. В первой программе мы только изменяли координаты полета х и у, а в этой программе мы используем эти координаты вместе со значениями xv и yv скоростей. На рис. 12.2 изображен вид получившейся программы. Отлично, мы подошли к концу первой части этой главы. В следующем разделе вы познакомитесь с двумя простейшими вещами, выполнение которых не так уж и сложно: погоня и маневрирование. Рис. 12.3. Кадр программы demol 2-02.bb
Погоня и маневрирование Итак, теперь, когда мы знаем, как использовать случайные переменные и таймеры, нам нужно познакомиться с тем, как создать искусственный разум, который действительно работает. Погоня и маневрирование - хорошие способы продемонстрировать его способности. Оба просты и интересны: погоня заставляет объект следовать за другим, а маневрирование помогает объекту уходить от другого. Без дальнейших рассуждений, погоня! Погоня Погоня влечет за собой нахождение координат одного объекта и перемещение другого туда, где расположен первый. Может быть, это покажется сложным, но не стоит падать духом. Представьте, что у вас есть один космический корабль, следующий за другим кораблем; фактически мы напишем для них программу, которая сделает это, за несколько минут. Итак, программа начинается с двух кораблей, находящихся на экране таким образом, что корабль А следует за кораблем В. Когда корабль В находится слева от корабля А, то корабль А движется влево. Когда корабль В находится справа от корабля А. то корабль А перемещается вправо. Точно так же происходит, когда корабль В находится выше или ниже корабля А: когда он находится выше, то корабль А двигается вверх, а когда ниже, корабль А опускается вниз. На рис. 12.4 изображена погоня. Рис. 12.4. Слежка, погоня, преследование или охота? Судите сами
Каталог Programs Этот раздел содержит несколько программ, которые вы можете использовать, чтобы помочь себе в завоевании игрового мира. Включены демонстрационные файлы для следующих программ. BlitzPIus - программа, в которой фактически вы будете писать и компилировать исходный код из книги. Обеспечьте прежде всего установку этой программы. Blitz3D - трехмерная версия программы BlitzPIus. Проверьте ее, вы можете найти трехмерные возможности приятными. Blitz Basic 2D - демонстрационная версия для первоначальной 2-й версии программы. Некоторые файлы могут работать лучше с этой версией. BlitzMax - измененная версия BlitzPIus, которая работает с компьютерами Macintosh. Jasc Paint Shop Pro - художественная программа, очень похожая на Microsoft Paint, но намного лучше. MilkShape 3D - разработчик трехмерных моделей для более продвинутых методов. CoolEdit Pro - программа для редактирования звука. Итак, вотто, что касается этого компакт-диска. Наслаждайтесь всем, что прилагается на этом компакт-диске!
Так как для всех операций в этой книге мы использовали космические корабли, давайте, используя их, сделаем погоню в игре с помощью программы Blitz Basic. ;demol2-03.bb - Демонстрирует алгоритмы погони Graphics 800,600 ; Задание точки привязки и вторичного буфера SetBuffer BackBufferО AutoMidHandle True ;ИЗОБРАЖЕНИЯ ;Корабли игрока и противника playership = Loadimage("spaceship.bmp") enemyship = Loadimage("enemyship.bmp") ;Загружаем изображение заднего фона backgroundimage = Loadimage("scars.bmp") ;КОНСТАНТЫ ;Следующие константы используются при проверк нажатий клавиш Const ESCKEY = 1, UPKEY = 200, LEPTKEY = 203, RIGHTKEY = 205, DOWNKEY =208 ;Следующие константы определяют, насколько быстро передвигаются игрок и противник Const PLAYERSPEED = 10 Const ENEMYSPEED = 5 ;Помещаем игрока внизу центра экрана playerx = 400 playery = 400 ;Помещаем противника вверху центра экрана enemyх = 400 enemyy = 200 ;Задаем переменную прокрутки scrolly - 0 .-ГЛАВНЫЙ ЦИКЛ While Not KeyDown(ESCKEY) гЗамостим изображение заднего фона
Каталог Programs Этот раздел содержит несколько программ, которые вы можете использовать, чтобы помочь себе в завоевании игрового мира. Включены демонстрационные файлы для следующих программ. BlitzPIus - программа, в которой фактически вы будете писать и компилировать исходный код из книги. Обеспечьте прежде всего установку этой программы. Blitz3D - трехмерная версия программы BlitzPIus. Проверьте ее, вы можете найти трехмерные возможности приятными. Blitz Basic 2D - демонстрационная версия для первоначальной 2-й версии программы. Некоторые файлы могут работать лучше с этой версией. BlitzMax - измененная версия BlitzPIus, которая работает с компьютерами Macintosh. Jasc Paint Shop Pro - художественная программа, очень похожая на Microsoft Paint, но намного лучше. MilkShape3D - разработчик трехмерных моделей для более продвинутых методов. CoolEdit Pro - программа для редактирования звука. Итак, вот то, что касается этого компакт-диска. Наслаждайтесь всем, что прилагается на этом компакт-диске!
TileBlock baclgroundimage,0,scrolly /Передвигаем задний фон немного вверх scrolly = scrolly + 1 /Если прокрутка получится слишком быстрой, переделайте ее If scrolly > ImageHeight(backgroundimage) scrolly = 0 Endlf ;Проверка нажатий клавиш игроком /Если игрок нажимает клавишу "вверх", мы перемещаем его вверх If KeyDown(UPKEY) playery = playery - PLAYERSPEED Endlf /Если игрок нажимает клавишу "влево", мы перемещаем его влево If KeyDown(LEFTKEY) playerx = playerx - PLAYERSPEED Endlf /Если игрок нажимает клавишу "вправо", мы перемещаем его вправо If KeyDown (RIGHTKEY) playerx = playerx + PLAYERSPEED End If /Если игрок нажимает клавишу "вниз", мы перемещаем его вниз If KayDown (DOWNKEY) playery = playery * PLAYERSPEED Endlf /Теперь перемещаем противника в зависимости от положения игрока /Если игрок находится над противником, переместим противника вверх If playery > enemyy enemyy = enernyy + ENEMYSPEED Endlf /Если игрок находится слева от противника, переместим противника влево
Программирование игр If playerx < enemyх enemyx = enemyx - ENEMYSPEED Endlf ;Если игрок находится справа от противника, переместим противника правее If playerx > enemyx Enemyx = enemyx + ENEMYSPEED Endlf {Если игрок находится ниже противника, переместим противника еще ниже If playery < enemyy eneniyy = enemyy - ENEMYSPEED Endlf .-Выводим игрока и противника на экран Drawlmage playership,playerx,playery Drawlmage enemyship,enemyx,enemyy ;Депаем небольшую паузу Delay 25 ;Переключаем первичный и вторичный буферы Flip Wend ;КОНЕЦ ГЛАВНОГО ЦИКЛА Согласитесь, программа довольна забавная, когда наблюдаешь за передвижениями кораблей на экране. Куда бы вы ни направлялись, этот приставучий космический корабль следует за вами! На снимке экрана, сделанного с файла demo12-03.bb, изображена нешуточная погоня (рис. 12.5). Я хочу остановиться подробнее на одной части программы: отслеживание. Код выглядит примерно так: ;Теперь перемещаем противника в зависимости от положения игрока {Если игрок находится под противником, переместим противника ниже If playery > enemyy enemyy - enemyy + ENEMYSPEED Endlf ;Если игрок находится слева от противника, переместим противника левее If playerx < enemyx
Рис. 12.5. Захватывающая погоня enemyx - enemyx - ENEMYSPEED End If ;Если игрок находится справа от противника, переместим противника правее If playerx > enemyx enemyx = enemyx + ENEMYSPEED Endlf ;Если игрок находится выше противника, переместим противника еще выше If playery < enemyy enemyy = enemyy - ENEMYSPEED Endlf Давайте начнем с первой строки -If playery > enemyy. Для чего она служит? Этот код сравнивает координату у игрока с координатой у противника. Поэтому, чем выше на экране располагается объект, тем меньше значение координаты у (помните, на самом верху экрана координата у = 0). Когда переменная playery больше, чем enemyy (это проверяется первыми операторами If...Endlf), игрок располагается ниже противника. Следовательно, враг перемешается немного ниже. То же самое получается в следующей проверке If-Endlf. Когда переменная playerx меньше enemyx, игрок находится слева от противника, и тот двигается влево. Когда переменная playerx больше, чем enemyx, игрок располагается справа от противника, сам же противник двигается вправо. И, наконец, если переменная playery меньше enemyy, противник направляется вниз. Ну, вот и все о преследовании. Оставшаяся часть главы посвящена крайне сложному понятию - маневрированию.
Маневрирование Всегда, когда я шучу; мои друзья замечают, что не могут решить, правду я говорю или же обманываю их. Последнее предложение предыдущей части - прямое тому подтверждение. Просто знайте, что маневрирование не «чересчур» сложно, как я вам сказал ранее. Но вы, вероятно, уже и так это поняли, верно? Так или иначе, я уверен, что вы хотите узнать о маневрировании все «от» и «до». Фактически, вы уже делаете это. Маневрирование противоположно погоне, так как противник убегает от вас. На рис. 12.6 изображено, как выглядит маневрирование Во всяком случае, вы уже догадываетесь, как можно записать команды маневрирования. Действия, которые вы совершали в предыдущей программе, - и есть влгоритм преследования, просто изменены плюсы на минусы, а минусы на плюсы’ Программа demo 12-04.bb наглядно показывает маневрирование, котя она почти такая же, как и ее предшественница, demo12-03.bb. Фактически, я сделвл лишь два изменения. Сначала о первом из них ;Теперь мы переместим противника в зависимости от того, где находится игрок ;Если игрок находится под противником, переместим противника вверх If playery > enemyy
enemyy = enemyy - ENEMYSPEED Endlf ;Если игрок находится слева от противника, переместим противника вправо If playerx < enemyx enemyx = enemyx + ENEMYSPEED Endlf ;Если игрок находится справа от противника, переместим противника влево If playerx > enemyx enemyx = enemyx - ENEMYSPEED Endlf ;Если игрок находится выше противника, переместим противника вниз If playery < enemyy enemyy = enemyy + ENEMYSPEED Endlf Это кажется вам знакомым? По суш, код этой программы такой же, как и файла demo12-03-bb, только плюсы и минусы переброшены. Теперь, когда противник справа от игрока, он продолжает двигаться вправо. Когда враг слева, он двигается влево. Когда игрок над противником, тот двигается вниз, а, если ниже противника, то последний перемещается вверх. Еще я добаяил новый фрагмент кода в программу. Он следит за тем, чтобы вражеский корабль не вышел за пределы экрана, что может произойти, когда он спасается бегством от игрока. ;Если противник выйдет за пределы экрана, возвратим его назад на экран If enemyx <= О enemyx = О Elself enemyx >= 800 enemyx - 800 Endlf If enemyy <= 0 enemyy = 0 Elself enemyy >= 600 enemyy = 600 Endlf Этот код проверяет координаты противника, прослеживая, на экране он или нет. Если противник покинет пределы экрана, код не позволит двигаться ему дальше в том же направлении и вернет его на экран.
Заключение Великолепно, нс правда ли? Всего одна глава, а мы уже сделали настоящую игру. В ней мы слегка затронули понятия об искусственном разуме, Существует множество (подчеркиваю, МНОЖЕСТВО) друтх вещей, которые мы могли бы сделать с искусственным интеллектом, большинство из них действительно интересны и захватывающи. Некоторые программисты находят разные способы моделирования генома человека (генетические влгоритмы) и даже человеческого мозга (нейронные сети)! Продолжая практиковаться в искусственном разуме, вы, вероятно, столкнетесь с некоторой неуверенностью в собственных представлениях о программировании. Просто имейте в виду, что в настоящее время практически невозможно сделать компьютер по-настоящему мыслящим. Ваша работа, как программиста, заключается в том, чтобы компьютер казался думающим. Если это можно осуществить, значит, ваша задача считается выполненной. В этой главе мы познакомились со следующими понятиями: случайные переменные; таймеры; преследование; маневрирование. Надеюсь, вы сможете применить полученные знания в ваших предстоящих играх и программах. Если вы хотите больше узнать об искусственном разуме, ознакомьтесь с книгой “Технологии искусственного разума» Мэта Бакленда (Mat Buckland) (издательства Premier Press, ISBN: 1-931841-08-Х).
ГЛАВА 1 3 Последний рубеж: игра «Захватчики!!!» Вы готовы? Спрашиваете, к чему? Заключительная игра - последняя программа! Когда я писвл эту главу, то планировал скопировать и вставить в кишу весь источник программы, а затем от шага к шагу постепенно объяснить содержание ее кода. К несчастью, весь код занимает 17 страниц. Правда, 17 страниц! Другими словами, полный источник программы находится на компакт-диске! Поэтому мы остановимся только на наиболее важных частях и алгоритмах этого кода. Я не буду объяснять простые места, а расскажу подробнее о тех моментах, выполнение которых требует особой внимательности. Большинство предпринимаемых действий интуитивно понятно и не требует особых комментариев. Хочу дать рекомендацию. Читая эту главу, ПОЖАЛУЙСТА, держите копию источника кода открытой! Этот файл называется invaderz.bb и находится на компакт-диске. Просматривать код файла источнике целиком будет намного легче, и вы лучше поймете важные моменты. Давайте разберемся: планирование игры Итак, я знал, что мне нужно сделать игру для заключительной программы в книге. Как вы думаете, какую я выбрал? Догадались: космическую «стрелялку». Я решил, что хочу сделать игру, похожую на Space Invaders (Космические захватчики) (если вы играли в нее раньше, то знаете, о чем я говорю). Суть игры заключает ся в том, что игрок стреляет во вражеские летающие тарелки, как только они появляются на экране. Игрок управляет человеческим кораблем, а противник - инопланетными кораблями. Сейчас я немного расскажу о том, как создавал игру, а затем мы вместе поговорим о деталях.
Прежде всего, я спланировал, как будет выглядеть игра. Она была спроектирована так, что вражеские летающие тарелки появляются наверху экрана, а игрок находится внизу. Как только враги появляются на экране, игрок стреляет в них, и, если попадет, они взрываются. Мой эскиз игры изображен на рис. 13.1. В действительности набросок был сделан карандашом, а потом перерисован в книгу. Затем я построил основу игры, дающую представление о том, как она должна выглядеть и действовать. Заметьте, что я сделал строку, показывающую здоровье и сумму выпущенных выстрелов/попаданий в противника. Это позволило больше времени уделить на саму игру, а не на размышления, как будет выглвдеть строка состояния, когда игра будет написана. Теперь, когда план игры готов, я создал изображения, которые будут в ней использоваться. Самыми важными из них, конечно, были изображения игрока и противника. Битовое изображение противника показано на рис. 13.2, а изображение игрока на рис. 13.3. Рис. 13.2. Битовое изображение противника Рис. 13.3. Битовое ивображение игрока
Эти изображения были использованы в игре. Как вы видите, они оживлены, однако, их передвижения созданы разными способами. Битовое изображение противника имеет довольно простой анимационный стиль, оно всего лишь повторяет все циклы от первого кадря до последнего. Я имею в вид>, что, когда вражеский корабль двигается, его кадры проигрываются последовательно. Первый кадр игрового цикла является первым кадром космического корабля, второй кадр игрового цикла является вторым кадром космического корабля, и так будет продолжаться до последнего кадра изображения вражеского корабля (10-й кадр). Одиннадцатым кадром игрового цикла снова будет первый кадр битового изображения, и весь процесс повторится. Битовое изображение игрока сделано немного по-другому Допустим, нам нужно, чтобы корабль игрока наклонялся влево, когда он двигается влево, и вправо, если корабль перемещается вправо. Оставаясь же неподвижным, корабль будет в ровном положении. Давайте используем какой-нибудь интересный код, чтобы осуществить это. Прежде всего, загрузим битовое изображение, как вы догадываетесь, командой LoadAnimlmage (). Global playerimage = LoadAnimlmage("player.bmp",35,32,0,13) Из этой команды следует, что каждый кадр битового изображения имеет размеры 35 х 32 пиксела (это действи гельно так), аколичество всех кадров равно 13 (тоже является правдой). Как видите, первый кадр расположен не в ровном положении - корабль наклонен влево. Нам нужно, чтобы корабль игрока стоял ровно в седьмом кадре. Последующие кадры пусть увеличивали бы его наклон (наклоняли вправо), когда игрок нажимает клавишу Right, и, наоборот, уменьшали наклон кадров (наклоняя их влево). если юрок нажимает клавишу Left. Код установки в ровное положение седьмого кадра, коща не нажата ни одна клавиша, выглядит примерно так: playerXframe - 7 Затем, если игрок нажмет клавишу Right, код будет записан следующим образом: ;Наклон игрока вправо playerXframe = playerXframe * 1 ;Ограничивает максимальный угол наклона корабля If playerXframe >= 12 playerXframe = 12 Endlf Что у нас теперь получилось? Заметьте, этот код действует в пределах блока проверки нажатия правой клавиши, то есть проверка осуществляется, когда нажата правая кл» виша. Наклон изображения в кадре будет увеличиваться с каждым последующим кадром, пока правая клавиша будет нажата. Код проверки нажатия правой клавиши охватывает предыдущий код и не рассматривается в книге. Код ограничивает, как высоко может подняться playerXframe, поскольку всего 13 кадров с изображениями космического корабля игрока.
Почему мы проверяем, когда переменная frame больше или равна 12, вместо 13? Вспомните, отсчет кадров начинается с 0, следовательно, заключительным будет 13—1, или 12-й кадр. Хорошо, пора заканчивать раздел планирования. Существует еще множество вещей, которые нужно сделать, например выбрать, откуда будут появляться враги, сколько их будет на каждом уровне и тому подобное. Если вы хотите увидеть, как я это реализовал, откройте файл программы invaderz.bb находящийся на компакт-диске. Он содержит источник и комментарии, которые помогут вам понять код моих действий при написании игры. Между прочим, я назвал ее Invaderz!!! (Захватчики!!!). Почему? Это прекрасное название. Само по себе оно очень значительное, потому что содержит не один, не два, а целых три восклицательных знака! Переменные, функции и типы в игре «Захватчики!!!» Давайте перейдем к рассмотрению каждой из переменных, постоянных, функций и типов в игре Захватчики!!! Прежде всего, остановимся на константах. В игре существует только несколько констант, но они очень важны. В табл. 18.1 приведен весь список используемых констант и их описание. Табл. 13.1. Константы в игре Захватчики!!! Константа Значение Описание ESCKEY 1 Код для клавиши Esc SPACEBAR 57 Код для клавиши Spasebar LEFTKEY 208 Код для клавиши Left arrow RIGHTKEY 205 Код для клавиши Right arrow CHANGEENEMYDIRECTION 700 Время (в миллисекундах) между изменением скорости вражеских тарелок TIMEBETMEENENEMYBULLETS 1200 Время (в миллисекундах) между выстрелами противника Я уверен, что вы поняли, как работают первые четыре клавишных кода (напоминаю, они используются в функциях KeyDown () и KeyHit ()), но, думаю, не знаете, для
чего нужны константы CHANGEENEMYDIRECTION И TIMEBETWEENENEMYBULLETS. Остановимся подробно на каждой из них. В этой программе движения противников произвольны. Нам нужно передвигать летающие тарелки в случайном направлении. Действительно, мы не хотим знать, в каком месте они появятся, однако нам необходимо отрегулировать переменные для каждого из этих направлений, которые их определяют. Константа CHANGEEIJEMYDIRECTION определяет время между изменениями направлений. Каждая летающая тарелка меняет свое направление каждые 700 миллисекунд (или 7/10 секунды). Постоянная TIMEBETWEENENEMYBULLETS делает практически то же самое. Она определяет, сколько времени длится пауза между двумя выстрелами противника. Вместо 700 миллисекунд, как константа CHANGEENEMYDIRECTION, она показывает, что промежуток между выстрелами врага равен 1,200 миллисекунд (11/5 секунды или 1,2 секунды). Классно? Давайте теперь рассмотрим функции Большинство из них приведены в табл. 13.2. Табл. 13.2. Функции игры Захватчики!!! Функция Описание InitializeLevel{) Восстанавливает уровень с присущим ему количеством врагов и со всеми стартовыми переменными DrawHUDO Отображает оставшееся здоровье и выстрелы/ попадания вверху экрана CreateNewEnemy' ’ Создает новый вражеский корабль на экране DrawShips() Показывает космические корабли противника и игрока EnemyAl() Обновляет направления и выстрелы кораблей противника CreateBullet( Создает на экране новый выстрел UpdateBullets() Управляет выстрелами и следит за их попаданием в корябли каждого из противников CreateExplosion() Создает взрыв после столкновения корабля со снарядом UpdateExplosions() Внедряет взрывы в эти кадры и удаляет их. когда они закончатся GameOver() Готовит игру для выхода и перемещения на рабочий стол Не все эти функции вызываются в главном цикле, поэтому я сделал схему функций, которая объясняет, как они взаимодействуют друг с другом (рис. 13.4). Вот и все, что нужно знать о функциях. И в заключение рассмотрим, как использованы типы в игре Захватчики!!!.
Рис. 13.4. Схема взаимодействия функций в игре Захватчики!!! Следуюгцие четыре типа, которые применяются в игре: тип ship; тип user; тип bul let; тип explosion. Тип ship относится ко всем противникам, которые созданы в течение игры, типом user является космический корабль игрока, находящийся на экране. Тип bullet описывает каждый выстрел, сделанный во время игры (как противником, так и игроком). а тип explosion относится ко всем взрывам, которые происходят после уничтожения корабля противника или игрока Я сделал таблицу со всеми полями типов, в том порядке, в котором они используются в программе (табл. 13.3). Табл. 13.3. Поля типа sh Лоле Описание X Координата х корабли У Координата у корабля
Табл.13.3. (окончание) Поле Описание hi ts Очки корабля XV Переменная х направления, которая определяет, как далеко влево и вправо двигается корабль в кадре Yw Переменная у направления, которав определяет, как далеко вверх и вниз двигается корабль в кадре frame Кадр анимированного изображения, который должен быть выведен (см. рис. 13.2) В табл 13.4 приведен список областей типа user, которые используются игроком. Табл. 13.4. Голя типа user Поле Описание X Координата х игрока У Координата у игрока hits Очки игрока frame Кадр анимированого изображения, который должен быть выведен draw Определяет, должен игрок быть выведен на экран или нет. Если он находится там, то это значение устанавливается на 1. а если нет - 0 Заметьте, что типы ship и user очень похожи друг на друга. Причина этого заключается в том, что они оба являются космическими кораблями, которые, хотя и находятся но разные стороны баррикад, перемешаются одинаковыми способами. Их сходство напоминает мне героев и суперзлодеев комиксов: суперзлодеи почти такие же. как герои, фактически, они часто являются друзьями, становясь взрослыми. В табл. 13.5 приведен список областей типа выстрела. Табл. 13.5. Поля типа bullet Поле Описание X Координата х выстрела У Координата у выстрела
Табл. 13.5. (окончание) Поле Описание draw Определяет, должен ли быть выведен выстрел на экран или нет. Выстрел выводится, если это значение устанавливается на I, и не отображается, если значение устанавливается на 0 from Определяет, кто произвел выстрел. Значение устанавливается на 1. если выстрел сделал пользователь, а когда значение устанавливается на 2, значит, выстрел сделал противник И последняя, но не менее важная таблица со списком областей типа взрыва (табл. 13.6). Табл. 13.6. Поля типа explosion Поле Описание X Координата х взрыва У Координата у взрыва from Определяет, какой корабль взорвался. Это значение устанавливается на 1, если взорвался корабль пользователя, а когда значение устанавливается на 2, значит, взорвался корабль противника from Кадр анимированного изображения, который должен выводиться У-ух! Теперь давайте перейдем к практической игре. Игра Захватчики!!! Мы прошли через все этапы создания игры (или, по крайней мере, проследили) и теперь можем перейти к самой приятной части: опробовании игры в действии! Игра Захватчики!!! является очень простой. Существуют два способа, чтобы открыть ее, каждый из которых требует перехода в папку Chapter 13, находящуюся на компакт-диске. Вставьте компакт-диск в привод, найдите папку Source (Источник), дважды щелкните па ней, чтобы открыть, и найдите папку Chapter 13, которая в ней содержится. Проделав это, вы увидите группу1 файлов, имеющих отношение к игре Захватчики!!!. Чтобы запустить игру, дважды щелкните на значке, который изображает ракету. Этот файл называется invaderz.exe. Можно запустить игру и другим способом: открыть ее компилятором программы Blitz Basic и непосредственно составить код.
Найдите файл invaderz.bb и дважды щелкните его. Он загрузится в компилятор программы Blitz Basic всего за несколько секунд. Посмотрите на верхнюю часть экрана, ивы найдете панель меню, начинающуюся с меню File (Файл). Выберите меню с названием Program (Программа) и щелкните в открывшемся списке доступных действий команду Run Program (Запустить программу). Сделать это можно также нажатием клавиши F5 на клавиатуре (после загрузки программы Blitz Basic). Если вы хотите запустить игру с помощью компилятора программы Blitz Basic, то вам сначала необходимо установить эту программу на ваш компьютер. Возможно, вы ее уже установили ранее, но если еще нет, то сделайте это сейчас. См. главу 1 с инструкцией по установке компилятора. Итак, вы открыли игру. Теперь можете играть в свое удовольствие. В программе существуют только три функциональные клавиши, которые приведены в табл. 13.7. Табл. 13.7. Клавиши управления в игре Захватчики!!! Клавиша Описание Left Arrow Перемещение корабля игрока влево Right Arrow Перемещение корабля игрока вправо Spacebar Произведение выстрела Позвольте дать вам несколько советов, как играть в игру Захватчики!!!. Между прочим, заметьте, что вы не можете «обыграть» игру. Она непрерывно усложняется и усложняется до тех пор, пока игрок не погибнет. Постарайтесь оставаться на одном месте и стрелять так быстро, как только возможно. Однако, когда один из неприятелей стреляет в вас, попробуйте увернуться. Помните, что снаряды повреждают противника, не только когда попадают прямо в него, но и когда противник натыкается на них. Выпуская снаряд, постара<гтесь определить, как быстро противник передвигается. Часто, следя за направлением движения противника, вы можете выпускать снаряд по его траектории или наперехват. Вопреки предыдущему совету помните, что скорости каждого корабля меняются каждые 7/10 секунды. Если летающие тарелки быстро передвигались влево, то их маршрут может вдруг легко измениться вверх. Если корабли очень медленно перемещались вправо, они могут поменять направление с точностью до наоборот! Это все, что необходимо знать об игре Захватчики!!!. На рис. 13.6 и 13.6 изображены кадры игры.
Рис, 13.5. Заставка игры Захватчики!!! Рис. 13.6. Игра Захватчики!!! Эпилог Игра закончена, и книга тоже подошла к концу. Я получил большое удовольствие, пройдя весь этот путь с вами. Надеюсь, то, чему я вас научил, поможет вам достичь новых высот в игровом программировании и в жизни. Я знаю, что эти фразы звучат как клише, но я действительно хочу, чтобы вы пользовались полученными знаниями для создания новых игр. Поговорим о будущем игрового программирования, которое, я надеюсь, связано с вами. Если вам понравилось то, чем мы занимались в этой книге, вы должны знать, что существует еще множество приемов, которым стоит научиться. В Приложении В приведен список адресов сайтов, на которых вы можете найти интересующую вас информацию о программировании. Экспериментируйте с компилятором языка программы Blitz Basic и создавайте собственные игры. Поверьте мне, лучший способ осво-ить-это - практиковаться.
Blitz Basic - замечательный язык для знакомства с программированием. Теперь, когда у вас есть необходимые навыки, вы сможете понимать большинство предпринимаемых действий, даже если перейдете на другие языки программирования. Некоторые понятия, такие как циклы и функции, накрепко засели в вашей голове после этой книги и уже не кажутся столь невыполнимыми, когда применяются в различных языках. Так как вы освоили программу Blitz Basic, существуют еще два пути, предлагаемые для рассмотрения. Первый из них - это переход на трехмерное игровое программирование с использованием программы Blitz3d. Этот язык сделан теми же людьми, которые придумали Blitz Basic. Он довольно сложный, но вещи, которые вы сможете делать с помощью программы Blitz3d, просто изумительны. Вы сможете создать в игре целый мир с людьми, домами и тому подобным. Удивительно. Альтернатива - оставить в стороне все языки Blitz. Существует язык программирования, называемый С (и его преемник, язык C++), который является наиболее употребительным языком для создания и практического опубликования игр. Преимущество языка С перед Blitz заключается в том, что он является более мощным языком в аппаратном смысле для выполнения функций, а также более быстрым и удобным. Вы можете задуматься о выборе книги для знакомства с языком С или C++ (С был первым языком программирования, который я когдато освоил сам). Как вы хорошо знаете, жизнь - это просто лабиринт дорог. Вы сами выбираете свою дорогу, которая ведет вас за собой. Сделайте свой выбор в пользу продолжения занятий программированием, продолжения создания игр. Только от вас зависит, будете вы получать удовольствие от того, чем занимаетесь ияи нет. Это просто. Так или иначе, моя тирада закончена. ТЬпсрь я хочу услышать вас. Я буду рад помочь вам с играми или программами, которые вы сделаете. Если захотите, чтобы я оценил их или помог вам, просто пришлите программу мне на электронную почту: maneesh@maneeshsethi.com. Я хочу также, чтобы вы посетили мой Web-сайт: http://www.maneeshsethi.com и присоединились к группе единомышленников. Там вы найдете форумы по обсуждению различных тем, в том числе форум об этой книге. И последнее: если вам понравилась эта книга, оставьте отзыв на сайте www.amazon.com1 Это действптетьно помогает продажам! (Расскажите об этом также своим друзьям.) Я собираюсь организовать в ближайшем будущем конкурс на моем сайте. Представьте на конкурс свою лучшую игру, и вы сможете выиграть бесплатную книгу или. если по-настоящему повезет, фотографию, подписанную миой. Да, еще одна вещь. Убедитесь, что вы ознакомлены с другими моими кишами. Одна из них называется «Web-дизайн для подростков» и рассказывает, как создавать Web-сайты, а другая книга, «Как достичь цели ленивому студенту», поможет вам научиться побеждать в школе, не прилагая усилий. Следите за выпуском новых книг, которые я напишу. Я буду рад услышать ваши отклики, поэтому нс стесняйтесь писать мне на электронную почту'. Итак, точный адрес я привел, и, если хотите, просто черкните мне: «Привет!». «Самый большой когда-либо обман дьявола состоял в убеждении мира, что он не существует. И так он со «свистом» и пропал». Это обо мие. Ваш Маниш Сети.
Часть 4 I Приложения Приложение А Справочник кодов клавиш................371 Приложение В Полезные ссылки....................379 Приложение С Содержание компакт-диска...........381
Приложение А Справочник кодов клавиш Это приложение содержит список всех кодов опроса, которые вы можете использовать для обработки ввода в ваших программах. Коды опроса используются в функциях типа KeyHit () или KeyDown () примерно так: KeyDown (scancode) Введите код опроса для клавиши, которую вы хотите проверить, и эта функция возвратит значение 1, если клавиша была нажата. Многих из приведенных ниже клавиш не окажется на вашей клавиатуре; некоторые из них являются международными клавишами (подобно символу для иены), а некоторые из них существуют только на расширенных клавиатурах, которые имеют дополнительные клавиши (подобно клавише «калькулятор»). Так или иначе, вы можете найти любую клавишу, которую вы когда-либо думали использовать, в этом списке, показанном в табл. А. 1.
Табл. А.1 Список кодов опроса Клавиша Скан-код Комментарии ESCAPE 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 0 11 Минус (2) 12 На главной панели Знак равенства (=) 13 Возвращение курсора на одну позицию 14 Клавиша возвращения курсора на одну позицию Табуляция 15 Q Ifi W 17 Е 18 R 19 Т 20 Y 21 и 22 1 23 О 24
Клавиша Скан-код Комментарии Р 25 Левая квадратная скобка (() 26 Правая квадратная скобка (I) 27 Return/Ввод (Enter) 28 Return ''Ввод (Enter) на главной панели Левый Ctrl 29 А 30 S 31 D 32 F 33 G 34 Н 35 J 36 К .37 L 38 Точка с запятой (;) 39 Апостроф (’) 40 * Гравис 41 Диакритический знак в виде черты над гласной буквой, левый конец которой выше правого Левый Shift 42 Обратная косая черта (\) 43 Z 44 X 45 с 46 V 47
Табл. А.1(продолжение) Клавиша Скан-код Комментарии В 48 N 44 М 50 Запятая (,) 5] Точка (.) 52 На главной панели Косая черта (/) 58 На главной панели Правый Shift 54 Звёздочка (*) 55 На цифровой панели Левый Alt 56 Spasebar 57 Двоеточие 58 F1 59 F2 60 F3 61 F4 62 F5 63 F6 64 F7 65 F6 66 F9 67 F10 68 NumLock (выключение-чисел) 69
Клавиша Скан-код Комментарии ScrollLock 70 7 71 На цифровой панели 8 72 На цифровой панели 9 73 На цифровой панели Минус (-) 74 На цифровой панели 4 75 На цифровой панели 5 76 На цифровой панели 6 77 На цифровой панели Плюс (+) 78 На цифровой панели 1 79 На цифровой панели 2 80 На цифровой панели 3 81 На цифровой панели 0 82 На цифровой панели Точка (.) 86 На цифровой панели ОЕМ_Ю2 87 На клавиатурах для Великобритании/1ермании F11 87 F12 88 F13 100 (NEC РС98) F14 101 (NEC РС98) F15 102 (NEC РС98) Капа 112 На клавиатурах для Японии ABNT_C1 115 /? На клавиатурах для Португалии (Бразилии)
Табл. А. 1(продолжение) Клавиша Скан-код Комментарии Convert 121 На клавиатурах для Японии NoConvert 123 На клавиатурах д ля Японии Символ иены 125 На клавиатурах для Японии ABNT_C2 126 Цифровая панель на клавиатурах для Португалии (Бразилии) Равно(=) 141 Равно (=) иа цифровой панели (NEC РС98) PrevTrack 144 Предыдущая дорожка (DIK_CIRCUMFLEX) па японской клавиатуре АТ 145 (NEX РС98) Двоеточие (:) 146 (NEC PG98) Подчерк 147 (NEC РС98) Kanji 148 На клавиатурах для Японии Stop 149 (NEC РС98) АХ 150 АХ для Японии Unlabeled 151 (J3100) Next track 153 Следующая дорожка Ввод (Enter) 156 Ввод на цифровой панели Правый Ctrl 157 Mute 160 Выключение звука Calculator 161 Калькулятор Play/pause 162 Играть ''пауза Media stop 164 Останов носителей
Табл. А-1(продолжение) Клавиша Скан-код Комментарии Снижение громкости 174 Звук- Повышение громкости 176 Звук + Web home 178 Домашняя страница Интернет Запятая (,) 179 На цифровой панели (NEX РС98) Косая черта (/} 181 На цифровой панели SysReq 183 Правый Alt 184 Правый All Пауза 197 Пауза Домой 199 Домой па панели стрелок Up arrow 200 Стрелка вверх па напели стрелок Страница вверх/Предыдущая 201 PageUp иа панели стрелок Left arrow 203 Стрелка влево на панели стрелок Right arrow 205 Стрелка вправо на панели стрелок Конец(End) 207 Клавиша End иа панели стрелок Down arrow 208 Стрелка вниз на панели стрелок Следующий (Next) 209 Клавиша Next на панели стрелок Вставка (Insert) 210 Клавиша Insert на панели стрелок Удаление (Delete) 211 Клавиша Delete на панели стрелок Левая Windows 219 Левав клавиша Windows Правая Windows 220 Правая клавиша Windows Apps 221 Клавиша вызова меню приложений Выключение (Power) 222 Выключение питания
Табл. А.1 (окончание) Клавиша Скан-код Комментарии Выключение (Power) 222 Выключение питания Спящий режим (Sleep) 223 Переход в спящий режим Пробуждение (Wake) 227 Выход из спящего режима Поиск в Интернете (Web search) 229 Избранные страницы Интернета (Web favorites) 230 Обновление страницы Интернета (Web refresh) 231 Остановка загрузки страницы Интернета (Web stop) 232 Переход на следующую страницу Интернета (Web forward) 233 Переход на предыдущую страницу Интернета (Web hack) 234 Мой компьютер 235 Почта 236 Выбор носителя 237
Приложение В Полезные ссылки Это приложение перечисляет некоторые ссылки иа материалы, из которых вы могли бы узнать больше относительно программирования игр с помощью программы Blitz Basic, Ссылки на программу Blitz Basic Имеются некоторые чрезвычайно корошие сайты для освоения программирования с помощью программы Blitz Basic. Проверьте форумы иа каждом из них: они активны и полезны. www.maneeshsethl.com - официальный сайт этой книги. Вы найдете на этом сайте обновления к этой книге и обучающим руководствам/программам. Вы можете также связаться со мной непосредственно с этого сайта. www.BlitzBasic.com - официальный сайт программы BlitzPIus. Вы можете найти действующую версию программы BlitzPIus для загрузки (эта программа также включена в компакт-диск) наряду с некоторыми обучающими программами. На этом информационном узле находится наиболее свежая версия справочника команд. Чтобы добраться до справочника команд, идите иа сайт www.blitzbasic.com, щелкните мышью на ссылке Community (Сообщество) и нажмите ссылку Blitz3D Docs (Документы BlitzSD) сразу ниже. Оттуда вы можете выбрать ссылку иа справочник команд двухмерного программирования. www.BlitzCoder.com - превосходный сайт, созданный Джоном Логсдоном (John «Krylar» Logsdon). Этот сайт имеет многочисленные статьи и обучающие примеры по программам Blitz Basic и Blitz3D наряду с очень активным сообществом. Если у вас есть какие-нибудь проблемы с программой Blitz, оставляете сообщение на форуме, и вы быстро получите ответ. Я обещаю.
Ссылки по общему программированию игр Несмотря на то, что количество сайтов по программированию на Blitz Basic ограничено, существует намного больше вебсайтов по общему игровому программированию Очень полезен тот, что приведен ниже. www.GameDev.net - наиболее широко известный и наиболее посещаемый сайт по программированию игр в Интернете. Сайт может похвастать буквально сотнями статей и обучающих программ по кодированию игр. Этот сайт может помочь ввести вас в другие языки, а также обеспечить теорией и понятиями, которые вы можете иегюлы зовать в программировании с помощью Blitz Basic. http://www-cs-students.stanford.edu/-amitp/gameprog.html является местом расположения сайта Amit’s Game Programming Information. Этот сайт - введение в игровое программирование. Оп содержит ответы на некоторые вопросы, которые у вас могут быть, для развития ваших знаний о программировании игр,
Приложение С Содержание компакт-диска Компакт-диск, который находится в конце книги, идет со множеством полезных программ и демонстрационных версий. Разрешите мне объяснить вам все, чго вы найдете, когда загрузите этого малыша. Проверьте содержащие указания фаГыы в каждом каталоге! В них будуг содержаться инструкции и модернизации ко всему материалу на компакт-диске. Структура каталогов для этого компакт-диска прослеживается довольно просто. Вы найдете все расположенным примерно так. Source\ Chapter01\ Chapter02\ Chapter13\ BlitzMax Source\ Art\ Book Art\ Spritelib_Gpl\ Sounds\ Sound\ Music\ Games\ Programs\ Нитке следует объяснение всех этих категорий.
Каталог Source На компакт-диске вы найдете все исходные тексты из примеров книги. Я рекомендую вам скопировать весь код на ваш жесткий диск перед его запуском. Вы не сможете от компилировать исходный текст, если он оставлен на компакт-диске, но перемещение его на жесткий диск устраняет ошибки компиляции. Также включены исполняемые файлы для каждой демонстрационной программы Папка BlitzMax - это перенесенный исходный код для новой программы Blitz, BlitzMax. Если у вас компьютер Macintosh, вы можете использовать демонстрационную версию программы BlitzMax, которая включена в компакт-диск. Приношу благодарность Николасу де Джегеру (Nicolas de Jaeghere) за перенос этого кода. Все перенесено за исключением заключительной игры Invaderz!!!. Каталог Art Я включил раздел, который содержит все изобразительное искусство, которое я использовал в книге, наряду с несколькими другими художественными библиотеками, которые я нашел. Главная папка содержит изображения, созданные Эдгаром Л. Ибаррой (Edgar L. Ibarra) для книги, а подкаталог с именем SpriteIib_Gpl - это библиотека изображений, сделанных Эри Фельдманом (Ari Feldman). Папка Spritelib_Gp1 содержит многочисленные подкаталоги, каждый из которых содержит различные изображения. Особая благодарность Эдгару и Эри за искусство. Каталог Sounds Этот раздел содержит два подкаталога: Sound и Music. Внутри звукового каталога вы найдете многочисленные звуковые эффекты, которые могут использоваться в ваших программах бесплатно. Подкаталог Music имеет несколько файлов музыки в форматах MP3 и MIDI, которые могут также использоваться в ваших программах. Если вы хотите употребить МРЗ-файл для чего нибудь ещё кроме персонального использования (например, если вы решили продавать вашу игру), пожалуйста, свяжитесь с Томасом Стенбэком из компании «Временная Нация» для лицензирования информации. Вы можете связаться с Томасом по адресу электронной почты interfmnation@hotmail.com. Каталог Games Эта папка содержит демонстрационные версии нескольких игр, которые были написаны с помощью программы Blitz Basic. Поиграйте в них, и попробуйте из них чему-нибудь научиться. Особая благодарность Джейсону Брэйзеру (Jason Brasier), Эдгару Ибарре (Edgar Ibarra) и Маркусу «Эйкону» Смиту (Marcus «Eikon» Smith) за эти игры.
Каталог Programa Этот раздел содержит несколько программ, которые вы можете использовать, чтобы помочь себе н завоевании игрового мира. Включены демонстрационные файлы для следующих программ. BlitzPIus - программа, в которой фактически вы будете писать и компилировать исходный код из книги. Обеспечьте прежде всего установку этой программы. Blitz3D - трехмерная версия программы BlitzPIus. Проверьте ее, вы можете найти трехмерные возможности приятными. Blitz Basic 2D - демонстрационная версия для первоначальной 2-й версии программы. Некоторые файлы могут работать лучше с этой версией. BlitzMax - измененная версия BlitzPIus, которая работает с компьютерами Macintosh. Jasc Paint Shop Pro - художественная программа, очень похожая на Microsoft Paint, но намного лучше. MilkShape3D - разработчик трехмерных моделей для более продвинутых методов CoolEdit Pro - программа для редактирования звука. Итак, вот то, что касается этого компакт-диска. Наслаждайтесь всем, что прилагается на этом компакт-диске!
GAME. PROGRAMMING ПРОГРАММИРОВАНИЕ ИГР ч Эта книга научит вас основным приемам программирования собственных игр. Вы научитесь всему, начиная от создания графических компонентов и их анимации, до включения в игру музыки и звуков. В книге даже рассказано, как включить в игру элементы искусственного интеллекта! Чтобы начать чтение книги, от вас не потребуется никакой стартовой подготовки - все, что вам нужно, вы узнаете прямо из книги. А по завершению чтения, вы, с помощью автора, создадите собственную компьютерную игру! Чтобы облегчить вам труд, к книге приложен компакт-диск, в котором вы найдете все программы, необходимые для создания собственной игры, исходные данные для исполнения практических упражнений и множество вещей, полезных для программирования игр - музыку, звуки и высококачественную графику. И 3 ДАТЕЛЬСТВ □ ТРИУМФ THOMSON -------*—------- COURSE TECHNOLOGY Телефон для товароведов: (495) 772 19 56 E-mail: opt@triumph.ru Интернет-магазин www.3st.ru