Главная » Статьи » Учебник: практика

Tut-Aseroids rus
# -*- coding: cp1251 -*-
# Автор: Чжан Шао, Фил Зальцман, и Грег Линдли
# Последнее обновление: 5/1/2005
# Перевод: Константин "esniper" Загрядский, 15.11.2009,
# kzagradskiy[kz]gmail.com, [kz] -- "собака"
#
# Этот урок демонстрирует использование задач. Задача это функция,
# которая вызывается один раз в каждом кадре. Они хорошо подходят
# для вещей, которые нуждаются в обновлении очень часто. В случае
# астероидов, мы используем задачи для обновления позиций всех объектов,
# а также для проверки, если пуля или судно попали в астероид.
#
# Обратите внимание: это определенно сложный пример. Задачи составляют ядро
# большинства игр поэтому представляется целесообразным, показать, на что
# может быть похожа полная игра в Panda

import direct.directbase.DirectStart
from pandac.PandaModules import TextNode
from pandac.PandaModules import Point2,Point3,Vec3,Vec4
from direct.gui.OnscreenText import OnscreenText
from direct.showbase.DirectObject import DirectObject
from direct.task.Task import Task
from math import sin, cos, pi
from random import randint, choice, random
from direct.interval.MetaInterval import Sequence
from direct.interval.FunctionInterval import Wait,Func
import cPickle, sys
# Константы, которые будут контролировать поведение в игре. Удобно группировать
# константы, как здесь -- они могут быть изменены один раз, без долгого поиска
# везде, где они используются в коде
SPRITE_POS = 55 # По умолчанию поле зрения и глубина 55,
# размерность экрана 40x30 единиц
SCREEN_X = 20 # Экран будет от -20 до 20 по X
SCREEN_Y = 15 # Экран будет от -15 до 15 по Y
TURN_RATE = 360 # Количество градусов на которые корабль может повернуться за 1 секунду
ACCELERATION = 10 # Ускорение корабля в единицах/сек/сек
MAX_VEL = 6 # Максимальная скорость корабля в единицах/сек
MAX_VEL_SQ = MAX_VEL ** 2 # Площадь скорости корабля
DEG_TO_RAD = pi/180 # переводит градусы в радианы для синуса и косинуса
BULLET_LIFE = 2 # Как долго пули отображаются на экране до удаления
BULLET_REPEAT = .2 # Как часто пули могут быть выстреляны
BULLET_SPEED = 10 # Скорость перемещения пули
AST_INIT_VEL = 1 # Скорость самых крупных астероидов
AST_INIT_SCALE = 3 # Первоначальный масштаб астероида
AST_VEL_SCALE = 2.2 # На сколько умножается скорость астероида, когда он раскололся
AST_SIZE_SCALE = .6 # Во сколько изменяется масштаб астероидов, когда он раскололся
AST_MIN_SCALE = 1.1 # Если астероид меньше, чем это значение и в него попали,
# тогда он пропадает вместо раскола

# Эта функция помогает уменьшить объем кода, используемого по загрузке объектов,
# поскольку все объекты в значительной степени схожи.
def loadObject(tex = None, pos = Point2(0,0), depth = SPRITE_POS, scale = 1,
transparency = True):
obj = loader.loadModelCopy("models/plane") # Каждый объект использует модель плоскости
obj.reparentTo(camera) # Для всех объектов поставим родителем камеру
# чтобы они смотрели на экран
obj.setPos(Point3(pos.getX(), depth, pos.getY())) # Установить исходное положение
obj.setScale(scale) # Установить первоначальный масштаб
obj.setBin("unsorted", 0) # Это говорит Panda не беспокоиться о
# порядке в котором отображается объект. (это предотвращает
# эффект известный под названием z-fighting(z-драка))
obj.setDepthTest(False) # Сообщает Panda не проводить проверку:
# "Не нарисовано ли что-то уже поверх объекта?"
# (Все в этой игре расположено на одинаковой
# глубине в любом случае)
if transparency: obj.setTransparency(1) # Все наши объекты имеют прозрачные области
if tex:
tex = loader.loadTexture("textures/"+tex+".png") # Загрузить текстуру
obj.setTexture(tex, 1) # Задать текстуру

