add_widget()只能用于Widget类

时间:2015-07-10 16:54:36

标签: kivy

我不知道为什么会收到此错误...

我有这个类HungerBar在画布上显示一个矩形。我希望它能在游戏开始时出现。所以我把它放在这个程序启动时调用的更新函数中。这是代码的一部分。

class HungerBar(Rectangle):
    def hunger_dec(self):
        if (self.size[0] > 0):
            self.size = Vector(-1, 0) + self.size
    def hunger_inc(self, increase):
        if (self.size[0] + increase < 100):
            self.size = Vector(increase, 0) + self.size

class ShootingGame(Widget):
    hungerBar = HungerBar()

    def drawBar(self):
        self.hungerBar.size = Vector(1000, 20)
        self.add_widget(self.hungerBar)

    def update(self, dt):
        self.drawBar()

class GameScreen(Screen):
    def __init__(self, **kwargs):
        super(GameScreen, self).__init__(**kwargs)
        self.game = ShootingGame()
        self.add_widget(self.game)
        Clock.schedule_interval(self.game.update, 1.0 / 60.0)

最后,GameScreen会添加到App。

这就是在.kv文件中定义HungerBar的方式

<HungerBar>:
    Color: 
        rgb: (0, 1, 1)
    canvas:
        Rectangle:
            pos: self.width * 7, 30
            size: self.size

当我运行此代码时,这是我得到的错误:

Traceback (most recent call last):
   File "main.py", line 323, in <module>
     ShootingApp().run()
   File "/Applications/Kivy.app/Contents/Resources/kivy/kivy/app.py", line 824, in run
     runTouchApp()
   File "/Applications/Kivy.app/Contents/Resources/kivy/kivy/base.py", line 487, in runTouchApp
     EventLoop.window.mainloop()
   File "/Applications/Kivy.app/Contents/Resources/kivy/kivy/core/window/window_sdl2.py", line 539, in mainloop
     self._mainloop()
   File "/Applications/Kivy.app/Contents/Resources/kivy/kivy/core/window/window_sdl2.py", line 300, in _mainloop
     EventLoop.idle()
   File "/Applications/Kivy.app/Contents/Resources/kivy/kivy/base.py", line 330, in idle
     self.dispatch_input()
   File "/Applications/Kivy.app/Contents/Resources/kivy/kivy/base.py", line 315, in dispatch_input
     post_dispatch_input(*pop(0))
   File "/Applications/Kivy.app/Contents/Resources/kivy/kivy/base.py", line 221, in post_dispatch_input
     listener.dispatch('on_motion', etype, me)
   File "kivy/_event.pyx", line 699, in kivy._event.EventDispatcher.dispatch (kivy/_event.c:7011)
   File "/Applications/Kivy.app/Contents/Resources/kivy/kivy/core/window/__init__.py", line 904, in on_motion
     self.dispatch('on_touch_down', me)
   File "kivy/_event.pyx", line 699, in kivy._event.EventDispatcher.dispatch (kivy/_event.c:7011)
   File "/Applications/Kivy.app/Contents/Resources/kivy/kivy/core/window/__init__.py", line 920, in on_touch_down
     if w.dispatch('on_touch_down', touch):
   File "kivy/_event.pyx", line 699, in kivy._event.EventDispatcher.dispatch (kivy/_event.c:7011)
   File "/Applications/Kivy.app/Contents/Resources/kivy/kivy/uix/screenmanager.py", line 1069, in on_touch_down
     return super(ScreenManager, self).on_touch_down(touch)
   File "/Applications/Kivy.app/Contents/Resources/kivy/kivy/uix/widget.py", line 382, in on_touch_down
     if child.dispatch('on_touch_down', touch):
   File "kivy/_event.pyx", line 699, in kivy._event.EventDispatcher.dispatch (kivy/_event.c:7011)
   File "/Applications/Kivy.app/Contents/Resources/kivy/kivy/uix/relativelayout.py", line 276, in on_touch_down
     ret = super(RelativeLayout, self).on_touch_down(touch)
   File "/Applications/Kivy.app/Contents/Resources/kivy/kivy/uix/widget.py", line 382, in on_touch_down
     if child.dispatch('on_touch_down', touch):
   File "kivy/_event.pyx", line 699, in kivy._event.EventDispatcher.dispatch (kivy/_event.c:7011)
   File "main.py", line 94, in on_touch_down
     self.drawBar()
   File "main.py", line 173, in drawBar
     self.add_widget(self.hungerBar)
   File "/Applications/Kivy.app/Contents/Resources/kivy/kivy/uix/widget.py", line 442, in add_widget
     'add_widget() can be used only with instances'
 kivy.uix.widget.WidgetException: add_widget() can be used only with instances of the Widget class.

