Пособие желающим начать изучать Python для написания скриптов к BGE

Список разделов Геймдев в Blender Игровой движок Blender

Описание: Все вопросы и обсуждения, касающиеся BGE
Модераторы: exooman, denis8424

  • 24

Сообщение #1 exooman » 12.02.2014, 11:37

Здравствуйте, дорогие пользователи Blender GE.

Зачастую, на форумах касающихся BGE, я вижу проблему – когда пользователь, обладающий необходимыми навыками для работы с движком, попадает в безвыходную ситуацию, назвать которую можно как «ограниченный функционал стандартной логики в BGE». Речь идет о тех самых «кирпичах» - они дают возможность быстро набросать минимальный функционал для некой игровой демонстрации. Но для сложных логических задач «кирпичи» не подходят. В большом проекте паутина «взаимосвязей» сильно путает разработчика, очень сложно вернуться к проекту после долгого перерыва и разобраться, что есть где. К тому же, некоторые логические решения либо вообще не реализуемы с помощью «кирпичей», либо, по итогу, оказываются чрезмерно сложными и запутанными.

В качестве решения этой проблемы, опытные пользователи предлагают изучить используемый в BGE ЯП – Python. Но человек, ни разу не сталкивающийся с программированием, не может связать написанное в учебниках по Питону с той непонятной кашей, которую нужно написать в качестве скрипта для BGE. В итоге, пользователь откладывает изучение написания скриптов для BGE, продолжая дальше мучить злосчастные «кирпичи». Ниже я попытаюсь помочь преодолеть этот небольшой барьер «недопонимания», а так же наглядно покажу явные недостатки "кирпичной" логики.

Добавлено спустя 5 минут 11 секунд:
Для примера возьмем такую логическую цепочку:

Задача состоит в том, чтобы заставить куб двигаться при определенных условиях, а именно:
1) должна быть нажата какая-нибудь из двух кнопок(W или S) на клавиатуре.
2) Свойство(property) «Life» должно быть больше нуля.
3) Кнопка Space должна быть отпущена.


Реализовать задачу с помощью «кирпичей» можно несколькими способами:

Способ 1.
Создаем сенсоры клавиатуры для всех нужных нам кнопок(W,S и Space), создаем свойство «Life», создаем сенсор «property» , срабатывающий, когда свойство «Life» больше нуля. Стоп. Вот и первая проблема. В «кирпичах» нельзя сделать сенсор «property» , срабатывающий, когда свойство «Life» больше нуля . Можно установить срабатывание на «ровно нулю», «не ровно нулю», а так же задать интервал срабатывания. В нашем случае это – 1 до over9999, что не совсем одно и то же, что «больше нуля». Досадно.

Ну ладно, идем дальше. Создаем актуатор движения. Так... А теперь как все это связать, чтобы все заработало как планировалось?
Вспоминаем условия:

1. должна быть нажата какая-нибудь из двух кнопок(W или S) на клавиатуре.
Значит нужно создать контроллер OR и примотать к нему сенсоры W и S. А как же 2й и 3й пункты?

2.Свойство(property) «Life» должно быть больше нуля.

3. Кнопка Space должна быть отпущена.

Если мы прицепим все сенсоры к контроллеру OR, то актуатор включится при срабатывании любого из сенсоров. Придется выдумать «костыль». Создаем новое свойство, назовем его незамысловато - «qwerty». Создаем два актуатора где в первом свойство «qwerty» = 1, а во втором «qwerty» = 0. Создаем контролеры OR и OR-NOT, прикручиваем к ним сенсоры W и S. Далее, к OR прикручиваем актуатор где свойство «qwerty» = 1, а к OR-NOT - актуатор где свойство «qwerty» = 0.
В итоге вышло, что когда мы жмем W или S, то свойство «qwerty» = 1, если не жмем «qwerty» = 0.


Костыль готов, теперь создаем сенсор, срабатывающий, когда свойство «qwerty» = 1. Создаем контроллер AND, и приматываем к нему все нужные условия. А то есть инвертированный сенсор клавиатуры Spase (нам ведь нужно, чтобы space не был нажат), сенсор где свойство «Life» должно быть в интервале 1 до over9999, и, наконец, наш сенсор-костыль. С другой стороны прикручиваем нужные актуаторы. В итоге, очень простая задача превратилась какую-то трудно понятную ерунду.
Изображение