return obj

# Макро-подобная функция используется для уменьшения количества кода, необходимого для создания
# инструкций на экране
def genLabelText(text, i):
return OnscreenText(text = text, pos = (-1.3, .95-.05*i), fg=(1,1,0,1),
align = TextNode.ALeft, scale = .05)
class World(DirectObject):
def __init__(self):
# Этот код ставит стандартный заголовочный текст и текст с инструкциями на экране
self.title = OnscreenText(text="Panda3D: Tutorial - Tasks",
style=1, fg=(1,1,0,1),
pos=(0.8,-0.95), scale = .07)
self.escapeText = genLabelText("ESC: Quit", 0)
self.leftkeyText = genLabelText("[Left Arrow]: Turn Left (CCW)", 1)
self.rightkeyText = genLabelText("[Right Arrow]: Turn Right (CW)", 2)
self.upkeyText = genLabelText("[Up Arrow]: Accelerate", 3)
self.spacekeyText = genLabelText("[Space Bar]: Fire", 4)

base.disableMouse() # Отключить управление камерой мышью по умолчанию

self.bg = loadObject("stars", scale = 146, depth = 200,
transparency = False) # Загрузить фон звездного неба

self.ship = loadObject("ship") # Загрузить корабль
self.setVelocity(self.ship, Vec3(0,0,0)) # Начальная скорость

# Словарь с клавишами которые в текущий момент нажаты
# События клавиш обновляют этот список, и наша задача будет запрашивать его в качестве входа
self.keys = {"turnLeft" : 0, "turnRight": 0,
"accel": 0, "fire": 0}

self.accept("escape", sys.exit) # Escape завершает программу
# Другие события клавиш устанавливают соответствующие значение в наш словарь клавиш
self.accept("arrow_left", self.setKey, ["turnLeft", 1])
self.accept("arrow_left-up", self.setKey, ["turnLeft", 0])
self.accept("arrow_right", self.setKey, ["turnRight", 1])
self.accept("arrow_right-up", self.setKey, ["turnRight", 0])
self.accept("arrow_up", self.setKey, ["accel", 1])
self.accept("arrow_up-up", self.setKey, ["accel", 0])
self.accept("space", self.setKey, ["fire", 1])

# Теперь мы создадим эту задачу. taskMgr это менеджер задач, который
# фактически вызывает эту функцию каждый кадра. Метод add создает новую задачу.
# Первый аргументом является функция, которая будет вызываться,
# второй аргумент представляет собой имя для этой задачи.
# Он возвращает объект задачи, который выполняет функцию каждый кадр
self.gameTask = taskMgr.add(self.gameLoop, "gameLoop")
# Объект задачи является хорошим местом для размещения переменных, которые
# должны оставаться прежними для этой функции задачи от кадра к кадру
self.gameTask.last = 0 # Время задачи в последнем кадре
self.gameTask.nextBullet = 0 # Время задачи, когда следующая пуля может быть выстреляна

self.bullets = [] # Этот пустой список будет содержать выстреляные пули
self.spawnAsteroids() # Полная инициализация "спауна" (порождения) астероидов

# Как описано выше, эта функция просто устанавливает key (клавишу) в словаре self.keys
# в переданное значение
def setKey(self, key, val): self.keys[key] = val
############################################################
#
# В этой версии, используется 'setTag', работает прекрасно.
#
############################################################

def setVelocity(self, obj, val):
list = [val[0], val[1], val[2]]
obj.setTag("velocity", cPickle.dumps(list))

def getVelocity(self, obj):
list = cPickle.loads(obj.getTag("velocity"))
return Vec3(list[0], list[1], list[2])

