[Черновик] hello python

Данный урок расскажет вам о подключении к игре в Blender скриптов, написанных на языке программирования Python, и об основных модулях игрового движка, доступных скрипту. Вы должны знать основы Blender и BGE.

Язык программирования Python. Экспресс-курс.
(Если вы уже знаете Python, можете пропустить этот раздел. Если вы не знаете Python, но хотите его знать нормально, почитайте что-нибудь нормальное, например, Викиучебник. Часть фактов, изложенных в этом разделе, может быть искажена ради понятности для «чайников».)
UPD: поскольку с понятностью для чайников всё-таки проблемы, читайте какой-нибудь Dive into Python.
Читать

В Blender имеется консоль Python. В ней вы можете проверять примеры.
Консоль Python в Blender

В Python всё — объекты. Числа, строки, функции, классы, и даже None — всё это объекты. Объекты могут содержать в себе другие объекты. Переменная — ссылка на какой-либо объект. Пример объявления переменных:
i = 5 # i — ссылка на число 5
title = "Hello, Python!" # переменная title указывает на строку
elements = [1, 2, 8] # массив
empty = None # переменная ничего не содержит

Справа от знака = можно писать выражения любой сложности: числа, строки, вызовы функций, сравнения, математические действия и так далее.

Важно учитывать, что несколько переменных могут указывать на один и тот же объект.
elements = [1, 2, 8] # массив
arr = elements
# Теперь в переменных elements и arr один и тот же массив.
# Не два одинаковых массива, а именно один и тот же массив!

Функции — куски кода, которые могут быть вызваны из других кусков кода и которые могут вернуть вызвавшему их куску кода какой-либо объект. Функциям можно передавать аргументы — какие-либо объекты (переменные, соответственно, тоже, они ведь на объекты ссылаются). Python содержит очень много встроенных функций.
elements = range(10)
# вызывается функция range, в скобках указан один аргумент — число 10
# функция range вернёт массив с числами от 0 до указанного числа, не включая само число.

i = 2.6
i2 = round(i)
# функция round округляет переданное ей число

print(i, i2)
# функция print печатает в консоль все переданные ей аргументы
# при передаче нескольких аргументов они разделяются запятыми

print(i, round(i))
# можно передавать не только переменные, но и сразу
# результат выполнения функции! Там же тоже объект.

print("Переменная i2:", i2)
print("Переменная i:", i2)
print() # без аргументов печатает пустую строчку
print("Я вас обманул. В предыдущей строчке я тоже вывел i2.")


Мы будем создавать свои функции. Синтаксис следующий:
def имя(переменная1, переменная2, и так далее):
код функции

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

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

Чтобы вернуть значение, в функции следует указать слово return и через пробел указать объект, который надо вернуть. Если не надо ничего возвращать, можно не указывать объект или указать None. return можно писать в любом месте функции, поэтому так можно прерывать её выполнение в любой нужный момент. Пример функции:
def summa(a, b):
return a + b
print( summa(3, 4) ) # выведет: 7
# на функции print отступ закончился — функция объявлена

def f(x):
return 2*x + 3 # как математическую функцию тоже можно

Консоль Python с примером выше

Условия — самая важная часть программирования. На них строится почти всё. Синтаксис следующий:
if условие:
кусок кода 1
elif условие 2:
кусок кода 2
elif и так далее
...
else:
кусок кода 3

Поочерёдно проверяются условия из if и elif. Если условие верно (оно считается верным, если в результате получается не None, не 0, не False и не "" (пустая строка)), то выполняется следующий за ним кусок кода (с таким же отступом, да), и выполнение всего блока завершается: далее выполняется код, следующий за блоком. Если ни одно из условий не верно, выполняется код из блока else.

Блоки elif и else можно вообще не писать.

В условиях можно использовать следующие операции: == (равно), != (не равно), >, <, >=, <= (больше/меньше [или равно]), ещё кучу других, которые описывать здесь нет смысла. Также есть логические операции and, or, not и несколько других, действие которых аналогично контроллёрам в Blender.

Вообще условия являются теми же самыми выражениями, которые мы пишем справа от знака = при объявлении переменных.
Пример функции с условием:
def mod(n):
if n < 0:
return -n
else:
return n

Функция вычисляет модуль числа. Коду после блока if (которого тут нет) управление никогда не будет передано, потому что мы по всех кусках кода в блоке if написали return, который прерывает выполнение функции и что-то возвращает.

Ещё пример:
def check(minval, i, maxval):
if i < minval:
print("i меньше минимума")
i = minval
elif i > maxval:
print("i больше максимума")
i = maxval
else:
print("С i всё в порядке")
return i

Вот тут возникает вопрос: мы присваиваем i новое значение, что произойдёт? Так как переменная в Python — всего лишь ссылка на какой-то объект, операцией присваивания мы просто заменяем эту ссылку. Старый объект не изменится.
a1 = 3
a2 = 8

n = 14
n2 = check(a1, n, a2) # Выведет: i больше максимума
print(n, n2) # Выведет: 14 8
# оригинальный объект не изменился


Циклы повторяют один и тот же кусок кода согласно каким-либо условиям. Цикл for отличается от циклов в других языках. Его синтаксис следующий:
for имя_переменной in массив:
кусок кода

