VertexEngine 1.2rc4__4-py3-none-any.whl
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.
- VertexEngine/AdvancedVWidgets/ChekBox.py +8 -0
- VertexEngine/AdvancedVWidgets/Dropdown.py +12 -0
- VertexEngine/AdvancedVWidgets/List.py +12 -0
- VertexEngine/AdvancedVWidgets/WindowMods.py +71 -0
- VertexEngine/AdvancedVWidgets/Windows.py +66 -0
- VertexEngine/InputSystem/BtnInputs.py +63 -0
- VertexEngine/InputSystem/Events.py +73 -0
- VertexEngine/InputSystem/KeyInputs.py +64 -0
- VertexEngine/SimpleGUI.py +142 -0
- VertexEngine/Vertex/__init__.py +224 -0
- VertexEngine/VertexScreenModifiers.py +70 -0
- VertexEngine/__init__.py +322 -0
- VertexEngine/assets.py +63 -0
- VertexEngine/audio.py +25 -0
- VertexEngine/engine.py +70 -0
- VertexEngine/example.py +59 -0
- VertexEngine/examples/games/Dodge/main.py +233 -0
- VertexEngine/examples/games/platformer/main.py +205 -0
- VertexEngine/examples/games/snake/main.py +135 -0
- VertexEngine/examples/test.py +55 -0
- VertexEngine/scenes.py +46 -0
- vertexengine-1.2.0rc4.dist-info/METADATA +100 -0
- vertexengine-1.2.0rc4.dist-info/RECORD +26 -0
- vertexengine-1.2.0rc4.dist-info/WHEEL +5 -0
- vertexengine-1.2.0rc4.dist-info/licenses/LICENSE +21 -0
- vertexengine-1.2.0rc4.dist-info/top_level.txt +1 -0
VertexEngine/engine.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from PyQt6.QtWidgets import QWidget
|
|
2
|
+
from PyQt6.QtGui import QImage, QPainter
|
|
3
|
+
from PyQt6.QtCore import QTimer, Qt
|
|
4
|
+
import pygame
|
|
5
|
+
from .scenes import SceneManager
|
|
6
|
+
pygame.init()
|
|
7
|
+
|
|
8
|
+
class GameEngine(QWidget):
|
|
9
|
+
def __init__(self, width=800, height=600, fps=60):
|
|
10
|
+
super().__init__()
|
|
11
|
+
self.width = width
|
|
12
|
+
self.height = height
|
|
13
|
+
self.fps = fps
|
|
14
|
+
|
|
15
|
+
# Qt key tracking
|
|
16
|
+
self.keys_down = set()
|
|
17
|
+
|
|
18
|
+
# pygame surface
|
|
19
|
+
self.screen = pygame.Surface((self.width, self.height))
|
|
20
|
+
|
|
21
|
+
# Scene manager
|
|
22
|
+
self.scene_manager = SceneManager()
|
|
23
|
+
|
|
24
|
+
# Timer
|
|
25
|
+
self.timer = QTimer(self)
|
|
26
|
+
self.timer.timeout.connect(self.update_frame)
|
|
27
|
+
self.timer.start(1000 // self.fps)
|
|
28
|
+
|
|
29
|
+
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
|
30
|
+
|
|
31
|
+
# ---------------------- RENDER ----------------------
|
|
32
|
+
|
|
33
|
+
def paintEvent(self, event):
|
|
34
|
+
self.screen.fill((50, 50, 100))
|
|
35
|
+
|
|
36
|
+
self.scene_manager.draw(self.screen)
|
|
37
|
+
|
|
38
|
+
raw = pygame.image.tostring(self.screen, "RGBA")
|
|
39
|
+
img = QImage(
|
|
40
|
+
raw,
|
|
41
|
+
self.width,
|
|
42
|
+
self.height,
|
|
43
|
+
QImage.Format.Format_RGBA8888
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
painter = QPainter(self)
|
|
47
|
+
painter.drawImage(0, 0, img)
|
|
48
|
+
|
|
49
|
+
def resizeEvent(self, event):
|
|
50
|
+
size = event.size()
|
|
51
|
+
self.width = size.width()
|
|
52
|
+
self.height = size.height()
|
|
53
|
+
self.screen = pygame.Surface((self.width, self.height))
|
|
54
|
+
|
|
55
|
+
# ---------------------- UPDATE ----------------------
|
|
56
|
+
|
|
57
|
+
def update_frame(self):
|
|
58
|
+
if not self.hasFocus():
|
|
59
|
+
self.keys_down.clear()
|
|
60
|
+
|
|
61
|
+
self.scene_manager.update()
|
|
62
|
+
self.update() # triggers paintEvent
|
|
63
|
+
|
|
64
|
+
# ---------------------- INPUT ----------------------
|
|
65
|
+
|
|
66
|
+
def keyPressEvent(self, event):
|
|
67
|
+
self.keys_down.add(event.key())
|
|
68
|
+
|
|
69
|
+
def keyReleaseEvent(self, event):
|
|
70
|
+
self.keys_down.discard(event.key())
|
VertexEngine/example.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from PyQt6.QtWidgets import QApplication
|
|
2
|
+
from engine import GameEngine
|
|
3
|
+
import pygame
|
|
4
|
+
from scenes import Scene
|
|
5
|
+
from assets import AssetManager
|
|
6
|
+
from audio import AudioManager
|
|
7
|
+
import sys
|
|
8
|
+
class MyScene(Scene):
|
|
9
|
+
def __init__(self, engine):
|
|
10
|
+
super().__init__(engine)
|
|
11
|
+
self.assets = AssetManager()
|
|
12
|
+
self.assets.load_image("ball", "ball.png")
|
|
13
|
+
self.x = 100
|
|
14
|
+
self.y = 100
|
|
15
|
+
self.speed = 5
|
|
16
|
+
self.width = engine.width
|
|
17
|
+
self.height = engine.height
|
|
18
|
+
|
|
19
|
+
def update(self):
|
|
20
|
+
# Process pygame events
|
|
21
|
+
for event in pygame.event.get():
|
|
22
|
+
if event.type == pygame.KEYDOWN:
|
|
23
|
+
if event.key == pygame.K_LEFT:
|
|
24
|
+
self.x -= self.speed
|
|
25
|
+
elif event.key == pygame.K_RIGHT:
|
|
26
|
+
self.x += self.speed
|
|
27
|
+
elif event.key == pygame.K_UP:
|
|
28
|
+
self.y -= self.speed
|
|
29
|
+
elif event.key == pygame.K_DOWN:
|
|
30
|
+
self.y += self.speed
|
|
31
|
+
|
|
32
|
+
# Clamp the ball inside the engine window
|
|
33
|
+
self.x = max(0, min(self.x, self.width - 50))
|
|
34
|
+
self.y = max(0, min(self.y, self.height - 50))
|
|
35
|
+
|
|
36
|
+
def draw(self, surface):
|
|
37
|
+
surface.fill((0, 0, 0)) # clear screen
|
|
38
|
+
ball = self.assets.get_image("ball")
|
|
39
|
+
if ball:
|
|
40
|
+
ball_scaled = pygame.transform.scale(ball, (55, 50))
|
|
41
|
+
surface.blit(ball_scaled, (self.x, self.y))
|
|
42
|
+
else:
|
|
43
|
+
pygame.draw.circle(surface, (255, 0, 0), (self.x, self.y), 25)
|
|
44
|
+
|
|
45
|
+
app = QApplication(sys.argv) # ✅ must be first
|
|
46
|
+
|
|
47
|
+
engine = GameEngine()
|
|
48
|
+
engine.resize(800, 600)
|
|
49
|
+
|
|
50
|
+
# Create scene and add to manager
|
|
51
|
+
my_scene = MyScene(engine)
|
|
52
|
+
engine.scene_manager.add_scene("main", my_scene)
|
|
53
|
+
engine.scene_manager.switch_to("main") # make it the active scene
|
|
54
|
+
|
|
55
|
+
# Show the engine
|
|
56
|
+
engine.show()
|
|
57
|
+
|
|
58
|
+
# Start the PyQt6 event loop
|
|
59
|
+
sys.exit(app.exec())
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import random
|
|
3
|
+
from PyQt6.QtWidgets import QApplication
|
|
4
|
+
from PyQt6.QtCore import Qt
|
|
5
|
+
|
|
6
|
+
from VertexEngine.engine import GameEngine
|
|
7
|
+
from VertexEngine.scenes import Scene
|
|
8
|
+
from VertexEngine import VertexScreen
|
|
9
|
+
from VertexEngine.InputSystem.KeyInputs import Input
|
|
10
|
+
|
|
11
|
+
# =====================
|
|
12
|
+
# CONFIG
|
|
13
|
+
# =====================
|
|
14
|
+
VIRTUAL_WIDTH = 800
|
|
15
|
+
VIRTUAL_HEIGHT = 450
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
#class ScreenScaler:
|
|
19
|
+
# def __init__(self, engine):
|
|
20
|
+
# self.engine = engine
|
|
21
|
+
# @property
|
|
22
|
+
# def scale(self):
|
|
23
|
+
# return min(
|
|
24
|
+
# self.engine.width / VIRTUAL_WIDTH,
|
|
25
|
+
# self.engine.height / VIRTUAL_HEIGHT,
|
|
26
|
+
# )
|
|
27
|
+
# @property
|
|
28
|
+
# def offset_x(self):
|
|
29
|
+
# return int((self.engine.width - VIRTUAL_WIDTH * self.scale) / 2)
|
|
30
|
+
# @property
|
|
31
|
+
# def offset_y(self):
|
|
32
|
+
# return int((self.engine.height - VIRTUAL_HEIGHT * self.scale) / 2)
|
|
33
|
+
# def rect(self, x, y, w, h):
|
|
34
|
+
# s = self.scale
|
|
35
|
+
# return (
|
|
36
|
+
# int(x * s) + self.offset_x,
|
|
37
|
+
# int(y * s) + self.offset_y,
|
|
38
|
+
# int(w * s),
|
|
39
|
+
# int(h * s),
|
|
40
|
+
# )
|
|
41
|
+
# def pos(self, x, y):
|
|
42
|
+
# s = self.scale
|
|
43
|
+
# return (
|
|
44
|
+
# int(x * s) + self.offset_x,
|
|
45
|
+
# int(y * s) + self.offset_y,
|
|
46
|
+
# )
|
|
47
|
+
class ScreenScaler:
|
|
48
|
+
def __init__(self, engine):
|
|
49
|
+
self.engine = engine
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def scale(self):
|
|
53
|
+
return min(
|
|
54
|
+
self.engine.width / VIRTUAL_WIDTH,
|
|
55
|
+
self.engine.height / VIRTUAL_HEIGHT,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# 👇 NO CENTERING
|
|
59
|
+
@property
|
|
60
|
+
def offset_x(self):
|
|
61
|
+
return 0
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def offset_y(self):
|
|
65
|
+
return 0
|
|
66
|
+
|
|
67
|
+
def rect(self, x, y, w, h):
|
|
68
|
+
s = self.scale
|
|
69
|
+
return (
|
|
70
|
+
int(x * s),
|
|
71
|
+
int(y * s),
|
|
72
|
+
int(w * s),
|
|
73
|
+
int(h * s),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def pos(self, x, y):
|
|
77
|
+
s = self.scale
|
|
78
|
+
return int(x * s), int(y * s)
|
|
79
|
+
# =====================
|
|
80
|
+
# GAME OBJECTS
|
|
81
|
+
# =====================
|
|
82
|
+
class Player:
|
|
83
|
+
def __init__(self):
|
|
84
|
+
self.x = 230
|
|
85
|
+
self.y = 300
|
|
86
|
+
self.w = 40
|
|
87
|
+
self.h = 40
|
|
88
|
+
self.vel_y = 0
|
|
89
|
+
self.on_ground = True
|
|
90
|
+
|
|
91
|
+
def update(self):
|
|
92
|
+
speed = 5
|
|
93
|
+
|
|
94
|
+
if Input.is_pressed("a") or Input.is_pressed("left"):
|
|
95
|
+
self.x -= speed
|
|
96
|
+
if Input.is_pressed("d") or Input.is_pressed("right"):
|
|
97
|
+
self.x += speed
|
|
98
|
+
|
|
99
|
+
if self.x > VIRTUAL_WIDTH:
|
|
100
|
+
self.x = 0
|
|
101
|
+
|
|
102
|
+
if self.x < 0:
|
|
103
|
+
self.x = VIRTUAL_WIDTH
|
|
104
|
+
|
|
105
|
+
if self.on_ground and Input.is_just_pressed("space"):
|
|
106
|
+
self.vel_y = -15
|
|
107
|
+
self.on_ground = False
|
|
108
|
+
|
|
109
|
+
self.vel_y += 1
|
|
110
|
+
self.y += self.vel_y
|
|
111
|
+
|
|
112
|
+
if self.y >= 300:
|
|
113
|
+
self.y = 300
|
|
114
|
+
self.vel_y = 0
|
|
115
|
+
self.on_ground = True
|
|
116
|
+
print(self.x)
|
|
117
|
+
|
|
118
|
+
class Block:
|
|
119
|
+
def __init__(self):
|
|
120
|
+
self.size = 40
|
|
121
|
+
self.x = random.randint(0, VIRTUAL_WIDTH - self.size)
|
|
122
|
+
self.y = -self.size
|
|
123
|
+
self.speed = random.randint(4, 8)
|
|
124
|
+
|
|
125
|
+
def update(self):
|
|
126
|
+
self.y += self.speed
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# =====================
|
|
130
|
+
# SCENE
|
|
131
|
+
# =====================
|
|
132
|
+
class MainScene(Scene):
|
|
133
|
+
def __init__(self, engine):
|
|
134
|
+
super().__init__(engine)
|
|
135
|
+
self.scaler = ScreenScaler(engine)
|
|
136
|
+
|
|
137
|
+
self.player = Player()
|
|
138
|
+
self.blocks = []
|
|
139
|
+
self.dead = False
|
|
140
|
+
|
|
141
|
+
def update(self):
|
|
142
|
+
if not self.dead:
|
|
143
|
+
self.player.update()
|
|
144
|
+
|
|
145
|
+
if random.random() < 0.03:
|
|
146
|
+
self.blocks.append(Block())
|
|
147
|
+
|
|
148
|
+
for b in self.blocks:
|
|
149
|
+
b.update()
|
|
150
|
+
if (
|
|
151
|
+
self.player.x < b.x + b.size and
|
|
152
|
+
self.player.x + self.player.w > b.x and
|
|
153
|
+
self.player.y < b.y + b.size and
|
|
154
|
+
self.player.y + self.player.h > b.y
|
|
155
|
+
):
|
|
156
|
+
self.dead = True
|
|
157
|
+
|
|
158
|
+
self.blocks = [b for b in self.blocks if b.y < VIRTUAL_HEIGHT]
|
|
159
|
+
|
|
160
|
+
Input.input_update()
|
|
161
|
+
|
|
162
|
+
def draw(self, surface):
|
|
163
|
+
s = self.scaler
|
|
164
|
+
|
|
165
|
+
# background
|
|
166
|
+
VertexScreen.Draw.rect(
|
|
167
|
+
VertexScreen.Draw,
|
|
168
|
+
surface,
|
|
169
|
+
(15, 15, 15),
|
|
170
|
+
s.rect(0, 0, VIRTUAL_WIDTH, VIRTUAL_HEIGHT),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# player
|
|
174
|
+
VertexScreen.Draw.rect(
|
|
175
|
+
VertexScreen.Draw,
|
|
176
|
+
surface,
|
|
177
|
+
(0, 255, 255),
|
|
178
|
+
s.rect(self.player.x, self.player.y, self.player.w, self.player.h),
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# blocks
|
|
182
|
+
for b in self.blocks:
|
|
183
|
+
VertexScreen.Draw.rect(
|
|
184
|
+
VertexScreen.Draw,
|
|
185
|
+
surface,
|
|
186
|
+
(255, 0, 0),
|
|
187
|
+
s.rect(b.x, b.y, b.size, b.size),
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# death text
|
|
191
|
+
if self.dead:
|
|
192
|
+
deathfont = VertexScreen.Font(None, 24)
|
|
193
|
+
deathfont.draw(
|
|
194
|
+
surface,
|
|
195
|
+
"DOGPILED",
|
|
196
|
+
s.pos(VIRTUAL_WIDTH // 2, VIRTUAL_HEIGHT // 2)
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# =====================
|
|
201
|
+
# ENGINE (Qt INPUT HERE)
|
|
202
|
+
# =====================
|
|
203
|
+
class DogpileEngine(GameEngine):
|
|
204
|
+
def __init__(self, **kwargs):
|
|
205
|
+
super().__init__(**kwargs)
|
|
206
|
+
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
|
207
|
+
|
|
208
|
+
def keyPressEvent(self, event):
|
|
209
|
+
Input.key_down(event.key())
|
|
210
|
+
|
|
211
|
+
def keyReleaseEvent(self, event):
|
|
212
|
+
Input.key_up(event.key())
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# =====================
|
|
216
|
+
# RUN
|
|
217
|
+
# =====================
|
|
218
|
+
if __name__ == "__main__":
|
|
219
|
+
app = QApplication(sys.argv)
|
|
220
|
+
|
|
221
|
+
engine = DogpileEngine(
|
|
222
|
+
fps=60,
|
|
223
|
+
width=1000,
|
|
224
|
+
height=800,
|
|
225
|
+
)
|
|
226
|
+
engine.setWindowTitle("Dogpile Dodger (VertexEngine)")
|
|
227
|
+
engine.setMinimumSize(VIRTUAL_WIDTH, VIRTUAL_HEIGHT)
|
|
228
|
+
engine.show()
|
|
229
|
+
|
|
230
|
+
scene = MainScene(engine)
|
|
231
|
+
engine.scene_manager.set_scene(scene)
|
|
232
|
+
|
|
233
|
+
sys.exit(app.exec())
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This is a simple single scene platformer, made in the Vertex Game Engine.
|
|
3
|
+
|
|
4
|
+
This teaches you:
|
|
5
|
+
Basics of Asset rendering
|
|
6
|
+
Scenes
|
|
7
|
+
Game Engine
|
|
8
|
+
rendering to a screen
|
|
9
|
+
handle input
|
|
10
|
+
dictionaries
|
|
11
|
+
physics
|
|
12
|
+
update and drawing
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from VertexEngine.engine import GameEngine
|
|
16
|
+
from VertexEngine.scenes import Scene
|
|
17
|
+
from VertexEngine import VertexScreen
|
|
18
|
+
from PyQt6.QtWidgets import QApplication
|
|
19
|
+
from PyQt6.QtCore import Qt
|
|
20
|
+
import sys
|
|
21
|
+
from VertexEngine.audio import AudioManager
|
|
22
|
+
import pygame
|
|
23
|
+
import os
|
|
24
|
+
# 🔽 WORLD SCALE
|
|
25
|
+
SCALE = 0.5
|
|
26
|
+
|
|
27
|
+
# 🔽 Physics (scaled)
|
|
28
|
+
GRAVITY = 1.2 * SCALE
|
|
29
|
+
JUMP_FORCE = -22 * SCALE
|
|
30
|
+
MOVE_SPEED = 8 * SCALE
|
|
31
|
+
GROUND_Y = 300 * SCALE
|
|
32
|
+
maindir = os.path.split(os.path.abspath(__file__))[0]
|
|
33
|
+
coin_img = pygame.image.load(maindir + "/data/coin.png")
|
|
34
|
+
coin_img = pygame.transform.scale(
|
|
35
|
+
coin_img,
|
|
36
|
+
(32 * SCALE, 32 * SCALE)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
class Main(Scene):
|
|
40
|
+
def __init__(self, engine):
|
|
41
|
+
super().__init__(engine)
|
|
42
|
+
self.world_offset_y = engine.height // 2
|
|
43
|
+
self.score = 0
|
|
44
|
+
self.coins = [
|
|
45
|
+
{"x": 200, "y": -35 * SCALE, "collected": False},
|
|
46
|
+
{"x": 50, "y": 80 * SCALE, "collected": False},
|
|
47
|
+
{"x": 380, "y": -300 * SCALE, "collected": False},
|
|
48
|
+
]
|
|
49
|
+
self.platforms = [
|
|
50
|
+
(200, 1 * SCALE, 200 * SCALE, 20 * SCALE),
|
|
51
|
+
(360, -200 * SCALE, 200 * SCALE, 20 * SCALE),
|
|
52
|
+
(0, 120 * SCALE, 220 * SCALE, 20 * SCALE),
|
|
53
|
+
(350, 200 * SCALE, 180 * SCALE, 20 * SCALE),
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
# 🔽 Player (scaled)
|
|
57
|
+
self.w = int(80 * SCALE)
|
|
58
|
+
self.h = int(80 * SCALE)
|
|
59
|
+
|
|
60
|
+
self.x = 0
|
|
61
|
+
self.y = 0
|
|
62
|
+
|
|
63
|
+
self.vx = 0
|
|
64
|
+
self.vy = 0
|
|
65
|
+
self.on_ground = False
|
|
66
|
+
|
|
67
|
+
self.keys = set()
|
|
68
|
+
|
|
69
|
+
# --- INPUT ---
|
|
70
|
+
def keyPressEvent(self, event):
|
|
71
|
+
self.keys.add(event.key())
|
|
72
|
+
|
|
73
|
+
def keyReleaseEvent(self, event):
|
|
74
|
+
self.keys.discard(event.key())
|
|
75
|
+
|
|
76
|
+
# --- UPDATE ---
|
|
77
|
+
def update(self):
|
|
78
|
+
self.vx = 0
|
|
79
|
+
|
|
80
|
+
if Qt.Key.Key_A in self.keys:
|
|
81
|
+
self.vx = -MOVE_SPEED
|
|
82
|
+
if Qt.Key.Key_D in self.keys:
|
|
83
|
+
self.vx = MOVE_SPEED
|
|
84
|
+
|
|
85
|
+
if Qt.Key.Key_Space in self.keys and self.on_ground:
|
|
86
|
+
self.vy = JUMP_FORCE
|
|
87
|
+
self.on_ground = False
|
|
88
|
+
|
|
89
|
+
# Gravity
|
|
90
|
+
self.vy += GRAVITY
|
|
91
|
+
|
|
92
|
+
# Apply movement
|
|
93
|
+
self.x += self.vx
|
|
94
|
+
self.y += self.vy
|
|
95
|
+
|
|
96
|
+
self.on_ground = False
|
|
97
|
+
|
|
98
|
+
for coin in self.coins:
|
|
99
|
+
if coin["collected"]:
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
cx = coin["x"]
|
|
103
|
+
cy = coin["y"]
|
|
104
|
+
cw = coin_img.get_width()
|
|
105
|
+
ch = coin_img.get_height()
|
|
106
|
+
|
|
107
|
+
if (
|
|
108
|
+
self.x < cx + cw and
|
|
109
|
+
self.x + self.w > cx and
|
|
110
|
+
self.y < cy + ch and
|
|
111
|
+
self.y + self.h > cy
|
|
112
|
+
):
|
|
113
|
+
coin["collected"] = True
|
|
114
|
+
self.score += 1
|
|
115
|
+
print("Coin collected! Score:", self.score)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# --- PLATFORM COLLISIONS ---
|
|
119
|
+
for px, py, pw, ph in self.platforms:
|
|
120
|
+
if (
|
|
121
|
+
self.vy >= 0 and
|
|
122
|
+
self.x + self.w > px and
|
|
123
|
+
self.x < px + pw and
|
|
124
|
+
self.y + self.h > py and
|
|
125
|
+
self.y + self.h < py + ph + self.vy + 1
|
|
126
|
+
):
|
|
127
|
+
self.y = py - self.h
|
|
128
|
+
self.vy = 0
|
|
129
|
+
self.on_ground = True
|
|
130
|
+
|
|
131
|
+
# --- GROUND COLLISION ---
|
|
132
|
+
if self.y + self.h >= GROUND_Y:
|
|
133
|
+
self.y = GROUND_Y - self.h
|
|
134
|
+
self.vy = 0
|
|
135
|
+
self.on_ground = True
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# --- DRAW ---
|
|
139
|
+
def draw(self, surface):
|
|
140
|
+
oy = self.world_offset_y # visual offset
|
|
141
|
+
|
|
142
|
+
# Background
|
|
143
|
+
VertexScreen.Draw.rect(
|
|
144
|
+
VertexScreen.Draw,
|
|
145
|
+
surface,
|
|
146
|
+
(30, 30, 30),
|
|
147
|
+
(0, 0, self.engine.width, self.engine.height)
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Ground (shifted DOWN)
|
|
151
|
+
VertexScreen.Draw.rect(
|
|
152
|
+
VertexScreen.Draw,
|
|
153
|
+
surface,
|
|
154
|
+
(50, 200, 50),
|
|
155
|
+
(-2000 * SCALE, GROUND_Y + oy, 4000 * SCALE, 200 * SCALE)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Player (shifted DOWN)
|
|
159
|
+
VertexScreen.Draw.rect(
|
|
160
|
+
VertexScreen.Draw,
|
|
161
|
+
surface,
|
|
162
|
+
(255, 100, 100),
|
|
163
|
+
(self.x, self.y + oy, self.w, self.h)
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Coins
|
|
167
|
+
for coin in self.coins:
|
|
168
|
+
if coin["collected"]:
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
surface.blit(
|
|
172
|
+
coin_img,
|
|
173
|
+
(coin["x"], coin["y"] + oy)
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Platforms
|
|
177
|
+
for px, py, pw, ph in self.platforms:
|
|
178
|
+
VertexScreen.Draw.rect(
|
|
179
|
+
VertexScreen.Draw,
|
|
180
|
+
surface,
|
|
181
|
+
(100, 180, 255),
|
|
182
|
+
(px, py + oy, pw, ph)
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if __name__ == "__main__":
|
|
186
|
+
app = QApplication(sys.argv)
|
|
187
|
+
|
|
188
|
+
engine = GameEngine(
|
|
189
|
+
fps=60,
|
|
190
|
+
width=1920,
|
|
191
|
+
height=1080,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
engine.setWindowTitle("Vertex Platformer (Scaled World)")
|
|
195
|
+
|
|
196
|
+
engine.show()
|
|
197
|
+
|
|
198
|
+
main = Main(engine)
|
|
199
|
+
engine.scene_manager.add_scene("main", main)
|
|
200
|
+
engine.scene_manager.switch_to("main")
|
|
201
|
+
|
|
202
|
+
engine.keyPressEvent = main.keyPressEvent
|
|
203
|
+
engine.keyReleaseEvent = main.keyReleaseEvent
|
|
204
|
+
|
|
205
|
+
app.exec()
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import random
|
|
3
|
+
import pygame
|
|
4
|
+
|
|
5
|
+
from PyQt6.QtWidgets import QApplication
|
|
6
|
+
from PyQt6.QtCore import Qt
|
|
7
|
+
|
|
8
|
+
from VertexEngine.engine import GameEngine
|
|
9
|
+
from VertexEngine.scenes import Scene
|
|
10
|
+
|
|
11
|
+
# =============================
|
|
12
|
+
# CONFIG
|
|
13
|
+
# =============================
|
|
14
|
+
WIDTH = 800
|
|
15
|
+
HEIGHT = 600
|
|
16
|
+
TILE = 30
|
|
17
|
+
FPS = 60
|
|
18
|
+
GRID_W = WIDTH // TILE
|
|
19
|
+
GRID_H = HEIGHT // TILE
|
|
20
|
+
|
|
21
|
+
# =============================
|
|
22
|
+
# SNAKE SCENE
|
|
23
|
+
# =============================
|
|
24
|
+
class SnakeScene(Scene):
|
|
25
|
+
def __init__(self, engine):
|
|
26
|
+
super().__init__(engine)
|
|
27
|
+
|
|
28
|
+
self.snake = [(10, 10)]
|
|
29
|
+
self.direction = (1, 0)
|
|
30
|
+
self.next_direction = self.direction
|
|
31
|
+
|
|
32
|
+
self.food = self.spawn_food()
|
|
33
|
+
self.dead = False
|
|
34
|
+
|
|
35
|
+
self.tick = 0
|
|
36
|
+
self.speed = 8 # lower = faster
|
|
37
|
+
|
|
38
|
+
def spawn_food(self):
|
|
39
|
+
return (
|
|
40
|
+
random.randrange(GRID_W),
|
|
41
|
+
random.randrange(GRID_H)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def set_direction(self, d):
|
|
45
|
+
# Prevent instant reverse
|
|
46
|
+
if (-d[0], -d[1]) != self.direction:
|
|
47
|
+
self.next_direction = d
|
|
48
|
+
|
|
49
|
+
def update(self):
|
|
50
|
+
if self.dead:
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
self.tick += 1
|
|
54
|
+
if self.tick < self.speed:
|
|
55
|
+
return
|
|
56
|
+
self.tick = 0
|
|
57
|
+
|
|
58
|
+
self.direction = self.next_direction
|
|
59
|
+
|
|
60
|
+
hx, hy = self.snake[0]
|
|
61
|
+
dx, dy = self.direction
|
|
62
|
+
new_head = (hx + dx, hy + dy)
|
|
63
|
+
|
|
64
|
+
# Death checks
|
|
65
|
+
if (
|
|
66
|
+
new_head in self.snake or
|
|
67
|
+
new_head[0] < 0 or new_head[1] < 0 or
|
|
68
|
+
new_head[0] >= WIDTH // TILE or
|
|
69
|
+
new_head[1] >= HEIGHT // TILE
|
|
70
|
+
):
|
|
71
|
+
print("💀 GAME OVER")
|
|
72
|
+
self.dead = True
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
self.snake.insert(0, new_head)
|
|
76
|
+
|
|
77
|
+
if new_head == self.food:
|
|
78
|
+
self.food = self.spawn_food()
|
|
79
|
+
else:
|
|
80
|
+
self.snake.pop()
|
|
81
|
+
|
|
82
|
+
def draw(self, screen):
|
|
83
|
+
screen.fill((0, 0, 0))
|
|
84
|
+
|
|
85
|
+
for x, y in self.snake:
|
|
86
|
+
pygame.draw.rect(
|
|
87
|
+
screen,
|
|
88
|
+
(0, 255, 0),
|
|
89
|
+
(x * TILE, y * TILE, TILE, TILE)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
fx, fy = self.food
|
|
93
|
+
pygame.draw.rect(
|
|
94
|
+
screen,
|
|
95
|
+
(255, 0, 0),
|
|
96
|
+
(fx * TILE, fy * TILE, TILE, TILE)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# =============================
|
|
100
|
+
# BOOTSTRAP (QT FIRST OR DIE)
|
|
101
|
+
# =============================
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
app = QApplication(sys.argv)
|
|
104
|
+
|
|
105
|
+
engine = GameEngine(
|
|
106
|
+
width=WIDTH,
|
|
107
|
+
height=HEIGHT,
|
|
108
|
+
fps=FPS
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
engine.setWindowTitle("Snake 🐍 — RC Survivor Edition")
|
|
112
|
+
engine.show()
|
|
113
|
+
|
|
114
|
+
scene = SnakeScene(engine)
|
|
115
|
+
engine.scene_manager.add_scene("main", scene)
|
|
116
|
+
engine.scene_manager.switch_to("main")
|
|
117
|
+
|
|
118
|
+
# =============================
|
|
119
|
+
# 🔑 QT INPUT — THIS IS THE FIX
|
|
120
|
+
# =============================
|
|
121
|
+
def engine_key_press(event):
|
|
122
|
+
key = event.key()
|
|
123
|
+
|
|
124
|
+
if key == Qt.Key.Key_Up:
|
|
125
|
+
scene.set_direction((0, -1))
|
|
126
|
+
elif key == Qt.Key.Key_Down:
|
|
127
|
+
scene.set_direction((0, 1))
|
|
128
|
+
elif key == Qt.Key.Key_Left:
|
|
129
|
+
scene.set_direction((-1, 0))
|
|
130
|
+
elif key == Qt.Key.Key_Right:
|
|
131
|
+
scene.set_direction((1, 0))
|
|
132
|
+
|
|
133
|
+
engine.keyPressEvent = engine_key_press
|
|
134
|
+
|
|
135
|
+
app.exec()
|