Droid Game with Panda3d

Специально для Русских

Если вы русский, то вы меня поймёте.
Короче, попрошу всех русских написать мне свои идеи для этого проекта. И желательно
чтобы это были новые модели, динамика, и самое главное колизии. Все модели, и ваши скрипты
отошлите на почту m7db@mail.ru

Hi, guys! I created the simple droid game
with game engine “Panda3D”. I worked a sounds.
Simply, its no cool… But, game i created, i can move further.
Ok, let us give link to you(our).
DroidHGame.zip (1.0 MB)
It’s the source code :slightly_smiling_face:

#!/usr/bin/env python

# Импортируем все необходимые инструменты
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 panda3d.core import CollideMask
from direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectSlider import DirectSlider
from direct.actor.Actor import Actor
import random
import sys
import os
import math


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

        self.win.setClearColor((0, 0, 0, 1)) # Закрашиваем поверхность чёрным. Дело в том, что по умолчанию в этом игровом движке поверхность закрашивается серым.

        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) # Загружаем модель мира в окно

        self.restartSound = loader.loadMusic('sound/restart.ogg') # Загрузим звук перезагрузки
        self.restartSound.setVolume(1) # Поставим громкость звука на 1.
        self.restartSound.setLoopCount(0) # Скорость проигрывания - 0.

        droidStartPos = (-1, 6, 1.5) # Загружаем стартовую позицию игрока в мире.
        enemyStartPos = (-6, 1, 0.5) # Загружаем позицию помощника-дроида.
        self.droid = Actor("models/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.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("s", 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("s-up", self.setKey, ["cam-right", False]) # При кнопке s+вперёд - поворачиваем камеру вправо и идём вперёд

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

        self.isMoving = False # Ставим значение isMoving на False(Вы можете менять это значение) чтобы игрок изначально стоял.

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

        self.sliderText = OnscreenText("Volume", pos=(-0.1, 0.87), scale=.07,
                                       fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1)) # Создаём текст Zvuk. Дело в том, что Panda3D не умеет писать текст по русски. 

        self.slider = DirectSlider(pos=(-0.1, 0, .75), scale=0.8, value=.50,
                                   command=self.setVolume) # Создаём слайдер звука

        # Создаём механику коллизий. Пока этот инструмент не работает. В дальнеййшим я реализую его по другому и колизии заработают.

        self.cTrav = CollisionTraverser()

        self.droidGroundRay = CollisionRay()
        self.droidGroundRay.setOrigin(0, 0, 9)
        self.droidGroundRay.setDirection(0, 0, -1)
        self.droidGroundCol = CollisionNode('DroidRay')
        self.droidGroundCol.addSolid(self.droidGroundRay)
        self.droidGroundCol.setFromCollideMask(CollideMask.bit(0))
        self.droidGroundCol.setIntoCollideMask(CollideMask.allOff())
        self.droidGroundColNp = self.droid.attachNewNode(self.droidGroundCol)
        self.droidGroundHandler = CollisionHandlerQueue()
        self.cTrav.addCollider(self.droidGroundColNp, self.droidGroundHandler)

        self.camGroundRay = CollisionRay()
        self.camGroundRay.setOrigin(0, 0, 9)
        self.camGroundRay.setDirection(0, 0, -1)
        self.camGroundCol = CollisionNode('camRay')
        self.camGroundCol.addSolid(self.camGroundRay)
        self.camGroundCol.setFromCollideMask(CollideMask.bit(0))
        self.camGroundCol.setIntoCollideMask(CollideMask.allOff())
        self.camGroundColNp = self.camera.attachNewNode(self.camGroundCol)
        self.camGroundHandler = CollisionHandlerQueue()
        self.cTrav.addCollider(self.camGroundColNp, self.camGroundHandler)

        self.cTrav.showCollisions(render)

        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor((.3, .3, .3, 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 setVolume(self):
        '''Сделаем функцию измены звука'''
        newVol = self.slider.guiItem.getValue()
        self.restartSound.setVolume(newVol)

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

    def move(self, task):

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

        dt = globalClock.getDt() - 0.005

        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.restartSound.play()
        if self.keyMap["right"]:
            self.restartSound.play()
            self.droid.setH(self.droid.getH() - 300 * dt)
        if self.keyMap["forward"]:
            self.droid.setY(self.droid, -25 * dt)

        if self.keyMap["forward"] or self.keyMap["left"] or self.keyMap["right"]:
            if self.isMoving is False:
                self.droid.loop("run")
                self.isMoving = True
        else:
            if self.isMoving:
                self.droid.stop()
                self.droid.pose("walk", 5)
                self.isMoving = False

        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

        entries = list(self.camGroundHandler.getEntries())
        entries.sort(key=lambda x: x.getSurfacePoint(render).getZ())

        if len(entries) > 0 and entries[0].getIntoNode().getName() == "terrain":
            self.camera.setZ(entries[0].getSurfacePoint(render).getZ() + 1.0)
        if self.camera.getZ() < self.droid.getZ() + 2.0:
            self.camera.setZ(self.droid.getZ() + 2.0)

        entries = list(self.camGroundHandler.getEntries())
        entries.sort(key=lambda x: x.getSurfacePoint(render).getZ())

        if len(entries) > 0 and entries[0].getIntoNode().getName() == "terrain":
            self.camera.setZ(entries[0].getSurfacePoint(render).getZ() + 1.0)
        if self.camera.getZ() < self.droid.getZ() + 2.0:
            self.camera.setZ(self.droid.getZ() + 2.0)
            
        self.camera.lookAt(self.floater)


        return task.cont


demo = DroidShooter() # Cоздаём объект класса нашей игры
demo.run() # Запускаем

4

Look to models new robot :slightly_smiling_face:

30 min later…

Robot r2d2 in the shooter!
Screenshot the new person in game :slight_smile:

23:52… Full screen update!
Screenshot :slight_smile:
New code :

#!/usr/bin/env python

# Импортируем все необходимые инструменты
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 panda3d.core import CollideMask
from panda3d.core import loadPrcFileData
from direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectSlider import DirectSlider
from direct.actor.Actor import Actor
import random
import sys
import os
import math

loadPrcFileData('', 'window-title Droid Game 1.0.2') # Сделаем не стандартный заголовок
loadPrcFileData('', 'win-size 1920 1200') # Ставим размер окна на полный экран


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

        self.win.setClearColor((0, 0, 0, 1)) # Закрашиваем поверхность чёрным. Дело в том, что по умолчанию в этом игровом движке поверхность закрашивается серым.

        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) # Загружаем модель мира в окно

        self.restartSound = loader.loadMusic('sound/restart.ogg') # Загрузим звук перезагрузки
        self.restartSound.setVolume(1) # Поставим громкость звука на 1.
        self.restartSound.setLoopCount(0) # Скорость проигрывания - 0.

        droidStartPos = (-1, 6, 1.5) # Загружаем стартовую позицию игрока в мире.
        enemyStartPos = (-6, 1, 0.5) # Загружаем позицию помощника-дроида.
        self.droid = Actor("models/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.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("s", 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("s-up", self.setKey, ["cam-right", False]) # При кнопке s+вперёд - поворачиваем камеру вправо и идём вперёд

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

        self.isMoving = False # Ставим значение isMoving на False(Вы можете менять это значение) чтобы игрок изначально стоял.

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

        self.sliderText = OnscreenText("Volume", pos=(-0.1, 0.87), scale=.07,
                                       fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1)) # Создаём текст Zvuk. Дело в том, что Panda3D не умеет писать текст по русски. 

        self.slider = DirectSlider(pos=(-0.1, 0, .75), scale=0.8, value=.50,
                                   command=self.setVolume) # Создаём слайдер звука

        # Создаём механику коллизий. Пока этот инструмент не работает. В дальнеййшим я реализую его по другому и колизии заработают.

        self.cTrav = CollisionTraverser()

        self.droidGroundRay = CollisionRay()
        self.droidGroundRay.setOrigin(0, 0, 9)
        self.droidGroundRay.setDirection(0, 0, -1)
        self.droidGroundCol = CollisionNode('DroidRay')
        self.droidGroundCol.addSolid(self.droidGroundRay)
        self.droidGroundCol.setFromCollideMask(CollideMask.bit(0))
        self.droidGroundCol.setIntoCollideMask(CollideMask.allOff())
        self.droidGroundColNp = self.droid.attachNewNode(self.droidGroundCol)
        self.droidGroundHandler = CollisionHandlerQueue()
        self.cTrav.addCollider(self.droidGroundColNp, self.droidGroundHandler)

        self.camGroundRay = CollisionRay()
        self.camGroundRay.setOrigin(0, 0, 9)
        self.camGroundRay.setDirection(0, 0, -1)
        self.camGroundCol = CollisionNode('camRay')
        self.camGroundCol.addSolid(self.camGroundRay)
        self.camGroundCol.setFromCollideMask(CollideMask.bit(0))
        self.camGroundCol.setIntoCollideMask(CollideMask.allOff())
        self.camGroundColNp = self.camera.attachNewNode(self.camGroundCol)
        self.camGroundHandler = CollisionHandlerQueue()
        self.cTrav.addCollider(self.camGroundColNp, self.camGroundHandler)

        self.cTrav.showCollisions(render)

        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor((.3, .3, .3, 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 setVolume(self):
        '''Сделаем функцию измены звука'''
        newVol = self.slider.guiItem.getValue()
        self.restartSound.setVolume(newVol)

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

    def move(self, task):

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

        dt = globalClock.getDt() - 0.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.restartSound.play() # Играем звук перезагрузки дроида. 
        if self.keyMap["right"]:
            self.restartSound.play() # Играем звук перезагрузки дроида.
            self.droid.setH(self.droid.getH() - 300 * dt)
        if self.keyMap["forward"]:
            self.droid.setY(self.droid, -25 * dt) 

        if self.keyMap["forward"] or self.keyMap["left"] or self.keyMap["right"]:
            if self.isMoving is False:
                self.droid.loop("run")
                self.isMoving = True
        else:
            if self.isMoving:
                self.droid.stop()
                self.droid.pose("walk", 5)
                self.isMoving = False

        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

        entries = list(self.camGroundHandler.getEntries())
        entries.sort(key=lambda x: x.getSurfacePoint(render).getZ())

        if len(entries) > 0 and entries[0].getIntoNode().getName() == "terrain":
            self.camera.setZ(entries[0].getSurfacePoint(render).getZ() + 1.0)
        if self.camera.getZ() < self.droid.getZ() + 2.0:
            self.camera.setZ(self.droid.getZ() + 2.0)

        entries = list(self.camGroundHandler.getEntries())
        entries.sort(key=lambda x: x.getSurfacePoint(render).getZ())

        if len(entries) > 0 and entries[0].getIntoNode().getName() == "terrain":
            self.camera.setZ(entries[0].getSurfacePoint(render).getZ() + 1.0)
        if self.camera.getZ() < self.droid.getZ() + 2.0:
            self.camera.setZ(self.droid.getZ() + 2.0)
            
        self.camera.lookAt(self.floater)


        return task.cont


demo = DroidShooter() # Cоздаём объект класса нашей игры
demo.run() # Запускаем

Screenshot :

23 January 2021, 10:22
Weapon Update.

Screenshot new weapon :


New ZIP-archive for weapon : DroidHGame.zip (1.0 MB)

Sorry, i deleted full screen mod :frowning: FPS in weak comuters = < 30 :frowning_face:

Hey, please comment this project. I very listen your ideas and wish.

3 Likes

Hi!

Download 1.0.8 version!!!

Structure :
in directory models :
world : (download panda3d art and added to world directory falcon.egg and his textures)
whishlyflash : whishlyflash.zip (105.2 KB)
weapon : weapon.zip (254.1 KB)
player : player.zip (714.1 KB)
enemies : enemies.zip (403.4 KB)

special_effects(add to main directory, no directory models) : special_effects.zip (6.1 KB)

code main.py :

#!/usr/bin/env python

# Импортируем все необходимые инструменты
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 direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectSlider import DirectSlider
from direct.actor.Actor import Actor
import random
import sys
import time
import os
import math

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

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

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

        self.win.setClearColor((0, 0, 0, 1)) # Закрашиваем поверхность чёрным. Дело в том, что по умолчанию в этом игровом движке поверхность закрашивается серым.

        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) -  загрузим файл дыма и переместим в конфиг чтобы именно эта анимация стала отображением дыма
        
        taskMgr.add(self.move, "moveTask") # Добавляем задачу в наш движок

        self.isMoving = False # Ставим значение isMoving на False(Вы можете менять это значение) чтобы игрок изначально стоял.
        # Делаем так, чтобы свет был изначально выключен.
        self.light = (0, 0, 0, 1)

        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=(-1, 0.8), scale=0.07, fg=(1, 1, 1, 1), align=TextNode.ALeft) # Напишем сообщение о состоянии корабля
                
        self.check_loss() # Проверяем поражение
        self.hide_weapon = False # Поставим, что оружие не убрано

        


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

        self.p.start(self.t)
        self.p.setPos(random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 5))

    def particle_start(self):
        self.state -= 1
        self.state_info.hide()
        self.state_info = OnscreenText(text=str(self.state), pos=(-1, 0.8), scale=0.07, fg=(1, 1, 1, 1), align=TextNode.ALeft) # Напишем сообщение о состоянии корабля
        self.check_loss()
        self.loadParticleConfig('special_effects/steam/steam.ptf')
        
    def black_light(self):
        # Выключеный свет
        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 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() # Выдаём скорость пули
        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='YOU LOSS', style=1, fg=(1,1,1,1),
                            pos=(-1.3, -1), align=TextNode.ALeft, scale=0.5)

    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

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