Цикл for поочерёдно берёт элементы из массива, записывает в переменную (ссылки на них, да) и выполняет кусок кода, которому будет доступна эта переменная. Пример:
for i in range(10): # range вернёт массив от 0 до 9
print(i)

Выведет 10 строчек с числами от 0 до 9.
m = ["Мама", "мыла", "раму"]
s = ""
for x in m:
s += x.title() + "! " # функция title делает первый символ прописным, остальные строчными

print(s) # Выведет: Мама! Мыла! Раму!


Есть также цикл с условием while:
while условие:
код

Пока будет верно условие, будет выполняться код (он, кстати, называется тело цикла). Пример:
i = 128
a = 0
while i > 1 or i == 1: # написал or просто для примера
i = i / 2
a = a + 1

print(i, a) # Выведет: 0.5 8


Выше я упоминал, что объект может содержать другие объекты. Для доступа к этим другим объектам указывается через точку имя нужного объекта. Функции — такие же полноценные объекты, и они как могут находиться в других объектах, так и сами содержать другие объекты (но нормальные программисты в функциях объекты не хранят, поэтому там находятся только всякие служебные питоновые объекты). Их так же можно вызывать, указав скобочки после имени переменной.
s = "   Строка с ПРОБЕЛАМИ     "
s2 = s.strip() # функция strip из строки откинет пробелы в начале и конце и вернёт новую строку
s3 = s2.lower() # функция lower переведёт все символы в нижний регистр

print(s3) # Выведет: строка с пробелами

Здесь появляется ещё одна проблема с передачей аргументов в функцию: что будет, если функция вызовет в объекте из аргумента какую-нибудь функцию, которая изменяет сам объект? Так как переменная в Python — всего лишь ссылка на какой-то объект и при вызове функции передаётся эта самая ссылка, а не объект, то будет изменён сам объект. Пример:
def add(arr):
arr.append(8) # функция append добавляет новый элемент в массив

m = [1, 2, 3]
print(m) # Выведет: [1, 2, 3]

add(m)

print(m) # Выведет: [1, 2, 3, 8]


Массивы, или списки в языке Python — набор из нескольких объектов. Объекты могут быть разного типа, в том числе и самим массивом.
m = [ 1, 2, None, "Строка", range(10), [2,3,4] ] # вполне корректный массив

Чтобы получить элемент из массива, нужно написать в квадратных скобках порядковый номер (индекс).
m = [ 1, 2, None, "Строка", range(10), [2,3,4] ]
for i in range(len(m)): # функция len вернёт длину переданного ей массива (или строки)
print(m[i])

(на месте range(10) так и выведется range(0, 10) вместо массива от 0 до 9. Не пугайтесь, на самом деле range является не совсем массивом.)
Присваивать тоже можно.
arr = [1, 2, 3, 4]
arr[0] = arr[0] + 10
print(arr[0] + arr[1]) # Выведет: 13

Некоторые функции массива:
a = [10, 20, 30, 34, 40, 50]
a.append(60) # добавляет новый элемент в массив
a.remove(34) # удаляет элемент из массива. Если его там нет, будет ошибка
print( a.pop(-1) ) # удаляет элемент по индексу и возвращает этот самый элемент.
# Фича: если индекс отрицательный, то берётся элемент с конца!
# то есть -1 это последний элемент, -2 предпоследний и так далее

a.insert(0, 1) # вставляет элемент, указанный вторым аргументом, в место по индексу,
# указанному первым аргументом. Элементы, стоящие после указанного индекса,
# сдвигаются вправо.
print("Массив после insert:", a)

a.extend([2, 3, 4]) # добавляет в конец массива элементы другого массива.
a.reverse() # переворачивает массив
print("End", a)

После всего этого выведется следующее:
60
Массив после insert: [1, 10, 20, 30, 40, 50]
End [4, 3, 2, 50, 40, 30, 20, 10, 1]


Чтобы проверить наличие элемента в массиве, можно использовать in:
if 30 in a:
print("Тридцатка есть в массиве")
else:
print("Тридцатки нет в массиве")


Ассоциативные массивы, также известные как словари вместо чисел-индексов работают с индексами в виде произвольных объектов — ключами. Но, как правило, используют только строки. Вместо квадратных скобок при объявлении (только при объявлении) используются фигурные.
d = {"a": 2, "b": 3} # пишем пары ключ:значение
d["c"] = 8 # добавляем новый элемент
print(d.pop("a")) # выведет: 2
print(d.keys()) # keys возвращает массив ключей: dict_keys(['b', 'c'])
print(d.values()) # values возвращает массив значение: dict_values([3, 8])

if not "q" in d.keys():
d["q"] = 0

print(d["q"])


Модули можно рассматривать как специальный объект с набором других объектов (как правило, функциями). Python содержит очень большое количество встроенных модулей. Модули добавляются с помощью ключевого слова import.
import os
print(os.listdir(".")) # функция listdir возвращает массив с содержимым каталога, адрес которого
# указан первым аргументом; точка означает текущий каталог.

import sys as SYSTEM # с помощью слова as можно переименовывать модули, как нам нужно
SYSTEM.exit() # выход


Пока что нам этого хватит.


