【问题标题】:Kivy performance degradation over timeKivy 性能随时间下降
【发布时间】:2018-08-20 10:05:06
【问题描述】:

我正在学习 Kivy,为了让它更有趣,我正在创建一个小型 2D 游戏。目前它只是一个可以用 WASD 控制并用 o 发射弹丸的坦克。

问题在于 FPS 会随着时间的推移而降低。即使我在游戏中什么都不做也会发生这种情况。我没有 FPS 计数器,但在一分钟的游戏时间后,它大约是 FPS 的一半。

我的感觉是问题出在小部件中画布的更新中。由于即使玩家在整个游戏中什么都不做也会发生减速,因此似乎某处有数据只是添加和添加的。我不知道如何更好地解释它,除了它很奇怪......


到目前为止游戏编程方式的简要概述:

主要的小部件是 Game 类。它检测按键并运行“Clock.schedule_interval-function”。

Tank 小部件是 Game 的子部件。它保存一些数据并通过 Kivys Image 小部件加载船体和炮塔精灵,该小部件成为它的孩子。它有自己的更新功能,可以更新与坦克相关的所有内容,包括设置其画布的位置以及旋转船体和炮塔图像画布。 Tank 小部件类中的更新函数由 Game 类中的“Clock.schedule_interval”调用。

Shots 小部件与 Tank 小部件的功能相同,只是它保存每次射击的数据

“Clock schedule_interval”-函数保存每个镜头部件的列表,并在它们离开屏幕时将其删除。但是,即使没有射击,减速问题仍然存在。


我附上了完整的代码。这可能是过度的,但我不知道其中哪一部分会导致减速。如果要运行游戏,只需将这四个 python 文件放在同一个文件夹中,并将图像放在一个名为“images tank”的子文件夹中。

希望有人能看一下:)

main.py:

    #Import my own modules:
import tank
import shot
from stats import Stats
#Import kivy:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.clock import Clock

#Set window size properties:
from kivy.config import Config
Config.set('graphics','resizable',0)
from kivy.core.window import Window


class Game(Widget):
    def __init__(self):
        #General settings:
        super(Game, self).__init__()
        Clock.schedule_interval(self.update, 1.0/60.0)
        
        #Add keyboard: 
        self._keyboard = Window.request_keyboard (callback=None, target=self, input_type="text")
        #Bind the keyboard to a function:
        self._keyboard.bind(on_key_down=self.keypress)
        self._keyboard.bind(on_key_up=self.keyUp)
        
        #P1 tank starting values:
        self.keypress_p1 = {"forward":False, "backward":False, "left":False, "right":False, "turret_left":False, "turret_right":False, "fire":False}
        
        #P1 tank widget:
        self.tank_p1 = tank.Tank()
        self.add_widget(self.tank_p1)
        
        #P1 shots list:
        self.shotsList_p1 = []
        
        
    #Keyboard press detection:    
    def keypress(self, *args):
        key = args[2]
        if key == "w":
            self.keypress_p1["forward"]=True
        if key == "s":
            self.keypress_p1["backward"]=True
        if key == "a":
            self.keypress_p1["left"]=True
        if key == "d":
            self.keypress_p1["right"]=True
        if key == "q":
            self.keypress_p1["turret_left"]=True
        if key == "e":
            self.keypress_p1["turret_right"]=True
        if key == "o":
            self.keypress_p1["fire"]=True
            
    #Keyboard button up detection:    
    def keyUp(self, *args):
        key = args[1][1]
        if key == "w":
            self.keypress_p1["forward"]=False
        if key == "s":
            self.keypress_p1["backward"]=False
        if key == "a":
            self.keypress_p1["left"]=False
        if key == "d":
            self.keypress_p1["right"]=False
        if key == "q":
            self.keypress_p1["turret_left"]=False
        if key == "e":
            self.keypress_p1["turret_right"]=False
        if key == "o":
            self.keypress_p1["fire"]=False
            
            
    #Parent update function that the clock runs:   
    def update(self, dt):
    
        #Add new shots:
        if self.keypress_p1["fire"]:
            self.shot = shot.Shots(self.tank_p1.my_pos, self.tank_p1.my_angle+self.tank_p1.my_turretAngle)
            self.shotsList_p1.append(self.shot)
            self.add_widget(self.shot)
            self.keypress_p1["fire"] = False
            
        #P1 tank update:
        self.tank_p1.update(self.keypress_p1)
        
        #P1 shot update:
        for i in range(len(self.shotsList_p1)-1,-1,-1):
            self.shotsList_p1[i].update()
            #Remove widgets that are outside the screen:
            if ( 0<=self.shotsList_p1[i].my_pos[0]<Stats.winSize[0] and 0<=self.shotsList_p1[i].my_pos[1]<Stats.winSize[1] )==False:
                self.remove_widget(self.shotsList_p1[i])
                del self.shotsList_p1[i]
            