Added :
Hide the weapon, wishlyflash, hide wishlyflash,
steam effect, lifes the you machine, new light system
and more.

Screenshot :

Font update! 1.0.9 update!!!

Added new font to information of lifes the you machine.
Structure directories models and special_effects not change.

Added directory of fonts game.
download this directory : fonts.zip (5.9 KB)
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 direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectSlider import DirectSlider
from direct.actor.Actor import Actor
import random
import sys
import time
import os
import math

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

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

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

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

        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) -  загрузим файл дыма и переместим в конфиг чтобы именно эта анимация стала отображением дыма
        
        taskMgr.add(self.move, "moveTask") # Добавляем задачу в наш движок

        self.isMoving = False # Ставим значение isMoving на False(Вы можете менять это значение) чтобы игрок изначально стоял.
        # Делаем так, чтобы свет был изначально выключен.
        self.light = (0, 0, 0, 1)

        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 # Поставим, что оружие не убрано

        


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

        self.p.start(self.t)
        self.p.setPos(random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 5))

    def particle_start(self):
        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.check_loss()
        self.loadParticleConfig('special_effects/steam/steam.ptf')
        
    def black_light(self):
        # Выключеный свет
        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 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() # Выдаём скорость пули
        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)

    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

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

Screenshot :

1.0.9.1 - mini update. Bag fixes and hide the wishlyflash.
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 direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectSlider import DirectSlider
from direct.actor.Actor import Actor
import random
import sys
import time
import os
import math

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

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

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

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

        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 - уберём оружие.
        self.accept(";", self.flash_hide) # при кнопке ; - уберём фонарик

        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) -  загрузим файл дыма и переместим в конфиг чтобы именно эта анимация стала отображением дыма
        
        taskMgr.add(self.move, "moveTask") # Добавляем задачу в наш движок

        self.isMoving = False # Ставим значение isMoving на False(Вы можете менять это значение) чтобы игрок изначально стоял.
        # Делаем так, чтобы свет был изначально выключен.
        self.light = (0, 0, 0, 1)

        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.hide_flash = True # Поставим, что фонарик убран

        


    def loadParticleConfig(self, filename):
        # загрузить эффект пара
        self.p.cleanup()
        self.p = ParticleEffect()
        self.p.loadConfig(Filename(filename))

        self.p.start(self.t)
        self.p.setPos(random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 5))

    def particle_start(self):
        # запускаем пар с главными функциями.
        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.check_loss()
        self.loadParticleConfig('special_effects/steam/steam.ptf')
        
    def black_light(self):
        # Выключеный свет
        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 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.hide_flash = False # фонарик не убран
        self.weapon.hide() # убираем оружие
        
    def flash_hide(self):
        # Убрать фонарик
        self.hide_flash = True # фонарик убран
        self.hide_weapon = False # оружие не убрано
        self.flash.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() # Выдаём скорость пули
        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)

    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

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

