api cleanup
This commit is contained in:
parent
3bfa6e0eaf
commit
67c2d5093d
94
README.md
94
README.md
@ -0,0 +1,94 @@
|
|||||||
|
# Doodles
|
||||||
|
|
||||||
|
This is a library that lets you write short programs that draw images or animations.
|
||||||
|
|
||||||
|
The design goals are, in approximate order of importance:
|
||||||
|
|
||||||
|
* Demonstrate some design patterns & concepts.
|
||||||
|
* Provide an example of a library design.
|
||||||
|
* Have a learning tool that's somewhat fun to play with.
|
||||||
|
|
||||||
|
Notably absent from this list: "make a useful tool" and "demonstrate the *right* way to do things."
|
||||||
|
|
||||||
|
This library demonstrates *ways* to do things, there is almost never a single correct way.
|
||||||
|
Sometimes choices will be made to provide more interesting code to learn from, or a more fun API to use.
|
||||||
|
|
||||||
|
## Rationales
|
||||||
|
|
||||||
|
I will document the reason that certain decisions were made, particularly when they were not my first thought.
|
||||||
|
|
||||||
|
If any decisions are unclear, I consider that a bug, please file a corresponding GitHub issue.
|
||||||
|
|
||||||
|
### Mutability
|
||||||
|
|
||||||
|
Often, an API based on chaining like this would be comprised of immutable objects.
|
||||||
|
Django's QuerySet mostly works in this way.
|
||||||
|
|
||||||
|
That was the original intention here as well, but once `Text` was added it was clear that it was going to be a lot more complex than it was worth.
|
||||||
|
|
||||||
|
`Text` requires an intermediate buffer to render the text to, and that buffer is then rendered to the screen. This would be incredibly expensive in a pure immutable approach, since there'd be no place to store that state.
|
||||||
|
|
||||||
|
The addition of state to the objects simplifies a lot of things, now when an object is created it can be registered with the `World`, if many intermediate copies were created in a chained call like `Circle().pos(100, 100).color(255, 0, 0).z(10).radius(4)` this approach would not be available to us.
|
||||||
|
|
||||||
|
### Naming Scheme
|
||||||
|
|
||||||
|
This library makes use of @property to create getters, but uses function chaining instead of property-based setters.
|
||||||
|
|
||||||
|
This complicates things a bit, since you can't write `self.x(100)` and use `self.x` as a property.
|
||||||
|
|
||||||
|
The unorthodox decision was made to use `x(100)` as a setter function, and use `.x_val` as the property name.
|
||||||
|
|
||||||
|
This is primarily because setting properties is more common than getting properties in doodles.
|
||||||
|
|
||||||
|
## Design Patterns
|
||||||
|
|
||||||
|
TODO: flesh this out with more notes
|
||||||
|
|
||||||
|
A number of design patterns are utilized in this library.
|
||||||
|
I'm open to suggestions for more to add, especially where an interesting feature can demonstrate the utility.
|
||||||
|
|
||||||
|
### Factory
|
||||||
|
|
||||||
|
TODO: No factory yet surprisingly, should be easy to add one to add shapes, but can consider other options.
|
||||||
|
|
||||||
|
### Prototype
|
||||||
|
|
||||||
|
The `Doodle` class (and an override in `Group`) demonstrate the utility of the Prototype pattern.
|
||||||
|
|
||||||
|
Letting these objects specify how they are copied makes the desired behavior possible, the utility of which can be seen in `examples/copies.py`.
|
||||||
|
|
||||||
|
This would often be done via the `__copy__/__deepcopy__` methods, but for simplicity as well as API compatibility this is done in `.copy()` here.
|
||||||
|
|
||||||
|
### Singleton
|
||||||
|
|
||||||
|
The `World` class is treated as a Singleton and contains notes on alternate implementations.
|
||||||
|
|
||||||
|
### Bridge / Strategy
|
||||||
|
|
||||||
|
TODO: the planned Renderer class will demonstrate these
|
||||||
|
|
||||||
|
### Composite
|
||||||
|
|
||||||
|
The Group class (along with Doodle) forms a composite.
|
||||||
|
|
||||||
|
### Command
|
||||||
|
|
||||||
|
The structure of the Doodle object is a command class.
|
||||||
|
It stores the information about the action to be performed encapsulated.
|
||||||
|
It's entire purpose is to provide arguments to a draw* method.
|
||||||
|
|
||||||
|
### Observer
|
||||||
|
|
||||||
|
TODO: room to implement, dynamic properties?
|
||||||
|
|
||||||
|
### Template Method
|
||||||
|
|
||||||
|
The update method, the draw method.
|
||||||
|
|
||||||
|
### Visitor
|
||||||
|
|
||||||
|
TODO: maybe use along with group?
|
||||||
|
|
||||||
|
### Flyweight Cache
|
||||||
|
|
||||||
|
Text's Font Cache is a flyweight
|
@ -25,9 +25,10 @@ class Doodle(ABC):
|
|||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
self._parent = parent
|
self._parent = parent
|
||||||
self._color = parent._color if parent else Color.BLACK
|
|
||||||
self._z_index = 0
|
|
||||||
self._updates = []
|
self._updates = []
|
||||||
|
self._color = parent._color if parent else Color.BLACK
|
||||||
|
self._alpha = parent._alpha if parent else 255
|
||||||
|
self._z_index = 0
|
||||||
# Is storing this vector in a tuple the right thing to do?
|
# Is storing this vector in a tuple the right thing to do?
|
||||||
# It might make more sense to store _x and _y, or use
|
# It might make more sense to store _x and _y, or use
|
||||||
# a library's optimized 2D vector implementation.
|
# a library's optimized 2D vector implementation.
|
||||||
@ -38,7 +39,7 @@ class Doodle(ABC):
|
|||||||
self._register()
|
self._register()
|
||||||
|
|
||||||
def _register(self):
|
def _register(self):
|
||||||
""" register with parent and world """
|
"""register with parent and world"""
|
||||||
if self._parent:
|
if self._parent:
|
||||||
# register with parent for updates
|
# register with parent for updates
|
||||||
self._parent.add(self)
|
self._parent.add(self)
|
||||||
@ -87,6 +88,8 @@ class Doodle(ABC):
|
|||||||
new._register()
|
new._register()
|
||||||
return new
|
return new
|
||||||
|
|
||||||
|
# Setters #######################
|
||||||
|
|
||||||
def color(self, color: tuple[int, int, int]) -> "Doodle":
|
def color(self, color: tuple[int, int, int]) -> "Doodle":
|
||||||
"""
|
"""
|
||||||
Color works as a kind of setter function.
|
Color works as a kind of setter function.
|
||||||
@ -106,13 +109,34 @@ class Doodle(ABC):
|
|||||||
self._pos_vec = (x, y)
|
self._pos_vec = (x, y)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def z_index(self, z: float) -> "Doodle":
|
def x(self, x: float) -> "Doodle":
|
||||||
|
"""
|
||||||
|
Setter for x component.
|
||||||
|
"""
|
||||||
|
self._pos_vec = (x, self._pos_vec[1])
|
||||||
|
|
||||||
|
def y(self, y: float) -> "Doodle":
|
||||||
|
"""
|
||||||
|
Setter for x component.
|
||||||
|
"""
|
||||||
|
self._pos_vec = (self._pos_vec[0], y)
|
||||||
|
|
||||||
|
def alpha(self, a: int) -> "Doodle":
|
||||||
|
"""
|
||||||
|
Setter for alpha transparency
|
||||||
|
"""
|
||||||
|
self._alpha = a
|
||||||
|
return self
|
||||||
|
|
||||||
|
def z(self, z: float) -> "Doodle":
|
||||||
"""
|
"""
|
||||||
Setter for z_index
|
Setter for z_index
|
||||||
"""
|
"""
|
||||||
self._z_index = z
|
self._z_index = z
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
# Modifiers #################
|
||||||
|
|
||||||
def move(self, dx: float, dy: float) -> "Doodle":
|
def move(self, dx: float, dy: float) -> "Doodle":
|
||||||
"""
|
"""
|
||||||
This shifts the vector by a set amount.
|
This shifts the vector by a set amount.
|
||||||
@ -135,47 +159,52 @@ class Doodle(ABC):
|
|||||||
# used by all downstream functions
|
# used by all downstream functions
|
||||||
return self.pos(x, y).color(color)
|
return self.pos(x, y).color(color)
|
||||||
|
|
||||||
|
# Getters ################
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def x(self) -> float:
|
def world_x(self) -> float:
|
||||||
"""
|
"""
|
||||||
A read-only attribute "doodle.x" that will
|
A read-only attribute "doodle.world_x" that will
|
||||||
return the screen position derived from the parent position
|
return the screen position derived from the parent position
|
||||||
plus the current object's x component.
|
plus the current object's x component.
|
||||||
|
|
||||||
Note the recursion here, parent.x is an instance of doodle.x.
|
Note the recursion here, parent.world_x is an instance of doodle.world_x.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
A.x = 100
|
A x = 100
|
||||||
|--------B.x 10
|
|--------B x = 10
|
||||||
|--------C.x 20
|
|--------C.world_x 20
|
||||||
|
|
||||||
When drawing object C, parent.x will call B.x, which will call A.x.
|
When drawing object C, world_x will call B.world_x which will call
|
||||||
B.x will return 110, and C.x will therefore return 130.
|
A.world_x.
|
||||||
|
B will return 110, and C therefore returns 130.
|
||||||
"""
|
"""
|
||||||
if self._parent:
|
if self._parent:
|
||||||
return self._parent.x + self._pos_vec[0]
|
return self._parent.world_x + self._pos_vec[0]
|
||||||
return self._pos_vec[0]
|
return self._pos_vec[0]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def y(self) -> float:
|
def world_y(self) -> float:
|
||||||
"""
|
"""
|
||||||
See documentation for .x above.
|
See documentation for .world_y above.
|
||||||
"""
|
"""
|
||||||
if self._parent:
|
if self._parent:
|
||||||
return self._parent.y + self._pos_vec[1]
|
return self._parent.world_y + self._pos_vec[1]
|
||||||
return self._pos_vec[1]
|
return self._pos_vec[1]
|
||||||
|
|
||||||
# @property
|
|
||||||
# def z_index(self) -> float:
|
|
||||||
# return self._z_index
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pos_vec(self) -> (float, float):
|
def world_vec(self) -> (float, float):
|
||||||
"""
|
"""
|
||||||
Obtain derived position vector as a 2-tuple.
|
Obtain derived position vector as a 2-tuple.
|
||||||
"""
|
"""
|
||||||
return self.x, self.y
|
return self.world_x, self.world_y
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rgba(self) -> (int, int, int, int):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
return (*self._color, self._alpha)
|
||||||
|
|
||||||
|
|
||||||
class Group(Doodle):
|
class Group(Doodle):
|
||||||
@ -200,7 +229,7 @@ class Group(Doodle):
|
|||||||
self._doodles = []
|
self._doodles = []
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"Group(pos={self.pos_vec}, doodles={len(self._doodles)})"
|
return f"Group(pos={self.world_vec}, doodles={len(self._doodles)})"
|
||||||
|
|
||||||
def draw(self, screen):
|
def draw(self, screen):
|
||||||
"""
|
"""
|
||||||
|
@ -14,6 +14,7 @@ it is left as a pass-through like we see here.
|
|||||||
Objects without an update method are static.
|
Objects without an update method are static.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class Ball(Circle):
|
class Ball(Circle):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -21,24 +22,27 @@ class Ball(Circle):
|
|||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
self.move(0, self.speed)
|
self.move(0, self.speed)
|
||||||
if self.y > world.HEIGHT + 20:
|
if self.world_y > world.HEIGHT + 20:
|
||||||
self.move(0, -world.HEIGHT-20)
|
self.move(0, -world.HEIGHT - 20)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class GravityBall(Circle):
|
class GravityBall(Circle):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.accel = 0.5 # accel per frame
|
self.accel = 0.5 # accel per frame
|
||||||
self.speed = random.random() * 10
|
self.speed = random.random() * 10
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
self.speed += self.accel
|
self.speed += self.accel
|
||||||
self.move(0, self.speed)
|
self.move(0, self.speed)
|
||||||
if self.y > world.HEIGHT - 10:
|
if self.world_y > world.HEIGHT - 10:
|
||||||
self.speed *= -0.98 # dampening
|
self.speed *= -0.98 # dampening
|
||||||
self.pos(self.x, world.HEIGHT - 10.01)
|
self.y(world.HEIGHT - 10.01)
|
||||||
|
|
||||||
|
|
||||||
def create():
|
def create():
|
||||||
[Ball().pos(40*i, 0).radius(10).color(Color.BLUE) for i in range(21)]
|
[Ball().pos(40 * i, 0).radius(10).color(Color.BLUE) for i in range(21)]
|
||||||
[GravityBall().pos(20+40*i, 0).radius(10).color(Color.PURPLE) for i in range(21)]
|
[
|
||||||
|
GravityBall().pos(20 + 40 * i, 0).radius(10).color(Color.PURPLE)
|
||||||
|
for i in range(21)
|
||||||
|
]
|
||||||
|
@ -10,6 +10,6 @@ def create():
|
|||||||
color = color_cycle()
|
color = color_cycle()
|
||||||
g = Group().pos(400, 300)
|
g = Group().pos(400, 300)
|
||||||
for r in range(20, 100, 12):
|
for r in range(20, 100, 12):
|
||||||
Circle(g).radius(r).color(next(color)).z_index(-r)
|
Circle(g).radius(r).color(next(color)).z(-r)
|
||||||
for r in range(100, 250, 12):
|
for r in range(100, 250, 12):
|
||||||
Circle(g).radius(r).color(next(color)).z_index(-r)
|
Circle(g).radius(r).color(next(color)).z(-r)
|
||||||
|
@ -3,7 +3,7 @@ from doodles import Circle, Color, Line, Group
|
|||||||
|
|
||||||
def create():
|
def create():
|
||||||
g = Group().pos(400, 300)
|
g = Group().pos(400, 300)
|
||||||
Circle(g).radius(300).color(Color.BLACK).z_index(1)
|
Circle(g).radius(300).color(Color.BLACK).z(1)
|
||||||
Circle(g).radius(290).color(Color.BROWN).z_index(10)
|
Circle(g).radius(290).color(Color.BROWN).z(10)
|
||||||
Circle(g).radius(20).color(Color.BLACK).z_index(50)
|
Circle(g).radius(20).color(Color.BLACK).z(50)
|
||||||
Line(g).vec(lambda: time.time() % 60 / 60 * 360, 200).z_index(100)
|
Line(g).vec(lambda: time.time() % 60 / 60 * 360, 200).z(100)
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
from doodles import Group, Circle, Color
|
from doodles import Group, Circle, Color
|
||||||
|
|
||||||
def original():
|
def create():
|
||||||
g = Group()
|
g = Group()
|
||||||
c = Circle(g).radius(80).color(Color.RED).pos(0, 0)
|
c = Circle(g).radius(80).color(Color.RED).pos(0, 0)
|
||||||
for _ in range(15):
|
for _ in range(15):
|
||||||
c = c.copy().move(45, 45)
|
c = c.copy().move(45, 45)
|
||||||
return g
|
g.copy().move(200, 0).color(Color.GREEN)
|
||||||
|
g.copy().move(400, 0).color(Color.BLUE)
|
||||||
def create():
|
|
||||||
r = original()
|
|
||||||
r.copy().move(200, 0).color(Color.GREEN)
|
|
||||||
r.copy().move(400, 0).color(Color.BLUE)
|
|
||||||
|
@ -3,10 +3,10 @@ import random
|
|||||||
|
|
||||||
def create():
|
def create():
|
||||||
for _ in range(25):
|
for _ in range(25):
|
||||||
Rectangle().random(200).color(Color.BLACK).z_index(10)
|
Rectangle().random(200).color(Color.BLACK).z(10)
|
||||||
Rectangle().random(150).color(Color.DARK_BLUE).z_index(15)
|
Rectangle().random(150).color(Color.DARK_BLUE).z(15)
|
||||||
Rectangle().random(100).color(Color.DARK_GREY).z_index(20)
|
Rectangle().random(100).color(Color.DARK_GREY).z(20)
|
||||||
Rectangle().random(50).color(Color.LIGHT_GREY).z_index(30)
|
Rectangle().random(50).color(Color.LIGHT_GREY).z(30)
|
||||||
# Rectangle().random(250).color(
|
# Rectangle().random(250).color(
|
||||||
# random.choice((Color.BLACK, Color.LIGHT_GREY, Color.DARK_GREY, Color.WHITE))
|
# random.choice((Color.BLACK, Color.LIGHT_GREY, Color.DARK_GREY, Color.WHITE))
|
||||||
# )
|
# )
|
||||||
|
11
src/doodles/examples/scale.py
Normal file
11
src/doodles/examples/scale.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
from doodles import Group, Circle, Color
|
||||||
|
|
||||||
|
def tri():
|
||||||
|
g = Group()
|
||||||
|
Circle(g).radius(50).color(Color.RED).pos(0, 0).alpha(128)
|
||||||
|
Circle(g).radius(50).color(Color.GREEN).pos(-25, 35).alpha(128)
|
||||||
|
Circle(g).radius(50).color(Color.BLUE).pos(25, 35).alpha(128)
|
||||||
|
return g
|
||||||
|
|
||||||
|
def create():
|
||||||
|
r = tri().move(200, 200)
|
@ -7,7 +7,6 @@ from doodles import Group, Circle, Color, Text
|
|||||||
Text.make_font("small", 16, "mono")
|
Text.make_font("small", 16, "mono")
|
||||||
Text.make_font("medium", 24, "copperplate")
|
Text.make_font("medium", 24, "copperplate")
|
||||||
Text.make_font("large", 48, "papyrus")
|
Text.make_font("large", 48, "papyrus")
|
||||||
print(Text._fonts)
|
|
||||||
|
|
||||||
# Via ChatGPT
|
# Via ChatGPT
|
||||||
hello_world = [
|
hello_world = [
|
||||||
@ -39,4 +38,4 @@ def create():
|
|||||||
for greeting in itertools.chain.from_iterable(itertools.repeat(hello_world, 3)):
|
for greeting in itertools.chain.from_iterable(itertools.repeat(hello_world, 3)):
|
||||||
Text().random().font(
|
Text().random().font(
|
||||||
random.choice(("small", "medium", "large"))
|
random.choice(("small", "medium", "large"))
|
||||||
).text(greeting)
|
).color(random.choice((Color.LIGHT_GREY, Color.DARK_GREY))).text(greeting)
|
||||||
|
@ -20,7 +20,7 @@ class Line(Doodle):
|
|||||||
self._offset_vec = (10, 0)
|
self._offset_vec = (10, 0)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"Line(pos={self.pos_vec}, end={self.end_vec}, {self._color})"
|
return f"Line(pos={self.world_vec}, end={self.end_vec}, {self._color})"
|
||||||
|
|
||||||
def draw(self, screen):
|
def draw(self, screen):
|
||||||
"""
|
"""
|
||||||
@ -41,7 +41,7 @@ class Line(Doodle):
|
|||||||
to the class and gaining flexibility from separating
|
to the class and gaining flexibility from separating
|
||||||
presentation logic from data manipulation.
|
presentation logic from data manipulation.
|
||||||
"""
|
"""
|
||||||
pygame.draw.aaline(screen, self._color, self.pos_vec, self.end_vec)
|
pygame.draw.aaline(screen, self._color, self.world_vec, self.end_vec)
|
||||||
|
|
||||||
def to(self, x: float, y: float) -> "Doodle":
|
def to(self, x: float, y: float) -> "Doodle":
|
||||||
"""
|
"""
|
||||||
@ -93,9 +93,9 @@ class Line(Doodle):
|
|||||||
@property
|
@property
|
||||||
def end_vec(self):
|
def end_vec(self):
|
||||||
"""
|
"""
|
||||||
Parallel to pos_vec for end of line.
|
Parallel to world_vec for end of line.
|
||||||
"""
|
"""
|
||||||
return (
|
return (
|
||||||
self.x + self._offset_vec[0],
|
self.world_x + self._offset_vec[0],
|
||||||
self.y + self._offset_vec[1],
|
self.world_y + self._offset_vec[1],
|
||||||
)
|
)
|
||||||
|
@ -13,10 +13,10 @@ class Circle(Doodle):
|
|||||||
self._radius = 0
|
self._radius = 0
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"Circle(pos={self.pos_vec}, radius={self._radius}, {self._color}, parent={self._parent}))"
|
return f"Circle(pos={self.world_vec}, radius={self._radius}, {self._color}, parent={self._parent}))"
|
||||||
|
|
||||||
def draw(self, screen):
|
def draw(self, screen):
|
||||||
pygame.draw.circle(screen, self._color, self.pos_vec, self._radius)
|
pygame.draw.circle(screen, self.rgba, self.world_vec, self._radius)
|
||||||
|
|
||||||
def radius(self, r: float) -> "Doodle":
|
def radius(self, r: float) -> "Doodle":
|
||||||
"""
|
"""
|
||||||
@ -48,12 +48,12 @@ class Rectangle(Doodle):
|
|||||||
self._height = 100
|
self._height = 100
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"Rect(pos={self.pos_vec}, width={self._width}, height={self._height}, parent={self._parent})"
|
return f"Rect(pos={self.world_vec}, width={self._width}, height={self._height}, parent={self._parent})"
|
||||||
|
|
||||||
def draw(self, screen):
|
def draw(self, screen):
|
||||||
rect = pygame.Rect(
|
rect = pygame.Rect(
|
||||||
self.x - self._width / 2,
|
self.world_x - self._width / 2,
|
||||||
self.y - self._height / 2,
|
self.world_y - self._height / 2,
|
||||||
self._width,
|
self._width,
|
||||||
self._height,
|
self._height,
|
||||||
)
|
)
|
||||||
|
@ -63,7 +63,7 @@ class Text(Doodle):
|
|||||||
return f"Text(pos={self.pos_vec}, text={self._text}, parent={self._parent})"
|
return f"Text(pos={self.pos_vec}, text={self._text}, parent={self._parent})"
|
||||||
|
|
||||||
def draw(self, screen):
|
def draw(self, screen):
|
||||||
text_rect = self._rendered.get_rect(center=(self.x, self.y))
|
text_rect = self._rendered.get_rect(center=self.world_vec)
|
||||||
screen.blit(self._rendered, text_rect)
|
screen.blit(self._rendered, text_rect)
|
||||||
|
|
||||||
def text(self, text: str) -> "Doodle":
|
def text(self, text: str) -> "Doodle":
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from .color import Color
|
from .color import Color
|
||||||
import pygame
|
import pygame
|
||||||
|
|
||||||
|
|
||||||
class World:
|
class World:
|
||||||
"""
|
"""
|
||||||
This class is a singleton, only one instance should ever exist.
|
This class is a singleton, only one instance should ever exist.
|
||||||
@ -57,6 +58,7 @@ class World:
|
|||||||
raise ValueError("Can't initialize world twice!")
|
raise ValueError("Can't initialize world twice!")
|
||||||
pygame.init()
|
pygame.init()
|
||||||
self.screen = pygame.display.set_mode((world.WIDTH, world.HEIGHT))
|
self.screen = pygame.display.set_mode((world.WIDTH, world.HEIGHT))
|
||||||
|
self.buffer = pygame.Surface((world.WIDTH, world.HEIGHT), pygame.SRCALPHA)
|
||||||
self.clock = pygame.time.Clock()
|
self.clock = pygame.time.Clock()
|
||||||
self._elapsed = 0
|
self._elapsed = 0
|
||||||
|
|
||||||
@ -81,12 +83,12 @@ class World:
|
|||||||
self.tick()
|
self.tick()
|
||||||
|
|
||||||
# rendering
|
# rendering
|
||||||
self.screen.fill(self.background_color)
|
self.buffer.fill((*self.background_color, 255))
|
||||||
for d in sorted(self._drawables, key=lambda d: d._z_index):
|
for d in sorted(self._drawables, key=lambda d: d._z_index):
|
||||||
d.draw(self.screen)
|
d.draw(self.buffer)
|
||||||
|
self.screen.blit(self.buffer, (0, 0))
|
||||||
pygame.display.flip()
|
pygame.display.flip()
|
||||||
|
|
||||||
|
|
||||||
# our singleton instance
|
# our singleton instance
|
||||||
world = World()
|
world = World()
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user