Droid Game with Panda3d ShowCase

Hi! This project i created in other forum(Third Person Shooter (TPS) COSMOS - #36 by ma3rx)
Download structure this(my game archive the big to panda3d server) :

./models :
enemies : enemies.zip
player : player.zip
weapon : weapon.zip
whishlyflash : whishlyflash.zip

world(instruction and in directory ./models) :
https://www.panda3d.org/download/noversion/art-gallery.zip - download this panda3d art.
Go to directory cat-enviroments/bvw-f2004–milleniumfalcon.

drop falcon to directory in my project models/world


./fonts : fonts.zip

./sounds : sounds.zip

./special_effects : special_effects.zip

code droid.py :

#!/usr/bin/env python
# -*- coding: utf_8 -*-

# Импортируем все необходимые инструменты
from direct.showbase.ShowBase import ShowBase
from panda3d.core import CollisionTraverser, CollisionNode
from panda3d.core import CollisionHandlerQueue, CollisionRay
from panda3d.core import Filename, AmbientLight, DirectionalLight
from panda3d.core import PandaNode, NodePath, Camera, TextNode
from direct.particles.Particles import Particles
from direct.particles.ParticleEffect import ParticleEffect
from panda3d.core import PointLight, Spotlight
from panda3d.core import CollideMask
from panda3d.core import loadPrcFileData
from panda3d.core import *
from direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectButton import DirectButton
from direct.actor.Actor import Actor
import random
import sys
import time
import os
import math

settings = ['window-title Droid Game 2.0.3.3',
            'full-screen false']

loadPrcFileData('', settings[0]) # Сделаем не стандартный заголовок (возмём его из переменной settings)
loadPrcFileData('', settings[1]) # Не будем включать полноэкранный режим

SPRITE_POS = SPRITE_POS = 55

# Создадим главный класс нашей игры
class DroidShooter(ShowBase):
    def __init__(self):
        ShowBase.__init__(self) # Загружаем все селфы из direct
        self.menu() # звпуск меню
        
    def menu(self, pos=LPoint3(0, 0), depth=SPRITE_POS, scale=1):
        
        base.enableParticles() # инициализируем эффект дыма

        self.win.setClearColor((0, 0, 0, 1)) # Закрашиваем поверхность чёрным. Дело в том, что по умолчанию в этом игровом движке поверхность закрашивается серым.
        self.font = loader.loadFont('./fonts/doom_font.ttf') # загрузим шрифт из игры doom
        self.inst_font = loader.loadFont('./fonts/arial.ttf') # загрузим шрифт arial

        self.crackSound = loader.loadMusic('./sounds/crack.wav') # звук взрыва корабля
        self.shotSound = loader.loadMusic('./sounds/shot.wav') # звук выстрела
        self.crackSound.setVolume(1) # полная громкость звука
        self.shotSound.setVolume(1) # полная громкость звука

        self.button_start = DirectButton(pos=(0.7, 0, 0), text="CTAPT",
                                   scale=.3, pad=(.2, .2),
                                   command=self.load_game) # кнопка запуска игры
        
        # Напечатаем управление
        self.inst1 = self.addInstructions(0.95, u"[ESC]: Выход")
        self.inst2 = self.addInstructions(0.90, u"[Left Arrow]: Поворот дроида в лево")
        self.inst3 = self.addInstructions(0.85, u"[Right Arrow]: Поворот дроида в право")
        self.inst4 = self.addInstructions(0.80, u"[Up Arrow]: Вперед")
        self.inst5 = self.addInstructions(0.75, u"[a]: поворот камеры в право")
        self.inst6 = self.addInstructions(0.70, u"[d]: поворот камеры в лево")
        self.inst7 = self.addInstructions(0.65, u"[Left Arrow + Up arrow]: поворот дроида в лево и вперёд")
        self.inst8 = self.addInstructions(0.60, u"[Right Arrow + Up arrow]: поворот дроида в право и вперёд")
        self.inst9 = self.addInstructions(0.55, u"[a + Up arrow]: поворот камеры в право и вперёд")
        self.inst10 = self.addInstructions(0.50, u"[d + Up arrow]: поворот камеры в лево и вперёд")
        self.inst11 = self.addInstructions(0.45, u"[space]: стрельба")
        self.inst12 = self.addInstructions(0.40, u"[l]: включить свет")
        self.inst13 = self.addInstructions(0.35, u"[s]: достать фонарик")
        self.inst14 = self.addInstructions(0.30, u"[w]: убрать оружие")
        self.inst15 = self.addInstructions(0.25, u"[f]: полный разгон")
        self.inst16 = self.addInstructions(0.20, u"[0]: пожаротушительная система")

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.
        
    def load_game(self):
        self.button_start.hide() # удалим кнопку старта
        # удалим кнопки управления
        self.inst1.hide()
        self.inst2.hide()
        self.inst3.hide()
        self.inst4.hide()
        self.inst5.hide()
        self.inst6.hide()
        self.inst7.hide()
        self.inst8.hide()
        self.inst9.hide()
        self.inst10.hide()
        self.inst11.hide()
        self.inst12.hide()
        self.inst13.hide()
        self.inst14.hide()
        self.inst15.hide()
        self.inst16.hide()
        self.keyMap = {
            "left": 0, "right": 0, "forward": 0, "cam-left": 0, "cam-right": 0} # Создадим словарь кнопок, по их нажатию




        self.environ = loader.loadModel("models/world/falcon.egg") # Загрузим уже созданный в blender мир.
        self.environ.reparentTo(render) # Загружаем модель мира в окно

        droidStartPos = (-1, 6, 1.5 ) # Загружаем стартовую позицию игрока в мире.
        enemyStartPos = (-6, 1, 0.5) # Загружаем позицию помощника-дроида.
        self.droid = Actor("models/player/seeker.egg") # Загружаем модель игрока (созданная в blender)
        self.enemy = Actor("models/enemies/r2d2/r2d2.egg") # Загружаем модель помощника (созданная в blender)
        self.droid.reparentTo(render) # Перемещаем модель игрока в наш мир.
        self.enemy.reparentTo(render) # Перемещаем модель помощника в наш мир.
        self.droid.setScale(1) # Делаем размер игрока на 1
        self.enemy.setScale(1) # Делаем размер помощника на 1
        self.droid.setPos(droidStartPos) # Ставим позицию игрока на ранее созданую нами позицию
        self.enemy.setPos(enemyStartPos) # Ставим позицию помощника на ранее созданую нами позицию

        self.weapon = Actor('models/weapon/lightsaber.egg') # загрузим оружие
        self.bullet = Actor('models/weapon/bullet/bullet.egg.pz') # Загрузим пулю
        self.flash = Actor('models/whishlyflash/handlamp.egg') # Загрузим фонарик
               
        self.weapon.reparentTo(render) # переместим оружие в наш мир
        self.bullet.reparentTo(render) # переместим пулю в мир но появится она сама не скоро
        self.flash.reparentTo(render) # переместим фонарик в наш мир
        self.weapon.setScale(.5) # зададим размер пушки
        self.bullet.setScale(.5) # зададим размер пули
        self.flash.setScale(.5) # зададим размер фонарика
        

        self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 1 # Позиция пушки
        
        self.weapon.setPos(self.weapon_pos) # Поместим оружие чуть выше игрока
        
        
        self.spotlight = camera.attachNewNode(Spotlight("spotlight")) # Конфиги фонарика
        self.spotlight.node().setColor((.3, .3, .3, 1))
        self.spotlight.node().setSpecularColor((0, 0, 0, 1))

        self.floater = NodePath(PandaNode("floater")) 
        self.floater.reparentTo(self.droid)
        self.floater.setZ(2.0)

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.
        
        self.accept("arrow_left", self.setKey, ["left", True]) # При кнопке влево - поворачиваем игрока влево
        self.accept("arrow_right", self.setKey, ["right", True]) # При кнопке вправо - поворачиваем игрока вправо
        self.accept("arrow_up", self.setKey, ["forward", True]) # При кнопке вперёд - идём вперёд
        self.accept("a", self.setKey, ["cam-left", True]) # При кнопке a - разворачиваем камеру вокруг нашей модельки
        self.accept("d", self.setKey, ["cam-right", True]) # При кнопке s - разворачиваем камеру вокруг нашей модельки
        self.accept("arrow_left-up", self.setKey, ["left", False]) # При кнопке вверх+влево - поворачиваем игрока влево и идём вперёд
        self.accept("arrow_right-up", self.setKey, ["right", False]) # При кнопке вверх+право - поворачиваем игрока вправо и идём вперёд
        self.accept("arrow_up-up", self.setKey, ["forward", False]) # При кнопке вверх+вверъ - идём вперёд и идём вперёд
        self.accept("a-up", self.setKey, ["cam-left", False]) # При кнопке a+вперёд - поворачиваем камеру влево и идём вперёд
        self.accept("d-up", self.setKey, ["cam-right", False]) # При кнопке s+вперёд - поворачиваем камеру вправо и идём вперёд

        self.accept("space", self.shot) # при пробеле стреляем
        self.accept("l", self.white_light) # при нажатии l - включаем свет.
        self.accept("s", self.toggleLights, [[self.spotlight]]) # Включить фонарик
        self.accept("w", self.weapon_hide) # при кнопке w - уберём оружие.

        base.enableParticles() # Включаем инициализацию дыма
        self.t = loader.loadModel("models/weapon/lightsaber.egg") # Загрузим ещё оружие
        self.t.reparentTo(render) # Переместим оружие в наш мир
        self.p = ParticleEffect() # Включим эффект дыма
        self.accept('f', self.particle_start) # при нажатии f(от force) -  загрузим файл дыма и переместим в конфиг чтобы именно эта анимация стала отображением дыма
        self.accept('0', self.fountain) # при  нажатии кнопок f+o(fountain) включим пожаротушительную систему

        taskMgr.add(self.move, "moveTask") # Добавляем задачу в наш движок

        self.isMoving = False # Ставим значение isMoving на False(Вы можете менять это значение) чтобы игрок изначально стоял.
        # Делаем так, чтобы свет был изначально выключен.
        self.disableMouse() # Отключаем перемещение через мышку
        self.camera.setPos(self.droid.getX(), self.droid.getY() + 1, 3) # Ставим позицию камеры чуть больше позиции игрока

        self.black_light() # Сделаем выключенный свет

        self.state = 100 # Состояние корабля
        self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
                
        self.check_loss() # Проверяем поражение
        self.hide_weapon = False # Поставим, что оружие не убрано
        self.steam_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 5)
        self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 1)        
                

    def loadParticleConfig(self, filename, pos):
        self.p.cleanup()
        self.p = ParticleEffect()
        self.p.loadConfig(Filename(filename))

        self.p.start(self.t)
        self.p.setPos(pos)

    def particle_start(self):
        self.state -= 1
        self.state_info.hide()
        if self.state != 10:
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss()
            self.loadParticleConfig('special_effects/steam/steam.ptf', self.steam_pos)
        else:
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss()
            self.loadParticleConfig('special_effects/steam_critic/steam.ptf', self.steam_pos)

        if self.state < 10:
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss()
            self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.steam_pos)          

    def fountain(self):
        if self.state != 100:
            self.state += 1
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
        self.loadParticleConfig('special_effects/fountain/fountain.ptf', self.fountain_pos)
        
        
    def black_light(self):
        # Выключеный свет
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor((0, 0, 0, 1))
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))
        
    def white_light(self):
        # Освещение
        self.light = (.3, .3, .3, 1)
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor(self.light)
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))

    def weapon_hide(self):
        # Убрать оружие
        self.hide_weapon = True
        self.weapon.hide()

    def toggleLights(self, lights):
        # Специализированное освещение
        if self.hide_weapon:
            for light in lights:
                if render.hasLight(light):
                    render.clearLight(light)
                else:
                    render.setLight(light)
            
            self.flash.setPos(self.weapon_pos)
        else:
            return


    def shot(self):
        if not self.hide_weapon : # если оружие убрано то мы не можем стрелять
            self.shotSound.play() # играем звук выстрела
            dt = globalClock.getDt() # Выдаём скорость пули
            self.bullet.setPos(self.weapon_pos) # Пуля будет спавнится внутри пушки
            self.bullet.setY(self.bullet, -25 * dt) # Сдвигаем пулю на огромной скорости вперёд

    def check_loss(self):
        if self.state == 0:
            self.black_light()
            self.weapon.hide()
            self.info = OnscreenText(text='поражение', style=1, font=self.font,
                                     fg=(1,1,1,1),
                            pos=(-1.3, -0.5), align=TextNode.ALeft, scale=0.3)
            self.crackSound.play()
            

    def setKey(self, key, value):
        self.keyMap[key] = value # Делаем мехaнuзм нажатия клавиш.

    def move(self, task):

        ''' Делаем фуцнкцию движения игрока '''

        dt = globalClock.getDt() - .005 # Cкорость движения

        # Поворот камеры влево и вправо

        if self.keyMap["cam-left"]:
            self.camera.setX(self.camera, -20 * dt) # Меняем положеника камеры по икс. Таким образом получается илюзия поворота угла луча. Но на самом деле камера просто перемещается.
        if self.keyMap["cam-right"]:
            self.camera.setX(self.camera, +20 * dt) # Тоже самое, что и наверху.

        startpos = self.droid.getPos() # Сделаем удобную переменную позиции игрока

        if self.keyMap["left"]:
            self.droid.setH(self.droid.getH() + 300 * dt)
        if self.keyMap["right"]:
            self.droid.setH(self.droid.getH() - 300 * dt)
        if self.keyMap["forward"]:
            self.droid.setY(self.droid, -25 * dt)
            # Сделаем специальное условие
            if not self.hide_weapon:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.weapon.setPos(self.weapon_pos) # Обновляем позицию пушки
            else:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.flash.setPos(self.weapon_pos)



        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)


        return task.cont

    def addInstructions(self, pos, msg):
        return OnscreenText(text=msg, style=1, fg=(1,1,1,1), font=self.inst_font, pos=(-1.3, pos), align=TextNode.ALeft, scale = .04, shadow=(0,0,0,1))
    

droid = DroidShooter() # Создадим экземпляр класса нашей игры
droid.run() # 3апустим игру

Our developers :
@panda3dmastercoder
@ma3rx (I)

Screenshot :

Direction :
123

Join to our team developers !

1 Like

Hi! Created 2.0.4 update! - motor updating.
New code :

#!/usr/bin/env python
# -*- coding: utf_8 -*-

# Импортируем все необходимые инструменты
from direct.showbase.ShowBase import ShowBase
from panda3d.core import CollisionTraverser, CollisionNode
from panda3d.core import CollisionHandlerQueue, CollisionRay
from panda3d.core import Filename, AmbientLight, DirectionalLight
from panda3d.core import PandaNode, NodePath, Camera, TextNode
from direct.particles.Particles import Particles
from direct.particles.ParticleEffect import ParticleEffect
from panda3d.core import PointLight, Spotlight
from panda3d.core import CollideMask
from panda3d.core import loadPrcFileData
from panda3d.core import *
from direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectButton import DirectButton
from direct.actor.Actor import Actor
import random
import sys
import time
import os
import math

settings = ['window-title Droid Game 2.0.4',
            'full-screen false']

loadPrcFileData('', settings[0]) # Сделаем не стандартный заголовок (возмём его из переменной settings)
loadPrcFileData('', settings[1]) # Не будем включать полноэкранный режим

SPRITE_POS = SPRITE_POS = 55

# Создадим главный класс нашей игры
class DroidShooter(ShowBase):
    def __init__(self):
        ShowBase.__init__(self) # Загружаем все селфы из direct
        self.menu() # звпуск меню
        
    def menu(self):
        
        base.enableParticles() # инициализируем эффект дыма

        self.win.setClearColor((0, 0, 0, 1)) # Закрашиваем поверхность чёрным. Дело в том, что по умолчанию в этом игровом движке поверхность закрашивается серым.
        self.font = loader.loadFont('./fonts/doom_font.ttf') # загрузим шрифт из игры doom
        self.inst_font = loader.loadFont('./fonts/arial.ttf') # загрузим шрифт arial

        self.crackSound = loader.loadMusic('./sounds/crack.wav') # звук взрыва корабля
        self.shotSound = loader.loadMusic('./sounds/shot.wav') # звук выстрела
        self.crackSound.setVolume(1) # полная громкость звука
        self.shotSound.setVolume(1) # полная громкость звука

        self.button_start = DirectButton(pos=(0.7, 0, 0), text="CTAPT",
                                   scale=.3, pad=(.2, .2),
                                   command=self.load_game) # кнопка запуска игры
        
        # Напечатаем управление
        self.inst1 = self.addInstructions(0.95, u"[ESC]: Выход")
        self.inst2 = self.addInstructions(0.90, u"[Left Arrow]: Поворот дроида в лево")
        self.inst3 = self.addInstructions(0.85, u"[Right Arrow]: Поворот дроида в право")
        self.inst4 = self.addInstructions(0.80, u"[Up Arrow]: Вперед")
        self.inst5 = self.addInstructions(0.75, u"[a]: поворот камеры в право")
        self.inst6 = self.addInstructions(0.70, u"[d]: поворот камеры в лево")
        self.inst7 = self.addInstructions(0.65, u"[Left Arrow + Up arrow]: поворот дроида в лево и вперёд")
        self.inst8 = self.addInstructions(0.60, u"[Right Arrow + Up arrow]: поворот дроида в право и вперёд")
        self.inst9 = self.addInstructions(0.55, u"[a + Up arrow]: поворот камеры в право и вперёд")
        self.inst10 = self.addInstructions(0.50, u"[d + Up arrow]: поворот камеры в лево и вперёд")
        self.inst11 = self.addInstructions(0.45, u"[space]: стрельба")
        self.inst12 = self.addInstructions(0.40, u"[l]: включить свет")
        self.inst13 = self.addInstructions(0.35, u"[s]: достать фонарик")
        self.inst14 = self.addInstructions(0.30, u"[w]: убрать оружие")
        self.inst15 = self.addInstructions(0.25, u"[f]: полный разгон")
        self.inst16 = self.addInstructions(0.20, u"[0]: пожаротушительная система")

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.
        
    def load_game(self):
        self.button_start.hide() # удалим кнопку старта
        # удалим кнопки управления
        self.inst1.hide()
        self.inst2.hide()
        self.inst3.hide()
        self.inst4.hide()
        self.inst5.hide()
        self.inst6.hide()
        self.inst7.hide()
        self.inst8.hide()
        self.inst9.hide()
        self.inst10.hide()
        self.inst11.hide()
        self.inst12.hide()
        self.inst13.hide()
        self.inst14.hide()
        self.inst15.hide()
        self.inst16.hide()
        self.keyMap = {
            "left": 0, "right": 0, "forward": 0, "cam-left": 0, "cam-right": 0} # Создадим словарь кнопок, по их нажатию




        self.environ = loader.loadModel("models/world/falcon.egg") # Загрузим уже созданный в blender мир.
        self.environ.reparentTo(render) # Загружаем модель мира в окно

        droidStartPos = (-1, 6, 1.5 ) # Загружаем стартовую позицию игрока в мире.
        enemyStartPos = (-6, 1, 0.5) # Загружаем позицию помощника-дроида.
        self.droid = Actor("models/player/seeker.egg") # Загружаем модель игрока (созданная в blender)
        self.enemy = Actor("models/enemies/r2d2/r2d2.egg") # Загружаем модель помощника (созданная в blender)
        self.droid.reparentTo(render) # Перемещаем модель игрока в наш мир.
        self.enemy.reparentTo(render) # Перемещаем модель помощника в наш мир.
        self.droid.setScale(1) # Делаем размер игрока на 1
        self.enemy.setScale(1) # Делаем размер помощника на 1
        self.droid.setPos(droidStartPos) # Ставим позицию игрока на ранее созданую нами позицию
        self.enemy.setPos(enemyStartPos) # Ставим позицию помощника на ранее созданую нами позицию

        self.weapon = Actor('models/weapon/lightsaber.egg') # загрузим оружие
        self.bullet = Actor('models/weapon/bullet/bullet.egg.pz') # Загрузим пулю
        self.flash = Actor('models/whishlyflash/handlamp.egg') # Загрузим фонарик
               
        self.weapon.reparentTo(render) # переместим оружие в наш мир
        self.bullet.reparentTo(render) # переместим пулю в мир но появится она сама не скоро
        self.flash.reparentTo(render) # переместим фонарик в наш мир
        self.weapon.setScale(.5) # зададим размер пушки
        self.bullet.setScale(.5) # зададим размер пули
        self.flash.setScale(.5) # зададим размер фонарика
        

        self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 1 # Позиция пушки
        
        self.weapon.setPos(self.weapon_pos) # Поместим оружие чуть выше игрока
        
        
        self.spotlight = camera.attachNewNode(Spotlight("spotlight")) # Конфиги фонарика
        self.spotlight.node().setColor((.3, .3, .3, 1))
        self.spotlight.node().setSpecularColor((0, 0, 0, 1))

        self.floater = NodePath(PandaNode("floater")) 
        self.floater.reparentTo(self.droid)
        self.floater.setZ(2.0)

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.
        
        self.accept("arrow_left", self.setKey, ["left", True]) # При кнопке влево - поворачиваем игрока влево
        self.accept("arrow_right", self.setKey, ["right", True]) # При кнопке вправо - поворачиваем игрока вправо
        self.accept("arrow_up", self.setKey, ["forward", True]) # При кнопке вперёд - идём вперёд
        self.accept("a", self.setKey, ["cam-left", True]) # При кнопке a - разворачиваем камеру вокруг нашей модельки
        self.accept("d", self.setKey, ["cam-right", True]) # При кнопке s - разворачиваем камеру вокруг нашей модельки
        self.accept("arrow_left-up", self.setKey, ["left", False]) # При кнопке вверх+влево - поворачиваем игрока влево и идём вперёд
        self.accept("arrow_right-up", self.setKey, ["right", False]) # При кнопке вверх+право - поворачиваем игрока вправо и идём вперёд
        self.accept("arrow_up-up", self.setKey, ["forward", False]) # При кнопке вверх+вверъ - идём вперёд и идём вперёд
        self.accept("a-up", self.setKey, ["cam-left", False]) # При кнопке a+вперёд - поворачиваем камеру влево и идём вперёд
        self.accept("d-up", self.setKey, ["cam-right", False]) # При кнопке s+вперёд - поворачиваем камеру вправо и идём вперёд

        self.accept("space", self.shot) # при пробеле стреляем
        self.accept("l", self.white_light) # при нажатии l - включаем свет.
        self.accept("s", self.toggleLights, [[self.spotlight]]) # Включить фонарик
        self.accept("w", self.weapon_hide) # при кнопке w - уберём оружие.

        base.enableParticles() # Включаем инициализацию дыма
        self.t = loader.loadModel("models/weapon/lightsaber.egg") # Загрузим ещё оружие
        self.t.reparentTo(render) # Переместим оружие в наш мир
        self.p = ParticleEffect() # Включим эффект дыма
        self.accept('f', self.particle_start) # при нажатии f(от force) -  загрузим файл дыма и переместим в конфиг чтобы именно эта анимация стала отображением дыма
        self.accept('0', self.fountain) # при  нажатии кнопок f+o(fountain) включим пожаротушительную систему

        taskMgr.add(self.move, "moveTask") # Добавляем задачу в наш движок

        self.isMoving = False # Ставим значение isMoving на False(Вы можете менять это значение) чтобы игрок изначально стоял.
        # Делаем так, чтобы свет был изначально выключен.
        self.disableMouse() # Отключаем перемещение через мышку
        self.camera.setPos(self.droid.getX(), self.droid.getY() + 1, 3) # Ставим позицию камеры чуть больше позиции игрока

        self.black_light() # Сделаем выключенный свет

        self.state = 100 # Состояние корабля
        self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
                
        self.check_loss() # Проверяем поражение
        self.hide_weapon = False # Поставим, что оружие не убрано
        self.steam_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 5)
        self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 1)

        self.motor_pos1 = 25.238849639892578, 8.962211608886719, 1.5 # позиция первого мотора
        self.motor_pos2 = -20.676218032836914, 10.55816650390625, 1.5 # позиция второго мотора

        self.motor1 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos1) # мотор 1
        self.motor2 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos2) # мотор 2

                

    def loadParticleConfig(self, filename, pos):
        self.p.cleanup()
        self.p = ParticleEffect()
        self.p.loadConfig(Filename(filename))

        self.p.start(self.t)
        self.p.setPos(pos)


    def particle_start(self):
        self.state -= 1
        self.state_info.hide()
        if self.state != 10:
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss()
            self.loadParticleConfig('special_effects/steam/steam.ptf', self.steam_pos)
        else:
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss()
            self.loadParticleConfig('special_effects/steam_critic/steam.ptf', self.steam_pos)

        if self.state < 10:
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss()
            self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.steam_pos)          

    def fountain(self):
        if self.state != 100:
            self.state += 1
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
        self.loadParticleConfig('special_effects/fountain/fountain.ptf', self.fountain_pos)
        
        
    def black_light(self):
        # Выключеный свет
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor((0, 0, 0, 1))
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))
        
    def white_light(self):
        # Освещение
        self.light = (.3, .3, .3, 1)
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor(self.light)
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))

    def weapon_hide(self):
        # Убрать оружие
        self.hide_weapon = True
        self.weapon.hide()

    def toggleLights(self, lights):
        # Специализированное освещение
        if self.hide_weapon:
            for light in lights:
                if render.hasLight(light):
                    render.clearLight(light)
                else:
                    render.setLight(light)
            
            self.flash.setPos(self.weapon_pos)
        else:
            return


    def shot(self):
        if not self.hide_weapon : # если оружие убрано то мы не можем стрелять
            self.shotSound.play() # играем звук выстрела
            dt = globalClock.getDt() # Выдаём скорость пули
            self.bullet.setPos(self.weapon_pos) # Пуля будет спавнится внутри пушки
            self.bullet.setY(self.bullet, -25 * dt) # Сдвигаем пулю на огромной скорости вперёд

    def check_loss(self):
        if self.state == 0:
            self.black_light()
            self.weapon.hide()
            self.info = OnscreenText(text='поражение', style=1, font=self.font,
                                     fg=(1,1,1,1),
                            pos=(-1.3, -0.5), align=TextNode.ALeft, scale=0.3)
            self.crackSound.play()
            

    def setKey(self, key, value):
        self.keyMap[key] = value # Делаем мехaнuзм нажатия клавиш.

    def move(self, task):

        ''' Делаем фуцнкцию движения игрока '''

        dt = globalClock.getDt() - .005 # Cкорость движения

        # Поворот камеры влево и вправо

        if self.keyMap["cam-left"]:
            self.camera.setX(self.camera, -20 * dt) # Меняем положеника камеры по икс. Таким образом получается илюзия поворота угла луча. Но на самом деле камера просто перемещается.
        if self.keyMap["cam-right"]:
            self.camera.setX(self.camera, +20 * dt) # Тоже самое, что и наверху.

        startpos = self.droid.getPos() # Сделаем удобную переменную позиции игрока

        if self.keyMap["left"]:
            self.droid.setH(self.droid.getH() + 300 * dt)
        if self.keyMap["right"]:
            self.droid.setH(self.droid.getH() - 300 * dt)
        if self.keyMap["forward"]:
            self.droid.setY(self.droid, -25 * dt)
            # Сделаем специальное условие проверки убрано оружие или нет
            if not self.hide_weapon:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.weapon.setPos(self.weapon_pos) # Обновляем позицию пушки
            else:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.flash.setPos(self.weapon_pos)



        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)


        return task.cont

    def addInstructions(self, pos, msg):
        return OnscreenText(text=msg, style=1, fg=(1,1,1,1), font=self.inst_font, pos=(-1.3, pos), align=TextNode.ALeft, scale = .04, shadow=(0,0,0,1))
    