1.0.9.2 - mini update. More bug fixes.
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 direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectSlider import DirectSlider
from direct.actor.Actor import Actor
import random
import sys
import time
import os
import math

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

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

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

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

        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) -  загрузим файл дыма и переместим в конфиг чтобы именно эта анимация стала отображением дыма
        
        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 # Поставим, что оружие не убрано

        


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

        self.p.start(self.t)
        self.p.setPos(random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 5))

    def particle_start(self):
        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.check_loss()
        self.loadParticleConfig('special_effects/steam/steam.ptf')
        
    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() # Выдаём скорость пули
        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)

    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

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

2.0.0!!! New update!! Add new fonts and bug fixes!
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 direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectSlider import DirectSlider
from direct.actor.Actor import Actor
import random
import sys
import time
import os
import math

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

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

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

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

        self.keyMap = {
            "left": 0, "right": 0, "forward": 0, "cam-left": 0, "cam-right": 0} # Создадим словарь кнопок, по их нажатию

        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.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) -  загрузим файл дыма и переместим в конфиг чтобы именно эта анимация стала отображением дыма
        
        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 # Поставим, что оружие не убрано

        


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

        self.p.start(self.t)
        self.p.setPos(random.randrange(0, 5), random.randrange(0, 5), random.randrange(0, 5))

    def particle_start(self):
        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.check_loss()
        self.loadParticleConfig('special_effects/steam/steam.ptf')
        
    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() # Выдаём скорость пули
        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)

    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апустим игру