Ищем консоль в Blender. Чтобы читать то, что нам выводит функция print, и ошибки, нам надо найти консоль в игровом движке: окно в блендере — это просто интерпретатор, игровой движок выведет всё в стандартный вывод. Чтобы консоль появилась в операционной системе Windows, нужно выбрать в меню Window — Toggle System Console. Для UNIX-подобных систем (Linux, например) достаточно просто запустить блендер из консоли (надеюсь, вы знаете, как это делать).

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

Режим «Скрипт».
Контроллёр в режиме Script

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

Режим «Модуль».
Контроллёр в режиме Module

В этом режиме требуется указать имя модуля и через точку функцию из этого модуля. Перед вызовом функции Blender импортирует этот модуль и потом вызывает из него функцию. Модуль импортируется один раз и работает в течение всей игры, поэтому все переменные внутри него сохраняются. Также за счёт этого повышается производительность, потому что нет необходимости исполнять весь файл каждый раз: вызывается только нужная функция из модуля, которая загружена заранее при импорте. Модуль должен иметь расширение «.py» в имени файла.

Hello, World! В текстовом редакторе Blender создайте такой файл:
Text.py

И добавьте какому-нибудь объекту такую логику:
Кирпичики

При запуске игры вы должны обнаружить в консоли соответствующее приветствие.

Если вы поменяете сенсор Always на Keyboard, вы обнаружите, что текст выводится два раза. Это связано с тем, что сенсор «будит» контроллёр не только при нажатии клавиши, но и при её отпускании.

Ещё Hello, World! Теперь посмотрим, как работает модуль:
Новый Text.py
Новые кирпичики

В консоли вы будете наблюдать что-то подобное:
Вывод консоли

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

Модуль bge.logic.Все модули, связанные с игровым движком Blender, хранятся в одном модуле bge. Создадим новый скрипт и напишем туда:
import bge

Здесь мы рассмотрим только два модуля: bge.logic и bge.render.

bge.logic. Контроллёр. Как правило, скрипт начинается с получения контроллёра — того самого контроллёра Python, из которого был запущен скрипт. Переменную с контроллёром обычно называют cont.
import bge

cont = bge.logic.getCurrentController()

Через контроллёр мы может получить доступ к подключенном к нему сенсорам и актуаторам и самому объекту. Переменную с текущим объектом обычно называют obj или me.
obj = cont.owner

Через объект мы тоже может получить доступ к сенсорам и актуаторам, но уже ко всем. Доступ к ним осуществляется через одноимённые словари, в качестве ключей которого используются имена сенсоров и актуаторов соответственно.
sens = cont.sensors["Keyboard"] # получаем сенсор с именем(!) Keyboard

Учтите, что сенсоры и актуаторы берутся именно по имени, то есть, например, если вы создали сенсор Always (и именем Always) и поменяли его тип на Keyboard, то имя так и останется Always и получать сенсор надо именно через него. Также при добавлении нескольких сенсоров одного типа может добавлять порядковый номер (Keyboard1, Keyboard2 и т.д.), он тоже часть имени, не забывайте его указывать в скрипте. Лучше переименовывайте сенсоры или актуаторы в соответствие с их назначением (Key_Up, Fire и т.д.), чтобы не путаться.

Каждый сенсор имеет переменную positive, которая имеет значение True в случае, когда сенсор сработал, и False в случае, когда не сработал. Сравнение на True и False делать в Python, как и в любом другом языке, не обязательно, достаточно написать так:
if sens.positive:
print("Сенсор активен")
else:
print("Сенсор не активен")


При таком раскладе:
Text.py
Кирпичики

Мы в консоли получим:
Консоль

Теперь мы получим актуатор и повертим объект с помощью него (в моём случае дефолтный кубик). Перепишем часть скрипта с условием:
act = cont.actuators["Motion"]

if sens.positive:
cont.activate(act) # это включает актуатор
else:
cont.deactivate(act) # это выключит актуатор

TODO: заменить скриншот
Text.py
Кирпичики

Сделаем вращение кубика ускоряющимся: пока кнопка нажата, куб вращается всё быстрее, когда кнопка отпущена — кубик замедляется и останавливается совсем. Это можно сделать с помощью обычной угловой скорости (Angular Velocity), но мы сделаем через изменение параметра Rot в актуаторе.

Все три значения со строчек Loc и Rot хранятся в массивах dLoc и dRot соответственно. Их, собственно, менять и будем. Кстати, к значениям этих массивов можно получать доступ не только через индексы (0, 1, 2), но и через переменные x, y, z внутри них.

Но нужно учесть, что в скрипте вращение задаётся не в градусах, а в радианах.

Снова перепишем последнюю часть скрипта с условием:
if sens.positive:
act.dRot = [act.dRot.x, act.dRot.y, act.dRot.z+0.001]
cont.activate(act)
else:
if abs(act.dRot.z) < 0.001:
# останавливаем актуатор только когда скорости нет
cont.deactivate(act)
# из-за погрешностей в вычислении дробных чисел
# «в лоб» через == лучше не сравнивать
else:
# скорость ещё есть - уменьшаем
act.dRot = [act.dRot.x, act.dRot.y, act.dRot.z-0.001]