droid = DroidShooter() # Создадим экземпляр класса нашей игры
droid.run() # 3апустим игру

Screenshot :

Hi! I created 2.0.5 update!

New code :

#!/usr/bin/env python
# -*- coding: utf_8 -*-

# Импортируем все необходимые инструменты
from direct.showbase.ShowBase import ShowBase
from panda3d.core import CollisionTraverser, CollisionNode
from panda3d.core import CollisionHandlerQueue, CollisionRay
from panda3d.core import Filename, AmbientLight, DirectionalLight
from panda3d.core import PandaNode, NodePath, Camera, TextNode
from direct.particles.Particles import Particles
from direct.particles.ParticleEffect import ParticleEffect
from panda3d.core import PointLight, Spotlight
from panda3d.core import CollideMask
from panda3d.core import loadPrcFileData
from panda3d.core import *
from direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectButton import DirectButton
from direct.actor.Actor import Actor
import random
import sys
import time
import os
import math

VERSION = '2.0.5'

settings = ['window-title Droid Game ' +  VERSION,
            'full-screen false']

loadPrcFileData('', settings[0]) # Сделаем не стандартный заголовок (возмём его из переменной settings)
loadPrcFileData('', settings[1]) # Не будем включать полноэкранный режим

SPRITE_POS = SPRITE_POS = 55

# Создадим главный класс нашей игры
class DroidShooter(ShowBase):
    def __init__(self):
        ShowBase.__init__(self) # Загружаем все селфы из direct
        self.menu() # звпуск меню
        
    def menu(self):
        
        base.enableParticles() # инициализируем эффект дыма

        self.win.setClearColor((0, 0, 0, 1)) # Закрашиваем поверхность чёрным. Дело в том, что по умолчанию в этом игровом движке поверхность закрашивается серым.
        self.font = loader.loadFont('./fonts/doom_font.ttf') # загрузим шрифт из игры doom
        self.inst_font = loader.loadFont('./fonts/arial.ttf') # загрузим шрифт arial

        self.crackSound = loader.loadMusic('./sounds/crack.wav') # звук взрыва корабля
        self.shotSound = loader.loadMusic('./sounds/shot.wav') # звук выстрела
        self.startSound = loader.loadMusic('./sounds/started.wav') # звук запуска
        self.startSound.setVolume(1) # полная громкость звука
        self.crackSound.setVolume(1) # полная громкость звука
        self.shotSound.setVolume(1) # полная громкость звука
        self.startSound.play() # играем звук запуска игры

        self.button_start = DirectButton(pos=(0.7, 0, 0), text="CTAPT",
                                   scale=.3, pad=(.2, .2),
                                   command=self.load_game) # кнопка запуска игры


        self.title = self.addTitle("Droid game " + VERSION)
        
        # Напечатаем управление
        self.inst1 = self.addInstructions(0.95, u"[ESC]: Выход")
        self.inst2 = self.addInstructions(0.90, u"[Left Arrow]: Поворот дроида в лево")
        self.inst3 = self.addInstructions(0.85, u"[Right Arrow]: Поворот дроида в право")
        self.inst4 = self.addInstructions(0.80, u"[Up Arrow]: Вперед")
        self.inst5 = self.addInstructions(0.75, u"[a]: поворот камеры в право")
        self.inst6 = self.addInstructions(0.70, u"[d]: поворот камеры в лево")
        self.inst7 = self.addInstructions(0.65, u"[Left Arrow + Up arrow]: поворот дроида в лево и вперёд")
        self.inst8 = self.addInstructions(0.60, u"[Right Arrow + Up arrow]: поворот дроида в право и вперёд")
        self.inst9 = self.addInstructions(0.55, u"[a + Up arrow]: поворот камеры в право и вперёд")
        self.inst10 = self.addInstructions(0.50, u"[d + Up arrow]: поворот камеры в лево и вперёд")
        self.inst11 = self.addInstructions(0.45, u"[space]: стрельба")
        self.inst12 = self.addInstructions(0.40, u"[l]: включить свет")
        self.inst13 = self.addInstructions(0.35, u"[s]: достать фонарик")
        self.inst14 = self.addInstructions(0.30, u"[w]: убрать оружие")
        self.inst15 = self.addInstructions(0.25, u"[f]: полный разгон(ОПАСНО!)")
        self.inst16 = self.addInstructions(0.20, u"[0]: пожаротушительная система")

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.
        
    def load_game(self):
        self.button_start.hide() # удалим кнопку старта
        self.title.hide() # удалим заголовок игры
        # удалим кнопки управления
        self.inst1.hide()
        self.inst2.hide()
        self.inst3.hide()
        self.inst4.hide()
        self.inst5.hide()
        self.inst6.hide()
        self.inst7.hide()
        self.inst8.hide()
        self.inst9.hide()
        self.inst10.hide()
        self.inst11.hide()
        self.inst12.hide()
        self.inst13.hide()
        self.inst14.hide()
        self.inst15.hide()
        self.inst16.hide()
        self.keyMap = {
            "left": 0, "right": 0, "forward": 0, "cam-left": 0, "cam-right": 0} # Создадим словарь кнопок, по их нажатию




        self.environ = loader.loadModel("models/world/falcon.egg") # Загрузим уже созданный в blender мир.
        self.environ.reparentTo(render) # Загружаем модель мира в окно

        droidStartPos = (-1, 6, 1.5 ) # Загружаем стартовую позицию игрока в мире.
        enemyStartPos = (-6, 1, 0.5) # Загружаем позицию помощника-дроида.
        self.droid = Actor("models/player/seeker.egg") # Загружаем модель игрока (созданная в blender)
        self.enemy = Actor("models/enemies/r2d2/r2d2.egg") # Загружаем модель помощника (созданная в blender)
        self.droid.reparentTo(render) # Перемещаем модель игрока в наш мир.
        self.enemy.reparentTo(render) # Перемещаем модель помощника в наш мир.
        self.droid.setScale(1) # Делаем размер игрока на 1
        self.enemy.setScale(1) # Делаем размер помощника на 1
        self.droid.setPos(droidStartPos) # Ставим позицию игрока на ранее созданую нами позицию
        self.enemy.setPos(enemyStartPos) # Ставим позицию помощника на ранее созданую нами позицию

        self.weapon = Actor('models/weapon/lightsaber.egg') # загрузим оружие
        self.bullet = Actor('models/weapon/bullet/bullet.egg.pz') # Загрузим пулю
        self.flash = Actor('models/whishlyflash/handlamp.egg') # Загрузим фонарик
               
        self.weapon.reparentTo(render) # переместим оружие в наш мир
        self.bullet.reparentTo(render) # переместим пулю в мир но появится она сама не скоро
        self.flash.reparentTo(render) # переместим фонарик в наш мир
        self.weapon.setScale(.5) # зададим размер пушки
        self.bullet.setScale(.5) # зададим размер пули
        self.flash.setScale(.5) # зададим размер фонарика
        

        self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 1 # Позиция пушки
        
        self.weapon.setPos(self.weapon_pos) # Поместим оружие чуть выше игрока
        
        
        self.spotlight = camera.attachNewNode(Spotlight("spotlight")) # Конфиги фонарика
        self.spotlight.node().setColor((.3, .3, .3, 1))
        self.spotlight.node().setSpecularColor((0, 0, 0, 1))

        self.floater = NodePath(PandaNode("floater")) 
        self.floater.reparentTo(self.droid)
        self.floater.setZ(2.0)

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.
        
        self.accept("arrow_left", self.setKey, ["left", True]) # При кнопке влево - поворачиваем игрока влево
        self.accept("arrow_right", self.setKey, ["right", True]) # При кнопке вправо - поворачиваем игрока вправо
        self.accept("arrow_up", self.setKey, ["forward", True]) # При кнопке вперёд - идём вперёд
        self.accept("a", self.setKey, ["cam-left", True]) # При кнопке a - разворачиваем камеру вокруг нашей модельки
        self.accept("d", self.setKey, ["cam-right", True]) # При кнопке s - разворачиваем камеру вокруг нашей модельки
        self.accept("arrow_left-up", self.setKey, ["left", False]) # При кнопке вверх+влево - поворачиваем игрока влево и идём вперёд
        self.accept("arrow_right-up", self.setKey, ["right", False]) # При кнопке вверх+право - поворачиваем игрока вправо и идём вперёд
        self.accept("arrow_up-up", self.setKey, ["forward", False]) # При кнопке вверх+вверъ - идём вперёд и идём вперёд
        self.accept("a-up", self.setKey, ["cam-left", False]) # При кнопке a+вперёд - поворачиваем камеру влево и идём вперёд
        self.accept("d-up", self.setKey, ["cam-right", False]) # При кнопке s+вперёд - поворачиваем камеру вправо и идём вперёд

        self.accept("space", self.shot) # при пробеле стреляем
        self.accept("l", self.white_light) # при нажатии l - включаем свет.
        self.accept("s", self.toggleLights, [[self.spotlight]]) # Включить фонарик
        self.accept("w", self.weapon_hide) # при кнопке w - уберём оружие.

        base.enableParticles() # Включаем инициализацию дыма
        self.t = loader.loadModel("models/weapon/lightsaber.egg") # Загрузим ещё оружие
        self.t.reparentTo(render) # Переместим оружие в наш мир
        self.p = ParticleEffect() # Включим эффект дыма
        self.accept('f', self.particle_start) # при нажатии f(от force) -  загрузим файл дыма и переместим в конфиг чтобы именно эта анимация стала отображением дыма
        self.accept('0', self.fountain) # при  нажатии кнопок f+o(fountain) включим пожаротушительную систему

        taskMgr.add(self.move, "moveTask") # Добавляем задачу в наш движок

        self.isMoving = False # Ставим значение isMoving на False(Вы можете менять это значение) чтобы игрок изначально стоял.
        # Делаем так, чтобы свет был изначально выключен.
        self.disableMouse() # Отключаем перемещение через мышку
        self.camera.setPos(self.droid.getX(), self.droid.getY() + 1, 3) # Ставим позицию камеры чуть больше позиции игрока

        self.black_light() # Сделаем выключенный свет

        self.state = 100 # Состояние корабля
        self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
                
        self.check_loss() # Проверяем поражение
        self.hide_weapon = False # Поставим, что оружие не убрано
        self.steam_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 5)
        self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 1)

        self.motor_pos1 = 25.238849639892578, 8.962211608886719, 1.5 # позиция первого мотора
        self.motor_pos2 = -20.676218032836914, 10.55816650390625, 1.5 # позиция второго мотора

        self.motor1 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos1) # мотор 1
        self.motor2 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos2) # мотор 2

                

    def loadParticleConfig(self, filename, pos):
        self.p.cleanup()
        self.p = ParticleEffect()
        self.p.loadConfig(Filename(filename))

        self.p.start(self.t)
        self.p.setPos(pos)


    def particle_start(self):
        self.state -= 1
        self.state_info.hide()
        if self.state != 10:
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss()
            self.loadParticleConfig('special_effects/steam/steam.ptf', self.steam_pos)
        else:
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss()
            self.loadParticleConfig('special_effects/steam_critic/steam.ptf', self.steam_pos)

        if self.state < 10:
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss()
            self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.steam_pos)          

    def fountain(self):
        if self.state != 100:
            self.state += 1
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
        self.loadParticleConfig('special_effects/fountain/fountain.ptf', self.fountain_pos)
        
        
    def black_light(self):
        # Выключеный свет
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor((0, 0, 0, 1))
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))
        
    def white_light(self):
        # Освещение
        self.light = (.3, .3, .3, 1)
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor(self.light)
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))

    def weapon_hide(self):
        # Убрать оружие
        self.hide_weapon = True
        self.weapon.hide()

    def toggleLights(self, lights):
        # Специализированное освещение
        if self.hide_weapon:
            for light in lights:
                if render.hasLight(light):
                    render.clearLight(light)
                else:
                    render.setLight(light)
            
            self.flash.setPos(self.weapon_pos)
        else:
            return


    def shot(self):
        if not self.hide_weapon : # если оружие убрано то мы не можем стрелять
            self.shotSound.play() # играем звук выстрела
            dt = globalClock.getDt() # Выдаём скорость пули
            self.bullet.setPos(self.weapon_pos) # Пуля будет спавнится внутри пушки
            self.bullet.setY(self.bullet, -25 * dt) # Сдвигаем пулю на огромной скорости вперёд

    def check_loss(self):
        if self.state == 0:
            self.black_light()
            self.weapon.hide()
            self.info = OnscreenText(text='поражение', style=1, font=self.font,
                                     fg=(1,1,1,1),
                            pos=(-1.3, -0.5), align=TextNode.ALeft, scale=0.3)
            self.crackSound.play()
            

    def setKey(self, key, value):
        self.keyMap[key] = value # Делаем мехaнuзм нажатия клавиш.

    def move(self, task):

        ''' Делаем фуцнкцию движения игрока '''

        dt = globalClock.getDt() - .005 # Cкорость движения

        # Поворот камеры влево и вправо

        if self.keyMap["cam-left"]:
            self.camera.setX(self.camera, -20 * dt) # Меняем положеника камеры по икс. Таким образом получается илюзия поворота угла луча. Но на самом деле камера просто перемещается.
        if self.keyMap["cam-right"]:
            self.camera.setX(self.camera, +20 * dt) # Тоже самое, что и наверху.

        startpos = self.droid.getPos() # Сделаем удобную переменную позиции игрока

        if self.keyMap["left"]:
            self.droid.setH(self.droid.getH() + 300 * dt)
        if self.keyMap["right"]:
            self.droid.setH(self.droid.getH() - 300 * dt)
        if self.keyMap["forward"]:
            self.droid.setY(self.droid, -25 * dt)
            # Сделаем специальное условие проверки убрано оружие или нет
            if not self.hide_weapon:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.weapon.setPos(self.weapon_pos) # Обновляем позицию пушки
            else:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.flash.setPos(self.weapon_pos)



        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)


        return task.cont

    def addInstructions(self, pos, msg):
        return OnscreenText(text=msg, style=1, fg=(1,1,1,1), font=self.inst_font, pos=(-1.3, pos), align=TextNode.ALeft, scale = .04, shadow=(0,0,0,1))

    def addTitle(self, text):
        return OnscreenText(text=text, style=1, fg=(1, 1, 1, 1), scale=0.2,
                            parent=base.a2dBottomRight, align=TextNode.ARight,
                            pos=(-0.1, 0.09), shadow=(0, 0, 0, 1), font=self.font)
    

droid = DroidShooter() # Создадим экземпляр класса нашей игры
droid.run() # 3апустим игру

Screenshot :

Hi! I create 2.0.6 update!!
R2d2 now can walk!
New code :

#!/usr/bin/env python
# -*- coding: utf_8 -*-

# Импортируем все необходимые инструменты
from direct.showbase.ShowBase import ShowBase
from panda3d.core import CollisionTraverser, CollisionNode
from panda3d.core import CollisionHandlerQueue, CollisionRay
from panda3d.core import Filename, AmbientLight, DirectionalLight
from panda3d.core import PandaNode, NodePath, Camera, TextNode
from direct.particles.Particles import Particles
from direct.particles.ParticleEffect import ParticleEffect
from panda3d.core import PointLight, Spotlight
from panda3d.core import CollideMask
from panda3d.core import loadPrcFileData
from panda3d.core import *
from direct.gui.OnscreenText import OnscreenText
from direct.gui.OnscreenImage import OnscreenImage
from direct.gui.DirectButton import DirectButton
from direct.actor.Actor import Actor
import random
import sys
import time
import os
import math

VERSION = '2.0.6'

settings = ['window-title Droid Game ' +  VERSION]
            

loadPrcFileData('', settings[0]) # Сделаем не стандартный заголовок (возмём его из переменной settings)
SPRITE_POS = SPRITE_POS = 55

# Создадим главный класс нашей игры
class DroidShooter(ShowBase):
    def __init__(self):
        ShowBase.__init__(self) # Загружаем все селфы из direct
        self.menu() # звпуск меню
        
    def menu(self, scale=(1, 1, 1), pos=(0, 0, 0), textsize=0.125):
        
        base.enableParticles() # инициализируем эффект дыма

        self.win.setClearColor((0, 0, 0, 1)) # Закрашиваем поверхность чёрным. Дело в том, что по умолчанию в этом игровом движке поверхность закрашивается серым.
        self.font = loader.loadFont('./fonts/doom_font.ttf') # загрузим шрифт из игры doom
        self.inst_font = loader.loadFont('./fonts/arial.ttf') # загрузим шрифт arial

        self.crackSound = loader.loadMusic('./sounds/crack.wav') # звук взрыва корабля
        self.shotSound = loader.loadMusic('./sounds/shot.wav') # звук выстрела
        self.startSound = loader.loadMusic('./sounds/started.wav') # звук запуска
        self.startSound.setVolume(1) # полная громкость звука
        self.crackSound.setVolume(1) # полная громкость звука
        self.shotSound.setVolume(1) # полная громкость звука
        self.startSound.play() # играем звук запуска игры

        self.button_start = DirectButton(pos=(0.7, 0, 0), text="CTAPT",
                                   scale=.3, pad=(.2, .2),
                                   command=self.load_game) # кнопка запуска игры


        self.title = self.addTitle("Droid game " + VERSION)
        
        # Напечатаем управление
        self.inst1 = self.addInstructions(0.95, u"[ESC]: Выход")
        self.inst2 = self.addInstructions(0.90, u"[Left Arrow]: Поворот дроида в лево")
        self.inst3 = self.addInstructions(0.85, u"[Right Arrow]: Поворот дроида в право")
        self.inst4 = self.addInstructions(0.80, u"[Up Arrow]: Вперед")
        self.inst5 = self.addInstructions(0.75, u"[a]: поворот камеры в право")
        self.inst6 = self.addInstructions(0.70, u"[d]: поворот камеры в лево")
        self.inst7 = self.addInstructions(0.65, u"[Left Arrow + Up arrow]: поворот дроида в лево и вперёд")
        self.inst8 = self.addInstructions(0.60, u"[Right Arrow + Up arrow]: поворот дроида в право и вперёд")
        self.inst9 = self.addInstructions(0.55, u"[a + Up arrow]: поворот камеры в право и вперёд")
        self.inst10 = self.addInstructions(0.50, u"[d + Up arrow]: поворот камеры в лево и вперёд")
        self.inst11 = self.addInstructions(0.45, u"[space]: стрельба")
        self.inst12 = self.addInstructions(0.40, u"[l]: включить свет")
        self.inst13 = self.addInstructions(0.35, u"[s]: достать фонарик")
        self.inst14 = self.addInstructions(0.30, u"[w]: убрать оружие")
        self.inst15 = self.addInstructions(0.25, u"[f]: полный разгон(ОПАСНО!)")
        self.inst16 = self.addInstructions(0.20, u"[0]: пожаротушительная система")

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.


        
    def load_game(self):
        self.button_start.hide() # удалим кнопку старта
        self.title.hide() # удалим заголовок игры
        # удалим кнопки управления
        self.inst1.hide()
        self.inst2.hide()
        self.inst3.hide()
        self.inst4.hide()
        self.inst5.hide()
        self.inst6.hide()
        self.inst7.hide()
        self.inst8.hide()
        self.inst9.hide()
        self.inst10.hide()
        self.inst11.hide()
        self.inst12.hide()
        self.inst13.hide()
        self.inst14.hide()
        self.inst15.hide()
        self.inst16.hide()
        self.keyMap = {
            "left": 0, "right": 0, "forward": 0, "cam-left": 0, "cam-right": 0} # Создадим словарь кнопок, по их нажатию




        self.environ = loader.loadModel("models/world/falcon.egg") # Загрузим уже созданный в blender мир.
        self.environ.reparentTo(render) # Загружаем модель мира в окно

        droidStartPos = (-1, 6, 1.5 ) # Загружаем стартовую позицию игрока в мире.
        enemyStartPos = (-6, 1, 0.5) # Загружаем позицию помощника-дроида.
        self.droid = Actor("models/player/seeker.egg") # Загружаем модель игрока (созданная в blender)
        self.enemy = Actor("models/enemies/r2d2/r2d2.egg") # Загружаем модель помощника (созданная в blender)
        self.droid.reparentTo(render) # Перемещаем модель игрока в наш мир.
        self.enemy.reparentTo(render) # Перемещаем модель помощника в наш мир.
        self.droid.setScale(1) # Делаем размер игрока на 1
        self.enemy.setScale(1) # Делаем размер помощника на 1
        self.droid.setPos(droidStartPos) # Ставим позицию игрока на ранее созданую нами позицию
        self.enemy.setPos(enemyStartPos) # Ставим позицию помощника на ранее созданую нами позицию

        self.weapon = Actor('models/weapon/lightsaber.egg') # загрузим оружие
        self.bullet = Actor('models/weapon/bullet/bullet.egg.pz') # Загрузим пулю
        self.flash = Actor('models/whishlyflash/handlamp.egg') # Загрузим фонарик
               
        self.weapon.reparentTo(render) # переместим оружие в наш мир
        self.bullet.reparentTo(render) # переместим пулю в мир но появится она сама не скоро
        self.flash.reparentTo(render) # переместим фонарик в наш мир
        self.weapon.setScale(.5) # зададим размер пушки
        self.bullet.setScale(.5) # зададим размер пули
        self.flash.setScale(.5) # зададим размер фонарика
        

        self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 1 # Позиция пушки
        
        self.weapon.setPos(self.weapon_pos) # Поместим оружие чуть выше игрока
        
        
        self.spotlight = camera.attachNewNode(Spotlight("spotlight")) # Конфиги фонарика
        self.spotlight.node().setColor((.3, .3, .3, 1))
        self.spotlight.node().setSpecularColor((0, 0, 0, 1))

        self.floater = NodePath(PandaNode("floater")) 
        self.floater.reparentTo(self.droid)
        self.floater.setZ(2.0)


        

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.
        
        self.accept("arrow_left", self.setKey, ["left", True]) # При кнопке влево - поворачиваем игрока влево
        self.accept("arrow_right", self.setKey, ["right", True]) # При кнопке вправо - поворачиваем игрока вправо
        self.accept("arrow_up", self.setKey, ["forward", True]) # При кнопке вперёд - идём вперёд
        self.accept("a", self.setKey, ["cam-left", True]) # При кнопке a - разворачиваем камеру вокруг нашей модельки
        self.accept("d", self.setKey, ["cam-right", True]) # При кнопке s - разворачиваем камеру вокруг нашей модельки
        self.accept("arrow_left-up", self.setKey, ["left", False]) # При кнопке вверх+влево - поворачиваем игрока влево и идём вперёд
        self.accept("arrow_right-up", self.setKey, ["right", False]) # При кнопке вверх+право - поворачиваем игрока вправо и идём вперёд
        self.accept("arrow_up-up", self.setKey, ["forward", False]) # При кнопке вверх+вверъ - идём вперёд и идём вперёд
        self.accept("a-up", self.setKey, ["cam-left", False]) # При кнопке a+вперёд - поворачиваем камеру влево и идём вперёд
        self.accept("d-up", self.setKey, ["cam-right", False]) # При кнопке s+вперёд - поворачиваем камеру вправо и идём вперёд

        self.accept("space", self.shot) # при пробеле стреляем
        self.accept("l", self.white_light) # при нажатии l - включаем свет.
        self.accept("s", self.toggleLights, [[self.spotlight]]) # Включить фонарик
        self.accept("w", self.weapon_hide) # при кнопке w - уберём оружие.

        base.enableParticles() # Включаем инициализацию дыма
        self.t = loader.loadModel("models/weapon/lightsaber.egg") # Загрузим ещё оружие
        self.t.reparentTo(render) # Переместим оружие в наш мир
        self.p = ParticleEffect() # Включим эффект дыма
        self.accept('f', self.particle_start) # при нажатии f(от force) -  загрузим файл дыма и переместим в конфиг чтобы именно эта анимация стала отображением дыма
        self.accept('0', self.fountain) # при  нажатии кнопок f+o(fountain) включим пожаротушительную систему

        taskMgr.add(self.move, "moveTask") # Добавляем задачу в наш движок

        self.isMoving = False # Ставим значение isMoving на False(Вы можете менять это значение) чтобы игрок изначально стоял.
        # Делаем так, чтобы свет был изначально выключен.
        self.disableMouse() # Отключаем перемещение через мышку
        self.camera.setPos(self.droid.getX(), self.droid.getY() + 1, 3) # Ставим позицию камеры чуть больше позиции игрока

        self.black_light() # Сделаем выключенный свет

        self.state = 100 # Состояние корабля
        self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
                
        self.check_loss() # Проверяем поражение
        self.hide_weapon = False # Поставим, что оружие не убрано
        self.steam_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 5)
        self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 1)

        self.motor_pos1 = 25.238849639892578, 8.962211608886719, 1.5 # позиция первого мотора
        self.motor_pos2 = -20.676218032836914, 10.55816650390625, 1.5 # позиция второго мотора

        self.motor1 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos1) # мотор 1
        self.motor2 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos2) # мотор 2

        

    def loadParticleConfig(self, filename, pos):
        self.p.cleanup()
        self.p = ParticleEffect()
        self.p.loadConfig(Filename(filename))

        self.p.start(self.t)
        self.p.setPos(pos)


    def particle_start(self):
        self.state -= 1
        self.state_info.hide()
        if self.state != 10:
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss()
            self.loadParticleConfig('special_effects/steam/steam.ptf', self.steam_pos)
        else:
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss()
            self.loadParticleConfig('special_effects/steam_critic/steam.ptf', self.steam_pos)

        if self.state < 10:
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss()
            self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.steam_pos)          

    def fountain(self):
        if self.state != 100:
            self.state += 1
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
        self.loadParticleConfig('special_effects/fountain/fountain.ptf', self.fountain_pos)
        
        
    def black_light(self):
        # Выключеный свет
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor((0, 0, 0, 1))
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))
        
    def white_light(self):
        # Освещение
        self.light = (.3, .3, .3, 1)
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor(self.light)
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))

    def weapon_hide(self):
        # Убрать оружие
        self.hide_weapon = True
        self.weapon.hide()

    def toggleLights(self, lights):
        # Специализированное освещение
        if self.hide_weapon:
            for light in lights:
                if render.hasLight(light):
                    render.clearLight(light)
                else:
                    render.setLight(light)
            
            self.flash.setPos(self.weapon_pos)
        else:
            return


    def shot(self):
        if not self.hide_weapon : # если оружие убрано то мы не можем стрелять
            self.shotSound.play() # играем звук выстрела
            dt = globalClock.getDt() # Выдаём скорость пули
            self.bullet.setPos(self.weapon_pos) # Пуля будет спавнится внутри пушки
            self.bullet.setY(self.bullet, -25 * dt) # Сдвигаем пулю на огромной скорости вперёд

    def check_loss(self):
        if self.state == 0:
            self.black_light()
            self.weapon.hide()
            self.info = OnscreenText(text='поражение', style=1, font=self.font,
                                     fg=(1,1,1,1),
                            pos=(-1.3, -0.5), align=TextNode.ALeft, scale=0.3)
            self.crackSound.play()
            

    def setKey(self, key, value):
        self.keyMap[key] = value # Делаем мехaнuзм нажатия клавиш.

    def move(self, task):

        ''' Делаем фуцнкцию движения игрока '''

        dt = globalClock.getDt() - .005 # Cкорость движения

        # Поворот камеры влево и вправо

        if self.keyMap["cam-left"]:
            self.camera.setX(self.camera, -20 * dt) # Меняем положеника камеры по икс. Таким образом получается илюзия поворота угла луча. Но на самом деле камера просто перемещается.
        if self.keyMap["cam-right"]:
            self.camera.setX(self.camera, +20 * dt) # Тоже самое, что и наверху.

        startpos = self.droid.getPos() # Сделаем удобную переменную позиции игрока

        if self.keyMap["left"]:
            self.droid.setH(self.droid.getH() + 300 * dt)
            self.enemy.setY(self.enemy, 1 * dt)
        if self.keyMap["right"]:
            self.droid.setH(self.droid.getH() - 300 * dt)
            self.enemy.setY(self.enemy, -1 * dt)
        if self.keyMap["forward"]:
            self.droid.setY(self.droid, -25 * dt)
            self.enemy.setX(self.enemy, 1 * dt)
            # Сделаем специальное условие проверки убрано оружие или нет
            if not self.hide_weapon:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.weapon.setPos(self.weapon_pos) # Обновляем позицию пушки
            else:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.flash.setPos(self.weapon_pos)



        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)


        return task.cont

    def addInstructions(self, pos, msg):
        return OnscreenText(text=msg, style=1, fg=(1,1,1,1), font=self.inst_font, pos=(-1.3, pos), align=TextNode.ALeft, scale = .04, shadow=(0,0,0,1))

    def addTitle(self, text):
        return OnscreenText(text=text, style=1, fg=(1, 1, 1, 1), scale=0.2,
                            parent=base.a2dBottomRight, align=TextNode.ARight,
                            pos=(-0.1, 0.09), shadow=(0, 0, 0, 1), font=self.font)
    