Hi! I created particles update!! 2.0.1, we walk our border(2.0.0) !!!
Added to special_effects :
fountain : fountain.zip (982 Bytes)
steam_critic : steam_critic.zip (6.8 KB)
steam_critic+ : steam_critic+.zip (5.3 KB)

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.actor.Actor import Actor
import random
import sys
import time
import os
import math

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

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

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

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

        self.keyMap = {
            "left": 0, "right": 0, "forward": 0, "cam-left": 0, "cam-right": 0} # Создадим словарь кнопок, по их нажатию

        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.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 pick():  
       if base.mouseWatcherNode.hasMouse(): # делаем проверку в окне ли курсор(мышь).  
           mpos = base.mouseWatcherNode.getMouse() # получаем координаты курсора.  
           CurPickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY()) # уставливаем их лучу.  
           CurPicker.traverse(render) # запуск проверки коллизий(луча с объектами).  
           CurPickerOr.sortEntries() # сортируем что бы получить ближний объект.  
           if (CurPickerOr.getNumEntries() > 0): # проверяем есть ли пересечения луча с полигонами объекта.  
               info = CurPickerOr.getEntry(0) # получаем информацию по первому полигону.  
               print (info.getSurfacePoint(render))  

    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() # Выдаём скорость пули
        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)

    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! 2.0.2 update - add sound of crack the machine!