提前谢谢你的帮助! :)

这是完整的代码。对不起,它真的很长很乱。

import kivy
__version__ = "1.9.0"

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty, ObjectProperty, StringProperty
from kivy.uix.relativelayout import RelativeLayout
from kivy.animation import Animation
from kivy.uix.label import Label
from kivy.vector import Vector
from kivy.clock import Clock
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.graphics import Line, Color, Rectangle
from functools import partial
import socket, time, math, random, math
from random import randint
from kivy.lang import Builder

Builder.load_file('shooting.kv')

#setup graphics
from kivy.config import Config
Config.set('graphics','resizable',0)

#Graphics fix
from kivy.core.window import Window;
Window.clearcolor = (1,1,1,1)

UDP_IP = "10.0.1.5"
UDP_PORT = 5005
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

class Missile(Widget):
    angle = NumericProperty(0)
    source = StringProperty("")
    def expend_size(self):
        if (self.size[0] < 200):
            self.size = Vector(3, 3) + self.size
    def cont_travel(self, velocity):
        self.pos = Vector(*velocity) + self.pos
    def rotate(self, a):
        self.angle = a

class Enemy(Widget):
    velocity_x = NumericProperty(0)
    velocity_y = NumericProperty(0)
    spawnT = NumericProperty(0)
    angle = NumericProperty(0)
    source = StringProperty("")
    def move(self):
        self.x = self.x + self.velocity_x
        self.y = self.y + self.velocity_y
    def drawWalking(self, *large):
        with self.canvas:
            self.source = './images/walk1.png'
            def changeWalk(rect, newSource, *largs):
                rect.source = newSource
            Clock.schedule_once(partial(changeWalk, self, './images/walk2.png'),0.5)

class HungerBar(Widget):
    def hunger_dec(self):
        if (self.size[0] > 0):
            self.size = Vector(-1, 0) + self.size
    def hunger_inc(self, increase):
        if (self.size[0] + increase < 100):
            self.size = Vector(increase, 0) + self.size

class Score(Label):
    def show_score(self, s):
        self.text = str(s)

class TestCircle(Widget):
    def move(self, dt):
        self.x = self.x + self.velocity_x