droid = DroidShooter() # Создадим экземпляр класса нашей игры
droid.run() # 3апустим игру

Hi! I created new update! 2.0.7 - sound and walk update!!
Directory sounds : sounds.zip
New code :

#!/usr/bin/env python
# -*- coding: utf_8 -*-

# Создано : 22 число, январь, 2021 год

# Импортируем все необходимые инструменты
from direct.showbase.ShowBase import ShowBase
from panda3d.core import CollisionTraverser, CollisionNode
from panda3d.core import CollisionHandlerQueue, CollisionRay
from panda3d.core import Filename, AmbientLight, DirectionalLight
from panda3d.core import PandaNode, NodePath, Camera, TextNode
from direct.particles.Particles import Particles
from direct.particles.ParticleEffect import ParticleEffect
from panda3d.core import PointLight, Spotlight
from panda3d.core import CollideMask
from panda3d.core import loadPrcFileData
from panda3d.core import *
from direct.gui.OnscreenText import OnscreenText
from direct.gui.OnscreenImage import OnscreenImage
from direct.gui.DirectButton import DirectButton
from direct.actor.Actor import Actor
import random
import sys
import time
import os
import math

VERSION = '2.0.7'

settings = ['window-title Droid Game ' +  VERSION]
            

loadPrcFileData('', settings[0]) # Сделаем не стандартный заголовок (возмём его из переменной settings)
SPRITE_POS = SPRITE_POS = 55

# Создадим главный класс нашей игры
class DroidShooter(ShowBase):
    def __init__(self):
        ShowBase.__init__(self) # Загружаем все селфы из direct
        self.menu() # звпуск меню
        
    def menu(self):

        
        
        base.enableParticles() # инициализируем эффект дыма

        self.win.setClearColor((0, 0, 0, 1)) # Закрашиваем поверхность чёрным. Дело в том, что по умолчанию в этом игровом движке поверхность закрашивается серым.
        self.font = loader.loadFont('./fonts/doom_font.ttf') # загрузим шрифт из игры doom
        self.inst_font = loader.loadFont('./fonts/arial.ttf') # загрузим шрифт arial

        self.crackSound = loader.loadMusic('./sounds/crack.wav') # звук взрыва корабля
        self.shotSound = loader.loadMusic('./sounds/shot.wav') # звук выстрела
        self.startSound = loader.loadMusic('./sounds/started.wav') # звук запуска
        self.errorSound = loader.loadMusic('./sounds/error.wav') # звук запуска
        self.errorSound.setVolume(1) # полная громкость звука
        self.startSound.setVolume(1) # полная громкость звука
        self.crackSound.setVolume(1) # полная громкость звука
        self.shotSound.setVolume(1) # полная громкость звука
        self.startSound.play() # играем звук запуска игры

        self.button_start = DirectButton(pos=(0.7, 0, 0), text="CTAPT",
                                   scale=.3, pad=(.2, .2),
                                   command=self.load_game) # кнопка запуска игры


        self.title = self.addTitle("Droid game " + VERSION)
        
        # Напечатаем управление
        self.inst1 = self.addInstructions(0.95, u"[ESC]: Выход")
        self.inst2 = self.addInstructions(0.90, u"[Left Arrow]: Поворот дроида в лево")
        self.inst3 = self.addInstructions(0.85, u"[Right Arrow]: Поворот дроида в право")
        self.inst4 = self.addInstructions(0.80, u"[Up Arrow]: Вперед")
        self.inst5 = self.addInstructions(0.75, u"[a]: поворот камеры в право")
        self.inst6 = self.addInstructions(0.70, u"[d]: поворот камеры в лево")
        self.inst7 = self.addInstructions(0.65, u"[Left Arrow + Up arrow]: поворот дроида в лево и вперёд")
        self.inst8 = self.addInstructions(0.60, u"[Right Arrow + Up arrow]: поворот дроида в право и вперёд")
        self.inst9 = self.addInstructions(0.55, u"[a + Up arrow]: поворот камеры в право и вперёд")
        self.inst10 = self.addInstructions(0.50, u"[d + Up arrow]: поворот камеры в лево и вперёд")
        self.inst11 = self.addInstructions(0.45, u"[space]: стрельба")
        self.inst12 = self.addInstructions(0.40, u"[l]: включить свет")
        self.inst13 = self.addInstructions(0.35, u"[s]: достать фонарик")
        self.inst14 = self.addInstructions(0.30, u"[w]: убрать оружие")
        self.inst15 = self.addInstructions(0.25, u"[f]: полный разгон(ОПАСНО!)")
        self.inst16 = self.addInstructions(0.20, u"[0]: пожаротушительная система")

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.


        
    def load_game(self):
        self.button_start.hide() # удалим кнопку старта
        self.title.hide() # удалим заголовок игры
        # удалим кнопки управления
        self.inst1.hide()
        self.inst2.hide()
        self.inst3.hide()
        self.inst4.hide()
        self.inst5.hide()
        self.inst6.hide()
        self.inst7.hide()
        self.inst8.hide()
        self.inst9.hide()
        self.inst10.hide()
        self.inst11.hide()
        self.inst12.hide()
        self.inst13.hide()
        self.inst14.hide()
        self.inst15.hide()
        self.inst16.hide()
        self.keyMap = {
            "left": 0, "right": 0, "forward": 0, "cam-left": 0, "cam-right": 0} # Создадим словарь кнопок, по их нажатию




        self.environ = loader.loadModel("models/world/falcon.egg") # Загрузим уже созданный в blender мир.
        self.environ.reparentTo(render) # Загружаем модель мира в окно

        droidStartPos = (-1, 6, 1.5 ) # Загружаем стартовую позицию игрока в мире.
        enemyStartPos = (-6, 1, 0.5) # Загружаем позицию помощника-дроида.
        self.droid = Actor("models/player/seeker.egg") # Загружаем модель игрока (созданная в blender)
        self.enemy = Actor("models/enemies/r2d2/r2d2.egg") # Загружаем модель помощника (созданная в blender)
        self.droid.reparentTo(render) # Перемещаем модель игрока в наш мир.
        self.enemy.reparentTo(render) # Перемещаем модель помощника в наш мир.
        self.droid.setScale(1) # Делаем размер игрока на 1
        self.enemy.setScale(1) # Делаем размер помощника на 1
        self.droid.setPos(droidStartPos) # Ставим позицию игрока на ранее созданую нами позицию
        self.enemy.setPos(enemyStartPos) # Ставим позицию помощника на ранее созданую нами позицию

        self.weapon = Actor('models/weapon/lightsaber.egg') # загрузим оружие
        self.bullet = Actor('models/weapon/bullet/bullet.egg.pz') # Загрузим пулю
        self.flash = Actor('models/whishlyflash/handlamp.egg') # Загрузим фонарик
               
        self.weapon.reparentTo(render) # переместим оружие в наш мир
        self.bullet.reparentTo(render) # переместим пулю в мир но появится она сама не скоро
        self.flash.reparentTo(render) # переместим фонарик в наш мир
        self.weapon.setScale(.5) # зададим размер пушки
        self.bullet.setScale(.5) # зададим размер пули
        self.flash.setScale(.5) # зададим размер фонарика
        

        self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 1 # Позиция пушки
        
        self.weapon.setPos(self.weapon_pos) # Поместим оружие чуть выше игрока
        
        
        self.spotlight = camera.attachNewNode(Spotlight("spotlight")) # Конфиги фонарика
        self.spotlight.node().setColor((.3, .3, .3, 1))
        self.spotlight.node().setSpecularColor((0, 0, 0, 1))

        self.floater = NodePath(PandaNode("floater")) 
        self.floater.reparentTo(self.droid)
        self.floater.setZ(2.0)


        

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.
        
        self.accept("arrow_left", self.setKey, ["left", True]) # При кнопке влево - поворачиваем игрока влево
        self.accept("arrow_right", self.setKey, ["right", True]) # При кнопке вправо - поворачиваем игрока вправо
        self.accept("arrow_up", self.setKey, ["forward", True]) # При кнопке вперёд - идём вперёд
        self.accept("a", self.setKey, ["cam-left", True]) # При кнопке a - разворачиваем камеру вокруг нашей модельки
        self.accept("d", self.setKey, ["cam-right", True]) # При кнопке s - разворачиваем камеру вокруг нашей модельки
        self.accept("arrow_left-up", self.setKey, ["left", False]) # При кнопке вверх+влево - поворачиваем игрока влево и идём вперёд
        self.accept("arrow_right-up", self.setKey, ["right", False]) # При кнопке вверх+право - поворачиваем игрока вправо и идём вперёд
        self.accept("arrow_up-up", self.setKey, ["forward", False]) # При кнопке вверх+вверъ - идём вперёд и идём вперёд
        self.accept("a-up", self.setKey, ["cam-left", False]) # При кнопке a+вперёд - поворачиваем камеру влево и идём вперёд
        self.accept("d-up", self.setKey, ["cam-right", False]) # При кнопке s+вперёд - поворачиваем камеру вправо и идём вперёд

        self.accept("space", self.shot) # при пробеле стреляем
        self.accept("l", self.white_light) # при нажатии l - включаем свет.
        self.accept("s", self.toggleLights, [[self.spotlight]]) # Включить фонарик
        self.accept("w", self.weapon_hide) # при кнопке w - уберём оружие.

        base.enableParticles() # Включаем инициализацию дыма
        self.t = loader.loadModel("models/weapon/lightsaber.egg") # Загрузим ещё оружие
        self.t.reparentTo(render) # Переместим оружие в наш мир
        self.p = ParticleEffect() # Включим эффект дыма
        self.accept('f', self.particle_start) # при нажатии f(от force) -  загрузим файл дыма и переместим в конфиг чтобы именно эта анимация стала отображением дыма
        self.accept('0', self.fountain) # при  нажатии кнопок f+o(fountain) включим пожаротушительную систему

        taskMgr.add(self.move, "moveTask") # Добавляем задачу в наш движок

        self.isMoving = False # Ставим значение isMoving на False(Вы можете менять это значение) чтобы игрок изначально стоял.
        # Делаем так, чтобы свет был изначально выключен.
        self.disableMouse() # Отключаем перемещение через мышку
        self.camera.setPos(self.droid.getX(), self.droid.getY() + 1, 3) # Ставим позицию камеры чуть больше позиции игрока

        self.black_light() # Сделаем выключенный свет

        self.state = 11 # Состояние корабля
        self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
                
        self.check_loss() # Проверяем поражение
        self.hide_weapon = False # Поставим, что оружие не убрано
        self.steam_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 5)
        self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 1)

        self.motor_pos1 = 25.238849639892578, 8.962211608886719, 1.5 # позиция первого мотора
        self.motor_pos2 = -20.676218032836914, 10.55816650390625, 1.5 # позиция второго мотора

        self.motor1 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos1) # мотор 1
        self.motor2 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos2) # мотор 2

        self.state_info2 = OnscreenText(text='', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля

    def loadParticleConfig(self, filename, pos):
        self.p.cleanup()
        self.p = ParticleEffect()
        self.p.loadConfig(Filename(filename))

        self.p.start(self.t)
        self.p.setPos(pos)

    def particle_start(self):
        self.environ.setY(self.environ.getY() + 300) # несёмся вправо
        self.droid.setY(self.droid.getY() + 300) # сдвигаем дроида за кораблём
        print(self.environ.getY())
        print(self.droid.getY())
        self.state -= 1 # сделаем меньше очков
        self.state_info.hide() # удалим текстовые очки

        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)
        
        if self.state != 10 and self.state > 10:
            self.state_info.hide() # убираем предедущее сообщение о жизнях корабля
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.loadParticleConfig('special_effects/steam/steam.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide() # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='двигатель неисправен', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
        else:
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.loadParticleConfig('special_effects/steam_critic/steam.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide()  # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='критическое состояние', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
    
        if self.state < 10:
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide() # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='падаем!', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля


    def fountain(self):
        if self.state != 100:
            self.state += 1
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
        self.loadParticleConfig('special_effects/fountain/fountain.ptf', self.fountain_pos)
        
        
    def black_light(self):
        # Выключеный свет
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor((0, 0, 0, 1))
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))
        
    def white_light(self):
        # Освещение
        self.light = (.3, .3, .3, 1)
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor(self.light)
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))

    def weapon_hide(self):
        # Убрать оружие
        self.hide_weapon = True
        self.weapon.hide()

    def toggleLights(self, lights):
        # Специализированное освещение
        if self.hide_weapon:
            for light in lights:
                if render.hasLight(light):
                    render.clearLight(light)
                else:
                    render.setLight(light)
            
            self.flash.setPos(self.weapon_pos)
        else:
            return


    def shot(self):
        if not self.hide_weapon : # если оружие убрано то мы не можем стрелять
            self.shotSound.play() # играем звук выстрела
            self.bullet.setPos(self.weapon_pos) # Пуля будет спавнится внутри пушки
            self.bullet.setY(self.bullet, -11) # Сдвигаем пулю на огромной скорости вперёд

    def check_loss(self):
        if self.state == 0: # если жизней у корабля не осталось
            self.black_light() # включим чёрный свет
            self.weapon_hide() # уберём оружие
            self.info = OnscreenText(text='поражение', style=1, font=self.font,
                                     fg=(1,1,1,1),
                            pos=(-1.3, -0.5), align=TextNode.ALeft, scale=0.3) # напишем о поражении
            self.crackSound.play() # играем звук взрыва


    def setKey(self, key, value):
        self.keyMap[key] = value # Делаем мехaнuзм нажатия клавиш.

    def move(self, task):

        ''' Делаем фуцнкцию движения игрока '''

        dt = globalClock.getDt() - .005 # Cкорость движения

        # Поворот камеры влево и вправо

        if self.keyMap["cam-left"]:
            self.camera.setX(self.camera, -20 * dt) # Меняем положеника камеры по икс. Таким образом получается илюзия поворота угла луча. Но на самом деле камера просто перемещается.
        if self.keyMap["cam-right"]:
            self.camera.setX(self.camera, +20 * dt) # Тоже самое, что и наверху.

        startpos = self.droid.getPos() # Сделаем удобную переменную позиции игрока

        if self.keyMap["left"]:
            self.droid.setH(self.droid.getH() + 300 * dt)
            self.enemy.setY(self.enemy, 1 * dt)
        if self.keyMap["right"]:
            self.droid.setH(self.droid.getH() - 300 * dt)
            self.enemy.setY(self.enemy, -1 * dt)
        if self.keyMap["forward"]:
            self.droid.setY(self.droid, -25 * dt)
            self.enemy.setX(self.enemy, 1 * dt)
            # Сделаем специальное условие проверки убрано оружие или нет
            if not self.hide_weapon:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.weapon.setPos(self.weapon_pos) # Обновляем позицию пушки
            else:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.flash.setPos(self.weapon_pos)



        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)


        return task.cont

    def addInstructions(self, pos, msg):
        return OnscreenText(text=msg, style=1, fg=(1,1,1,1), font=self.inst_font, pos=(-1.3, pos), align=TextNode.ALeft, scale = .04, shadow=(0,0,0,1))

    def addTitle(self, text):
        return OnscreenText(text=text, style=1, fg=(1, 1, 1, 1), scale=0.2,
                            parent=base.a2dBottomRight, align=TextNode.ARight,
                            pos=(-0.1, 0.09), shadow=(0, 0, 0, 1), font=self.font)
    

droid = DroidShooter() # Создадим экземпляр класса нашей игры
droid.run() # 3апустим игру


# разроботчики :
# Главный автор : ma3rx
# Помощник : panda3dmastercoder( присоеденился к проекту 31 января, 12:??)

Machine can go! Press F to check!

2.0.7.1 Bug fix update.
New code :

#!/usr/bin/env python
# -*- coding: utf_8 -*-

# Создано : 22 число, январь, 2021 год

# Импортируем все необходимые инструменты
from direct.showbase.ShowBase import ShowBase
from panda3d.core import CollisionTraverser, CollisionNode
from panda3d.core import CollisionHandlerQueue, CollisionRay
from panda3d.core import Filename, AmbientLight, DirectionalLight
from panda3d.core import PandaNode, NodePath, Camera, TextNode
from direct.particles.Particles import Particles
from direct.particles.ParticleEffect import ParticleEffect
from panda3d.core import PointLight, Spotlight
from panda3d.core import CollideMask
from panda3d.core import loadPrcFileData
from panda3d.core import *
from direct.gui.OnscreenText import OnscreenText
from direct.gui.OnscreenImage import OnscreenImage
from direct.gui.DirectButton import DirectButton
from direct.actor.Actor import Actor
import random
import sys
import time
import os
import math

VERSION = '2.0.7.1'

settings = ['window-title Droid Game ' +  VERSION]
            

loadPrcFileData('', settings[0]) # Сделаем не стандартный заголовок (возмём его из переменной settings)
SPRITE_POS = SPRITE_POS = 55