Функция abs откидывает минус у переданного ей числа.

Но теперь нам надо немного изменить сенсор. Дело в том, что, как было видно через print, скрипт запускается один раз при нажатии клавиши и один раз при отпускании, а нам надо менять скорость в течение всей игры. Для этого мы нажмём две кнопки с тремя «точечками» на сенсоре.
Сенсор с нажатыми точечками
Первая кнопка включает вызов скрипта каждый раз, когда сенсор срабатывает. Вторая кнопка — каждый раз, когда сенсор не срабатывает. То есть скрипт у нас теперь будет работать вообще на каждом игровом кадре.

Кстати, через «Частоту» (Freq) справа от этих кнопочек можно задавать пропуск кадров. Если, например, там написать 1, то скрипт будет вызываться не каждый кадр, а с пропуском через кадр.
TODO: заменить скриншот
Text.py
Теперь скрипт при нажатии на пробел будет ускоряться и замедляться в противном случае. Конечно, если вы не забыли обнулить все значения в актуаторе Motion. :)

Насчёт погрешности в вычислениях: попробуйте в питоне сложить 0.1 и 0.2, посмотрите на результат и всё поймёте.

Свойства. Объект можно рассматривать как обычный словарь, ключами которого являются имена свойств (Property). Отличие только в том, что для получения списка имеющихся свойств надо использовать не функцию keys(), а функцию getPropertyNames().

Соответственно, для получения свойства из объекта надо указать его имя в квадратных скобках (и кавычках, потому что имя — это строка) после имени переменной с нужным объектом, примерно так: obj["koef"]

Вот мы и вынесем наше число 0.001 в свойство. Создадим его.
Свойство speed

Теперь достаточно везде в скрипте поменять 0.001 на obj["speed"]
if sens.positive:
act.dRot = [act.dRot.x, act.dRot.y, act.dRot.z+obj["speed"]]
cont.activate(act)
else:
if abs(act.dRot.z) < obj["speed"]:
cont.deactivate(act)
else:
act.dRot = [act.dRot.x, act.dRot.y, act.dRot.z-obj["speed"]]


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

Text.py с переменной speed

С помощью списка имеющихся свойств getPropertyNames() мы можем сделать проверку на наличие данного свойства, чтобы избежать ошибки при его отсутствии. Мы можем даже создать это свойство. Заменим строчку с получением свойства speed на следующий код:
if not "speed" in obj.getPropertyNames(): # проверка отсутствия
obj["speed"] = 0.001 # создаём свойство
speed = obj["speed"] # получаем гарантированно присутствующее свойство


Теперь вы можете удалить свойство из объекта, и скрипт сохранит свою работоспособность.

Но вообще для получения значений у объектов, списка сенсоров (obj.sensors) и актуаторов (obj.atuators) имеется функция get(), первым аргументом для которой нужно указать имя нужного свойства/сенсора/актуатора, а вторым аргументом значение, которое нужно вернуть в случае отсутствия этого свойства (если его не указывать, при отсутствии функция вернёт None). И код сокращается до одной строчки:
speed = obj.get("speed", 0.001)


Скачать blend-файл 1

Варианты написания скрипта

Конечно, этот скрипт можно написать кучей способов. Можно, например, не делать переменные под всё, можно делать переменные абсолютно под всё.

Упоротый вариант (никогда не пишите так!) (да, кстати, можно не импортировать bge, он сам импортируется):

if not "speed" in bge.logic.getCurrentController().owner.getPropertyNames():
bge.logic.getCurrentController().owner["speed"] = 0.001

if bge.logic.getCurrentController().sensors["Keyboard"].positive:
bge.logic.getCurrentController().actuators["Motion"].dRot = [
bge.logic.getCurrentController().actuators["Motion"].dRot.x,
bge.logic.getCurrentController().actuators["Motion"].dRot.y,
bge.logic.getCurrentController().actuators["Motion"].dRot.z+bge.logic.getCurrentController().owner["speed"]]
bge.logic.getCurrentController().activate(bge.logic.getCurrentController().actuators["Motion"])
else:
if bge.logic.getCurrentController().actuators["Motion"].dRot.z < bge.logic.getCurrentController().owner["speed"]:
bge.logic.getCurrentController().deactivate(bge.logic.getCurrentController().actuators["Motion"])
else:
bge.logic.getCurrentController().actuators["Motion"].dRot = [
bge.logic.getCurrentController().actuators["Motion"].dRot.x,
bge.logic.getCurrentController().actuators["Motion"].dRot.y,
bge.logic.getCurrentController().actuators["Motion"].dRot.z-bge.logic.getCurrentController().owner["speed"]]


Всё запихиваем в переменные:
import bge

cont = bge.logic.getCurrentController()
obj = cont.owner
sens = cont.sensors["Keyboard"]
ok = sens.positive

act = cont.actuators["Motion"]

props = obj.getPropertyNames()
if not "speed" in props:
obj["speed"] = 0.001
speed = obj["speed"]

rot = act.dRot