def setExpires(self, obj, val):
obj.setTag("expires", str(val))

def getExpires(self, obj):
return float(obj.getTag("expires"))

########################################################
#
# В этой версии, используется 'setPythonTag', "падает".
#
########################################################

# def setVelocity(self, obj, val):
# obj.setPythonTag("velocity", val)
#
# def getVelocity(self, obj):
# return obj.getPythonTag("velocity")
#
# def setExpires(self, obj, val):
# obj.setPythonTag("expires", val)
#
# def getExpires(self, obj):
# return obj.getPythonTag("expires")
#
def spawnAsteroids(self):
self.alive = True # Управляющая переменная, если корабль является живым
self.asteroids = [] # Список, который будет содержать наши астероиды
for i in range(10):
# Здесь загружается астероид. Текстура выбрана случайным образом из "asteroid1" до
# "asteroid3"
self.asteroids.append(loadObject("asteroid" + str(randint(1,3)),
scale = AST_INIT_SCALE))
# Это своего рода "хак", но он удерживает астероиды от порождения вблизи
# игрока. Создается список (-20, -19 ... -5, 5, 6, 7, ... 20)
# и выбирается значение из него. Так как игрок начинает в позиции 0, а этот список
# не содержит ничего от -4 до 4, астероид не сможет появиться близко к игроку
self.asteroids[i].setX(choice(range(-SCREEN_X, -5) +
range(5, SCREEN_X)))
#Same thing for Y, but from -15 to 15
self.asteroids[i].setZ(choice(range(-SCREEN_Y, -5) +
range(5, SCREEN_Y)))
heading = random()*2*pi # Heading (направление) случайный угол в радианах
# Преобразует heading (направление) в вектор и умножает его на скорость, чтобы получить
# вектор скорости
v = Vec3(sin(heading), 0, cos(heading)) * AST_INIT_VEL
self.setVelocity(self.asteroids[i], v)

# Это наша главная функция задачи, которая делает всю покадровую обработку
# Она берет в виде self все функции в классе, и задачу, объект задачи
# возвращенный taskMgr
def gameLoop(self, task):
# task (задача) содержит переменную time (время), в ней время в секундах, в течение которого,
# задача была запущен. По умолчанию, task не имеет дельту времени (или dt), которая является
# количеством времени, прошедшим с последнего кадра. Простой способ это реализовать,
# хранить текущее времени в task.last. Эту переменную можно использовать для поиска dt
dt = task.time - task.last
task.last = task.time

# Если корабль не жив, ничего не делать. Задача возвращает Task.cont для обозначения,
# что задача должна продолжить выполнение. Если вместо этого был возвращен Task.done,
# задача будет удалена и больше не будет вызываться каждый кадр
if not self.alive: return Task.cont
# обновить позицию судна
self.updateShip(dt)

# проверяем, может ли корабль стрелять
if self.keys["fire"] and task.time > task.nextBullet:
self.fire(task.time) # Если да, то вызвать функцию стрельбы
# И отключить стрельбу не на долго
task.nextBullet = task.time + BULLET_REPEAT
self.keys["fire"] = 0 # Удалить флаг стрельбы до следующего нажатия пробела

# обновить астероиды
for obj in self.asteroids: self.updatePos(obj, dt)

# обновить пули
newBulletArray = []
for obj in self.bullets:
self.updatePos(obj, dt) # Обновить пулю
# Пули имеют время истечения (см. определение fire (стрельба))
# Если время пули еще не истекло, добавить её в новый список пуль, чтобы она
# продолжала существовать
if self.getExpires(obj) > task.time: newBulletArray.append(obj)
else: obj.remove() # Иначе удалить её из сцены
# Установить массив пуль, как новый обновленный массив
self.bullets = newBulletArray