# Создадим главный класс нашей игры
class DroidShooter(ShowBase):
    def __init__(self):
        ShowBase.__init__(self) # Загружаем все селфы из direct
        self.menu() # звпуск меню
        
    def menu(self):

        
        
        base.enableParticles() # инициализируем эффект дыма

        self.win.setClearColor((0, 0, 0, 1)) # Закрашиваем поверхность чёрным. Дело в том, что по умолчанию в этом игровом движке поверхность закрашивается серым.
        self.font = loader.loadFont('./fonts/doom_font.ttf') # загрузим шрифт из игры doom
        self.inst_font = loader.loadFont('./fonts/arial.ttf') # загрузим шрифт arial

        self.crackSound = loader.loadMusic('./sounds/crack.wav') # звук взрыва корабля
        self.shotSound = loader.loadMusic('./sounds/shot.wav') # звук выстрела
        self.startSound = loader.loadMusic('./sounds/started.wav') # звук запуска
        self.errorSound = loader.loadMusic('./sounds/error.wav') # звук запуска
        self.errorSound.setVolume(1) # полная громкость звука
        self.startSound.setVolume(1) # полная громкость звука
        self.crackSound.setVolume(1) # полная громкость звука
        self.shotSound.setVolume(1) # полная громкость звука
        self.startSound.play() # играем звук запуска игры

        self.button_start = DirectButton(pos=(0.7, 0, 0), text="CTAPT",
                                   scale=.3, pad=(.2, .2),
                                   command=self.load_game) # кнопка запуска игры


        self.title = self.addTitle("Droid game " + VERSION)
        
        # Напечатаем управление
        self.inst1 = self.addInstructions(0.95, u"[ESC]: Выход")
        self.inst2 = self.addInstructions(0.90, u"[Left Arrow]: Поворот дроида в лево")
        self.inst3 = self.addInstructions(0.85, u"[Right Arrow]: Поворот дроида в право")
        self.inst4 = self.addInstructions(0.80, u"[Up Arrow]: Вперед")
        self.inst5 = self.addInstructions(0.75, u"[a]: поворот камеры в право")
        self.inst6 = self.addInstructions(0.70, u"[d]: поворот камеры в лево")
        self.inst7 = self.addInstructions(0.65, u"[Left Arrow + Up arrow]: поворот дроида в лево и вперёд")
        self.inst8 = self.addInstructions(0.60, u"[Right Arrow + Up arrow]: поворот дроида в право и вперёд")
        self.inst9 = self.addInstructions(0.55, u"[a + Up arrow]: поворот камеры в право и вперёд")
        self.inst10 = self.addInstructions(0.50, u"[d + Up arrow]: поворот камеры в лево и вперёд")
        self.inst11 = self.addInstructions(0.45, u"[space]: стрельба")
        self.inst12 = self.addInstructions(0.40, u"[l]: включить свет")
        self.inst13 = self.addInstructions(0.35, u"[s]: достать фонарик")
        self.inst14 = self.addInstructions(0.30, u"[w]: убрать оружие")
        self.inst15 = self.addInstructions(0.25, u"[f]: полный разгон(ОПАСНО!)")
        self.inst16 = self.addInstructions(0.20, u"[0]: пожаротушительная система")

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.


        
    def load_game(self):
        self.button_start.hide() # удалим кнопку старта
        self.title.hide() # удалим заголовок игры
        # удалим кнопки управления
        self.inst1.hide()
        self.inst2.hide()
        self.inst3.hide()
        self.inst4.hide()
        self.inst5.hide()
        self.inst6.hide()
        self.inst7.hide()
        self.inst8.hide()
        self.inst9.hide()
        self.inst10.hide()
        self.inst11.hide()
        self.inst12.hide()
        self.inst13.hide()
        self.inst14.hide()
        self.inst15.hide()
        self.inst16.hide()
        self.keyMap = {
            "left": 0, "right": 0, "forward": 0, "cam-left": 0, "cam-right": 0} # Создадим словарь кнопок, по их нажатию




        self.environ = loader.loadModel("models/world/falcon.egg") # Загрузим уже созданный в blender мир.
        self.environ.reparentTo(render) # Загружаем модель мира в окно

        droidStartPos = (-1, 6, 1.5 ) # Загружаем стартовую позицию игрока в мире.
        enemyStartPos = (-6, 1, 0.5) # Загружаем позицию помощника-дроида.
        self.droid = Actor("models/player/seeker.egg") # Загружаем модель игрока (созданная в blender)
        self.enemy = Actor("models/enemies/r2d2/r2d2.egg") # Загружаем модель помощника (созданная в blender)
        self.droid.reparentTo(render) # Перемещаем модель игрока в наш мир.
        self.enemy.reparentTo(render) # Перемещаем модель помощника в наш мир.
        self.droid.setScale(1) # Делаем размер игрока на 1
        self.enemy.setScale(1) # Делаем размер помощника на 1
        self.droid.setPos(droidStartPos) # Ставим позицию игрока на ранее созданую нами позицию
        self.enemy.setPos(enemyStartPos) # Ставим позицию помощника на ранее созданую нами позицию

        self.weapon = Actor('models/weapon/lightsaber.egg') # загрузим оружие
        self.bullet = Actor('models/weapon/bullet/bullet.egg.pz') # Загрузим пулю
        self.flash = Actor('models/whishlyflash/handlamp.egg') # Загрузим фонарик
               
        self.weapon.reparentTo(render) # переместим оружие в наш мир
        self.bullet.reparentTo(render) # переместим пулю в мир но появится она сама не скоро
        self.flash.reparentTo(render) # переместим фонарик в наш мир
        self.weapon.setScale(.5) # зададим размер пушки
        self.bullet.setScale(.5) # зададим размер пули
        self.flash.setScale(.5) # зададим размер фонарика
        

        self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 1 # Позиция пушки
        
        self.weapon.setPos(self.weapon_pos) # Поместим оружие чуть выше игрока
        
        
        self.spotlight = camera.attachNewNode(Spotlight("spotlight")) # Конфиги фонарика
        self.spotlight.node().setColor((.3, .3, .3, 1))
        self.spotlight.node().setSpecularColor((0, 0, 0, 1))

        self.floater = NodePath(PandaNode("floater")) 
        self.floater.reparentTo(self.droid)
        self.floater.setZ(2.0)


        

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.
        
        self.accept("arrow_left", self.setKey, ["left", True]) # При кнопке влево - поворачиваем игрока влево
        self.accept("arrow_right", self.setKey, ["right", True]) # При кнопке вправо - поворачиваем игрока вправо
        self.accept("arrow_up", self.setKey, ["forward", True]) # При кнопке вперёд - идём вперёд
        self.accept("a", self.setKey, ["cam-left", True]) # При кнопке a - разворачиваем камеру вокруг нашей модельки
        self.accept("d", self.setKey, ["cam-right", True]) # При кнопке s - разворачиваем камеру вокруг нашей модельки
        self.accept("arrow_left-up", self.setKey, ["left", False]) # При кнопке вверх+влево - поворачиваем игрока влево и идём вперёд
        self.accept("arrow_right-up", self.setKey, ["right", False]) # При кнопке вверх+право - поворачиваем игрока вправо и идём вперёд
        self.accept("arrow_up-up", self.setKey, ["forward", False]) # При кнопке вверх+вверъ - идём вперёд и идём вперёд
        self.accept("a-up", self.setKey, ["cam-left", False]) # При кнопке a+вперёд - поворачиваем камеру влево и идём вперёд
        self.accept("d-up", self.setKey, ["cam-right", False]) # При кнопке s+вперёд - поворачиваем камеру вправо и идём вперёд

        self.accept("space", self.shot) # при пробеле стреляем
        self.accept("l", self.white_light) # при нажатии l - включаем свет.
        self.accept("s", self.toggleLights, [[self.spotlight]]) # Включить фонарик
        self.accept("w", self.weapon_hide) # при кнопке w - уберём оружие.

        base.enableParticles() # Включаем инициализацию дыма
        self.t = loader.loadModel("models/weapon/lightsaber.egg") # Загрузим ещё оружие
        self.t.reparentTo(render) # Переместим оружие в наш мир
        self.p = ParticleEffect() # Включим эффект дыма
        self.accept('f', self.particle_start) # при нажатии f(от force) -  загрузим файл дыма и переместим в конфиг чтобы именно эта анимация стала отображением дыма
        self.accept('0', self.fountain) # при  нажатии кнопок f+o(fountain) включим пожаротушительную систему

        taskMgr.add(self.move, "moveTask") # Добавляем задачу в наш движок

        self.isMoving = False # Ставим значение isMoving на False(Вы можете менять это значение) чтобы игрок изначально стоял.
        # Делаем так, чтобы свет был изначально выключен.
        self.disableMouse() # Отключаем перемещение через мышку
        self.camera.setPos(self.droid.getX(), self.droid.getY() + 1, 3) # Ставим позицию камеры чуть больше позиции игрока

        self.black_light() # Сделаем выключенный свет

        self.state = 11 # Состояние корабля
        self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
                
        self.check_loss() # Проверяем поражение
        self.hide_weapon = False # Поставим, что оружие не убрано
        self.steam_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 5)
        self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 1)

        self.motor_pos1 = 25.238849639892578, 8.962211608886719, 1.5 # позиция первого мотора
        self.motor_pos2 = -20.676218032836914, 10.55816650390625, 1.5 # позиция второго мотора

        self.motor1 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos1) # мотор 1
        self.motor2 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos2) # мотор 2

        self.state_info2 = OnscreenText(text='', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля

    def loadParticleConfig(self, filename, pos):
        self.p.cleanup()
        self.p = ParticleEffect()
        self.p.loadConfig(Filename(filename))

        self.p.start(self.t)
        self.p.setPos(pos)

    def particle_start(self):
        dt = globalClock.getDt() + 0.5 # Cкорость движения
        self.environ.setY(self.environ.getY() + 300 * dt) # несёмся вправо
        self.droid.setY(self.droid.getY() + 300 * dt) # сдвигаем дроида за кораблём
        print(self.environ.getY())
        print(self.droid.getY())
        self.state -= 1 # сделаем меньше очков
        self.state_info.hide() # удалим текстовые очки

        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)
        
        if self.state != 10 and self.state > 10:
            self.state_info.hide() # убираем предедущее сообщение о жизнях корабля
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.loadParticleConfig('special_effects/steam/steam.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide() # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='двигатель неисправен', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
        else:
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.loadParticleConfig('special_effects/steam_critic/steam.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide()  # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='критическое состояние', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
    
        if self.state < 10:
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide() # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='падаем!', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля


    def fountain(self):
        if self.state != 100:
            self.state += 1
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
        self.loadParticleConfig('special_effects/fountain/fountain.ptf', self.fountain_pos)
        
        
    def black_light(self):
        # Выключеный свет
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor((0, 0, 0, 1))
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))
        
    def white_light(self):
        # Освещение
        self.light = (.3, .3, .3, 1)
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor(self.light)
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))

    def weapon_hide(self):
        # Убрать оружие
        self.hide_weapon = True
        self.weapon.hide()

    def toggleLights(self, lights):
        # Специализированное освещение
        if self.hide_weapon:
            for light in lights:
                if render.hasLight(light):
                    render.clearLight(light)
                else:
                    render.setLight(light)
            
            self.flash.setPos(self.weapon_pos)
        else:
            return


    def shot(self):
        dt = globalClock.getDt() + 0.5 # Cкорость движения
        if not self.hide_weapon : # если оружие убрано то мы не можем стрелять
            self.shotSound.play() # играем звук выстрела
            self.bullet.setPos(self.weapon_pos) # Пуля будет спавнится внутри пушки
            self.bullet.setY(self.bullet, -11 * dt) # Сдвигаем пулю на огромной скорости вперёд

    def check_loss(self):
        if self.state == 0: # если жизней у корабля не осталось
            self.black_light() # включим чёрный свет
            self.weapon_hide() # уберём оружие
            self.info = OnscreenText(text='поражение', style=1, font=self.font,
                                     fg=(1,1,1,1),
                            pos=(-1.3, -0.5), align=TextNode.ALeft, scale=0.3) # напишем о поражении
            self.crackSound.play() # играем звук взрыва


    def setKey(self, key, value):
        self.keyMap[key] = value # Делаем мехaнuзм нажатия клавиш.

    def move(self, task):

        ''' Делаем фуцнкцию движения игрока '''

        dt = globalClock.getDt() - .005 # Cкорость движения

        # Поворот камеры влево и вправо

        if self.keyMap["cam-left"]:
            self.camera.setX(self.camera, -20 * dt) # Меняем положеника камеры по икс. Таким образом получается илюзия поворота угла луча. Но на самом деле камера просто перемещается.
        if self.keyMap["cam-right"]:
            self.camera.setX(self.camera, +20 * dt) # Тоже самое, что и наверху.

        startpos = self.droid.getPos() # Сделаем удобную переменную позиции игрока

        if self.keyMap["left"]:
            self.droid.setH(self.droid.getH() + 300 * dt)
            self.enemy.setY(self.enemy, 1 * dt)
        if self.keyMap["right"]:
            self.droid.setH(self.droid.getH() - 300 * dt)
            self.enemy.setY(self.enemy, -1 * dt)
        if self.keyMap["forward"]:
            self.droid.setY(self.droid, -25 * dt)
            self.enemy.setX(self.enemy, 1 * dt)
            # Сделаем специальное условие проверки убрано оружие или нет
            if not self.hide_weapon:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.weapon.setPos(self.weapon_pos) # Обновляем позицию пушки
            else:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.flash.setPos(self.weapon_pos)



        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)


        return task.cont

    def addInstructions(self, pos, msg):
        return OnscreenText(text=msg, style=1, fg=(1,1,1,1), font=self.inst_font, pos=(-1.3, pos), align=TextNode.ALeft, scale = .04, shadow=(0,0,0,1))

    def addTitle(self, text):
        return OnscreenText(text=text, style=1, fg=(1, 1, 1, 1), scale=0.2,
                            parent=base.a2dBottomRight, align=TextNode.ARight,
                            pos=(-0.1, 0.09), shadow=(0, 0, 0, 1), font=self.font)
    

droid = DroidShooter() # Создадим экземпляр класса нашей игры
droid.run() # 3апустим игру


# разроботчики :
# Главный автор : ma3rx
# Помощник : panda3dmastercoder( присоеденился к проекту 31 января, 12:??)

Everybody, I am helping @ma3rx in making this game. Version 2.0.8 coming soon!

Yes. This human - helper, develepover.
@panda3dmastercoder thank you :slight_smile:

Everybody! The Droid Game 2.0.8 collision update has come. Now you can collide into things.You can go outside the falcon. But if you go too far, it will push you back. You can even collide with r2d2.
So here is the code (Put all of this in the main directory):
fonts.zip (199.4 KB) sound.zip (328.9 KB) sounds.zip (1.8 MB) special_effects.zip (18.9 KB)

Now make a new directory called models, and put all of this in it:
enemies.zip (404.7 KB) player.zip (709.8 KB) weapon.zip (253.6 KB) whishlyflash.zip (105.7 KB)

Now, download panda3d art from https://www.panda3d.org/download/noversion/art-gallery.zip
Go to the cat-environment folder. In it you will see a millenium falcon folder. Take all the files from it and put it in the directory ./models/world

And lastly, the source code:

#!/usr/bin/env python
# -*- coding: utf_8 -*-

# Создано : 22 число, январь, 2021 год

# Импортируем все необходимые инструменты
from direct.showbase.ShowBase import ShowBase
from panda3d.core import CollisionTraverser, CollisionNode
from panda3d.core import CollisionHandlerPusher, CollisionSphere, CollisionInvSphere
from panda3d.core import Filename, AmbientLight, DirectionalLight
from panda3d.core import PandaNode, NodePath, Camera, TextNode
from direct.particles.Particles import Particles
from direct.particles.ParticleEffect import ParticleEffect
from panda3d.core import PointLight, Spotlight
from panda3d.core import CollideMask
from panda3d.core import loadPrcFileData
from panda3d.core import *
from direct.gui.OnscreenText import OnscreenText
from direct.gui.OnscreenImage import OnscreenImage
from direct.gui.DirectButton import DirectButton
from direct.actor.Actor import Actor
import random
import sys
import time
import os
import math

VERSION = '2.0.8'

settings = ['window-title Droid Game ' +  VERSION]
            

loadPrcFileData('', settings[0]) # Сделаем не стандартный заголовок (возмём его из переменной settings)
SPRITE_POS = SPRITE_POS = 55

# Создадим главный класс нашей игры
class DroidShooter(ShowBase):
    def __init__(self):
        ShowBase.__init__(self) # Загружаем все селфы из direct
        self.menu() # звпуск меню
        
    def menu(self):

        
        
        base.enableParticles() # инициализируем эффект дыма

        self.win.setClearColor((0, 0, 0, 1)) # Закрашиваем поверхность чёрным. Дело в том, что по умолчанию в этом игровом движке поверхность закрашивается серым.
        self.font = loader.loadFont('./fonts/doom_font.ttf') # загрузим шрифт из игры doom
        self.inst_font = loader.loadFont('./fonts/arial.ttf') # загрузим шрифт arial

        self.crackSound = loader.loadMusic('./sounds/crack.wav') # звук взрыва корабля
        self.shotSound = loader.loadMusic('./sounds/shot.wav') # звук выстрела
        self.startSound = loader.loadMusic('./sounds/started.wav') # звук запуска
        self.errorSound = loader.loadMusic('./sounds/error.wav') # звук запуска
        self.errorSound.setVolume(1) # полная громкость звука
        self.startSound.setVolume(1) # полная громкость звука
        self.crackSound.setVolume(1) # полная громкость звука
        self.shotSound.setVolume(1) # полная громкость звука
        self.startSound.play() # играем звук запуска игры

        self.button_start = DirectButton(pos=(0.7, 0, 0), text="CTAPT",
                                   scale=.3, pad=(.2, .2),
                                   command=self.load_game) # кнопка запуска игры


        self.title = self.addTitle("Droid game " + VERSION)
        
        # Напечатаем управление
        self.inst1 = self.addInstructions(0.95, u"[ESC]: Выход")
        self.inst2 = self.addInstructions(0.90, u"[Left Arrow]: Поворот дроида в лево")
        self.inst3 = self.addInstructions(0.85, u"[Right Arrow]: Поворот дроида в право")
        self.inst4 = self.addInstructions(0.80, u"[Up Arrow]: Вперед")
        self.inst5 = self.addInstructions(0.75, u"[a]: поворот камеры в право")
        self.inst6 = self.addInstructions(0.70, u"[d]: поворот камеры в лево")
        self.inst7 = self.addInstructions(0.65, u"[Left Arrow + Up arrow]: поворот дроида в лево и вперёд")
        self.inst8 = self.addInstructions(0.60, u"[Right Arrow + Up arrow]: поворот дроида в право и вперёд")
        self.inst9 = self.addInstructions(0.55, u"[a + Up arrow]: поворот камеры в право и вперёд")
        self.inst10 = self.addInstructions(0.50, u"[d + Up arrow]: поворот камеры в лево и вперёд")
        self.inst11 = self.addInstructions(0.45, u"[space]: стрельба")
        self.inst12 = self.addInstructions(0.40, u"[l]: включить свет")
        self.inst13 = self.addInstructions(0.35, u"[s]: достать фонарик")
        self.inst14 = self.addInstructions(0.30, u"[w]: убрать оружие")
        self.inst15 = self.addInstructions(0.25, u"[f]: полный разгон(ОПАСНО!)")
        self.inst16 = self.addInstructions(0.20, u"[0]: пожаротушительная система")

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.
        
    def load_game(self):
        self.button_start.hide() # удалим кнопку старта
        self.title.hide() # удалим заголовок игры
        # удалим кнопки управления
        self.inst1.hide()
        self.inst2.hide()
        self.inst3.hide()
        self.inst4.hide()
        self.inst5.hide()
        self.inst6.hide()
        self.inst7.hide()
        self.inst8.hide()
        self.inst9.hide()
        self.inst10.hide()
        self.inst11.hide()
        self.inst12.hide()
        self.inst13.hide()
        self.inst14.hide()
        self.inst15.hide()
        self.inst16.hide()
        self.keyMap = {
            "left": 0, "right": 0, "forward": 0, "cam-left": 0, "cam-right": 0} # Создадим словарь кнопок, по их нажатию




        self.environ = loader.loadModel("models/world/falcon.egg") # Загрузим уже созданный в blender мир.
        self.environ.reparentTo(render) # Загружаем модель мира в окно

        droidStartPos = (-1, 6, 1.5 ) # Загружаем стартовую позицию игрока в мире.
        enemyStartPos = (-6, 1, 0.5) # Загружаем позицию помощника-дроида.
        self.droid = Actor("models/player/seeker.egg") # Загружаем модель игрока (созданная в blender)
        self.enemy = Actor("models/enemies/r2d2/r2d2.egg") # Загружаем модель помощника (созданная в blender)
        self.droid.reparentTo(render) # Перемещаем модель игрока в наш мир.
        self.enemy.reparentTo(render) # Перемещаем модель помощника в наш мир.
        self.droid.setScale(1) # Делаем размер игрока на 1
        self.enemy.setScale(1) # Делаем размер помощника на 1
        self.droid.setPos(droidStartPos) # Ставим позицию игрока на ранее созданую нами позицию
        self.enemy.setPos(enemyStartPos) # Ставим позицию помощника на ранее созданую нами позицию

        self.weapon = Actor('models/weapon/lightsaber.egg') # загрузим оружие
        self.bullet = Actor('models/weapon/bullet/bullet.egg.pz') # Загрузим пулю
        self.flash = Actor('models/whishlyflash/handlamp.egg') # Загрузим фонарик
               
        self.weapon.reparentTo(render) # переместим оружие в наш мир
        self.bullet.reparentTo(render) # переместим пулю в мир но появится она сама не скоро
        self.flash.reparentTo(render) # переместим фонарик в наш мир
        self.weapon.setScale(.5) # зададим размер пушки
        self.bullet.setScale(.5) # зададим размер пули
        self.flash.setScale(.5) # зададим размер фонарика
        

        self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 1 # Позиция пушки
        
        self.weapon.setPos(self.weapon_pos) # Поместим оружие чуть выше игрока
        
        
        self.spotlight = camera.attachNewNode(Spotlight("spotlight")) # Конфиги фонарика
        self.spotlight.node().setColor((.3, .3, .3, 1))
        self.spotlight.node().setSpecularColor((0, 0, 0, 1))

        self.floater = NodePath(PandaNode("floater")) 
        self.floater.reparentTo(self.droid)
        self.floater.setZ(2.0)


        

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.
        
        self.accept("arrow_left", self.setKey, ["left", True]) # При кнопке влево - поворачиваем игрока влево
        self.accept("arrow_right", self.setKey, ["right", True]) # При кнопке вправо - поворачиваем игрока вправо
        self.accept("arrow_up", self.setKey, ["forward", True]) # При кнопке вперёд - идём вперёд
        self.accept("a", self.setKey, ["cam-left", True]) # При кнопке a - разворачиваем камеру вокруг нашей модельки
        self.accept("d", self.setKey, ["cam-right", True]) # При кнопке s - разворачиваем камеру вокруг нашей модельки
        self.accept("arrow_left-up", self.setKey, ["left", False]) # При кнопке вверх+влево - поворачиваем игрока влево и идём вперёд
        self.accept("arrow_right-up", self.setKey, ["right", False]) # При кнопке вверх+право - поворачиваем игрока вправо и идём вперёд
        self.accept("arrow_up-up", self.setKey, ["forward", False]) # При кнопке вверх+вверъ - идём вперёд и идём вперёд
        self.accept("a-up", self.setKey, ["cam-left", False]) # При кнопке a+вперёд - поворачиваем камеру влево и идём вперёд
        self.accept("d-up", self.setKey, ["cam-right", False]) # При кнопке s+вперёд - поворачиваем камеру вправо и идём вперёд

        self.accept("space", self.shot) # при пробеле стреляем
        self.accept("l", self.white_light) # при нажатии l - включаем свет.
        self.accept("s", self.toggleLights, [[self.spotlight]]) # Включить фонарик
        self.accept("w", self.weapon_hide) # при кнопке w - уберём оружие.

        base.enableParticles() # Включаем инициализацию дыма
        self.t = loader.loadModel("models/weapon/lightsaber.egg") # Загрузим ещё оружие
        self.t.reparentTo(render) # Переместим оружие в наш мир
        self.p = ParticleEffect() # Включим эффект дыма
        self.accept('f', self.particle_start) # при нажатии f(от force) -  загрузим файл дыма и переместим в конфиг чтобы именно эта анимация стала отображением дыма
        self.accept('0', self.fountain) # при  нажатии кнопок f+o(fountain) включим пожаротушительную систему

        taskMgr.add(self.move, "moveTask") # Добавляем задачу в наш движок

        self.isMoving = False # Ставим значение isMoving на False(Вы можете менять это значение) чтобы игрок изначально стоял.
        # Делаем так, чтобы свет был изначально выключен.
        self.disableMouse() # Отключаем перемещение через мышку
        self.camera.setPos(self.droid.getX(), self.droid.getY() + 1, 3) # Ставим позицию камеры чуть больше позиции игрока

        self.black_light() # Сделаем выключенный свет

        self.state = 11 # Состояние корабля
        self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
                
        self.check_loss() # Проверяем поражение
        self.hide_weapon = False # Поставим, что оружие не убрано
        self.steam_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 5)
        self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 1)

        self.motor_pos1 = 25.238849639892578, 8.962211608886719, 1.5 # позиция первого мотора
        self.motor_pos2 = -20.676218032836914, 10.55816650390625, 1.5 # позиция второго мотора

        self.motor1 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos1) # мотор 1
        self.motor2 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos2) # мотор 2

        self.state_info2 = OnscreenText(text='', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
        
        # Collision Detection
        
        self.cTrav = CollisionTraverser()
        pusher = CollisionHandlerPusher()
        
        environCenter = self.environ.getBounds().getCenter()
        environRad = self.environ.getBounds().getRadius()
        cNode = CollisionNode("environment")
        cNode.addSolid(CollisionInvSphere(environCenter, environRad))
        environC = self.environ.attachNewNode(cNode)
        
        droidCenter = self.droid.getBounds().getCenter()
        droidRad = self.droid.getBounds().getRadius()
        cNode = CollisionNode("droid")
        cNode.addSolid(CollisionSphere(droidCenter, droidRad))
        droidC = self.droid.attachNewNode(cNode)
        
        pusher.addCollider(droidC, self.droid)
        self.cTrav.addCollider(droidC, pusher)
        
        enemyCenter = self.enemy.getBounds().getCenter()
        enemyRad = self.enemy.getBounds().getRadius()
        cNode = CollisionNode("enemy")
        cNode.addSolid(CollisionSphere(enemyCenter, enemyRad))
        enemyC = self.enemy.attachNewNode(cNode)
        
        pusher.addCollider(enemyC, self.enemy)
        self.cTrav.addCollider(enemyC, pusher)
        
    def loadParticleConfig(self, filename, pos):
        self.p.cleanup()
        self.p = ParticleEffect()
        self.p.loadConfig(Filename(filename))

        self.p.start(self.t)
        self.p.setPos(pos)

    def particle_start(self):
        dt = globalClock.getDt() + 0.5 # Cкорость движения
        self.environ.setY(self.environ.getY() + 300 * dt) # несёмся вправо
        self.droid.setY(self.droid.getY() + 300 * dt) # сдвигаем дроида за кораблём
        print(self.environ.getY())
        print(self.droid.getY())
        self.state -= 1 # сделаем меньше очков
        self.state_info.hide() # удалим текстовые очки

        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)
        
        if self.state != 10 and self.state > 10:
            self.state_info.hide() # убираем предедущее сообщение о жизнях корабля
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.loadParticleConfig('special_effects/steam/steam.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide() # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='двигатель неисправен', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
        else:
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.loadParticleConfig('special_effects/steam_critic/steam.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide()  # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='критическое состояние', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
    
        if self.state < 10:
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide() # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='падаем!', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля


    def fountain(self):
        if self.state != 100:
            self.state += 1
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
        self.loadParticleConfig('special_effects/fountain/fountain.ptf', self.fountain_pos)
        
        
    def black_light(self):
        # Выключеный свет
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor((0, 0, 0, 1))
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))
        
    def white_light(self):
        # Освещение
        self.light = (.3, .3, .3, 1)
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor(self.light)
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))

    def weapon_hide(self):
        # Убрать оружие
        self.hide_weapon = True
        self.weapon.hide()

    def toggleLights(self, lights):
        # Специализированное освещение
        if self.hide_weapon:
            for light in lights:
                if render.hasLight(light):
                    render.clearLight(light)
                else:
                    render.setLight(light)
            
            self.flash.setPos(self.weapon_pos)
        else:
            return


    def shot(self):
        dt = globalClock.getDt() + 0.5 # Cкорость движения
        if not self.hide_weapon : # если оружие убрано то мы не можем стрелять
            self.shotSound.play() # играем звук выстрела
            self.bullet.setPos(self.weapon_pos) # Пуля будет спавнится внутри пушки
            self.bullet.setY(self.bullet, -11 * dt) # Сдвигаем пулю на огромной скорости вперёд

    def check_loss(self):
        if self.state == 0: # если жизней у корабля не осталось
            self.black_light() # включим чёрный свет
            self.weapon_hide() # уберём оружие
            self.info = OnscreenText(text='поражение', style=1, font=self.font,
                                     fg=(1,1,1,1),
                            pos=(-1.3, -0.5), align=TextNode.ALeft, scale=0.3) # напишем о поражении
            self.crackSound.play() # играем звук взрыва


    def setKey(self, key, value):
        self.keyMap[key] = value # Делаем мехaнuзм нажатия клавиш.

    def move(self, task):

        ''' Делаем фуцнкцию движения игрока '''

        dt = globalClock.getDt() - .005 # Cкорость движения

        # Поворот камеры влево и вправо

        if self.keyMap["cam-left"]:
            self.camera.setX(self.camera, -20 * dt) # Меняем положеника камеры по икс. Таким образом получается илюзия поворота угла луча. Но на самом деле камера просто перемещается.
        if self.keyMap["cam-right"]:
            self.camera.setX(self.camera, +20 * dt) # Тоже самое, что и наверху.

        startpos = self.droid.getPos() # Сделаем удобную переменную позиции игрока

        if self.keyMap["left"]:
            self.droid.setH(self.droid.getH() + 300 * dt)
            self.enemy.setY(self.enemy, 1 * dt)
        if self.keyMap["right"]:
            self.droid.setH(self.droid.getH() - 300 * dt)
            self.enemy.setY(self.enemy, -1 * dt)
        if self.keyMap["forward"]:
            self.droid.setY(self.droid, -25 * dt)
            self.enemy.setX(self.enemy, 1 * dt)
            # Сделаем специальное условие проверки убрано оружие или нет
            if not self.hide_weapon:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.weapon.setPos(self.weapon_pos) # Обновляем позицию пушки
            else:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.flash.setPos(self.weapon_pos)



        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)


        return task.cont

    def addInstructions(self, pos, msg):
        return OnscreenText(text=msg, style=1, fg=(1,1,1,1), font=self.inst_font, pos=(-1.3, pos), align=TextNode.ALeft, scale = .04, shadow=(0,0,0,1))

    def addTitle(self, text):
        return OnscreenText(text=text, style=1, fg=(1, 1, 1, 1), scale=0.2,
                            parent=base.a2dBottomRight, align=TextNode.ARight,
                            pos=(-0.1, 0.09), shadow=(0, 0, 0, 1), font=self.font)
    