Добавлено спустя 3 минуты 4 секунды:
Рассмотрим другой способ.

Способ 2.


Тут уже будет использоваться не совсем стандартная «кирпичная» логика. Здесь мы прибегнем к помощи так называемых «выражений»( Expression). Выражения отчасти используют питоновский синтаксис, но все же довольно просты в понимании.
Начнем с создания сенсоров. Теперь нам понадобятся только три. Они остались прежними – сенсоры для кнопок W, S и Space. Отсеялся сенсор «property». Также создаем нужное нам свойство «Life». Создаем нужные актуаторы. А вот в качестве контроллера создадим «выражение». В этом «выражении» мы зададим все необходимые нам условия. А то есть:

1) должна быть нажата какая-нибудь из двух кнопок(W или S) на клавиатуре.
Значит, в поле ввода выражения пишем названия сенсоров для кнопок W и S. К примеру, названия у нас keyW и keyS. Так как сработать должен какой-либо из этих сенсоров, то пишем выражение добавив между названиями сенсоров питоновский оператор or

keyW or keyS

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

(keyW or keyS)

Смысл скобок тот же, что и в математике. То есть (2+4)*2 это не одно и то же, что 2+4*2.

2) Свойство(property) «Life» должно быть больше нуля.

Поэтому пишем в выражении and Life>0. Все логично - оператор and работает так же как и одноименный контроллер, Life – это наше свойство, оно должно быть больше нуля, поэтому и пишем >0.

3) Кнопка Space должна быть отпущена.

Далее приписываем and space. Это при условии, что сенсор Space у нас инвертированный. Если же нет, то нужно написать and not space. Нам ведь нужно чтобы кнопка space была отпущена.

В итоге получилось выражение:

(keyW or keyS) and Life>0 and not space

Прикручиваем к нему актуаторы и наша задача выполнена.
Изображение

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

Добавлено спустя 2 минуты 33 секунды:
В первом способе мы ограничились всего одной логической цепочкой «сенсоры-актуаторы» и все равно пришлось добавлять костыль. Страшно представить, как все будет выглядеть, если этих цепочек будет несколько. К примеру, несколько разных анимаций, для разных условий.

Подготовим «поле» для переноса всего этого мракобесия в скрипт. Как и в ситуации с «выражением» нам понадобятся три клавиатурных сенсора, свойство «Life» и необходимые актуаторы (у меня он один).

Включим системную консоль, дабы следить за ошибками.

Изображение


Настроим текстовый редактор блендера, включив отображение номеров строк и подсветку синтаксиса .Создадим новый текстовый файл, назвав его text.py

Изображение

Так как я буду использовать модуль, а не весь скрипт, то разрешение «.py» обязательно. Разницу между модулем и непосредственно скриптом, в своем блоге описал AndreyMal.
Спойлер
http://andreymal.org/20/

Суть в том, что модуль в нашей ситуации более приемлем в плане производительности.


Далее пишем скрипт.
Сначала, нам нужно подключить библиотеки BGE. Без них ничего работать не будет:

Код: Выделить всё
from bge import logic

Далее, нужно создать функцию, которую мы будем использовать в нашем модуле:

Код: Выделить всё
def mod(cont):


Имя функции«mod» произвольное. Можно написать любую абракадабру. То есть «def qwerty(cont):» - вполне допустимое обозначение функции.
Далее идет знак табуляции, что это такое, можно вычитать в любом учебнике по питону. Но сейчас нужно понять – все операции функции, идущие после её обозначения, должны иметь отступ вправо. То есть жмем TAB для табуляции. Чтобы было нагляднее, далее я буду дополнять скрипт, а не только писать новую строчку.

И так, дальше нам нужно «взять» текущую игровую сцену. На ней находятся все игровые объекты. «Взяв» сцену, мы получаем возможность «взять» объект. «Взяв» объект мы получим доступ к его функциям.

Берем сцену(3-я строка. Не забываем про отступы(TAB или четыре пробела)):