# Проверить столкновения пуль с астероидами
# Короче говоря, он проверяет столкновение каждой пули с каждым астероидом. Это довольно
# медленно. Большой оптимизацией могла бы стать, сортировка объектов слева направо и
# проверка их столкновений, только если они перекрываются. Частота кадров может упасть
# если будет слишком много пуль на экране, а в остальном всё нормально.
for bullet in self.bullets:
# range с указанными параметрами совершает шаг назад по списку астроидов.
# Это происходит потому, что если снять астероид со списка, элементы, после него
# изменят позицию в списке. Если вы идете в обратном направлении,
# длина остается неизменной
for i in range(len(self.asteroids)-1, -1, -1):
# Обнаружение столкновений в Panda является более сложным, чем нам необходимо здесь.
# Будем использовать базовую проверку столкновений сфер. Если расстояние между
# центрами объектов меньше суммы радиусов двух объектов, тогда у нас
# происходит столкновение. Мы используем lengthSquared поскольку это
# более быстрая векторная операция, чем length
if ((bullet.getPos() - self.asteroids[i].getPos()).lengthSquared() <
(((bullet.getScale().getX() + self.asteroids[i].getScale().getX())
* .5 ) ** 2)):
self.setExpires(bullet, 0) # Занести пулю в список на удаление
self.asteroidHit(i) # Обработать попадание
# Теперь мы делаем такой же проход столкновений для корабля
for ast in self.asteroids:
# Такая же проверка столкновений сфер для корабля с астероидом
if ((self.ship.getPos() - ast.getPos()).lengthSquared() <
(((self.ship.getScale().getX() + ast.getScale().getX()) * .5) ** 2)):
# Если есть попадание, очистить экран и спланировать перезагрузку
self.alive = False # Корабля уже нет в живых
# Удалить все объекты в asteroids (астероиды) и bullets (пули) со сцены
for i in self.asteroids + self.bullets: i.remove()
self.bullets = [] # Очистить список пуль
self.ship.hide() # Скрыть корабль
self.setVelocity(self.ship, Vec3(0,0,0)) # Сбросить скорость
Sequence(Wait(2), # Подождать 2 секунды
Func(self.ship.setR, 0), # Сбросить направление
Func(self.ship.setX, 0), # Сбросить позицию X
Func(self.ship.setZ, 0), # Сбросить позицию Y (Z для Panda)
Func(self.ship.show), # Показать корабль
Func(self.spawnAsteroids)).start() # И респаун (переродить) астероидов
return Task.cont

# Если игрок успешно уничтожил все астероиды, тогда респаун (переродить) их
if len(self.asteroids) == 0: self.spawnAsteroids()

return Task.cont # Поскольку каждый return имеет значение Task.cont, задача будет
# продолжаться бесконечно

# Обновляем позиции объектов
def updatePos(self, obj, dt):
vel = self.getVelocity(obj)
newPos = obj.getPos() + (vel*dt)

# Проверить, если объект находится вне границ.
# Если это так, сделать его варп (перенести к противоположной границе)
radius = .5 * obj.getScale().getX()
if newPos.getX() - radius > SCREEN_X: newPos.setX(-SCREEN_X)
elif newPos.getX() + radius < -SCREEN_X: newPos.setX(SCREEN_X)
if newPos.getZ() - radius > SCREEN_Y: newPos.setZ(-SCREEN_Y)
elif newPos.getZ() + radius < -SCREEN_Y: newPos.setZ(SCREEN_Y)

obj.setPos(newPos)
# Обработчик для попадания в астероид пули
def asteroidHit(self, index):
# Если астероид мал его просто удаляем
if self.asteroids[index].getScale().getX() <= AST_MIN_SCALE:
self.asteroids[index].remove()
# Здесь используется особенность Python называется slices (слайсы).
# В двух словах, слайс говорит сделать список из элементов текущего списка
# до указателя плюс элементов остальной части списка после указателя.
# Получаем эффект удаления объекта на указателе
self.asteroids = self.asteroids[:index]+self.asteroids[index+1:]
else:
# Если астероид достаточно велик, расколоть его
# Сначала мы обновляем текущий астероид
newScale = self.asteroids[index].getScale().getX() * AST_SIZE_SCALE
self.asteroids[index].setScale(newScale) #Перемасштабируем его