droid = DroidShooter() # Создадим экземпляр класса нашей игры
droid.run() # 3апустим игру


# разроботчики :
# Главный автор : ma3rx
# Помощник : panda3dmastercoder( присоеденился к проекту 31 января, 12:??)

New update coming soon!

1 Like

THANK YOU for helping!

Hi! 2.0.8.1 updating!

add directory ./tex - tex.zip (95.1 KB)

New code :

#!/usr/bin/env python
# -*- coding: utf_8 -*-

# Создано : 22 число, январь, 2021 год

# Импортируем все необходимые инструменты
from direct.showbase.ShowBase import ShowBase
from panda3d.core import CollisionTraverser, CollisionNode
from panda3d.core import CollisionHandlerQueue, CollisionRay
from panda3d.core import Filename, AmbientLight, DirectionalLight
from panda3d.core import PandaNode, NodePath, Camera, TextNode
from direct.particles.Particles import Particles
from direct.particles.ParticleEffect import ParticleEffect
from panda3d.core import PointLight, Spotlight
from panda3d.core import CollideMask
from panda3d.core import loadPrcFileData
from panda3d.core import *
from direct.gui.OnscreenText import OnscreenText
from direct.gui.OnscreenImage import OnscreenImage
from direct.gui.DirectButton import DirectButton
from direct.actor.Actor import Actor
import random
import sys
import time
import os
import math

VERSION = '2.0.8.1'

settings = ['window-title Droid Game ' +  VERSION]
            

loadPrcFileData('', settings[0]) # Сделаем не стандартный заголовок (возмём его из переменной settings)

# Создадим главный класс нашей игры
class DroidShooter(ShowBase):
    def __init__(self):
        ShowBase.__init__(self) # Загружаем все селфы из direct
        self.menu() # звпуск меню
        
    def menu(self):

        
        
        base.enableParticles() # инициализируем эффект дыма

        self.win.setClearColor((0, 0, 0, 1)) # Закрашиваем поверхность чёрным. Дело в том, что по умолчанию в этом игровом движке поверхность закрашивается серым.
        self.font = loader.loadFont('./fonts/doom_font.ttf') # загрузим шрифт из игры doom
        self.inst_font = loader.loadFont('./fonts/arial.ttf') # загрузим шрифт arial

        self.crackSound = loader.loadMusic('./sounds/crack.wav') # звук взрыва корабля
        self.shotSound = loader.loadMusic('./sounds/shot.wav') # звук выстрела
        self.startSound = loader.loadMusic('./sounds/started.wav') # звук запуска
        self.errorSound = loader.loadMusic('./sounds/error.wav') # звук запуска
        self.errorSound.setVolume(1) # полная громкость звука
        self.startSound.setVolume(1) # полная громкость звука
        self.crackSound.setVolume(1) # полная громкость звука
        self.shotSound.setVolume(1) # полная громкость звука
        self.startSound.play() # играем звук запуска игры

        self.button_start = DirectButton(pos=(0.7, 0, 0), text="CTAPT",
                                   scale=.3, pad=(.2, .2),
                                   command=self.load_game) # кнопка запуска игры


        self.title = self.addTitle("Droid game " + VERSION)
        
        # Напечатаем управление
        self.inst1 = self.addInstructions(0.95, u"[ESC]: Выход")
        self.inst2 = self.addInstructions(0.90, u"[Left Arrow]: Поворот дроида в лево")
        self.inst3 = self.addInstructions(0.85, u"[Right Arrow]: Поворот дроида в право")
        self.inst4 = self.addInstructions(0.80, u"[Up Arrow]: Вперед")
        self.inst5 = self.addInstructions(0.75, u"[a]: поворот камеры в право")
        self.inst6 = self.addInstructions(0.70, u"[d]: поворот камеры в лево")
        self.inst7 = self.addInstructions(0.65, u"[Left Arrow + Up arrow]: поворот дроида в лево и вперёд")
        self.inst8 = self.addInstructions(0.60, u"[Right Arrow + Up arrow]: поворот дроида в право и вперёд")
        self.inst9 = self.addInstructions(0.55, u"[a + Up arrow]: поворот камеры в право и вперёд")
        self.inst10 = self.addInstructions(0.50, u"[d + Up arrow]: поворот камеры в лево и вперёд")
        self.inst11 = self.addInstructions(0.45, u"[space]: стрельба")
        self.inst12 = self.addInstructions(0.40, u"[l]: включить свет")
        self.inst13 = self.addInstructions(0.35, u"[s]: достать фонарик")
        self.inst14 = self.addInstructions(0.30, u"[w]: убрать оружие")
        self.inst15 = self.addInstructions(0.25, u"[f]: полный разгон(ОПАСНО!)")
        self.inst16 = self.addInstructions(0.20, u"[0]: пожаротушительная система")

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.


        
    def load_game(self):
        self.button_start.hide() # удалим кнопку старта
        self.title.hide() # удалим заголовок игры
        # удалим кнопки управления
        self.inst1.hide()
        self.inst2.hide()
        self.inst3.hide()
        self.inst4.hide()
        self.inst5.hide()
        self.inst6.hide()
        self.inst7.hide()
        self.inst8.hide()
        self.inst9.hide()
        self.inst10.hide()
        self.inst11.hide()
        self.inst12.hide()
        self.inst13.hide()
        self.inst14.hide()
        self.inst15.hide()
        self.inst16.hide()
        self.keyMap = {
            "left": 0, "right": 0, "forward": 0, "cam-left": 0, "cam-right": 0} # Создадим словарь кнопок, по их нажатию        

        # Рисуем звёзды

        self.sky = loader.loadModel("./models/sky/solar_sky_sphere") # загрузим модель космоса(это сфера)

        self.sky_tex = loader.loadTexture("./tex/stars_1k_tex.jpg") # загрузим текстуру
        self.sky.setTexture(self.sky_tex, 1) # зарендерим текстуру на небо
        self.sky.reparentTo(render) # инициализируем небо
        self.sky.setScale(40000) # расширим небо до максимальной величины panda3d

        self.environ = loader.loadModel("models/world/falcon.egg") # Загрузим уже созданный в blender мир.
        self.environ.reparentTo(render) # Загружаем модель мира в окно

        droidStartPos = (-1, 6, 1.5 ) # Загружаем стартовую позицию игрока в мире.
        enemyStartPos = (-6, 1, 0.5) # Загружаем позицию помощника-дроида.
        self.droid = Actor("models/player/seeker.egg") # Загружаем модель игрока (созданная в blender)
        self.enemy = Actor("models/enemies/r2d2/r2d2.egg") # Загружаем модель помощника (созданная в blender)
        self.droid.reparentTo(render) # Перемещаем модель игрока в наш мир.
        self.enemy.reparentTo(render) # Перемещаем модель помощника в наш мир.
        self.droid.setScale(1) # Делаем размер игрока на 1
        self.enemy.setScale(1) # Делаем размер помощника на 1
        self.droid.setPos(droidStartPos) # Ставим позицию игрока на ранее созданую нами позицию
        self.enemy.setPos(enemyStartPos) # Ставим позицию помощника на ранее созданую нами позицию

        self.weapon = Actor('models/weapon/lightsaber.egg') # загрузим оружие
        self.bullet = Actor('models/weapon/bullet/bullet.egg.pz') # Загрузим пулю
        self.flash = Actor('models/whishlyflash/handlamp.egg') # Загрузим фонарик
               
        self.weapon.reparentTo(render) # переместим оружие в наш мир
        self.bullet.reparentTo(render) # переместим пулю в мир но появится она сама не скоро
        self.flash.reparentTo(render) # переместим фонарик в наш мир
        self.weapon.setScale(.5) # зададим размер пушки
        self.bullet.setScale(.5) # зададим размер пули
        self.flash.setScale(.5) # зададим размер фонарика
        

        self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 1 # Позиция пушки
        
        self.weapon.setPos(self.weapon_pos) # Поместим оружие чуть выше игрока
        
        
        self.spotlight = camera.attachNewNode(Spotlight("spotlight")) # Конфиги фонарика
        self.spotlight.node().setColor((.3, .3, .3, 1))
        self.spotlight.node().setSpecularColor((0, 0, 0, 1))

        self.floater = NodePath(PandaNode("floater")) 
        self.floater.reparentTo(self.droid)
        self.floater.setZ(2.0)


        

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.
        
        self.accept("arrow_left", self.setKey, ["left", True]) # При кнопке влево - поворачиваем игрока влево
        self.accept("arrow_right", self.setKey, ["right", True]) # При кнопке вправо - поворачиваем игрока вправо
        self.accept("arrow_up", self.setKey, ["forward", True]) # При кнопке вперёд - идём вперёд
        self.accept("a", self.setKey, ["cam-left", True]) # При кнопке a - разворачиваем камеру вокруг нашей модельки
        self.accept("d", self.setKey, ["cam-right", True]) # При кнопке s - разворачиваем камеру вокруг нашей модельки
        self.accept("arrow_left-up", self.setKey, ["left", False]) # При кнопке вверх+влево - поворачиваем игрока влево и идём вперёд
        self.accept("arrow_right-up", self.setKey, ["right", False]) # При кнопке вверх+право - поворачиваем игрока вправо и идём вперёд
        self.accept("arrow_up-up", self.setKey, ["forward", False]) # При кнопке вверх+вверъ - идём вперёд и идём вперёд
        self.accept("a-up", self.setKey, ["cam-left", False]) # При кнопке a+вперёд - поворачиваем камеру влево и идём вперёд
        self.accept("d-up", self.setKey, ["cam-right", False]) # При кнопке s+вперёд - поворачиваем камеру вправо и идём вперёд

        self.accept("space", self.shot) # при пробеле стреляем
        self.accept("l", self.white_light) # при нажатии l - включаем свет.
        self.accept("s", self.toggleLights, [[self.spotlight]]) # Включить фонарик
        self.accept("w", self.weapon_hide) # при кнопке w - уберём оружие.

        base.enableParticles() # Включаем инициализацию дыма
        self.t = loader.loadModel("models/weapon/lightsaber.egg") # Загрузим ещё оружие
        self.t.reparentTo(render) # Переместим оружие в наш мир
        self.p = ParticleEffect() # Включим эффект дыма
        self.accept('f', self.particle_start) # при нажатии f(от force) -  загрузим файл дыма и переместим в конфиг чтобы именно эта анимация стала отображением дыма
        self.accept('0', self.fountain) # при  нажатии кнопок f+o(fountain) включим пожаротушительную систему

        taskMgr.add(self.move, "moveTask") # Добавляем задачу в наш движок

        self.isMoving = False # Ставим значение isMoving на False(Вы можете менять это значение) чтобы игрок изначально стоял.
        # Делаем так, чтобы свет был изначально выключен.
        self.disableMouse() # Отключаем перемещение через мышку
        self.camera.setPos(self.droid.getX(), self.droid.getY() + 1, 3) # Ставим позицию камеры чуть больше позиции игрока

        self.black_light() # Сделаем выключенный свет

        self.state = 100 # Состояние корабля
        self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
                
        self.check_loss() # Проверяем поражение
        self.hide_weapon = False # Поставим, что оружие не убрано
        self.steam_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 5)
        self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 1)

        self.motor_pos1 = 25.238849639892578, 8.962211608886719, 1.5 # позиция первого мотора
        self.motor_pos2 = -20.676218032836914, 10.55816650390625, 1.5 # позиция второго мотора

        self.motor1 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos1) # мотор 1
        self.motor2 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos2) # мотор 2

        self.state_info2 = OnscreenText(text='', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля

        self.force = False # Поставим, что не разгонялись

        # Проверяем коллизии
        
        self.cTrav = CollisionTraverser()
        pusher = CollisionHandlerPusher()
        
        environCenter = self.environ.getBounds().getCenter()
        environRad = self.environ.getBounds().getRadius()
        cNode = CollisionNode("environment")
        cNode.addSolid(CollisionInvSphere(environCenter, environRad))
        environC = self.environ.attachNewNode(cNode)
        
        droidCenter = self.droid.getBounds().getCenter()
        droidRad = self.droid.getBounds().getRadius()
        cNode = CollisionNode("droid")
        cNode.addSolid(CollisionSphere(droidCenter, droidRad))
        droidC = self.droid.attachNewNode(cNode)
        
        pusher.addCollider(droidC, self.droid)
        self.cTrav.addCollider(droidC, pusher)
        
        enemyCenter = self.enemy.getBounds().getCenter()
        enemyRad = self.enemy.getBounds().getRadius()
        cNode = CollisionNode("enemy")
        cNode.addSolid(CollisionSphere(enemyCenter, enemyRad))
        enemyC = self.enemy.attachNewNode(cNode)
        
        pusher.addCollider(enemyC, self.enemy)
        self.cTrav.addCollider(enemyC, pusher)

    def loadParticleConfig(self, filename, pos):
        self.p.cleanup()
        self.p = ParticleEffect()
        self.p.loadConfig(Filename(filename))

        self.p.start(self.t)
        self.p.setPos(pos)

    def particle_start(self):
        self.force = True # Поставим, что мы уже разгонялись
        self.environ.setY(self.environ.getY() + 300) # несёмся вправо
        self.droid.setY(self.droid.getY() + 300) # сдвигаем дроида за кораблём
        self.enemy.setY(self.enemy.getY() + 300) # сдфигаем помощника за кораблём

        self.state -= 1 # сделаем меньше очков
        self.state_info.hide() # удалим текстовые очки

        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)
        
        if self.state != 10 and self.state > 10:
            self.state_info.hide() # убираем предедущее сообщение о жизнях корабля
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.steam_pos = random.randrange(0, 5), random.randrange(0, 5) + 300, random.randrange(0, 5)
            self.loadParticleConfig('special_effects/steam/steam.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide() # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='двигатель неисправен', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
        else:
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.steam_pos = random.randrange(0, 5), random.randrange(0, 5) + 300, random.randrange(0, 5)
            self.loadParticleConfig('special_effects/steam_critic/steam.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide()  # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='критическое состояние', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
    
        if self.state < 10:
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.steam_pos = random.randrange(0, 5), random.randrange(0, 5) + 300, random.randrange(0, 5)            
            self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide() # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='падаем!', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля


    def fountain(self):
        if self.force:
            if self.state != 100:
                self.state += 1
                self.state_info.hide()
                self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля

            self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5) + 500, random.randrange(0, 1)    
            self.loadParticleConfig('special_effects/fountain/fountain.ptf', self.fountain_pos)

        else :
            if self.state != 100:
                self.state += 1
                self.state_info.hide()
                self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля

            self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 1)    
            self.loadParticleConfig('special_effects/fountain/fountain.ptf', self.fountain_pos)
        
        
    def black_light(self):
        # Выключеный свет
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor((0, 0, 0, 1))
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))
        
    def white_light(self):
        # Освещение
        self.light = (.3, .3, .3, 1)
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor(self.light)
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))

    def weapon_hide(self):
        # Убрать оружие
        self.hide_weapon = True
        self.weapon.hide()

    def toggleLights(self, lights):
        # Специализированное освещение
        if self.hide_weapon:
            for light in lights:
                if render.hasLight(light):
                    render.clearLight(light)
                else:
                    render.setLight(light)
            
            self.flash.setPos(self.weapon_pos)
        else:
            return


    def shot(self):
        dt = globalClock.getDt() + 0.5 # Cкорость движения
        if not self.hide_weapon : # если оружие убрано то мы не можем стрелять
            self.shotSound.play() # играем звук выстрела
            self.bullet.setPos(self.weapon_pos) # Пуля будет спавнится внутри пушки
            self.bullet.setY(self.bullet, -11 * dt) # Сдвигаем пулю на огромной скорости вперёд

    def check_loss(self):
        if self.state == 0: # если жизней у корабля не осталось
            self.black_light() # включим чёрный свет
            self.weapon_hide() # уберём оружие
            self.info = OnscreenText(text='поражение', style=1, font=self.font,
                                     fg=(1,1,1,1),
                            pos=(-1.3, -0.5), align=TextNode.ALeft, scale=0.3) # напишем о поражении
            self.crackSound.play() # играем звук взрыва


    def setKey(self, key, value):
        self.keyMap[key] = value # Делаем мехaнuзм нажатия клавиш.

    def move(self, task):

        ''' Делаем фуцнкцию движения игрока '''

        dt = globalClock.getDt() - .005 # Cкорость движения

        # Поворот камеры влево и вправо

        if self.keyMap["cam-left"]:
            self.camera.setX(self.camera, -20 * dt) # Меняем положеника камеры по икс. Таким образом получается илюзия поворота угла луча. Но на самом деле камера просто перемещается.
        if self.keyMap["cam-right"]:
            self.camera.setX(self.camera, +20 * dt) # Тоже самое, что и наверху.

        startpos = self.droid.getPos() # Сделаем удобную переменную позиции игрока

        if self.keyMap["left"]:
            self.droid.setH(self.droid.getH() + 300 * dt)
            self.enemy.setY(self.enemy, 1 * dt)
        if self.keyMap["right"]:
            self.droid.setH(self.droid.getH() - 300 * dt)
            self.enemy.setY(self.enemy, -1 * dt)
        if self.keyMap["forward"]:
            self.droid.setY(self.droid, -25 * dt)
            self.enemy.setX(self.enemy, 1 * dt)
            # Сделаем специальное условие проверки убрано оружие или нет
            if not self.hide_weapon:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.weapon.setPos(self.weapon_pos) # Обновляем позицию пушки
            else:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.flash.setPos(self.weapon_pos)



        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)


        return task.cont

    def addInstructions(self, pos, msg):
        return OnscreenText(text=msg, style=1, fg=(1,1,1,1), font=self.inst_font, pos=(-1.3, pos), align=TextNode.ALeft, scale = .04, shadow=(0,0,0,1))

    def addTitle(self, text):
        return OnscreenText(text=text, style=1, fg=(1, 1, 1, 1), scale=0.2,
                            parent=base.a2dBottomRight, align=TextNode.ARight,
                            pos=(-0.1, 0.09), shadow=(0, 0, 0, 1), font=self.font)
    

droid = DroidShooter() # Создадим экземпляр класса нашей игры
droid.run() # 3апустим игру


# разроботчики :
# Главный автор : ma3rx
# Помощник : panda3dmastercoder( присоеденился к проекту 31 января, 12:??)

Bug fixes and add sky!

This is the real 2.0.8.1 code. There is a mistake in the last one:

#!/usr/bin/env python
# -*- coding: utf_8 -*-

# Создано : 22 число, январь, 2021 год

# Импортируем все необходимые инструменты
from direct.showbase.ShowBase import ShowBase
from panda3d.core import CollisionTraverser, CollisionNode
from panda3d.core import CollisionHandlerPusher, CollisionSphere, CollisionInvSphere
from panda3d.core import Filename, AmbientLight, DirectionalLight
from panda3d.core import PandaNode, NodePath, Camera, TextNode
from direct.particles.Particles import Particles
from direct.particles.ParticleEffect import ParticleEffect
from panda3d.core import PointLight, Spotlight
from panda3d.core import CollideMask
from panda3d.core import loadPrcFileData
from panda3d.core import *
from direct.gui.OnscreenText import OnscreenText
from direct.gui.OnscreenImage import OnscreenImage
from direct.gui.DirectButton import DirectButton
from direct.actor.Actor import Actor
import random
import sys
import time
import os
import math

VERSION = '2.0.8.1'

settings = ['window-title Droid Game ' +  VERSION]
            

loadPrcFileData('', settings[0]) # Сделаем не стандартный заголовок (возмём его из переменной settings)

