I am learning Kivy, and to make it more fun I am creating a small 2D game. For now its just a tank that can be controlled with WASD and shot projectiles with o.
The problem is that the FPS degrades over time. This happens even if I do nothing in the game. I don´t have an FPS counter, but its something like half the FPS after a minute of gametime.
My feeling is that the problem lies somewhere in the updating of canvas in widgets. Since the slowdowns occour even if the player does nothing for the whole game, its seems like there is data somewhere that is just added and added. I don´t know how to better explain it, other than its weird ...
A quick overview of how the game is programmed so far:
The main widget is the class Game. It detects keypresses and runs "Clock.schedule_interval-function".
The Tank widget is a child of Game. It holds some data and loads the hull and turret sprites via Kivys Image widget, which becomes its child. It has its own update function that updates everything tank-related, including setting the position of its canvas and rotating the hull and turret image canvas. The update-function in the Tank widget class is inwoked by "Clock.schedule_interval" in Game class.
The Shots widget does the same as the Tank widget, only it holds data for each shot fired instead
"Clock schedule_interval"-function holds a list of each shot widget, and deletes them when they get off screen. However, the slowdown problems persist even if no shots are fired.
I have attached the complete code. This might be excessive, but I don´t know wich part of it that provokes slowdowns. If you want to run the game, just put those four python-files in the same folder, and the images in a sub-folder called "images tank".
I hope someone can take a look at it :)
main.py:
#Import my own modules:
import tank
import shot
from stats import Stats
#Import kivy:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.clock import Clock
#Set window size properties:
from kivy.config import Config
Config.set('graphics','resizable',0)
from kivy.core.window import Window
class Game(Widget):
def __init__(self):
#General settings:
super(Game, self).__init__()
Clock.schedule_interval(self.update, 1.0/60.0)
#Add keyboard:
self._keyboard = Window.request_keyboard (callback=None, target=self, input_type="text")
#Bind the keyboard to a function:
self._keyboard.bind(on_key_down=self.keypress)
self._keyboard.bind(on_key_up=self.keyUp)
#P1 tank starting values:
self.keypress_p1 = {"forward":False, "backward":False, "left":False, "right":False, "turret_left":False, "turret_right":False, "fire":False}
#P1 tank widget:
self.tank_p1 = tank.Tank()
self.add_widget(self.tank_p1)
#P1 shots list:
self.shotsList_p1 = []
#Keyboard press detection:
def keypress(self, *args):
key = args[2]
if key == "w":
self.keypress_p1["forward"]=True
if key == "s":
self.keypress_p1["backward"]=True
if key == "a":
self.keypress_p1["left"]=True
if key == "d":
self.keypress_p1["right"]=True
if key == "q":
self.keypress_p1["turret_left"]=True
if key == "e":
self.keypress_p1["turret_right"]=True
if key == "o":
self.keypress_p1["fire"]=True
#Keyboard button up detection:
def keyUp(self, *args):
key = args[1][1]
if key == "w":
self.keypress_p1["forward"]=False
if key == "s":
self.keypress_p1["backward"]=False
if key == "a":
self.keypress_p1["left"]=False
if key == "d":
self.keypress_p1["right"]=False
if key == "q":
self.keypress_p1["turret_left"]=False
if key == "e":
self.keypress_p1["turret_right"]=False
if key == "o":
self.keypress_p1["fire"]=False
#Parent update function that the clock runs:
def update(self, dt):
#Add new shots:
if self.keypress_p1["fire"]:
self.shot = shot.Shots(self.tank_p1.my_pos, self.tank_p1.my_angle+self.tank_p1.my_turretAngle)
self.shotsList_p1.append(self.shot)
self.add_widget(self.shot)
self.keypress_p1["fire"] = False
#P1 tank update:
self.tank_p1.update(self.keypress_p1)
#P1 shot update:
for i in range(len(self.shotsList_p1)-1,-1,-1):
self.shotsList_p1[i].update()
#Remove widgets that are outside the screen:
if ( 0<=self.shotsList_p1[i].my_pos[0]<Stats.winSize[0] and 0<=self.shotsList_p1[i].my_pos[1]<Stats.winSize[1] )==False:
self.remove_widget(self.shotsList_p1[i])
del self.shotsList_p1[i]
class MyApp(App):
def build(self):
game = Game()
Window.size = Stats.winSize
return game
MyApp().run()
tank.py:
#Import own modules:
from stats import Stats
#import python:
import math
#Import Kivy:
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.graphics.context_instructions import PushMatrix, PopMatrix, Rotate, Translate, MatrixInstruction
from kivy.graphics.fbo import Fbo
class Tank(Widget):
def __init__(self):
super(Tank, self).__init__()
#Position and rotation values for the tank:
self.my_pos = [0,0]
self.posChange = [0,0]
self.my_angle = 0
self.angleChange = 0
self.my_turretAngle = 0
self.turretAngleChange = 0
#Hull widget:
self.hull = Hull()
self.add_widget(self.hull)
self.hull.center_x = self.my_pos[0]
self.hull.center_y = self.my_pos[1]
#Turret widget:
self.turret = Turret()
self.add_widget(self.turret)
self.turret.center_x = self.my_pos[0]
self.turret.center_y = self.my_pos[1]
def update(self, keypress):
if keypress["forward"]:
self.my_pos[0] -= Stats.hull_t1["forward"]*math.sin(math.radians(self.my_angle))
self.my_pos[1] += Stats.hull_t1["forward"]*math.cos(math.radians(self.my_angle))
self.posChange[0] = -Stats.hull_t1["forward"]*math.sin(math.radians(self.my_angle))
self.posChange[1] = Stats.hull_t1["forward"]*math.cos(math.radians(self.my_angle))
if keypress["backward"]:
self.my_pos[0] -= Stats.hull_t1["backward"]*math.sin(math.radians(self.my_angle))
self.my_pos[1] += Stats.hull_t1["backward"]*math.cos(math.radians(self.my_angle))
self.posChange[0] = -Stats.hull_t1["backward"]*math.sin(math.radians(self.my_angle))
self.posChange[1] = Stats.hull_t1["backward"]*math.cos(math.radians(self.my_angle))
if keypress["left"]:
self.my_angle += Stats.hull_t1["left"]
self.angleChange = Stats.hull_t1["left"]
if keypress["right"]:
self.my_angle += Stats.hull_t1["right"]
self.angleChange = Stats.hull_t1["right"]
if keypress["turret_left"]:
self.my_turretAngle += Stats.turret_t1["left"]
self.turretAngleChange = Stats.turret_t1["left"]
if keypress["turret_right"]:
self.my_turretAngle += Stats.turret_t1["right"]
self.turretAngleChange = Stats.turret_t1["right"]
#Tank Position:
with self.canvas.before:
PushMatrix()
Translate(self.posChange[0], self.posChange[1])
with self.canvas.after:
PopMatrix()
#Rotate hull image:
with self.hull.canvas.before:
PushMatrix()
self.rot = Rotate()
self.rot.axis = (0,0,1)
self.rot.origin = self.hull.center
self.rot.angle = self.angleChange
with self.hull.canvas.after:
PopMatrix()
#Rotate turret image:
with self.turret.canvas.before:
PushMatrix()
self.rot = Rotate()
self.rot.axis = (0,0,1)
self.rot.origin = self.turret.center
self.rot.angle = self.turretAngleChange + self.angleChange
with self.turret.canvas.after:
PopMatrix()
#Reset pos, angle and turretAngle change values:
self.posChange = [0,0]
self.angleChange = 0
self.turretAngleChange = 0
#--------------------------------------------------------------------------------------------------
class Hull(Image):
def __init__(self):
super(Hull, self).__init__(source="images tank/Tank.png")
self.size = self.texture_size
class Turret(Image):
def __init__(self):
super(Turret, self).__init__(source="images tank/GunTurret.png")
self.size = self.texture_size
shot.py:
#Import own modules:
from stats import Stats
#import python:
import math
from copy import copy
#Import Kivy:
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.graphics.context_instructions import PushMatrix, PopMatrix, Rotate, Translate, MatrixInstruction
from kivy.graphics.fbo import Fbo
class Shots(Widget):
def __init__(self, tankPos, turretAngle):
super(Shots, self).__init__()
#Shot data:
self.my_pos = copy(tankPos)
self.my_angle = turretAngle
self.angleChange = self.my_angle
self.posChange = [ -Stats.shot_t1["speed"]*math.sin(math.radians(self.my_angle)), Stats.shot_t1["speed"]*math.cos(math.radians(self.my_angle)) ]
#Add image:
self.shotImg = ShotImg()
self.add_widget(self.shotImg)
self.shotImg.pos = self.my_pos
self.shotImg.center_x = self.my_pos[0]
self.shotImg.center_y = self.my_pos[1]
def update(self):
self.my_pos[0] += self.posChange[0]
self.my_pos[1] += self.posChange[1]
#Shot Position:
with self.canvas.before:
PushMatrix()
Translate(self.posChange[0], self.posChange[1])
with self.canvas.after:
PopMatrix()
#Rotate shot image:
if self.angleChange != 0:
with self.shotImg.canvas.before:
PushMatrix()
self.rot = Rotate()
self.rot.axis = (0,0,1)
self.rot.origin = self.shotImg.center
self.rot.angle = self.angleChange
with self.shotImg.canvas.after:
PopMatrix()
self.angleChange = 0
class ShotImg(Image):
def __init__(self):
super(ShotImg, self).__init__(source="images tank/Bullet.png")
self.size = self.texture_size
stats.py:
class Stats:
winSize = (800,800)
hull_t1 = {"forward":2, "backward":-2, "left":2, "right": -2}
turret_t1 = {"left":5, "right":-5}
shot_t1 = {"speed":3}
images should go in a subfolder called "images tank":
Bullet
GunTurret
Tank
The way you manage your position will create 8 new instructions per tank, and 6 per shot, every frame, which, at 60fps, will quickly create thousands of instructions, and for kivy will be slower and slower to process them.
#Tank Position:
with self.canvas.before:
PushMatrix()
Translate(self.posChange[0], self.posChange[1])
with self.canvas.after:
PopMatrix()
you don't want to do that, you want to create one Translate insruction (and same for Rotate) in your widget, and update it, move this block to __init__ and save Translate to self.translate for example, then in update, instead of using posChange, simply do self.translate.x, self.translate.y = self.my_pos
apply the same logic for rotation, and for shots, and the performances should be much more stable over time.
I had the same issue, I'm working on a space invaders game and it happened to be the way I treated my logic, I used "with self.canvas"
to draw my widgets, which appeared to be on the long term exhaustive as widgets that get generated on canvas are not being cleared, which is an important thing to be concerned about.
I Fixed it with creating a class for my objects (enemy, bullet, player) and when I need lets say 6 enemies to be created, I create a list of enemy widgets to keep track of them, and them add them to my on my main game widget by using self.add_widget(YourWidgetItem)
and simply when the widget has ended its purpose, like an enemy dying, I set my dead variable for this widget to be true, then scan for dead widgets and remove them from my main game widget by using self.remove_widget(YourWidgetItem)
this is one of many other possible solutions, you just need to take into consideration killing the widget that get off your screen to no overload your game on higher levels
You can check my project if it would help:
https://github.com/steveroseik/Invaders_Game
Related
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 have a class that changes the background color constantly with a Clock.schedule_interval in init. I would like to create multiple instances of this class simultaneous; however, I think this means creating multiple threads which isn't allowed? What I would like is the top half to be changing colors while the bottom half is changing colors differently. What is happening is only the bottom half is changing colors while the top half is black. So here is the code.
The /teacher/main.py file is
from kivy.app import App
from kivy.clock import Clock
from kivy.graphics import Color
from kivy.properties import NumericProperty, ReferenceListProperty
from kivy.uix.gridlayout import GridLayout
from kivy.uix.widget import Widget
from random import randint
class ChangingBackgroundColor(Widget):
r = NumericProperty(.5)
g = NumericProperty(.5)
b = NumericProperty(.5)
a = NumericProperty(1)
color = ReferenceListProperty(r, g, b, a)
def __init__(self,**kwargs):
super(ChangingBackgroundColor, self).__init__(**kwargs)
Clock.schedule_interval(self.update, .2)
def update(self, dt):
position = randint(0,2) # change to randint(0,3) to change a as well
direction = randint(0,1)
if direction == 0:
if self.color[position] == 0:
self.color[position] += .1
else:
self.color[position] -= .1
elif direction == 1:
if self.color[position] == 1:
self.color[position] -= .1
else:
self.color[position] += .1
self.color[position] = round(self.color[position], 2)
self.canvas.add(Color(self.color))
class TeachingApp(App):
def build(self):
grid = GridLayout(rows=2)
a = ChangingBackgroundColor()
b = ChangingBackgroundColor()
grid.add_widget(a)
grid.add_widget(b)
return grid
if __name__ == '__main__':
TeachingApp().run()
and the /teacher/teaching.kv file is
#:kivy 1.0.9
<ChangingBackgroundColor>:
canvas:
Color:
rgba: self.color
Rectangle:
size: self.width, self.height
I looked here and and still fuzzy on the threading issue. Clock documentation.
This is my first question I have submitted so if I did anything wrong regarding question submission please let me know.
Your code is fine, using Clock.schedule_interval doesn't use threads (it's all in the main thread), and can be used from other threads even if you did have them, although callbacks would still happen in the main thread.
The problem is that your Rectangle entry in the kv needs to have:
pos: self.pos
Without this, both rectangles have the default pos of (0, 0), so the second one is on top of the first one and the top half of the screen is black.
I'm trying to make a game where you can move a character around screen and I had it so that if my character ran into a picture of a tree, the character would stop moving. After getting this to work, What I tried to do was change the code so instead of just using the tree variable, I wanted to iterate through a list of widgets, so that if my character runs into any of them, my character stops moving. What's strange is that it works when I have only one widget in the list. For example if I put list[0] or list[1] in my code, then my character will stop when encountering those widgets. But again, if I have more than one widget in the list and try to iterate through the list, it does not work, my character does not stop when encountering any of the widgets.
I'm guessing I did something wrong with the for loop. Ultimately I want it so that if my character runs into any of the images in the list, the character will stop moving.
Here is a snippet of my for loop, and below I have included the entire code (if that helps).
Snippet:
def on_keyboard_down(self, keyboard, keycode, text, modifiers):
if keycode[1] == 'left':
self.source = 'selectionscreen/left.zip'
self.anim_delay=.20
if self.x < (Window.width * .25):
bglayout.x += 4
else:
for i in listofwidgets:
if self.collide_widget(i):
self.x -=0
else:
self.x -=6
Full Code:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.label import Label
from kivy.uix.behaviors import ButtonBehavior
from kivy.core.audio import SoundLoader
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import FallOutTransition
from kivy.clock import Clock
from kivy.graphics import Color
gamelayout = FloatLayout(size=(300, 300))
bglayout = FloatLayout()
characterselectionlayout = GridLayout(cols=2)
class Game(Screen):
class Bg(Image):
def __init__(self, **kwargs):
super(Bg, self).__init__(**kwargs)
self.allow_stretch = True
self.size_hint = (None, None)
self.size = (1440, 1440)
class Npcs(Image):
def __init__(self, **kwargs):
super(Npcs, self).__init__(**kwargs)
self.allow_stretch=True
class MoveableImage(Image):
def __init__(self, **kwargs):
super(MoveableImage, self).__init__(**kwargs)
self._keyboard = Window.request_keyboard(None, self)
if not self._keyboard:
return
self._keyboard.bind(on_key_down=self.on_keyboard_down)
self._keyboard.bind(on_key_up=self.on_keyboard_up)
self.y = (Window.height/2.1)
self.app = App.get_running_app()
def on_keyboard_down(self, keyboard, keycode, text, modifiers):
if keycode[1] == 'left':
self.source = 'selectionscreen/left.zip'
self.anim_delay=.20
if self.x < (Window.width * .25):
bglayout.x += 4
else:
for i in listofwidgets:
if self.collide_widget(i):
self.x -=0
else:
self.x -=6
elif keycode[1] == 'right':
self.source ='selectionscreen/right.zip'
self.anim_delay=.20
if self.x > (Window.width * .70):
bglayout.x -= 4
else:
for i in listofwidgets:
if self.collide_widget(i):
self.x += 0
else:
self.x += 6
else:
return False
return True
class gameApp(App):
def build(self):
global sm
sm = ScreenManager()
game = Game(name='game')
sm.add_widget(game)
global listofobject
listofobject = []
hero = MoveableImage(source='selectionscreen/right1.png', size_hint=(None,None), allow_stretch = False, size=(40, 65))
self.tree = Npcs(source='selectionscreen/tree.zip', allow_stretch=False, size_hint=(None,None), pos_hint={'x':.20, 'y':.30}, size=(50, 50), pos=(300, 300))
self.testdude = Npcs(source='selectionscreen/testdude.png', allow_stretch=False, size_hint=(None,None), pos_hint={'x':.60, 'y':.70}, size=(100, 124), pos=(800, 900))
listofwidgets.append(self.tree)
listofwidgets.append(self.testdude)
self.background=Bg(source='selectionscreen/background12.png', pos_hint={'x':0, 'y':0})
bglayout.add_widget(self.background)
bglayout.add_widget(self.tree)
bglayout.add_widget(self.testdude)
gamelayout.add_widget(bglayout)
gamelayout.add_widget(hero)
game.add_widget(gamelayout)
return sm
if __name__ == '__main__':
gameApp().run()
The issue you're having is that your character is moved if he fails to collide with each of your objects. So if you have three objects and he doesn't hit any, he'll move at three times his normal speed. If he collides with one of them, he'll be slowed to twice his normal speed, but keep moving.
You need to change your code to test that he doesn't collide with anything before allowing him to move. The built in any function may help with this (call it on a generator expression):
if any(self.collide_widget(i) for i in listofwidgets):
self.x -=0
else:
self.x -=6
If you wanted to write an explicit loop, an equivalent one would be:
for i in listofwidgets:
if self.collide_widget(i):
self.x -= 0
break
else: # this block is run only if the loop ran to the end without breaking
self.x -= 6
Walk plus type() is handy here.
for widget in gameApp.walk(self, loopback=False):
if type(widget) == Npcs:
This will get you a particular widgets. You could think about rewriting your code somehow like this :
for widget in gameApp.walk(self, loopback=False):
if type(widget) == Npcs:
if not self.collide_widget(widget):
x-=6
break
But this looks wrong way round if this game will have a lots, lots of widgets.