# Новое направление сделаем перпендикулярно к старому направлению
# Его можно рассчитать с помощью векторного произведения (cross),
# которое возвращает вектор перпендикулярный к двум взятым векторам.
# Пересечением скорости с вектором, который направлен в экран, мы
# получим вектор, перпендикулярный для первоначальной скорости в
# плоскости экрана
vel = self.getVelocity(self.asteroids[index])
speed = vel.length() * AST_VEL_SCALE
vel.normalize()
vel = Vec3(0,1,0).cross(vel)
vel *= speed
self.setVelocity(self.asteroids[index], vel)

# Теперь мы создаем новый астероид идентичный текущему
newAst = loadObject(scale = newScale)
self.setVelocity(newAst, vel * -1)
newAst.setPos(self.asteroids[index].getPos())
newAst.setTexture(self.asteroids[index].getTexture(), 1)
self.asteroids.append(newAst)
# Здесь обновляется позиция корабля. Это похоже на общее обновление
# но включает в себя вращение и тягу
def updateShip(self, dt):
heading = self.ship.getR() # Направление (heading) это значение
# для "катить" (roll) в данной модели
# Измененить направление, если были нажаты "влево" или "вправо"
if self.keys["turnRight"]:
heading += dt * TURN_RATE
self.ship.setR(heading %360)
elif self.keys["turnLeft"]:
heading -= dt * TURN_RATE
self.ship.setR(heading%360)

# Тяга причина ускорения в направлении, куда корабль в настоящее время направлен
if self.keys["accel"]:
heading_rad = DEG_TO_RAD * heading
# Здесь создается новый вектор скорости и добавляется к текущему
# С точки зрения камеры, на экраном в Panda является плоскость XZ.
# Поэтому все значения Y в наших скоростях имеют значение 0, что
# означать что нет изменений в этом направлении
newVel = (
Vec3(sin(heading_rad), 0, cos(heading_rad)) * ACCELERATION * dt)
newVel += self.getVelocity(self.ship)
# Ограничим новую скорость до максимальной возможной скоростьи.
# lengthSquared() используется опять, поскольку она быстрее, чем length()
if newVel.lengthSquared() > MAX_VEL_SQ:
newVel.normalize()
newVel *= MAX_VEL
self.setVelocity(self.ship, newVel)

# Наконец, обновим позицию как и с любым другим объектом
self.updatePos(self.ship, dt)
# Создадим пулю, и добавим её в список пуль
def fire(self, time):
direction = DEG_TO_RAD * self.ship.getR()
pos = self.ship.getPos()
bullet = loadObject("bullet", scale = .2) # Создаем объект
bullet.setPos(pos)
# Скорость относительно корабля
vel = (self.getVelocity(self.ship) +
(Vec3(sin(direction), 0, cos(direction)) *
BULLET_SPEED))
self.setVelocity(bullet, vel)
# Установить время истечения пули как сумму текущего time (время) и времени жизни пули
self.setExpires(bullet, time + BULLET_LIFE)

# Наконец, добавим новую пулю в список
self.bullets.append(bullet)

# Теперь у нас есть все, что нам нужно. Сделаем экземпляр класса и начнем
# 3D-рендеринг
w = World()
run()

Источник: http://block32.site88.net/index.php?option=com_content&view=article&id=50%3Atut-asteroidsrupy&catid=39%3Atutor&Itemid=62

Категория: Учебник: практика | Добавил: ninth (15.11.2009) | Автор: esniper
Просмотров: 5313 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Онлайн всего: 1
Гостей: 1
Пользователей: 0