# Создадим главный класс нашей игры
class DroidShooter(ShowBase):
    def __init__(self):
        ShowBase.__init__(self) # Загружаем все селфы из direct
        self.menu() # звпуск меню
        
    def menu(self):

        
        
        base.enableParticles() # инициализируем эффект дыма

        self.win.setClearColor((0, 0, 0, 1)) # Закрашиваем поверхность чёрным. Дело в том, что по умолчанию в этом игровом движке поверхность закрашивается серым.
        self.font = loader.loadFont('./fonts/doom_font.ttf') # загрузим шрифт из игры doom
        self.inst_font = loader.loadFont('./fonts/arial.ttf') # загрузим шрифт arial

        self.crackSound = loader.loadMusic('./sounds/crack.wav') # звук взрыва корабля
        self.shotSound = loader.loadMusic('./sounds/shot.wav') # звук выстрела
        self.startSound = loader.loadMusic('./sounds/started.wav') # звук запуска
        self.errorSound = loader.loadMusic('./sounds/error.wav') # звук запуска
        self.errorSound.setVolume(1) # полная громкость звука
        self.startSound.setVolume(1) # полная громкость звука
        self.crackSound.setVolume(1) # полная громкость звука
        self.shotSound.setVolume(1) # полная громкость звука
        self.startSound.play() # играем звук запуска игры

        self.button_start = DirectButton(pos=(0.7, 0, 0), text="CTAPT",
                                   scale=.3, pad=(.2, .2),
                                   command=self.load_game) # кнопка запуска игры


        self.title = self.addTitle("Droid game " + VERSION)
        
        # Напечатаем управление
        self.inst1 = self.addInstructions(0.95, u"[ESC]: Выход")
        self.inst2 = self.addInstructions(0.90, u"[Left Arrow]: Поворот дроида в лево")
        self.inst3 = self.addInstructions(0.85, u"[Right Arrow]: Поворот дроида в право")
        self.inst4 = self.addInstructions(0.80, u"[Up Arrow]: Вперед")
        self.inst5 = self.addInstructions(0.75, u"[a]: поворот камеры в право")
        self.inst6 = self.addInstructions(0.70, u"[d]: поворот камеры в лево")
        self.inst7 = self.addInstructions(0.65, u"[Left Arrow + Up arrow]: поворот дроида в лево и вперёд")
        self.inst8 = self.addInstructions(0.60, u"[Right Arrow + Up arrow]: поворот дроида в право и вперёд")
        self.inst9 = self.addInstructions(0.55, u"[a + Up arrow]: поворот камеры в право и вперёд")
        self.inst10 = self.addInstructions(0.50, u"[d + Up arrow]: поворот камеры в лево и вперёд")
        self.inst11 = self.addInstructions(0.45, u"[space]: стрельба")
        self.inst12 = self.addInstructions(0.40, u"[l]: включить свет")
        self.inst13 = self.addInstructions(0.35, u"[s]: достать фонарик")
        self.inst14 = self.addInstructions(0.30, u"[w]: убрать оружие")
        self.inst15 = self.addInstructions(0.25, u"[f]: полный разгон(ОПАСНО!)")
        self.inst16 = self.addInstructions(0.20, u"[0]: пожаротушительная система")

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.


        
    def load_game(self):
        self.button_start.hide() # удалим кнопку старта
        self.title.hide() # удалим заголовок игры
        # удалим кнопки управления
        self.inst1.hide()
        self.inst2.hide()
        self.inst3.hide()
        self.inst4.hide()
        self.inst5.hide()
        self.inst6.hide()
        self.inst7.hide()
        self.inst8.hide()
        self.inst9.hide()
        self.inst10.hide()
        self.inst11.hide()
        self.inst12.hide()
        self.inst13.hide()
        self.inst14.hide()
        self.inst15.hide()
        self.inst16.hide()
        self.keyMap = {
            "left": 0, "right": 0, "forward": 0, "cam-left": 0, "cam-right": 0} # Создадим словарь кнопок, по их нажатию        

        # Рисуем звёзды

        self.sky = loader.loadModel("./models/sky/solar_sky_sphere") # загрузим модель космоса(это сфера)

        self.sky_tex = loader.loadTexture("./tex/stars_1k_tex.jpg") # загрузим текстуру
        self.sky.setTexture(self.sky_tex, 1) # зарендерим текстуру на небо
        self.sky.reparentTo(render) # инициализируем небо
        self.sky.setScale(40000) # расширим небо до максимальной величины panda3d

        self.environ = loader.loadModel("models/world/falcon.egg") # Загрузим уже созданный в blender мир.
        self.environ.reparentTo(render) # Загружаем модель мира в окно

        droidStartPos = (-1, 6, 1.5 ) # Загружаем стартовую позицию игрока в мире.
        enemyStartPos = (-6, 1, 0.5) # Загружаем позицию помощника-дроида.
        self.droid = Actor("models/player/seeker.egg") # Загружаем модель игрока (созданная в blender)
        self.enemy = Actor("models/enemies/r2d2/r2d2.egg") # Загружаем модель помощника (созданная в blender)
        self.droid.reparentTo(render) # Перемещаем модель игрока в наш мир.
        self.enemy.reparentTo(render) # Перемещаем модель помощника в наш мир.
        self.droid.setScale(1) # Делаем размер игрока на 1
        self.enemy.setScale(1) # Делаем размер помощника на 1
        self.droid.setPos(droidStartPos) # Ставим позицию игрока на ранее созданую нами позицию
        self.enemy.setPos(enemyStartPos) # Ставим позицию помощника на ранее созданую нами позицию

        self.weapon = Actor('models/weapon/lightsaber.egg') # загрузим оружие
        self.bullet = Actor('models/weapon/bullet/bullet.egg.pz') # Загрузим пулю
        self.flash = Actor('models/whishlyflash/handlamp.egg') # Загрузим фонарик
               
        self.weapon.reparentTo(render) # переместим оружие в наш мир
        self.bullet.reparentTo(render) # переместим пулю в мир но появится она сама не скоро
        self.flash.reparentTo(render) # переместим фонарик в наш мир
        self.weapon.setScale(.5) # зададим размер пушки
        self.bullet.setScale(.5) # зададим размер пули
        self.flash.setScale(.5) # зададим размер фонарика
        

        self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 1 # Позиция пушки
        
        self.weapon.setPos(self.weapon_pos) # Поместим оружие чуть выше игрока
        
        
        self.spotlight = camera.attachNewNode(Spotlight("spotlight")) # Конфиги фонарика
        self.spotlight.node().setColor((.3, .3, .3, 1))
        self.spotlight.node().setSpecularColor((0, 0, 0, 1))

        self.floater = NodePath(PandaNode("floater")) 
        self.floater.reparentTo(self.droid)
        self.floater.setZ(2.0)


        

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.
        
        self.accept("arrow_left", self.setKey, ["left", True]) # При кнопке влево - поворачиваем игрока влево
        self.accept("arrow_right", self.setKey, ["right", True]) # При кнопке вправо - поворачиваем игрока вправо
        self.accept("arrow_up", self.setKey, ["forward", True]) # При кнопке вперёд - идём вперёд
        self.accept("a", self.setKey, ["cam-left", True]) # При кнопке a - разворачиваем камеру вокруг нашей модельки
        self.accept("d", self.setKey, ["cam-right", True]) # При кнопке s - разворачиваем камеру вокруг нашей модельки
        self.accept("arrow_left-up", self.setKey, ["left", False]) # При кнопке вверх+влево - поворачиваем игрока влево и идём вперёд
        self.accept("arrow_right-up", self.setKey, ["right", False]) # При кнопке вверх+право - поворачиваем игрока вправо и идём вперёд
        self.accept("arrow_up-up", self.setKey, ["forward", False]) # При кнопке вверх+вверъ - идём вперёд и идём вперёд
        self.accept("a-up", self.setKey, ["cam-left", False]) # При кнопке a+вперёд - поворачиваем камеру влево и идём вперёд
        self.accept("d-up", self.setKey, ["cam-right", False]) # При кнопке s+вперёд - поворачиваем камеру вправо и идём вперёд

        self.accept("space", self.shot) # при пробеле стреляем
        self.accept("l", self.white_light) # при нажатии l - включаем свет.
        self.accept("s", self.toggleLights, [[self.spotlight]]) # Включить фонарик
        self.accept("w", self.weapon_hide) # при кнопке w - уберём оружие.

        base.enableParticles() # Включаем инициализацию дыма
        self.t = loader.loadModel("models/weapon/lightsaber.egg") # Загрузим ещё оружие
        self.t.reparentTo(render) # Переместим оружие в наш мир
        self.p = ParticleEffect() # Включим эффект дыма
        self.accept('f', self.particle_start) # при нажатии f(от force) -  загрузим файл дыма и переместим в конфиг чтобы именно эта анимация стала отображением дыма
        self.accept('0', self.fountain) # при  нажатии кнопок f+o(fountain) включим пожаротушительную систему

        taskMgr.add(self.move, "moveTask") # Добавляем задачу в наш движок

        self.isMoving = False # Ставим значение isMoving на False(Вы можете менять это значение) чтобы игрок изначально стоял.
        # Делаем так, чтобы свет был изначально выключен.
        self.disableMouse() # Отключаем перемещение через мышку
        self.camera.setPos(self.droid.getX(), self.droid.getY() + 1, 3) # Ставим позицию камеры чуть больше позиции игрока

        self.black_light() # Сделаем выключенный свет

        self.state = 100 # Состояние корабля
        self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
                
        self.check_loss() # Проверяем поражение
        self.hide_weapon = False # Поставим, что оружие не убрано
        self.steam_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 5)
        self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 1)

        self.motor_pos1 = 25.238849639892578, 8.962211608886719, 1.5 # позиция первого мотора
        self.motor_pos2 = -20.676218032836914, 10.55816650390625, 1.5 # позиция второго мотора

        self.motor1 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos1) # мотор 1
        self.motor2 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos2) # мотор 2

        self.state_info2 = OnscreenText(text='', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля

        self.force = False # Поставим, что не разгонялись

        # Проверяем коллизии
        
        self.cTrav = CollisionTraverser()
        pusher = CollisionHandlerPusher()
        
        environCenter = self.environ.getBounds().getCenter()
        environRad = self.environ.getBounds().getRadius()
        cNode = CollisionNode("environment")
        cNode.addSolid(CollisionInvSphere(environCenter, environRad))
        environC = self.environ.attachNewNode(cNode)
        
        droidCenter = self.droid.getBounds().getCenter()
        droidRad = self.droid.getBounds().getRadius()
        cNode = CollisionNode("droid")
        cNode.addSolid(CollisionSphere(droidCenter, droidRad))
        droidC = self.droid.attachNewNode(cNode)
        
        pusher.addCollider(droidC, self.droid)
        self.cTrav.addCollider(droidC, pusher)
        
        enemyCenter = self.enemy.getBounds().getCenter()
        enemyRad = self.enemy.getBounds().getRadius()
        cNode = CollisionNode("enemy")
        cNode.addSolid(CollisionSphere(enemyCenter, enemyRad))
        enemyC = self.enemy.attachNewNode(cNode)
        
        pusher.addCollider(enemyC, self.enemy)
        self.cTrav.addCollider(enemyC, pusher)

    def loadParticleConfig(self, filename, pos):
        self.p.cleanup()
        self.p = ParticleEffect()
        self.p.loadConfig(Filename(filename))

        self.p.start(self.t)
        self.p.setPos(pos)

    def particle_start(self):
        self.force = True # Поставим, что мы уже разгонялись
        self.environ.setY(self.environ.getY() + 300) # несёмся вправо
        self.droid.setY(self.droid.getY() + 300) # сдвигаем дроида за кораблём
        self.enemy.setY(self.enemy.getY() + 300) # сдфигаем помощника за кораблём

        self.state -= 1 # сделаем меньше очков
        self.state_info.hide() # удалим текстовые очки

        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)
        
        if self.state != 10 and self.state > 10:
            self.state_info.hide() # убираем предедущее сообщение о жизнях корабля
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.steam_pos = random.randrange(0, 5), random.randrange(0, 5) + 300, random.randrange(0, 5)
            self.loadParticleConfig('special_effects/steam/steam.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide() # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='двигатель неисправен', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
        else:
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.steam_pos = random.randrange(0, 5), random.randrange(0, 5) + 300, random.randrange(0, 5)
            self.loadParticleConfig('special_effects/steam_critic/steam.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide()  # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='критическое состояние', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
    
        if self.state < 10:
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.steam_pos = random.randrange(0, 5), random.randrange(0, 5) + 300, random.randrange(0, 5)            
            self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide() # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='падаем!', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля


    def fountain(self):
        if self.force:
            if self.state != 100:
                self.state += 1
                self.state_info.hide()
                self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля

            self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5) + 500, random.randrange(0, 1)    
            self.loadParticleConfig('special_effects/fountain/fountain.ptf', self.fountain_pos)

        else :
            if self.state != 100:
                self.state += 1
                self.state_info.hide()
                self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля

            self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 1)    
            self.loadParticleConfig('special_effects/fountain/fountain.ptf', self.fountain_pos)
        
        
    def black_light(self):
        # Выключеный свет
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor((0, 0, 0, 1))
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))
        
    def white_light(self):
        # Освещение
        self.light = (.3, .3, .3, 1)
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor(self.light)
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))

    def weapon_hide(self):
        # Убрать оружие
        self.hide_weapon = True
        self.weapon.hide()

    def toggleLights(self, lights):
        # Специализированное освещение
        if self.hide_weapon:
            for light in lights:
                if render.hasLight(light):
                    render.clearLight(light)
                else:
                    render.setLight(light)
            
            self.flash.setPos(self.weapon_pos)
        else:
            return


    def shot(self):
        dt = globalClock.getDt() + 0.5 # Cкорость движения
        if not self.hide_weapon : # если оружие убрано то мы не можем стрелять
            self.shotSound.play() # играем звук выстрела
            self.bullet.setPos(self.weapon_pos) # Пуля будет спавнится внутри пушки
            self.bullet.setY(self.bullet, -11 * dt) # Сдвигаем пулю на огромной скорости вперёд

    def check_loss(self):
        if self.state == 0: # если жизней у корабля не осталось
            self.black_light() # включим чёрный свет
            self.weapon_hide() # уберём оружие
            self.info = OnscreenText(text='поражение', style=1, font=self.font,
                                     fg=(1,1,1,1),
                            pos=(-1.3, -0.5), align=TextNode.ALeft, scale=0.3) # напишем о поражении
            self.crackSound.play() # играем звук взрыва


    def setKey(self, key, value):
        self.keyMap[key] = value # Делаем мехaнuзм нажатия клавиш.

    def move(self, task):

        ''' Делаем фуцнкцию движения игрока '''

        dt = globalClock.getDt() - .005 # Cкорость движения

        # Поворот камеры влево и вправо

        if self.keyMap["cam-left"]:
            self.camera.setX(self.camera, -20 * dt) # Меняем положеника камеры по икс. Таким образом получается илюзия поворота угла луча. Но на самом деле камера просто перемещается.
        if self.keyMap["cam-right"]:
            self.camera.setX(self.camera, +20 * dt) # Тоже самое, что и наверху.

        startpos = self.droid.getPos() # Сделаем удобную переменную позиции игрока

        if self.keyMap["left"]:
            self.droid.setH(self.droid.getH() + 300 * dt)
            self.enemy.setY(self.enemy, 1 * dt)
        if self.keyMap["right"]:
            self.droid.setH(self.droid.getH() - 300 * dt)
            self.enemy.setY(self.enemy, -1 * dt)
        if self.keyMap["forward"]:
            self.droid.setY(self.droid, -25 * dt)
            self.enemy.setX(self.enemy, 1 * dt)
            # Сделаем специальное условие проверки убрано оружие или нет
            if not self.hide_weapon:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.weapon.setPos(self.weapon_pos) # Обновляем позицию пушки
            else:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.flash.setPos(self.weapon_pos)



        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)


        return task.cont

    def addInstructions(self, pos, msg):
        return OnscreenText(text=msg, style=1, fg=(1,1,1,1), font=self.inst_font, pos=(-1.3, pos), align=TextNode.ALeft, scale = .04, shadow=(0,0,0,1))

    def addTitle(self, text):
        return OnscreenText(text=text, style=1, fg=(1, 1, 1, 1), scale=0.2,
                            parent=base.a2dBottomRight, align=TextNode.ARight,
                            pos=(-0.1, 0.09), shadow=(0, 0, 0, 1), font=self.font)
    

droid = DroidShooter() # Создадим экземпляр класса нашей игры
droid.run() # 3апустим игру


# разроботчики :
# Главный автор : ma3rx
# Помощник : panda3dmastercoder( присоеденился к проекту 31 января, 12:??)
1 Like

One mistake in code :
Delete from panda3d.core import CollisionHandlerOusher, CollisionSphere, CollisionInvSphere
and add :

from panda3d.core import CollisionHandlerPusher, CollisionSphere, CollisionInvSphere
1 Like

Sorry. I made a typing mistake

I am getting this error:

:loader(error): Couldn’t load file ./models/sky/solar_sky_sphere.egg: not found
on model path (currently: “/c/Python-DOOM-master;/c/Panda3D-1.10.8-x64/etc/…;/c
/Panda3D-1.10.8-x64/etc/…/models”)
Traceback (most recent call last):
File “C:\Panda3D-1.10.8-x64\direct\showbase\EventManager.py”, line 49, in even
tLoopTask
self.doEvents()
File “C:\Panda3D-1.10.8-x64\direct\showbase\EventManager.py”, line 43, in doEv
ents
processFunc(dequeueFunc())
File “C:\Panda3D-1.10.8-x64\direct\showbase\EventManager.py”, line 99, in proc
essEvent
messenger.send(eventName, paramList)
File “C:\Panda3D-1.10.8-x64\direct\showbase\Messenger.py”, line 337, in send
self.__dispatch(acceptorDict, event, sentArgs, foundWatch)
File “C:\Panda3D-1.10.8-x64\direct\showbase\Messenger.py”, line 422, in __disp
atch
result = method (*(extraArgs + sentArgs))
File “C:\Panda3D-1.10.8-x64\direct\gui\DirectButton.py”, line 107, in commandF
unc
self’command’
File “droid.py”, line 115, in load_game
self.sky = loader.loadModel("./models/sky/solar_sky_sphere") # ??? ???
?? ???(??? ???)
File “C:\Panda3D-1.10.8-x64\direct\showbase\Loader.py”, line 298, in loadModel

raise IOError(message)

OSError: Could not load model file(s): [’./models/sky/solar_sky_sphere’]
:task(error): Exception occurred in PythonTask eventManager
Traceback (most recent call last):
File “droid.py”, line 446, in
droid.run() # 3??? ???
File “C:\Panda3D-1.10.8-x64\direct\showbase\ShowBase.py”, line 3325, in run
self.taskMgr.run()
File “C:\Panda3D-1.10.8-x64\direct\task\Task.py”, line 541, in run
self.step()
File “C:\Panda3D-1.10.8-x64\direct\task\Task.py”, line 495, in step
self.mgr.poll()
File “C:\Panda3D-1.10.8-x64\direct\showbase\EventManager.py”, line 49, in even
tLoopTask
self.doEvents()
File “C:\Panda3D-1.10.8-x64\direct\showbase\EventManager.py”, line 43, in doEv
ents
processFunc(dequeueFunc())
File “C:\Panda3D-1.10.8-x64\direct\showbase\EventManager.py”, line 99, in proc
essEvent
messenger.send(eventName, paramList)
File “C:\Panda3D-1.10.8-x64\direct\showbase\Messenger.py”, line 337, in send
self.__dispatch(acceptorDict, event, sentArgs, foundWatch)
File “C:\Panda3D-1.10.8-x64\direct\showbase\Messenger.py”, line 422, in __disp
atch
result = method (*(extraArgs + sentArgs))
File “C:\Panda3D-1.10.8-x64\direct\gui\DirectButton.py”, line 107, in commandF
unc
self’command’
File “droid.py”, line 115, in load_game
self.sky = loader.loadModel("./models/sky/solar_sky_sphere") # ??? ???
?? ???(??? ???)
File “C:\Panda3D-1.10.8-x64\direct\showbase\Loader.py”, line 298, in loadModel

raise IOError(message)

OSError: Could not load model file(s): [’./models/sky/solar_sky_sphere’]

@panda3dmastercoder, Sorry.
Add to directory models : sky.zip (12.5 KB)

If anybody wants prevoious updates, go to: Droid game with Panda3D

All structure(this is no update!!) :
./models :

enemies.zip (404.7 KB) player.zip (709.8 KB) weapon.zip (253.6 KB) whishlyflash.zip (105.7 KB )sky.zip (12.5 KB)

Now, download panda3d art from https://www.panda3d.org/download/noversion/art-gallery.zip
Go to the cat-environment folder. In it you will see a millenium falcon folder. Take all the files from it and put it in the directory ./models/world

./fonts : fonts.zip (192.5 KB)
./sounds : sounds.zip (1.8 MB)
./special_effects : special_effects.zip (19.3 KB)
./tex : tex.zip (95.1 KB)

droid.py source code :

#!/usr/bin/env python
# -*- coding: utf_8 -*-

# Создано : 22 число, январь, 2021 год

# Импортируем все необходимые инструменты
from direct.showbase.ShowBase import ShowBase
from panda3d.core import CollisionTraverser, CollisionNode
from panda3d.core import CollisionHandlerPusher, CollisionSphere, CollisionInvSphere
from panda3d.core import Filename, AmbientLight, DirectionalLight
from panda3d.core import PandaNode, NodePath, Camera, TextNode
from direct.particles.Particles import Particles
from direct.particles.ParticleEffect import ParticleEffect
from panda3d.core import PointLight, Spotlight
from panda3d.core import CollideMask
from panda3d.core import loadPrcFileData
from panda3d.core import *
from direct.gui.OnscreenText import OnscreenText
from direct.gui.OnscreenImage import OnscreenImage
from direct.gui.DirectButton import DirectButton
from direct.actor.Actor import Actor
import random
import sys
import time
import os
import math

VERSION = '2.0.8.2'

settings = ['window-title Droid Game ' +  VERSION]
            

loadPrcFileData('', settings[0]) # Сделаем не стандартный заголовок (возмём его из переменной settings)

