Извините, ничего не найдено.

Не расстраивайся! Лучше выпей чайку!
Регистрация
Справка
Календарь

Вернуться   forum.boolean.name > Программирование игр для компьютеров > Blitz3D > FAQ

FAQ Туториалы и часто задаваемые вопросы

Ответ
 
Опции темы
Старый 19.07.2006, 04:01   #1
SubZer0
Администратор
 
Аватар для SubZer0
 
Регистрация: 03.09.2005
Сообщений: 2,408
Написано 301 полезных сообщений
(для 996 пользователей)
Сообщение Туториал "Змейка"

Доброго времени суток!

Заметил я что юные программисты часто сталкиваются с проблемой написания простой змейки! Попробуем это исправить, подсказав как это можно реализовать!

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

(для удобства описания обзовем нашу змею кольчатым червем, и соответственно отдельные "куски" змеи - кольцами.)

но прежде чем займемся кодом немножко теории:

В наипростейшем случае змейка представляет собой массив ячеек в которых хранятся координаты положения колец змейки т.е. все тело змейки представляет собой примерно такую конструкцию:




в ячейке с номером "1" хранится голова змеи, в ячейке под номером "2" - первое кольцо, в "3" - второе и т.п.

Наверное вы уже дагадываетесь как будет организовано ползание змеи... всё просто... при перемещении головы змеи за ней должен двигаться весь ее хвост. Но программировать это прийдется детально. Нужно первое кольцо передвинуть на место где раньше была голова, второе кольцо передвинуть туда, где раньше было первое. Третье туда, где было второе и так до саамого конца хвоста.

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

схематично это можно отобразить так:




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

схематично можно отобразить это так:




ну теперь уж точно всё! теперь все секреты змеи раскрыты приступим к программированию
__________________
Как минимум я помог многим (с)
(Offline)
 
Ответить с цитированием
Старый 19.07.2006, 04:47   #2
SubZer0
Администратор
 
Аватар для SubZer0
 
Регистрация: 03.09.2005
Сообщений: 2,408
Написано 301 полезных сообщений
(для 996 пользователей)
Туториал "Змейка" (продолжение)

Приступим к написанию кода:

будем писать используя динамичный массив типов

создадим тип массива для змеи:
Type sn
 Field h%
 Field s%
 Field x#
 Field y#
 Field z#
End Type
значения переменных:
h - будет хранить хендл объекта головы или кольца змеи
s - будет использоваться только в первом элементе массива, и будет задавать количество "хранилищ" координат между кольцами змеи
x, y и z - будут хранить сами координаты кольца, или просто промежуточные координаты.

Создадим саму змею т.е. создадим динамичный массив и заполним его первыми характеристиками:
Snake.sn=New sn

Snake\h%=CreateSphere()
Snake\s%=5
Snake\x#=0
Snake\y#=0
Snake\z#=0
вот мы создали голову змеи, в h занесем сам объект для отображения головы, в s запишем сколько будет хранилищ координат до первого кольца. Присвоим голове змеи изначальные координаты, в этом случае это будут 0,0,0 (середина мира)

создадим одно кольцо для змеи:
Snake.sn=First sn
j=Snake\s%
x=Snake\x#
y=Snake\y#
z=Snake\z#

Snake.sn = Last sn
For i%=1 To j
 Snake.sn=New sn
 Snake\x#=x#
 Snake\y#=y#
 Snake\z#=z#
 If i%=j% Then Snake\h%=CreateSphere()   
 k%=k%+1
Next
для создания кольца, нам нужно "спросить" у головы сколько создавать резервных хранилищ... перемещаем индикатор указателя в массиве в самое начало (Snake.sn=First sn) затем присваиваем какойнить резервной переменной количество хранилищ (j=Snake\s%). Я еще сохранял резервно координаты, чтоб змея появилась в одной точке, это не особо-важно, можно не делать.

далее, переместим индикатор указателя в конец массива и приступим к созданию: запускаем цикл создающий резервные хранилища, и в этом цикле зададим условие (If i%=j%) если цикл идет последний раз, то надо вместо простого хранилища создать само кольцо... на самом деле хранилище отличается от кольца только наличием объекта отображающегося на экране... запишем в s индентификатор какого-нибудь объекта...

теперь уже становится интересней... чтобы отобразить змейку на экране, используется такой код:
 For Snake.sn = Each sn
  If Snake\h<>0 Then PositionEntity Snake\h,Snake\x,Snake\y,Snake\z
 Next
Здесь мы просто запускаем цикл, и если в переменной h что-нибудь записано (какойнибудь объект) то позиционируем его согласно новых позиций.

Ну а теперь самое интересное.. процедура перемещения координат от головы к хвосту... в нормальном случае, используя массив, цикл должен запускаться от конца к началу, чтобы не затирать старые координаты новыми, но за неимением такового цикла в блице, и из-за сложности адресации в типах сделаем все проще и понятней:

x1#=x#
y1#=y#
z1#=z#

If KeyDown(200) Then z=z+0.2
If KeyDown(208) Then z=z-0.2
If KeyDown(203) Then x=x-0.2
If KeyDown(205) Then x=x+0.2

If x1#<>x# Or y1#<>y# Or z1#<>z# Then
; rotate snake

x2#=x#
y2#=y#
z2#=z#

 For Snake.sn=Each sn

   x1#=Snake\x
   y1#=Snake\y
   z1#=Snake\z

   Snake\x=x#
   Snake\y=y#
   Snake\z=z#

   x#=x1#
   y#=y1#
   z#=z1#

 Next