Код: Выделить всё
from bge import logic
def mod(cont):
    scene = logic.getCurrentScene()

Далее «берем» объект к которому прикручен наш контроллер со скриптом. Сделать это можно двумя способами. Первый и простой написании:

Код: Выделить всё
obj = cont.owner

Здесь имя obj так же как и имя функции – абсолютно произвольно. Написать можно что угодно. «сont.owner» - это владелец нашего контроллера с модулем, то есть наш объект. То есть, написав obj = cont.owner , мы всего лишь в будущем упрощаем себе задачу – нам не нужно будет постоянно писать cont.owner ( то есть «владелец контроллера») чтобы обратиться к объекту, мы будем писать просто – obj

Второй способ. Он необходим для «взятия» конкретного объекта, находящегося на сцене. Взять можно и владельца контроллера. Для этого пишем:

Код: Выделить всё
obj = scene.objects["Cube"]

obj – все так же является произвольным именем, как и в прошлый раз. scene.objects – это список всех объектов на сцене. То есть, указав в квадратных скобках имя нужного нам объекта, ["Cube"], мы сможем обратиться к нему.
И так, продолжим скрипт, укажем наш объект:

1.Способ (4-я строка):

Код: Выделить всё
from bge import logic
def mod(cont):
    scene = logic.getCurrentScene()
    obj = cont.owner

2. Способ (4-я строка):

Код: Выделить всё
from bge import logic
def mod(cont):
    scene = logic.getCurrentScene()
    obj = scene.objects["Cube"]

Добавлено спустя 1 минуту 36 секунд:
Далее начнем внедрять логику. В «кирпичах» создаем контроллер «python», меняем в нем настройку с script на module. Теперь ручками пишем названия нашего модуля. Сначала идет имя текстового файла скрипта без разрешения (то есть без .py), у нас он зовется text - это и есть наш модуль. Далее, через точку, идет имя функции, которая будет использоваться нашим модулем(это то, что в «def mod(cont)» ), у нас это mod. В итоге в контроллер пишем:

text.mod

Кстати, берите во внимание, что Python чувствителен к регистру(Заглавные и строчные буквы). То есть «Text» это не одно и то же, что «text».
Теперь прикручиваем к нашему контроллеру-модулю все необходимые сенсоры и актуаторы.


Изображение


Возвращаемся к скрипту.
Вспомним, перед нами стоит задача из трех пунктов:
1) должна быть нажата какая-нибудь из двух кнопок(W или S) на клавиатуре.
2) Свойство(property) «Life» должно быть больше нуля.
3) Кнопка Space должна быть отпущена.


Начнем выполнять первый.
Чтобы определить сенсор, нам нужно написать:

Код: Выделить всё
S = cont.sensors['keyS']

Опять же, S – произвольное имя(по сути, мы просто создаем таким способом переменную), чисто для удобства. Сам же сенсор – это cont.sensors['keyS'], то есть сенсор «keyS» присоединенный к нашему контроллеру.

Далее начинается тот самый питон, которому учат в учебниках – со стандартными функциями и операторами, с плюсами, минусами и знаками "ровно". Так что, можете смело вооружаться необходимой литературой. Там более понятно будет объяснено, что и как. Особенно это касается операторов - их возможности тут абсолютно идентичны возможностям в обычном программировании.

Определив сенсор, с помощью оператора if(если) я буду включать актуаторы. Актуаторы определяются аналогично сенсорам :

Код: Выделить всё
 act = cont.actuators['actuator']


где act – опять же переменная с произвольным именем (опять же, чисто для удобства), а сам актуатор – это cont.actuators['actuator'], где в квадратных скобках имя актуатора, присоединенного к контроллеру, то есть - ['actuator'].

Так как сенсор у нас это S, а актуатор – act (не зря же мы для удобства создавали эти переменные), то функция включения актуатора, при срабатывании сенсора у нас будет выглядеть так:

Код: Выделить всё
if S.positive:
    cont.activate(act)

«.positive» - это один из возможных атрибутов для сенсора. Как и « .activate» для контроллера. «.positive» означает, что сенсор сработал,а, с помощью « .activate», контроллер запускает указанный в скобках актуатор, то есть «act».


