PyScene 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyscene-1.0.0/.gitignore +12 -0
- pyscene-1.0.0/LICENSE +21 -0
- pyscene-1.0.0/PKG-INFO +18 -0
- pyscene-1.0.0/README.md +4 -0
- pyscene-1.0.0/pyproject.toml +22 -0
- pyscene-1.0.0/src/PyScene/Collisions.py +62 -0
- pyscene-1.0.0/src/PyScene/Game.py +297 -0
- pyscene-1.0.0/src/PyScene/Keys.py +100 -0
- pyscene-1.0.0/src/PyScene/Math.py +52 -0
- pyscene-1.0.0/src/PyScene/Net.py +31 -0
- pyscene-1.0.0/src/PyScene/__init__.py +34 -0
- pyscene-1.0.0/src/PyScene/objs/Collision_shapes.py +41 -0
- pyscene-1.0.0/src/PyScene/objs/GameObj.py +20 -0
- pyscene-1.0.0/src/PyScene/objs/Shapes.py +45 -0
- pyscene-1.0.0/src/PyScene/objs/Text.py +50 -0
- pyscene-1.0.0/src/PyScene/objs/__init__.py +7 -0
- pyscene-1.0.0/src/PyScene/objs/animation.py +116 -0
- pyscene-1.0.0/src/PyScene/utils/Assets.py +151 -0
- pyscene-1.0.0/src/PyScene/utils/Subscreen.py +36 -0
- pyscene-1.0.0/src/PyScene/utils/__init__.py +4 -0
- pyscene-1.0.0/tests/flappy_bird.py +38 -0
- pyscene-1.0.0/tests/pause.png +0 -0
- pyscene-1.0.0/tests/test.py +21 -0
- pyscene-1.0.0/tests/testColl.py +39 -0
pyscene-1.0.0/.gitignore
ADDED
pyscene-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [year] [fullname]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pyscene-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: PyScene
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A small example package
|
|
5
|
+
Project-URL: Homepage, https://github.com/sfertig/PyScene
|
|
6
|
+
Project-URL: Issues, https://github.com/sfertig/PyScene/issues
|
|
7
|
+
Author-email: Sam Fertig <sfertig007@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# PyScene
|
|
16
|
+
|
|
17
|
+
PyScene is a custom python + pygame framework / engine developed to streamline 2d game development.
|
|
18
|
+
|
pyscene-1.0.0/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling >= 1.26"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
[project]
|
|
5
|
+
name = "PyScene"
|
|
6
|
+
version = "1.0.0"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name="Sam Fertig", email="sfertig007@gmail.com" },
|
|
9
|
+
]
|
|
10
|
+
description = "A small example package"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
]
|
|
17
|
+
license = "MIT"
|
|
18
|
+
license-files = ["LICEN[CS]E*"]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://github.com/sfertig/PyScene"
|
|
22
|
+
Issues = "https://github.com/sfertig/PyScene/issues"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import pygame
|
|
2
|
+
|
|
3
|
+
#collisions generators
|
|
4
|
+
|
|
5
|
+
def _coll_gen_combination(Game): #this functions as the main function for collision rect generation
|
|
6
|
+
if not Game._update_coll: return
|
|
7
|
+
# -- else start collisions generation -- #
|
|
8
|
+
Game.collisions.clear() #clear existing rects
|
|
9
|
+
|
|
10
|
+
#get rects
|
|
11
|
+
rects: list[pygame.Rect] = []
|
|
12
|
+
for col in Game.col: rects.append(col.rect.copy())
|
|
13
|
+
|
|
14
|
+
if Game.optimize_static_colliders: rects = _optimize_static_colliders(Game, rects)
|
|
15
|
+
|
|
16
|
+
#set collisions
|
|
17
|
+
Game.collisions = rects
|
|
18
|
+
|
|
19
|
+
#add dynamic rects
|
|
20
|
+
for col in Game.dynamic_col:
|
|
21
|
+
if col.active: rects.append(col.rect)
|
|
22
|
+
|
|
23
|
+
Game._update_coll = False
|
|
24
|
+
|
|
25
|
+
def _optimize_static_colliders(Game, colliders: list[pygame.Rect]):
|
|
26
|
+
if not colliders:
|
|
27
|
+
return []
|
|
28
|
+
|
|
29
|
+
rects = sorted([r.copy() for r in colliders], key=lambda r: (r.y, r.x))
|
|
30
|
+
merged = []
|
|
31
|
+
|
|
32
|
+
# 2. Horizontal Merge Pass
|
|
33
|
+
while rects:
|
|
34
|
+
current = rects.pop(0)
|
|
35
|
+
i = 0
|
|
36
|
+
while i < len(rects):
|
|
37
|
+
other = rects[i]
|
|
38
|
+
if current.top == other.top and current.height == other.height and current.right == other.x:
|
|
39
|
+
current.width += other.width
|
|
40
|
+
rects.pop(i)
|
|
41
|
+
else:
|
|
42
|
+
i += 1
|
|
43
|
+
merged.append(current)
|
|
44
|
+
|
|
45
|
+
# 3. Vertical Merge Pass
|
|
46
|
+
final_rects = []
|
|
47
|
+
merged = sorted(merged, key=lambda r: (r.x, r.y))
|
|
48
|
+
while merged:
|
|
49
|
+
current = merged.pop(0)
|
|
50
|
+
i = 0
|
|
51
|
+
while i < len(merged):
|
|
52
|
+
other = merged[i]
|
|
53
|
+
if current.left == other.left and current.width == other.width and current.bottom == other.y:
|
|
54
|
+
current.height += other.height
|
|
55
|
+
merged.pop(i)
|
|
56
|
+
else:
|
|
57
|
+
i += 1
|
|
58
|
+
final_rects.append(current)
|
|
59
|
+
|
|
60
|
+
return final_rects
|
|
61
|
+
|
|
62
|
+
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import pygame
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from .Keys import Keys
|
|
5
|
+
from .Math import Vector2D
|
|
6
|
+
from .Collisions import _coll_gen_combination
|
|
7
|
+
|
|
8
|
+
class _game:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.__on = False
|
|
11
|
+
pygame.init()
|
|
12
|
+
pygame.font.init()
|
|
13
|
+
|
|
14
|
+
def init(self, width, height, title, bg="black", fps=60, fullscreen=False):
|
|
15
|
+
self.__on = True
|
|
16
|
+
self.width = width
|
|
17
|
+
self.height = height
|
|
18
|
+
self.title = title
|
|
19
|
+
self.bg = bg
|
|
20
|
+
self.fps = fps
|
|
21
|
+
self.clock = pygame.time.Clock()
|
|
22
|
+
self.dt = 0.0
|
|
23
|
+
self.mouse_down = False
|
|
24
|
+
self.click = False
|
|
25
|
+
self.events = []
|
|
26
|
+
self.cam = Camera(self)
|
|
27
|
+
|
|
28
|
+
self.show_collisions: bool = False
|
|
29
|
+
self.optimize_static_colliders: bool = False
|
|
30
|
+
|
|
31
|
+
#screen
|
|
32
|
+
if not fullscreen: self.screen = pygame.display.set_mode((self.width, self.height))
|
|
33
|
+
else: self.screen = pygame.display.set_mode((width, height), pygame.SCALED | pygame.FULLSCREEN)
|
|
34
|
+
pygame.display.set_caption(self.title)
|
|
35
|
+
|
|
36
|
+
Keys._add_net(self) #to accsess events
|
|
37
|
+
|
|
38
|
+
#game obj info
|
|
39
|
+
self.objects = []
|
|
40
|
+
self.queue_creation = []
|
|
41
|
+
self.queue_del = []
|
|
42
|
+
|
|
43
|
+
self.collisions: list[pygame.Rect] = [] #accual list of rects
|
|
44
|
+
self.col = []
|
|
45
|
+
self.queue_creation_coll = []
|
|
46
|
+
self.queue_del_coll = []
|
|
47
|
+
self._update_coll = False
|
|
48
|
+
|
|
49
|
+
self.dynamic_col = []
|
|
50
|
+
self.queue_creation_D_coll = []
|
|
51
|
+
self.queue_del_D_coll = []
|
|
52
|
+
|
|
53
|
+
self.render_queue = []
|
|
54
|
+
|
|
55
|
+
self.active_scene = None
|
|
56
|
+
self.scenes = {}
|
|
57
|
+
self.id = 0
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _queue_add(self, obj):
|
|
61
|
+
if self.active_scene is None: self.queue_creation.append(obj)
|
|
62
|
+
else: self.active_scene._queue_add(obj)
|
|
63
|
+
def _queue_remove(self, obj):
|
|
64
|
+
if obj in self.objects: self.objects.remove(obj)
|
|
65
|
+
else: self.active_scene._queue_remove(obj)
|
|
66
|
+
def _get_id(self):
|
|
67
|
+
self.id += 1
|
|
68
|
+
return self.id
|
|
69
|
+
|
|
70
|
+
def _queue_add_coll(self, obj): self.queue_creation_coll.append(obj)
|
|
71
|
+
def _queue_remove_coll(self, obj): self.queue_del_coll.append(obj)
|
|
72
|
+
def _queue_add_D_coll(self, obj): self.queue_creation_D_coll.append(obj)
|
|
73
|
+
def _queue_remove_D_coll(self, obj): self.queue_del_D_coll.append(obj)
|
|
74
|
+
|
|
75
|
+
def __update_render_queue(self): #update order of the renders based on z-index
|
|
76
|
+
self.render_queue = sorted(self.objects, key=lambda obj: obj.z_index)
|
|
77
|
+
|
|
78
|
+
def __handle_queue(self):
|
|
79
|
+
for obj in self.queue_creation: self.objects.append(obj)
|
|
80
|
+
for obj in self.queue_del: self.objects.remove(obj)
|
|
81
|
+
if len(self.queue_creation) > 0 or len(self.queue_del) > 0: self.__update_render_queue()
|
|
82
|
+
self.queue_creation.clear()
|
|
83
|
+
self.queue_del.clear()
|
|
84
|
+
#collisions
|
|
85
|
+
for obj in self.queue_creation_coll: self.col.append(obj)
|
|
86
|
+
for obj in self.queue_del_coll: self.col.remove(obj)
|
|
87
|
+
self.queue_creation_coll.clear()
|
|
88
|
+
self.queue_del_coll.clear()
|
|
89
|
+
for obj in self.queue_creation_D_coll: self.dynamic_col.append(obj)
|
|
90
|
+
for obj in self.queue_del_D_coll: self.dynamic_col.remove(obj)
|
|
91
|
+
self.queue_creation_D_coll.clear()
|
|
92
|
+
self.queue_del_D_coll.clear()
|
|
93
|
+
_coll_gen_combination(self)
|
|
94
|
+
self._update_coll = False
|
|
95
|
+
|
|
96
|
+
def __update(self):
|
|
97
|
+
self.click = False
|
|
98
|
+
self.dt = self.clock.tick(self.fps)/1000.0
|
|
99
|
+
self.events = pygame.event.get()
|
|
100
|
+
|
|
101
|
+
for obj in self.dynamic_col: obj.update()
|
|
102
|
+
|
|
103
|
+
for event in self.events:
|
|
104
|
+
if event.type == pygame.QUIT:
|
|
105
|
+
self.quit()
|
|
106
|
+
if event.type == pygame.MOUSEBUTTONDOWN:
|
|
107
|
+
if event.button == 1:
|
|
108
|
+
self.mouse_down = True
|
|
109
|
+
if event.type == pygame.MOUSEBUTTONUP and self.mouse_down:
|
|
110
|
+
if event.button == 1:
|
|
111
|
+
self.mouse_down = False
|
|
112
|
+
self.click = True
|
|
113
|
+
|
|
114
|
+
def update(self, _clear=True):
|
|
115
|
+
if not self.__on: return
|
|
116
|
+
self.__update()
|
|
117
|
+
self.__handle_queue()
|
|
118
|
+
|
|
119
|
+
#update objs
|
|
120
|
+
for obj in self.objects: obj.update()
|
|
121
|
+
#update scene
|
|
122
|
+
if self.active_scene is not None:
|
|
123
|
+
self.active_scene.update()
|
|
124
|
+
self.active_scene._update()
|
|
125
|
+
|
|
126
|
+
if _clear: self.clear_screen() #used if user is drawing objects themselves
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def clear_screen(self): self.screen.fill(self.bg)
|
|
130
|
+
|
|
131
|
+
def render(self, _clear=True):
|
|
132
|
+
if not self.__on: return
|
|
133
|
+
|
|
134
|
+
if _clear: self.screen.fill(self.bg)
|
|
135
|
+
#render call
|
|
136
|
+
if self.active_scene is None:
|
|
137
|
+
for obj in self.render_queue: obj.render(self.cam)
|
|
138
|
+
#render scene
|
|
139
|
+
else:
|
|
140
|
+
self.active_scene.render(self.cam)
|
|
141
|
+
self.active_scene._render(self.cam)
|
|
142
|
+
if self.show_collisions: self.__render_collisions()
|
|
143
|
+
#update screen
|
|
144
|
+
pygame.display.flip()
|
|
145
|
+
|
|
146
|
+
def __render_collisions(self):
|
|
147
|
+
for coll in self.collisions:
|
|
148
|
+
pygame.draw.rect(self.screen, "yellow", coll, 2)
|
|
149
|
+
|
|
150
|
+
def quit(self):
|
|
151
|
+
self.__on = False
|
|
152
|
+
pygame.quit()
|
|
153
|
+
sys.exit()
|
|
154
|
+
|
|
155
|
+
def set_title(self, title):
|
|
156
|
+
self.title = title
|
|
157
|
+
pygame.display.set_caption(self.title)
|
|
158
|
+
|
|
159
|
+
def Tick(self):
|
|
160
|
+
self.update()
|
|
161
|
+
self.render()
|
|
162
|
+
def Run(self, _esc=False):
|
|
163
|
+
while True:
|
|
164
|
+
self.Tick()
|
|
165
|
+
if _esc and Keys.is_pressed(pygame.K_ESCAPE): self.quit()
|
|
166
|
+
def full_reset(self):
|
|
167
|
+
self.objects = []
|
|
168
|
+
self.queue_creation = []
|
|
169
|
+
self.queue_del = []
|
|
170
|
+
self.render_queue = []
|
|
171
|
+
self.scenes = {}
|
|
172
|
+
self.id = 0
|
|
173
|
+
self.active_scene = None
|
|
174
|
+
self._clear_all_collisions_()
|
|
175
|
+
def _clear_all_collisions_(self):
|
|
176
|
+
self.collisions = []
|
|
177
|
+
self.col = []
|
|
178
|
+
self.queue_creation_coll = []
|
|
179
|
+
self.queue_del_coll = []
|
|
180
|
+
self._update_coll = False
|
|
181
|
+
|
|
182
|
+
def set_scene(self, scene=None):
|
|
183
|
+
if self.active_scene is not None: self.active_scene.destroy()
|
|
184
|
+
self._clear_all_collisions_()
|
|
185
|
+
self.active_scene = self.scenes.get(scene, None)
|
|
186
|
+
if self.active_scene: self.active_scene.on_change()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class Scene:
|
|
190
|
+
def __init__(self, name):
|
|
191
|
+
self.name = name
|
|
192
|
+
Game.scenes[name] = self
|
|
193
|
+
self._objects = []
|
|
194
|
+
self._render_queue = []
|
|
195
|
+
self.queue_creation = []
|
|
196
|
+
self.queue_del = []
|
|
197
|
+
Game.set_scene(name)
|
|
198
|
+
|
|
199
|
+
def on_change(self): pass
|
|
200
|
+
|
|
201
|
+
def update(self): pass #user defined
|
|
202
|
+
def _update(self): #built in method
|
|
203
|
+
self.__handle_queue()
|
|
204
|
+
for obj in self._objects: obj.update()
|
|
205
|
+
|
|
206
|
+
def render(self, cam): pass #user defined
|
|
207
|
+
def _render(self, cam): #built in method
|
|
208
|
+
#update render queue based on global objects
|
|
209
|
+
_length = len(self._objects)+len(Game.objects)
|
|
210
|
+
if _length != len(self._render_queue): self.__update_render_queue()
|
|
211
|
+
#render
|
|
212
|
+
for obj in self._render_queue: obj.render(cam)
|
|
213
|
+
|
|
214
|
+
def _queue_add(self, obj): self.queue_creation.append(obj)
|
|
215
|
+
def _queue_remove(self, obj): self.queue_del.append(obj)
|
|
216
|
+
def __update_render_queue(self):
|
|
217
|
+
objs = self._objects + Game.objects
|
|
218
|
+
self._render_queue = sorted(objs, key=lambda obj: obj.z_index)
|
|
219
|
+
def __handle_queue(self):
|
|
220
|
+
for obj in self.queue_creation: self._objects.append(obj)
|
|
221
|
+
for obj in self.queue_del: self._objects.remove(obj)
|
|
222
|
+
if len(self.queue_creation) > 0 or len(self.queue_del) > 0: self.__update_render_queue()
|
|
223
|
+
self.queue_creation = []
|
|
224
|
+
self.queue_del = []
|
|
225
|
+
|
|
226
|
+
def destroy(self):
|
|
227
|
+
for obj in self._objects: obj.destroy()
|
|
228
|
+
self._objects = []
|
|
229
|
+
self._render_queue = []
|
|
230
|
+
self.queue_creation = []
|
|
231
|
+
self.queue_del = []
|
|
232
|
+
|
|
233
|
+
Game: _game = _game()
|
|
234
|
+
|
|
235
|
+
class Camera:
|
|
236
|
+
def __init__(self, game):
|
|
237
|
+
self.pos = Vector2D(0, 0)
|
|
238
|
+
self.game = game
|
|
239
|
+
|
|
240
|
+
#rendering helpers
|
|
241
|
+
def draw_rect(self, rect, color, width=0, offset=True):
|
|
242
|
+
r = rect.copy()
|
|
243
|
+
if offset: r.topleft = (r.x - self.pos.x, r.y - self.pos.y)
|
|
244
|
+
pygame.draw.rect(self.game.screen, color, r, width)
|
|
245
|
+
def draw_circle(self, pos, r, color, width=0, offset=True):
|
|
246
|
+
p = pos.copy()
|
|
247
|
+
if offset: p -= self.pos
|
|
248
|
+
pygame.draw.circle(self.game.screen, color, p.to_int(), r, width)
|
|
249
|
+
def draw_line(self, start, end, color, width=1, offset=True):
|
|
250
|
+
s = start.copy()
|
|
251
|
+
e = end.copy()
|
|
252
|
+
if offset:
|
|
253
|
+
s -= self.pos
|
|
254
|
+
e -= self.pos
|
|
255
|
+
pygame.draw.line(self.game.screen, color, s.to_int(), e.to_int(), width)
|
|
256
|
+
def draw_text(self, surface, rect, offset=True):
|
|
257
|
+
r = rect.copy()
|
|
258
|
+
if offset: r.topleft = (r.x - self.pos.x, r.y - self.pos.y)
|
|
259
|
+
self.game.screen.blit(surface, r)
|
|
260
|
+
def draw_image(self, image, pos, offset=True):
|
|
261
|
+
p = pos.copy()
|
|
262
|
+
if offset: p -= self.pos
|
|
263
|
+
self.game.screen.blit(image, p.to_int())
|
|
264
|
+
|
|
265
|
+
def handle_collision(self, dim: Vector2D, Rect=None, ignore_self=True) -> dict:
|
|
266
|
+
returns = {"on_floor": False, "on_wall": False}
|
|
267
|
+
|
|
268
|
+
# 1. Horizontal Collisions
|
|
269
|
+
self.pos.x += self.vel.x * Game.dt
|
|
270
|
+
player_rect = pygame.Rect(self.pos.to_int(), dim.to_int())
|
|
271
|
+
for rect in Game.collisions:
|
|
272
|
+
if ignore_self and Rect is not None:
|
|
273
|
+
if rect == Rect: continue
|
|
274
|
+
if player_rect.colliderect(rect):
|
|
275
|
+
returns["on_wall"] = True
|
|
276
|
+
self.pos.x -= self.vel.x * Game.dt
|
|
277
|
+
self.vel.x = 0
|
|
278
|
+
break
|
|
279
|
+
|
|
280
|
+
# 2. Vertical Collisions
|
|
281
|
+
self.pos.y += self.vel.y * Game.dt
|
|
282
|
+
player_rect = pygame.Rect(self.pos.to_int(), dim.to_int())
|
|
283
|
+
for rect in Game.collisions:
|
|
284
|
+
if ignore_self and Rect is not None:
|
|
285
|
+
if rect == Rect: continue
|
|
286
|
+
if player_rect.colliderect(rect):
|
|
287
|
+
# Step back first
|
|
288
|
+
self.pos.y -= self.vel.y * Game.dt
|
|
289
|
+
|
|
290
|
+
if self.vel.y > 0 and player_rect.bottom >= rect.top and player_rect.top < rect.top:
|
|
291
|
+
returns["on_floor"] = True
|
|
292
|
+
|
|
293
|
+
self.vel.y = 0
|
|
294
|
+
break
|
|
295
|
+
|
|
296
|
+
return returns
|
|
297
|
+
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import pygame
|
|
2
|
+
from .Math import Vector2D
|
|
3
|
+
|
|
4
|
+
class _keys:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
|
|
7
|
+
#alphabet
|
|
8
|
+
self.a = pygame.K_a
|
|
9
|
+
self.b = pygame.K_b
|
|
10
|
+
self.c = pygame.K_c
|
|
11
|
+
self.d = pygame.K_d
|
|
12
|
+
self.e = pygame.K_e
|
|
13
|
+
self.f = pygame.K_f
|
|
14
|
+
self.g = pygame.K_g
|
|
15
|
+
self.h = pygame.K_h
|
|
16
|
+
self.i = pygame.K_i
|
|
17
|
+
self.j = pygame.K_j
|
|
18
|
+
self.k = pygame.K_k
|
|
19
|
+
self.l = pygame.K_l
|
|
20
|
+
self.m = pygame.K_m
|
|
21
|
+
self.n = pygame.K_n
|
|
22
|
+
self.o = pygame.K_o
|
|
23
|
+
self.p = pygame.K_p
|
|
24
|
+
self.q = pygame.K_q
|
|
25
|
+
self.r = pygame.K_r
|
|
26
|
+
self.s = pygame.K_s
|
|
27
|
+
self.t = pygame.K_t
|
|
28
|
+
self.u = pygame.K_u
|
|
29
|
+
self.v = pygame.K_v
|
|
30
|
+
self.w = pygame.K_w
|
|
31
|
+
self.x = pygame.K_x
|
|
32
|
+
self.y = pygame.K_y
|
|
33
|
+
self.z = pygame.K_z
|
|
34
|
+
|
|
35
|
+
#numbers
|
|
36
|
+
self.n0 = pygame.K_0
|
|
37
|
+
self.n1 = pygame.K_1
|
|
38
|
+
self.n2 = pygame.K_2
|
|
39
|
+
self.n3 = pygame.K_3
|
|
40
|
+
self.n4 = pygame.K_4
|
|
41
|
+
self.n5 = pygame.K_5
|
|
42
|
+
self.n6 = pygame.K_6
|
|
43
|
+
self.n7 = pygame.K_7
|
|
44
|
+
self.n8 = pygame.K_8
|
|
45
|
+
self.n9 = pygame.K_9
|
|
46
|
+
|
|
47
|
+
#special keys
|
|
48
|
+
self.enter = pygame.K_RETURN
|
|
49
|
+
self.escape = pygame.K_ESCAPE
|
|
50
|
+
self.space = pygame.K_SPACE
|
|
51
|
+
self.backspace = pygame.K_BACKSPACE
|
|
52
|
+
self.tab = pygame.K_TAB
|
|
53
|
+
self.control = pygame.K_LCTRL
|
|
54
|
+
self.alt = pygame.K_LALT
|
|
55
|
+
|
|
56
|
+
#arrow keys
|
|
57
|
+
self.up = pygame.K_UP
|
|
58
|
+
self.down = pygame.K_DOWN
|
|
59
|
+
self.left = pygame.K_LEFT
|
|
60
|
+
self.right = pygame.K_RIGHT
|
|
61
|
+
|
|
62
|
+
self.shift = pygame.K_LSHIFT
|
|
63
|
+
|
|
64
|
+
#mouse
|
|
65
|
+
self.mouse_x, self.mouse_y = 0, 0
|
|
66
|
+
|
|
67
|
+
def is_pressed(self, key):
|
|
68
|
+
events = self.Net.events
|
|
69
|
+
if events is None: return False
|
|
70
|
+
for event in events:
|
|
71
|
+
if event.type == pygame.KEYDOWN:
|
|
72
|
+
if event.key == key: return True
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
def is_pressed_list(self, keys):
|
|
76
|
+
for key in keys:
|
|
77
|
+
if self.is_pressed(key): return True
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
def is_held(self, key):
|
|
81
|
+
keys = pygame.key.get_pressed()
|
|
82
|
+
return keys[key]
|
|
83
|
+
|
|
84
|
+
def get_axis(self, negative_action, positive_action) -> int:
|
|
85
|
+
value = 0
|
|
86
|
+
if self.is_held(positive_action): value += 1
|
|
87
|
+
if self.is_held(negative_action): value -= 1
|
|
88
|
+
return value
|
|
89
|
+
def get_vector(self, neg_x, pos_x, neg_y, pos_y, normalise=True) -> Vector2D:
|
|
90
|
+
value = Vector2D(self.get_axis(neg_x, pos_x), self.get_axis(neg_y, pos_y))
|
|
91
|
+
if normalise and value.length() > 1.0:
|
|
92
|
+
value.normalize()
|
|
93
|
+
return value
|
|
94
|
+
|
|
95
|
+
def _add_net(self, net): self.Net = net
|
|
96
|
+
|
|
97
|
+
def update(self): self.mouse_x, self.mouse_y = pygame.mouse.get_pos()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
Keys = _keys()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import pygame
|
|
2
|
+
|
|
3
|
+
class Vector2D:
|
|
4
|
+
def __init__(self, x, y):
|
|
5
|
+
self.x = x
|
|
6
|
+
self.y = y
|
|
7
|
+
def to_int(self): return (int(self.x), int(self.y))
|
|
8
|
+
def to_tuple(self): return (tuple(self.x), tuple(self.y))
|
|
9
|
+
def copy(self): return Vector2D(self.x, self.y)
|
|
10
|
+
def int(self):
|
|
11
|
+
self.x = int(self.x)
|
|
12
|
+
self.y = int(self.y)
|
|
13
|
+
|
|
14
|
+
def length(self):
|
|
15
|
+
return (self.x**2 + self.y**2)**0.5
|
|
16
|
+
|
|
17
|
+
def normalize(self):
|
|
18
|
+
length = self.length()
|
|
19
|
+
self.x /= length
|
|
20
|
+
self.y /= length
|
|
21
|
+
return self
|
|
22
|
+
|
|
23
|
+
def __add__(self, other):
|
|
24
|
+
if type(other) == Vector2D:
|
|
25
|
+
return Vector2D(self.x + other.x, self.y + other.y)
|
|
26
|
+
else:
|
|
27
|
+
return Vector2D(self.x + other, self.y + other)
|
|
28
|
+
|
|
29
|
+
def __sub__(self, other):
|
|
30
|
+
if type(other) == Vector2D:
|
|
31
|
+
return Vector2D(self.x - other.x, self.y - other.y)
|
|
32
|
+
else:
|
|
33
|
+
return Vector2D(self.x - other, self.y - other)
|
|
34
|
+
|
|
35
|
+
def __mul__(self, other):
|
|
36
|
+
if type(other) == Vector2D:
|
|
37
|
+
return Vector2D(self.x * other.x, self.y * other.y)
|
|
38
|
+
else:
|
|
39
|
+
return Vector2D(self.x * other, self.y * other)
|
|
40
|
+
|
|
41
|
+
def __truediv__(self, other):
|
|
42
|
+
if type(other) == Vector2D:
|
|
43
|
+
return Vector2D(self.x / other.x, self.y / other.y)
|
|
44
|
+
else:
|
|
45
|
+
return Vector2D(self.x / other, self.y / other)
|
|
46
|
+
|
|
47
|
+
def clamp(value, min, max):
|
|
48
|
+
if value < min: return min
|
|
49
|
+
if value > max: return max
|
|
50
|
+
return value
|
|
51
|
+
|
|
52
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import pygame
|
|
2
|
+
|
|
3
|
+
class _net_:
|
|
4
|
+
def __init__(self):
|
|
5
|
+
self.screen: pygame.Surface = None
|
|
6
|
+
self.dt: float = 0.0
|
|
7
|
+
self.clock: pygame.time.Clock = None
|
|
8
|
+
self.width, self.height = 0, 0
|
|
9
|
+
self.fps = 0
|
|
10
|
+
|
|
11
|
+
self.events = []
|
|
12
|
+
self.click = False
|
|
13
|
+
|
|
14
|
+
def _update(self):
|
|
15
|
+
self.click = False
|
|
16
|
+
self.dt = self.clock.tick(self.FPS)/1000.0
|
|
17
|
+
self.events = pygame.event.get()
|
|
18
|
+
|
|
19
|
+
for event in self.events:
|
|
20
|
+
if event.type == pygame.QUIT:
|
|
21
|
+
self.shut_down()
|
|
22
|
+
if event.type == pygame.MOUSEBUTTONDOWN:
|
|
23
|
+
if event.button == 1:
|
|
24
|
+
self.mouse_down = True
|
|
25
|
+
if event.type == pygame.MOUSEBUTTONUP and self.mouse_down:
|
|
26
|
+
if event.button == 1:
|
|
27
|
+
self.mouse_down = False
|
|
28
|
+
self.click = True
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
Net = _net_()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#imports
|
|
2
|
+
"PyScene is a custom python + pygame framework / engine developed to streamline 2d game development."
|
|
3
|
+
"Version: 1.0.0"
|
|
4
|
+
|
|
5
|
+
from .Game import Game, Scene, handle_collision
|
|
6
|
+
from .Math import *
|
|
7
|
+
from .utils.Subscreen import *
|
|
8
|
+
|
|
9
|
+
from .Keys import *
|
|
10
|
+
from .objs import *
|
|
11
|
+
from .utils import *
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
#all
|
|
15
|
+
__all__ = [
|
|
16
|
+
"Vector2D",
|
|
17
|
+
"SubScreen",
|
|
18
|
+
"Game",
|
|
19
|
+
"Scene",
|
|
20
|
+
"Keys",
|
|
21
|
+
|
|
22
|
+
"GameObj",
|
|
23
|
+
"Rect",
|
|
24
|
+
"Circle",
|
|
25
|
+
"Line",
|
|
26
|
+
"Text",
|
|
27
|
+
"Image",
|
|
28
|
+
|
|
29
|
+
"StaticCollisionRect",
|
|
30
|
+
"DynamicCollisionRect",
|
|
31
|
+
|
|
32
|
+
"Assets",
|
|
33
|
+
"handle_collision"
|
|
34
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import pygame
|
|
2
|
+
from ..Game import Game, handle_collision
|
|
3
|
+
from ..Math import Vector2D
|
|
4
|
+
from .GameObj import GameObj
|
|
5
|
+
|
|
6
|
+
class StaticCollisionRect:
|
|
7
|
+
def __init__(self, x, y, w, h, active=True):
|
|
8
|
+
self.rect = pygame.Rect(x, y, w, h)
|
|
9
|
+
self.active = active
|
|
10
|
+
|
|
11
|
+
Game._update_coll = True
|
|
12
|
+
Game._queue_add_coll(self)
|
|
13
|
+
|
|
14
|
+
def set_active(self, active=True):
|
|
15
|
+
self.active = active
|
|
16
|
+
Game._update_coll = True
|
|
17
|
+
def destroy(self):
|
|
18
|
+
Game._queue_remove_coll(self)
|
|
19
|
+
|
|
20
|
+
class DynamicCollisionRect:
|
|
21
|
+
def __init__(self, x, y, w, h, active=True):
|
|
22
|
+
self.pos = Vector2D(x, y)
|
|
23
|
+
self.dim = Vector2D(w, h)
|
|
24
|
+
self.active = active
|
|
25
|
+
self.vel = Vector2D(0.0, 0.0)
|
|
26
|
+
self.rect = pygame.Rect(x, y, w, h)
|
|
27
|
+
|
|
28
|
+
Game._update_coll = True
|
|
29
|
+
Game._queue_add_D_coll(self)
|
|
30
|
+
|
|
31
|
+
def update(self):
|
|
32
|
+
if self.vel.to_int != (0.0, 0.0): Game._update_coll = True
|
|
33
|
+
#print(self.active, len(Game.collisions), self.vel.x)
|
|
34
|
+
handle_collision(self, self.dim, self.rect)
|
|
35
|
+
self.rect.topleft = self.pos.to_int()
|
|
36
|
+
|
|
37
|
+
def set_active(self, active=True):
|
|
38
|
+
self.active = active
|
|
39
|
+
Game._update_coll = True
|
|
40
|
+
def destroy(self):
|
|
41
|
+
Game._queue_remove_D_coll(self)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import pygame
|
|
2
|
+
from ..Math import Vector2D
|
|
3
|
+
from ..Game import Game
|
|
4
|
+
|
|
5
|
+
class GameObj: #this will be the base class of every game obj
|
|
6
|
+
def __init__(self, pos, z_index=0, active=True, visible=True):
|
|
7
|
+
self.pos: Vector2D = pos
|
|
8
|
+
self.vel: Vector2D = Vector2D(0, 0)
|
|
9
|
+
self.z_index = z_index
|
|
10
|
+
self.active = active
|
|
11
|
+
self.visible = visible
|
|
12
|
+
self._id = Game._get_id()
|
|
13
|
+
|
|
14
|
+
#add to creation
|
|
15
|
+
Game._queue_add(self)
|
|
16
|
+
|
|
17
|
+
def update(self): pass
|
|
18
|
+
def render(self, cam): pass
|
|
19
|
+
def destroy(self): Game._queue_remove(self)
|
|
20
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import pygame
|
|
2
|
+
from ..Math import Vector2D
|
|
3
|
+
from ..Game import Game
|
|
4
|
+
|
|
5
|
+
from .GameObj import GameObj
|
|
6
|
+
|
|
7
|
+
class Rect(GameObj):
|
|
8
|
+
def __init__(self, x, y, w, h, color, z_index=0, width=0, active=True, visible=True, offset=True):
|
|
9
|
+
super().__init__(Vector2D(x, y), z_index, active, visible)
|
|
10
|
+
self.dim = Vector2D(w, h)
|
|
11
|
+
self.color = color
|
|
12
|
+
self.rect = pygame.Rect((x, y), (w, h))
|
|
13
|
+
self.offset = offset
|
|
14
|
+
self.width = width
|
|
15
|
+
|
|
16
|
+
def update(self):
|
|
17
|
+
if self.pos.to_int() != self.rect.topleft: self.rect.topleft = self.pos.to_int()
|
|
18
|
+
|
|
19
|
+
def render(self, cam):
|
|
20
|
+
if not self.visible: return
|
|
21
|
+
cam.draw_rect(self.rect, self.color, self.width, self.offset)
|
|
22
|
+
|
|
23
|
+
class Circle(GameObj):
|
|
24
|
+
def __init__(self, x, y, r, color, z_index=0, width=0, active=True, visible=True, offset=True):
|
|
25
|
+
super().__init__(Vector2D(x, y), z_index, active, visible)
|
|
26
|
+
self.radius = r
|
|
27
|
+
self.color = color
|
|
28
|
+
self.width = width
|
|
29
|
+
self.offset = offset
|
|
30
|
+
|
|
31
|
+
def render(self, cam):
|
|
32
|
+
if not self.visible: return
|
|
33
|
+
cam.draw_circle(self.pos, self.radius, self.color, self.width, self.offset)
|
|
34
|
+
|
|
35
|
+
class Line(GameObj):
|
|
36
|
+
def __init__(self, x1, y1, x2, y2, color, z_index=0, width=1, active=True, visible=True, offset=True):
|
|
37
|
+
super().__init__(Vector2D(x1, y1), z_index, active, visible)
|
|
38
|
+
self.end = Vector2D(x2, y2)
|
|
39
|
+
self.color = color
|
|
40
|
+
self.width = width
|
|
41
|
+
self.offset = offset
|
|
42
|
+
|
|
43
|
+
def render(self, cam):
|
|
44
|
+
if not self.visible: return
|
|
45
|
+
cam.draw_line(self.pos, self.end, self.color, self.width, self.offset)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from ..Math import Vector2D
|
|
2
|
+
from ..Game import Game
|
|
3
|
+
from .GameObj import GameObj
|
|
4
|
+
import pygame
|
|
5
|
+
|
|
6
|
+
class Text(GameObj):
|
|
7
|
+
def __init__(self, font, text, color, pos=Vector2D(0, 0), align="center", z_index=0, active=True, visible=True, offset=True):
|
|
8
|
+
super().__init__(pos, z_index, active, visible)
|
|
9
|
+
self.font = font
|
|
10
|
+
self.text = text
|
|
11
|
+
self.color = color
|
|
12
|
+
self.align = align
|
|
13
|
+
self.surface = None
|
|
14
|
+
self.rect = None
|
|
15
|
+
self.offset = offset
|
|
16
|
+
|
|
17
|
+
self._update_surface()
|
|
18
|
+
|
|
19
|
+
def _update_surface(self):
|
|
20
|
+
"""Internal method to re-render surface only when text changes."""
|
|
21
|
+
self.surface = self.font.render(str(self.text), False, self.color) # False for sharp pixel fonts!
|
|
22
|
+
# Dynamically set rect based on alignment (topleft, center, topright, etc.)
|
|
23
|
+
self.rect = self.surface.get_rect(**{self.align: self.pos.to_int()})
|
|
24
|
+
|
|
25
|
+
def set_text(self, new_text, color=None):
|
|
26
|
+
"""Only call this when the string actually changes!"""
|
|
27
|
+
if color is not None:
|
|
28
|
+
self.color = color
|
|
29
|
+
if new_text != self.text:
|
|
30
|
+
self.text = new_text
|
|
31
|
+
self._update_surface()
|
|
32
|
+
|
|
33
|
+
def set_pos(self, new_pos):
|
|
34
|
+
self.pos = new_pos
|
|
35
|
+
self.rect = self.surface.get_rect(**{self.align: self.pos})
|
|
36
|
+
|
|
37
|
+
def render(self, cam):
|
|
38
|
+
if not self.visible: return
|
|
39
|
+
cam.draw_text(self.surface, self.rect, self.offset)
|
|
40
|
+
|
|
41
|
+
class Image(GameObj):
|
|
42
|
+
def __init__(self, image, pos=Vector2D(0, 0), z_index=0, active=True, visible=True, offset=True):
|
|
43
|
+
super().__init__(pos, z_index, active, visible)
|
|
44
|
+
self.image = image
|
|
45
|
+
self.offset = offset
|
|
46
|
+
|
|
47
|
+
def render(self, cam):
|
|
48
|
+
if not self.visible: return
|
|
49
|
+
cam.draw_image(self.image, self.pos, self.offset)
|
|
50
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from .GameObj import GameObj
|
|
2
|
+
from .Shapes import *
|
|
3
|
+
from .Text import *
|
|
4
|
+
from .animation import *
|
|
5
|
+
from .Collision_shapes import *
|
|
6
|
+
|
|
7
|
+
__all__ = ["GameObj", "Rect", "Circle", "Line", "Text", "Image", "Animation", "AnimationManager", "StaticCollisionRect", "DynamicCollisionRect"]
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import pygame
|
|
2
|
+
|
|
3
|
+
from ..Math import Vector2D
|
|
4
|
+
from .GameObj import GameObj
|
|
5
|
+
from ..Game import Game
|
|
6
|
+
|
|
7
|
+
class Animation(GameObj):
|
|
8
|
+
def __init__(self, image, size, fps, loop=True, pos=Vector2D(0, 0), z_index=0, active=True, visible=True, offset=True):
|
|
9
|
+
super().__init__(pos, z_index, active, visible)
|
|
10
|
+
self.offset = offset
|
|
11
|
+
self.image = image
|
|
12
|
+
self.size = size
|
|
13
|
+
self.fps = fps
|
|
14
|
+
self.images = []
|
|
15
|
+
self.index = 0.0
|
|
16
|
+
self.loop=loop
|
|
17
|
+
self.done=False
|
|
18
|
+
|
|
19
|
+
# Automatically calculate frames based on image width
|
|
20
|
+
total_frames = int(image.get_width() // size)
|
|
21
|
+
|
|
22
|
+
img_rect = image.get_rect()
|
|
23
|
+
|
|
24
|
+
for i in range(total_frames):
|
|
25
|
+
# Slice each frame from the sheet
|
|
26
|
+
frame_rect = pygame.Rect(i * size, 0, size, size)
|
|
27
|
+
#checks
|
|
28
|
+
#if frame_rect.height > img_rect.height: frame_rect.height = img_rect.height
|
|
29
|
+
self.images.append(self.image.subsurface(frame_rect))
|
|
30
|
+
|
|
31
|
+
def last_frame(self):
|
|
32
|
+
return int(self.index) >= (len(self.images) - 1)
|
|
33
|
+
def reset(self):
|
|
34
|
+
self.index = 0
|
|
35
|
+
self.done = False
|
|
36
|
+
|
|
37
|
+
def update(self):
|
|
38
|
+
if self.done:
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
self.index += self.fps * Game.dt
|
|
42
|
+
|
|
43
|
+
if self.index >= len(self.images):
|
|
44
|
+
if self.loop:
|
|
45
|
+
# Wrap around using modulo so fractional progress carries over smoothly
|
|
46
|
+
self.index %= len(self.images)
|
|
47
|
+
else:
|
|
48
|
+
# Clamp to the exact last frame index and mark as finished
|
|
49
|
+
self.index = float(len(self.images) - 1)
|
|
50
|
+
self.done = True
|
|
51
|
+
|
|
52
|
+
def get_image(self):
|
|
53
|
+
# Safe indexing guard
|
|
54
|
+
idx = min(int(self.index), len(self.images) - 1)
|
|
55
|
+
return self.images[idx]
|
|
56
|
+
|
|
57
|
+
def flip_v(self):
|
|
58
|
+
for i in range(len(self.images)):
|
|
59
|
+
self.images[i] = pygame.transform.flip(self.images[i], False, True)
|
|
60
|
+
|
|
61
|
+
def flip_h(self):
|
|
62
|
+
for i in range(len(self.images)):
|
|
63
|
+
self.images[i] = pygame.transform.flip(self.images[i], True, False)
|
|
64
|
+
|
|
65
|
+
def copy(self):
|
|
66
|
+
return Animation(self.image, self.size, self.fps, self.loop)
|
|
67
|
+
def render(self, cam):
|
|
68
|
+
if not self.visible: return
|
|
69
|
+
cam.draw_image(self.get_image(), self.pos, self.offset)
|
|
70
|
+
|
|
71
|
+
class AnimationManager(GameObj):
|
|
72
|
+
def __init__(self, animations: dict[str, Animation], current_anim=None, z_index=0, active=True, visible=True, offset=True):
|
|
73
|
+
super().__init__(Vector2D(0, 0), z_index, active, visible)
|
|
74
|
+
self.animations = animations
|
|
75
|
+
self.current_anim = current_anim
|
|
76
|
+
|
|
77
|
+
for anim in self.animations:
|
|
78
|
+
anim.destroy() #remove from global or scene calls
|
|
79
|
+
|
|
80
|
+
def new_anim(self, name, anim):
|
|
81
|
+
self.animations[name] = anim
|
|
82
|
+
anim.destroy()
|
|
83
|
+
|
|
84
|
+
def new_anim_set(self, anims: dict[str, Animation]):
|
|
85
|
+
#add new anims to existing ones
|
|
86
|
+
self.animations.update(anims)
|
|
87
|
+
for anim in anims:
|
|
88
|
+
anims[anim].destroy()
|
|
89
|
+
|
|
90
|
+
def flip_v(self):
|
|
91
|
+
for name in self.animations:
|
|
92
|
+
self.animations[name].flip_v()
|
|
93
|
+
|
|
94
|
+
def flip_h(self):
|
|
95
|
+
for name in self.animations:
|
|
96
|
+
self.animations[name].flip_h()
|
|
97
|
+
|
|
98
|
+
def change_anim(self, name):
|
|
99
|
+
self.current_anim = name
|
|
100
|
+
|
|
101
|
+
def update(self):
|
|
102
|
+
if self.current_anim is None and self.current_anim in self.animations:
|
|
103
|
+
return
|
|
104
|
+
self.animations[self.current_anim].update(Game.dt)
|
|
105
|
+
def get_image(self) -> pygame.Surface:
|
|
106
|
+
if self.current_anim is None and self.current_anim in self.animations:
|
|
107
|
+
return
|
|
108
|
+
return self.animations[self.current_anim].get_image()
|
|
109
|
+
def get_animation(self) -> Animation:
|
|
110
|
+
if self.current_anim is None and self.current_anim in self.animations:
|
|
111
|
+
return
|
|
112
|
+
return self.animations[self.current_anim]
|
|
113
|
+
|
|
114
|
+
def render(self, cam):
|
|
115
|
+
if not self.visible: return
|
|
116
|
+
self.get_animation().render(cam)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import pygame
|
|
2
|
+
|
|
3
|
+
from ..objs import Animation
|
|
4
|
+
from ..Math import Vector2D
|
|
5
|
+
|
|
6
|
+
class _assets:
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self.images: dict[str, pygame.Surface] = {}
|
|
9
|
+
self.animations: dict[str, Animation] = {}
|
|
10
|
+
self.fonts: dict[str, pygame.font.Font] = {}
|
|
11
|
+
self.sounds: dict[str, pygame.mixer.Sound] = {}
|
|
12
|
+
self.tilesets: dict[str, dict[tuple[int, int], pygame.surface.Surface]] = {}
|
|
13
|
+
self._tileset_cache_data: dict[str, tuple[int, int]] = {}
|
|
14
|
+
|
|
15
|
+
#clearing
|
|
16
|
+
def _clear(self, _confirm=False):
|
|
17
|
+
if _confirm:
|
|
18
|
+
self.images = {}
|
|
19
|
+
self.animations = {}
|
|
20
|
+
self.fonts = {}
|
|
21
|
+
self.sounds = {}
|
|
22
|
+
self.tilesets = {}
|
|
23
|
+
self._tileset_cache_data = {}
|
|
24
|
+
|
|
25
|
+
def clear_images(self, _confirm=False):
|
|
26
|
+
if _confirm:
|
|
27
|
+
self.images = {}
|
|
28
|
+
|
|
29
|
+
def del_image(self, name):
|
|
30
|
+
if name in self.images:
|
|
31
|
+
del self.images[name]
|
|
32
|
+
|
|
33
|
+
def clear_animations(self, _confirm=False):
|
|
34
|
+
if _confirm:
|
|
35
|
+
self.animations = {}
|
|
36
|
+
|
|
37
|
+
def del_animation(self, name):
|
|
38
|
+
if name in self.animations:
|
|
39
|
+
del self.animations[name]
|
|
40
|
+
|
|
41
|
+
def clear_fonts(self, _confirm=False):
|
|
42
|
+
if _confirm:
|
|
43
|
+
self.fonts = {}
|
|
44
|
+
|
|
45
|
+
def del_font(self, name):
|
|
46
|
+
if name in self.fonts:
|
|
47
|
+
del self.fonts[name]
|
|
48
|
+
|
|
49
|
+
def clear_sounds(self, _confirm=False):
|
|
50
|
+
if _confirm:
|
|
51
|
+
self.sounds = {}
|
|
52
|
+
|
|
53
|
+
def del_sound(self, name):
|
|
54
|
+
if name in self.sounds:
|
|
55
|
+
del self.sounds[name]
|
|
56
|
+
|
|
57
|
+
def clear_tilesets(self, _confirm=False):
|
|
58
|
+
if _confirm:
|
|
59
|
+
self.tilesets = {}
|
|
60
|
+
self._tileset_cache_data = {}
|
|
61
|
+
|
|
62
|
+
def del_tileset(self, name):
|
|
63
|
+
if name in self.tilesets:
|
|
64
|
+
del self.tilesets[name]
|
|
65
|
+
del self._tileset_cache_data[name]
|
|
66
|
+
|
|
67
|
+
#creation
|
|
68
|
+
def new_image(self, name, path, rect=None, scale=1.0, colorKey=None, sWidth=None, sHeight=None):
|
|
69
|
+
image = pygame.image.load(path)
|
|
70
|
+
#rect
|
|
71
|
+
if rect is not None:
|
|
72
|
+
image = image.subsurface(rect)
|
|
73
|
+
#scale
|
|
74
|
+
if scale != 1.0:
|
|
75
|
+
image = pygame.transform.scale_by(image, scale)
|
|
76
|
+
#colorkey
|
|
77
|
+
if colorKey is not None:
|
|
78
|
+
image.set_colorkey(colorKey)
|
|
79
|
+
#size
|
|
80
|
+
if sWidth is not None and sHeight is not None:
|
|
81
|
+
image = pygame.transform.scale(image, (sWidth, sHeight))
|
|
82
|
+
self.images[name] = image.convert()
|
|
83
|
+
|
|
84
|
+
def new_font(self, name, path, size):
|
|
85
|
+
self.fonts[name] = pygame.font.Font(path, size)
|
|
86
|
+
|
|
87
|
+
def new_animation(self, name, image, size=16, fps=3, loop=True, pos=Vector2D(0, 0), z_index=0, active=True, visible=True, offset=True):
|
|
88
|
+
self.animations[name] = Animation(image, size, fps, loop, pos, z_index, active, visible, offset)
|
|
89
|
+
def new_anim_nIMG(self, name, size=16, fps=3, path=None, rect=None, scale=1.0, colorKey=(0, 0, 0), sWidth=None, sHeight=None, loop=True, pos=Vector2D(0, 0), z_index=0, active=True, visible=True, offset=True):
|
|
90
|
+
#create image
|
|
91
|
+
self.new_image(name, path, rect, scale, colorKey, sWidth, sHeight)
|
|
92
|
+
#create animation
|
|
93
|
+
self.new_animation(name, self.images[name], size*scale, fps, loop, pos, z_index, active, visible, offset)
|
|
94
|
+
|
|
95
|
+
def new_sound(self, name, path):
|
|
96
|
+
self.sounds[name] = pygame.mixer.Sound(path)
|
|
97
|
+
|
|
98
|
+
def new_tileset(self, path, name, width=16, height=16, scale=1.0, colorkey=None):
|
|
99
|
+
image = pygame.image.load(path).convert_alpha()
|
|
100
|
+
if colorkey is not None:
|
|
101
|
+
image.set_colorkey(colorkey)
|
|
102
|
+
if scale != 1.0:
|
|
103
|
+
image = pygame.transform.scale(image, (int(image.get_width()*scale), int(image.get_height()*scale)))
|
|
104
|
+
data, cache = load_tileset(image, width*scale, height*scale)
|
|
105
|
+
self.tilesets[name] = data
|
|
106
|
+
self._tileset_cache_data[name] = cache
|
|
107
|
+
|
|
108
|
+
#getters
|
|
109
|
+
def get_image(self, name):
|
|
110
|
+
if name in self.images:
|
|
111
|
+
return self.images[name]
|
|
112
|
+
else:
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
def get_animation(self, name):
|
|
116
|
+
if name in self.animations:
|
|
117
|
+
return self.animations[name]
|
|
118
|
+
else:
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
def get_font(self, name):
|
|
122
|
+
if name in self.fonts:
|
|
123
|
+
return self.fonts[name]
|
|
124
|
+
else:
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
def get_sound(self, name):
|
|
128
|
+
if name in self.sounds:
|
|
129
|
+
return self.sounds[name]
|
|
130
|
+
else:
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
def get_tileset(self, name):
|
|
134
|
+
if name in self.tilesets:
|
|
135
|
+
return self.tilesets[name]
|
|
136
|
+
else:
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
Assets = _assets()
|
|
142
|
+
|
|
143
|
+
def load_tileset(image, width, height):
|
|
144
|
+
tileset = {}
|
|
145
|
+
w, h = image.get_size()
|
|
146
|
+
w = int(w/width)
|
|
147
|
+
h = int(h/height)
|
|
148
|
+
for y in range(int(image.get_height()//height)):
|
|
149
|
+
for x in range(int(image.get_width()//width)):
|
|
150
|
+
tileset[(x, y)] = image.subsurface(pygame.Rect(x*width, y*height, width, height))
|
|
151
|
+
return tileset, (w, h)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import pygame
|
|
2
|
+
|
|
3
|
+
from ..Math import Vector2D
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SubScreen:
|
|
7
|
+
def __init__(self, x, y, w, h, bgColor: None, displaySurface: pygame.Surface = None, parent=None):
|
|
8
|
+
self.pos = Vector2D(x, y)
|
|
9
|
+
self.dim = Vector2D(w, h)
|
|
10
|
+
self.color = bgColor
|
|
11
|
+
self._screen = displaySurface
|
|
12
|
+
self.parent = parent # Reference to parent SubScreen
|
|
13
|
+
if self.color == None: self.screen = pygame.Surface((w, h), pygame.SRCALPHA).convert_alpha()
|
|
14
|
+
else: self.screen = pygame.Surface((w, h)).convert()
|
|
15
|
+
|
|
16
|
+
def clear(self):
|
|
17
|
+
if self.color == None: self.screen.fill((0, 0, 0, 0))
|
|
18
|
+
else: self.screen.fill(self.color)
|
|
19
|
+
|
|
20
|
+
def get_absolute_pos(self):
|
|
21
|
+
"""Recursively calculates the true global screen position."""
|
|
22
|
+
if self.parent:
|
|
23
|
+
return self.pos + self.parent.get_absolute_pos()
|
|
24
|
+
return self.pos
|
|
25
|
+
|
|
26
|
+
def local_mouse_pos(self, cam: Vector2D = Vector2D(0, 0)):
|
|
27
|
+
# Uses absolute position so nested screens correctly account for parent offsets
|
|
28
|
+
return Vector2D(*pygame.mouse.get_pos()) - (self.get_absolute_pos() - cam)
|
|
29
|
+
|
|
30
|
+
def get_global_rect(self, rect):
|
|
31
|
+
# Uses absolute position so hitboxes align on screen regardless of nesting level
|
|
32
|
+
abs_pos = self.get_absolute_pos()
|
|
33
|
+
return pygame.Rect((abs_pos.x + rect.x, abs_pos.y + rect.y), rect.size)
|
|
34
|
+
|
|
35
|
+
def render(self, cam: Vector2D = Vector2D(0, 0)):
|
|
36
|
+
self._screen.blit(self.screen, (self.pos - cam).to_int())
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from src import *
|
|
2
|
+
import pygame
|
|
3
|
+
from random import randint as I
|
|
4
|
+
while True:
|
|
5
|
+
Game.full_reset()
|
|
6
|
+
Game.init(640, 360, "Floppy Bird", "darkblue")
|
|
7
|
+
bird = Circle(100, 25, 10, "yellow", -1) #show behind pipes
|
|
8
|
+
vel = 0
|
|
9
|
+
top_pipes = [Rect(400, 0, 20, I(20, 100), "green"),Rect(600, 0, 20, I(20, 100), "green"),Rect(800, 0, 20, I(20, 100), "green"),Rect(1000, 0, 20, I(20, 100), "green"),]
|
|
10
|
+
bottom_pipes = [Rect(400, Game.height-I(175, 225), 20, 225, "green"),Rect(600, Game.height-I(175, 225), 20, 225, "green"),Rect(800, Game.height-I(175, 225), 20, 225, "green"),Rect(1000, Game.height-I(175, 225), 20, 225, "green"),]
|
|
11
|
+
text = Text(pygame.font.Font(None, 20), "Score: 0", "white", Vector2D(3, Game.height-20), "topleft", 100)
|
|
12
|
+
score = 0
|
|
13
|
+
added = False
|
|
14
|
+
while True:
|
|
15
|
+
bird.pos.y += vel*Game.dt
|
|
16
|
+
if bird.pos.y < -10 or bird.pos.y > Game.height-10: break
|
|
17
|
+
vel += 4
|
|
18
|
+
if Keys.is_pressed(Keys.space): vel = -150
|
|
19
|
+
for pipe in top_pipes: pipe.pos.x -= 100*Game.dt
|
|
20
|
+
for pipe in bottom_pipes: pipe.pos.x -= 100*Game.dt
|
|
21
|
+
#lose
|
|
22
|
+
if top_pipes[0].rect.collidepoint(bird.pos.to_int()): break
|
|
23
|
+
elif bottom_pipes[0].rect.collidepoint(bird.pos.to_int()): break
|
|
24
|
+
elif top_pipes[0].pos.x <= bird.pos.x and not added: #score
|
|
25
|
+
score+=1
|
|
26
|
+
text.set_text(f"Score: {score}")
|
|
27
|
+
added = True
|
|
28
|
+
#resetting pipes
|
|
29
|
+
if top_pipes[0].pos.x < -21:
|
|
30
|
+
top_pipes[0].destroy()
|
|
31
|
+
top_pipes.pop(0)
|
|
32
|
+
top_pipes.append(Rect(top_pipes[-1].pos.x + 200, 0, 20, I(20, 100), "green"))
|
|
33
|
+
bottom_pipes[0].destroy()
|
|
34
|
+
bottom_pipes.pop(0)
|
|
35
|
+
bottom_pipes.append(Rect(bottom_pipes[-1].pos.x + 200, Game.height-I(175, 225), 20, 225, "green"))
|
|
36
|
+
added = False
|
|
37
|
+
Game.Tick()
|
|
38
|
+
if Keys.is_pressed(Keys.escape): Game.quit()
|
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from src import *
|
|
2
|
+
import pygame
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
Game.init(640, 360, "Test - 01", "darkblue", fullscreen=False)
|
|
6
|
+
|
|
7
|
+
class player(GameObj):
|
|
8
|
+
def __init__(self):
|
|
9
|
+
super().__init__(Vector2D(0, 0), 0)
|
|
10
|
+
#create animation
|
|
11
|
+
Assets.new_anim_nIMG("player", 8, 2, "tests/pause.png", (0, 0, 16, 8), colorKey=None, scale=3.0)
|
|
12
|
+
self.anim = Assets.get_animation("player")
|
|
13
|
+
|
|
14
|
+
def update(self):
|
|
15
|
+
self.pos += (Keys.get_vector(Keys.a, Keys.d, Keys.w, Keys.s, False)*150)*Game.dt
|
|
16
|
+
self.anim.pos = self.pos #positions stay linked
|
|
17
|
+
|
|
18
|
+
player()
|
|
19
|
+
|
|
20
|
+
Game.Run()
|
|
21
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from src import *
|
|
2
|
+
import pygame
|
|
3
|
+
|
|
4
|
+
Game.init(640, 360, "Test - 02: Collisions", "black")
|
|
5
|
+
Game.show_collisions = True
|
|
6
|
+
Game.optimize_static_colliders = True
|
|
7
|
+
|
|
8
|
+
class Player(GameObj):
|
|
9
|
+
def __init__(self):
|
|
10
|
+
super().__init__(Vector2D(200, 0))
|
|
11
|
+
self.coll = DynamicCollisionRect(*self.pos.to_int(), 25, 25)
|
|
12
|
+
|
|
13
|
+
def update(self):
|
|
14
|
+
self.coll.vel = Keys.get_vector(Keys.a, Keys.d, Keys.w, Keys.s, False)*150
|
|
15
|
+
|
|
16
|
+
class TestDynamic(GameObj):
|
|
17
|
+
def __init__(self):
|
|
18
|
+
super().__init__(Vector2D(0, 0), 5)
|
|
19
|
+
self.coll = DynamicCollisionRect(100, 100, 20, 40, True)
|
|
20
|
+
self.coll.vel.x = 100
|
|
21
|
+
self.vel.x = 100
|
|
22
|
+
self.timer = 0.0
|
|
23
|
+
|
|
24
|
+
def update(self):
|
|
25
|
+
self.timer += Game.dt
|
|
26
|
+
if self.timer >= 3.0:
|
|
27
|
+
self.vel.x *= -1
|
|
28
|
+
self.timer = 0.0
|
|
29
|
+
self.coll.vel = self.vel.copy()
|
|
30
|
+
|
|
31
|
+
StaticCollisionRect(0, 0, 50, 100, True)
|
|
32
|
+
StaticCollisionRect(0, 100, 50, 50, True)
|
|
33
|
+
StaticCollisionRect(250, 100, 50, 50, True)
|
|
34
|
+
StaticCollisionRect(300, 100, 50, 50, True)
|
|
35
|
+
TestDynamic()
|
|
36
|
+
|
|
37
|
+
Player()
|
|
38
|
+
Game.Run(True)
|
|
39
|
+
|