tileforge 0.1.1__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.
- tileforge-0.1.1/LICENSE +18 -0
- tileforge-0.1.1/PKG-INFO +108 -0
- tileforge-0.1.1/README.md +68 -0
- tileforge-0.1.1/pyproject.toml +35 -0
- tileforge-0.1.1/setup.cfg +4 -0
- tileforge-0.1.1/src/tileforge/__init__.py +9 -0
- tileforge-0.1.1/src/tileforge/lib.py +4 -0
- tileforge-0.1.1/src/tileforge/map.py +61 -0
- tileforge-0.1.1/src/tileforge/renderer.py +47 -0
- tileforge-0.1.1/src/tileforge/tileset.py +87 -0
- tileforge-0.1.1/src/tileforge.egg-info/PKG-INFO +108 -0
- tileforge-0.1.1/src/tileforge.egg-info/SOURCES.txt +14 -0
- tileforge-0.1.1/src/tileforge.egg-info/dependency_links.txt +1 -0
- tileforge-0.1.1/src/tileforge.egg-info/requires.txt +1 -0
- tileforge-0.1.1/src/tileforge.egg-info/top_level.txt +1 -0
- tileforge-0.1.1/tests/test_tileset.py +118 -0
tileforge-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026-present Joshua Hegedus <josh.hegedus@outlook.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
6
|
+
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
|
9
|
+
following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
12
|
+
portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
15
|
+
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
|
16
|
+
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
18
|
+
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
tileforge-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tileforge
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A pygame rendering engine for top-down tile sets.
|
|
5
|
+
Author-email: Joshua Hegedus <josh.hegedus@outlook.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026-present Joshua Hegedus <josh.hegedus@outlook.com>
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
11
|
+
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
|
12
|
+
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
|
14
|
+
following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
17
|
+
portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
20
|
+
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
|
21
|
+
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
22
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
23
|
+
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
24
|
+
|
|
25
|
+
Project-URL: Homepage, https://github.com/lsc-jh/tileforge
|
|
26
|
+
Project-URL: Repository, https://github.com/lsc-jh/tileforge
|
|
27
|
+
Keywords: rendering,engine,tile set,top-down
|
|
28
|
+
Classifier: Development Status :: 4 - Beta
|
|
29
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Requires-Python: >=3.10
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
License-File: LICENSE
|
|
38
|
+
Requires-Dist: pygame-ce
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# Tile Forge
|
|
42
|
+
|
|
43
|
+
This is a package for rendering top-down maps from a provided tile set image.
|
|
44
|
+
|
|
45
|
+
## Class Diagram
|
|
46
|
+
|
|
47
|
+
```mermaid
|
|
48
|
+
---
|
|
49
|
+
title: Class Relations
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
classDiagram
|
|
53
|
+
Renderer *-- Tileset
|
|
54
|
+
Renderer *-- Map
|
|
55
|
+
class Tileset {
|
|
56
|
+
+ all_properties: dict[int, set[int]]
|
|
57
|
+
+ tile_size: int
|
|
58
|
+
+ path: str
|
|
59
|
+
+ load()
|
|
60
|
+
+ get_scaled_tiles(scale: int)
|
|
61
|
+
+ has_property(tile_index: int, property_id: int): bool
|
|
62
|
+
+ set_property(tile_index: int, property_index: int): void
|
|
63
|
+
+ remove_property(tile_index: int, property_index: int): void
|
|
64
|
+
+ toggle_property(tile_index: int, property_index: int): void
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
class Map {
|
|
68
|
+
+ width: int
|
|
69
|
+
+ height: int
|
|
70
|
+
+ layers: list[Layer]
|
|
71
|
+
+ set_layers(layers: list[Layer])
|
|
72
|
+
+ set_layer_count(count: int)
|
|
73
|
+
+ add_layer(layer: Layer)
|
|
74
|
+
+ cell_has_property(tileset: Tileset, pos: tuple[int, int], property_id: int): bool
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
class Renderer {
|
|
78
|
+
- __map: Map
|
|
79
|
+
- __tileset: Tileset
|
|
80
|
+
+ render_tile_size: int
|
|
81
|
+
+ tiles: list[Surface]
|
|
82
|
+
+ set_render_scale(scale: int)
|
|
83
|
+
+ render(surface: Surface, offset: [int, int], callback): void
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Usage
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
import pygame
|
|
91
|
+
from tileforge import Tileset, Map, Renderer
|
|
92
|
+
|
|
93
|
+
pygame.init()
|
|
94
|
+
|
|
95
|
+
# You need a pygame surface to render the map, so make sure to initialize pygame first
|
|
96
|
+
surface = pygame.Surface((640, 480)) # Create a surface to render on
|
|
97
|
+
|
|
98
|
+
# Load the tileset and map
|
|
99
|
+
tileset = Tileset("path/to/tileset.png", 32)
|
|
100
|
+
tileset.load()
|
|
101
|
+
|
|
102
|
+
# Create a map with the loaded tileset
|
|
103
|
+
map = Map(10, 10) # 10x10
|
|
104
|
+
|
|
105
|
+
# Create a renderer and render the map
|
|
106
|
+
renderer = Renderer(tileset, map)
|
|
107
|
+
renderer.render(surface)
|
|
108
|
+
```
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Tile Forge
|
|
2
|
+
|
|
3
|
+
This is a package for rendering top-down maps from a provided tile set image.
|
|
4
|
+
|
|
5
|
+
## Class Diagram
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
---
|
|
9
|
+
title: Class Relations
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
classDiagram
|
|
13
|
+
Renderer *-- Tileset
|
|
14
|
+
Renderer *-- Map
|
|
15
|
+
class Tileset {
|
|
16
|
+
+ all_properties: dict[int, set[int]]
|
|
17
|
+
+ tile_size: int
|
|
18
|
+
+ path: str
|
|
19
|
+
+ load()
|
|
20
|
+
+ get_scaled_tiles(scale: int)
|
|
21
|
+
+ has_property(tile_index: int, property_id: int): bool
|
|
22
|
+
+ set_property(tile_index: int, property_index: int): void
|
|
23
|
+
+ remove_property(tile_index: int, property_index: int): void
|
|
24
|
+
+ toggle_property(tile_index: int, property_index: int): void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class Map {
|
|
28
|
+
+ width: int
|
|
29
|
+
+ height: int
|
|
30
|
+
+ layers: list[Layer]
|
|
31
|
+
+ set_layers(layers: list[Layer])
|
|
32
|
+
+ set_layer_count(count: int)
|
|
33
|
+
+ add_layer(layer: Layer)
|
|
34
|
+
+ cell_has_property(tileset: Tileset, pos: tuple[int, int], property_id: int): bool
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class Renderer {
|
|
38
|
+
- __map: Map
|
|
39
|
+
- __tileset: Tileset
|
|
40
|
+
+ render_tile_size: int
|
|
41
|
+
+ tiles: list[Surface]
|
|
42
|
+
+ set_render_scale(scale: int)
|
|
43
|
+
+ render(surface: Surface, offset: [int, int], callback): void
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import pygame
|
|
51
|
+
from tileforge import Tileset, Map, Renderer
|
|
52
|
+
|
|
53
|
+
pygame.init()
|
|
54
|
+
|
|
55
|
+
# You need a pygame surface to render the map, so make sure to initialize pygame first
|
|
56
|
+
surface = pygame.Surface((640, 480)) # Create a surface to render on
|
|
57
|
+
|
|
58
|
+
# Load the tileset and map
|
|
59
|
+
tileset = Tileset("path/to/tileset.png", 32)
|
|
60
|
+
tileset.load()
|
|
61
|
+
|
|
62
|
+
# Create a map with the loaded tileset
|
|
63
|
+
map = Map(10, 10) # 10x10
|
|
64
|
+
|
|
65
|
+
# Create a renderer and render the map
|
|
66
|
+
renderer = Renderer(tileset, map)
|
|
67
|
+
renderer.render(surface)
|
|
68
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tileforge"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "A pygame rendering engine for top-down tile sets."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = { file = "LICENSE" }
|
|
8
|
+
keywords = ["rendering", "engine", "tile set", "top-down"]
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Joshua Hegedus", email = "josh.hegedus@outlook.com" },
|
|
11
|
+
]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Programming Language :: Python :: 3.10",
|
|
15
|
+
"Programming Language :: Python :: 3.11",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Programming Language :: Python :: 3.13",
|
|
18
|
+
"Programming Language :: Python :: 3.14",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
dependencies = [
|
|
23
|
+
"pygame-ce"
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/lsc-jh/tileforge"
|
|
28
|
+
Repository = "https://github.com/lsc-jh/tileforge"
|
|
29
|
+
|
|
30
|
+
[build-system]
|
|
31
|
+
requires = ["setuptools>=61.0"]
|
|
32
|
+
build-backend = "setuptools.build_meta"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["src"]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from .lib import Layer
|
|
2
|
+
from .tileset import Tileset
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Map:
|
|
6
|
+
def __init__(self, width: int, height: int):
|
|
7
|
+
self.__layer_count = 0
|
|
8
|
+
self.__layers: list[Layer] = []
|
|
9
|
+
self.__width = width
|
|
10
|
+
self.__height = height
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def width(self) -> int:
|
|
14
|
+
return self.__width
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def height(self) -> int:
|
|
18
|
+
return self.__height
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def layers(self) -> list[Layer]:
|
|
22
|
+
return self.__layers
|
|
23
|
+
|
|
24
|
+
def set_layer_count(self, count: int) -> None:
|
|
25
|
+
if count < self.__layer_count:
|
|
26
|
+
self.__layers = self.__layers[:count]
|
|
27
|
+
else:
|
|
28
|
+
for _ in range(count - self.__layer_count):
|
|
29
|
+
self.__layers.append([[(0, 0) for _ in range(self.__width)] for _ in range(self.__height)])
|
|
30
|
+
|
|
31
|
+
self.__layer_count = count
|
|
32
|
+
|
|
33
|
+
def set_layers(self, layers: list[Layer]) -> None:
|
|
34
|
+
self.__layers = layers
|
|
35
|
+
self.__layer_count = len(layers)
|
|
36
|
+
|
|
37
|
+
def add_layer(self) -> None:
|
|
38
|
+
self.set_layer_count(self.__layer_count + 1)
|
|
39
|
+
|
|
40
|
+
def cell_has_property(self, tileset: Tileset, pos: tuple[int, int], property_id: int) -> bool:
|
|
41
|
+
"""Check if any tile in the cell at (x, y) across all layers has the specified property."""
|
|
42
|
+
x, y = pos
|
|
43
|
+
for layer in self.__layers:
|
|
44
|
+
if y >= len(layer) or x >= len(layer[y]):
|
|
45
|
+
continue
|
|
46
|
+
index, _ = layer[y][x]
|
|
47
|
+
if tileset.has_property(index, property_id):
|
|
48
|
+
return True
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
def __getitem__(self, item: tuple[int, int, int]) -> tuple[int, int]:
|
|
52
|
+
layer, x, y = item
|
|
53
|
+
if layer >= self.__layer_count or x >= self.__width or y >= self.__height:
|
|
54
|
+
raise IndexError("Layer, x, or y index out of bounds")
|
|
55
|
+
return self.__layers[layer][y][x]
|
|
56
|
+
|
|
57
|
+
def __setitem__(self, key: tuple[int, int, int], value: tuple[int, int]) -> None:
|
|
58
|
+
layer, x, y = key
|
|
59
|
+
if layer >= self.__layer_count or x >= self.__width or y >= self.__height:
|
|
60
|
+
raise IndexError("Layer, x, or y index out of bounds")
|
|
61
|
+
self.__layers[layer][y][x] = value
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from .lib import DrawCallback
|
|
2
|
+
from .tileset import Tileset
|
|
3
|
+
from .map import Map
|
|
4
|
+
import pygame
|
|
5
|
+
from pygame import Surface
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Renderer:
|
|
9
|
+
def __init__(self, tileset: Tileset, map_layout: Map, render_scale: int = 1):
|
|
10
|
+
self.__render_scale = render_scale
|
|
11
|
+
self.__tileset = tileset
|
|
12
|
+
self.__tiles = self.__tileset.get_scaled_tiles(render_scale)
|
|
13
|
+
self.__map = map_layout
|
|
14
|
+
|
|
15
|
+
def set_render_scale(self, new_scale: int):
|
|
16
|
+
self.__render_scale = new_scale
|
|
17
|
+
self.__tiles = self.__tileset.get_scaled_tiles(new_scale)
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def tiles(self) -> list[Surface]:
|
|
21
|
+
return self.__tiles
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def render_tile_size(self) -> int:
|
|
25
|
+
return self.__tileset.tile_size * self.__render_scale
|
|
26
|
+
|
|
27
|
+
def render(self, surface: Surface, offset: tuple[int, int] = (0, 0), callback: DrawCallback | None = None) -> None:
|
|
28
|
+
offset_x, offset_y = offset
|
|
29
|
+
for y in range(self.__map.height):
|
|
30
|
+
for x in range(self.__map.width):
|
|
31
|
+
draw_x = x * self.render_tile_size + offset_x
|
|
32
|
+
draw_y = y * self.render_tile_size + offset_y
|
|
33
|
+
|
|
34
|
+
for layer in self.__map.layers:
|
|
35
|
+
if y >= len(layer) or x >= len(layer[y]):
|
|
36
|
+
continue
|
|
37
|
+
index, rotation = layer[y][x]
|
|
38
|
+
if index >= len(self.__tiles):
|
|
39
|
+
continue
|
|
40
|
+
tile = pygame.transform.rotate(self.__tiles[index], -90 * rotation)
|
|
41
|
+
surface.blit(tile, (draw_x, draw_y))
|
|
42
|
+
|
|
43
|
+
if callback:
|
|
44
|
+
try:
|
|
45
|
+
callback(x, y, draw_x, draw_y)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
print(f"Error in callback for tile ({x}, {y}): {e}")
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import pygame
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Tileset:
|
|
5
|
+
def __init__(self, path: str, tile_size: int):
|
|
6
|
+
self.__path = path
|
|
7
|
+
self.__tile_size = tile_size
|
|
8
|
+
self.tiles: list[pygame.Surface] = []
|
|
9
|
+
self.__tile_properties: dict[int, set[int]] = {}
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def tile_size(self) -> int:
|
|
13
|
+
return self.__tile_size
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def path(self) -> str:
|
|
17
|
+
return self.__path
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def all_properties(self) -> dict[int, set[int]]:
|
|
21
|
+
return self.__tile_properties
|
|
22
|
+
|
|
23
|
+
def __load_tileset(self):
|
|
24
|
+
image = pygame.image.load(self.__path).convert_alpha()
|
|
25
|
+
tiles = []
|
|
26
|
+
w, h = image.get_size()
|
|
27
|
+
|
|
28
|
+
for y in range(0, h - self.__tile_size + 1, self.__tile_size):
|
|
29
|
+
for x in range(0, w - self.__tile_size + 1, self.__tile_size):
|
|
30
|
+
tile = image.subsurface((x, y, self.__tile_size, self.__tile_size))
|
|
31
|
+
tiles.append(tile)
|
|
32
|
+
|
|
33
|
+
empty_tile = pygame.Surface((self.__tile_size, self.__tile_size), pygame.SRCALPHA)
|
|
34
|
+
tiles.insert(0, empty_tile)
|
|
35
|
+
|
|
36
|
+
return tiles
|
|
37
|
+
|
|
38
|
+
def load(self):
|
|
39
|
+
self.tiles = self.__load_tileset()
|
|
40
|
+
|
|
41
|
+
def get_scaled_tiles(self, scale: int) -> list[pygame.Surface]:
|
|
42
|
+
return [pygame.transform.scale(tile, (self.__tile_size * scale, self.__tile_size * scale)) for tile in
|
|
43
|
+
self.tiles]
|
|
44
|
+
|
|
45
|
+
def has_property(self, tile_index: int, property_id: int) -> bool:
|
|
46
|
+
return tile_index in self.__tile_properties.get(property_id, set())
|
|
47
|
+
|
|
48
|
+
def serialize_properties(self):
|
|
49
|
+
return {str(index): list(props) for index, props in self.__tile_properties.items()}
|
|
50
|
+
|
|
51
|
+
def deserialize_properties(self, properties: dict[str, list[int]]):
|
|
52
|
+
self.__tile_properties = {int(index): set(props) for index, props in properties.items()}
|
|
53
|
+
|
|
54
|
+
def set_properties(self, properties: dict[int, set[int]]):
|
|
55
|
+
self.__tile_properties = properties
|
|
56
|
+
|
|
57
|
+
def set_property(self, tile_index: int, property_id: int):
|
|
58
|
+
if property_id not in self.__tile_properties:
|
|
59
|
+
self.__tile_properties[property_id] = set()
|
|
60
|
+
self.__tile_properties[property_id].add(tile_index)
|
|
61
|
+
|
|
62
|
+
def remove_property(self, tile_index: int, property_id: int):
|
|
63
|
+
if property_id in self.__tile_properties:
|
|
64
|
+
self.__tile_properties[property_id].discard(tile_index)
|
|
65
|
+
if not self.__tile_properties[property_id]:
|
|
66
|
+
del self.__tile_properties[property_id]
|
|
67
|
+
|
|
68
|
+
def toggle_property(self, tile_index: int, property_id: int):
|
|
69
|
+
if property_id not in self.__tile_properties:
|
|
70
|
+
self.__tile_properties[property_id] = set()
|
|
71
|
+
if tile_index in self.__tile_properties[property_id]:
|
|
72
|
+
self.__tile_properties[property_id].remove(tile_index)
|
|
73
|
+
if not self.__tile_properties[property_id]:
|
|
74
|
+
del self.__tile_properties[property_id]
|
|
75
|
+
else:
|
|
76
|
+
self.__tile_properties[property_id].add(tile_index)
|
|
77
|
+
|
|
78
|
+
def get_properties(self, property_id: int) -> set[int]:
|
|
79
|
+
return self.__tile_properties.get(property_id, set())
|
|
80
|
+
|
|
81
|
+
def set_tile_size(self, new_size: int):
|
|
82
|
+
self.__tile_size = new_size
|
|
83
|
+
self.tiles = self.__load_tileset()
|
|
84
|
+
|
|
85
|
+
def set_path(self, new_path: str):
|
|
86
|
+
self.__path = new_path
|
|
87
|
+
self.tiles = self.__load_tileset()
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tileforge
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A pygame rendering engine for top-down tile sets.
|
|
5
|
+
Author-email: Joshua Hegedus <josh.hegedus@outlook.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026-present Joshua Hegedus <josh.hegedus@outlook.com>
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
11
|
+
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
|
12
|
+
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
|
14
|
+
following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
17
|
+
portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
20
|
+
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
|
21
|
+
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
22
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
23
|
+
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
24
|
+
|
|
25
|
+
Project-URL: Homepage, https://github.com/lsc-jh/tileforge
|
|
26
|
+
Project-URL: Repository, https://github.com/lsc-jh/tileforge
|
|
27
|
+
Keywords: rendering,engine,tile set,top-down
|
|
28
|
+
Classifier: Development Status :: 4 - Beta
|
|
29
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Requires-Python: >=3.10
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
License-File: LICENSE
|
|
38
|
+
Requires-Dist: pygame-ce
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# Tile Forge
|
|
42
|
+
|
|
43
|
+
This is a package for rendering top-down maps from a provided tile set image.
|
|
44
|
+
|
|
45
|
+
## Class Diagram
|
|
46
|
+
|
|
47
|
+
```mermaid
|
|
48
|
+
---
|
|
49
|
+
title: Class Relations
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
classDiagram
|
|
53
|
+
Renderer *-- Tileset
|
|
54
|
+
Renderer *-- Map
|
|
55
|
+
class Tileset {
|
|
56
|
+
+ all_properties: dict[int, set[int]]
|
|
57
|
+
+ tile_size: int
|
|
58
|
+
+ path: str
|
|
59
|
+
+ load()
|
|
60
|
+
+ get_scaled_tiles(scale: int)
|
|
61
|
+
+ has_property(tile_index: int, property_id: int): bool
|
|
62
|
+
+ set_property(tile_index: int, property_index: int): void
|
|
63
|
+
+ remove_property(tile_index: int, property_index: int): void
|
|
64
|
+
+ toggle_property(tile_index: int, property_index: int): void
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
class Map {
|
|
68
|
+
+ width: int
|
|
69
|
+
+ height: int
|
|
70
|
+
+ layers: list[Layer]
|
|
71
|
+
+ set_layers(layers: list[Layer])
|
|
72
|
+
+ set_layer_count(count: int)
|
|
73
|
+
+ add_layer(layer: Layer)
|
|
74
|
+
+ cell_has_property(tileset: Tileset, pos: tuple[int, int], property_id: int): bool
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
class Renderer {
|
|
78
|
+
- __map: Map
|
|
79
|
+
- __tileset: Tileset
|
|
80
|
+
+ render_tile_size: int
|
|
81
|
+
+ tiles: list[Surface]
|
|
82
|
+
+ set_render_scale(scale: int)
|
|
83
|
+
+ render(surface: Surface, offset: [int, int], callback): void
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Usage
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
import pygame
|
|
91
|
+
from tileforge import Tileset, Map, Renderer
|
|
92
|
+
|
|
93
|
+
pygame.init()
|
|
94
|
+
|
|
95
|
+
# You need a pygame surface to render the map, so make sure to initialize pygame first
|
|
96
|
+
surface = pygame.Surface((640, 480)) # Create a surface to render on
|
|
97
|
+
|
|
98
|
+
# Load the tileset and map
|
|
99
|
+
tileset = Tileset("path/to/tileset.png", 32)
|
|
100
|
+
tileset.load()
|
|
101
|
+
|
|
102
|
+
# Create a map with the loaded tileset
|
|
103
|
+
map = Map(10, 10) # 10x10
|
|
104
|
+
|
|
105
|
+
# Create a renderer and render the map
|
|
106
|
+
renderer = Renderer(tileset, map)
|
|
107
|
+
renderer.render(surface)
|
|
108
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/tileforge/__init__.py
|
|
5
|
+
src/tileforge/lib.py
|
|
6
|
+
src/tileforge/map.py
|
|
7
|
+
src/tileforge/renderer.py
|
|
8
|
+
src/tileforge/tileset.py
|
|
9
|
+
src/tileforge.egg-info/PKG-INFO
|
|
10
|
+
src/tileforge.egg-info/SOURCES.txt
|
|
11
|
+
src/tileforge.egg-info/dependency_links.txt
|
|
12
|
+
src/tileforge.egg-info/requires.txt
|
|
13
|
+
src/tileforge.egg-info/top_level.txt
|
|
14
|
+
tests/test_tileset.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pygame-ce
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tileforge
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import pygame
|
|
3
|
+
|
|
4
|
+
from tileforge import Tileset
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.fixture(scope="module", autouse=True)
|
|
8
|
+
def pygame_init():
|
|
9
|
+
pygame.init()
|
|
10
|
+
pygame.display.set_mode((1, 1))
|
|
11
|
+
yield
|
|
12
|
+
pygame.quit()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def mock_tileset(monkeypatch):
|
|
17
|
+
tile_size = 8
|
|
18
|
+
surface = pygame.Surface((16, 16), pygame.SRCALPHA)
|
|
19
|
+
|
|
20
|
+
def mock_load(path):
|
|
21
|
+
return surface
|
|
22
|
+
|
|
23
|
+
monkeypatch.setattr(pygame.image, "load", mock_load)
|
|
24
|
+
|
|
25
|
+
ts = Tileset("fake_path.png", tile_size)
|
|
26
|
+
ts.load()
|
|
27
|
+
return ts
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_load_tiles_count(mock_tileset):
|
|
31
|
+
assert len(mock_tileset.tiles) == 5
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_empty_tile_is_first(mock_tileset):
|
|
35
|
+
first_tile = mock_tileset.tiles[0]
|
|
36
|
+
assert first_tile.get_size() == (mock_tileset.tile_size, mock_tileset.tile_size)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_scaled_tiles(mock_tileset):
|
|
40
|
+
scale = 2
|
|
41
|
+
scaled = mock_tileset.get_scaled_tiles(scale)
|
|
42
|
+
|
|
43
|
+
assert len(scaled) == len(mock_tileset.tiles)
|
|
44
|
+
for tile in scaled:
|
|
45
|
+
assert tile.get_size() == (
|
|
46
|
+
mock_tileset.tile_size * scale,
|
|
47
|
+
mock_tileset.tile_size * scale,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_set_and_has_property(mock_tileset):
|
|
52
|
+
mock_tileset.set_property(100, 1)
|
|
53
|
+
|
|
54
|
+
assert mock_tileset.has_property(100, 1)
|
|
55
|
+
assert not mock_tileset.has_property(200, 1)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_remove_property(mock_tileset):
|
|
59
|
+
mock_tileset.set_property(1, 100)
|
|
60
|
+
mock_tileset.remove_property(1, 100)
|
|
61
|
+
|
|
62
|
+
assert not mock_tileset.has_property(1, 100)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_toggle_property(mock_tileset):
|
|
66
|
+
mock_tileset.toggle_property(1, 100)
|
|
67
|
+
assert mock_tileset.has_property(1, 100)
|
|
68
|
+
|
|
69
|
+
mock_tileset.toggle_property(1, 100)
|
|
70
|
+
assert not mock_tileset.has_property(1, 100)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_get_properties(mock_tileset):
|
|
74
|
+
mock_tileset.set_property(1, 100)
|
|
75
|
+
mock_tileset.set_property(2, 100)
|
|
76
|
+
|
|
77
|
+
props = mock_tileset.get_properties(100)
|
|
78
|
+
assert props == {1, 2}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_serialize_deserialize(mock_tileset):
|
|
82
|
+
mock_tileset.set_property(1, 100)
|
|
83
|
+
mock_tileset.set_property(2, 200)
|
|
84
|
+
|
|
85
|
+
data = mock_tileset.serialize_properties()
|
|
86
|
+
|
|
87
|
+
new_ts = Tileset("fake_path.png", 8)
|
|
88
|
+
new_ts.deserialize_properties(data)
|
|
89
|
+
|
|
90
|
+
assert new_ts.has_property(1, 100)
|
|
91
|
+
assert new_ts.has_property(2, 200)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_set_tile_size(monkeypatch):
|
|
95
|
+
surface = pygame.Surface((16, 16), pygame.SRCALPHA)
|
|
96
|
+
|
|
97
|
+
monkeypatch.setattr(pygame.image, "load", lambda _: surface)
|
|
98
|
+
|
|
99
|
+
ts = Tileset("fake.png", 8)
|
|
100
|
+
ts.load()
|
|
101
|
+
|
|
102
|
+
ts.set_tile_size(4)
|
|
103
|
+
|
|
104
|
+
assert ts.tile_size == 4
|
|
105
|
+
assert len(ts.tiles) == 17
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_set_path(monkeypatch):
|
|
109
|
+
surface = pygame.Surface((16, 16), pygame.SRCALPHA)
|
|
110
|
+
|
|
111
|
+
monkeypatch.setattr(pygame.image, "load", lambda _: surface)
|
|
112
|
+
|
|
113
|
+
ts = Tileset("old.png", 8)
|
|
114
|
+
ts.load()
|
|
115
|
+
|
|
116
|
+
ts.set_path("new.png")
|
|
117
|
+
|
|
118
|
+
assert ts.path == "new.png"
|