Add ./sounds directory : sounds.zip (136.2 KB)

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.actor.Actor import Actor
import random
import sys
import time
import os
import math

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

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

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

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

        self.crackSound = loader.loadMusic('./sounds/crack.wav')
        self.crackSound.setVolume(1)
        

        self.keyMap = {
            "left": 0, "right": 0, "forward": 0, "cam-left": 0, "cam-right": 0} # Создадим словарь кнопок, по их нажатию

        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.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 pick():  
       if base.mouseWatcherNode.hasMouse(): # делаем проверку в окне ли курсор(мышь).  
           mpos = base.mouseWatcherNode.getMouse() # получаем координаты курсора.  
           CurPickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY()) # уставливаем их лучу.  
           CurPicker.traverse(render) # запуск проверки коллизий(луча с объектами).  
           CurPickerOr.sortEntries() # сортируем что бы получить ближний объект.  
           if (CurPickerOr.getNumEntries() > 0): # проверяем есть ли пересечения луча с полигонами объекта.  
               info = CurPickerOr.getEntry(0) # получаем информацию по первому полигону.  
               print (info.getSurfacePoint(render))  

    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() # Выдаём скорость пули
        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апустим игру

Hey, please comment this project. I very listen your ideas and wish.

Hi!
2.0.3 update! - menu update!!!

