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
Related
I have been trying to make this code work. Im using ScreenManager to manage my screen.
I want the Input I entered on the first screen to be displayed the next screen. But instead, it just shows the initial value, and it doesn't change to the Inputted value.
Here is the Code i have done
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
from kivy.clock import Clock
Builder.load_string("""
<MenuScreen>:
promptObject: prompts
BoxLayout:
orientation: 'horizontal'
TextInput:
id: prompts
pos: 20,20
Button:
text: "Enter Prompt"
pos: 30,30
size: 100, 30
on_press: root.submit()
<Newscreen>
BoxLayout:
orientation: 'vertical'
TextInput:
id: display_output
text: root.output
readonly: True
""")
class MenuScreen(Screen):
promptObject = ObjectProperty()
prompt = ''
def submit(self):
prompt = self.promptObject.text
global result
result = prompt
sm.add_widget(NewScreen(name="Settings"))
sm.switch_to(sm.get_screen("Settings"))
NewScreen.display(self)
class NewScreen(Screen):
output = "testing testing"
def display(self):
self.output = result
print(result) #To test if it works
class TestApp(App):
def build(self):
global sm
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
return sm
if __name__ == '__main__':
TestApp().run()
I'm also thinking if i can instead Declare the Layout for the second screen later right before i call for the next Screen. Maybe that could work but if you have other method, it would be nice to see it.
thank you for the concise single-file example. this is a very helpful way to submit a kivy question. I have modified and tested the below app with various changes.
I changed the root.submit to app.submit. This is not strictly required, it is just a choice in this example to put the logic in the main app. it is also possible to use root.submit and put the logic in the widget but one would have to pass a reference to the screen manager into that widget in that case.
imported TextInput object instead of using ObjectProperty. when using an IDE it is helpful to declare objects with the specific type because it enables auto-complete
assigned the ScreenManager to self.sm so this object is available throughout the app.
finally, got rid of any reference to global. I think it is better to avoid use of this keyword and explicitly create the variable at the highest level where you need it and pass the value into the objects requiring it.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
# from kivy.properties import ObjectProperty
from kivy.uix.textinput import TextInput
from kivy.properties import StringProperty
from kivy.clock import Clock
Builder.load_string("""
<MenuScreen>:
promptObject: prompts
BoxLayout:
orientation: 'horizontal'
TextInput:
id: prompts
pos: 20,20
Button:
text: "Enter Prompt"
pos: 30,30
size: 100, 30
on_press: app.submit()
<Newscreen>
BoxLayout:
orientation: 'vertical'
TextInput:
id: display_output
text: root.output
readonly: True
""")
class MenuScreen(Screen):
promptObject = TextInput()
class NewScreen(Screen):
output = StringProperty()
def __init__(self, **kw):
super().__init__(**kw)
def display(self, result):
# set the string property equal to the value you sent
self.output = result
print(result) # To test if it works
class TestApp(App):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# create screen manager with self so that you have access
# anywhere inside the App
self.sm = ScreenManager()
# create the main screen
self.menu_screen = MenuScreen(name='menu')
# this could be deferred, or created at initialization
self.settings_screen = NewScreen(name='Settings')
#
def submit(self):
prompt = self.menu_screen.promptObject.text
result = prompt
# optional, deferred creation
# self.settings_screen = NewScreen(name='Settings')
# add to the screen manager
self.sm.add_widget(self.settings_screen)
# enter the value into your other screen
self.settings_screen.display(result)
# switch to this screen
self.sm.current="Settings"
def build(self) -> ScreenManager:
# could create this screen right away, depending...
# self.sm.add_widget(self.settings_screen)
# of course you need the main screen
self.sm.add_widget(self.menu_screen)
# redundant, unless you create all screens at the beginning
self.sm.current = 'menu'
return self.sm
if __name__ == '__main__':
TestApp().run()
I have a game I am building and the whole game logic is contained in a Game() class. I would like to pass an object of this game into the kv file. Here is my code:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.uix.screenmanager import Screen, ScreenManager
class Game():
name = "Game Name"
def __init__(self, player):
self.player = player
build = """
WindowManager:
HomeScreen:
<HomeScreen>:
name: 'home'
BoxLayout:
orientation: 'horizontal'
size: root.width, root.height
Button:
text: "New Game"
on_release:
app.root.load_game("player")
app.root.current = 'game'
<GameScreen>:
name: 'game'
BoxLayout:
orientation: 'horizontal'
Label:
text: root.game.name
"""
class HomeScreen(Screen):
pass
class GameScreen(Screen):
player = StringProperty()
def __init__(self, player, **kw):
super().__init__(**kw)
self.player = player
self.game = Game(self.player)
class WindowManager(ScreenManager):
def load_game(self, player):
self.add_widget(GameScreen(player))
class Application(App):
def build(self):
return Builder.load_string(build)
if __name__ == '__main__':
Application().run()
This runs, but when I try to click the button to instantiate the game screen it throws an error saying that the GameScreen doesn't have a "game" property. There isn't, as far as I'm aware, a ClassProperty property in Kivy, how would I pass in an object that's not a primitive type?
To add an object as a property:
from kivy.properties import StringProperty, ObjectProperty # Import ObjectProperty
class GameScreen(Screen):
player = StringProperty()
game = ObjectProperty(Game) #Add an object property
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
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
As far as I am aware, kv language is useful when making a static display, e.g. not when making game which need much widget's positioning during runtime. Here I try to make a simple game, but still need much positioning, so kv language is out of context for the widget, but not for the screen. I use screen to differentiate the main menu and game screen. But when I try to use 'add_widget' to insert my image, it always positioned at the middle of the window. Later I found out that the screen size is only 100x100.
Below are the only way that I can thought of, but still with no luck:
class HomeScreen(Screen):
pass
class GameScreen(Screen):
pass
class ScreenManagement(ScreenManager):
pass
presentation=Builder.load_file('ProjectSD.kv')
class ProjectSDApp(App):
def build(self):
A=presentation
A.screens[0].size=(Window.size)
A.screens[0].add_widget(Label(text='hello',font_Size=80,pos=(0,0)))
return A
if __name__=='__main__':
print(Window.size)
ProjectSDApp().run()
and my ProjectSD.kv file:
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
ScreenManagement:
transition: FadeTransition()
HomeScreen:
GameScreen:
<Button>:
font_name:'attackofthecucumbers.ttf'
<HomeScreen>:
name:'home'
canvas:
Rectangle:
pos: self.pos
size: self.size
source: 'nature.jpg'
Label:
text: 'Monopoly GX'
font_name:'KBDunkTank.ttf'
font_size:100
size_hint:0.7,0.2
pos:root.width*0.15,root.height*0.70
Button:
on_release: app.root.current = "game"
text: 'Play Game'
font_size:root.width/20
size_hint:0.3,0.15
pos:root.width*0.35,root.height*0.45
Button:
on_release: app.stop()
text: 'exit'
font_size:root.width/20
size_hint:0.3,0.15
pos:root.width*0.35,root.height*0.20
<GameScreen>:
name:'game'
Button:
on_release: app.root.current = "home"
background_color: (1,0.15,0.2,0.8)
text: 'X'
font_size:root.width/40
size_hint:0.05,0.05
pos:root.width*0.95,root.height*0.95
Since there is no 'pos' method in screen object, I put my widget to position (0,0) manually.
The only way I found is just below:
https://kivyspacegame.wordpress.com/2014/08/10/tutorial-flappy-ship-part-2-build-simple-menus-and-animate-your-games-using-clock/
So my question is, if I use screen object from kivy's build in, how to achieve the same result? So I can still adding and remove widget as I want it later?
I'm not entirely clear on what you're asking; I don't see anywhere in your code where you are trying to add an Image to a Layout. You add a label to the middle and that works fine.
I think because you are creating screens without any layouts and not setting a default window size, that the screens just take up their minimum default size. You need to add a layout and fill it with stuff that has a defined size, or set a window size at the beginning e.g:
# window import
from kivy.core.window import Window
from kivy.utils import get_color_from_hex
Window.size = (1920/2,1080/2)
Window.clearcolor = get_color_from_hex('#000000') # black
Here is some code that creates your two Screens, and adds a draggable and a static Image at a position to a FloatLayout.
from kivy.app import App
from kivy.uix.screenmanager import (ScreenManager, Screen)
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.behaviors import DragBehavior
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
class HomeScreen(Screen):
pass
class GameScreen(Screen):
pass
class ScreenManagement(ScreenManager):
pass
class MovingImage(DragBehavior,Image):
pass
class DragLabel(DragBehavior, Label):
pass
class StaticImage(Image):
pass
class MyApp(App):
def build(self):
A = ScreenManagement()
A.current = 'game'
A.screens[1].ids.gamefloat.add_widget(MovingImage(size_hint = [0.3,0.3]))
A.screens[1].ids.gamefloat.add_widget(StaticImage(pos = (150,300)))
return A
if __name__=='__main__':
MyApp().run()
and the .kv file called myapp.kv
<ScreenManagement>:
HomeScreen:
GameScreen:
<Button>:
<HomeScreen>:
name:'home'
canvas:
Rectangle:
pos: self.pos
size: self.size
source: 'image.jpg'
Label:
text: 'Monopoly GX'
font_size:100
size_hint:0.7,0.2
pos:root.width*0.15,root.height*0.70
Button:
on_release: app.root.current = "game"
text: 'Play Game'
font_size:root.width/20
size_hint:0.3,0.15
pos:root.width*0.35,root.height*0.45
Button:
on_release: app.stop()
text: 'exit'
font_size:root.width/20
size_hint:0.3,0.15
pos:root.width*0.35,root.height*0.20
<GameScreen>:
name:'game'
FloatLayout:
id: gamefloat
Button:
on_release: app.root.current = "home"
background_color: (1,0.15,0.2,0.8)
text: 'X'
font_size:root.width/40
size_hint:0.05,0.05
pos:root.width*0.95,root.height*0.95
<MovingImage>:
drag_rectangle: self.x, self.y, self.width, self.height
drag_timeout: 1000000
drag_distance: 0
source: 'image.jpg'
<StaticImage>:
source: 'image.jpg'