# Создадим главный класс нашей игры
class DroidShooter(ShowBase):
    def __init__(self):
        ShowBase.__init__(self) # Загружаем все селфы из direct
        self.menu() # звпуск меню
        
    def menu(self):
        
        base.enableParticles() # инициализируем эффект дыма

        self.win.setClearColor((0, 0, 0, 1)) # Закрашиваем поверхность чёрным. Дело в том, что по умолчанию в этом игровом движке поверхность закрашивается серым.
        self.font = loader.loadFont('./fonts/doom_font.ttf') # загрузим шрифт из игры doom
        self.inst_font = loader.loadFont('./fonts/arial.ttf') # загрузим шрифт arial

        self.crackSound = loader.loadMusic('./sounds/crack.wav') # звук взрыва корабля
        self.shotSound = loader.loadMusic('./sounds/shot.wav') # звук выстрела
        self.startSound = loader.loadMusic('./sounds/started.wav') # звук запуска
        self.errorSound = loader.loadMusic('./sounds/error.wav') # звук запуска
        self.errorSound.setVolume(1) # полная громкость звука
        self.startSound.setVolume(1) # полная громкость звука
        self.crackSound.setVolume(1) # полная громкость звука
        self.shotSound.setVolume(1) # полная громкость звука
        self.startSound.play() # играем звук запуска игры

        self.button_start = DirectButton(pos=(0.7, 0, 0), text="CTAPT",
                                   scale=.3, pad=(.2, .2),
                                   command=self.load_game) # кнопка запуска игры


        self.title = self.addTitle("Droid game " + VERSION)
        
        # Напечатаем управление
        self.inst1 = self.addInstructions(0.95, u"[ESC]: Выход")
        self.inst2 = self.addInstructions(0.90, u"[Left Arrow]: Поворот дроида в лево")
        self.inst3 = self.addInstructions(0.85, u"[Right Arrow]: Поворот дроида в право")
        self.inst4 = self.addInstructions(0.80, u"[Up Arrow]: Вперед")
        self.inst5 = self.addInstructions(0.75, u"[a]: поворот камеры в право")
        self.inst6 = self.addInstructions(0.70, u"[d]: поворот камеры в лево")
        self.inst7 = self.addInstructions(0.65, u"[Left Arrow + Up arrow]: поворот дроида в лево и вперёд")
        self.inst8 = self.addInstructions(0.60, u"[Right Arrow + Up arrow]: поворот дроида в право и вперёд")
        self.inst9 = self.addInstructions(0.55, u"[a + Up arrow]: поворот камеры в право и вперёд")
        self.inst10 = self.addInstructions(0.50, u"[d + Up arrow]: поворот камеры в лево и вперёд")
        self.inst11 = self.addInstructions(0.45, u"[space]: стрельба")
        self.inst12 = self.addInstructions(0.40, u"[l]: включить свет")
        self.inst13 = self.addInstructions(0.35, u"[s]: достать фонарик")
        self.inst14 = self.addInstructions(0.30, u"[w]: убрать оружие")
        self.inst15 = self.addInstructions(0.25, u"[f]: полный разгон(ОПАСНО!)")
        self.inst16 = self.addInstructions(0.20, u"[0]: пожаротушительная система")

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.


        
    def load_game(self):
        self.button_start.hide() # удалим кнопку старта
        self.title.hide() # удалим заголовок игры
        # удалим кнопки управления
        self.inst1.hide()
        self.inst2.hide()
        self.inst3.hide()
        self.inst4.hide()
        self.inst5.hide()
        self.inst6.hide()
        self.inst7.hide()
        self.inst8.hide()
        self.inst9.hide()
        self.inst10.hide()
        self.inst11.hide()
        self.inst12.hide()
        self.inst13.hide()
        self.inst14.hide()
        self.inst15.hide()
        self.inst16.hide()
        self.keyMap = {
            "left": 0, "right": 0, "forward": 0, "cam-left": 0, "cam-right": 0} # Создадим словарь кнопок, по их нажатию        

        # Рисуем звёзды

        self.sky = loader.loadModel("./models/sky/solar_sky_sphere") # загрузим модель космоса(это сфера)

        self.sky_tex = loader.loadTexture("./tex/stars_1k_tex.jpg") # загрузим текстуру
        self.sky.setTexture(self.sky_tex, 1) # зарендерим текстуру на небо
        self.sky.reparentTo(render) # инициализируем небо
        self.sky.setScale(40000) # расширим небо до максимальной величины panda3d

        self.environ = loader.loadModel("models/world/falcon.egg") # Загрузим уже созданный в blender мир.
        self.environ.reparentTo(render) # Загружаем модель мира в окно

        droidStartPos = (-1, 6, 1.5 ) # Загружаем стартовую позицию игрока в мире.
        enemyStartPos = (-6, 1, 0.5) # Загружаем позицию помощника-дроида.
        self.droid = Actor("models/player/seeker.egg") # Загружаем модель игрока (созданная в blender)
        self.enemy = Actor("models/enemies/r2d2/r2d2.egg") # Загружаем модель помощника (созданная в blender)
        self.droid.reparentTo(render) # Перемещаем модель игрока в наш мир.
        self.enemy.reparentTo(render) # Перемещаем модель помощника в наш мир.
        self.droid.setScale(1) # Делаем размер игрока на 1
        self.enemy.setScale(1) # Делаем размер помощника на 1
        self.droid.setPos(droidStartPos) # Ставим позицию игрока на ранее созданую нами позицию
        self.enemy.setPos(enemyStartPos) # Ставим позицию помощника на ранее созданую нами позицию

        self.weapon = Actor('models/weapon/lightsaber.egg') # загрузим оружие
        self.bullet = Actor('models/weapon/bullet/bullet.egg.pz') # Загрузим пулю
        self.flash = Actor('models/whishlyflash/handlamp.egg') # Загрузим фонарик
               
        self.weapon.reparentTo(render) # переместим оружие в наш мир
        self.bullet.reparentTo(render) # переместим пулю в мир но появится она сама не скоро
        self.flash.reparentTo(render) # переместим фонарик в наш мир
        self.weapon.setScale(.5) # зададим размер пушки
        self.bullet.setScale(.5) # зададим размер пули
        self.flash.setScale(.5) # зададим размер фонарика
        

        self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 1 # Позиция пушки
        
        self.weapon.setPos(self.weapon_pos) # Поместим оружие чуть выше игрока
        
        
        self.spotlight = camera.attachNewNode(Spotlight("spotlight")) # Конфиги фонарика
        self.spotlight.node().setColor((.3, .3, .3, 1))
        self.spotlight.node().setSpecularColor((0, 0, 0, 1))

        self.floater = NodePath(PandaNode("floater")) 
        self.floater.reparentTo(self.droid)
        self.floater.setZ(2.0)


        

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.
        
        self.accept("arrow_left", self.setKey, ["left", True]) # При кнопке влево - поворачиваем игрока влево
        self.accept("arrow_right", self.setKey, ["right", True]) # При кнопке вправо - поворачиваем игрока вправо
        self.accept("arrow_up", self.setKey, ["forward", True]) # При кнопке вперёд - идём вперёд
        self.accept("a", self.setKey, ["cam-left", True]) # При кнопке a - разворачиваем камеру вокруг нашей модельки
        self.accept("d", self.setKey, ["cam-right", True]) # При кнопке s - разворачиваем камеру вокруг нашей модельки
        self.accept("arrow_left-up", self.setKey, ["left", False]) # При кнопке вверх+влево - поворачиваем игрока влево и идём вперёд
        self.accept("arrow_right-up", self.setKey, ["right", False]) # При кнопке вверх+право - поворачиваем игрока вправо и идём вперёд
        self.accept("arrow_up-up", self.setKey, ["forward", False]) # При кнопке вверх+вверъ - идём вперёд и идём вперёд
        self.accept("a-up", self.setKey, ["cam-left", False]) # При кнопке a+вперёд - поворачиваем камеру влево и идём вперёд
        self.accept("d-up", self.setKey, ["cam-right", False]) # При кнопке s+вперёд - поворачиваем камеру вправо и идём вперёд

        self.accept("space", self.shot) # при пробеле стреляем
        self.accept("l", self.white_light) # при нажатии l - включаем свет.
        self.accept("s", self.toggleLights, [[self.spotlight]]) # Включить фонарик
        self.accept("w", self.weapon_hide) # при кнопке w - уберём оружие.

        base.enableParticles() # Включаем инициализацию дыма
        self.t = loader.loadModel("models/weapon/lightsaber.egg") # Загрузим ещё оружие
        self.t.reparentTo(render) # Переместим оружие в наш мир
        self.p = ParticleEffect() # Включим эффект дыма
        self.accept('f', self.particle_start) # при нажатии f(от force) -  загрузим файл дыма и переместим в конфиг чтобы именно эта анимация стала отображением дыма
        self.accept('0', self.fountain) # при  нажатии кнопок f+o(fountain) включим пожаротушительную систему

        taskMgr.add(self.move, "moveTask") # Добавляем задачу в наш движок

        self.isMoving = False # Ставим значение isMoving на False(Вы можете менять это значение) чтобы игрок изначально стоял.
        # Делаем так, чтобы свет был изначально выключен.
        self.disableMouse() # Отключаем перемещение через мышку
        self.camera.setPos(self.droid.getX(), self.droid.getY() + 1, 3) # Ставим позицию камеры чуть больше позиции игрока

        self.black_light() # Сделаем выключенный свет

        self.state = 100 # Состояние корабля
        self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
                
        self.check_loss() # Проверяем поражение
        self.hide_weapon = False # Поставим, что оружие не убрано
        self.steam_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 5)
        self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 1)

        self.motor_pos1 = 25.238849639892578, 8.962211608886719, 1.5 # позиция первого мотора
        self.motor_pos2 = -20.676218032836914, 10.55816650390625, 1.5 # позиция второго мотора

        self.motor1 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos1) # мотор 1
        self.motor2 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos2) # мотор 2

        self.state_info2 = OnscreenText(text='', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля

        self.force = False # Поставим, что не разгонялись

        # Проверяем коллизии
        
        self.cTrav = CollisionTraverser()
        pusher = CollisionHandlerPusher()
        
        environCenter = self.environ.getBounds().getCenter()
        environRad = self.environ.getBounds().getRadius()
        cNode = CollisionNode("environment")
        cNode.addSolid(CollisionInvSphere(environCenter, environRad))
        environC = self.environ.attachNewNode(cNode)
        
        droidCenter = self.droid.getBounds().getCenter()
        droidRad = self.droid.getBounds().getRadius()
        cNode = CollisionNode("droid")
        cNode.addSolid(CollisionSphere(droidCenter, droidRad))
        droidC = self.droid.attachNewNode(cNode)
        
        pusher.addCollider(droidC, self.droid)
        self.cTrav.addCollider(droidC, pusher)
        
        enemyCenter = self.enemy.getBounds().getCenter()
        enemyRad = self.enemy.getBounds().getRadius()
        cNode = CollisionNode("enemy")
        cNode.addSolid(CollisionSphere(enemyCenter, enemyRad))
        enemyC = self.enemy.attachNewNode(cNode)
        
        pusher.addCollider(enemyC, self.enemy)
        self.cTrav.addCollider(enemyC, pusher)

    def loadParticleConfig(self, filename, pos):
        self.p.cleanup()
        self.p = ParticleEffect()
        self.p.loadConfig(Filename(filename))

        self.p.start(self.t)
        self.p.setPos(pos)

    def particle_start(self):
        self.force = True # Поставим, что мы уже разгонялись
        self.environ.setY(self.environ.getY() + 300) # несёмся вправо
        self.droid.setY(self.droid.getY() + 300) # сдвигаем дроида за кораблём
        self.enemy.setY(self.enemy.getY() + 300) # сдфигаем помощника за кораблём

        self.state -= 1 # сделаем меньше очков
        self.state_info.hide() # удалим текстовые очки

        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)
        
        if self.state != 10 and self.state > 10:
            self.state_info.hide() # убираем предедущее сообщение о жизнях корабля
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.steam_pos = random.randrange(0, 5), random.randrange(0, 5) + 300, random.randrange(0, 5)
            self.loadParticleConfig('special_effects/steam/steam.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide() # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='двигатель неисправен', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
        else:
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.steam_pos = random.randrange(0, 5), random.randrange(0, 5) + 300, random.randrange(0, 5)
            self.loadParticleConfig('special_effects/steam_critic/steam.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide()  # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='критическое состояние', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
    
        if self.state < 10:
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.steam_pos = random.randrange(0, 5), random.randrange(0, 5) + 300, random.randrange(0, 5)            
            self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide() # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='падаем!', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля


    def fountain(self):
        if self.force:
            if self.state != 100:
                self.state += 1
                self.state_info.hide()
                self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля

            self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5) + 500, random.randrange(0, 1)    
            self.loadParticleConfig('special_effects/fountain/fountain.ptf', self.fountain_pos)

        else :
            if self.state != 100:
                self.state += 1
                self.state_info.hide()
                self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля

            self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 1)    
            self.loadParticleConfig('special_effects/fountain/fountain.ptf', self.fountain_pos)
        
        
    def black_light(self):
        # Выключеный свет
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor((0, 0, 0, 1))
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))
        
    def white_light(self):
        # Освещение
        self.light = (.3, .3, .3, 1)
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor(self.light)
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))

    def weapon_hide(self):
        # Убрать оружие
        self.hide_weapon = True
        self.weapon.hide()

    def toggleLights(self, lights):
        # Специализированное освещение
        if self.hide_weapon:
            for light in lights:
                if render.hasLight(light):
                    render.clearLight(light)
                else:
                    render.setLight(light)
            
            self.flash.setPos(self.weapon_pos)
        else:
            return


    def shot(self):
        dt = globalClock.getDt() + 0.5 # Cкорость движения
        if not self.hide_weapon : # если оружие убрано то мы не можем стрелять
            self.shotSound.play() # играем звук выстрела
            self.bullet.setPos(self.weapon_pos) # Пуля будет спавнится внутри пушки
            self.bullet.setY(self.bullet, -11 * dt) # Сдвигаем пулю на огромной скорости вперёд

    def check_loss(self):
        if self.state == 0: # если жизней у корабля не осталось
            self.black_light() # включим чёрный свет
            self.weapon_hide() # уберём оружие
            self.info = OnscreenText(text='поражение', style=1, font=self.font,
                                     fg=(1,1,1,1),
                            pos=(-1.3, -0.5), align=TextNode.ALeft, scale=0.3) # напишем о поражении
            self.crackSound.play() # играем звук взрыва


    def setKey(self, key, value):
        self.keyMap[key] = value # Делаем мехaнuзм нажатия клавиш.

    def move(self, task):

        ''' Делаем фуцнкцию движения игрока '''

        dt = globalClock.getDt() - .005 # Cкорость движения

        # Поворот камеры влево и вправо

        if self.keyMap["cam-left"]:
            self.camera.setX(self.camera, -20 * dt) # Меняем положеника камеры по икс. Таким образом получается илюзия поворота угла луча. Но на самом деле камера просто перемещается.
        if self.keyMap["cam-right"]:
            self.camera.setX(self.camera, +20 * dt) # Тоже самое, что и наверху.

        startpos = self.droid.getPos() # Сделаем удобную переменную позиции игрока

        if self.keyMap["left"]:
            self.droid.setH(self.droid.getH() + 300 * dt)
            self.enemy.setY(self.enemy, 1 * dt)
        if self.keyMap["right"]:
            self.droid.setH(self.droid.getH() - 300 * dt)
            self.enemy.setY(self.enemy, -1 * dt)
        if self.keyMap["forward"]:
            self.droid.setY(self.droid, -25 * dt)
            self.enemy.setX(self.enemy, 1 * dt)
            # Сделаем специальное условие проверки убрано оружие или нет
            if not self.hide_weapon:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.weapon.setPos(self.weapon_pos) # Обновляем позицию пушки
            else:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.flash.setPos(self.weapon_pos)



        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)


        return task.cont

    def addInstructions(self, pos, msg):
        return OnscreenText(text=msg, style=1, fg=(1,1,1,1), font=self.inst_font, pos=(-1.3, pos), align=TextNode.ALeft, scale = .04, shadow=(0,0,0,1))

    def addTitle(self, text):
        return OnscreenText(text=text, style=1, fg=(1, 1, 1, 1), scale=0.2,
                            parent=base.a2dBottomRight, align=TextNode.ARight,
                            pos=(-0.1, 0.09), shadow=(0, 0, 0, 1), font=self.font)
    

droid = DroidShooter() # Создадим экземпляр класса нашей игры
droid.run() # 3апустим игру


# разроботчики :
# Главный автор : ma3rx
# Помощник : panda3dmastercoder( присоеденился к проекту 31 января, 12:??)

Hi! I create 2.0.9 update!
./models :

enemies.zip (404.7 KB) player.zip (709.8 KB) weapon.zip (253.6 KB) whishlyflash.zip (105.7 KB )sky.zip (12.5 KB)

Now, download panda3d art from https://www.panda3d.org/download/noversion/art-gallery.zip
Go to the cat-environment folder. In it you will see a millenium falcon folder. Take all the files from it and put it in the directory ./models/world

./fonts : fonts.zip (192.5 KB)
./sounds : sounds.zip (1.8 MB)
./special_effects : special_effects.zip (19.3 KB)
./tex : tex.zip (95.1 KB)

droid.py source code :

#!/usr/bin/env python
# -*- coding: utf_8 -*-

# Создано : 22 число, январь, 2021 год

# Импортируем все необходимые инструменты
from direct.showbase.ShowBase import ShowBase
from panda3d.core import CollisionTraverser, CollisionNode
from panda3d.core import CollisionHandlerPusher, CollisionSphere, CollisionInvSphere
from panda3d.core import Filename, AmbientLight, DirectionalLight
from panda3d.core import PandaNode, NodePath, Camera, TextNode
from direct.particles.Particles import Particles
from direct.particles.ParticleEffect import ParticleEffect
from panda3d.core import PointLight, Spotlight
from panda3d.core import CollideMask
from panda3d.core import loadPrcFileData
from panda3d.core import *
from direct.gui.OnscreenText import OnscreenText
from direct.gui.OnscreenImage import OnscreenImage
from direct.gui.DirectButton import DirectButton
from direct.actor.Actor import Actor
import random
import sys
import time
import os
import math

VERSION = '2.0.9'

settings = ['window-title Droid Game ' +  VERSION]
            

loadPrcFileData('', settings[0]) # Сделаем не стандартный заголовок (возмём его из переменной settings)

# Создадим главный класс нашей игры
class DroidShooter(ShowBase):
    def __init__(self):
        ShowBase.__init__(self) # Загружаем все селфы из direct
        self.speed = 800
        self.menu() # звпуск меню
        
    def menu(self):
        
        base.enableParticles() # инициализируем эффект дыма

        self.win.setClearColor((0, 0, 0, 1)) # Закрашиваем поверхность чёрным. Дело в том, что по умолчанию в этом игровом движке поверхность закрашивается серым.
        self.font = loader.loadFont('./fonts/doom_font.ttf') # загрузим шрифт из игры doom
        self.inst_font = loader.loadFont('./fonts/arial.ttf') # загрузим шрифт arial

        self.crackSound = loader.loadMusic('./sounds/crack.wav') # звук взрыва корабля
        self.shotSound = loader.loadMusic('./sounds/shot.wav') # звук выстрела
        self.startSound = loader.loadMusic('./sounds/started.wav') # звук запуска
        self.errorSound = loader.loadMusic('./sounds/error.wav') # звук запуска
        self.errorSound.setVolume(1) # полная громкость звука
        self.startSound.setVolume(1) # полная громкость звука
        self.crackSound.setVolume(1) # полная громкость звука
        self.shotSound.setVolume(1) # полная громкость звука
        self.startSound.play() # играем звук запуска игры

        self.button_start = DirectButton(pos=(0.7, 0, 0), text="CTAPT",
                                   scale=.3, pad=(.2, .2),
                                   command=self.load_game) # кнопка запуска игры


        self.title = self.addTitle("Droid game " + VERSION)
        
        # Напечатаем управление
        self.inst1 = self.addInstructions(0.95, u"[ESC]: Выход")
        self.inst2 = self.addInstructions(0.90, u"[Left Arrow]: Поворот дроида в лево")
        self.inst3 = self.addInstructions(0.85, u"[Right Arrow]: Поворот дроида в право")
        self.inst4 = self.addInstructions(0.80, u"[Up Arrow]: Вперед")
        self.inst5 = self.addInstructions(0.75, u"[a]: поворот камеры в право")
        self.inst6 = self.addInstructions(0.70, u"[d]: поворот камеры в лево")
        self.inst7 = self.addInstructions(0.65, u"[Left Arrow + Up arrow]: поворот дроида в лево и вперёд")
        self.inst8 = self.addInstructions(0.60, u"[Right Arrow + Up arrow]: поворот дроида в право и вперёд")
        self.inst9 = self.addInstructions(0.55, u"[a + Up arrow]: поворот камеры в право и вперёд")
        self.inst10 = self.addInstructions(0.50, u"[d + Up arrow]: поворот камеры в лево и вперёд")
        self.inst11 = self.addInstructions(0.45, u"[space]: стрельба")
        self.inst12 = self.addInstructions(0.40, u"[l]: включить свет")
        self.inst13 = self.addInstructions(0.35, u"[s]: достать фонарик")
        self.inst14 = self.addInstructions(0.30, u"[w]: убрать оружие")
        self.inst15 = self.addInstructions(0.25, u"[f]: полный разгон(ОПАСНО!)")
        self.inst16 = self.addInstructions(0.20, u"[0]: пожаротушительная система")

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.


        
    def load_game(self):
        self.button_start.hide() # удалим кнопку старта
        self.title.hide() # удалим заголовок игры
        # удалим кнопки управления
        self.inst1.hide()
        self.inst2.hide()
        self.inst3.hide()
        self.inst4.hide()
        self.inst5.hide()
        self.inst6.hide()
        self.inst7.hide()
        self.inst8.hide()
        self.inst9.hide()
        self.inst10.hide()
        self.inst11.hide()
        self.inst12.hide()
        self.inst13.hide()
        self.inst14.hide()
        self.inst15.hide()
        self.inst16.hide()
        self.keyMap = {
            "left": 0, "right": 0, "forward": 0, "cam-left": 0, "cam-right": 0} # Создадим словарь кнопок, по их нажатию        

        # Рисуем звёзды

        self.sky = loader.loadModel("./models/sky/solar_sky_sphere") # загрузим модель космоса(это сфера)

        self.sky_tex = loader.loadTexture("./tex/stars_1k_tex.jpg") # загрузим текстуру
        self.sky.setTexture(self.sky_tex, 1) # зарендерим текстуру на небо
        self.sky.reparentTo(render) # инициализируем небо
        self.sky.setScale(40000) # расширим небо до максимальной величины panda3d

        self.environ = loader.loadModel("models/world/falcon.egg") # Загрузим уже созданный в blender мир.
        self.environ.reparentTo(render) # Загружаем модель мира в окно

        droidStartPos = (-1, 6, 1.5 ) # Загружаем стартовую позицию игрока в мире.
        enemyStartPos = (-6, 1, 0.5) # Загружаем позицию помощника-дроида.
        self.droid = Actor("models/player/seeker.egg") # Загружаем модель игрока (созданная в blender)
        self.enemy = Actor("models/enemies/r2d2/r2d2.egg") # Загружаем модель помощника (созданная в blender)
        self.droid.reparentTo(render) # Перемещаем модель игрока в наш мир.
        self.enemy.reparentTo(render) # Перемещаем модель помощника в наш мир.
        self.droid.setScale(1) # Делаем размер игрока на 1
        self.enemy.setScale(1) # Делаем размер помощника на 1
        self.droid.setPos(droidStartPos) # Ставим позицию игрока на ранее созданую нами позицию
        self.enemy.setPos(enemyStartPos) # Ставим позицию помощника на ранее созданую нами позицию

        self.weapon = Actor('models/weapon/lightsaber.egg') # загрузим оружие
        self.bullet = Actor('models/weapon/bullet/bullet.egg.pz') # Загрузим пулю
        self.flash = Actor('models/whishlyflash/handlamp.egg') # Загрузим фонарик
               
        self.weapon.reparentTo(render) # переместим оружие в наш мир
        self.bullet.reparentTo(render) # переместим пулю в мир но появится она сама не скоро
        self.flash.reparentTo(render) # переместим фонарик в наш мир
        self.weapon.setScale(.5) # зададим размер пушки
        self.bullet.setScale(.5) # зададим размер пули
        self.flash.setScale(.5) # зададим размер фонарика
        

        self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 1 # Позиция пушки
        
        self.weapon.setPos(self.weapon_pos) # Поместим оружие чуть выше игрока
        
        
        self.spotlight = camera.attachNewNode(Spotlight("spotlight")) # Конфиги фонарика
        self.spotlight.node().setColor((.3, .3, .3, 1))
        self.spotlight.node().setSpecularColor((0, 0, 0, 1))

        self.floater = NodePath(PandaNode("floater")) 
        self.floater.reparentTo(self.droid)
        self.floater.setZ(2.0)


        

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.
        
        self.accept("arrow_left", self.setKey, ["left", True]) # При кнопке влево - поворачиваем игрока влево
        self.accept("arrow_right", self.setKey, ["right", True]) # При кнопке вправо - поворачиваем игрока вправо
        self.accept("arrow_up", self.setKey, ["forward", True]) # При кнопке вперёд - идём вперёд
        self.accept("a", self.setKey, ["cam-left", True]) # При кнопке a - разворачиваем камеру вокруг нашей модельки
        self.accept("d", self.setKey, ["cam-right", True]) # При кнопке s - разворачиваем камеру вокруг нашей модельки
        self.accept("arrow_left-up", self.setKey, ["left", False]) # При кнопке вверх+влево - поворачиваем игрока влево и идём вперёд
        self.accept("arrow_right-up", self.setKey, ["right", False]) # При кнопке вверх+право - поворачиваем игрока вправо и идём вперёд
        self.accept("arrow_up-up", self.setKey, ["forward", False]) # При кнопке вверх+вверъ - идём вперёд и идём вперёд
        self.accept("a-up", self.setKey, ["cam-left", False]) # При кнопке a+вперёд - поворачиваем камеру влево и идём вперёд
        self.accept("d-up", self.setKey, ["cam-right", False]) # При кнопке s+вперёд - поворачиваем камеру вправо и идём вперёд

        self.accept("space", self.shot) # при пробеле стреляем
        self.accept("l", self.white_light) # при нажатии l - включаем свет.
        self.accept("s", self.toggleLights, [[self.spotlight]]) # Включить фонарик
        self.accept("w", self.weapon_hide) # при кнопке w - уберём оружие.

        base.enableParticles() # Включаем инициализацию дыма
        self.t = loader.loadModel("models/weapon/lightsaber.egg") # Загрузим ещё оружие
        self.t.reparentTo(render) # Переместим оружие в наш мир
        self.p = ParticleEffect() # Включим эффект дыма
        self.accept('f', self.particle_start) # при нажатии f(от force) -  загрузим файл дыма и переместим в конфиг чтобы именно эта анимация стала отображением дыма
        self.accept('0', self.fountain) # при  нажатии кнопок f+o(fountain) включим пожаротушительную систему

        taskMgr.add(self.move, "moveTask") # Добавляем задачу в наш движок

        self.isMoving = False # Ставим значение isMoving на False(Вы можете менять это значение) чтобы игрок изначально стоял.
        # Делаем так, чтобы свет был изначально выключен.
        self.disableMouse() # Отключаем перемещение через мышку
        self.camera.setPos(self.droid.getX(), self.droid.getY() + 1, 3) # Ставим позицию камеры чуть больше позиции игрока

        self.black_light() # Сделаем выключенный свет

        self.state = 100 # Состояние корабля
        self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
                
        self.check_loss() # Проверяем поражение
        self.hide_weapon = False # Поставим, что оружие не убрано
        self.steam_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 5)
        self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 1)

        self.motor_pos1 = 25.238849639892578, 8.962211608886719, 1.5 # позиция первого мотора
        self.motor_pos2 = -20.676218032836914, 10.55816650390625, 1.5 # позиция второго мотора

        self.motor1 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos1) # мотор 1
        self.motor2 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos2) # мотор 2

        self.state_info2 = OnscreenText(text='', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля

        self.force = False # Поставим, что не разгонялись

        # Проверяем коллизии
        
        self.cTrav = CollisionTraverser()
        pusher = CollisionHandlerPusher()
        
        environCenter = self.environ.getBounds().getCenter()
        environRad = self.environ.getBounds().getRadius()
        cNode = CollisionNode("environment")
        cNode.addSolid(CollisionInvSphere(environCenter, environRad))
        environC = self.environ.attachNewNode(cNode)
        
        droidCenter = self.droid.getBounds().getCenter()
        droidRad = self.droid.getBounds().getRadius()
        cNode = CollisionNode("droid")
        cNode.addSolid(CollisionSphere(droidCenter, droidRad))
        droidC = self.droid.attachNewNode(cNode)
        
        pusher.addCollider(droidC, self.droid)
        self.cTrav.addCollider(droidC, pusher)
        
        enemyCenter = self.enemy.getBounds().getCenter()
        enemyRad = self.enemy.getBounds().getRadius()
        cNode = CollisionNode("enemy")
        cNode.addSolid(CollisionSphere(enemyCenter, enemyRad))
        enemyC = self.enemy.attachNewNode(cNode)
        
        pusher.addCollider(enemyC, self.enemy)
        self.cTrav.addCollider(enemyC, pusher)

    def loadParticleConfig(self, filename, pos):
        self.p.cleanup()
        self.p = ParticleEffect()
        self.p.loadConfig(Filename(filename))

        self.p.start(self.t)
        self.p.setPos(pos)

    def particle_start(self):
        self.force = True # Поставим, что мы уже разгонялись
        # перемещаем все обьекты за кораблём
        self.environ.setY(self.environ.getY() + self.speed) # несёмся вправо
        self.droid.setY(self.droid.getY() + self.speed) # сдвигаем дроида за кораблём
        self.enemy.setY(self.enemy.getY() + self.speed) # сдфигаем помощника за кораблём

        self.state -= 1 # сделаем меньше очков
        self.state_info.hide() # удалим текстовые очки

        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)
        
        if self.state != 10 and self.state > 10:
            self.state_info.hide() # убираем предедущее сообщение о жизнях корабля
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.steam_pos = random.randrange(0, 5), random.randrange(0, 5) + 300, random.randrange(0, 5)
            self.loadParticleConfig('special_effects/steam/steam.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide() # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='двигатель неисправен', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
        else:
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.steam_pos = random.randrange(0, 5), random.randrange(0, 5) + 300, random.randrange(0, 5)
            self.loadParticleConfig('special_effects/steam_critic/steam.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide()  # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='критическое состояние', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
    
        if self.state < 10:
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.steam_pos = random.randrange(0, 5), random.randrange(0, 5) + 300, random.randrange(0, 5)            
            self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide() # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='падаем!', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля


    def fountain(self):
        if self.force:
            if self.state != 100:
                self.state += 1
                self.state_info.hide()
                self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля

            self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5) + 500, random.randrange(0, 1)    
            self.loadParticleConfig('special_effects/fountain/fountain.ptf', self.fountain_pos)

        else :
            if self.state != 100:
                self.state += 1
                self.state_info.hide()
                self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля

            self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 1)    
            self.loadParticleConfig('special_effects/fountain/fountain.ptf', self.fountain_pos)
        
        
    def black_light(self):
        # Выключеный свет
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor((0, 0, 0, 1))
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))
        
    def white_light(self):
        # Освещение
        self.light = (.3, .3, .3, 1)
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor(self.light)
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))

    def weapon_hide(self):
        # Убрать оружие
        self.hide_weapon = True
        self.weapon.hide()

    def toggleLights(self, lights):
        # Специализированное освещение
        if self.hide_weapon:
            for light in lights:
                if render.hasLight(light):
                    render.clearLight(light)
                else:
                    render.setLight(light)
            
            self.flash.setPos(self.weapon_pos)
        else:
            return


    def shot(self):
        dt = globalClock.getDt() + 0.5 # Cкорость движения
        if not self.hide_weapon : # если оружие убрано то мы не можем стрелять
            self.shotSound.play() # играем звук выстрела
            self.bullet.setPos(self.weapon_pos) # Пуля будет спавнится внутри пушки
            self.bullet.setY(self.bullet, -11 * dt) # Сдвигаем пулю на огромной скорости вперёд

    def check_loss(self):
        if self.state == 0: # если жизней у корабля не осталось
            self.black_light() # включим чёрный свет
            self.weapon_hide() # уберём оружие
            self.info = OnscreenText(text='поражение', style=1, font=self.font,
                                     fg=(1,1,1,1),
                            pos=(-1.3, -0.5), align=TextNode.ALeft, scale=0.3) # напишем о поражении
            self.crackSound.play() # играем звук взрыва


    def setKey(self, key, value):
        self.keyMap[key] = value # Делаем мехaнuзм нажатия клавиш.

    def move(self, task):

        ''' Делаем фуцнкцию движения игрока '''

        dt = globalClock.getDt() - .005 # Cкорость движения

        # Поворот камеры влево и вправо

        if self.keyMap["cam-left"]:
            self.camera.setX(self.camera, -20 * dt) # Меняем положеника камеры по икс. Таким образом получается илюзия поворота угла луча. Но на самом деле камера просто перемещается.
        if self.keyMap["cam-right"]:
            self.camera.setX(self.camera, +20 * dt) # Тоже самое, что и наверху.

        startpos = self.droid.getPos() # Сделаем удобную переменную позиции игрока

        if self.keyMap["left"]:
            self.droid.setH(self.droid.getH() + 300 * dt)
            self.enemy.setY(self.enemy, 1 * dt)
        if self.keyMap["right"]:
            self.droid.setH(self.droid.getH() - 300 * dt)
            self.enemy.setY(self.enemy, -1 * dt)
        if self.keyMap["forward"]:
            self.droid.setY(self.droid, -25 * dt)
            self.enemy.setX(self.enemy, 1 * dt)
            # Сделаем специальное условие проверки убрано оружие или нет
            if not self.hide_weapon:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.weapon.setPos(self.weapon_pos) # Обновляем позицию пушки
            else:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.flash.setPos(self.weapon_pos)



        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)


        return task.cont

    def addInstructions(self, pos, msg):
        return OnscreenText(text=msg, style=1, fg=(1,1,1,1), font=self.inst_font, pos=(-1.3, pos), align=TextNode.ALeft, scale = .04, shadow=(0,0,0,1))

    def addTitle(self, text):
        return OnscreenText(text=text, style=1, fg=(1, 1, 1, 1), scale=0.2,
                            parent=base.a2dBottomRight, align=TextNode.ARight,
                            pos=(-0.1, 0.09), shadow=(0, 0, 0, 1), font=self.font)
    

