import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.clock import Clock
from kivy.graphics import Rectangle
from kivy.core.image import Image as CoreImage
from kivy.core.window import Window
class Background(Widget):
def __init__(self, **kw):
super(Background, self).__init__(**kw)
with self.canvas:
texture = CoreImage('space.png').texture
texture.wrap = 'repeat'
self.rect_1 = Rectangle(texture=texture, size=self.size, pos=self.pos)
Clock.schedule_interval(self.txupdate, 0)
def txupdate(self, *l):
t = Clock.get_boottime()
self.rect_1.tex_coords = -(t * 0.001), 0, -(t * 0.001 + 10), 0, -(t * 0.001 + 10), -10, -(t * 0.001), -10
class CosmicPolygons(App):
def build(self):
return Background(size=Window.size)
if __name__ == "__main__":
CosmicPolygons().run()
I've tried many different ways and attempts in order to create a scrolling background in Kivy. This was the best method I could find as it was the only one that didn't crash. But I'm pretty sure it's still outdated as it did not work as intended, in addition to distorting my png greatly.
If anyone has any ideas on how to achieve this, let me know. Thanks in advance.
Image of current app:
Space.png:
Here is another approach that just creates a list of Images and moves them across the screen:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.clock import Clock
from kivy.core.window import Window
class BgImage(Image):
pass
Builder.load_string('''
<BgImage>:
source: 'stars.png'
allow_stretch: True
size_hint: None, 1
width: self.image_ratio * self.height
''')
class Background(FloatLayout):
def __init__(self, **kwargs):
super(Background, self).__init__(**kwargs)
self.deltax = 3 # sets speed of background movement
self.bg_images = [] # list of Images that form the background
Clock.schedule_once(self.set_up_bg)
def set_up_bg(self, dt):
# create BgImages and position them to fill the Background
self.start_x = None
pos_x = -1
while pos_x < self.width:
img = BgImage()
if self.start_x is None:
# starting position of first Image is just off screen
self.start_x = -self.height * img.image_ratio
pos_x = self.start_x
img.pos = (pos_x, 0)
self.bg_images.append(img)
self.add_widget(img)
# calculate starting position of next Image by adding its width
pos_x += self.height * img.image_ratio
# start moving the background
Clock.schedule_interval(self.update, 1.0/30.0)
def update(self, dt):
for img in self.bg_images:
img.x += self.deltax
if img.x > self.width:
# this Image is off screen, move it back to starting position
img.x = self.start_x
class CosmicPolygons(App):
def build(self):
return Background(size=Window.size)
if __name__ == "__main__":
CosmicPolygons().run()
Related
I am trying to build an application with buttons (similar to the calculator), everything was good until I tries to make the app window thinner, the text go out the borders of the button.
I tried font_size: self.width/5 to change the font with the screen size but it works in one situation (width or height) I also found a code
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy.uix.label import Label
class MyLabel(Image):
text = StringProperty('')
def on_text(self, *_):
# Just get large texture:
l = Label(text=self.text)
l.font_size = '1000dp' # something that'll give texture bigger than phone's screen size
l.texture_update()
# Set it to image, it'll be scaled to image size automatically:
self.texture = l.texture
class RootWidget(BoxLayout):
pass
class TestApp(App):
def build(self):
return MyLabel(text='Test test test')
if __name__ == '__main__':
TestApp().run()
that solved this problem but it was using Image in kivy and I don't know how to use it in my situation or in the kv file.
Currently texts are like this:
THANKS!
You can use kivy.core.text.Label to calculate sizes of rendered text, and adjust the font size to make the text fit the Label. Here is a custom Label class that does it:
from kivy.core.text import Label as CoreLabel
from kivy.core.text.markup import MarkupLabel
def MyLabel(Label):
# this Label automatically adjusts font size to fill the Label
def on_text(self, instance, new_text):
self.adjust_font_size()
def on_size(self, instance, new_size):
self.adjust_font_size()
def adjust_font_size(self):
font_size = self.font_size
while True:
# this loops reduces font size if needed
if self.markup:
cl = MarkupLabel(font_name=self.font_name, font_size=font_size, text=self.text)
else:
cl = CoreLabel(font_name=self.font_name, font_size=font_size, text=self.text)
cl.refresh()
if font_size > self.height - self.padding_y * 2:
font_size = self.height - self.padding_y * 2
elif cl.content_width > self.width - self.padding_x * 2 or \
cl.content_height > self.height - self.padding_y * 2:
font_size *= 0.95
else:
break
while True:
# this loop increases font size if needed
if self.markup:
cl = MarkupLabel(font_name=self.font_name, font_size=font_size, text=self.text)
else:
cl = CoreLabel(font_name=self.font_name, font_size=font_size, text=self.text)
cl.refresh()
if cl.content_width * 1.1 < self.width - self.padding_x * 2 and \
cl.content_height * 1.1 < self.height - self.padding_y * 2:
font_size *= 1.05
else:
break
self.font_size = font_size
So i'm making this arcade game and i have a state machine that takes an object(the enemy class) as a constructor parameter, and i have 1 folder for the enemy sprite.
So when i instantiate 1 state machine the game works fine, but when i instantiate 2 state machines(2 enemies) things get weird, when 1 enemy gets to the end of the screen and turn around with kivy's "flip_horizontal" the other enemy turns around too, and its because both state machines are controlling the same enemy folder(same images).
Now if i create an enemy folder for each state machine then it works fine but this is not good. so i thought about manipulating the images in memory, this way the effects of a state machine on the images wont be shared to other state machines.
I tried to do this using CoreImage but the furthest i got was to get a static on the screen and move the "x position", no animation, nothing.
So would anyone help me with this? or if you have a better solution i will be grateful.
heres a sample code:
statemachine.py:
from kivy.app import App
from kivy.properties import Clock, StringProperty, NumericProperty
from kivy.atlas import Atlas
from kivy.uix.image import Image
from itertools import cycle
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.core.window import Window
class EnemyFSM(FloatLayout):
def __init__(self, enemy_object, **kwargs):
super().__init__(**kwargs)
self.enemy = enemy_object
self.states = {'ROAM': 'ROAM',
'CHASE': 'CHASE',
'ATTACK': 'ATTACK',
'DEAD': 'DEAD',
'IDLE': 'IDLE'}
self.state = self.states['ROAM']
self.init_state = True
def update(self, dt):
if self.state == 'ROAM':
self.roam_state()
def idle_state(self):
self.enemy.methods_caller("walk")
def roam_state(self):
self.enemy.methods_caller("walk")
main.py:
from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
from kivymd.uix.floatlayout import MDFloatLayout
from kivy.properties import Clock
from kivy.uix.image import Image
from kivy.properties import NumericProperty
from random import uniform as rdm
from random import randint as rdi
from random import choice
enemy_list = []
from baseenemy import Enemy
from statemachine import EnemyFSM
class LevelOne(MDScreen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self.setup, 1/60)
def setup(self, dt):
self.left_side_spawn = rdi(-500, -100)
self.right_side_spawn = rdi(500, 600)
self.spawn_side = [self.left_side_spawn, self.right_side_spawn]
self.speed = rdi(1, 5)
"""here we instatiate the enemies, 1 works fine, 2 or more then hell breaks loose unless we give each state machine its own image folder"""
for _ in range(1):
self.base_enemy = Enemy(choice(self.spawn_side), rdm(1, 5))
self.add_widget(self.base_enemy)
self.enemy_ai = EnemyFSM(self.base_enemy)
self.add_widget(self.enemy_ai)
enemy_list.append(self.enemy_ai)
Clock.schedule_interval(self.init_game, 1/60)
def init_game(self, dt):
for i in range(len(enemy_list)):
enemy_list[i].update(dt)
class MainApp(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Red"
return None
def on_start(self):
self.fps_monitor_start()
main.kv:
LevelOne:
<EnemyFSM>:
<Enemy>:
source: root.frame_path #the frame_path variable in the enenmy class
size_hint: None, None
size: "150dp", "150dp"
x: root.xpos
y: root.ypos
baseenemy.py:
from kivy.app import App
from kivy.atlas import Atlas
from kivy.properties import Clock, StringProperty, NumericProperty
from kivy.uix.image import Image
from kivy.core.image import Image as CoreImage
import itertools
"""Method to set up the frame names"""
def frame_setup(src):
return [f"1_enemies_1_{src}_{i}" for i in range(20)]
"""Path to the image folder with the atlas"""
def create_frame_path(frame_name):
return {'my_source':
f"atlas://{frame_name}/{frame_name}_atlas/1_enemies_1_{frame_name}_0",
'my_atlas': f"atlas://{frame_name}/{frame_name}_atlas/"}
class Enemy(Image):
xpos = NumericProperty(10)
ypos = NumericProperty(50)
init_frame = create_frame_path('walk')#default frame state 'walk'
frame_path = StringProperty(init_frame['my_source'])#path to the image, this var is also the images source in the .kv file
frame_atlas_path = None
updated_frames = []
previous_frame = None
frame_cycle_called = False
updated_frames_cycle = None
fliped = 0
def __init__(self, spawn_pos, speed, *args, **kwargs):
super().__init__(**kwargs)
self.goal = 0
self.methods_dict = {'walk': self.walk}
self.methods_call = None
self.step = 3
self.xpos = spawn_pos
"""this method, takes a param, and sets up everything
the new frame if the frame is diferent than the current frames'walk'
creates the frame list..."""
def methods_caller(self, new_src):
#The src param is the state of the enemy
#which will tell which frames to set up
#now its just "walk"
self.src = new_src
self.updated_frames = frame_setup(self.src)
#if a state change has occured these lines will run and set up
# a new set of frames according to the new state but for now its just the 'walk' state
if self.previous_frame is not new_src:
self.previous_frame = new_src
self.methods_call = self.methods_dict[new_src]#this get the dict element which is a function and assign to the var
self.frame_path = str(create_frame_path(new_src))
self.frame_atlas_path = create_frame_path(new_src)
self.frame_cycle_called = False
if not self.frame_cycle_called:
self.updated_frames_cycle = itertools.cycle(self.updated_frames)
self.frame_cycle_called = True
self.methods_call()#calling the method we got from the dict element which call the walk method
def walk(self):
#this is where the walking animation happen
if self.xpos >= 700:
self.goal = 1
if self.xpos <= -300:
self.goal = 0
self.frame_path =
self.frame_atlas_path['my_atlas']+next(self.updated_frames_cycle)
image_side = Image(source=self.frame_path).texture
if self.goal == 0 and int(round(image_side.tex_coords[0])) != 0:
image_side.flip_horizontal()
self.fliped = 0
if self.goal == 1 and int(round(image_side.tex_coords[0])) != 1:
image_side.flip_horizontal()
self.fliped = 1
if self.fliped == 0:
self.xpos += self.step
if self.fliped == 1:
self.xpos -= self.step
And finally heres the link to a zip file with the image folder:
https://drive.google.com/file/d/11eGTS_pI690eI5EhxAAoRmT05uNW3VOM/view?usp=sharing
and sorry for the long text.
What I am trying to do is continuously generate new cloud images onto the kivy window -which I have done successfully-, but now I want them to continuously move to the left and off the screen to give the "sky" environment a moving effect. Though I haven't been able to change the images' positions. Any suggestions as to how I could make it work?
from kivy.app import App
from kivy.properties import Clock
from kivy.uix.widget import Widget
from kivy.graphics.context_instructions import Color
from kivy.uix.image import Image
from kivy.core.window import Window
import random
class MainWidget(Widget):
state_game_ongoing = True
clouds = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_interval(self.init_clouds, 1 / 10)
def init_clouds(self, dt):
x = random.randint(-100, self.width)
y = random.randint(-100, self.height)
cloud = Image(source="images/cloudbig.png",
pos=(x, y))
self.add_widget(cloud)
class BalloonGameApp(App):
def build(self):
Window.clearcolor = (.2, .6, .8, 1)
xd = MainWidget()
return xd
BalloonGameApp().run()
I tried adding the clouds into a list using .append(), and calling each cloud and changing its position in a for loop via
clouds = []
def init_clouds(self, dt):
x = random.randint(-100, self.width)
y = random.randint(-100, self.height)
cloud = Image(source="images/cloudbig.png",
pos=(x, y))
self.add_widget(cloud)
clouds.append(cloud)
x += 100
y += 100
for cloud in self.clouds:
cloud.pos(x, y)
I also tried the same method but in an "update" function that was on loop via Clock.schedule_interval() kivy function, which also didn't work the error I usually get is
TypeError: 'ObservableReferenceList' object is not callable
Any help would be appreciated, thanks in advance!
You need to check all Image's Parent. For more soft move lets create another Clock:
Clock.schedule_interval(self.move_clouds,1/100)
Now lets edit this function:
def move_clouds(self,*args):
out_cloud_list = [] #If cloud not in screen remove:
for child in self.children:
if child.pos[0] >= self.width:
out_cloud_list.append(child)
else:
child.pos[0] += 5
for cloud in out_cloud_list:
self.remove_widget(cloud)
First, I am a beginner with kivy, and not very experienced in Python either.
The following program is strictly meant to educate myself in kivy basics, nothing more.
The program is a game where the user controls a gun turret. He shots at objects that bounce around in the program window, while points and time used are shown in the bar below. (Currently occupied by a button.) The keys that turn the turret are "A" and "D", while "P" is for shooting.
The problem I am currently having is this:
I need to generate additional projectile graphics every time the user press "P". At a later stage I will also need to generate and remove target objects graphics when they get hit by a shot, which I guess will involve the same technique.
How should I go about to generate additional canvasĀ“ and\or ellipse objects? (the graphical representation of each shot)
My Python code:
from kivy.config import Config
Config.set('graphics', 'width', '800')
Config.set('graphics', 'height', '900')
Config.set('graphics','resizable',0)
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.properties import NumericProperty
from kivy.properties import ReferenceListProperty
from kivy.properties import BooleanProperty
from kivy.properties import ListProperty
from kivy.clock import Clock
import math
from kivy.core.window import Window
class MainScreen(BoxLayout): #Contains the whole program
#Turret rotation:
the_angle = NumericProperty(0)
#Projectile vector:
the_pos_x = NumericProperty(0)
the_pos_y = NumericProperty(0)
the_pos = ReferenceListProperty(the_pos_x, the_pos_y)
#Shots data:
shot_fired = BooleanProperty(False)
shot_count = NumericProperty(0)
angle_list = ListProperty([])
pos_list_x = ListProperty([])
pos_list_y = ListProperty([])
def update(self, dt):
if self.shot_fired == True:
self.shot_count += 1
self.angle_list.append(self.the_angle)
self.pos_list_x.append(400)
self.pos_list_y.append(400)
self.shot_fired = False
for i in range(0, self.shot_count):
self.shoot(i, self.angle_list[i], self.pos_list_x[i], self.pos_list_y[i])
def shoot(self, cur_shot, the_angle, pos_x, pos_y):
#Make correct shot data:
velocity = 1.5
angle_rad = math.radians(the_angle+90)
pos_x += ( math.cos(angle_rad)*velocity )
pos_y += ( math.sin(angle_rad)*velocity )
#Give the shots positions new data :
self.the_pos_x = pos_x
self.the_pos_y = pos_y
#Update data for the next round of this function:
self.pos_list_x[cur_shot] = pos_x
self.pos_list_y[cur_shot] = pos_y
def keypress(self, *args):
the_key = args[3]
if the_key == "a":
self.the_angle += 1.5
elif the_key == "d":
self.the_angle += -1.5
elif the_key == "p":
self.shot_fired = True
class GameScreen(RelativeLayout): #The game part of the screen
pass
class Gun(Widget): #Holds canvas and drawings for gun
pass
class Projectile(Widget): #Holds canvas and drawings for projectile
pass
class InfoBar(FloatLayout): #Holds point counters and buttons
pass
#Set screen size and prevent resizing:
Window.size = (800, 900)
class MyApp(App):
def build(self):
Builder.load_file("interface.kv")
game = MainScreen()
Window.bind(on_key_down=game.keypress)
Clock.schedule_interval(game.update, 1.0/60.0)
return game
MyApp().run()
My kv code:
<MainScreen>:
the_angle: 45
the_pos: [400,400]
GameScreen:
size_hint:(None,None)
size:800,800
pos_hint:{"top":1, "left":1}
Gun:
canvas.before:
PushMatrix
Rotate:
axis: 0,0,1
angle: root.the_angle
origin: 400,400
canvas:
Rectangle:
size:50,100
pos: 400,400
canvas.after:
PopMatrix
Projectile:
canvas:
Ellipse:
size:10,20
pos: root.the_pos[0], root.the_pos[1]
InfoBar:
size_hint:(None,None)
size:800,100
pos_hint:{"bottom":1, "left":1}
Button:
I am writing my first Kivy app in python only (I'm avoiding kv for now). I have created a custom widget called WorldviewWidget, and I'm trying to use it as a place to draw. With the button widgets, I just give a size_hint and a pos_hint, and they show up where I want them. But with my widget, I don't know how to use the size_hint and position_hint to size the rectangle I am drawing in my WorldviewWidget. Here is the code. Thanks in advance!
#! /usr/bin/python
from kivy.app import App
from kivy.graphics import *
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.uix.button import Button
class WorldviewWidget(Widget):
def __init__(self, **kwargs):
super(WorldviewWidget, self).__init__(**kwargs)
self.canvas.clear()
print self.size, self.pos
with self.canvas:
Color(1, 0, 0, 1, mode='rgba')
# HELP! I want the rectangle to be resized when the window changes size so that it always takes up the same proportion of the screen.
self.rect = Rectangle(size=???, pos=???)
class JFROCS_App(App):
def build(self):
Window.clearcolor = [1,1,1,1]
parent = FloatLayout(size=Window.size)
worldview = WorldviewWidget(size_hint=(0.4, 0.4), pos_hint = {'x':0.2, 'y':0.2})
parent.add_widget(worldview)
start_btn = Button(text='Start', size_hint=(0.1, 0.1), pos_hint={'x':.02, 'y':.7}, background_color=[0,1,0,1])
start_btn.bind(on_release=self.start_simulation)
parent.add_widget(start_btn)
pause_btn = Button(text='Pause', size_hint=(0.1,0.1), pos_hint={'x':.02, 'y':.6}, background_color=[1,1,0,1])
pause_btn.bind(on_release=self.pause_simulation)
parent.add_widget(pause_btn)
stop_btn = Button(text='Stop', size_hint=(0.1,0.1), pos_hint={'x':.02, 'y':.5}, background_color=[1,0,0,1])
stop_btn.bind(on_release=self.stop_simulation)
parent.add_widget(stop_btn)
return parent
def start_simulation(self, obj):
print "You pushed the start button!"
def pause_simulation(self, obj):
print "You pushed the pause button!"
def stop_simulation(self, obj):
print "You pushed the stop button!"
if __name__ == '__main__':
JFROCS_App().run()
I think that this sort of task is predestined for the kivy-language but here is a solution in Python. Basically, I have used the bind-method to make your widget listen for changes in its parent's size. Have a look at this for more information on this mechanism.
from kivy.app import App
from kivy.graphics import *
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.uix.button import Button
class WorldviewWidget(Widget):
def __init__(self, **kwargs):
super(WorldviewWidget, self).__init__(**kwargs)
self.canvas.clear()
print self.size, self.pos
with self.canvas:
Color(1, 0, 0, 1, mode='rgba')
# *changed* ##############################################
self.rect = Rectangle(size=self.size, pos=self.pos)
# *new* ##########################################################
def update_size(self, instance, new_size):
print "UPDATING SIZE", instance, new_size
self.size[0] = new_size[0] * 0.4
self.size[1] = new_size[1] * 0.4
self.rect.size = self.size
self.pos[0] = self.parent.size[0] * 0.2
self.pos[1] = self.parent.size[1] * 0.2
self.rect.pos = self.pos
class JFROCS_App(App):
def build(self):
Window.clearcolor = [1,1,1,1]
parent = FloatLayout(size=Window.size)
# *changed* ##################################################
worldview = WorldviewWidget(size=(0.4*parent.size[0], 0.4*parent.size[1]),
pos=(0.2*parent.size[0], 0.2*parent.size[1]))
# makes sure that the widget gets updated when parent's size changes:
parent.bind(size=worldview.update_size)
parent.add_widget(worldview)
start_btn = Button(text='Start', size_hint=(0.1, 0.1), pos_hint={'x':.02, 'y':.7}, background_color=[0,1,0,1])
start_btn.bind(on_release=self.start_simulation)
parent.add_widget(start_btn)
pause_btn = Button(text='Pause', size_hint=(0.1,0.1), pos_hint={'x':.02, 'y':.6}, background_color=[1,1,0,1])
pause_btn.bind(on_release=self.pause_simulation)
parent.add_widget(pause_btn)
stop_btn = Button(text='Stop', size_hint=(0.1,0.1), pos_hint={'x':.02, 'y':.5}, background_color=[1,0,0,1])
stop_btn.bind(on_release=self.stop_simulation)
parent.add_widget(stop_btn)
return parent
def start_simulation(self, obj):
print "You pushed the start button!"
def pause_simulation(self, obj):
print "You pushed the pause button!"
def stop_simulation(self, obj):
print "You pushed the stop button!"
if __name__ == '__main__':
JFROCS_App().run()
And have a look at the kivy-language -- it takes care of all the binding for you :)