problem with collision in kivy (simulating gravity) - python

I have a simple code in kivy where the ball bounces off the paddle and what i would like to achieve is make ball grounded after the bouncing of the ball ends. So i would like to simulate gravity. Problem is that collide_widget does not detect collision properly. I expect collide_widget will detect collision when ball.y == paddle.top but it detect collision when ball.y < paddle.top and it is always different depending on starting position of ball.
gravity.py
import kivy
kivy.require('1.11.1')
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty
from kivy.vector import Vector
from kivy.clock import Clock
DELTA_TIME = 1.0 / 60.0
class Ball(Widget):
velocity_x = NumericProperty(0)
velocity_y = NumericProperty(0)
velocity = ReferenceListProperty(velocity_x, velocity_y)
def move(self, dt):
self.pos = Vector(*self.velocity) *dt + self.pos
class Paddle(Widget):
velocity_x = NumericProperty(0)
velocity_y = NumericProperty(0)
velocity = ReferenceListProperty(velocity_x, velocity_y)
def move(self, dt):
self.pos = Vector(*self.velocity) *dt + self.pos
class GravityBall(Ball):
def update(self, dt, idle):
if idle == 0:
self.velocity_y -= 20
self.move(dt)
else:
self.velocity_y = 0
self.move(dt)
class GravityGame(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.idle = 0
self.ball = GravityBall()
self.ball.center = (200,300)
self.add_widget(self.ball)
self.paddle = Paddle()
self.paddle.center = (200,100)
self.add_widget(self.paddle)
Clock.schedule_interval(self.update, DELTA_TIME)
def update(self, dt):
self.ball.update(dt, self.idle)
self.paddle.move(dt)
if self.ball.collide_widget(self.paddle) and self.ball.y+5 >= self.paddle.top:
self.ball.velocity_y *= -1
self.idle = 0
elif self.ball.collide_widget(self.paddle) and self.ball.y+5 < self.paddle.top:
self.idle = 1
class GravityApp(App):
def build(self):
return GravityGame()
if __name__ == '__main__':
GravityApp().run()
gravity.kv
#:kivy 1.11.1
<GravityBall>:
size: 30, 30
canvas:
Ellipse:
pos: self.pos
size: self.size
<Paddle>:
size: 100, 30
canvas:
Color:
rgb: 1, 0, 1
Rectangle:
pos: self.pos
size: self.size

The collide_point() method is not likely to capture the exact moment when your ball touches the paddle, so you must an approximation. Here is a modified version of your GravityGame class that does so:
class GravityGame(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.idle = 0
self.ball = GravityBall()
self.ball.center = (200, 300)
self.add_widget(self.ball)
self.paddle = Paddle()
self.paddle.center = (200, 100)
self.add_widget(self.paddle)
self.sched = Clock.schedule_interval(self.update, DELTA_TIME) # save a reference to this event
def update(self, dt):
self.ball.update(dt, self.idle)
self.paddle.move(dt)
# capture approximate collision event
if self.ball.collide_widget(self.paddle):
self.ball.y = self.paddle.top # adjust ball position to top of paddle
self.ball.velocity_y *= -0.75 # collision is not perfectly elastic, so use a value between 0 and -1
# check if ball velocity is nearly stopped (does not move a single pixel in an interval)
if self.ball.velocity_y * DELTA_TIME < 1.0:
self.idle = 1
self.sched.cancel() # stop updating
else:
self.idle = 0
Note a reference to the scheduled update event is saved, so that the updates can be cancelled when the ball stops. The reversing of the velocity on a collision should emulate a non-elastic collision, so I have changed *= -1 to *= -0.75. Finally, if the ball velocity is low enough that the ball does not move in a time interval, then consider the ball to have stopped.

Related

Rotate individual Kivy elements

I have a helper class that I use to spawn rotating images to the screen. The problem is, when I try to rotate the images, the whole layout rotates instead, although I do use PushMatrix and PopMatrix. They seem to have no effect and I cannot understand why.
I have also tried to separate everything with canvas.before and canvas.after, but got the same result.
The helper class:
class WidgetDrawer(Widget):
def __init__(self, imageStr, windowsize, **kwargs):
super(WidgetDrawer, self).__init__(**kwargs)
# this is part of the**kwargs notation
# if you haven't seen with before, here's a link
# http://effbot.org/zone/python-with-statement.html
self.WindowHeigth, self.WindowWidth = windowsize
with self.canvas:
PushMatrix()
# setup a default size for the object
self.size = (self.WindowWidth*.002*25, self.WindowWidth*.002*25)
# center the widget
self.pos = (self.center_x, self.center_y)
self.rot = Rotate()
self.rot.origin = self.pos
self.rot.axis = (0, 0, 1)
self.rot.angle = 30
# this line creates a rectangle with the image drawn on top
self.rect_bg = Rectangle(source=imageStr,
pos=self.pos,
size=self.size)
PopMatrix()
# this line calls the update_graphics_pos function every time the
# position variable is modified
self.bind(pos=self.update_graphics_pos)
self.velocity_x = 0 # initialize velocity_x and velocity_y
self.velocity_y = 0
def update_graphics_pos(self, instance, value):
# if the widgets position moves, the rectangle that contains the image
# is also moved
self.rect_bg.pos = value
# use this function to change widget position
def setPos(self, xpos, ypos):
self.x = xpos
self.y = ypos
def move(self):
self.x = self.x + self.velocity_x
self.y = self.y + self.velocity_y
if self.y > self.WindowHeigth*0.95:
# don't let the ship go up too high
self.y = self.WindowHeigth*0.95
if self.y <= 0:
self.y = 0 # set heigth to 0 if ship too low
def update(self):
# the update function moves the astreoid. Other things could happen
# here as well (speed changes for example)
self.move()
Edit: added minimal reproducible example of the above class in being used: The bellow code spawns items that go from right lo left (and should rotate, but instead the whole layout rotates)
from kivy.config import Config
# don't make the app re-sizeable
Config.set('graphics', 'resizable', False)
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.clock import Clock
from kivy.graphics.vertex_instructions import Rectangle
from kivy.properties import partial
from kivy.graphics.context_instructions import PopMatrix, PushMatrix, Rotate
from random import randint
class WidgetDrawer(Widget):
# This widget is used to draw all of the objects on the screen
# it handles the following:
# widget movement, size, positioning
# whever a WidgetDrawer object is created, an image string needs to be
# specified, along with the windowsize tuple
# example: wid - WidgetDrawer('./image.png', (Window.height, Window.width))
# windowsize is used for the default size and position,
# but can pe updated later
def __init__(self, imageStr, windowsize, **kwargs):
super(WidgetDrawer, self).__init__(**kwargs)
# this is part of the**kwargs notation
# if you haven't seen with before, here's a link
# http://effbot.org/zone/python-with-statement.html
self.WindowHeigth, self.WindowWidth = windowsize
with self.canvas:
PushMatrix()
# setup a default size for the object
self.size = (self.WindowWidth*.002*25, self.WindowWidth*.002*25)
# center the widget
self.pos = (self.center_x, self.center_y)
self.rot = Rotate()
self.rot.origin = self.pos
self.rot.axis = (0, 0, 1)
self.rot.angle = 30
# this line creates a rectangle with the image drawn on top
self.rect_bg = Rectangle(source=imageStr,
pos=self.pos,
size=self.size)
PopMatrix()
# this line calls the update_graphics_pos function every time the
# position variable is modified
self.bind(pos=self.update_graphics_pos)
self.velocity_x = 0 # initialize velocity_x and velocity_y
self.velocity_y = 0
def update_graphics_pos(self, instance, value):
# if the widgets position moves, the rectangle that contains the image
# is also moved
self.rect_bg.pos = value
# use this function to change widget position
def setPos(self, xpos, ypos):
self.x = xpos
self.y = ypos
def move(self):
self.x = self.x + self.velocity_x
self.y = self.y + self.velocity_y
if self.y > self.WindowHeigth*0.95:
# don't let the ship go up too high
self.y = self.WindowHeigth*0.95
if self.y <= 0:
self.y = 0 # set heigth to 0 if ship too low
def update(self):
# the update function moves the astreoid. Other things could happen
# here as well (speed changes for example)
self.move()
class Asteroid(WidgetDrawer):
# Asteroid class. The flappy ship will dodge these
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def update(self):
# self.rot.angle += 1 # this should animate the object, but rotates the layout
super().update()
class Game(Widget):
# this is the main widget that contains the game.
def __init__(self, **kwargs):
super(Game, self).__init__(**kwargs)
self.windowsize = (Window.height, Window.width)
self.asteroidList = [] # use this to keep track of asteroids
self.minProb = 1700 # this variable used in spawning asteroids
def addAsteroid(self):
# add an asteroid to the screen
# self.asteroid
imageNumber = randint(1, 4)
imageStr = './sandstone_'+str(imageNumber)+'.png' #change the image here
tmpAsteroid = Asteroid(imageStr, self.windowsize)
tmpAsteroid.x = Window.width*0.99
# randomize y position
ypos = randint(0, 16)
ypos = ypos*Window.height*.0625
tmpAsteroid.y = ypos
tmpAsteroid.velocity_y = 0
vel = 10
tmpAsteroid.velocity_x = -0.1*vel
self.asteroidList.append(tmpAsteroid)
self.add_widget(tmpAsteroid)
def update(self, dt):
# This update function is the main update function for the game
# All of the game logic has its origin here
# events are setup here as well
# update game objects
# update asteroids
# randomly add an asteroid
tmpCount = randint(1, 1800)
if tmpCount > self.minProb:
self.addAsteroid()
if self.minProb < 1700:
self.minProb = 1900
self.minProb = self.minProb - 1
for k in self.asteroidList:
if k.x < -20:
self.remove_widget(k)
self.asteroidList.remove(k)
k.update()
class ClientApp(App):
def build(self):
# this is where the root widget goes
# should be a canvas
parent = Widget() # this is an empty holder for buttons, etc
Window.clearcolor = (0, 0, 0, 1.)
game = Game()
# Start the game clock (runs update function once every (1/60) seconds
# Clock.schedule_interval(app.update, 1.0/60.0)
parent.add_widget(game)
Clock.schedule_interval(game.update, 1.0/60.0)
# use this hierarchy to make it easy to deal w/buttons
return parent
if __name__ == '__main__':
ClientApp().run()
You code rotates all your Asteroids by the same amount and that rotation does not vary as time goes on. You can fix that by updating the rotation with every call to update(). Try modifying your code like this:
First define a delta_angle for each Asteroid in its __init__() method:
self.delta_angle = randint(-3, 3)
This gives each Asteroid its own random rotation.
Then create a method to actually update the rotation in the WidgetDrawer:
def rotate(self):
self.rot.origin = self.center
self.rot.angle += self.delta_angle
Then call this new method from the update() method:
def update(self):
# the update function moves the astreoid. Other things could happen
# here as well (speed changes for example)
self.move()
self.rotate()
Now, each Asteroid should rotate independently as time passes.

Play Again function in kivy

I am currently building a game with kivy. Everything works fine except the play again button.
So here is what I want for my play again. The score should be set to 0 and everything has to freeze and a play again button should appear. When I click on it the enemies should respawn to the top.
I tried to t´do it with Clock.schedule_interval and Clock.unschedule_interval. But that only works for the resume button. When I try it with enemies. Everything freezes but then when I click play again. Nothing happens. And the score also doesn't go to 0.
Here is my code:
Main GameClass: The play again button appers after stop game. And then when I click on it it does not unfreeze
class PongGame(Widget):
ball = ObjectProperty(None)
ship = ObjectProperty(None)
enemy = ObjectProperty(None)
badenemy = ObjectProperty(None)
resume_button = ObjectProperty(None)
play_again = ObjectProperty(None)
x_change = NumericProperty(3)
start_button = ObjectProperty(None)
label_text = StringProperty()
def __init__(self, **kwargs):
super(PongGame, self).__init__(**kwargs)
self.count = 0
self.label_text = str(self.count)
def increment(self, **kwargs):
self.count += 5
self.label_text = str(self.count)
print(self.label_text)
enemies = ListProperty([])
badenemies = ListProperty([])
def add_enemy(self, *args):
enemy = Enemy()
enemy.pos = (random.randint(0,600),random.randint(30,600))
self.add_widget(enemy)
self.enemies.append(enemy)
def add_badenemy(self, *args):
badenemy = BadEnemy()
badenemy.pos = (random.randint(0,600),randint(350,600))
self.add_widget(badenemy)
self.badenemies.append(badenemy)
def update(self,dt):
self.ball.move()
for enemy in self.enemies:
enemy.move()
enemy.collision(self.ball)
enemy.ship_collision(self.ship)
for badenemy in self.badenemies:
badenemy.move()
def stop(self):
Clock.unschedule(self.update)
Clock.unschedule(self.add_enemy)
self.resume_button.x = self.width / 2.25 -50
self.resume_button.y = self.height - 350
def resume(self):
Clock.schedule_interval(self.update, 1 / 60.0)
self.count = 0
new_enemy_event = Clock.schedule_interval(self.add_enemy, 2)
self.resume_button.x = 1000
self.resume_button.y = 1000
def stop_game(self):
Clock.unschedule(self.update)
Clock.unschedule(self.add_enemy)
Clock.unschedule(self.add_badenemy)
self.count = 0
self.play_again.x = self.width / 2.25 - 50
self.play_again.y = self.height - 350
Enemy Class. So here I cant call the stopgame function
class Enemy(Widget):
x_change = NumericProperty(3)
y_change = NumericProperty(-50)
score = NumericProperty(0)
def collision(self, ball):
if self.collide_widget(ball):
self.y = 1000
PongGame.increment(self.parent)
ball.x = -1000
ball.y = self.height / self.width
def ship_collision(self,ship):
if self.collide_widget(ship):
PongGame.stop_game(self.parent)
def move(self, *args):
self.x -= self.x_change
if self.x >= self.parent.width - 64:
self.x_change = 3
self.y += self.y_change
elif self.x <= 0:
self.x_change = -3
self.y += self.y_change
App class
class PongApp(App):
def build(self):
game = PongGame()
# game.serve_ball()
# pro second 60 frames are shown
self.sound = SoundLoader.load('kgf.mp3')
self.sound.play()
Clock.schedule_interval(self.loop_sound, 1)
return game
Some of my kv code
<PongGame>:
ball: pong_ball
ship: space_ship
start_button: start_button
resume_button:resume_button
play_again:play_again
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: 'background.png'
Label:
font_size: 40
x:root.width/ 1/4 +70
y:root.height -350
text: 'Your score :'
Label:
font_size: 40
x:root.width/ 1/2
y:root.height -350
text: root.label_text
Button:
id:play_again
font_size:40
size:200,100
x:10000
y:10000
text:'Play Again'
on_release:
self.parent.play_again_game()
PongBall:
id: pong_ball
x: root.width /1.4
y: root.height / root.width
SpaceShip:
id: space_ship
x: root.width / 2.25
y: root.height / root.width +60
So I think my main problem is ,that I cant refer to the method in my game class. And my score is not going to 0. I think I just cant write self.count = 0

How do I make the enemy float around randomly?

I have pretty much everything done for this little game except I can't seem to get the enemy to just aimlessly float around. They spawn at the top of the window but it's rather bland having them stand in line Civil War style. I'm pretty sure it's something to do with class Enemy, but not sure. Any tips on how to get the player and aliens moving around would be appreciated!
import sys, logging, os, random, math, open_color, arcade
#check to make sure we are running the right version of Python
version = (3,7)
assert sys.version_info >= version, "This script requires at least Python {0}.{1}".format(version[0],version[1])
#turn on logging, in case we have to leave ourselves debugging messages
logging.basicConfig(format='[%(filename)s:%(lineno)d] %(message)s', level=logging.DEBUG)
logger = logging.getLogger(__name__)
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
MARGIN = 30
SCREEN_TITLE = "Intergalactic slam"
NUM_ENEMIES = 5
STARTING_LOCATION = (400,100)
BULLET_DAMAGE = 10
ENEMY_HP = 10
HIT_SCORE = 10
KILL_SCORE = 100
PLAYER_HP = 100
class Bullet(arcade.Sprite):
def __init__(self, position, velocity, damage):
'''
initializes the bullet
Parameters: position: (x,y) tuple
velocity: (dx, dy) tuple
damage: int (or float)
'''
super().__init__("assets/Player/PNG/Sprites/Missiles/spaceMissiles_012.png", 0.5)
(self.center_x, self.center_y) = position
(self.dx, self.dy) = velocity
self.damage = damage
def update(self):
'''
Moves the bullet
'''
self.center_x += self.dx
self.center_y += self.dy
class Enemy_Bullet(arcade.Sprite):
def __init__(self, position, velocity, damage):
super().__init__("PNG/laserGreen1.png", 0.5)
(self.center_x, self.center_y) = position
(self.dx, self.dy) = velocity
self.damage = damage
def update(self):
self.center_x += self.dx
self.center_y += self.dy
class Player(arcade.Sprite):
def __init__(self):
super().__init__("assets/Player/PNG/Sprites/Ships/spaceShips_005.png", 0.5)
(self.center_x, self.center_y) = STARTING_LOCATION
self.hp = PLAYER_HP
class Enemy(arcade.Sprite):
def __init__(self, position):
'''
initializes an alien enemy
Parameter: position: (x,y) tuple
'''
super().__init__("PNG/shipGreen_manned.png", 0.5)
self.hp = ENEMY_HP
(self.center_x, self.center_y) = position
class Window(arcade.Window):
def __init__(self, width, height, title):
super().__init__(width, height, title)
file_path = os.path.dirname(os.path.abspath(__file__))
os.chdir(file_path)
self.set_mouse_visible(True)
arcade.set_background_color(open_color.black)
self.bullet_list = arcade.SpriteList()
self.enemy_list = arcade.SpriteList()
self.enemy_bullet_list = arcade.SpriteList()
self.player = Player()
self.score = 0
self.win = False
self.lose = False
def setup(self):
'''
Set up enemies
'''
for i in range(NUM_ENEMIES):
x = 120 * (i+1) + 40
y = 500
enemy = Enemy((x,y))
self.enemy_list.append(enemy)
def update(self, delta_time):
self.bullet_list.update()
self.enemy_bullet_list.update()
if (not (self.win or self.lose)):
for e in self.enemy_list:
for b in self.bullet_list:
if (abs(b.center_x - e.center_x) <= e.width / 2 and abs(b.center_y - e.center_y) <= e.height / 2):
self.score += HIT_SCORE
e.hp -= b.damage
b.kill()
if (e.hp <= 0):
e.kill()
self.score += KILL_SCORE
if (len(self.enemy_list) == 0):
self.win = True
if (random.randint(1, 75) == 1):
self.enemy_bullet_list.append(Enemy_Bullet((e.center_x, e.center_y - 15), (0, -10), BULLET_DAMAGE))
for b in self.enemy_bullet_list:
if (abs(b.center_x - self.player.center_x) <= self.player.width / 2 and abs(b.center_y - self.player.center_y) <= self.player.height / 2):
self.player.hp -= b.damage
b.kill()
if (self.player.hp <= 0):
self.lose = True
def on_draw(self):
arcade.start_render()
arcade.draw_text(str(self.score), 20, SCREEN_HEIGHT - 40, open_color.white, 16)
arcade.draw_text("HP: {}".format(self.player.hp), 20, 40, open_color.white, 16)
if (self.player.hp > 0):
self.player.draw()
self.bullet_list.draw()
self.enemy_bullet_list.draw()
self.enemy_list.draw()
if (self.lose):
self.draw_game_loss()
elif (self.win):
self.draw_game_won()
def draw_game_loss(self):
arcade.draw_text(str("LOSER!"), SCREEN_WIDTH / 2 - 90, SCREEN_HEIGHT / 2 - 10, open_color.white, 30)
def draw_game_won(self):
arcade.draw_text(str("WINNER!"), SCREEN_WIDTH / 2 - 90, SCREEN_HEIGHT / 2 - 10, open_color.white, 30)
def on_mouse_motion(self, x, y, dx, dy):
'''
The player moves left and right with the mouse
'''
self.player.center_x = x
def on_mouse_press(self, x, y, button, modifiers):
if button == arcade.MOUSE_BUTTON_LEFT:
x = self.player.center_x
y = self.player.center_y + 15
bullet = Bullet((x,y),(0,10),BULLET_DAMAGE)
self.bullet_list.append(bullet)
def main():
window = Window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
window.setup()
arcade.run()
if __name__ == "__main__":
main()
You'd want to change the self.center_x and self.center_y of each Enemy on every update, like you're already doing for each Bullet, but make the dx and dy values random in some way. For example:
class Enemy(arcade.Sprite):
def __init__(self, position):
...
(self.center_x, self.center_y) = position
(self.dx, self.dy) = (0, 0)
def update(self):
self.dx += random.random() - 0.5
self.dy += random.random() - 0.5
self.center_x += self.dx
self.center_y += self.dy
Now, this may look more like "twitching wildly" than "floating": many times a second, the thing potentially changes course completely. That's technically random movement, but it's not something a spaceship would do.
If it's too twitchy, make it so that dx and dy change more slowly, for example by dividing the random.random() - 0.5 by a fixed number. If it's too floaty, make it so that every update changes it more.
If you want the enemy to prefer moving down, or towards the player, get out the trigonometry and adjust dx and dy to match.

where to place code for kivy button in a game

hello My problem is that I dont know how to place a button inside my game without making the gamescreen gone.
whenever I try to add return btn1 inside the code it just shows the button. but not the game. I am sure this is a beginner question but looking it up did not work for me.
BTW yes this is the code from a tutorial which I use privately
my code is:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty,\
ObjectProperty
from kivy.vector import Vector
from kivy.clock import Clock
import kivy.uix.button
from kivy.app import App
from kivy.uix.image import Image
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.button import Button
def callback(instance):
print('The button <%s> is being pressed' % instance.text)
btn1 = Button(text='Hello world 1')
btn1.bind(on_press=callback)
btn2 = Button(text='Hello world 2')
btn2.bind(on_press=callback)
class PongPaddle(Widget):
score = NumericProperty(0)
def bounce_ball(self, ball):
if self.collide_widget(ball):
vx, vy = ball.velocity
offset = (ball.center_y - self.center_y) / (self.height / 2)
bounced = Vector(-1 * vx, vy)
vel = bounced * 1.1
ball.velocity = vel.x, vel.y + offset
class PongBall(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 PongGame(Widget):
ball = ObjectProperty(None)
player1 = ObjectProperty(None)
player2 = ObjectProperty(None)
def serve_ball(self, vel=(4, 0)):
self.ball.center = self.center
self.ball.velocity = vel
def update(self, dt):
self.ball.move()
# bounce of paddles
self.player1.bounce_ball(self.ball)
self.player2.bounce_ball(self.ball)
# bounce ball off bottom or top
if (self.ball.y < self.y) or (self.ball.top > self.top):
self.ball.velocity_y *= -1
# went of to a side to score point?
if self.ball.x < self.x:
self.player2.score += 1
self.serve_ball(vel=(4, 0))
if self.ball.x > self.width:
self.player1.score += 1
self.serve_ball(vel=(-4, 0))
def on_touch_move(self, touch):
if touch.x < self.width / 3:
self.player1.center_y = touch.y
if touch.x > self.width - self.width / 3:
self.player2.center_y = touch.y
class PongApp(App):
def build(self):
game = PongGame()
game.serve_ball()
Clock.schedule_interval(game.update, 1.0 / 60.0)
if __name__ == '__main__':
PongApp().run()
If you want one widget to be part of another, this widget must be the son of the other widget or its ancestor is the son of that widget, and when you return btn1 in the build() you are indicating that it is the root, that is, the window, it does not show the game, to make a widget's son an option is to use add_widget():
class PongApp(App):
def build(self):
game = PongGame()
game.add_widget(btn1) # <--
btn2.pos = 100, 100
game.add_widget(btn2) # <--
game.serve_ball()
Clock.schedule_interval(game.update, 1.0 / 60.0)
return game

How to make the widget (ball) start on bottom of the screen - Python/Kivy

I'm trying to make a basic visual exercise in Python and Kivy, but i'm stuck in this part of the code, how to make the ball start on the bottom of the screen?
Here's the .py file:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty,\
ObjectProperty
from kivy.vector import Vector
from kivy.clock import Clock
class PongBall(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 PongGame(Widget):
ball = ObjectProperty(None)
def serve_ball(self, vel=(0, 4)):
self.ball.center = self.center
self.ball.velocity = vel
def update(self, dt):
self.ball.move()
# bounce ball off bottom or top or left or right
if (self.ball.y < self.y) or (self.ball.top > self.top):
self.ball.velocity_y = -2
self.ball.velocity_x = -4
# bounce ball off
if(self.ball.x < 0):
self.ball.velocity_x = 4
self.ball.velocity_y = 0
if(self.ball.x + self.ball.width > self.right):
self.ball.velocity_x *= -1
self.ball.velocity_y = 4
class PongApp(App):
def build(self):
game = PongGame()
game.serve_ball()
Clock.schedule_interval(game.update, 1.0 / 60.0)
return game
if __name__ == '__main__':
PongApp().run()
.kv file:
<PongBall>:
size: 50, 50
canvas:
Ellipse:
pos: self.pos
size: self.size
<PongGame>:
ball: pong_ball
PongBall:
id: pong_ball
center: self.parent.center
This code make the widget starts on center of the screen, i'm trying to make start on bottom, without success
Thanks for any tips!
The problem in your case is that the PongBall's initial position is the center of the PongGame:
<PongGame>:
ball: pong_ball
PongBall:
id: pong_ball
center: self.parent.center # <---
What you must do is that when you create the initial position is established. Another error is that you are launching the game before the window is displayed, instead of launching it in build() you must do it in on_start():
*.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty,\
ObjectProperty
from kivy.vector import Vector
from kivy.clock import Clock
class PongBall(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 PongGame(Widget):
ball = ObjectProperty(None)
def serve_ball(self, vel=(0, 4)):
self.ball.center = self.initial_pos
self.ball.velocity = vel
def update(self, dt):
self.ball.move()
# bounce ball off bottom or top or left or right
if self.ball.y < self.y or self.ball.top > self.top:
self.ball.velocity_y = -2
self.ball.velocity_x = -4
# bounce ball off
if self.ball.x < 0:
self.ball.velocity_x = 4
self.ball.velocity_y = 0
if self.ball.x + self.ball.width > self.right:
self.ball.velocity_x *= -1
self.ball.velocity_y = 4
class PongApp(App):
def build(self):
game = PongGame()
return game
def on_start(self):
self.root.serve_ball()
Clock.schedule_interval(self.root.update, 1.0 / 60.0)
if __name__ == '__main__':
PongApp().run()
*.kv
<PongBall>:
size: 50, 50
canvas:
Ellipse:
pos: self.pos
size: self.size
<PongGame>:
ball: pong_ball
initial_pos: [self.center_x , pong_ball.height/2]
PongBall:
id: pong_ball
In build() an incorrect size of the widgets is obtained since these are updated instants before being launched, it is convenient to use on_start() to obtain the size on which the initial position depends.

Categories

Resources