How to handle clock module with multiple screens in Kivy? - python

I tried to move a ball around an origin (like the moon in its orbit around the earth). At first I created the ball object inside GameScreen. Then I got the AttributeError that my object does not have "move" attribute. So i created another widget called MainGame as a parent of the ball object. Now the error is gone but the ball does not actively moving. I could not figure out what i'm missing since I'm pretty new to Kivy. I guess it's about Clock module and my custom update function. All answers and helps are appreciated, thanks!
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.lang import Builder
import math
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty, ObjectProperty
from kivy.clock import Clock
class GameBall(Widget):
org_x, org_y = 300, 300
dist = 100
ang_deg = 0
pos_x = NumericProperty(org_x+dist)
pos_y = NumericProperty(org_y)
pos = ReferenceListProperty(pos_x, pos_y)
def move(self):
self.ang_deg += 5
self.ang_rad = math.radians(self.ang_deg)
self.pos_x, self.pos_y = self.org_x + self.dist*math.cos(self.ang_rad), self.org_y + self.dist*math.sin(self.ang_rad)
class MainGame(Widget):
game_ball = GameBall()
def update(self, dt):
self.game_ball.move()
class ScreenManagement(ScreenManager):
pass
class MenuScreen(Screen):
pass
class GameScreen(Screen):
pass
GUI = Builder.load_file("gui.kv")
class MobileGameApp(App):
def build(self):
game = MainGame()
Clock.schedule_interval(game.update, 1.0/60.0)
return GUI
if __name__ == "__main__":
MobileGameApp().run()
---- Kv file:
ScreenManagement:
MenuScreen:
GameScreen:
<MenuScreen>:
name: "menu_screen"
Button:
size_hint: .2, .2
pos: root.width/2 - self.width/2, root.height/2 - self.height/2
text: "Play Game"
on_release:
root.manager.transition.direction = "left"
root.manager.current = "game_screen"
<GameScreen>:
game_ball: main_game.game_ball
name: "game_screen"
Button:
size_hint: .2, .2
pos_hint: {'top': 1}
text: "Go to menu"
on_release:
root.manager.transition.direction = "right"
root.manager.current = "menu_screen"
MainGame:
id: main_game
GameBall:
id: game_ball
center: self.parent.center
canvas:
Ellipse:
size: 50, 50
pos: self.pos

Several problems with your code. One important thing I see in your code is creating new object instances when you actually want to reference existing instances:
game = MainGame()
and
game_ball = GameBall()
are creating new instances of MainGame and GameBall. Neither of those new instances are in your GUI, so any changes you make to those new instances will have no effect on your GUI.
So, you need to get references to the existing MainGame and GameBall. Those are the instances created by your kv file. One way to do that is to make the following changes to your code.
You can simplify GameBall as:
class GameBall(Widget):
org_x, org_y = 300, 300
dist = 100
ang_deg = 0
def move(self):
self.ang_deg += 5
self.ang_rad = math.radians(self.ang_deg)
self.pos = (self.org_x + self.dist*math.cos(self.ang_rad), self.org_y + self.dist*math.sin(self.ang_rad))
And MainGame can become:
class MainGame(Widget):
game_ball = ObjectProperty(None)
def update(self, dt):
self.game_ball.move()
And starting the animation now looks like:
class MobileGameApp(App):
def build(self):
Clock.schedule_once(self.start_updates)
return GUI
def start_updates(self, dt):
Clock.schedule_interval(self.root.get_screen('game_screen').ids.main_game.update, 1.0/60)
And then, to simplify accessing the GameBall, I added a game_ball to the kv as:
MainGame:
game_ball: game_ball
id: main_game
GameBall:
id: game_ball
center: self.parent.center
canvas:
Ellipse:
size: 50, 50
pos: self.pos

Related

Why does my game window not work in Kivy?

i am trying to build a main menu in my app that then links you to the game, i have the game working the issue is that when you press play it takes you to the game that does not move however i used print statements to check and in fact the game is running in the background from the moment you start the app.
Here is the Py Code:
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang.builder import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.properties import NumericProperty, ReferenceListProperty, ObjectProperty
from kivy.vector import Vector
from kivy.uix.floatlayout import FloatLayout
from kivy.uix import label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.clock import Clock
from kivy.config import Config
from kivy.uix.boxlayout import BoxLayout
class WindowManager(ScreenManager):
def StartUp(self):
self.current = "Menu"
pass
class MenuWindow(Screen):
pass
class PongBall(Widget):
velocity_y= NumericProperty(0)
velocity_x= NumericProperty(0)
velocity=ReferenceListProperty(velocity_x,velocity_y)
def move(self):
self.pos = Vector(self.velocity) +self.pos
class PongPad(Widget):
score=NumericProperty(0)
def Check_bounce(self,ball):
if self.collide_widget(ball) :
vx,vy= ball.velocity
bounced= Vector(-1* vx, vy )
speedup= abs(((ball.velocity_x *0.1) -(ball.center_y - self.center_y)) *0.002)
vel = bounced * (speedup +1.1)
offset=(((ball.center_y - self.center_y)/2) - (ball.velocity_x /2)) *0.1
if (ball.center_y - self.center_y) > 0:
ball.velocity=vel.x,vel.y
ball.velocity_y= 2
else:
ball.velocity= vel.x,vel.y
ball.velocity_y= -2
class Game(Screen):
ball = ObjectProperty(None)
player_1 = ObjectProperty(None)
player_2 = ObjectProperty(None)
def serve_ball(self,vel=(1,0.5)):
print("234Served")
self.ball.velocity= vel
self.ball.center = self.center
def Check_Top_Bottom(self):
#Check bottom collion
if self.ball.y <0:
self.ball.velocity_y= abs(self.ball.velocity_y)
#Check top colision
if self.ball.y+50> self.height:
self.ball.velocity_y = -abs(self.ball.velocity_y)
def Check_if_score(self):#Score
if self.ball.x >self.width:
self.player_1.score +=1
self.serve_ball()
if self.ball.x+50 <0:
self.player_2.score += 1
self.serve_ball()
def update(self,dt):
self.ball.move()
self.Check_Top_Bottom()
self.Check_if_score()
self.player_1.Check_bounce(self.ball)
self.player_2.Check_bounce(self.ball)
print(WindowManager().children)
def on_touch_move(self,touch):
if touch.x > self.width/2:
self.player_2.center_y = touch.y
else:
self.player_1.center_y= touch.y
kv= Builder.load_file("myMenu.kv")
class myMenuApp(App):
def build(self):
game=Game()
print(WindowManager().current_screen)
game.serve_ball()
Clock.schedule_interval(game.update, 1.0/60.0)
return game
if __name__ == "__main__":
myMenuApp().run()
And here is the KV code:
WindowManager:
MenuWindow:
Game:
<MenuWindow>:
name: "Menu"
FloatLayout:
Button:
size_hint: 0.2,0.05
pos_hint: {"x":0.39,"y":0.75}
text:"Play"
on_release:
app.root.current= "Game"
root.manager.transition.direction= "left"
Button:
size_hint: 0.2,0.05
pos_hint: {"x":0.39,"y":0.7}
text:"Settings"
Button:
size_hint: 0.2,0.05
pos_hint: {"x":0.39,"y":0.65}
text:"High Score"
Button:
size_hint: 0.2,0.05
pos_hint: {"x":0.39,"y":0.6}
text:"Quit"
<PongBall>
size_hint: None, None
size: 50,50
canvas:
Ellipse:
pos:self.pos
size:self.size
<PongPad>
size_hint: None, None
size:25,150
canvas:
Rectangle:
pos:self.pos
size:self.size
<Game>:
name:"Game"
ball: Pong_ball
player_1: Player1
player_2: Player2
canvas:
Rectangle:
pos:self.center_x -5,0
size: 15,root.height
PongPad:
id: Player2
pos:root.width-25,root.center_y-75
PongPad:
id: Player1
pos:0,root.center_y-75
PongBall:
id: Pong_ball
center: self.parent.center
I have tried without the Builder but pretty much same issue, i also tried calling the Screen Manager in the return functions but that just gives me a blank screen.
Another thing is i tried checking the current screen when on the main menu and it outputs "NONE"
Thank you
A few issues:
When you execute print(WindowManager().current_screen) you are creating a new WindowManager and printing its current_screen. That is unrelated to any WindowManager in your GUI.
You are loading your kv file, which builds a GUI with a WindowManager that it returns. But you ignore that returned WindowManager and the build() method returns a Game instance, which becomes the root of your GUI (with no WindowManager).
You can use the on_enter() method of Screen to start the Game when it becomes the current Screen, like this:
class Game(Screen):
ball = ObjectProperty(None)
player_1 = ObjectProperty(None)
player_2 = ObjectProperty(None)
def on_enter(self, *args):
# start the game
self.serve_ball()
Clock.schedule_interval(self.update, 1.0 / 60.0)
And in the build() method of your App, just return the GUI that is build by the Builder:
class myMenuApp(App):
def build(self):
return kv

AttributeError: 'NoneType' object has no attribute 'ids' in Kivy when Clock.schedule_interval function is called

I am facing an Attribute error in the code below. I am new to this kivy framework. When I tried to call the Clock.schedule_interval function to infinite scroll the image. Clock.schedule_interval(self.root.ids.mainwindow.scroll_textures, 1/60.)
AttributeError: 'NoneType' object has no attribute 'ids'
This error popped up.
The .py file
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.clock import Clock
from kivy.uix.button import Button
from kivy.core.audio import SoundLoader
from kivy.config import Config
# You can create your kv code in the Python file
# Create a class for all screens in which you can include
# helpful methods specific to that screen
kv = Builder.load_file("main.kv")
class MainWindow(Screen):
cloud_texture = ObjectProperty(None)
floor_texture = ObjectProperty(None)
sound = ObjectProperty(None)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cloud_texture = Image(source = "cloud4.png").texture
self.cloud_texture.wrap = 'repeat'
self.cloud_texture.uvsize = (Window.width/self.cloud_texture.width,-1)
self.floor_texture = Image(source = "floor2.jpg").texture
self.floor_texture.wrap = 'repeat'
self.floor_texture.uvsize = (Window.width/self.floor_texture.width,-1)
self.sun_texture = Image(source = "sun.png").texture
self.sun_texture.uvsize = (Window.width/self.sun_texture.width,-1)
self.sound = SoundLoader.load('8bitmenu.wav')
self.sound.play()
def scroll_textures(self, time_passed):
#Update the uvpos
self.cloud_texture.uvpos = ((self.cloud_texture.uvpos[0] - time_passed/20) % Window.width, self.cloud_texture.uvpos[1])
self.floor_texture.uvpos = ((self.floor_texture.uvpos[0] + time_passed/8) % Window.width, self.floor_texture.uvpos[1])
#Redraw textures
texture = self.property('cloud_texture')
texture.dispatch(self)
texture = self.property('floor_texture')
texture.dispatch(self)
class Window1(Screen):
pass
class WindowManager(ScreenManager):
pass
#Config.write()
class MyApp(App):
def on_start(self):
Clock.schedule_interval(self.root.ids.mainwindow.scroll_textures, 1/60.)
pass
def build(self):
return kv
MyApp().run()
.kv file
<WindowManager>:
MainWindow:
Window1:
<MainWindow>:
name: "main"
id: mainwindow
canvas.before:
Rectangle:
size: self.size
pos: self.pos
source: "sky.jpg"
Rectangle:
size: self.width - 1000, 120
pos: self.pos[0] + 500, self.pos[1] + self.height - 138
texture: self.sun_texture
Rectangle:
size: self.width, 138
pos: self.pos[0], self.pos[1] + self.height - 168
texture: self.cloud_texture
Rectangle:
size: self.width, 100
pos: self.pos[0], self.pos[1]
texture: self.floor_texture
Image:
source: 'dog2.gif'
anim_delay: 0.09
size: self.width, self.height
pos: -270, -270
Image:
source: 'boy1.gif'
anim_delay: 0.09
size: self.width, self.height
pos: -100, -230
Button:
text: "Start your journey"
color: .8,.9,0,1
background_color: 1, 1, 1, 0.3
halign: 'center'
center: root.center
font_size: 12
size: 32, 32
size_hint: .2, .2
pos: 300, 250
on_release:
app.root.current = "win1"
<Window1>:
name: "win1"
Button:
text: "Go back"
on_release:
app.root.current = "main"
Can anyone help me with this?
I solved the Error here. Just declared the id in parent class and that worked
WindowManager:
MainWindow:
id: mainwindow
Window1:

change the background canvas kivy

I have a canvas background but I am trying to switch the back ground when a person his a correct button to move on to the next level. I am trying to do this all within one class. Is there a way to assign an image to a canvas rectangle and then on the press of a button the canvas image will change to a new source.
main.py
class MazeSolution(Screen):
def CheckDoor1(self):
if self.ids.door1.source == "correct":
print("correct")
self.ids.change.source = 's-curvee selection.png'
else:
print("incorrect")
main.kv
#:import utils kivy.utils
<MazeSolution>:
FloatLayout:
canvas:
Rectangle:
id: change
source: 'selection grass.png'
pos: self.pos
size: self.size
Button:
pos_hint: {"top": .8, "right": .75}
size_hint: .5, .1
text:
"Door 1"
source: "<random_name>"
id: door1
on_press:
root.CheckDoor1()
I don't think assigning ids within canvas instructions is supported. But you can accomplish the same thing by creating the Rectangle in python:
from kivy.app import App
from kivy.graphics.vertex_instructions import Rectangle
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
class MazeSolution(Screen):
def __init__(self):
super(MazeSolution, self).__init__()
# make the Rectangle here and save a reference to it
with self.canvas.before:
self.rect = Rectangle(source='selection grass.png')
def on_pos(self, *args):
# update Rectangle position when MazeSolution position changes
self.rect.pos = self.pos
def on_size(self, *args):
# update Rectangle size when MazeSolution size changes
self.rect.size = self.size
def CheckDoor1(self):
if self.ids.door1.source == "correct":
print("correct")
# use the saved reference to change the background
self.rect.source = 's-curvee selection.png'
else:
print("incorrect")
Builder.load_string('''
<MazeSolution>:
FloatLayout:
Button:
pos_hint: {"top": .8, "right": .75}
size_hint: .5, .1
text:
"Door 1"
source: "correct"
id: door1
on_press:
root.CheckDoor1()
''')
class TestApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(MazeSolution())
return sm
TestApp().run()
The on_pos() and on_size() methods perform the operations that kv would set up for you automatically, but must be set up manually since the Rectangle is not created in kv.

How to switch a widget's Animation transition in Kivy

Summary:
I would like to test all the possible transitions of Kivy's Animation function.
In my code, I call a method to switch the buttons' animations. The method is successfully called because I used print to confirm the change; however, the buttons' transitions don't accept the change. They keep using the first animation but do not take on the next animation in the list.
What am I doing wrong?
Code:
from kivy.app import App
from kivy.core.window import Window
from kivy.lang import Builder
from random import random
from kivy.uix.button import Button
from kivy.animation import Animation
from kivy.clock import Clock
class LittleButtons(Button):
transition = 'in_back'
dur = 2
num = 0
def change_transition(self):
list = ['in_back','in_bounce','in_circ','in_cubic','in_elastic','in_expo','in_out_back',
'in_out_bounce','in_out_cubic','in_out_elastic','in_out_expo',
'in_out_quad','in_out_quart','in_out_quint','in_out_sine','in_quad','in_quart',
'in_quint','in_sine','linear','out_back','out_bounce','out_circ','out_cubic',
'out_elastic','out_expo','out_quad','out_quart','out_quint','out_sine']
self.num += 1
self.transition = list[self.num]
self.reup()
print(self.transition)
if self.num == len(list) - 1:
self.num = -1
def reup(self, *args):
Animation.cancel_all(self)
anim = Animation(pos_hint = {'center_x': random(), 'center_y': random()},
duration = self.dur, t = self.transition)
anim.start(self)
def __init__(self, **kwargs):
super(LittleButtons, self).__init__(**kwargs)
self.pos_hint = {'center_x': random(), 'center_y': random()}
self.size_hint = None, None
self.width = random() * (Window.width / 20)
self.height = random() * (Window.width / 20)
self.background_color = [0,0,0,.05]
Animation(pos_hint = {'center_x': random(), 'center_y': random()},
duration = self.dur).start(self)
Clock.schedule_interval(self.reup, self.dur)
KV = Builder.load_string("""
#:import Factory kivy.factory.Factory
Screen:
FloatLayout:
on_parent:
(lambda ltext: [self.add_widget(Factory.LittleButtons(text=ltext)) for i in range (150)])('hi!')
LittleButtons:
id: base
Button:
background_color: 0,0,0,0
canvas:
Color:
rgba: 0,1,1,1
Rectangle:
pos: self.pos
size:self.size
on_release:
base.change_transition()
""")
class MyApp(App):
def build(self):
return KV
if __name__ == '__main__':
MyApp().run()
Your code is working, but not doing what you expect. Your on_release calls base.change_transition(), which refer to the base id. That id is the one LittleButtons in your KV string that is not built by the on_parent event. The animation transition of that one LittleButtons is successfully modified, but the others are unaffected. Also, that particular LittleButtons is unseen, since the big Button hides it. So, you are successfully changing the transition of one LittleButtons, but it doesn't show.

kivy adding widget without kivy language

I wanted to make a small game with an enemy-widget that disappears when you click on it.
A added the Enemy to an widget using kivy-language and it worked fine, but I wanted to add multiple enemys and I don't want to add more and more Enemys, so I wanted to use the add_widget command to andd the enemy witget to the place widget, but I got the Error:
TypeError: unbound method add_widget() must be called with place instance as first argument (got WidgetMetaclass instance instead)
Here is the sourcecode:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.properties import NumericProperty
from kivy.clock import Clock
from kivy.animation import Animation
class place(Widget):
pass
class Enemy(Widget):
velocity = NumericProperty(1)
def __init__(self, **kwargs):
super(Enemy, self).__init__(**kwargs)
Clock.schedule_interval(self.Update, 1/60.)
def Update(self, *args):
self.x -= self.velocity
if self.x < 1:
self.velocity = 0
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
print 'es geht'
self.velocity = 0
self.parent.remove_widget(self)
ROOT = Builder.load_string('''
FloatLayout:
Button:
text: 'Go Back'
size_hint: 0.3, 0.1
pos_hint: {"x": 0, 'y':0}
place:
<place>:
Enemy:
pos: 400, 100
<Enemy>:
Image:
pos: root.pos
id: myimage
source: 'enemy.png'
''')
class Caption(App):
def build(self):
place.add_widget(Enemy)
return ROOT
if __name__ == '__main__':
Caption().run()
There are a couple problems with your code. First the python problem:
The error you are getting is telling you that you are trying to call in instance method on a class (as opposed to an object of that class).
place.add_widget(Enemy)
place is a class (or "type" if you prefer, and it might be helpful to name it Place to follow a constistent naming convention), and you need an object of type 'place' to call .add_widget on. Similarly, Enemy is a class, not an object, so you need to create a new object of type Enemy:
obj.add_widget(Enemy(pos=(400,300))
Where obj is an instance of place, and Enemy() creates an instance of enemy.
Now the kivy problem:
You can't access a widget in kv from python by it's name. You have to tag it with an id and then reference that id:
ROOT = Builder.load_string('''
FloatLayout:
Button:
text: 'Go Back'
size_hint: 0.3, 0.1
pos_hint: {"x": 0, 'y':0}
place:
id: place
<place>:
Enemy:
pos: 400, 100
<Enemy>:
Image:
pos: root.pos
id: myimage
source: 'enemy.png'
''')
class Caption(App):
def build(self):
obj = ROOT.ids.place
obj.add_widget(Enemy(pos=(400,300)))
return ROOT
See here for more info

Categories