charm ah
charm ah

Reputation: 93

Why does rescheduling Clock intervals cause image positions to reset?

Within the program, one can see that as the ball bounces around, one has the ability to open settings page from the top right corner. Doing so pauses the ball's motion and opens the settings page.

Sorry if this is an obvious question, but I am again stumped by the strange inner workings of Kivy, and the docs aren't much use on these types of issues.

Problem

Ball always start at centre position. Want ball to continue / resume from previous position before switching screen?

Steps to recreate problem

  1. Click on label, "Tap to start". Ball started bouncing from centre position
  2. Click on cogwheel image. Settings screen is displayed
  3. Click on "x" to close Settings screen. Ball started bouncing from centre position.

Code:

from kivy.lang import Builder
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy import Config
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition,\
SlideTransition
from kivy.uix.widget import Widget
from kivy.animation import Animation
from kivy.properties import NumericProperty, ReferenceListProperty,\
ObjectProperty
from kivy.clock import Clock
from kivy.vector import Vector
from random import randint

Builder.load_string('''
<Ball>:
    size_hint: None, None
    source: '58-Breakout-Tiles.png'
    pos: self.pos
    size: 15, 15



<SettingsScreen>:
    close: close
    AnchorLayout:
        anchor_x: 'left'
        anchor_y: 'top'
        Image:
            id: close
            size_hint: .03, .03
            source: 'grey_crossGrey.png'

    GridLayout:
        cols: 2
        Label:
            font_name: 'vgafix.fon'
            text: 'Music: '
        Switch:
            active: True
        Label:
            font_name: 'vgafix.fon'
            text: 'Sounds: '
        Switch:
            active: True

<MenuScreen>:
    cog: cog
    AnchorLayout:
        anchor_x: 'right'
        anchor_y: 'top'
        Image:
            id: cog
            size_hint: .03, .03
            source: 'settings-cog.png'

    BoxLayout:
        orientation: 'vertical'
        Image:
            source: 'brickbreaker log.png'
        Label:
            font_name: 'vgafix.fon'
            text: 'Tap to start'

<GameScreen>:
    ball: ball
    cog: cog
    AnchorLayout:
        anchor_x: 'right'
        anchor_y: 'top'
        Image:
            id: cog
            size_hint: .03, .03
            source: 'settings-cog.png'

    Ball:
        id: ball
        size_hint: None, None
        center: self.parent.center
''')

Config.set('graphics', 'multisamples', '0')



class Ball(Image):
    velocityX, velocityY = NumericProperty(0), NumericProperty(0)
    velocity = ReferenceListProperty(velocityX, velocityY)

    def move(self):
        self.pos = Vector(*self.velocity) + self.pos

class Player(Widget):
    pass

class Brick(Widget):
    pass




class SettingsScreen(Screen):
    def __init__(self, **kwargs):
        super(SettingsScreen, self).__init__(**kwargs)
        self.previous = False

    def on_touch_down(self, touch):
        if self.close.collide_point(*touch.pos):
            sm.transition = SlideTransition(direction = 'right')
            if self.previous == 'game':
                sm.get_screen('game').interval()
            sm.current = self.previous

class MenuScreen(Screen):
    def __init__(self, **kwargs):
        super(MenuScreen, self).__init__(**kwargs)

    def on_touch_down(self, touch):
        if self.cog.collide_point(*touch.pos):
            sm.transition = SlideTransition(direction = 'left')
            sm.get_screen('settings').previous = 'menu'
            sm.current = 'settings'
        else:
            sm.transition = FadeTransition()
            sm.current = 'game'

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

    def on_touch_down(self, touch):
        if self.cog.collide_point(*touch.pos):
            sm.transition = SlideTransition(direction = 'left')
            sm.get_screen('settings').previous = 'game'
            self.interval.cancel()
            sm.current = 'settings'

    def initBall(self):
        self.ball.center = self.center
        self.ball.velocity = Vector(0, 4).rotate(randint(0, 360))

    def update(self, dt):
        self.ball.move()
        if (self.ball.y < 0) or (self.ball.y > self.height-15):
            self.ball.velocityY *= -1
        # bounce off left and right
        if (self.ball.x < 0) or (self.ball.x > self.width-15):
            self.ball.velocityX *= -1


sm = ScreenManager(transition = FadeTransition())
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(GameScreen(name='game'))
sm.add_widget(SettingsScreen(name='settings'))



class BrickBreakerInsanityApp(App):
    def build(self):
        return sm

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