Screenshot :

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.3',
            'full-screen false']

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

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

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

        self.crackSound = loader.loadMusic('./sounds/crack.wav')
        self.crackSound.setVolume(1)

        self.button_start = DirectButton(pos=(0.7, 0, 0), text="START",
                                   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]: пожаротушительная система")
        
        
    def load_game(self):
        self.button_start.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 pick():  
       if base.mouseWatcherNode.hasMouse(): # делаем проверку в окне ли курсор(мышь).  
           mpos = base.mouseWatcherNode.getMouse() # получаем координаты курсора.  
           CurPickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY()) # уставливаем их лучу.  
           CurPicker.traverse(render) # запуск проверки коллизий(луча с объектами).  
           CurPickerOr.sortEntries() # сортируем что бы получить ближний объект.  
           if (CurPickerOr.getNumEntries() > 0): # проверяем есть ли пересечения луча с полигонами объекта.  
               info = CurPickerOr.getEntry(0) # получаем информацию по первому полигону.  
               print (info.getSurfacePoint(render))  

    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 : # если оружие убрано то мы не можем стрелять
            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апустим игру

And bux fix the shot!!

Hi, 2.0.3.1 update! More bug fixes!!!

Python 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.3.1',
            'full-screen false']

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

# Создадим главный класс нашей игры
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')
        self.inst_font = loader.loadFont('./fonts/arial.ttf')

        self.crackSound = loader.loadMusic('./sounds/crack.wav')
        self.crackSound.setVolume(1)

        self.button_start = DirectButton(pos=(0.7, 0, 0), text="START",
                                   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.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 pick():  
       if base.mouseWatcherNode.hasMouse(): # делаем проверку в окне ли курсор(мышь).  
           mpos = base.mouseWatcherNode.getMouse() # получаем координаты курсора.  
           CurPickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY()) # уставливаем их лучу.  
           CurPicker.traverse(render) # запуск проверки коллизий(луча с объектами).  
           CurPickerOr.sortEntries() # сортируем что бы получить ближний объект.  
           if (CurPickerOr.getNumEntries() > 0): # проверяем есть ли пересечения луча с полигонами объекта.  
               info = CurPickerOr.getEntry(0) # получаем информацию по первому полигону.  
               print (info.getSurfacePoint(render))  

    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 : # если оружие убрано то мы не можем стрелять
            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апустим игру

Hi! 2.0.3.2 update! Bug fix deleted text of movement.
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.3.2',
            'full-screen false']

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

# Создадим главный класс нашей игры
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.crackSound.setVolume(1) # полная громкость звука

        self.button_start = DirectButton(pos=(0.7, 0, 0), text="START",
                                   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 pick():  
       if base.mouseWatcherNode.hasMouse(): # делаем проверку в окне ли курсор(мышь).  
           mpos = base.mouseWatcherNode.getMouse() # получаем координаты курсора.  
           CurPickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY()) # уставливаем их лучу.  
           CurPicker.traverse(render) # запуск проверки коллизий(луча с объектами).  
           CurPickerOr.sortEntries() # сортируем что бы получить ближний объект.  
           if (CurPickerOr.getNumEntries() > 0): # проверяем есть ли пересечения луча с полигонами объекта.  
               info = CurPickerOr.getEntry(0) # получаем информацию по первому полигону.  
               print (info.getSurfacePoint(render))  

    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 : # если оружие убрано то мы не можем стрелять
            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апустим игру

Where do I get panda3d art?

@panda3dmastercoder, this instruction for download falcon map :

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

Hi! I created 2.0.3.3 update! Add shot sound!
Directory sounds : sounds.zip (176.8 KB)
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.3.3',
            'full-screen false']

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

# Создадим главный класс нашей игры
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="START",
                                   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апустим игру

Thank you for download my project :slightly_smiling_face:

English:
Thanks a lot! I looked at the screenshots and loved your game. Now I can play it
Русский:
Большое спасибо! Я посмотрел скриншоты и мне очень понравилась ваша игра.

Русский :
Спасибо вам большое! Буду дорабатывать свою игру. Не хотите присоеденится ко мне?
Тогда мы будем вместе разрабатывать эту игру :slight_smile:

Ладно! Чем могу помочь вам в игре?

Добавлением звуков, текстур. Можете даже создавать разные модели, всё приветсвуется :slight_smile: