Kivy widgets behaving erratically - python

I have been playing around with the Kivy Pong tutorial, getting up to speed with the framework, seeing if I could implement a few ideas. I have removed most of the Pong functionality, so I could have only bouncing ball on the screen and added some code to generate multiple bouncing balls on the screen, generated on touch. That worked fine. I then added some extra canvas instructions, so I would have a line drawn indicating the direction the ball is moving. This is where things got weird. The first ball acts just as it should, bouncing around the screen. But any following clicks generate balls that go off screen, randomly change direction and speed and in general behave chaotically. I have been looking at my code and I cannot seem to find any indication of what might be going wrong. I keep all the references to the widgets, I add them to the root widget, I don't seem to be sharing any information between them... Anyway, here is the code, maybe someone can enlighten me. Using latest kivy and python 3.6.
from random import randint
from kivy.app import App
from kivy.clock import Clock
from kivy.config import Config
from kivy.vector import Vector
from kivy.uix.widget import Widget
from kivy.properties import AliasProperty, ListProperty, NumericProperty, ReferenceListProperty
class Playground(Widget):
critters = ListProperty([])
def update(self, dt):
for critter in self.critters:
critter.move()
if (critter.y self.height):
critter.v_y *= -1
if (critter.x self.width):
critter.v_x *= -1
self.score.text = "{}".format(len(self.critters))
def on_touch_down(self, touch):
critter = Critter()
critter.pos = touch.x, touch.y
self.critters.append(critter)
self.add_widget(critter)
class Critter(Widget):
angle = NumericProperty(0)
v_x = NumericProperty(0)
v_y = NumericProperty(0)
velocity = ReferenceListProperty(v_x, v_y)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.velocity = Vector(5, 0).rotate(randint(0, 360))
self.angle = Vector(*self.velocity).angle(Vector(1, 0))
def move(self):
self.pos = Vector(*self.velocity) + self.pos
self.angle = Vector(*self.velocity).angle(Vector(1, 0))
class WorldApp(App):
def build(self):
game = Playground()
Clock.schedule_interval(game.update, 1.0/60.0)
return game
if __name__ == '__main__':
Config.set('kivy', 'desktop', 1)
Config.set('kivy', 'exit_on_escape', 1)
Config.set('graphics', 'resizable', 0)
WorldApp().run()
and the KV file
<Playground>
score: score
canvas:
Color:
rgb: 0.0, 0.1, 0.0
Rectangle
pos: self.pos
size: self.size
Label:
id: score
pos: self.parent.width - self.size[0], self.parent.height - self.size[1]
font_size: 16
size: self.texture_size
<Critter>
size: 30, 30
canvas:
Rotate:
angle: self.angle
origin: self.center
axis: 0, 0, 1
Color:
rgb: 0.5, 0.0, 0.0
Ellipse:
pos: self.pos
size: self.size
Color:
rgb: 1, 1, 0.0
Line:
width: 2
points: self.center[0], self.center[1], self.center[0] + self.size[0] / 2, self.center[1]

I'm not sure if it's causing your problem, but your Rotate instructions aren't bounded by the widget rule and will affect any later widgets - so the Rotate of each Critter is applied to every later one.
To avoid this, add PushMatrix: at the top of the canvas rule and PopMatrix: at the bottom. These instructions effectively save and later revert to the initial rotation state before your change.

Related

Kivy: How to change size of widget using on_touch_move function?

As a beginner python learner, I am trying to create this simple app using kivy to change the thickness of a rectangle through various inputs. Firstly, I had tried to do it using buttons, and with some help from this community managed to make it work.
Now that this problem got solved, I thought of taking it to the next level by using the on_touch_move function to slide on the screen to change thickness. But have again stumbled across a new problem.
When I run this code, there is no error, also the boundary_thickness_x and boundary_thickness_y are getting updated (tested using print). But the size (thickness) of the widgets is not updating in the window.
I wonder what mistake I might be doing?
**main.py**
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ObjectProperty
class Boundary(Widget):
boundary_thickness_x = NumericProperty(10)
boundary_thickness_y = NumericProperty(10)
def on_touch_move(self, touch):
x = touch.x/self.width * 100
y = touch.y/self.height * 100
boundary_thickness_x = x
boundary_thickness_y = y
#print(boundary_thickness_x, boundary_thickness_y)
class BounceApp(App):
def build(self):
return Boundary()
BounceApp().run()
**bounce.kv**
<Boundary>
canvas:
Rectangle:
pos : 0, 0
size: self.boundary_thickness_x, root.height
Rectangle:
pos : 0, 0
size: root.width, self.boundary_thickness_y
Rectangle:
pos : root.width - self.boundary_thickness_x, 0
size: self.boundary_thickness_x, root.height
Rectangle:
pos : 0, root.height - self.boundary_thickness_y
size: root.width, self.boundary_thickness_y
Your on_touch_move() method is not adjusting the correct properties. It is only adjusting a couple local variables. Just change that method to:
def on_touch_move(self, touch):
x = touch.x / self.width * 100
y = touch.y / self.height * 100
self.boundary_thickness_x = x
self.boundary_thickness_y = y
You must use self. to reference the properties.

Kivy: change z-index of canvas rectangles

I am very new to kivy, and have been trying to figure out how to order rectangles created with the canvas class in the z dimension. Using the .kv language I created a root widget that creates a grid of rectangles. That part worked out fine, then I created a child widget consisting of 1 blue rectangle that I was able to move around with the kivy clock. The problem is that I want to display the blue rectangle on top of everything else.
This is my python code (full of unnecessary imports):
import kivy
from kivy.config import Config
Config.set('graphics', 'resizable', False)
Config.set('graphics', 'width', '1000')
Config.set('graphics', 'height', '700')
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics import *
from kivy.core.window import Window
from kivy.properties import (ObjectProperty, ReferenceListProperty, ListProperty)
import random
from kivy.clock import Clock
from kivy.vector import Vector
class board(FloatLayout):
car = ObjectProperty(None)
def __init__(self, **k):
super(board, self).__init__(**k)
with self.canvas:
l = 2
w = 4
for b in range(1, l+1):
for a in range(1,w+1):
for i in range(3):
for x in range(2):
numb = random.randint(1, 2)
if numb == 1:
Color(1, 0, 0, 1)
Rectangle(pos=(a*70+20*(x)-50, b*80+20*(i)-20), size=(10, 10))
Color(0,1,0,1)
print(a)
print(b)
Rectangle(pos=(a*70-10, 20), size=(20, l*80+35))
Rectangle(pos=(0, 20), size=(20, l*80+35))
Rectangle(pos=(20, 20), size=(w*70-30, 20))
Rectangle(pos=(20, b*80 + 35), size=(w*70-30, 20))
class Car(Widget):
velocity = ListProperty([1, 1])
def __init__(self, **k):
super(Car, self).__init__(**k)
self.canvas.add(Color(1,0,0,1))
self.canvas.add(Rectangle(size=(50,50)))
Clock.schedule_interval(self.update, 1.0/60.0)
def update(self, *args):
self.x += self.velocity[0]
self.y += self.velocity[1]
print('hello')
class RobotApp(App):
def build(self):
ba= Car()
return board()
if __name__ == '__main__':
RobotApp().run()
This is my kivy code:
#:kivy 1.11.1
<Car>:
canvas:
Color:
rgba: 0, 0, 1, 1
Rectangle:
pos:self.pos
size: 10, 10
<board>:
car: Cars
canvas:
Rectangle:
pos: 0, 0
size: 500, 700
Car:
id: Cars
pos: self.parent.pos
Thanks to Inclement, I was able to figure out the problem. I had to change three lines:
One in the python file.
with self.canvas:
to
with self.canvas.before:
Two in the kivy file.
<Car>:
canvas:
to
<Car>:
canvas.after:
then
<board>:
canvas:
to
<board>:
canvas.before:
my blue square now displays on top, thanks!

Cannot get kivy to display the relevant canvas and items for a breakout game

I am trying to create a breakout game using Python and Kivy. I have tried displaying other kivy features like labels and buttons and they all work perfectly. However, when I try to run the below code I keep getting a blank screen. Any help would be appreciated.
BreakoutApp.py
from kivy.app import App # App is base for any kivy app
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.modalview import ModalView
from kivy.properties import (ListProperty, NumericProperty, ObjectProperty, StringProperty)
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
from kivy.uix.label import Label
from kivy.uix.scatter import Scatter
import random
__version__ = '0.1' # used in Android compilation
#Game is a big widget that will contain the entire game
# A subclass of Float layout as it will proportion its children in proportion to its own pos and size
class Game(FloatLayout): # Will contain everything
blocks = ListProperty([])
player = ObjectProperty() # The game's player instance
ball = ObjectProperty() # The game's ball instance
def setup_blocks(self):
for y_jump in range(5):
for x_jump in range(10):
print "in setup blocks"
block = Block(pos_hint={
'x': 0.05 + 0.09*x_jump,
'y': 0.05 + 0.09*y_jump})
self.blocks.append(block)
self.add_widget(block)
# App will initialise everything that kivy needs
class BreakoutApp(App):
def build(self):
print "in build self blocks"
g = Game()
g.setup_blocks()
return g
#f = FloatLayout()
#s = Scatter()
#l = Label(text="Hello!",
# font_size=150)
#f.add_widget(s)
#s.add_widget(l)
#return f
#Require a class for each game object
#kivy properties are special attributes declared at class level
class Player(Widget): # A moving paddle
position = NumericProperty(0.5)
direction = StringProperty("none")
def __init__(self, **kwargs):
super(Player, self).__init__(**kwargs)
with self.canvas:
Color(1, 0, 0, 1)
Rectangle(pos=self.pos, size=self.size)
class Ball(Widget): # A bouncing ball
# pos_hints are for proportional positioning, see below
pos_hint_x = NumericProperty(0.5)
pos_hint_y = NumericProperty(0.3)
proper_size = NumericProperty(0.)
velocity = ListProperty([0.1,0.5])
class Block(Widget): # Each coloured block to destroy
colour = ListProperty([1, 0, 0])
def __init__(self, **kwargs):
super(Block, self).__init__(**kwargs)
self.colour = random.choice([
(0.78,0.28,0), (0.28,0.63,0.28), (0.25,0.28,0.78)])
if __name__ == "__main__":
print "main method called"
BreakoutApp().run()
Breakout.kv
#:kivy 1.9.1
<Player>:
canvas:
Color:
rgba: 1, 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
<Ball>:
canvas:
Color:
rgb: 1, 0.55, 0
Rectangle:
pos: self.pos
size: self.size
<Block>:
canvas:
Color:
rgb: self.color #property defined above
Rectangle:
pos: self.pos
size: self.size
Color:
rgb: 0.1, 0.1, 0.1
Line:
rectangle:
[self.x, self.y, self.width, self.height]
Also, do I need to explicitly refer to the layout .kv file in the python file or are there any specific naming restrictions. I found some documentation online to name the two as found below.
You have in kv file color
<Block>:
canvas:
Color:
rgb: self.color #property defined above
yet in py file there is colour (with U):
class Block(Widget): # Each coloured block to destroy
colour = ListProperty([1, 0, 0])
If you change it, you'll get the desired output I believe:
<Block>:
canvas:
Color:
rgb: self.colour #property defined above
Proof:

Center widgets in Kivy

I am learning to use Kivy, so I walked through the Pong tutorial and started messing around with the code. So, I removed everything but the bouncing ball and decided to generate multiple balls on demand. The problem I am having is that while I can place balls where I want them when application is already running (for example, adding a ball on touch works fine), but when I add balls in the app build() they don't get placed right. Here is the code I have. The balls placed on touch, correctly start from the center. But the ball added in build() starts from the lower left corner. Why? I wanted to add more moving widgets with different properties, but I cannot seem to figure out how to place them on application start.
#:kivy 1.0.9
<World>:
canvas:
Ellipse:
pos: self.center
size: 10, 10
<Agent>:
size: 50, 50
canvas:
Ellipse:
pos: self.pos
size: self.size
from random import randint
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty, ListProperty
from kivy.vector import Vector
from kivy.clock import Clock
class World(Widget):
agents = ListProperty()
def add(self):
agent = Agent()
agent.center = self.center
agent.velocity = Vector(4, 0).rotate(randint(0, 360))
self.agents.append(agent)
self.add_widget(agent)
def on_touch_down(self, touch):
self.add()
def update(self, dt):
for agent in self.agents:
agent.move()
if agent.y < 0 or agent.top > self.height:
agent.velocity_y *= -1
if agent.x < 0 or agent.right > self.width:
agent.velocity_x *= -1
class Agent(Widget):
velocity_x = NumericProperty(0)
velocity_y = NumericProperty(0)
velocity = ReferenceListProperty(velocity_x, velocity_y)
def move(self):
self.pos = Vector(*self.velocity) + self.pos
class WorldApp(App):
def build(self):
world = World()
# add one ball by default
world.add()
Clock.schedule_interval(world.update, 1.0/60.0)
return world
if __name__ == '__main__':
WorldApp().run()
Found the answer. The default widget size is 100, 100. By the time I add the initial ball, the World widget is not rendered and therefore has a default size. But it is possible to pass the windows size in the Widget constructor. So changing the World instantiation to
world = World(size=Window.size)
solved the problem

How to fix aspect ratio of a kivy game?