Code assets (required):

https://drive.google.com/open?id=1GAnv5DfjNUuAXTybmsan90Dm0OuSVOfb

https://i.sstatic.net/rR799.png

https://i.sstatic.net/ngYvL.png

https://i.sstatic.net/AuxI3.png

https://i.sstatic.net/ypd7C.png

Upvotes: 0

Views: 76

Answers (1)

ikolim
ikolim

Reputation: 16031

Root cause

The SlideTransition and direction is causing the ball to start at the centre.

Solution

Remove all references to SlideTransition.

Example

from kivy.lang import Builder
from kivy.app import App
from kivy.uix.image import Image
from kivy import Config
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition,     
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty, StringProperty
from kivy.clock import Clock
from kivy.vector import Vector
from random import randint

Builder.load_string('''
<Ball>:
    size_hint: None, None
    source: './assets/icons/58-Breakout-Tiles.png'
    pos: self.pos
    size: 15, 15

<SettingsScreen>:
    close: close
    AnchorLayout:
        anchor_x: 'left'
        anchor_y: 'top'
        Image:
            id: close
            size_hint: .03, .03
            source: './assets/icons/grey_crossGrey.png'

    GridLayout:
        cols: 2
        Label:
            font_name: "./assets/fonts/vgafix.fon"
            text: 'Music: '
        Switch:
            active: True
        Label:
            font_name: "./assets/fonts/vgafix.fon"
            text: 'Sounds: '
        Switch:
            active: True

<MenuScreen>:
    cog: cog
    
    AnchorLayout:
        anchor_x: 'right'
        anchor_y: 'top'
        Image:
            id: cog
            size_hint: .03, .03
            source: './assets/icons/settings-cog.png'

    BoxLayout:
        orientation: 'vertical'
        Image:
            source: "./assets/icons/brickbreaker log.png"
        Label:
            font_name: "./assets/fonts/vgafix.fon"
            text: 'Tap to start'

<GameScreen>:
    ball: ball
    cog: cog
    
    AnchorLayout:
        anchor_x: 'right'
        anchor_y: 'top'
        Image:
            id: cog
            size_hint: .03, .03
            source: './assets/icons/settings-cog.png'

    Ball:
        id: ball
        size_hint: None, None
        center: self.parent.center
''')

Config.set('graphics', 'multisamples', '0')


class Ball(Image):
    velocityX, velocityY = NumericProperty(0), NumericProperty(0)
    velocity = ReferenceListProperty(velocityX, velocityY)

    def move(self):
        self.pos = Vector(*self.velocity) + self.pos


class Player(Widget):
    pass


class Brick(Widget):
    pass


class SettingsScreen(Screen):

    def __init__(self, **kwargs):
        super(SettingsScreen, self).__init__(**kwargs)
        self.previous = StringProperty('')

    def on_touch_down(self, touch):
        if self.close.collide_point(*touch.pos):
            self.manager.current = self.previous


class MenuScreen(Screen):

    def on_touch_down(self, touch):
        if self.cog.collide_point(*touch.pos):
            self.manager.get_screen('settings').previous = self.manager.current
            self.manager.current = 'settings'
        else:
            self.manager.transition = FadeTransition()
            self.manager.current = 'game'


class GameScreen(Screen):

    def __init__(self, **kwargs):
        super(GameScreen, self).__init__(**kwargs)
        self.initBall()

    def on_pre_enter(self, *args):
        self.interval = Clock.schedule_interval(self.update, 1.0 / 60.0)

    def on_touch_down(self, touch):
        if self.cog.collide_point(*touch.pos):
            self.manager.get_screen('settings').previous = self.manager.current
            self.manager.current = 'settings'

    def initBall(self):
        self.ball.center = self.center
        self.ball.velocity = Vector(0, 4).rotate(randint(0, 360))

    def update(self, dt):
        self.ball.move()
        if (self.ball.y < 0) or (self.ball.y > self.height - 15):
            self.ball.velocityY *= -1
        # bounce off left and right
        if (self.ball.x < 0) or (self.ball.x > self.width - 15):
            self.ball.velocityX *= -1

    def on_pre_leave(self, *args):
        self.interval.cancel()


sm = ScreenManager(transition=FadeTransition())
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(GameScreen(name='game'))
sm.add_widget(SettingsScreen(name='settings'))


class BrickBreakerInsanityApp(App):
    def build(self):
        return sm


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

Upvotes: 1

Related Questions