class ShootingGame(Widget):
    missile = Missile()
    hungerBar = ObjectProperty(None)
    enemy = Enemy()
    enemy_list = []
    enemy_count = 0
    enemy_amount = 1
    # this indicates if the frog is traveling or not
    travel = False
    pos_down = Vector(0, 0)
    pos_up = Vector(0, 0)
    time_down = 0
    time_up = 0
    enemy_on_left = True
    missile_onscreen = False
    vel = Vector(0, 0)
    points = NumericProperty(0)
    # this is the timer that controls if the bug stops or moves
    movement_timer = 5
    # if the bug is traveling or not
    movement = True

    def __init__(self, **kwargs):
        super(ShootingGame, self).__init__(**kwargs)
        randX = random.choice([0.1, 0.2, 0.8, 0.9])
        self.enemy = self.drawEnemy(randX)
        Clock.schedule_interval(self.enemy.drawWalking, 1)

        # self.hungerBar = self.drawBar()

    # The following functions let users to shoot missiles by touching the screen
    def on_touch_down(self, touch):
        # only draws a new object if there is none on the screen. Also checks for side of the bug
        if ((not self.missile_onscreen) and 
            ((self.enemy_on_left and touch.x > (self.parent.width/2)) or
            ((not self.enemy_on_left) and touch.x < self.parent.width/2))):
            # generate new line
            with self.canvas:
                Color(1, 0, 0)
                self.line = Line(points=(touch.x, touch.y, touch.x, touch.y), width = 2)
            # generate a new missile
            self.missile_onscreen = True
            self.missile = Missile()
            self.add_widget(self.missile)
            self.missile.pos = (touch.x - self.missile.size[0]/2 , touch.y - self.missile.size[0]/2)
            self.pos_down = Vector(touch.x, touch.y)
            self.time_down = time.time()
            self.missile.source = 'atlas://images/sprite.atlas/frog'
            # rotate sprite
            # left
        if (touch.x < self.parent.width/2):
                self.missile.angle = -45
                send_server(40002, 0.29, 400, 200)
            # right
        else:
                self.missile.angle = 45
                send_server(50002, 0.29, 400, 200)

    def on_touch_move(self, touch):
        if (self.missile_onscreen):
            self.time_down = time.time()
            with self.canvas:
                self.line.points = [touch.x, touch.y, self.pos_down.x, self.pos_down.y]

            if (touch.x - self.pos_down[0] != 0):
                a = math.degrees(math.atan((-(touch.y - self.pos_down[1]))/(-(touch.x - self.pos_down[0]))))
                # left
                if (self.pos_down[0] < self.parent.width/2) and (touch.x - self.pos_down[0] < 0):
                    self.missile.angle = a/2 - 45
                # right
                elif (self.pos_down[0] > self.parent.width/2) and (touch.x - self.pos_down[0] > 0): 
                    self.missile.angle = a/2 + 45

    def on_touch_up(self, touch):
        # only gives speed and orientation if there is no object traveling already
        if (self.missile_onscreen and (self.vel == Vector(0, 0))):
            self.time_up = time.time()
            self.pos_up = Vector(touch.x, touch.y)
            self.missile.source = 'atlas://images/sprite.atlas/frog_jump'

            # remove line
            self.canvas.remove(self.line)
            self.vel = Vector(-(self.pos_up[0] - self.pos_down[0])/10, -(self.pos_up[1] - self.pos_down[1])/10)
            # frog traveling (with speed threshold)
            if (abs(self.vel[0]) > 5 ) and (self.vel[1] != 0):
                print("x vel: "+str(self.vel[0]))
                print("y vel: "+str(self.vel[1]))
                # left
                if (self.vel[0] > 0 and self.pos_down[0] < (self.parent.width / 3)):
                    self.travel = True
                    (intensity, duration) = get_haptic_par(self.missile.size[0], self.vel, self.parent.width - self.pos_up[0])
                # right
                    send_server(40001, intensity, duration, 200)
                elif (self.vel[0] < 0 and self.pos_down[0] > (self.parent.width * 2 / 3)):
                    self.travel = True
                    (intensity, duration) = get_haptic_par(self.missile.size[0], self.vel, self.pos_up[0])
                    send_server(50001, intensity, duration, 200)
                else:
                    self.remove_widget(self.missile)
                    send_server(40003, 0, 0, 200)
                    send_server(50003, 0, 0, 200)
            else:
                self.remove_widget(self.missile)
                self.missile_onscreen = False
                self.vel = Vector(0, 0)
                send_server(40003, 0, 0, 200)
                send_server(50003, 0, 0, 200)


    def drawBar(self):
        bar = HungerBar()
        # self.hungerBar.size = Vector(1000, 20)
        self.add_widget(bar)
        return bar


    # Randomly generating an enemy
    def drawEnemy(self, x_pos):
        tmpEnemy = Enemy()
        tmpEnemy.x = self.width * x_pos

        # 1280 is the width of the screen
        if (tmpEnemy.x < 1280/2):
            self.enemy_on_left = True
        else:
            self.enemy_on_left = False

        randPos = randint(10, 90)
        # 1200 is the width of the screen
        tmpEnemy.y = float(randPos) /100 * 800

        tmpEnemy.velocity_x = 0
        tmpEnemy.velocity_y = randint(3, 5)
        tmpEnemy.spawnT = time.time()

        self.add_widget(tmpEnemy)
        return tmpEnemy

    def update(self, dt):
        # self.drawBar()
        # Missile travels is users flicks the missile
        if (self.travel):
            self.missile.cont_travel(self.vel)
            if ((self.missile.right < 0) or (self.missile.x > self.parent.width) or
                (self.missile.top < 0) or (self.missile.y > self.parent.height)):
                self.travel = False
                self.missile_onscreen = False
                self.vel = Vector(0, 0)

        # controls the timing of the enemy movoing
        # print(time.time() % self.movement_timer)
        if ((time.time() % self.movement_timer) < 0.015):
            self.movement = not (self.movement)
            self.movement_timer = random.randint(3, 5)

        if (self.movement):
            self.enemy.move()
        # Animate the bug turning around
        if (self.enemy.top > self.parent.height):
            angle = -90
            Animation(center=self.enemy.center, angle=angle, duration = 0.5).start(self.enemy)
            self.enemy.velocity_y = -(self.enemy.velocity_y)
        elif (self.enemy.y < 0):
            angle = 90
            Animation(center=self.enemy.center, angle=angle, duration = 0.5).start(self.enemy)
            self.enemy.velocity_y = -(self.enemy.velocity_y)
        # when frog catches the bug
        if (self.missile and self.enemy.collide_widget(self.missile) and self.missile_onscreen):
            #calculate score depending on how long it takes the player to hit the enemy
            round_score = int(1.0/(time.time() - self.enemy.spawnT)/self.missile.size[0] * 1000)
            if round_score > 30:
                round_score = 30
            elif round_score < 1:
                round_score = 1
            if (self.enemy.x < self.parent.width / 2):
                send_server(60001, 0, 0, 200)
            if (self.enemy.x > self.parent.width / 2):
                send_server(60002, 0, 0, 200)
            # self.points  = self.points + round_score
            score = Score()
            score.pos = Vector(self.enemy.x, self.enemy.y + 5)
            score.show_score("[color=ff3333]" + str(round_score) + "[/color]")
            self.add_widget(score)
            Clock.schedule_once(lambda dt: self.remove_widget(score), 1)
            #removing missile as well - removed bug: frog gets stuck on the edge of the screen
            self.remove_widget(self.missile)
            self.missile_onscreen = False
            self.vel = Vector(0, 0)
            # self.enemy_list.remove(e)

