Skip to content

Commit b0687f3

Browse files
committed
Add/update camera mechanism, Add collision detection
1 parent 9441e83 commit b0687f3

12 files changed

+632
-618
lines changed

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ disallow_any_explicit = false
7878
# explicit type parameters.
7979
disallow_any_generics = true
8080
# Disallows subclassing a value of type Any.
81-
disallow_subclassing_any = true
81+
disallow_subclassing_any = false
8282

8383
# Untyped definitions and calls
8484
# Disallows calling functions without type annotations

pysurvive/game/core.py

+54-72
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,74 @@
11
#!/usr/bin/env python
22
# coding=utf-8
3-
import pygame as pg
4-
5-
from pysurvive.config import SCREEN_RECT
6-
73

8-
class Singleton(type):
9-
_instances = {}
4+
import pygame as pg
105

11-
def __call__(cls, *args, **kwargs):
12-
if cls not in cls._instances:
13-
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
6+
from pysurvive.config import DEBUG_SPRITE, RED
147

15-
return cls._instances[cls]
168

9+
class CameraBoxBorder:
10+
left: int = 300
11+
right: int = 300
12+
top: int = 300
13+
bottom: int = 300
1714

18-
class Screen(pg.rect.Rect, metaclass=Singleton):
1915

20-
"""
21-
Class that represent the screen and is used to detect
22-
wheather objects are visible on the screen.
23-
"""
16+
class Camera(pg.sprite.Group):
17+
def __init__(self) -> None:
18+
super().__init__()
19+
self.display_surface = pg.display.get_surface()
20+
self.width = self.display_surface.get_size()[0]
21+
self.height = self.display_surface.get_size()[1]
22+
self.screenx = self.width // 2
23+
self.screeny = self.height // 2
24+
self.offset = pg.math.Vector2()
2425

25-
def __init__(self, *args, **kwargs) -> None:
26-
super().__init__(*args, **kwargs)
27-
# Calculate a slightly larger rectangle of the screen.
28-
# Otherwise, there will be white flickering at the edges
29-
# when drawing the sprites on the screen.
30-
rough_w = self.width * 0.01
31-
rough_h = self.height * 0.01
32-
self.rect_rough = pg.Rect(
33-
self.x - rough_w,
34-
self.y - rough_w,
35-
self.width + rough_w * 2,
36-
self.height + rough_h * 2,
26+
self.camera_box = pg.Rect(
27+
CameraBoxBorder.left,
28+
CameraBoxBorder.top,
29+
self.width - (CameraBoxBorder.left + CameraBoxBorder.right),
30+
self.height - (CameraBoxBorder.top + CameraBoxBorder.bottom),
3731
)
3832

39-
def __str__(self) -> str:
40-
return f"Screen (size={self.width}x{self.height})"
41-
42-
@classmethod
43-
def delete(cls) -> None:
44-
"""Unset singleton."""
45-
cls._instances = {}
33+
def __repr__(self) -> str:
34+
return f"Camera(x={self.x} x {self.x})"
4635

4736
@property
48-
def rect(self) -> pg.rect.Rect:
49-
"""
50-
The function spritecollide expects a property rect.
51-
Returns the rough rect of the screen here.
52-
"""
53-
return self.rect_rough
37+
def x(self) -> float:
38+
return self.offset.x
5439

40+
@property
41+
def y(self) -> float:
42+
return self.offset.y
5543

56-
class Camera(metaclass=Singleton):
57-
58-
"""
59-
Class that represents the absolute position of the
60-
camera (player) in the game world.
61-
"""
62-
63-
def __init__(self, x: int = 0, y: int = 0) -> None:
64-
self.screen = Screen(SCREEN_RECT)
65-
self.x = x
66-
self.y = y
44+
@x.setter
45+
def x(self, _x: float) -> None:
46+
self.offset.x = _x
6747

68-
def __str__(self) -> str:
69-
return f"Camera (x={self.x}, y={self.y})"
48+
@y.setter
49+
def y(self, _y: float) -> None:
50+
self.offset.y = _y
7051

71-
@classmethod
72-
def delete(cls) -> None:
73-
"""Unset singleton."""
74-
cls._instances = {}
52+
@property
53+
def rect(self) -> pg.FRect:
54+
"""Returns the camera / screen rect."""
55+
return pg.FRect(self.x, self.y, self.width, self.height)
7556

7657
@property
77-
def position(self) -> tuple[int, int]:
78-
"""Returns camera position in a tuple."""
79-
return (self.x, self.y)
80-
81-
def move(self, delta: tuple[int, int]) -> None:
82-
"""Move the camera by delta x, y."""
83-
self.x = round(self.x - delta[0])
84-
self.y = round(self.y - delta[1])
85-
86-
def get_rel_position(self, x: int, y: int) -> tuple[int, int]:
87-
"""Returns the relative position of a coordinate in relation
88-
to the global camera position (center of the screen)."""
89-
return (
90-
round(x - self.x + self.screen.centerx),
91-
round(y - self.y + self.screen.centery),
58+
def near_area_rect(self) -> pg.FRect:
59+
return pg.FRect(
60+
self.x + self.screenx - 75, self.y + self.screeny - 75, 150, 150
9261
)
62+
63+
def update(self, target) -> None:
64+
if target.x < self.camera_box.left:
65+
self.camera_box.left = target.x
66+
if target.x > self.camera_box.right:
67+
self.camera_box.right = target.x
68+
if target.y < self.camera_box.top:
69+
self.camera_box.top = target.y
70+
if target.y > self.camera_box.bottom:
71+
self.camera_box.bottom = target.y
72+
73+
self.x = self.camera_box.left - CameraBoxBorder.left
74+
self.y = self.camera_box.top - CameraBoxBorder.top

pysurvive/game/loop.py

+29-94
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/usr/bin/env python
22
# coding=utf-8
3+
import time
4+
35
import pygame as pg
46
from pygame.locals import (
57
K_ESCAPE,
@@ -10,17 +12,17 @@
1012
QUIT,
1113
)
1214

13-
from pysurvive.config import FPS, GRAY_LIGHT2, MAP_DIR, RED_LIGHT, SCREEN_RECT
14-
from pysurvive.game.core import Camera, Screen
15+
from pysurvive.config import FPS, GRAY_LIGHT2, MAP_DIR, SCREEN_RECT
16+
from pysurvive.game.core import Camera
1517
from pysurvive.logger import Logger
1618
from pysurvive.map.level import Level
17-
from pysurvive.player import PlayerGroup
19+
from pysurvive.player.player import PlayerGroup
20+
from pysurvive.player.viewpoint import Viewpoint
1821

1922
logger = Logger()
2023

2124

2225
class Game:
23-
2426
running = True
2527

2628
def __init__(self) -> None:
@@ -30,11 +32,10 @@ def __init__(self) -> None:
3032
self.clock = pg.time.Clock()
3133
self.fps_font = pg.font.SysFont("Arial", 14)
3234

33-
# Sprite that represent the screen.
34-
# Used to determine whether elements are in the viewing area.
35-
self.screen = Screen(SCREEN_RECT)
36-
# Set the height and width of the screen.
37-
self.window_surface = pg.display.set_mode(self.screen.size)
35+
# Set the height and width of the camera/screen.
36+
self.window_surface = pg.display.set_mode(
37+
(SCREEN_RECT.width, SCREEN_RECT.height)
38+
)
3839
# Set the window title.
3940
pg.display.set_caption("pysurvive")
4041
pg.transform.set_smoothscale_backend("SSE")
@@ -45,125 +46,59 @@ def __init__(self) -> None:
4546
[QUIT, KEYDOWN, K_ESCAPE, MOUSEBUTTONDOWN, MOUSEBUTTONUP, MOUSEMOTION]
4647
)
4748

48-
# Absolute (start) position of the player (camera) in game world.
49-
self.camera = Camera(150, 600)
50-
51-
# Prepare the shadow surface / screen.
52-
# self.screen_shadow = pg.Surface(self.screen.size)
53-
# self.screen_shadow = self.screen_shadow.convert()
54-
# self.screen_shadow.set_alpha(240)
55-
# self.screen_shadow.set_colorkey(COLORKEY)
56-
57-
#
58-
# Prepare game objects
59-
#
60-
61-
# Initialize the navmesh based on the map.
62-
# self.navmesh = NavMesh(self)
63-
64-
# self.enemy_sprites = pg.sprite.RenderPlain(
65-
# Enemy(self, 100, 100),
66-
# )
67-
# A sprite group that contains all close enemy sprites (render only).
68-
# self.enemy_render_sprites = pg.sprite.RenderPlain()
69-
# A sprite group that contains all close sprites (collision only).
70-
# self.collide_sprites = pg.sprite.RenderPlain()
71-
72-
# Get all unique points (corners) of block segments.
73-
# Prevent duplication of x, y coordinates.
74-
# self.unique_block_points = []
75-
# for block in self.block_sprites.sprites():
76-
# for block_point in block.get_points():
77-
# point = (block_point[0], block_point[1])
78-
# if point not in self.unique_block_points:
79-
# self.unique_block_points.append(point)
80-
81-
# Map
49+
self.camera = Camera()
50+
self.interface = pg.sprite.Group()
51+
8252
self.level = Level(f"{MAP_DIR}/map.json")
83-
# Player
84-
self.player_sprites = PlayerGroup(self.level)
53+
self.viewpoint = Viewpoint(self.interface)
54+
self.player_sprites = PlayerGroup(
55+
camera=self.camera,
56+
viewpoint=self.viewpoint,
57+
)
8558

8659
def start(self) -> None:
8760
"""
8861
This function is called when the program starts. It initializes
8962
everything it needs, then runs in a loop until the function returns.
9063
"""
9164

92-
while self.running:
65+
prev_time = time.time()
9366

67+
while self.running:
9468
# The number of milliseconds that passed between the
9569
# previous two calls to Clock.tick().
96-
dt = self.clock.get_time()
70+
dt = time.time() - prev_time
71+
prev_time = time.time()
9772

98-
# @todo: Get keyboard inputs by player object.
9973
for event in pg.event.get():
10074
if event.type == QUIT:
10175
self.running = False
10276
elif event.type == KEYDOWN and event.key == K_ESCAPE:
10377
self.running = False
104-
# elif event.type == MOUSEBUTTONDOWN:
105-
# if event.button == 1:
106-
# self.player_sprites.player.shot()
107-
# elif event.button == 3:
108-
# self.player_sprites.player.reload()
10978
elif event.type == MOUSEBUTTONUP:
11079
pass
11180

112-
keystate = pg.key.get_pressed()
113-
direction_x = keystate[pg.K_d] - keystate[pg.K_a]
114-
direction_y = keystate[pg.K_s] - keystate[pg.K_w]
115-
116-
# Fill the window surface with the default background color.
81+
# Default background color.
11782
self.window_surface.fill(GRAY_LIGHT2)
11883

11984
#
12085
# Updating
12186
#
12287

123-
self.level.update()
124-
self.player_sprites.update(dt, (direction_x, direction_y))
88+
self.level.update(self.camera)
89+
self.player_sprites.update(dt, self.level)
90+
self.interface.update()
12591

12692
#
12793
# Drawing
12894
#
12995

130-
self.level.draw(self.window_surface)
131-
132-
# if FLASHLIGHT_ENABLE:
133-
# # Currently, all vertices within a virtual screen of
134-
# # 3x width and 3x height of the screen are used. Later
135-
# # when the visibility is limited, this can be further reduced.
136-
# self.screen_shadow.fill(BLACK)
137-
# self.player_sprites.light.draw(self.screen_shadow)
138-
# self.window_surface.blit(self.screen_shadow, (0, 0))
139-
140-
self.player_sprites.draw(self.window_surface)
141-
142-
# Debugging
143-
# Draw navmesh
144-
# for tri in self.navmesh.mesh:
145-
# triangle = [(p[0] - self.game_x, p[1] - self.game_y)
146-
# for p in tri.triangle]
147-
# pg.draw.polygon(self.window_surface, (255, 0, 0), triangle, 1)
148-
# for node in tri.nodes:
149-
# pg.draw.circle(self.window_surface, (0, 255, 0),
150-
# (node.position[0] - self.game_x,
151-
# node.position[1] - self.game_y), 2)
152-
153-
# path = self.enemy_sprites.sprites()[0].path
154-
# if path:
155-
# path = [(p[0] - self.game_x, p[1] - self.game_y) for p in path]
156-
# pg.draw.lines(self.window_surface, (0, 0, 255), False, path)
157-
158-
self.window_surface.blit(self.update_fps(), (5, 5))
96+
self.level.draw(self.window_surface, self.camera)
97+
self.player_sprites.draw(self.window_surface, self.camera)
98+
self.interface.draw(self.window_surface)
15999

160100
# Go ahead and update the window surface with what we've drawn.
161101
# This MUST happen after all the other drawing commands.
162102
pg.display.flip()
163103
# This limits the while loop to a max of FPS times per second.
164104
self.clock.tick(FPS)
165-
166-
def update_fps(self):
167-
fps = str(int(self.clock.get_fps()))
168-
fps_text = self.fps_font.render(fps, 1, RED_LIGHT)
169-
return fps_text

0 commit comments

Comments
 (0)