I've started playing with Kivy, but I'm rather new to all this and am already struggling.
I'm trying to make a board game - I want the window to be resizeable, but I don't want resizing the window to mess up the aspect ratio of the game (so in other words, I want the window to have black bars above or to the sides of the content if the window is resized to something other than the intended aspect ratio of the content)
The easiest way I could see to ensure this is to either:
a) Lock the aspect ratio of the Window itself so it's always 10:9 (the aspect ratio of the board and all on-screen elements) - even when fullscreen.
or
b) Use some sort of widget/surface/layout that is centered on the window and has a locked 10:9 aspect ratio. I then subsequently use this as the base onto which all my other images, widgets, grids etc are placed.
However, I really don't know how to do either of these. I'm not sure if I can lock the Window aspect ratio, and the only kivy-specific object I've found that lets me lock the aspect ratio is from kivy.graphics.image... which I don't seem to be able to use as a 'base' for my other stuff.
EDIT: So far I've written the code below: it creates a layout (and colours it slightly red) that 'fixes' its own aspect ratio whenever it is resized. However, it still isn't centered in the window, and more problematic, it results in an endless loop (probably because the aspect fix code corrects the size, but then kivy 'corrects' its size back to being the size of the window, triggering the aspect fix again, thought I could be wrong).
EDIT: Modified the code again, but it's still an endless loop. I though that referring only to the parent for size info would fix that, but apparently not.
I'd appreciate anyone helping me fix my code.
Code below:
test.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.properties import (ObjectProperty,
NumericProperty,
ReferenceListProperty)
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
class Board(AnchorLayout):
pass
class BackgroundLayout(RelativeLayout):
def FixAspectRatio(self, *args):
correctedsize = self.parent.size
if correctedsize[0] > correctedsize[1]*(10/9):
correctedsize[0] = correctedsize[1]*(10/9)
elif correctedsize[0] < correctedsize[1]*(10/9):
correctedsize[1] = correctedsize[0]/(10/9)
return correctedsize
class test(App):
game = ObjectProperty(None)
def build(self):
self.game = Board()
return self.game
if __name__ == '__main__':
test().run()
test.kv
<Board>
BackgroundLayout:
canvas.before:
Color:
rgba: 1, 0, 0, 0.5
Rectangle:
size: self.size
pos: self.pos
size: self.FixAspectRatio(self.parent.size)
pos: self.parent.pos
One approach would be to create a Layout that always maximize the children given an aspect ratio. Here is an example::
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
from kivy.properties import NumericProperty
kv = """
ARLayout:
Widget:
canvas:
Color:
rgb: 1, 0, 0
Rectangle:
size: self.size
pos: self.pos
"""
class ARLayout(RelativeLayout):
# maximize the children given the ratio
ratio = NumericProperty(10 / 9.)
def do_layout(self, *args):
for child in self.children:
self.apply_ratio(child)
super(ARLayout, self).do_layout()
def apply_ratio(self, child):
# ensure the child don't have specification we don't want
child.size_hint = None, None
child.pos_hint = {"center_x": .5, "center_y": .5}
# calculate the new size, ensure one axis doesn't go out of the bounds
w, h = self.size
h2 = w * self.ratio
if h2 > self.height:
w = h / self.ratio
else:
h = h2
child.size = w, h
class TestApp(App):
def build(self):
return Builder.load_string(kv)
TestApp().run()
I managed to find a solution I was happy with with a lot of help from kived from the kivy chat.
The below bases the game bounds on a 'background' image, then places a Relative Layout over this background image. For all subsequent children, the coordinate (0, 0) will refer to the bottom left of the (aspect locked) background image, and they can use the 'magnification' property of the Background to adjust their size/position according to the screen size.
Provided any gamebounds.png image, the below code will implement this. Note that the RelativeLayout is shaded red (with 50% transparency) just to show its position (and show that it matches the gamebounds.png image size and position at all times):
test.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.properties import (ObjectProperty,
NumericProperty,
ReferenceListProperty,
ListProperty)
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
class Game(FloatLayout):
pass
class Background(Image):
offset = ListProperty()
magnification = NumericProperty(0)
class Bounds(RelativeLayout):
pass
class test(App):
game = ObjectProperty(None)
def build(self):
self.game = Game()
return self.game
if __name__ == '__main__':
test().run()
test.kv
<Game>
Background:
id: BackgroundId
#Provide the image that will form the aspect-locked bounds of the
#game. Image widgets have aspect_lock set to True by default.
source: 'gamebounds.png'
#All the image to stretch to fill the available window space.
allow_stretch: True
size: self.parent.size
#Find the coordinates of the bottom-left corner of the image from
#the bottom-left corner of the window. Call this the 'offset'.
offset: [self.center_x - (self.norm_image_size[0]/2),
self.center_y - (self.norm_image_size[1]/2)]
#Find out the factor by which the image is magnified/shrunk from
#its 'default' value:
magnification: self.norm_image_size[0] / self.texture_size[0]
Bounds:
#The canvas below isn't needed, it's just used to show the
#position of the RelativeLayout
canvas:
Color:
rgba: 1, 0, 0, 0.5
Rectangle:
size: self.size
pos: (0, 0)
#Set the position of the RelativeLayout so it starts at the
#bottom left of the image
pos: self.parent.offset
#Set the size of the RelativeLayout to be equal to the size of
#the image in Background
size: self.parent.norm_image_size

Categories