def get_haptic_par(size, vel, canvasWidth):
    intensity = float(size)/300 * 0.29
    vis_dur_tot = canvasWidth/(abs(vel[0]) * 60) * 1000
    hap_dur_tot = 0.69 * vis_dur_tot + 137.02
    #hap_dur_tot = dur + soa = dur + 0.28 * dur + 60.7 => dur = (hap_dur_tot - 60.7)/1.28
    return (intensity, (hap_dur_tot - 60.7) /1.28)

def send_server(*args):
    port = args[0]
    intensity = args[1] 
    duration = args[2]
    frequency = args[3]

    MESSAGE = "%d;%f;%f;%f" % (port, intensity, duration, frequency)
    sock.sendto(MESSAGE, (UDP_IP, UDP_PORT))

class WelcomeScreen(Screen):
    pass

class BasicScreen1(Screen):
    pass

class BasicScreen2(Screen):
    pass

class BasicScreen3(Screen):
    pass

class GameScreen(Screen):
    def __init__(self, **kwargs):
        super(GameScreen, self).__init__(**kwargs)
        self.game = ShootingGame()
        self.add_widget(self.game)
        Clock.schedule_interval(self.game.update, 1.0 / 60.0)

sm = ScreenManager()
sm.add_widget(WelcomeScreen(name='welcome'))
sm.add_widget(BasicScreen1(name='basic1'))
sm.add_widget(BasicScreen2(name='basic2'))
basicscreen3 = BasicScreen3(name='basic3')
sm.add_widget(basicscreen3)
game_screen = GameScreen(name='game')
sm.add_widget(game_screen)

class ShootingApp(App):
    def printThis(self, x):
        print(x)

    def play_haptic(self, intensity, duration):
        send_server(40001, intensity, duration, 70)

    def play_ball(self, intensity, duration):
        ball = TestCircle()
        # i = 0.0106 * math.pow(10, (intensity - 38.892)/9.7721)
        # ball.size = Vector(50, 50)
        ball.pos = Vector(0,405)
        basicscreen3.add_widget(ball)
        lengthT = duration + 0.4 * duration + 0.28 * duration + 60.7
        ball.velocity_x = ball.parent.width/lengthT * 20
        Clock.schedule_interval(ball.move, 1.0/60.0)
        if (ball.x > ball.parent.width):
            basicscreen3.remove_widget(ball)


    def build(self):
        return sm

if __name__ == '__main__':  
    ShootingApp().run()

kivy文件:

#:kivy 1.9.0
#:import atlas kivy.atlas.Atlas
#:import NoTransition kivy.uix.screenmanager.NoTransition
#:import SlideTransition kivy.uix.screenmanager.SlideTransition