class MyApp(App):
    def build(self):
        game = Game()
        Window.size = Stats.winSize
        return game
        
MyApp().run()

坦克.py:

    #Import own modules:
from stats import Stats
#import python:
import math
#Import Kivy:
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.graphics.context_instructions import PushMatrix, PopMatrix, Rotate, Translate, MatrixInstruction
from kivy.graphics.fbo import Fbo


class Tank(Widget):
    def __init__(self):
        super(Tank, self).__init__()
        
        #Position and rotation values for the tank:
        self.my_pos = [0,0]
        self.posChange = [0,0]
        self.my_angle = 0
        self.angleChange = 0
        self.my_turretAngle = 0
        self.turretAngleChange = 0
        
        #Hull widget:
        self.hull = Hull()
        self.add_widget(self.hull)
        self.hull.center_x = self.my_pos[0]
        self.hull.center_y = self.my_pos[1]
        
        #Turret widget:
        self.turret = Turret()
        self.add_widget(self.turret)
        self.turret.center_x = self.my_pos[0]
        self.turret.center_y = self.my_pos[1]
        
    def update(self, keypress):
        if keypress["forward"]:
            self.my_pos[0] -= Stats.hull_t1["forward"]*math.sin(math.radians(self.my_angle))
            self.my_pos[1] += Stats.hull_t1["forward"]*math.cos(math.radians(self.my_angle))
            self.posChange[0] = -Stats.hull_t1["forward"]*math.sin(math.radians(self.my_angle))
            self.posChange[1] = Stats.hull_t1["forward"]*math.cos(math.radians(self.my_angle))
            
        if keypress["backward"]:
            self.my_pos[0] -= Stats.hull_t1["backward"]*math.sin(math.radians(self.my_angle))
            self.my_pos[1] += Stats.hull_t1["backward"]*math.cos(math.radians(self.my_angle))
            self.posChange[0] = -Stats.hull_t1["backward"]*math.sin(math.radians(self.my_angle))
            self.posChange[1] = Stats.hull_t1["backward"]*math.cos(math.radians(self.my_angle))
             
        if keypress["left"]:
            self.my_angle += Stats.hull_t1["left"]
            self.angleChange = Stats.hull_t1["left"]
            
        if keypress["right"]:
            self.my_angle += Stats.hull_t1["right"]
            self.angleChange = Stats.hull_t1["right"]
            
        if keypress["turret_left"]:
            self.my_turretAngle += Stats.turret_t1["left"]
            self.turretAngleChange = Stats.turret_t1["left"]
            
        if keypress["turret_right"]:
            self.my_turretAngle += Stats.turret_t1["right"]
            self.turretAngleChange = Stats.turret_t1["right"]
            
        
            
        #Tank Position:
        with self.canvas.before:
            PushMatrix()
            Translate(self.posChange[0], self.posChange[1])
        with self.canvas.after:
            PopMatrix()
            
        #Rotate hull image:
        with self.hull.canvas.before:
            PushMatrix()
            self.rot = Rotate()
            self.rot.axis = (0,0,1)
            self.rot.origin = self.hull.center
            self.rot.angle = self.angleChange
        with self.hull.canvas.after:
            PopMatrix()
            
        #Rotate turret image:
        with self.turret.canvas.before:
            PushMatrix()
            self.rot = Rotate()
            self.rot.axis = (0,0,1)
            self.rot.origin = self.turret.center
            self.rot.angle = self.turretAngleChange + self.angleChange
        with self.turret.canvas.after:
            PopMatrix()
        
        #Reset pos, angle and turretAngle change values:
        self.posChange = [0,0]
        self.angleChange = 0
        self.turretAngleChange = 0
        
        
#--------------------------------------------------------------------------------------------------            
class Hull(Image):
    def __init__(self):
        super(Hull, self).__init__(source="images tank/Tank.png")
        self.size = self.texture_size
        
class Turret(Image):
    def __init__(self):
        super(Turret, self).__init__(source="images tank/GunTurret.png")
        self.size = self.texture_size
        

shot.py:

    #Import own modules:
from stats import Stats
#import python:
import math
from copy import copy
#Import Kivy:
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.graphics.context_instructions import PushMatrix, PopMatrix, Rotate, Translate, MatrixInstruction
from kivy.graphics.fbo import Fbo

class Shots(Widget):
    
    def __init__(self, tankPos, turretAngle):
        super(Shots, self).__init__()
        #Shot data:
        self.my_pos = copy(tankPos)
        self.my_angle = turretAngle
        self.angleChange = self.my_angle
        self.posChange = [ -Stats.shot_t1["speed"]*math.sin(math.radians(self.my_angle)), Stats.shot_t1["speed"]*math.cos(math.radians(self.my_angle)) ]
        #Add image:
        self.shotImg = ShotImg()
        self.add_widget(self.shotImg)
        self.shotImg.pos = self.my_pos
        self.shotImg.center_x = self.my_pos[0]
        self.shotImg.center_y = self.my_pos[1]
        
        
        
    def update(self):
        self.my_pos[0] += self.posChange[0]
        self.my_pos[1] += self.posChange[1]
        #Shot Position:
        with self.canvas.before:
            PushMatrix()
            Translate(self.posChange[0], self.posChange[1])
        with self.canvas.after:
            PopMatrix()
        
        #Rotate shot image:
        if self.angleChange != 0:
            with self.shotImg.canvas.before:
                PushMatrix()
                self.rot = Rotate()
                self.rot.axis = (0,0,1)
                self.rot.origin = self.shotImg.center
                self.rot.angle = self.angleChange
            with self.shotImg.canvas.after:
                PopMatrix()
            self.angleChange = 0
            
    
class ShotImg(Image):
    def __init__(self):
        super(ShotImg, self).__init__(source="images tank/Bullet.png")
        self.size = self.texture_size
        

stats.py:

    class Stats:
    winSize = (800,800)
    hull_t1 = {"forward":2, "backward":-2, "left":2, "right": -2}
    turret_t1 = {"left":5, "right":-5}
    shot_t1 = {"speed":3}

图片应该放在名为“images tank”的子文件夹中:

【问题讨论】:

    标签: python python-3.x frameworks kivy kivy-language


    【解决方案1】:

    您管理位置的方式将为每个坦克创建 8 条新指令,每发 6 条,每帧,在 60 fps 下,这将快速创建数千条指令,而 kivy 处理它们的速度会越来越慢。

        #Tank Position:
        with self.canvas.before:
            PushMatrix()
            Translate(self.posChange[0], self.posChange[1])
        with self.canvas.after:
            PopMatrix()
    

    您不想这样做,您想在您的小部件中创建一个翻译指令(旋转指令也是如此)并更新它,将此块移动到 __init__ 并将翻译保存到 self.translate 例如,然后在更新中,而不是使用 posChange,只需执行 self.translate.x, self.translate.y = self.my_pos

    对旋转和投篮应用相同的逻辑,随着时间的推移,性能应该会更加稳定。

    【讨论】:

    • 我明白了。不幸的是,接下来的几天我不在电脑前,但我回来后会试试这个。
    【解决方案2】:

    我明白了。不幸的是,接下来的几天我不在我的电脑前,但我回来后会尝试你的解决方案:)

    我觉得 Kivy 的行为真的很奇怪。在同一个小部件中设置位置和旋转也存在问题。我所做的旋转似乎对小部件有永久影响,这意味着当我尝试在当前更新轮次中设置位置时,坐标系已经从上次更新开始旋转。即使我在平移和旋转之前和之后都使用了 Push- 和 PopMatrix() ,这种情况也发生了。 我通过在单独的子小部件中进行旋转解决了这个问题,但我发现 kivys 的行为有些不合逻辑,或者至少难以预测......

    是否有任何关于坐标系的手册或教程以及它在某处的工作原理?我已经尝试过文档,但它无法以连贯的方式解释它是如何工作的......

    【讨论】:

    • 我向你保证这是合乎逻辑的,但我可以理解如果对它的工作原理感到困惑,很难预测。所有小部件都有它们的画布,它只是一个指令列表,为了绘制小部件,kivy 只是按照包含它们的小部件的顺序遍历完整的指令列表。任何改变矩阵的小部件都必须保存矩阵,并在它不想影响它的任何小部件被绘制之前重置它(孩子,兄弟姐妹或更远相关的小部件,碰巧在之后绘制> it),如果是孩子,则绘制顺序简单地遵循树顺序。
    猜你喜欢
    • 1970-01-01
    • 2021-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-25
    • 2017-09-14
    • 1970-01-01
    相关资源
    最近更新 更多