if ok:
act.dRot = [rot.x, rot.y, rot.z+speed]
cont.activate(act)
else:
if abs(rot < speed:
cont.deactivate(act)
else:
act.dRot = [rot.x, rot.y, rot.z-speed]



У сенсоров и актуаторов есть ещё куча своих атрибутов, которые можно читать и изменять, но их тут рассматривать бессмысленно, лучше вооружитесь переводчиком и штурмуйте официальную документацию (раздел Game Engine Modules). Атрибуты, которые имеются у сенсоров, контроллёров, актуаторов, объектов и сцен (о них ниже) расписаны в bge.types. Вот, например, используемый нами dRot.

А теперь мы рассмотри работу со сценами. Получить список всех запущенных сцен можно с помощью функции:
scenes = bge.logic.getSceneList()

Так как это самый обычный массив (нету даже прекрасной функции get), для поиска сцены по имени нам придётся в цикле перебирать все сцены и сравнивать имя.
scene = None
for x in scenes: # перебираем сцены
if x.name = "Menu": # в name содержится имя сцены
scene = x
break # выходим из цикла

if scene: # проверяем, что сцена нашлась
print("Сцена нашлась, работаю дальше")


Для получения текущей сцены (той, из которой запущен скрипт) достаточно воспользоваться другой функцией:
scene = bge.logic.getCurrentScene()
print("Меня запустили из сцены", scene.name)

Теперь у нас есть полный доступ к сцене. Частично рассмотрим, что у неё есть:
Читать

- scene.objects — массив/словарь из объектов, имеющихся в данный момент на сцене. К нему можно обращаться и как к массиву (по индексам), и как к словарю (по именам объектов: scene.objects["Camera"]). Но учтите, что могут возникнуть ситуации, когда в сцене будет несколько объектов с одним и тем же именем. Такое, например, происходит при добавлении объектов с другого слоя с помощью актуатора Edit Object. Для этого массива/словаря тоже имеется функция get(), с помощью которой можно получить None в случае отсутствия объекта.

- scene.objectsInactive — массив/словарь неактивных объектов с неактивных слоёв. Вы сможете их добавить в игру через скрипт, как с помощью актуатора Edit Object (об этом ниже).

- scene.active_camera — объект, текущая камера.

- scene.get(имя, значение) — функция действует аналогично функции get() у объектов. Да, в сцене тоже можно хранить свойства!

- scene.addObject(объект, ещё объект, время) — действует аналогично Add Object в актуаторе Edit Object. Первым параметром вы указываете объект (или строку с именем объекта), который надо добавить. Вторым параметром указывается объект (или строка), на месте которого появится новый объект. (Можете указать этот же объект и потом переместить с помощью worldPosition). Третьим параметром указывается время в кадрах, через которое объект будет удалён. Если указать ноль, объект удаляться не будет.

- scene.restart(), scene.suspend(), scene.pause(), scene.resume() — эти функции делают то, что указано в их названиях.

Подробности в официальной документации.

Рассмотрим, что мы можем делать с объектами, помимо работы со свойствами:
Читать

- obj.worldPosition — массив (точнее, вектор), указывающий положение объекта в пространстве. (С векторами можно делать много чего, подробности опять же в официальной документации)

- obj.worldOrientation — тут уже сложнее. Это матрица поворота 3x3. Чтобы получить привычные углы Эйлера, вызовите функцию obj.worldOrientation.to_euler().

- obj.worldScale — массив (точнее, тоже вектор), задающий размеры объекта.

- obj.worldLinearVelocity и obj.worldAngularVelocity — линейная и угловая скорости соответственно. Векторы.

- obj.name — просто имя объекта.

- obj.color цвет объекта (именно объекта, а не материала). Тоже вектор, но уже четырёхмерный. Цвет задаётся так: [красный, зелёный, синий, альфа-канал] значениями от 0.0 до 1.0.

- obj.visible — True или False, видимый объект или нет.

- obj.children — массив/словарь объектов-потомков в связи Родитель-Потомок (Ctrl+P).

- obj.parent — родитель объекта (в связи Родитель-Потомок, которая Ctrl+P) или None, если его нет.

- obj.setParent(объект) — установить нового родителя.

- obj.removeParent() — убрать родителя.

- obj.setVisible(True/False) — управляет видимостью объекта.

- obj.endObject() — удалить объект.

Для всех перечисленных атрибутов с world- есть одноимённый аналог с local-, задающий значение данного атрибута не в мировой системе координат, а в локальной системе координат объекта (её могут изменять перемещение, вращение и масштабирование объекта).

Из урока получается справочник, но что поделать ¯\(°_o)/¯

Функции в модуле bge.logic:
Читать

- bge.logic.expandPath("//путь") — важная функция для работы с файлами. Её особенность в том, что если в начале пути указать два слэша, то она вернёт путь относительно пути запущенного blend-файла (то есть, в нашем случае будет ссылка на файл «путь», лежащий в одной папке с blend-файлом). Простое указание пути при работе с файлами может вести неведомо куда.

- bge.logic.startGame(путь к blend-файлу) — запустить новую игру из указанного blend-файла. Вот тут уже и пригодится указанные выше функция.

- bge.logic.restartGame() — перезапустить игру.

- bge.logic.endGame() — завершить игру.

- bge.logic.addScene(имя, 1 или 0) — добавляет новую сцену в игру. Если второй параметр overlay установлен в единицу (стоит по умолчанию, поэтому можно не указывать), то сцена будет рисовать поверх всех текущих сцен; в случае нуля сцена будет фоновой.

- bge.logic.sendMessage(тема, тело, кому, от кого) — отправляет сообщение. Последние три параметра указывать необязательно. Если не указывать кому, сообщение будет разослано всем объектам. Собственно, тему (subject) как раз и читает сенсор Message. Тело (body) можно прочитать, получив сенсор и обратившись к body (cont.sensors["message"].body).

А ещё у нас есть модуль bge.render.
Читать

- bge.render.getWindowWidth() и bge.render.getWindowHeight() — возвращают длину и ширину окна с игрой соответственно.

- bge.render.showMouse(1 или 0) — показывает или скрывает курсор мыши.

- bge.render.setWindowSize(длина, ширина) — изменяет размеры окна или разрешение в полноэкранном режиме.

- bge.render.setFullScreen(1 или 0) — устанавливает или отключает полноэкранный режим.

- bge.render.getFullScreen() — возвращает True или False в зависимости от того, включен полноэкранный режим или нет.

- bge.render.setMousePosition(x, y) — устанавливает положение курсора мыши относительно окна. Координаты рекомендую получать через сенсор Mouse (cont.sensors["Mouse"].position — массив из двух чисел — собственно координат)

- bge.render.drawLine(начало, конец, цвет в RGB) — просто рисует линию. Все три аргумента — массивы из трёх чисел (координаты и цвет).

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

В процессе написания скриптов у вас может возникнуть проблема, что вам нужен не вектор и не навороченный список, а просто самый обыкновенный массив Python, без которого скрипт пишет ошибку. Чтобы преобразовать такой объект в обычный массив, воспользуйтесь функцией list(), передав ей проблемный объект:
arr = list(obj.worldPosition)
# arr теперь не вектор
print(arr)


globalDict и сохранение/загрузка игры. Модуль bge.logic также имеет такой интересный нам словарь, именуемый globalDict. Особенность его в том, что всё его содержимое можно сохранить файл с помощью функций bge.logic.saveGlobalDict() и загрузить из файла с помощью bge.logic.loadGlobalDict() (только если вы запихнёте туда то, что сохранить в принципе нельзя, может выскочить ошибка), а также с помощью соответствующих функций в актуаторе Edit Object.

globalDict тоже имеет функцию get(ключ, значение по умолчанию).

Попробуем сделать сохранение положения движущегося кубика. Заодно научимся писать модуль для BGE.

Кирпичики на кубике

Создадим файл Saver.py и напишем туда следующее:
import bge

print("Loading globalDict...")
bge.logic.loadGlobalDict()

def load(cont):
if not cont.sensors["Load"].positive:
# выходим, если кнопка не нажата
return

# в cont передаётся контроллёр, который вызвал функцию
# поэтому getCurrentController() запускать не надо
obj = cont.owner

# получаем положение объекта
pos = bge.logic.globalDict.get("cube_pos", [0.0, 0.0, 0.0])

# и двигаем
obj.worldPosition = pos

print("Loaded!")

def save(cont):
if not cont.sensors["Save"].positive:
return

obj = cont.owner
pos = list(obj.worldPosition) # через list убираем вектор, нам не надо

# сохраняем
bge.logic.globalDict["cube_pos"] = pos
bge.logic.saveGlobalDict()

print("Saved!")


Тогда во время игры мы можем наблюдать следующее:

Вывод консоли

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

Таким образом можно кидать в globalDict всё, что нужно сохранить.

Также globalDict можно использовать для обмена информацией между сценами: для всех сцен он один и тот же. Записываем в одной сцене — читаем в другой.

Скачать blend-файл 2

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

Ошибки и их обработка. Если скрипт пытается что-то неправильно делать, питон обижается, печатает подробную информацию об ошибке в консоль и прерывает работу скрипта. Эту информацию следует научиться читать. Английский пригодится.
Python script error - object 'Cube', controller 'Python':
Traceback (most recent call last):
File "Text", line 5, in <module>
NameError: name 'что_то' is not defined

Видим, что указаны файл, в котором возникла ошибка, строчка, в которой возникла ошибка, и собственно ошибка.

NameError: name 'что_то' is not defined
Вы пытаетесь обратиться к переменной или функции что_то, которой не существует. Возможно, вы забыли эту переменную создать. Возможно, вы забыли импортировать модуль. Возможно, вы просто опечатались.

SyntaxError: invalid syntax
SyntaxError: unexpected EOF while parsing
Вы где-то опечатались: забыли закрыть скобку, поставили лишний символ. Причём ошибка может быть и не на самой указанной строчке, а где-то на предыдущих. Тут придётся перечитывать скрипт.

IndentationError: unexpected indent
Вы накосячили с отступами и пробелами в скрипте. Проверьте отступы в указанной строчке. Учтите, что отступ вправо означает начало блока кода, отступ влево — конец этого блока. Рекомендуемый отступ — 4 пробела, не рекомендуемый — табуляция (символ такой).

ImportError: No module named 'модуль'
Вы пытаетесь импортировать несуществующий модуль. Проверьте его на опечатки и наличие его у вас в системе.

AttributeError: 'КакойТоТам' object has no attribute 'атрибут'
Вы пытаетесь получить у обекта несуществующий атрибут. Проверьте имя атрибута на опечатки (регистр в том числе) и перепроверьте в документации, существует ли такой атрибут вообще.

AttributeError: attribute 'parent' of 'KX_GameObject' objects is not writable
Вы пытаетесь изменить атрибут, изменять который нельзя. Смиритесь или найдите функцию, которая его меняет (например, для parent из примера есть функция setParent).

IndexError: CList[i]: Python ListIndex out of range in CValueList
IndexError: list index out of range
Вы пытаетесь получить доступ к элементу массива, которого нет. Например, вы указали индекс с числом, равным или больше количеству элементов в массиве (индексы считаются от нуля до количества минус один; последний элемент имеет индекс количество минус один). Добавьте в скрипт проверку размера массива len(arr).

KeyError: 'ключ'
Вы пытаетесь получить элемент из словаря по ключу, которого там нет. Проверьте его наличие с помощью выражения ключ in словарь.keys()


Ошибки можно ловить. Для этого используется конструкция try..except:
try:
obj = bge.logic.getCurrentScene().objects["Cube"]
except:
print("Ошибка при получении объекта")

Если возникнет какая-то ошибка (вообще их правильно называть исключениями) между try и catch, будет передано управления блоку после except.

Так будут ловиться вообще все ошибки. Чтобы поймать какую-то конкретную ошибку, нужно указать тип этой ошибки (кстати, типы в Python тоже являются объектами и ими можно манипулировать, как всеми объектами). Так можно указывать несколько блоков except:
try:
obj = bge.logic.getCurrentScene().objects["Cube"]
except KeyError:
print("Нет такого объекта")
except AttributeError:
print("У сцены нет атрибута object? Как так? o_O")


Исключение является таким же объектом, как и все остальные объекты, и оно может содержать информацию об ошибке. Чтобы получить информацию из него, его нужно получить. Делается это следующим образом:
try:
obj = bge.logic.getCurrentScene().objects["Cube"]
except Exception as exc: # в переменной exc у нас объект-исключение
print("Ошибка при получении объекта:", str(exc))

Указание типа Exception (или вообще отсутствие указанного типа) позволяет ловить любые исключения. Но так делать не рекомендуется, потому что можно случайно поймать и обработать не то исключение, которое нам надо.

try:
obj['result'] = 1 / 0
except ZeroDivisionError:
print("Деление на ноль!")
except:
print("Какая-то неизвестная ошибка")


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

Оператор raise в Python позволяет вызвать своё исключение. Его можно использовать, чтобы обработать исключение, «не перехватывая» его, то есть работа скрипта всё равно прекратится:
import traceback

try:
obj = bge.logic.getCurrentScene().objects["Cube"]
except:
obj["error"] = traceback.format_exc() # забираем сообщение об ошибке
raise # и делаем исключение. Скрипт прервёт свою работу и напечатает ошибку в консоль


Можно создавать свои типы исключений. Этим удобно пользоваться в больших скриптах.
class MyScriptError(Exception): pass
# создаём своё исключение на базе Exception (можно на базе любого исключения)

if not bge.logic.getCurrentScene().objects.get("Cube"):
raise MyScriptError("Нету кубика!")


Такая форма записи: тип(аргументы) — создаёт новый объект с указанным типом. Атрибуты передаются в специальную функцию — конструктор. В случае исключений атрибут может содержать текст ошибки. Тогда в консоли мы увидим свой текст:
Python script error - object 'Cube1', controller 'Python':
Traceback (most recent call last):
File "Text", line 5, in <module>
__main__.MyScriptError: Нету кубика!


Свои исключения тоже можно ловить.
class MyScriptError(Exception): pass
# создаём своё исключение на базе Exception (можно на базе любого исключения)

try:
cube = bge.logic.getCurrentScene().objects.get("Cube")
if not cube:
raise MyScriptError("Нету кубика!")
if not cube.get("prop"):
raise MyScriptError("Нету свойства!")
print("Проп:", cube["prop"])
except MyScriptError as mse:
print("Ошибка:", str(mse))


Ну и хватит на этом, наверно.
denis8424 9 июня 2013 г. 11:41
- scene.objects — массив/словарь из объектов, имеющихся в данный момент на сцене. К нему можно обращаться и как к массиву (по индексам), и как к словарю (по именам объектов: scene.objects["Camera"]). Для этого массива/словаря тоже имеется функция get(), с помощью которой можно получить None в случае отсутствия объекта. Но учтите, что могут возникнуть ситуации, когда в сцене будет несколько объектов с одним и тем же именем. Такое, например, происходит при добавлении объектов с другого слоя с помощью активатора Edit Object. В таком случае, нужный объект можно найти с помощью ID - уникального номера, как в паспорте. (http://www.blender.org/documentation/blender_python_api_2_65a_release/bge.types.html#bge.types.CListValue.from_id)
Добавь сюда упоминание о ID, примерно так.
Урок получился очень понятным.
denis8424 9 июня 2013 г. 14:58
Еще замечу, что связка "сенсор-контроллер-актуатор" пришла в БГЕ из робототехники. Может лучше использовать "родное" именование для актуаторов?
sss 9 июня 2013 г. 15:19
andreymal
Мне всё нравится, вот только с python что то у меня сложновато. С с++ было полегче. Вот на следующей недели подключу инет(безлим), тогда и займусь изучением bge и python, надеюсь видео уроки помогут... Тебе Спасибо за "черновик"!
andreymal ответил denis8424 9 июня 2013 г. 18:42
Про актуатор надо было переводчику говорить.
sss 10 июня 2013 г. 02:21
Пример со здоровьем очень Понравился, но разбирать пока не для меня, хотя помучаться можно.
anonymous 7 июля 2013 г. 00:35
andreymal
А можно как-то мой пароль, мне напомнить... я sss ?
Может появятся вопросы.
andreymal ответил s_s_s 7 июля 2013 г. 00:35
Нельзя, к сожалению.
s_s_s ответил andreymal 7 июля 2013 г. 00:43
Ну, тогда я теперь такой...
А как перейти на эту страницу с главной?
andreymal ответил andreymal 7 июля 2013 г. 00:44
Я бы мог просто старую учётку удалить, чтоб заново зарегистрироваться.
С главной — ткнуть по моему нику в каком-нибудь посте и найти там этот пост. Помещу на главной, когда руки дойдут доделать.
andreymal ответил s_s_s 7 июля 2013 г. 00:47
Какая-то хрень, стали появляться тайм-парадоксы на сайте
s_s_s ответил s_s_s 7 июля 2013 г. 01:04
Ну старую тогда удаляй, а эту оставляй.
С главной я захожу так: просто добавляю /20 в адрес.строку
andreymal ответил s_s_s 7 июля 2013 г. 01:04
Так музыка прерывается. :)
s_s_s ответил andreymal 7 июля 2013 г. 01:11
Какая музыка???
s_s_s ответил andreymal 7 июля 2013 г. 01:33
Ясно. Да музыка пропадает. Ты тут попрятал всё. Если честно то я не смог просто найти ни одного твоего комента на глав.стр., через который можно было бы добраться на эту стр.
andreymal ответил s_s_s 7 июля 2013 г. 01:35
Ткнуть по нику.
И всё ок
s_s_s ответил s_s_s 7 июля 2013 г. 01:47
Теперь нашёл :) и музыка есть. Звук громкий!
s_s_s 8 июля 2013 г. 19:39
А здесь можно вставлять скрины, если да то как?
s_s_s 8 июля 2013 г. 19:48
Почему так не работает, ты писал что должно работать. Или я что то не понял?:

if sens == True: # ТАК НЕ РАБОТАЕТ ?_________________________
#if sens.positive:
act.dRot = [act.dRot.x, act.dRot.y, act.dRot.z+0.001]
cont.activate(act)
else:
.................
andreymal ответил andreymal 8 июля 2013 г. 19:48
Ссылка на изображение с одного из ресурсов из белого списка:
'userapi.com',
'vk.com',
'imageshack.us',
'andreymal.org',
'radikal.ru',
'habrastorage.org',
'smile-o-pack.net',
'joyreactor.cc',
'poniez.net',
'blender3d.org.ua'
andreymal ответил sss 8 июля 2013 г. 19:48
в HTML-теге IMG
andreymal ответил andreymal 8 июля 2013 г. 19:51
«sens == True» не работает, потому что сенсор это не True и не False, это сенсор. А вот свойство positive из сенсора (sens.positive) как раз или True, или False, то есть sens.positive == True
s_s_s ответил sss 8 июля 2013 г. 20:05
Теперь понятно. Ты, это... мои глупые вопросы никому не показывай :)
andreymal ответил andreymal 8 июля 2013 г. 20:07
Гугл уже всё помнит.
sss 18 июля 2013 г. 22:20
Теперь почему то зашёл как "sss" ?????
andreymal ответил Noooth 18 июля 2013 г. 22:20
Пароль вспомнил?)
sss 18 июля 2013 г. 22:27
Нет, зашёл в очерёдный раз на сайт и увидел в правом верхнем углу свой старый ник.
andreymal ответил andreymal 18 июля 2013 г. 22:28
Лол
anonymous 1 июня 2015 г. 16:26
Може ж найти скрипт на два вида?
Noooth ответил andreymal 12 июля 2015 г. 12:10
Скрипт я нашов скоро виложу
anonymous 6 мая 2016 г. 01:41
Печаль: консоль никак не реагирует на скрипты из этого урока, и кубик не вращается и в консоли начиная с hello, world не пишет ничего. в чём мог ошибиться?
andreymal 6 мая 2016 г. 01:47
При таком описании без blend-файла ничего конкретного сказать нельзя
anonymous 17 июля 2016 г. 09:51
Отличный урок, очень помог мне в свое время, все изложено очень интересно и понятно :)
Очень надеюсь что он не последний))
andreymal 17 июля 2016 г. 12:04
>2013
>не последний
Ай оптимист))
anonymous 14 декабря 2016 г. 08:18
def good(): # благодарность
print ("спасибо")
anonymous 10 июня 2018 г. 05:04
Неужели я нашел годную инфу по скриптингу в BGE. Продолжайте в том же духе.
Комментировать
Вы anonymous