x#=x2#
y#=y2#
z#=z2#
в переменных x, y и z хранятся координаты головы

сначала сохраняем координаты в каких-нибудь резервных переменных, затем спрашиваем игрока не соизволит ли он нажать какуюнибудь клавишу (если соизволит, то изменим соответствующие координаты). после этого проверяем изменилось ли значение переменных координат, путем сравнения их с резервными переменными, в них ведь старые координаты лежат.
Если координаты изменились, то запускаем процедуру перемещения всей змеи:
для выполнения процедуры нам потребуется сохранить переменные координат, поскольку после прохождения цикла в них будет не то, что было раньше. сама процедура состоит из одного цикла, который пройдет по всем ячейкам змеи.
в цикле: временно сохраним координаты (для следующего шага) (x1#=Snake\x), присвоим новые координаты этой ячейке (Snake\x=x#), и затем присвоим временно сохраненные координаты основным переменным для того, чтобы на следующем шаге цикла присвоить их следующей ячейке массива (x#=x1#)...

после прохождения цикла в переменных x, y и z хранятся координаты последнего кольца змеи... восстановим их, чтобы там снова хранились координаты головы змеи (x#=x2#)

вот впринципе и всё! теперь собрав весь код вместе уже можно получить змею, которая будет ползать в зависимости от нажатия клавиш...

пример рабочего кода на этом шаге в аттаче этого сообщения.
__________________
Как минимум я помог многим (с)
(Offline)
 
Ответить с цитированием
Старый 19.07.2006, 16:22   #3
SubZer0
Администратор
 
Аватар для SubZer0
 
Регистрация: 03.09.2005
Сообщений: 2,408
Написано 301 полезных сообщений
(для 996 пользователей)
Сообщение Туториал "Змейка" (переработка кода)

Доброго времени суток (еще раз)!

сегодня мы продолжим разработку нашего кольцевого червя!

но прежде чем мы будем усовершенствовать код, немножко теории:

код в нашем теперяшнем состоянии имеет кучу недостатков, первый из них это излишняя занимаемая оперативная память... массив змейки выглядит примерно так:




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

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

еще недостаток - весь код в таком состоянии как будто он создан в GWBasic, надо маленько все распихать по функциям, таким добавить всему коду "юзабельность"...
__________________
Как минимум я помог многим (с)
(Offline)
 
Ответить с цитированием
Старый 20.07.2006, 18:32   #4
SubZer0
Администратор
 
Аватар для SubZer0
 
Регистрация: 03.09.2005
Сообщений: 2,408
Написано 301 полезных сообщений
(для 996 пользователей)
Сообщение Туториал "Змейка" (код по функциям)

Приступим к коду (все что новое, то красным цветом):

новые типы для змейки выглядят так:
Type sn
 Field id%
 Field h%
 Field x#
 Field y#
 Field z#
End Type 

Type snm
 Field id%
 Field s%
 Field x#
 Field y#
 Field z#
End Type
В нашем предидущем коде можно было создать только одну змейку, чтобы их можно было создавать не одну, а более, их нужно както отделять в общем массиве друг от друга, для этого вводим переменную id% которая будет содержать идентификатор змейки... кроме этого создаем еще один параллельный тип, в котором будем хранить координаты головы змеи, чтобы не прочесывать каждый раз основной массив в поисках головы и перемещения ее на новые координаты.

теперь функции:

Function SnakeCreate%(h%,s%,x#,y#,z#)
 id%=1
 For SnakeManager.snm = Each snm
  id%=id%+1
 Next
 SnakeManager.snm=New snm
 SnakeManager\id%=id%
 SnakeManager\s%=s%
 SnakeManager\x#=x#
 SnakeManager\y#=y#
 SnakeManager\z#=z#
 Snake.sn=New sn
 Snake\id%=id%
 Snake\h%=h%
 Snake\x#=x#
 Snake\y#=y#
 Snake\z#=z#
 Return id%
End Function
Создание змеи. Сперва подсчитываем сколько змей уже создано, чтобы новой змее выдать следующий уникальный номер. Здесь создаем голову змеи в основном массиве Snake и во вспомогательном SnakeManager создаем запись с координатами головы змеи. ну и естественно возвращаем номер змеи для последующего использования

Function SnakeAddSegment(id%,h%)

 For SnakeManager.snm = Each snm
  If id%=SnakeManager\id% Then s%=SnakeManager\s%
  x#=SnakeManager\x#
  y#=SnakeManager\y#
  z#=SnakeManager\z#
 Next 

 For i=1 To s
  Snake.sn = New sn
  Snake\id%=id%
  Snake\x#=x#
  Snake\y#=y#
  Snake\z#=z#
  If i=s Then Snake\h%=h% 
 Next

End Function
Добавление кольца змеи. Здесь немножко видоизменено, теперь нам не надо обращаться к первому элементу массива для получения количества промежуточных хранилищ... теперь эта информация хранится в другом массиве... ищем ее там и если есть змея с таким id%, то сохраним во временную переменную s% количество хранилищ. затем создадим нужное количествохранилищ, и самым последним новое кольцо.

Function SnakeMove(id%,x#,y#,z#)
 For SnakeManager.snm = Each snm
  If id%=SnakeManager\id% Then
   SnakeManager\x#=SnakeManager\x#+x#
   SnakeManager\y#=SnakeManager\y#+y#
   SnakeManager\z#=SnakeManager\z#+z#
   x#=SnakeManager\x#
   y#=SnakeManager\y#
   z#=SnakeManager\z#
   Exit
  EndIf
 Next


 For Snake.sn=Each sn
  If id%=Snake\id% Then
   x1#=Snake\x
   y1#=Snake\y
   z1#=Snake\z
   Snake\x=x#
   Snake\y=y#
   Snake\z=z#
   x#=x1#
   y#=y1#
   z#=z1#
  EndIf
 Next

End Function
Прермещение змеи. Этот код тоже немного изменен... теперь у нас нет переменных x, y и z которые хранили у нас координаты головы, теперь это все хранится во вспомогательном массиве. Ищем их там по id% и если есть такая змея с таким id% то добавляем к координатам головы координаты поступившие в функцию и сохраняем новые значения координат в переменные x, y и z для следующего цикла... затем выбираем из массива все части змеи с идентификатором текущей и "продвигаем" координаты от головы к хвосту...

Function SnakeUpdate()
 For Snake.sn = Each sn
  If Snake\h<>0 Then PositionEntity Snake\h,Snake\x,Snake\y,Snake\z
 Next
End Function
Обновление позиций объектов колец змеи. Здесь все без изменений...


теперь можно создавать не одну змейку, теперь нет лишней переменной s% которая просто так занимала бы место в массиве... кроме этого все стало более приятно глазу и легко в использовании.

пример рабочего кода этого шага лежит в аттаче
__________________
Как минимум я помог многим (с)
(Offline)
 
Ответить с цитированием
Старый 21.07.2006, 18:43   #5
SubZer0
Администратор
 
Аватар для SubZer0
 
Регистрация: 03.09.2005
Сообщений: 2,408
Написано 301 полезных сообщений
(для 996 пользователей)
Сообщение Туториал "Змейка" (углы)

Посмотрим, что у нас получилось... змея уже умеет ползать, супер! только если бы мы ее сделали из кубиков, то заметили бы, что она не поворачивает голову, и все остальные кольца тоже постоянно находятся в одном положении... это надо исправить... итак к делу!

С углами работа точно такаяже как и с координатами, их надо прописывать голове и потом в процедуру сдвига координат от головы к хвосту добавить сдвиг углов...

итак! возьмем клаву в ежевые рукавицы!!!


добавим в типы поля для сохранения углов объектов:
Type sn
 Field id%
 Field h%
 Field x#
 Field y#
 Field z#
 Field ax#
 Field ay#
 Field az#
End Type 

Type snm
 Field id%
 Field s%
 Field x#
 Field y#
 Field z#
 Field ax#
 Field ay#
 Field az#
End Type

В процедуру создания добавим сохранение текущего угла головы змеи в поля для углов, чтобы в последствии двигать его по всему телу.

Function SnakeCreate%(h%,s%,x#,y#,z#)
 id%=1
 For SnakeManager.snm = Each snm
  id%=id%+1
 Next
 SnakeManager.snm=New snm
 SnakeManager\id%=id%
 SnakeManager\s%=s%
 SnakeManager\x#=x#
 SnakeManager\y#=y#
 SnakeManager\z#=z#
 SnakeManager\ax#=EntityPitch(h%)
 SnakeManager\ay#=EntityYaw(h%)
 SnakeManager\az#=EntityRoll(h%)
 Snake.sn=New sn
 Snake\id%=id%
 Snake\h%=h%
 Snake\x#=x#
 Snake\y#=y#
 Snake\z#=z#
 Snake\ax#=EntityPitch(h%)
 Snake\ay#=EntityYaw(h%)
 Snake\az#=EntityRoll(h%)
 Return id%
End Function
в процедуру создания змеи поступает идентификатор объекта, из него и берем углы функциями EntityPitch(h%), EntityYaw(h%), EntityRoll(h%) и прописываем их как в основной так и во вспомогательный массив, во вспомагательный заносится для последующей ротации, а в основной, чтоб змея появилась нормально, до первого ее движения. Чтоб функция SnakeUpdate могла сразу обработать созданную змею.


Не забудем и процедуру добавления сегмента, туда тоже добавим обработку углов:
Function SnakeAddSegment(id%,h%)
 For SnakeManager.snm = Each snm
  If id%=SnakeManager\id% Then s%=SnakeManager\s%
  x#=SnakeManager\x#
  y#=SnakeManager\y#
  z#=SnakeManager\z#
  ax#=SnakeManager\ax#
  ay#=SnakeManager\ay#
  az#=SnakeManager\az#
 Next 
 For i=1 To s
  Snake.sn = New sn
  Snake\id%=id%
  Snake\x#=x#
  Snake\y#=y#
  Snake\z#=z#
  Snake\ax#=ax#
  Snake\ay#=ay#
  Snake\az#=az#
  If i=s Then Snake\h%=h% 
 Next
End Function
Здесь мы берем углы из вспомагательного массива SmakeManager (Угол положения головы) и присваиваем его очередному кольцу


Процедуру обновления обновляем одной командой установления угла очередному кольцу
Function SnakeUpdate()
 For Snake.sn = Each sn
  If Snake\h<>0 Then 
   PositionEntity Snake\h%,Snake\x#,Snake\y#,Snake\z#
   RotateEntity Snake\h%,Snake\ax#,Snake\ay#,Snake\az#
  EndIf
 Next
End Function

Ну и наконец процедура сдвига координат... в ней нужно добавить обработку углов... добавляем:
Function SnakeMove(id%,x#,y#,z#)
 For SnakeManager.snm = Each snm
  If id%=SnakeManager\id% Then
   SnakeManager\x#=SnakeManager\x#+x#
   SnakeManager\y#=SnakeManager\y#+y#
   SnakeManager\z#=SnakeManager\z#+z#
   x#=SnakeManager\x#
   y#=SnakeManager\y#
   z#=SnakeManager\z#
   ax#=SnakeManager\ax#
   ay#=SnakeManager\ay#
   az#=SnakeManager\az#
   Exit
  EndIf
 Next
 For Snake.sn=Each sn
  If id%=Snake\id% Then
   x1#=Snake\x
   y1#=Snake\y
   z1#=Snake\z
   ax1#=Snake\ax
   ay1#=Snake\ay
   az1#=Snake\az
   Snake\x=x#
   Snake\y=y#
   Snake\z=z#
   Snake\ax=ax#
   Snake\ay=ay#
   Snake\az=az#
   x#=x1#
   y#=y1#
   z#=z1#
   ax#=ax1#
   ay#=ay1#
   az#=az1#
  EndIf
 Next
End Function
Берем координаты головы из Вспомогательного массива SnakEManager и присваиваем их следующему кольцу (или временному хранилищу)... короче все точно также как и с координатами...

Кроме всего этого добавляем в код функцию поворота головы змеи
Function SnakeSetAngle(id%,ax#,ay#,az#)
 For SnakeManager.snm = Each snm
  If id=SnakeManager\id% Then
   SnakeManager\ax#=ax#
   SnakeManager\ay#=ay#
   SnakeManager\az#=az#
   Exit
  EndIf
 Next
End Function
здесь все просто... ищем во вспомогательном массиве нужную змею (id%) и прописываем углы... в последующем процедура SnakeMove возьмет отсюда эти углы и присвоит их голове а в последствии и всем кольцам тела змеи.


пример рабочего кода на этом шаге лежит в аттаче
__________________
Как минимум я помог многим (с)
(Offline)
 
Ответить с цитированием
Старый 22.07.2006, 22:27   #6
SubZer0
Администратор
 
Аватар для SubZer0
 
Регистрация: 03.09.2005
Сообщений: 2,408
Написано 301 полезных сообщений
(для 996 пользователей)
Сообщение Туториал "Змейка" (Скорость)

И снова доброго времени суток! для читателя пройдет наверное час, а для писателя с последнего урока прошел уже целый день

Продолжим!

на сегодня сделаем змее изменение скорости. Как уже упоминалось, если сделать интуитивно, т.е. увеличить шаг змеи, то получится нежелательный эффект "растягивания" змеи. Можно конечно это компенсировать динамичным количеством промежуточных хранилищ координат, если скорость (шаг) увеличивается, то уменьшать количество промежуточных хранилищ и наоборот. Но мы поступим по другому, мы введем время, через которое будет делаться шаг. Получится меньше гемороя в коде и соответственно потратится меньше трудов на его составление

поехали:

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

ну хватит теории к коду!

добавим в тип вспомагательного массива эти самые две переменные
Type sn
 Field id%
 Field h%
 Field x#
 Field y#
 Field z#
 Field ax#
 Field ay#
 Field az#
End Type 

Type snm
 Field id%
 Field t%
 Field mt%
 Field s%
 Field x#
 Field y#
 Field z#
 Field ax#
 Field ay#
 Field az#
End Type
Переменная t% будет хранить текущее время, а mt% будет хранить изначальное время для последующего его восстановления...


Добавим в процедуру создания змеи значение времени, чтобы можно было сразу при создании задать скорость змее
Function SnakeCreate%(h%,t%,s%,x#,y#,z#)
 id%=1
 For SnakeManager.snm = Each snm
  id%=id%+1
 Next
 SnakeManager.snm=New snm
 SnakeManager\id%=id%
 SnakeManager\mt%=t%
 SnakeManager\t%=t%
 SnakeManager\s%=s%
 SnakeManager\x#=x#
 SnakeManager\y#=y#
 SnakeManager\z#=z#
 SnakeManager\ax#=EntityPitch(h%)
 SnakeManager\ay#=EntityYaw(h%)
 SnakeManager\az#=EntityRoll(h%)
 Snake.sn=New sn
 Snake\id%=id%
 Snake\h%=h%
 Snake\x#=x#
 Snake\y#=y#
 Snake\z#=z#
 Snake\ax#=EntityPitch(h%)
 Snake\ay#=EntityYaw(h%)
 Snake\az#=EntityRoll(h%)
 Return id%
End Function
Занесем значение времени как в текущее. так и в резерв для восстановления, чтобы змея при создании сразу не сорвалась с места, а ждала нужное время


Внесем изменения в процедуру движения змеи
Function SnakeMove(id%,x#,y#,z#)
 For SnakeManager.snm = Each snm
  If id%=SnakeManager\id% Then
   SnakeManager\t%=SnakeManager\t%-1
   If SnakeManager\t%<=0 Then 
    SnakeManager\t%=SnakeManager\mt%
    SnakeManager\x#=SnakeManager\x#+x#
    SnakeManager\y#=SnakeManager\y#+y#
    SnakeManager\z#=SnakeManager\z#+z#
    x#=SnakeManager\x#
    y#=SnakeManager\y#
    z#=SnakeManager\z#
    ax#=SnakeManager\ax#
    ay#=SnakeManager\ay#
    az#=SnakeManager\az#
    For Snake.sn=Each sn
     If id%=Snake\id% Then
      x1#=Snake\x
      y1#=Snake\y
      z1#=Snake\z
      ax1#=Snake\ax
      ay1#=Snake\ay
      az1#=Snake\az
      Snake\x=x#
      Snake\y=y#
      Snake\z=z#
      Snake\ax=ax#
      Snake\ay=ay#
      Snake\az=az#
      x#=x1#
      y#=y1#
      z#=z1#
      ax#=ax1#
      ay#=ay1#
      az#=az1#
     EndIf
    Next
   EndIf
   Exit
  EndIf
 Next

End Function
Здесь уменьшаем значение текущего времени на единицу, если оно получилось ноль или меньше, то восстанавливаем значение времени и передвигаем змею на шаг...

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


Добавим процедуру изменения скорости, чтобы можно было в программе изменить скорость змеи
Function SnakeSetSpeed(id%,t%)
 For SnakeManager.snm = Each snm
  If id=SnakeManager\id% Then
   SnakeManager\mt%=t%
   SnakeManager\t%=t%
   Exit
  EndIf
 Next
End Function
Здесь как и в остальных процедурах, ищем нужную змею по id% и заменяем значения времени новым, при этом надо заменить оба значения: mt% для восстановления времени, t% - новое значение текущего времени, чтобы при уменьшении скорости если текущее время будет больше чем теперь вообще возможное мы можем потерять несколько шагов или иными словами сказать "змея будет не сразу реагировать на изменение скорости"


вот и всё! теперь можно изменять скорость передвижения змеи без ее растягивания...

пример рабочего кода на этом шаге лежит в аттаче
__________________
Как минимум я помог многим (с)
(Offline)
 
Ответить с цитированием
Старый 24.07.2006, 18:17   #7
SubZer0
Администратор
 
Аватар для SubZer0
 
Регистрация: 03.09.2005
Сообщений: 2,408
Написано 301 полезных сообщений
(для 996 пользователей)
Туториал "Змейка" (Игровое поле)

Доброго времени суток! Сорри за паузу, но времени в обрез...

сегодня создадим "квадратное" игровое поле, и заставим змею двигаться по квадратам...

Ну насегодня большой логики не будет... в основном рутинное программирование, поэтому приступим сразу к разборке сегодняшнего кода...

для начала добавим несколько полезных функций:
Function SnakePosition(id%,x#,y#,z#)
 For SnakeManager.snm = Each snm
  If id=SnakeManager\id% Then
   SnakeManager\x#=x#
   SnakeManager\y#=y#
   SnakeManager\z#=z#
   Exit
  EndIf
 Next
End Function

Function SnakeGetX(id%)
 For SnakeManager.snm = Each snm
  If id=SnakeManager\id% Then
   Return SnakeManager\x#
  EndIf
 Next
End Function

Function SnakeGetZ(id%)
 For SnakeManager.snm = Each snm
  If id=SnakeManager\id% Then
   Return SnakeManager\z#
  EndIf
 Next
End Function

Function SnakeGetY(id%)
 For SnakeManager.snm = Each snm
  If id=SnakeManager\id% Then
   Return SnakeManager\y#
  EndIf
 Next
End Function

Function SnakeGetAngleX#(id%)
 For SnakeManager.snm = Each snm
  If id=SnakeManager\id% Then
   Return SnakeManager\ax#
  EndIf
 Next
End Function

Function SnakeGetAngleY#(id%)
 For SnakeManager.snm = Each snm
  If id=SnakeManager\id% Then
   Return SnakeManager\ay#
  EndIf
 Next
End Function

Function SnakeGetAngleZ#(id%)
 For SnakeManager.snm = Each snm
  If id=SnakeManager\id% Then
   Return SnakeManager\az#
  EndIf
 Next
End Function
Функции просты но полезны, думаю объяснять их функционирование не стоит, а назначение: запрос угла, запрос позиции и установка позиции головы змеи...

так, змею больше трогать сегодня не будем, займемся самим игровым полем!

создадим его для начала
For i=0 To 15
 For j=0 To 15
  t%=CreateSphere ()
  PositionEntity t%,i*10-5,0,j*10-5
  ScaleMesh t%,0.5,0.5,0.5
 Next
Next
эти два цикла разметят кусок мирового пространства на клетки... 15 на 15, размер каждой клетки будет 10 на 10 единиц...

-5 это корректировка (сдвиг) игрового поля, чтобы позиция 0,0 как раз была в центре первой клетки... оно нам пригодится для дальнейших рассчетов.


теперь самое интересное на этот момент: движение змеи! до этого мы управляли ей клавишами, но теперь нам надо чтоб она сама ползала...

код управления будет следующим:
 speed#=0.7
 If Direction%=0 Then SnakeMove(a,0,0,speed#)
 If Direction%=2 Then SnakeMove(a,0,0,-speed#)
 If Direction%=1 Then SnakeMove(a,-speed#,0,0)
 If Direction%=3 Then SnakeMove(a,speed#,0,0)

 If KeyDown(200) Then 
  If (SnakeGetX(a) Mod 10)=0 
   Direction%=0
   If Int(SnakeGetAngleY(a))<>0
    SnakeSetAngle(a,0,0,0)
    SnakePosition (a,(Int(SnakeGetX(a))/10)*10,0,SnakeGetZ(a))
   EndIf
  EndIf
 EndIf

 If KeyDown(208) Then 
  If (SnakeGetX(a) Mod 10)=0 
   Direction%=2
   If Int(SnakeGetAngleY(a))<>180
    SnakeSetAngle(a,0,180,0)
    SnakePosition (a,(Int(SnakeGetX(a))/10)*10,0,SnakeGetZ(a))
   EndIf
  EndIf
 EndIf

 If KeyDown(203) Then 
  If (SnakeGetZ(a) Mod 10)=0 
   Direction%=1
   If Int(SnakeGetAngleY(a))<>90
    SnakeSetAngle(a,0,90,0)
    SnakePosition (a,SnakeGetX(a),0,(Int(SnakeGetZ(a))/10)*10)
   EndIf
  EndIf
 EndIf

 If KeyDown(205) Then 
  If (SnakeGetZ(a) Mod 10)=0 
   Direction%=3   
   If Int(SnakeGetAngleY(a))<>270
    SnakeSetAngle(a,0,270,0)
    SnakePosition (a,SnakeGetX(a),0,(Int(SnakeGetZ(a))/10)*10)
   EndIf
  EndIf
 EndIf
Этот код каждый может составить по своему усмотрению. мне понравился такой способ реализации.

значит так: вводим дополнительную переменную: Direction%, эта переменная будет отвечать за направление движения... у нас ведь всего 4-х стороннее движение (налево, направо, вверх, вниз), по диагонали нам ездить не надо... для быстрого понятия всего советую представить себе часы: "12" - направление вверх обозначим числом "0", "3" - направление вправо числом "1", "6" - направление вниз, числом "2", 9 - направление влево числом "3"...

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

далее обработку клавиш... теперь нам надо при нажатии клавиш только менять направления... о передвижении змеи заботится предыдущий блок условий... НО! просто так нам изменять направления нельзя, нам нужно еще изменить угол головы змеи и проверять находится ли змея на середине квадрата игрового поля...

"If (SnakeGetX(a) Mod 10)=0" проверяет находится-ли змея на середине квадрата! путем сравнения не пойдет, потому, что если шаг будет равен например 0.3 то попадание на ровное число 10 будет очень редким... поэтому здесь вычисляется остаток от деления на 10 (размер квадрата) если он равен нулю, то змея находится на позиции кратной 10, а такие позиции находятся ровно в центре квадратов (для этого мы и сдвигали игровое поле на -5)

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


вот, в принципе, и все на сегодня... теперь есть поле, по которому змея может ползать... и поворачивать можно только "по квадратам"..

рабочий код на этом шаге лежит в аттаче
__________________
Как минимум я помог многим (с)
(Offline)
 
Ответить с цитированием
Сообщение было полезно следующим пользователям:
LLI.T.A.L.K.E.R. (05.02.2010)
Старый 29.07.2006, 00:48   #8
SubZer0
Администратор
 
Аватар для SubZer0
 
Регистрация: 03.09.2005
Сообщений: 2,408
Написано 301 полезных сообщений
(для 996 пользователей)
Сообщение Туториал "Змейка" (ЕДА)

Продолжим:

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


на мнужно както представить весь игровой мир... в простых случаях (как наш) его представляют массивом (в сложном случае базой данных)... представим его трехмерным массивом map в котором: первое измерение - координаты по X, второе измерение - координаты по Y и третье измерение - типы объектов (0 - код объекта, 1 хендл объекта)... третье измерение нужно нам для хранения кода препятствия... у нас ведь будут различные типы препятствий (стена и еда), ну и чтобы в любой случай можно было чтонить с объектом (препятствием) сделать и не искать его или не заводить кучу дополнительный переменных тоже нужно поле массива.

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

значит так: создание мира:
Dim Map%(15,15,1)
мир создан (богом быть всетаки легко), идем далее...

добавим несколько препятствий
For i=2 To 10
 t%=CreateCube()
 PositionEntity t%,i*10,1,i*10
 ScaleEntity t%,5,2,5
 Map(i,i,0)=1
 Map(i,i,1)=t%
Next
для препядствий будет код "1", его и надо записать в нулевое индекс третьего измерения массива, в первый индекс запишем сам хендл, в случае если нам прийдется убрать препятствие или анимацию запускать надо будет, да мало-ли чего...


Покажем нашей змее что в этом мире не только она одна бывает

  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
   WaitKey()
   End
  EndIf
Думаю тут объяснять много не надо... просто берем координату головы змеи и проверяем есть ли по "кратным" координатам в массиве стена (код 1)

в коде условие двойное... объясняется это отсутствием функции округления... на самом деле функция int() не округляет а отбрасывает все что после запятой и получается что если у нас змея находится по Х координате примерно на 4.5, то физически будет отображено так, что половина головы будет уже в следующей клетке а результат вычисления будет показывать что она еще полностью в предидущей... вот для избежания таких случаев делается такого рода коррекция... заключается она в дополнительной проверке других координат (какбы координат объекта расположенного немножко в другом месте)...

Покажем змее, что мир не безграничный, а что это всеголишь карта из 14 на 14 клеток
 If SnakeGetX(a)<-3 Or SnakeGetX(a)>143 Or SnakeGetZ(a)<-3 Or SnakeGetZ(a)>143 Then 

  WaitKey()
  End()

 EndIf
Здесь вообще все просто... проверяем "выход" координат змеи за границы карты если да, то выходим...



Теперь змея уже чтото видит... займемся едой

Заведем переменную FoodT% которая будет определять есть ли еда на поле, создавать второй кусок еды или нет, и будет служить задержкой... т.е. чтоб еда не появлялась сразу как ее только съели (так не интересно играть), а чтоб появлялась немного позже...

теперь идем в главный цикл и пишем код создания куска еды:
; ----- Create a Food --------------------------------------------

 If FoodT%>0 Then FoodT%=FoodT%-1

 If FoodT%=0 

  FoodX%=Int(Rand(0,14))
  FoodZ%=Int(Rand(0,14))
  While map(FoodX%,FoodZ%,0)=1
   FoodX%=Int(Rand(0,14))
   FoodZ%=Int(Rand(0,14))
  Wend

  map(FoodX%,FoodZ%,1)=CreateSphere()
  ScaleMesh map(FoodX%,FoodZ%,1),3,3,3
  EntityColor map(FoodX%,FoodZ%,1),255,50,50
  PositionEntity map(FoodX%,FoodZ%,1),FoodX%*10,2,FoodZ%*10

  map(FoodX%,FoodZ%,0)=2

  FoodT%=-1 

 EndIf

; ----------------------------------------------------------------
Смотрим... если FoodT% больше нуля, значит еда съедена и запущен таймер по истечении времени которого надо создать еду... уменьшаем таймер на единицу...
если 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

   FoodT%=Rand(50,300)

   FreeEntity map(FoodX%,FoodZ%,1)
   map(FoodX%,FoodZ%,0)=0

   t%=CreateSphere()
   EntityColor t%,10,250,30
   ScaleMesh t%,1*j1#,1*j1#,1.5*j1#
   PositionMesh t%,0,(1*j1#)-0.2,0
   SnakeAddSegment(a%,t%)
   j1#=j1#-0.05

  EndIf
По принципу проверки на коллизию со стенкой проверяем на коллизию с едой... если true то приступаем к поеданию... для начала запустим таймер еды, поскольку эта будет съедена и надо будет создавать другую... затем удаляем с поля объект еды FreeEntity()? хендл еды хранится в соответствующей ячейке массива map... так, еду удалили,,, теоретически она должна быть в животе у змеи и она должна от этого вырасти - реализуем это на практике путем создания дополнительного сегмента... тут все просто: создаем сферу, скалим ее, позиционируем, и взываем функцию добавления

кстати про функцию добавления... ее пришлось маленько переписать... в прошлом мы брали позицию головы и создавали в ней очередной сегмент... теперь нам надо создавать новый сегмент в хвосте... иначе видно мигание куска хвоста в голове...

новая процедура выглядит следующим образом
Function SnakeAddSegment(id%,h%)
 For SnakeManager.snm = Each snm
  If id%=SnakeManager\id% Then 
   s%=SnakeManager\s%
   SnakeManager\c%=SnakeManager\c%+1
   For Snake.sn=Each sn
    x#=Snake\x#
    y#=Snake\y#
    z#=Snake\z#
    ax#=Snake\ax#
    ay#=Snake\ay#
    az#=Snake\az#
   Next
   For i=1 To s
    Snake.sn = New sn
    Snake\id%=id%
    Snake\x#=x#
    Snake\y#=y#
    Snake\z#=z#
    Snake\ax#=ax#
    Snake\ay#=ay#
    Snake\az#=az#
    If i=s Then Snake\h%=h% 
   Next
   Return
  EndIf
 Next


End Function
здесь мы в случае нахождения куска от нужной змеи сохраняем координаты... в конце цикла переменные куда мы сохраняли координаты будут содержать координаты последнего элемента (не забываем и про углы)... и теперь мы в создании очередных резервных хранилищ и сегмента оперируем координатами предыдущего...

ну вот вроде бы и все... теперь змея умеет есть, видеть препятствия и ей запрещено выходить за границы карты...

пример рабочего кода на этом шаге см в аттаче
__________________
Как минимум я помог многим (с)
(Offline)
 
Ответить с цитированием
Сообщение было полезно следующим пользователям:
LLI.T.A.L.K.E.R. (05.02.2010)
Старый 23.08.2006, 01:38   #9
Fant
Бывалый
 
Регистрация: 05.09.2005
Сообщений: 623
Написано 4 полезных сообщений
(для 5 пользователей)
Re: Туториал "Змейка"

Спасибо за тутор. Я его внимательно прочитал. Заюзал, но ничего не понял.
У тебя есть просто примерчик змеи?
Вот тут: Туториал "Змейка" (код по функциям) - это я заюзал
Но только не знаю как изменять резмер змеи!? А вдруг я захочу не сферу а подргужать свои элементы...
Вообщем очень прощу разъяснить хотя бы как изменять размер змеи...
(Offline)
 
Ответить с цитированием
Старый 12.06.2007, 21:23   #10
moka
.
 
Регистрация: 05.08.2006
Сообщений: 10,429
Написано 3,454 полезных сообщений
(для 6,863 пользователей)
Re: Туториал "Змейка"

Да проделанна немаленькая работа по изучению. Спасибо за тутор!
(Offline)
 
Ответить с цитированием
Старый 12.06.2007, 21:41   #11
impersonalis
Зануда с интернетом
 
Аватар для impersonalis
 
Регистрация: 04.09.2005
Сообщений: 14,014
Написано 6,798 полезных сообщений
(для 20,935 пользователей)
Re: Туториал "Змейка"

Прошу прощения, но
на самом деле функция int() не округляет а отбрасывает все что после запятой
Именно, что в Blitz3D округляет:
; Ceil / Floor / Int example, three kinds of rounding.

; Move mouse. Escape quits.

Graphics 640, 480

Const KEY_ESC = 1

SetBuffer BackBuffer()
Origin 320, 240

MoveMouse 320, 240  :  HidePointer

While Not KeyDown( KEY_ESC )

Cls

my = MouseY() - 240
Color 100, 100, 0
Line -320, my, 319, my

DrawNumberLine

y# = Float( -my ) / 32

Text 100, 50, "          y = "  + y
Text 100, 70, "  Ceil( y ) = "  + Ceil( y )
Text 100, 90, " Floor( y ) = "  + Floor( y )
Text 100, 110, "   Int( y ) = " + Int( y )

Flip

Wend
End

Function DrawNumberLine( )  ; vertical line with numeric labels

Color 255, 255, 255
Line 0, -240, 0, 239

For n = -7 To 7
yn = -32 * n
Line -2, yn, 2, yn
Text -30, yn - 6, RSet( n, 2 )
Next

End Function
из хелпа блитцевского. Возможно на тутор в целом это е повлияет или я не совсем понял формулировку.
Надеюсь мои комментарии не будут расценены негативно - т.к. я сам заинтерсован в максимальной достоверности наших тутуров
__________________
http://nabatchikov.com
Мир нужно делать лучше и чище. Иначе, зачем мы живем? tormoz
А я растила сына на преданьях
о принцах, троллях, потайных свиданьях,
погонях, похищениях невест.
Да кто же знал, что сказка душу съест?
(Offline)
 
Ответить с цитированием
Старый 15.06.2007, 01:52   #12
SubZer0
Администратор
 
Аватар для SubZer0
 
Регистрация: 03.09.2005
Сообщений: 2,408
Написано 301 полезных сообщений
(для 996 пользователей)
Re: Туториал "Змейка"

да, деление флоата на 2 работает нормально, ибо флоатом занимается математический роцессор...

для сравнения два римера:

For i=0 To 20
 Print Int(i/10)
Next

WaitKey()
For i=0 To 20
 Print Int(Float(i)/10)
Next

WaitKey()
как мы видим, в случае с Float двойка оявляется намного раньше...
(Offline)
 
Ответить с цитированием
Старый 15.06.2007, 02:07   #13
impersonalis
Зануда с интернетом
 
Аватар для impersonalis
 
Регистрация: 04.09.2005
Сообщений: 14,014
Написано 6,798 полезных сообщений
(для 20,935 пользователей)
Re: Туториал "Змейка"

Что не отменяет моего утверждения, что фраза:
на самом деле функция int() не округляет а отбрасывает все что после запятой
некорректна. int - не отбрасывает, а именно округляет. typecast инт-а в С++ -да, отбрасывает, т.е. на всём диапазоне 0..1 int вернёт 1 только в 1.
В Blitz же - 50/50.
Отбрасыванием в большую и меньшую сторону занимаются Ceil# ( y# ) и Floor# ( y# ) (не Float - это отдельный разговор) соответственно.
Тупое же отбрасывание не получится даже при принудительном преобразовнии типа:
x#=0.93
z%=x
Print z
x#=0.33
z%=x
Print z
WaitKey()
число 0.93 будет окурглено до 1, а 0.33 - до 0. Всё как положено.
Отбросить ненужные знаки можно следующим образом:
x#=0.93
z#=x- x Mod 1
Print z
x#=0.33
z#=x- x Mod 1
Print z
WaitKey()
Как видим - в обоих случаях, нули. Тут дело в том, что наряду с "непривычным" INT-ом, в B3D "непривычный" MOD - в качестве аргументов могут выступать дробные числа (сравни с С++ : error C2296: '%' : illegal, left operand has type 'const double'). Этим св-вом мы пользуемся - остаток отделения на 1 = дробной части.
__________________
http://nabatchikov.com
Мир нужно делать лучше и чище. Иначе, зачем мы живем? tormoz
А я растила сына на преданьях
о принцах, троллях, потайных свиданьях,
погонях, похищениях невест.
Да кто же знал, что сказка душу съест?
(Offline)
 
Ответить с цитированием
Старый 16.06.2007, 00:25   #14
SubZer0
Администратор
 
Аватар для SubZer0
 
Регистрация: 03.09.2005
Сообщений: 2,408
Написано 301 полезных сообщений
(для 996 пользователей)
Re: Туториал "Змейка"

да... ступил я

ошибка наверное в неверном определении функции... но сути тутора это конечно не меняет...
(Offline)
 
Ответить с цитированием
Старый 16.06.2007, 02:45   #15
impersonalis
Зануда с интернетом
 
Аватар для impersonalis
 
Регистрация: 04.09.2005
Сообщений: 14,014
Написано 6,798 полезных сообщений
(для 20,935 пользователей)
Re: Туториал "Змейка"

И на старуху бывает проруха
__________________
http://nabatchikov.com
Мир нужно делать лучше и чище. Иначе, зачем мы живем? tormoz
А я растила сына на преданьях
о принцах, троллях, потайных свиданьях,
погонях, похищениях невест.
Да кто же знал, что сказка душу съест?
(Offline)
 
Ответить с цитированием
Ответ


Опции темы

Ваши права в разделе
Вы не можете создавать темы
Вы не можете отвечать на сообщения
Вы не можете прикреплять файлы
Вы не можете редактировать сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.

Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Можно ли сделать так чтобы при нажатии "вверх" и "вниз" двигалась одна картинка, а при нажатии "вправо" и "влево" - другая Total_Nube_&_Lamo Основной форум 2 13.12.2009 22:00
"Кодирование/декодирование изображений", или "Давайте попробуем скрыть ресурсы мидлетов" Richik Библиотеки 17 03.06.2009 14:18
"DarkWing Duck" aka "Черный Плащ" Chrono Syndrome Болтовня 19 04.12.2007 16:05
Игра "Три слова". Рассказ "Время планет" Ilyich Юмор 77 02.04.2007 17:49
"мапэд", или оживление "превед" культуры jimon Юмор 0 06.11.2006 17:45


Часовой пояс GMT +4, время: 16:03.


vBulletin® Version 3.6.5.
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Перевод: zCarot
Style crйe par Allan - vBulletin-Ressources.com