Туториал "Змейка"
Доброго времени суток! :)
Заметил я что юные программисты часто сталкиваются с проблемой написания простой змейки! Попробуем это исправить, подсказав как это можно реализовать! значит задача: сделать "змейку" как на старом добром карманном "тетрисе"... чтоб она что-нибудь ела и при этом росла, ну и чтобы нельзя было себя за хвост "укусить", оградим игровое поле гранитом, чтоб нашей змее не удалось ее прогрызть и выбраться наружу. (для удобства описания обзовем нашу змею кольчатым червем, и соответственно отдельные "куски" змеи - кольцами.) но прежде чем займемся кодом немножко теории: В наипростейшем случае змейка представляет собой массив ячеек в которых хранятся координаты положения колец змейки т.е. все тело змейки представляет собой примерно такую конструкцию: в ячейке с номером "1" хранится голова змеи, в ячейке под номером "2" - первое кольцо, в "3" - второе и т.п. Наверное вы уже дагадываетесь как будет организовано ползание змеи... всё просто... при перемещении головы змеи за ней должен двигаться весь ее хвост. Но программировать это прийдется детально. Нужно первое кольцо передвинуть на место где раньше была голова, второе кольцо передвинуть туда, где раньше было первое. Третье туда, где было второе и так до саамого конца хвоста. если все это перевести в логику программирования, то получится так, что к координатам ячейки головы присваиваются новые координаты (она передвинулась) затем координаты головы присваиваются координатам первого кольца, координаты первого кольца присваиваются координатам второго кольца и в таком духе обрабатывается весь хвост. схематично это можно отобразить так: вот и весь секрет змейки... но во всем вышенаписанном есть один небольшой недостаток, змейка двигается не плавно... чтобы это исправить, у меня появилась такая идея (может кто делает по-другому, а я делаю так): нужно не сразу координатам первого кольца присваивать координаты головы, а немножко погодя... хммм... но их нужно ведь где-то сохранять... для этого увеличивается количество ячеек змейки, и кольца делаются зависимыми от ячеек через определенное "расстояние", допустим каждая пятая ячейка будет кольцом, а остальные будут промежуточными хранилищами координат... схематично можно отобразить это так: ну теперь уж точно всё! теперь все секреты змеи раскрыты приступим к программированию ;) |
Туториал "Змейка" (продолжение)
Вложений: 2
Приступим к написанию кода:
будем писать используя динамичный массив типов создадим тип массива для змеи: Код:
Type sn h - будет хранить хендл объекта головы или кольца змеи s - будет использоваться только в первом элементе массива, и будет задавать количество "хранилищ" координат между кольцами змеи x, y и z - будут хранить сами координаты кольца, или просто промежуточные координаты. Создадим саму змею т.е. создадим динамичный массив и заполним его первыми характеристиками: Код:
Snake.sn=New sn создадим одно кольцо для змеи: Код:
Snake.sn=First sn далее, переместим индикатор указателя в конец массива и приступим к созданию: запускаем цикл создающий резервные хранилища, и в этом цикле зададим условие (If i%=j%) если цикл идет последний раз, то надо вместо простого хранилища создать само кольцо... на самом деле хранилище отличается от кольца только наличием объекта отображающегося на экране... запишем в s индентификатор какого-нибудь объекта... теперь уже становится интересней... чтобы отобразить змейку на экране, используется такой код: Код:
For Snake.sn = Each sn Ну а теперь самое интересное.. процедура перемещения координат от головы к хвосту... в нормальном случае, используя массив, цикл должен запускаться от конца к началу, чтобы не затирать старые координаты новыми, но за неимением такового цикла в блице, и из-за сложности адресации в типах сделаем все проще и понятней: Код:
x1#=x# сначала сохраняем координаты в каких-нибудь резервных переменных, затем спрашиваем игрока не соизволит ли он нажать какуюнибудь клавишу (если соизволит, то изменим соответствующие координаты). после этого проверяем изменилось ли значение переменных координат, путем сравнения их с резервными переменными, в них ведь старые координаты лежат. Если координаты изменились, то запускаем процедуру перемещения всей змеи: для выполнения процедуры нам потребуется сохранить переменные координат, поскольку после прохождения цикла в них будет не то, что было раньше. сама процедура состоит из одного цикла, который пройдет по всем ячейкам змеи. в цикле: временно сохраним координаты (для следующего шага) (x1#=Snake\x), присвоим новые координаты этой ячейке (Snake\x=x#), и затем присвоим временно сохраненные координаты основным переменным для того, чтобы на следующем шаге цикла присвоить их следующей ячейке массива (x#=x1#)... после прохождения цикла в переменных x, y и z хранятся координаты последнего кольца змеи... восстановим их, чтобы там снова хранились координаты головы змеи (x#=x2#) вот впринципе и всё! теперь собрав весь код вместе уже можно получить змею, которая будет ползать в зависимости от нажатия клавиш... пример рабочего кода на этом шаге в аттаче этого сообщения. |
Туториал "Змейка" (переработка кода)
Доброго времени суток (еще раз)!
сегодня мы продолжим разработку нашего кольцевого червя! но прежде чем мы будем усовершенствовать код, немножко теории: код в нашем теперяшнем состоянии имеет кучу недостатков, первый из них это излишняя занимаемая оперативная память... массив змейки выглядит примерно так: В каждой ячейке есть место под координаты, под хендлы объектов колец змеи и под количество сегментов... Для резервных хранилищ не нужны переменные объектов, и практически для всего массива лежат дополнительные переменные s, используется-то переменная всеголишь единажды, и на протяжении всего массива эта переменная забивается нулями... это очень плохо, занимать много оперативной памяти не используя ее... надо исправить. второй недостаток - то, что если мы по интуиции чтобы увеличить скорость передвижения змеи будем увеличивать ее шаг, то она будет "растягиваться"... решается этот недостаток несколькими путями: можно сделать динамическое количество промежуточных резервных хранилищ, а можно перемещать змейку на равный шаг, но с определенной частотой... возьмем второй метод на вооружение... еще недостаток - весь код в таком состоянии как будто он создан в GWBasic, надо маленько все распихать по функциям, таким добавить всему коду "юзабельность"... |
Туториал "Змейка" (код по функциям)
Вложений: 2
Приступим к коду (все что новое, то красным цветом):
новые типы для змейки выглядят так: Код:
Type sn теперь функции: Код:
Function SnakeCreate%(h%,s%,x#,y#,z#) Код:
Function SnakeAddSegment(id%,h%) Код:
Function SnakeMove(id%,x#,y#,z#) Код:
Function SnakeUpdate() теперь можно создавать не одну змейку, теперь нет лишней переменной s% которая просто так занимала бы место в массиве... кроме этого все стало более приятно глазу и легко в использовании. пример рабочего кода этого шага лежит в аттаче |
Туториал "Змейка" (углы)
Вложений: 2
Посмотрим, что у нас получилось... змея уже умеет ползать, супер! только если бы мы ее сделали из кубиков, то заметили бы, что она не поворачивает голову, и все остальные кольца тоже постоянно находятся в одном положении... это надо исправить... итак к делу!
С углами работа точно такаяже как и с координатами, их надо прописывать голове и потом в процедуру сдвига координат от головы к хвосту добавить сдвиг углов... итак! возьмем клаву в ежевые рукавицы!!! ;) добавим в типы поля для сохранения углов объектов: Код:
Type sn В процедуру создания добавим сохранение текущего угла головы змеи в поля для углов, чтобы в последствии двигать его по всему телу. Код:
Function SnakeCreate%(h%,s%,x#,y#,z#) Не забудем и процедуру добавления сегмента, туда тоже добавим обработку углов: Код:
Function SnakeAddSegment(id%,h%) Процедуру обновления обновляем одной командой установления угла очередному кольцу Код:
Function SnakeUpdate() Ну и наконец процедура сдвига координат... в ней нужно добавить обработку углов... добавляем: Код:
Function SnakeMove(id%,x#,y#,z#) Кроме всего этого добавляем в код функцию поворота головы змеи Код:
Function SnakeSetAngle(id%,ax#,ay#,az#) пример рабочего кода на этом шаге лежит в аттаче |
Туториал "Змейка" (Скорость)
Вложений: 2
И снова доброго времени суток! для читателя пройдет наверное час, а для писателя с последнего урока прошел уже целый день
Продолжим! на сегодня сделаем змее изменение скорости. Как уже упоминалось, если сделать интуитивно, т.е. увеличить шаг змеи, то получится нежелательный эффект "растягивания" змеи. Можно конечно это компенсировать динамичным количеством промежуточных хранилищ координат, если скорость (шаг) увеличивается, то уменьшать количество промежуточных хранилищ и наоборот. Но мы поступим по другому, мы введем время, через которое будет делаться шаг. Получится меньше гемороя в коде и соответственно потратится меньше трудов на его составление ;) поехали: время будет меряться следующим образом: будут две дополнительные переменные, в одной будет хранится текущее время, а в другой сколько его было с самого начала, чтоб можно было не один раз время отмерять... и в самой процедуре передвижения мы будем вычитать их переменной текущего времени единицу, и если переменная стала нулем или меньше, то сделаем шаг и восстановим значение времени на первоначальное взяв его из переменной которая его надежно хранила выжыдав этого момента :) ну хватит теории к коду! добавим в тип вспомагательного массива эти самые две переменные Код:
Type sn Добавим в процедуру создания змеи значение времени, чтобы можно было сразу при создании задать скорость змее Код:
Function SnakeCreate%(h%,t%,s%,x#,y#,z#) Внесем изменения в процедуру движения змеи Код:
Function SnakeMove(id%,x#,y#,z#) здесь я переместил процедуру ротации координат основного массива внутрь цикла поиска змеи (зеленое), чтобы не писать условие времени дважды, да и при таком расположении не будет лишнего "прогона" цикла обработки основного массива при несуществующем идентификаторе... Добавим процедуру изменения скорости, чтобы можно было в программе изменить скорость змеи Код:
Function SnakeSetSpeed(id%,t%) вот и всё! теперь можно изменять скорость передвижения змеи без ее растягивания... пример рабочего кода на этом шаге лежит в аттаче |
Туториал "Змейка" (Игровое поле)
Вложений: 2
Доброго времени суток! Сорри за паузу, но времени в обрез...
сегодня создадим "квадратное" игровое поле, и заставим змею двигаться по квадратам... Ну насегодня большой логики не будет... в основном рутинное программирование, поэтому приступим сразу к разборке сегодняшнего кода... для начала добавим несколько полезных функций: Код:
Function SnakePosition(id%,x#,y#,z#) так, змею больше трогать сегодня не будем, займемся самим игровым полем! создадим его для начала Код:
For i=0 To 15 -5 это корректировка (сдвиг) игрового поля, чтобы позиция 0,0 как раз была в центре первой клетки... оно нам пригодится для дальнейших рассчетов. теперь самое интересное на этот момент: движение змеи! до этого мы управляли ей клавишами, но теперь нам надо чтоб она сама ползала... код управления будет следующим: Код:
speed#=0.7 значит так: вводим дополнительную переменную: Direction%, эта переменная будет отвечать за направление движения... у нас ведь всего 4-х стороннее движение (налево, направо, вверх, вниз), по диагонали нам ездить не надо... для быстрого понятия всего советую представить себе часы: "12" - направление вверх обозначим числом "0", "3" - направление вправо числом "1", "6" - направление вниз, числом "2", 9 - направление влево числом "3"... теперь пишем в главном цикле условия движения змеи, чтоб змея в любом случае кудато передвинулась независимо от того нажали мы какуюто кнопку или нет... если код направления движения равен нулю то двираем змею вверх, если единице, то влево и т.д. далее обработку клавиш... теперь нам надо при нажатии клавиш только менять направления... о передвижении змеи заботится предыдущий блок условий... НО! просто так нам изменять направления нельзя, нам нужно еще изменить угол головы змеи и проверять находится ли змея на середине квадрата игрового поля... "If (SnakeGetX(a) Mod 10)=0" проверяет находится-ли змея на середине квадрата! путем сравнения не пойдет, потому, что если шаг будет равен например 0.3 то попадание на ровное число 10 будет очень редким... поэтому здесь вычисляется остаток от деления на 10 (размер квадрата) если он равен нулю, то змея находится на позиции кратной 10, а такие позиции находятся ровно в центре квадратов (для этого мы и сдвигали игровое поле на -5) таким образом если змея находится на позиции кратной 10, то изменяем ее направление и при этом проверяем угол головы, если голова "повернута" не в том направлении то поворачиваем ее в нужное. и выравниваем позицию змеи... чтобы змея находилась именно посередине квадрата... поскольку на момент поворота она может быть и не в середине. вот, в принципе, и все на сегодня... теперь есть поле, по которому змея может ползать... и поворачивать можно только "по квадратам".. рабочий код на этом шаге лежит в аттаче |
Туториал "Змейка" (ЕДА)
Вложений: 2
Продолжим:
сегодня мы научим нашу змею есть и покажем что в этом мире бывают еще препятствия за которые кусать не желательно... на мнужно както представить весь игровой мир... в простых случаях (как наш) его представляют массивом (в сложном случае базой данных)... представим его трехмерным массивом map в котором: первое измерение - координаты по X, второе измерение - координаты по Y и третье измерение - типы объектов (0 - код объекта, 1 хендл объекта)... третье измерение нужно нам для хранения кода препятствия... у нас ведь будут различные типы препятствий (стена и еда), ну и чтобы в любой случай можно было чтонить с объектом (препятствием) сделать и не искать его или не заводить кучу дополнительный переменных тоже нужно поле массива. НЕ советую юзать проверку на столкновения объектов... поскольку на проверку этого уйдет наааамного больше компьютерного времени, по сравнению с проверкой на наличие элемента в массиве по нужным координатам... значит так: создание мира: Код:
Dim Map%(15,15,1) добавим несколько препятствий Код:
For i=2 To 10 Покажем нашей змее что в этом мире не только она одна бывает ;) Код:
If map(Int(SnakeGetX(a)/10),Int(SnakeGetZ(a)/10),0)=1 Or map(Int(SnakeGetX(a)+9)/10,Int(SnakeGetZ(a)+9)/10,0)=1 Then в коде условие двойное... :sorry: объясняется это отсутствием функции округления... на самом деле функция int() не округляет а отбрасывает все что после запятой и получается что если у нас змея находится по Х координате примерно на 4.5, то физически будет отображено так, что половина головы будет уже в следующей клетке а результат вычисления будет показывать что она еще полностью в предидущей... вот для избежания таких случаев делается такого рода коррекция... заключается она в дополнительной проверке других координат (какбы координат объекта расположенного немножко в другом месте)... Покажем змее, что мир не безграничный, а что это всеголишь карта из 14 на 14 клеток Код:
If SnakeGetX(a)<-3 Or SnakeGetX(a)>143 Or SnakeGetZ(a)<-3 Or SnakeGetZ(a)>143 Then Теперь змея уже чтото видит... займемся едой Заведем переменную FoodT% которая будет определять есть ли еда на поле, создавать второй кусок еды или нет, и будет служить задержкой... т.е. чтоб еда не появлялась сразу как ее только съели (так не интересно играть), а чтоб появлялась немного позже... теперь идем в главный цикл и пишем код создания куска еды: Код:
; ----- Create a Food -------------------------------------------- если FoodT равно нулю, то: или истекло время, или только зашли в игру... приступаем к созданию куска еды... делаем случайные координаты и запускаем цикл генерации новых координат пока по этим координатам не будет ничего в массиве map, чтобы кусок еды нечайно не создался в стене... как его потом оттудова выгрызать?... цикл будет крутиться пока не будут найдены координаты пустой ячейки массива... значит на выходе у нас будут координаты в которых можно создавать еду... создаем объект еды CreateSphere(), красим его в красный цвет, скалим, позиционируем где надо, и записываем в массив map код еды "2" и сам хендл объекта тоже сохраняем, это для того, чтобы при съедании еды объект можно было удалить... и не забываем FoodT% поставить в такое состояние, чтобы эта процедура не вызвалась второй раз... и чтоб не отмерялось время... если мы здесь присвоим чтонить положительное то автоматически запустим таймер и еда будет создаваться не зависимо от того съели ли предыдущую... приступаем к поеданию еды: Код:
If map(Int(SnakeGetX(a)/10),Int(SnakeGetZ(a)/10),0)=2 Or map(Int(SnakeGetX(a)+9)/10,Int(SnakeGetZ(a)+9)/10,0)=2 Then кстати про функцию добавления... ее пришлось маленько переписать... в прошлом мы брали позицию головы и создавали в ней очередной сегмент... теперь нам надо создавать новый сегмент в хвосте... иначе видно мигание куска хвоста в голове... новая процедура выглядит следующим образом Код:
Function SnakeAddSegment(id%,h%) ну вот вроде бы и все... теперь змея умеет есть, видеть препятствия и ей запрещено выходить за границы карты... пример рабочего кода на этом шаге см в аттаче |
Re: Туториал "Змейка"
Спасибо за тутор. Я его внимательно прочитал. Заюзал, но ничего не понял.
У тебя есть просто примерчик змеи? Вот тут: Туториал "Змейка" (код по функциям) - это я заюзал Но только не знаю как изменять резмер змеи!? А вдруг я захочу не сферу а подргужать свои элементы... Вообщем очень прощу разъяснить хотя бы как изменять размер змеи... |
Re: Туториал "Змейка"
Да проделанна немаленькая работа по изучению. Спасибо за тутор!
|
Re: Туториал "Змейка"
Прошу прощения, но
Цитата:
Код:
; Ceil / Floor / Int example, three kinds of rounding. Надеюсь мои комментарии не будут расценены негативно - т.к. я сам заинтерсован в максимальной достоверности наших тутуров |
Re: Туториал "Змейка"
да, деление флоата на 2 работает нормально, ибо флоатом занимается математический роцессор...
для сравнения два римера: Код:
For i=0 To 20 Код:
For i=0 To 20 |
Re: Туториал "Змейка"
Что не отменяет моего утверждения, что фраза:
Цитата:
В Blitz же - 50/50. Отбрасыванием в большую и меньшую сторону занимаются Ceil# ( y# ) и Floor# ( y# ) (не Float - это отдельный разговор) соответственно. Тупое же отбрасывание не получится даже при принудительном преобразовнии типа: Цитата:
Отбросить ненужные знаки можно следующим образом: Цитата:
|
Re: Туториал "Змейка"
да... ступил я :sorry:
ошибка наверное в неверном определении функции... но сути тутора это конечно не меняет... :) |
Re: Туториал "Змейка"
И на старуху бывает проруха ;)
|
Часовой пояс GMT +4, время: 18:16. |
vBulletin® Version 3.6.5.
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Перевод: zCarot