droid = DroidShooter() # Создадим экземпляр класса нашей игры
droid.run() # 3апустим игру


# разроботчики :
# Главный автор : ma3rx
# Помощник : panda3dmastercoder( присоеденился к проекту 31 января, 12:??)

2.0.9.1 bug fixes!
New code :

#!/usr/bin/env python
# -*- coding: utf_8 -*-

# Создано : 22 число, январь, 2021 год

# Импортируем все необходимые инструменты
from direct.showbase.ShowBase import ShowBase
from panda3d.core import CollisionTraverser, CollisionNode
from panda3d.core import CollisionHandlerPusher, CollisionSphere, CollisionInvSphere
from panda3d.core import Filename, AmbientLight, DirectionalLight
from panda3d.core import PandaNode, NodePath, Camera, TextNode
from direct.particles.Particles import Particles
from direct.particles.ParticleEffect import ParticleEffect
from panda3d.core import PointLight, Spotlight
from panda3d.core import CollideMask
from panda3d.core import loadPrcFileData
from panda3d.core import *
from direct.gui.OnscreenText import OnscreenText
from direct.gui.OnscreenImage import OnscreenImage
from direct.gui.DirectButton import DirectButton
from direct.actor.Actor import Actor
import random
import sys
import time
import os
import math

VERSION = '2.0.9.1'

settings = ['window-title Droid Game ' +  VERSION]
            

loadPrcFileData('', settings[0]) # Сделаем не стандартный заголовок (возмём его из переменной settings)

# Создадим главный класс нашей игры
class DroidShooter(ShowBase):
    def __init__(self):
        ShowBase.__init__(self) # Загружаем все селфы из direct
        self.speed = 800
        self.menu() # звпуск меню
        
    def menu(self):
        
        base.enableParticles() # инициализируем эффект дыма

        self.win.setClearColor((0, 0, 0, 1)) # Закрашиваем поверхность чёрным. Дело в том, что по умолчанию в этом игровом движке поверхность закрашивается серым.
        self.font = loader.loadFont('./fonts/doom_font.ttf') # загрузим шрифт из игры doom
        self.inst_font = loader.loadFont('./fonts/arial.ttf') # загрузим шрифт arial

        self.crackSound = loader.loadMusic('./sounds/crack.wav') # звук взрыва корабля
        self.shotSound = loader.loadMusic('./sounds/shot.wav') # звук выстрела
        self.startSound = loader.loadMusic('./sounds/started.wav') # звук запуска
        self.errorSound = loader.loadMusic('./sounds/error.wav') # звук запуска
        self.errorSound.setVolume(1) # полная громкость звука
        self.startSound.setVolume(1) # полная громкость звука
        self.crackSound.setVolume(1) # полная громкость звука
        self.shotSound.setVolume(1) # полная громкость звука
        self.startSound.play() # играем звук запуска игры

        self.button_start = DirectButton(pos=(0.7, 0, 0), text="CTAPT",
                                   scale=.3, pad=(.2, .2),
                                   command=self.load_game) # кнопка запуска игры


        self.title = self.addTitle("Droid game " + VERSION)
        
        # Напечатаем управление
        self.inst1 = self.addInstructions(0.95, u"[ESC]: Выход")
        self.inst2 = self.addInstructions(0.90, u"[Left Arrow]: Поворот дроида в лево")
        self.inst3 = self.addInstructions(0.85, u"[Right Arrow]: Поворот дроида в право")
        self.inst4 = self.addInstructions(0.80, u"[Up Arrow]: Вперед")
        self.inst5 = self.addInstructions(0.75, u"[a]: поворот камеры в право")
        self.inst6 = self.addInstructions(0.70, u"[d]: поворот камеры в лево")
        self.inst7 = self.addInstructions(0.65, u"[Left Arrow + Up arrow]: поворот дроида в лево и вперёд")
        self.inst8 = self.addInstructions(0.60, u"[Right Arrow + Up arrow]: поворот дроида в право и вперёд")
        self.inst9 = self.addInstructions(0.55, u"[a + Up arrow]: поворот камеры в право и вперёд")
        self.inst10 = self.addInstructions(0.50, u"[d + Up arrow]: поворот камеры в лево и вперёд")
        self.inst11 = self.addInstructions(0.45, u"[space]: стрельба")
        self.inst12 = self.addInstructions(0.40, u"[l]: включить свет")
        self.inst13 = self.addInstructions(0.35, u"[s]: достать фонарик")
        self.inst14 = self.addInstructions(0.30, u"[w]: убрать оружие")
        self.inst15 = self.addInstructions(0.25, u"[f]: полный разгон(ОПАСНО!)")
        self.inst16 = self.addInstructions(0.20, u"[0]: пожаротушительная система")

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.


        
    def load_game(self):
        self.button_start.hide() # удалим кнопку старта
        self.title.hide() # удалим заголовок игры
        # удалим кнопки управления
        self.inst1.hide()
        self.inst2.hide()
        self.inst3.hide()
        self.inst4.hide()
        self.inst5.hide()
        self.inst6.hide()
        self.inst7.hide()
        self.inst8.hide()
        self.inst9.hide()
        self.inst10.hide()
        self.inst11.hide()
        self.inst12.hide()
        self.inst13.hide()
        self.inst14.hide()
        self.inst15.hide()
        self.inst16.hide()
        self.keyMap = {
            "left": 0, "right": 0, "forward": 0, "cam-left": 0, "cam-right": 0} # Создадим словарь кнопок, по их нажатию        

        # Рисуем звёзды

        self.sky = loader.loadModel("./models/sky/solar_sky_sphere") # загрузим модель космоса(это сфера)

        self.sky_tex = loader.loadTexture("./tex/stars_1k_tex.jpg") # загрузим текстуру
        self.sky.setTexture(self.sky_tex, 1) # зарендерим текстуру на небо
        self.sky.reparentTo(render) # инициализируем небо
        self.sky.setScale(40000) # расширим небо до максимальной величины panda3d

        self.environ = loader.loadModel("models/world/falcon.egg") # Загрузим уже созданный в blender мир.
        self.environ.reparentTo(render) # Загружаем модель мира в окно

        droidStartPos = (-1, 6, 1.5 ) # Загружаем стартовую позицию игрока в мире.
        enemyStartPos = (-6, 1, 0.5) # Загружаем позицию помощника-дроида.
        self.droid = Actor("models/player/seeker.egg") # Загружаем модель игрока (созданная в blender)
        self.enemy = Actor("models/enemies/r2d2/r2d2.egg") # Загружаем модель помощника (созданная в blender)
        self.droid.reparentTo(render) # Перемещаем модель игрока в наш мир.
        self.enemy.reparentTo(render) # Перемещаем модель помощника в наш мир.
        self.droid.setScale(1) # Делаем размер игрока на 1
        self.enemy.setScale(1) # Делаем размер помощника на 1
        self.droid.setPos(droidStartPos) # Ставим позицию игрока на ранее созданую нами позицию
        self.enemy.setPos(enemyStartPos) # Ставим позицию помощника на ранее созданую нами позицию

        self.weapon = Actor('models/weapon/lightsaber.egg') # загрузим оружие
        self.bullet = Actor('models/weapon/bullet/bullet.egg.pz') # Загрузим пулю
        self.flash = Actor('models/whishlyflash/handlamp.egg') # Загрузим фонарик
               
        self.weapon.reparentTo(render) # переместим оружие в наш мир
        self.bullet.reparentTo(render) # переместим пулю в мир но появится она сама не скоро
        self.flash.reparentTo(render) # переместим фонарик в наш мир
        self.weapon.setScale(.5) # зададим размер пушки
        self.bullet.setScale(.5) # зададим размер пули
        self.flash.setScale(.5) # зададим размер фонарика
        

        self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 1 # Позиция пушки
        
        self.weapon.setPos(self.weapon_pos) # Поместим оружие чуть выше игрока
        
        
        self.spotlight = camera.attachNewNode(Spotlight("spotlight")) # Конфиги фонарика
        self.spotlight.node().setColor((.3, .3, .3, 1))
        self.spotlight.node().setSpecularColor((0, 0, 0, 1))

        self.floater = NodePath(PandaNode("floater")) 
        self.floater.reparentTo(self.droid)
        self.floater.setZ(2.0)


        

        self.accept("escape", sys.exit) # При нажатии клавиши Esc выходим.
        
        self.accept("arrow_left", self.setKey, ["left", True]) # При кнопке влево - поворачиваем игрока влево
        self.accept("arrow_right", self.setKey, ["right", True]) # При кнопке вправо - поворачиваем игрока вправо
        self.accept("arrow_up", self.setKey, ["forward", True]) # При кнопке вперёд - идём вперёд
        self.accept("a", self.setKey, ["cam-left", True]) # При кнопке a - разворачиваем камеру вокруг нашей модельки
        self.accept("d", self.setKey, ["cam-right", True]) # При кнопке s - разворачиваем камеру вокруг нашей модельки
        self.accept("arrow_left-up", self.setKey, ["left", False]) # При кнопке вверх+влево - поворачиваем игрока влево и идём вперёд
        self.accept("arrow_right-up", self.setKey, ["right", False]) # При кнопке вверх+право - поворачиваем игрока вправо и идём вперёд
        self.accept("arrow_up-up", self.setKey, ["forward", False]) # При кнопке вверх+вверъ - идём вперёд и идём вперёд
        self.accept("a-up", self.setKey, ["cam-left", False]) # При кнопке a+вперёд - поворачиваем камеру влево и идём вперёд
        self.accept("d-up", self.setKey, ["cam-right", False]) # При кнопке s+вперёд - поворачиваем камеру вправо и идём вперёд

        self.accept("space", self.shot) # при пробеле стреляем
        self.accept("l", self.white_light) # при нажатии l - включаем свет.
        self.accept("s", self.toggleLights, [[self.spotlight]]) # Включить фонарик
        self.accept("w", self.weapon_hide) # при кнопке w - уберём оружие.

        base.enableParticles() # Включаем инициализацию дыма
        self.t = loader.loadModel("models/weapon/lightsaber.egg") # Загрузим ещё оружие
        self.t.reparentTo(render) # Переместим оружие в наш мир
        self.p = ParticleEffect() # Включим эффект дыма
        self.accept('f', self.particle_start) # при нажатии f(от force) -  загрузим файл дыма и переместим в конфиг чтобы именно эта анимация стала отображением дыма
        self.accept('0', self.fountain) # при  нажатии кнопок f+o(fountain) включим пожаротушительную систему

        self.accept('arrow_1+s', self.shot)

        taskMgr.add(self.move, "moveTask") # Добавляем задачу в наш движок

        self.isMoving = False # Ставим значение isMoving на False(Вы можете менять это значение) чтобы игрок изначально стоял.
        # Делаем так, чтобы свет был изначально выключен.
        self.disableMouse() # Отключаем перемещение через мышку
        self.camera.setPos(self.droid.getX(), self.droid.getY() + 1, 3) # Ставим позицию камеры чуть больше позиции игрока

        self.black_light() # Сделаем выключенный свет

        self.state = 100 # Состояние корабля
        self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
                
        self.check_loss() # Проверяем поражение
        self.hide_weapon = False # Поставим, что оружие не убрано
        self.steam_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 5)
        self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 1)

        self.motor_pos1 = 25.238849639892578, 8.962211608886719, 1.5 # позиция первого мотора
        self.motor_pos2 = -20.676218032836914, 10.55816650390625, 1.5 # позиция второго мотора

        self.motor1 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos1) # мотор 1
        self.motor2 = self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.motor_pos2) # мотор 2

        self.state_info2 = OnscreenText(text='', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля

        self.force = False # Поставим, что не разгонялись

        # Проверяем коллизии
        
        self.cTrav = CollisionTraverser()
        pusher = CollisionHandlerPusher()
        
        environCenter = self.environ.getBounds().getCenter()
        environRad = self.environ.getBounds().getRadius()
        cNode = CollisionNode("environment")
        cNode.addSolid(CollisionInvSphere(environCenter, environRad))
        environC = self.environ.attachNewNode(cNode)
        
        droidCenter = self.droid.getBounds().getCenter()
        droidRad = self.droid.getBounds().getRadius()
        cNode = CollisionNode("droid")
        cNode.addSolid(CollisionSphere(droidCenter, droidRad))
        droidC = self.droid.attachNewNode(cNode)
        
        pusher.addCollider(droidC, self.droid)
        self.cTrav.addCollider(droidC, pusher)
        
        enemyCenter = self.enemy.getBounds().getCenter()
        enemyRad = self.enemy.getBounds().getRadius()
        cNode = CollisionNode("enemy")
        cNode.addSolid(CollisionSphere(enemyCenter, enemyRad))
        enemyC = self.enemy.attachNewNode(cNode)
        
        pusher.addCollider(enemyC, self.enemy)
        self.cTrav.addCollider(enemyC, pusher)

    def loadParticleConfig(self, filename, pos):
        self.p.cleanup()
        self.p = ParticleEffect()
        self.p.loadConfig(Filename(filename))

        self.p.start(self.t)
        self.p.setPos(pos)

    def particle_start(self):
        self.force = True # Поставим, что мы уже разгонялись
        # перемещаем все обьекты за кораблём
        self.environ.setY(self.environ.getY() + self.speed) # несёмся вправо
        self.droid.setY(self.droid.getY() + self.speed) # сдвигаем дроида за кораблём
        self.enemy.setY(self.enemy.getY() + self.speed) # сдфигаем помощника за кораблём
        self.weapon.setY(self.weapon.getY() + self.speed) # перемещаем оружие
        self.flash.setY(self.flash.getY() + self.speed) # перемещаем фонарик

        self.state -= 1 # сделаем меньше очков
        self.state_info.hide() # удалим текстовые очки

        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)
        
        if self.state != 10 and self.state > 10:
            self.state_info.hide() # убираем предедущее сообщение о жизнях корабля
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.steam_pos = random.randrange(0, 5), random.randrange(0, 5) + 300, random.randrange(0, 5)
            self.loadParticleConfig('special_effects/steam/steam.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide() # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='двигатель неисправен', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
        else:
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.steam_pos = random.randrange(0, 5), random.randrange(0, 5) + 300, random.randrange(0, 5)
            self.loadParticleConfig('special_effects/steam_critic/steam.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide()  # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='критическое состояние', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
    
        if self.state < 10:
            self.state_info.hide()
            self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля
            self.check_loss() # проверяем поражение
            self.steam_pos = random.randrange(0, 5), random.randrange(0, 5) + 300, random.randrange(0, 5)            
            self.loadParticleConfig('special_effects/steam_critic+/fireish.ptf', self.steam_pos)
            self.errorSound.play() # играем звук ошибки
            self.state_info2.hide() # убираем предедущее сообщение
            self.state_info2 = OnscreenText(text='падаем!', pos=(-0.8, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля


    def fountain(self):
        if self.force:
            if self.state != 100:
                self.state += 1
                self.state_info.hide()
                self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля

            self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5) + 500, random.randrange(0, 1)    
            self.loadParticleConfig('special_effects/fountain/fountain.ptf', self.fountain_pos)

        else :
            if self.state != 100:
                self.state += 1
                self.state_info.hide()
                self.state_info = OnscreenText(text='корабль :' + str(self.state), pos=(0.5, 0.8), scale=0.1, fg=(1, 1, 1, 1), align=TextNode.ALeft, font=self.font) # Напишем сообщение о состоянии корабля

            self.fountain_pos = random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 1)    
            self.loadParticleConfig('special_effects/fountain/fountain.ptf', self.fountain_pos)
        
        
    def black_light(self):
        # Выключеный свет
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor((0, 0, 0, 1))
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))
        
    def white_light(self):
        # Освещение
        self.light = (.3, .3, .3, 1)
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor(self.light)
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection((-5, -5, -5))
        directionalLight.setColor((1, 1, 1, 1))
        directionalLight.setSpecularColor((1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))

    def weapon_hide(self):
        # Убрать оружие
        self.hide_weapon = True
        self.weapon.hide()

    def toggleLights(self, lights):
        # Специализированное освещение
        if self.hide_weapon:
            for light in lights:
                if render.hasLight(light):
                    render.clearLight(light)
                else:
                    render.setLight(light)
            
            self.flash.setPos(self.weapon_pos)
        else:
            return


    def shot(self):
        dt = globalClock.getDt() + 0.5 # Cкорость движения
        if not self.hide_weapon : # если оружие убрано то мы не можем стрелять
            self.shotSound.play() # играем звук выстрела
            self.bullet.setPos(self.weapon_pos) # Пуля будет спавнится внутри пушки
            self.bullet.setY(self.bullet, -11 * dt) # Сдвигаем пулю на огромной скорости вперёд

    def check_loss(self):
        if self.state == 0: # если жизней у корабля не осталось
            self.black_light() # включим чёрный свет
            self.weapon_hide() # уберём оружие
            self.info = OnscreenText(text='поражение', style=1, font=self.font,
                                     fg=(1,1,1,1),
                            pos=(-1.3, -0.5), align=TextNode.ALeft, scale=0.3) # напишем о поражении
            self.crackSound.play() # играем звук взрыва


    def setKey(self, key, value):
        self.keyMap[key] = value # Делаем мехaнuзм нажатия клавиш.

    def move(self, task):

        ''' Делаем фуцнкцию движения игрока '''

        dt = globalClock.getDt() - .005 # Cкорость движения

        # Поворот камеры влево и вправо

        if self.keyMap["cam-left"]:
            self.camera.setX(self.camera, -20 * dt) # Меняем положеника камеры по икс. Таким образом получается илюзия поворота угла луча. Но на самом деле камера просто перемещается.
        if self.keyMap["cam-right"]:
            self.camera.setX(self.camera, +20 * dt) # Тоже самое, что и наверху.

        startpos = self.droid.getPos() # Сделаем удобную переменную позиции игрока

        if self.keyMap["left"]:
            self.droid.setH(self.droid.getH() + 300 * dt)
            self.enemy.setY(self.enemy, 1 * dt)
        if self.keyMap["right"]:
            self.droid.setH(self.droid.getH() - 300 * dt)
            self.enemy.setY(self.enemy, -1 * dt)
        if self.keyMap["forward"]:
            self.droid.setY(self.droid, -25 * dt)
            self.enemy.setX(self.enemy, 1 * dt)
            # Сделаем специальное условие проверки убрано оружие или нет
            if not self.hide_weapon:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.weapon.setPos(self.weapon_pos) # Обновляем позицию пушки
            else:
                self.weapon_pos = self.droid.getX(), self.droid.getY() + 0.7, 3
                self.flash.setPos(self.weapon_pos)



        camvec = self.droid.getPos() - self.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if camdist > 10.0:
            self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
            camdist = 10.0
        if camdist < 5.0:
            self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
            camdist = 5.0
            
        self.camera.lookAt(self.floater)


        return task.cont

    def addInstructions(self, pos, msg):
        return OnscreenText(text=msg, style=1, fg=(1,1,1,1), font=self.inst_font, pos=(-1.3, pos), align=TextNode.ALeft, scale = .04, shadow=(0,0,0,1))

    def addTitle(self, text):
        return OnscreenText(text=text, style=1, fg=(1, 1, 1, 1), scale=0.2,
                            parent=base.a2dBottomRight, align=TextNode.ARight,
                            pos=(-0.1, 0.09), shadow=(0, 0, 0, 1), font=self.font)
    

droid = DroidShooter() # Создадим экземпляр класса нашей игры
droid.run() # 3апустим игру


# разроботчики :
# Главный автор : ma3rx
# Помощник : panda3dmastercoder( присоеденился к проекту 31 января, 12:??)