Как я ранее говорил, «S» и «act» - это всего лишь переменные, для удобства.
Вышеизложенный код может выглядеть так:

Код: Выделить всё
if  cont.sensors['keyS'].positive:
    cont.activate('actuator')

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


Теперь, разобравшись кто есть кто, вернемся к нашей задаче.

Выполним пункт первый:
1. должна быть нажата какая-нибудь из двух кнопок(W или S) на клавиатуре.
Строим скрипт по тому же принципу, что и выражение:

Код: Выделить всё
if  cont.sensors['keyS'].positive or cont.sensors['keyW'].positive:

Выполним пункт второй:

2. Свойство(property) «Life» должно быть больше нуля.

Добавляем свойство «Life» в скрипт. Так как оно принадлежит объекту, которому равна переменная obj (снова вспоминаем, как присваивали объект переменной в строке obj = cont.owner , то обозначается «Life» как

Код: Выделить всё
obj[‘Life’]

так как оно должно быть больше нуля, то пишем:

Код: Выделить всё
obj[‘Life’]>0

Целиком наша строчка будет выглядеть так(сенсоры кнопок S и W я заключил в скобки, по тем же причинам, которые объяснил в ситуации с «выражением»):

Код: Выделить всё
if  (cont.sensors['keyS'].positive or  cont.sensors['keyW'].positive) and obj[‘Life’] >0:


С использованием переменных это будет выглядеть так:

Код: Выделить всё
if  (S.positive or W.positive) and obj[‘Life’] >0

Или даже так:

Код: Выделить всё
if  (S.positive or W.positive) and Life >0


Далее выполним последний пункт задания:

3.Кнопка Space должна быть отпущена.

Можно было бы использовать инвертированный сенсор Space и сделать все аналогично с сенсорами для кнопок W и S, а можно инвертировать его прямо в скрипте, просто , используя оператор Not, а то есть:

Код: Выделить всё
not cont.sensors['Space'].positive

в итоге получим такую строчку:

Код: Выделить всё
if  (cont.sensors['keyS'].positive or  cont.sensors['keyW'].positive) and obj[‘Life’] >0, and not cont.sensors['Space'].positive :


А весь скрипт будет выглядеть так:

Код: Выделить всё
from bge import logic
def mod(cont):
    scene = logic.getCurrentScene()
    obj = scene.objects["Cube"]

    if (cont.sensors['keyS'].positive or cont.sensors['keyW'].positive) and obj['Life']>0 and not cont.sensors['Space'].positive:
        cont.activate('actuator')
    else:
        cont.deactivate('actuator')

А с применением переменных так:

Код: Выделить всё
from bge import logic
def mod(cont):
    scene = logic.getCurrentScene()
    obj = scene.objects["Cube"]
    S = cont.sensors['keyS']
    W= cont.sensors['keyW']
    Space= cont.sensors['Space']
    Life = obj['Life']
    act =    cont.actuators['actuator']

    if  (S.positive or W.positive) and Life >0 and not Space.positive:
         cont.activate(act)


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

Дальше – больше. У игрового объекта есть куча атрибутов, не доступных через стандартные «кирпичи», такие как worldPosition, worldOrientation, hitObject для сенсоров Ray, Radar, и многое другое. Так же, за частую, в скриптах можно обходиться без стандартных сенсоров/актуаторов, что нередко только способствует увеличению функционала. Осведомиться по этим вопросам можно тут

http://www.tutorialsforblender3d.com/Python/Python_index.html

и тут

http://www.blender.org/documentation/blender_python_api_2_69_release/bge.types.html

Надеюсь, я помог вам сделать первый шаг и начать изучать Python для написания скриптов к BGE. Ведь на самом деле, сложного тут ничего нет, было бы желание.
exooman M
Аватара
Сообщения: 1449


Сообщение #2 exooman » 23.08.2016, 19:30

exooman M
Аватара
Сообщения: 1449



Вернуться в Игровой движок Blender

Кто сейчас на форуме (по активности за 5 минут)

Сейчас этот раздел просматривают: 2 гостя