I am trying to create a simple program in Pyglet that shows an animation and retrieves some mouse input and saves it to a text file. The code I have shows a significant unstable frame rate even when I am not using the mouse.
I have also looked at similar questions and the answers suggest to use a subclassed window, or to call the function on_draw with schedule_interval. I do not know, however, how to use a subclassed window to display my animation and, when I try to call on_draw with schedule_interval I get the error that on_draw does not receives any argument.
This is part of the code I am using:
fps = pyglet.clock.ClockDisplay()# Show FPS
#mywindow.event
def on_mouse_press(x, y, button, modifiers):
global timeStart, file, count
timeNow = time.clock() - timeStart
if button == mouse.LEFT:
print('left click press in {}').format(timeNow)
with open(out_file_name, 'a') as file:
file.write(str(count) +'\t'+ str(timeNow) +'\t'+ '-1\n')
#file.write('' + count + timeNow + 'left click press\n')
count += 1
def update_frames(dt):
global x
x=x+1
#mywindow.event
def on_draw():
pyglet.gl.glClearColor(0,0,0,0)
mywindow.clear()
glColor4f(1,0,0,1)
drawSquare(x,y)#this draws an opengl square
fps.draw()# Show FPS
dt = 1/10.0
pyglet.clock.schedule_interval(update_frames,dt)
pyglet.app.run()
What can I add to the code in order to obtain an stable frame rate?
I'd use something like this instead:
import pyglet
from pyglet.gl import *
from collections import OrderedDict
from time import time
from os.path import abspath
class GUI(pyglet.window.Window):
def __init__(self):
super(GUI, self).__init__(640,340, caption='Test')
pyglet.gl.glClearColor(1, 1, 1, 1)
self.alive = True
self.batches = OrderedDict()
self.batches['apples'] = pyglet.graphics.Batch()
self.framerate = 0, time()
self.count = 0
def render(self, *args):
self.clear()
#glColor4f(1,0,0,1)
#drawSquare(x,y)
if time() - self.framerate[1] > 1:
print('fps:',self.framerate[0])
self.framerate = 0, time()
else:
# Not an optimal way to do it, but it will work.
self.framerate = self.framerate[0]+1, self.framerate[1]
self.flip()
def on_draw(self):
self.render()
def on_close(self):
self.alive = False
def on_key_press(self, symbol, modkey):
pass
def on_key_release(self, symbol, modkey):
pass
def on_mouse_release(self, x, y, button, modifiers):
pass
def on_mouse_press(self, x, y, button, modifiers):
self.count += 1
with open('debug.log', 'w') as fh:
fh.write(str(count))
def on_mouse_motion(self, x, y, dx, dy):
pass
def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
pass
def run(self):
while self.alive:
event = self.dispatch_events()
if event:
print(event)
self.render()
if __name__ == '__main__':
x = GUI()
pyglet.clock.set_fps_limit(60)
x.run()
For one, this code actually produces 60 FPS,
You get way better control over your application and your main loop.
Secondly, the coding style is perhaps a personal preference but throwing stuff into class objects rather than doing a huge list of functions which you attach to say #window.update etc is more to my liking.. The code looks cleaner.
Give it a go, see if it works.
Note: Key here is event = self.dispatch_events() which must be called for each iteration, it's what replaces app.run().
Tie this together with objects and rendering them
class House(pyglet.sprite.Sprite):
def __init__(self):
self.texture = pyglet.image.load(abspath('./image.png'))
super(House, self).__init__(self.texture)
self.x = 0
self.y = 0
self.rotation = 0
self.name = 'house'
self.anchor = 'center'
def swap_image(self, image):
self.texture = pyglet.image.load(abspath(image))
self.image = self.texture
def rotate(self, deg):
self.image.anchor_x = self.image.width / 2
self.image.anchor_y = self.image.height / 2
self.rotation = self.rotation+deg
if self.anchor != 'center':
self.image.anchor_x = 0
self.image.anchor_y = 0
return True
def click(self):
print('Clicked:',self.name)
def work(self, *args):
pass
def click_check(self, x, y):
if x > self.x and x < (self.x + self.width):
if y > self.y and y < (self.y + self.height):
return self
def move(self, x, y):
if self.moveable:
self.x += x
self.y += y
def _draw(self):
self.draw()
in GUI() you'll do:
class GUI(pyglet.window.Window):
def __init__(self):
super(GUI, self).__init__(640,340, caption='Test')
...
self.house = House()
def render(self, *args):
self.house.rotate(1)
self.house._draw()
This should create a "house" (or whatever) and rotate the picture 1 degrees for each render occations, meaning you'll be rotating it 60 degrees a second with a nice flow.
There's more stuff to this than just this, but it's a simplification of what i usually use to squeeze out FPS while still maintaining readable code.. Because graphics get messy quite quickly.
Related
I have been trying to use some "key released" functions from libraries like pynput and so, but I ran in to the problem that if I try using them together with a library called pyglet, that is a library for window-based apps, it won't let me and the program would crash.
I was wondering if there is any way detect key releases without libraries.
P.S: I've tried using the on_key_release function from pyglet but it was buggy for me and even though I wrote something for it to upon key release it usually didn't do it. I have checked my code a thousand times and it's not a problem on my part.
Pressing = False # var to indicate when the user is doing a long press and when he is not
#window.event
def on_key_release(symbol, modifiers):
global Pressing
if((symbol == key.A) | (symbol == key.S) | (symbol == key.W) | (symbol == key.D)):
Pressing = False
pass
and that code causes my player to freeze after i start moving him, and it does this even if i do nothing and just leach the whole on_key_release dunction empty. really weird.
So, your issue is most likely that you're doing Pressing = False if any key is released. Forcing your player object to freeze due to Pressing being False as soon as you release any of your keys.
To work around this, you should store the states of your keys in a variable (I'll call it self.keys), and just before you render stuff, update/check the keys and update your player object accordingly.
Here's a working example of movement on a player object (a red square in this case):
from pyglet import *
from pyglet.gl import *
key = pyglet.window.key
class main(pyglet.window.Window):
def __init__ (self, width=800, height=600, fps=False, *args, **kwargs):
super(main, self).__init__(width, height, *args, **kwargs)
self.x, self.y = 0, 0
self.keys = {}
self.mouse_x = 0
self.mouse_y = 0
square = pyglet.image.SolidColorImagePattern((255, 0, 0, 255)).create_image(40, 40)
self.player_object = pyglet.sprite.Sprite(square, x=self.width/2, y=self.height/2)
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_mouse_motion(self, x, y, dx, dy):
self.mouse_x = x
def on_key_release(self, symbol, modifiers):
try:
del self.keys[symbol]
except:
pass
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
self.keys[symbol] = True
def render(self):
self.clear()
## Movement logic,
# check if key.W is in self.keys (updated via key_press and key_release)
if key.W in self.keys:
self.player_object.y += 1
if key.S in self.keys:
self.player_object.y -= 1
if key.A in self.keys:
self.player_object.x -= 1
if key.D in self.keys:
self.player_object.x += 1
self.player_object.draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
if __name__ == '__main__':
x = main()
x.run()
I've moved away from #window.event because it's easier for me to just copy paste this class/inheritance example since I had it laying around. But you could apply this logic any way you want. Just make sure you don't defined a solid state of pressing or not pressing, check individual keys and update accordingly before you render.
Given that so far I have always used Pygame, I wanted to start using Pyglet, to understand a little how it works.
import pyglet, os
class runGame():
widthDisplay = 1024
heightDiplay = 576
title = "Pokémon Life and Death: Esploratori del proprio Destino"
wra = pyglet.image.load("wra.png")
wrb = pyglet.image.load("wrb.png")
def __init__(self):
platform = pyglet.window.get_platform()
display = platform.get_default_display()
screen = display.get_default_screen()
self.widthScreen = screen.width
self.heightScreen = screen.height
self.xDisplay = int(self.widthScreen / 2 - self.widthDisplay / 2)
self.yDisplay = int(self.heightScreen / 2 - self.heightDiplay / 2)
self.Display = pyglet.window.Window(width=self.widthDisplay, height=self.heightDiplay, caption=self.title, resizable=False)
self.Display.set_location(self.xDisplay, self.yDisplay)
pyglet.app.run()
game = runGame()
Up to here everything is fine and everything works correctly. But I was wrong, in the sense, now that I have to draw something, how should I do? In the sense, can Pyglet stay in a class like Pygame or not?
The marked solution might solve the problem.
However, in my opinion it doesn't really add more functionality than you can already achieve.
If you truly want to make Pyglet into a class, you actually gotta inherit something, or make use of the possibility of programming in a OOP way i recon.
Here's my two cents:
import pyglet
from pyglet.gl import *
from collections import OrderedDict
from time import time
key = pyglet.window.key
class main(pyglet.window.Window):
def __init__ (self, width=1024, height=576, caption="Pokémon Life and Death: Esploratori del proprio Destino", fps=True, *args, **kwargs):
super(main, self).__init__(width, height, *args, **kwargs)
platform = pyglet.window.get_platform()
display = platform.get_default_display()
screen = display.get_default_screen()
self.xDisplay = int(screen.width / 2 - self.width / 2)
self.yDisplay = int(screen.height / 2 - self.height / 2)
self.set_location(self.xDisplay, self.yDisplay)
self.sprites = OrderedDict()
if fps:
self.sprites['fps_label'] = pyglet.text.Label('0 fps', x=10, y=10)
self.last_update = time()
self.fps_count = 0
self.keys = OrderedDict()
self.mouse_x = 0
self.mouse_y = 0
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_mouse_motion(self, x, y, dx, dy):
self.mouse_x = x
self.mouse_y = y
def on_mouse_release(self, x, y, button, modifiers):
print('Released mouse at {}x{}'.format(x, y))
def on_mouse_press(self, x, y, button, modifiers):
if button == 1:
print('Pressed mouse at {}x{}'.format(x, y))
def on_mouse_drag(self, x, y, dx, dy, button, modifiers):
self.drag = True
print('Dragging mouse at {}x{}'.format(x, y))
def on_key_release(self, symbol, modifiers):
try:
del self.keys[symbol]
except:
pass
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
self.keys[symbol] = True
def pre_render(self):
pass
def render(self):
self.clear()
# FPS stuff (if you want to)
self.fps_count += 1
if time() - self.last_update > 1: # 1 sec passed
self.sprites['fps_label'].text = str(self.fps_count)
self.fps_count = 0
self.last_update = time()
#self.bg.draw()
self.pre_render()
for sprite in self.sprites:
self.sprites[sprite].draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
if __name__ == '__main__':
x = main()
x.run()
This way, you can actually interact with your class as if it were the pyglet class. For instance, you got on_key_press without having to use decorators etc.
You should make more functions for each thing you are doing. For example if you want to make a line you might have
import pyglet, os
class runGame():
widthDisplay = 1024
heightDiplay = 576
title = "Pokémon Life and Death: Esploratori del proprio Destino"
wra = pyglet.image.load("wra.png")
wrb = pyglet.image.load("wrb.png")
def __init__(self):
platform = pyglet.window.get_platform()
display = platform.get_default_display()
screen = display.get_default_screen()
self.widthScreen = screen.width
self.heightScreen = screen.height
self.xDisplay = int(self.widthScreen / 2 - self.widthDisplay / 2)
self.yDisplay = int(self.heightScreen / 2 - self.heightDiplay / 2)
self.Display = pyglet.window.Window(width=self.widthDisplay, height=self.heightDiplay, caption=self.title, resizable=False)
self.Display.set_location(self.xDisplay, self.yDisplay)
pyglet.app.run()
def drawLine(x, y):
drawLine(x, y)
You will have to make the functions based on what you want to do, but if you aren't going to use the runGame class multiple times before you kill the program, you shouldn't use OOP programming and use Procedural Programming.
You can create a custom pyglet window, by inheriting from the pyglet.window.Window class. After that in your init method call the super classes init (this will call the the init inside pyglet.window.Window class )Then overwrite the inherited methods (on_draw, on_key_press, on_key_release etc.). After that create an update method, which will be called by the pyglet.clock.schedule_interval 60 times in a second.Lastly just call the pyglet.app.run() method.
import pyglet
class GameWindow(pyglet.window.Window):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def on_draw(self):
self.clear()
def on_mouse_press(self, x, y, button, modifiers):
pass
def on_mouse_release(self, x, y, button, modifiers):
pass
def on_mouse_motion(self, x, y, dx, dy):
pass
def update(self, dt):
pass
if __name__ == "__main__":
window = GameWindow(1280, 720, "My Window", resizable=False)
pyglet.clock.schedule_interval(window.update, 1/60.0)
pyglet.app.run()
Why is my program slow while rendering 128 particles? I think that's not enough to get less than 30 fps.
All I do is rendering 128 particles and giving them some basic gravitation
on_draw function
def on_draw(self, time=None):
glClearColor(0.0, 0.0, 0.0, 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
self.particles.append(Particle())
for particle in self.particles:
particle.draw()
if particle.is_dead:
self.particles.remove(particle)
Particle class
class Particle:
def __init__(self, **kwargs):
self.acceleration = Vector2(0, 0.05)
self.velocity = Vector2(random.uniform(-1, 1), random.uniform(-1, 0))
self.position = Vector2()
self.time_to_live = 255
self.numpoints = 50
self._vertices = []
for i in range(self.numpoints):
angle = math.radians(float(i) / self.numpoints * 360.0)
x = 10 * math.cos(angle) + self.velocity[0] + 300
y = 10 * math.sin(angle) + self.velocity[1] + 400
self._vertices += [x, y]
def update(self, time=None):
self.velocity += self.acceleration
self.position -= self.velocity
self.time_to_live -= 2
def draw(self):
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glPushMatrix()
glTranslatef(self.position[0], self.position[1], 0)
pyglet.graphics.draw(self.numpoints, GL_TRIANGLE_FAN, ('v2f', self._vertices), ('c4B', self.color))
glPopMatrix()
self.update()
#property
def is_dead(self):
if self.time_to_live <= 0:
return True
return False
#property
def color(self):
return tuple(color for i in range(self.numpoints) for color in (255, 255, 255, self.time_to_live))
I'm not overly happy about using GL_TRIANGLE_FAN because it's caused a lot of odd shapes when using batched rendering. So consider moving over to GL_TRIANGLES instead and simply add all the points to the object rather than leaning on GL to close the shape for you.
That way, you can easily move over to doing batched rendering:
import pyglet
from pyglet.gl import *
from collections import OrderedDict
from time import time, sleep
from math import *
from random import randint
key = pyglet.window.key
class CustomGroup(pyglet.graphics.Group):
def set_state(self):
#pyglet.gl.glLineWidth(5)
#glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
#glColor4f(1, 0, 0, 1) #FFFFFF
#glLineWidth(1)
#glEnable(texture.target)
#glBindTexture(texture.target, texture.id)
pass
def unset_state(self):
glLineWidth(1)
#glDisable(texture.target)
class Particle():
def __init__(self, x, y, batch, particles):
self.batch = batch
self.particles = particles
self.group = CustomGroup()
self.add_point(x, y)
def add_point(self, x, y):
colors = ()#255,0,0
sides = 50
radius = 25
deg = 360/sides
points = ()#x, y # Starting point is x, y?
prev = None
for i in range(sides):
n = ((deg*i)/180)*pi # Convert degrees to radians
point = int(radius * cos(n)) + x, int(radius * sin(n)) + y
if prev:
points += x, y
points += prev
points += point
colors += (255, i*int(255/sides), 0)*3 # Add a color pair for each point (r,g,b) * points[3 points added]
prev = point
points += x, y
points += prev
points += points[2:4]
colors += (255, 0, 255)*3
self.particles[len(self.particles)] = self.batch.add(int(len(points)/2), pyglet.gl.GL_TRIANGLES, self.group, ('v2i/stream', points), ('c3B', colors))
class main(pyglet.window.Window):
def __init__ (self, demo=False):
super(main, self).__init__(800, 600, fullscreen = False, vsync = True)
#print(self.context.config.sample_buffers)
self.x, self.y = 0, 0
self.sprites = OrderedDict()
self.batches = OrderedDict()
self.batches['default'] = pyglet.graphics.Batch()
self.active_batch = 'default'
for i in range(1000):
self.sprites[len(self.sprites)] = Particle(randint(0, 800), randint(0, 600), self.batches[self.active_batch], self.sprites)
self.alive = True
self.fps = 0
self.last_fps = time()
self.fps_label = pyglet.text.Label(str(self.fps) + ' fps', font_size=12, x=3, y=self.height-15)
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
def render(self):
self.clear()
#self.bg.draw()
self.batches[self.active_batch].draw()
self.fps += 1
if time()-self.last_fps > 1:
self.fps_label.text = str(self.fps) + ' fps'
self.fps = 0
self.last_fps = time()
self.fps_label.draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
if __name__ == '__main__':
x = main(demo=True)
x.run()
Bare in mind, on my nVidia 1070 I managed to get roughly 35fps out of this code, which isn't mind blowing. But it is 1000 objects * sides, give or take.
What I've changed is essentially this:
self.batch.add(int(len(points)/2), pyglet.gl.GL_TRIANGLES, self.group, ('v2i/stream', points), ('c3B', colors))
and in your draw loop, you'll do:
self.batch.draw()
Instead of calling Particle.draw() for each particle object.
What this does is that it'll send all the objects to the graphics card in one gigantic batch rather than having to tell the graphics card what to render object by object.
As #thokra pointed out, your code is more CPU intensive than GPU intensive.
Hopefully this fixes it or gives you a few pointers.
Most of this code is taking from a LAN project I did with a good friend of mine a while back:
https://github.com/Torxed/pyslither/blob/master/main.py
Because I didn't have all your code, mainly the main loop. I applied your problem to my own code and "solved " it by tweaking it a bit. Again, hope it helps and steal ideas from that github project if you need to. Happy new year!
I am trying to have a circle that, when clicked, moves somewhere else on the screen. However, when I click the circle, nothing happens.
#IMPORT STUFF
import pyglet as pg
from random import randint
mouse = pg.window.mouse
#VARS
window = pg.window.Window(width = 640, height = 480)
score = 0
circleImg = pg.image.load("circle.png")
circle = pg.sprite.Sprite(circleImg, randint(1, window.width), randint(1, window.height))
text = pg.text.Label("Click red!", font_name = "Times New Roman", font_size = 18, x = 260, y = 10)
#DETECT MOUSE PRESS ON CIRCLE
#window.event
def on_mouse_press(x, y, button, modifiers):
if x == circle.x and y == circle.y:
circle.x = randint(1, window.width)
circle.y = randint(1, window.height)
#window.event
def on_draw():
window.clear()
text.draw()
circle.draw()
pg.app.run()
import pyglet
from pyglet.gl import *
from random import randint
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
class Circle(pyglet.sprite.Sprite):
def __init__(self, radiance=5, x=0, y=0):
self.texture = pyglet.image.load('circle.png')
super(Circle, self).__init__(self.texture)
def click(self, x, y):
if x >= self.x and y >= self.y:
if x <= self.x + self.texture.width and y <= self.y + self.texture.height:
return self
mouse = pyglet.window.mouse
#VARS
window = pyglet.window.Window(width = 640, height = 480)
score = 0
#circleImg = pyglet.image.load("circle.png")
#circle = pyglet.sprite.Sprite(circleImg, randint(1, window.width), randint(1, window.height))
circle = Circle(x=50, y=50)
text = pyglet.text.Label("Click red!", font_name = "Times New Roman", font_size = 18, x = 260, y = 10)
#DETECT MOUSE PRESS ON CIRCLE
#window.event
def on_mouse_press(x, y, button, modifiers):
if circle.click(x, y):
print('Clicked in circle')
circle.x = randint(0, window.width - 10)
circle.y = randint(0, window.height - 10)
#window.event
def on_draw():
window.clear()
text.draw()
circle.draw()
pyglet.app.run()
A short description of what this does is it creates a custom class called Circle that inherits the Sprite class. It loads the circle.png as a texture with a alpha channel that gets blended by the GL library.
We add a custom function called click that checks if the lowest x,y coordinates are higher than the circles lowest x,y, then we check if the cursor is below x+width and same for y of the image region.
If that's the case, we return the circle sprite class as a True value in case we want to use the sprite.
Future enhancements:
You should draw the circle using gl functions, hence why I've defined radiance in the class definitions. However radiance here is never used, it's a placeholder for the future.
This is so you can use math to defined if you actually clicked within the circle, but this is beyond my scope of quick answers.. I would have to do a lot of debugging myself in order to get the math to add up (it's not my strong side).
What makes it work now is that we use the image width, height, x and y data to crudely check if we're within the image, aka "the circle".
trying to draw over sprite or change picture pyglet
As a bonus, I'll add this answer to the list of enhancements because it contains some stuff that might be useful. One would be to replace 90% of your code with a custom pyglet.window.Window class to replace global variables and decorators and stuff.
And it would look something like this:
import pyglet
from pyglet.gl import *
from random import randint
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
key = pyglet.window.key
class Circle(pyglet.sprite.Sprite):
def __init__(self, radiance=5, x=0, y=0):
self.texture = pyglet.image.load('circle.png')
super(Circle, self).__init__(self.texture)
def click(self, x, y):
if x >= self.x and y >= self.y:
if x <= self.x + self.texture.width and y <= self.y + self.texture.height:
return self
class MainScreen(pyglet.window.Window):
def __init__ (self):
super(MainScreen, self).__init__(800, 600, fullscreen = False)
self.x, self.y = 0, 0
self.bg = pyglet.sprite.Sprite(pyglet.image.load('background.jpg'))
self.sprites = {}
self.sprites['circle'] = Circle(x=50, y=50)
self.sprites['label'] = pyglet.text.Label("Click red!", font_name = "Times New Roman", font_size = 18, x = 260, y = 10)
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_mouse_press(self, x, y, button, modifiers):
if self.sprites['circle'].click(x, y):
print('Clicked in circle')
self.sprites['circle'].x = randint(0, self.width - 10)
self.sprites['circle'].y = randint(0, self.height - 10)
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
def render(self):
self.clear()
self.bg.draw()
for sprite_name, sprite_obj in self.sprites.items():
sprite_obj.draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
x = MainScreen()
x.run()
I'm not familiar with pyglet, but I'm guessing the problem is that you're checking whether x == circle.x etc, which means it only moves when you click the single pixel at the exact centre of the circle. Try some kind of maximum distance from the centre (e.g. a hypotenuse math.sqrt( (x-circle.x)**2 + (y-circle.y)**2) < circle.radius
I am making some kind of a ripoff of turtle module for drawing.
This is what i have so far:
class Turtle:
def __init__(self):
self.x = drawer.maxX / 2
self.y = drawer.maxY / 2
self.angle = 0
self.pen_active = True
self.pause = 0
self.body = drawer.circle(0, 0, 5, drawer.green, 3)
self.head = drawer.circle(0, 0, 3, drawer.green, 3)
self.length = 0
self.update()
def set_pause(self, n):
self.pause = n
def no_pause(self):
self.set_pause(0)
def hide(self):
self.body.hide()
self.head.hide()
def show(self):
self.body.show()
self.head.show()
def update(self):
self.body.setPos(self.x, self.y)
phi = radians(90 - self.angle)
self.head.setPos(self.x + 5 * cos(phi), self.y + 5 * sin(phi))
drawer.wait(self.pause)
def pen_up(self):
self.pen_active = False
def pen_down(self):
self.pen_active = True
def fly(self, x, y, angle):
self.x = x
self.y = y
self.angle = angle
self.update()
def forward(self, a):
phi = radians(90 - self.angle)
nx = self.x + a * cos(phi)
ny = self.y + a * sin(phi)
if self.pen_active:
drawer.line(self.x, self.y, nx, ny)
self.x = nx
self.y = ny
self.update()
def backward(self, a):
self.forward(-a)
def turn(self, phi):
self.angle += phi
self.update()
def left(self):
self.turn(-90)
def right(self):
self.turn(90)
def length(self):
pass
Now i am trying to write a method to compute the length of the line drawn but i don't know how to do it. The line is drawn when the pen is down and the turtle is going forwards or backwards. If the turtle flys or turns around the line is not drawn. Any ideas.
And thank your for all your help.
If I understand correctly, you want to maintain a record of the length of all the forward and backward movements your Turtle makes from the time it is created.
This is pretty easy, you just need to be a bit careful with your variable names. Currently you're using length both as an instance variable (to hold the running length sum) and as a method name. The former will shadow the latter, so you probably want two different names.
If we rename the instance variable to _length, you can make it work like this:
class Turtle:
def __init__(self):
# unmodified stuff omitted
self._length = 0
# unmodified methods skipped
def forward(self, a):
# all the current stuff
self._length += abs(a) # absolute value to count backing up as movement
def length():
return self._length
You may decide you don't need the trivial length method, in which case you can go back to using self.length as your instance variable name (and just access the attribute when you want the value).