<WelcomeScreen>:
    Button:
        text: "Learn about tactile illusions"
        size_hint: None, None
        size: 500, 70
        pos: 100, 200
        font_size: 30
        on_release: 
            app.root.transition = SlideTransition()
            app.root.current = "basic1"

    Button:
        text: "Play our game"
        size_hint: None, None
        size: 500, 70
        pos: 100, 100
        font_size: 30
        on_release: 
            app.root.transition = SlideTransition()
            app.root.current = "game"

<BasicScreen1>:
    name: "basic1"
    Label:
        text: "When two hands are vibrated with a time lapse in the middle, \nyou would feel a continuous motion across the hands." 
        font_size: 30
        color: .8,.9,0,1
    Button:
        text: "Play"
        size_hint: None, None
        size: 140, 70
        pos: 1000, 200
        font_size: 30
        on_release:
            app.play_haptic(0.5, 400)

    Button:
        text: "Next"
        size_hint: None, None
        size: 140, 70
        pos: 1000, 120
        font_size: 30
        on_release: 
            app.root.transition = SlideTransition()
            app.root.current = "basic2"

<BasicScreen2>:
    name: "basic2"
    Label:
        text: "You can change the speed of the motion across hands. \nBut regardless of speed, the motion always feels continuous." 
        font_size: 30
        color: .8,.9,0,1
        pos: 0, 150

    Slider:
        id: s1
        value: 100
        range: (100, 800)
        step: 1
        pos: 0, 0
        padding: 250

    Button:
        text: "Play"
        size_hint: None, None
        size: 140, 70
        pos: 1000, 200
        font_size: 30
        on_release:
            app.play_haptic(0.5, s1.value)

    Button:
        text: "Next"
        size_hint: None, None
        size: 140, 70
        pos: 1000, 120
        font_size: 30
        on_release: 
            app.root.transition = SlideTransition()
            app.root.current = "basic3"

<BasicScreen3>:
    name: "basic3"
    Label:
        text: "We can generate visual content that optimally matches with the tactile illusion. \nThe two motions work together to generate coherent multimodal experience." 
        font_size: 30
        pos: 0, 200
        color: .8,.9,0.1,1

    Label:
        text: "Speed (fast <-> slow)"
        font_size: 30
        pos: 0, -25
        color: 0, 0, 0, 1

    Slider: 
        id: s2
        min: 100
        max: 800
        pos: 0, -75
        padding: 250


    Button:
        text: "Play"
        size_hint: None, None
        size: 250, 70
        pos: 1000, 200
        font_size: 30
        on_release:
            app.play_ball(0.5, s2.value)
            app.play_haptic(0.5, s2.value)

    Button:
        text: "Play the game"
        size_hint: None, None
        size: 250, 70
        pos: 1000, 120
        font_size: 30
        on_release: 
            app.root.transition = SlideTransition()
            app.root.current = "game"

<TestCircle>:
    canvas:
        Color: 
            rgb: (0, 1, 1)
        Ellipse:
            size: 100, 100
            pos: self.pos

<GameScreen>:
    name: "game"
    Button: 
        text: "Back"
        size_hint: None, None
        size: 70, 70
        pos: 0, 0
        font_size: 30
        on_release: 
            app.root.transition = SlideTransition(direction="right")
            root.game.points = 0
            app.root.current = "welcome"

<Missile>:
    size: 50, 50
    canvas.before:
        PushMatrix
        Rotate:
            angle: self.angle
            origin: self.center
    canvas.after:
        PopMatrix
    canvas:
        Color: 
            rgb: (1, 1, 1)
        Rectangle:
            source: self.source
            pos: self.pos
            size: self.size 

<Enemy>:
    size: 70, 70
    canvas.before:
        PushMatrix
        Rotate:
            angle: self.angle
            origin: self.center
    canvas.after:
        PopMatrix
    canvas:
        Color:
            rgb: (1, 1, 1)
        Rectangle:
            source: self.source
            pos: self.pos
            size: self.size

<HungerBar>:
    size: 100, 5
    Color: 
        rgb: (0, 1, 1)
    canvas:
        Rectangle:
            pos: self.width * 7, 30

<Score>:
    font_size: 30  
    pos: self.pos
    text: self.text
    markup: True

1 个答案:

答案 0 :(得分:4)

B

此规则在画布块外添加一个Color(VertexInstruction),因此kivy会尝试将其添加为小部件。