batframework 1.0.9a6__py3-none-any.whl → 1.0.9a8__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.
Files changed (61) hide show
  1. batFramework/__init__.py +20 -11
  2. batFramework/action.py +1 -1
  3. batFramework/animatedSprite.py +47 -116
  4. batFramework/animation.py +30 -5
  5. batFramework/audioManager.py +16 -13
  6. batFramework/baseScene.py +240 -0
  7. batFramework/camera.py +4 -0
  8. batFramework/constants.py +6 -1
  9. batFramework/cutscene.py +221 -21
  10. batFramework/cutsceneManager.py +5 -2
  11. batFramework/drawable.py +7 -5
  12. batFramework/easingController.py +10 -11
  13. batFramework/entity.py +21 -2
  14. batFramework/enums.py +48 -33
  15. batFramework/gui/__init__.py +3 -1
  16. batFramework/gui/animatedLabel.py +10 -2
  17. batFramework/gui/button.py +4 -31
  18. batFramework/gui/clickableWidget.py +42 -30
  19. batFramework/gui/constraints/constraints.py +212 -136
  20. batFramework/gui/container.py +72 -48
  21. batFramework/gui/debugger.py +12 -17
  22. batFramework/gui/draggableWidget.py +8 -11
  23. batFramework/gui/image.py +3 -10
  24. batFramework/gui/indicator.py +73 -1
  25. batFramework/gui/interactiveWidget.py +117 -100
  26. batFramework/gui/label.py +73 -63
  27. batFramework/gui/layout.py +221 -452
  28. batFramework/gui/meter.py +21 -7
  29. batFramework/gui/radioButton.py +0 -1
  30. batFramework/gui/root.py +99 -29
  31. batFramework/gui/selector.py +257 -0
  32. batFramework/gui/shape.py +13 -5
  33. batFramework/gui/slider.py +260 -93
  34. batFramework/gui/textInput.py +45 -21
  35. batFramework/gui/toggle.py +70 -52
  36. batFramework/gui/tooltip.py +30 -0
  37. batFramework/gui/widget.py +203 -125
  38. batFramework/manager.py +7 -8
  39. batFramework/particle.py +4 -1
  40. batFramework/propertyEaser.py +79 -0
  41. batFramework/renderGroup.py +17 -50
  42. batFramework/resourceManager.py +43 -13
  43. batFramework/scene.py +15 -335
  44. batFramework/sceneLayer.py +138 -0
  45. batFramework/sceneManager.py +31 -36
  46. batFramework/scrollingSprite.py +8 -3
  47. batFramework/sprite.py +1 -1
  48. batFramework/templates/__init__.py +1 -2
  49. batFramework/templates/controller.py +97 -0
  50. batFramework/timeManager.py +76 -22
  51. batFramework/transition.py +37 -103
  52. batFramework/utils.py +121 -3
  53. {batframework-1.0.9a6.dist-info → batframework-1.0.9a8.dist-info}/METADATA +24 -3
  54. batframework-1.0.9a8.dist-info/RECORD +66 -0
  55. {batframework-1.0.9a6.dist-info → batframework-1.0.9a8.dist-info}/WHEEL +1 -1
  56. batFramework/character.py +0 -27
  57. batFramework/templates/character.py +0 -43
  58. batFramework/templates/states.py +0 -166
  59. batframework-1.0.9a6.dist-info/RECORD +0 -63
  60. /batframework-1.0.9a6.dist-info/LICENCE → /batframework-1.0.9a8.dist-info/LICENSE +0 -0
  61. {batframework-1.0.9a6.dist-info → batframework-1.0.9a8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,97 @@
1
+ import batFramework as bf
2
+ import pygame
3
+
4
+ class PlatformController(bf.DynamicEntity):
5
+ def __init__(self,*args,**kwargs):
6
+ super().__init__(*args,**kwargs)
7
+ self.control = bf.ActionContainer()
8
+ self.speed = 500
9
+ self.acceleration = 100
10
+ self.jump_force = -500
11
+ self.gravity = 1200
12
+ self.on_ground = False
13
+ self.friction = 0.7
14
+
15
+ def do_reset_actions(self):
16
+ self.control.reset()
17
+
18
+ def do_process_actions(self, event):
19
+ self.control.process_event(event)
20
+
21
+ def check_collision_y(self):
22
+ pass
23
+
24
+ def check_collision_x(self):
25
+ pass
26
+
27
+ def update(self, dt):
28
+ super().update(dt)
29
+ self.velocity.x *= self.friction
30
+ if abs(self.velocity.x) <= 0.01:
31
+ self.velocity.x = 0
32
+ if not self.on_ground:
33
+ self.velocity.y += self.gravity * dt
34
+ if self.control.is_active("left"):
35
+ self.velocity.x -= self.acceleration
36
+ if self.control.is_active("right"):
37
+ self.velocity.x += self.acceleration
38
+ if self.on_ground and self.control.is_active("jump") :
39
+ self.velocity.y = self.jump_force
40
+ self.on_ground = False
41
+
42
+ self.velocity.x = pygame.math.clamp(self.velocity.x,-self.speed,self.speed)
43
+ self.rect.x += self.velocity.x * dt
44
+ self.check_collision_x()
45
+ self.rect.y += self.velocity.y * dt
46
+ self.check_collision_y()
47
+
48
+
49
+
50
+ class TopDownController(bf.DynamicEntity):
51
+ def __init__(self,*args,**kwargs):
52
+ super().__init__(*args,**kwargs)
53
+ self.control = bf.ActionContainer()
54
+ self.input_velocity = pygame.Vector2()
55
+ self.speed = 500
56
+ self.acceleration = 100
57
+ self.friction = 0
58
+
59
+ def do_reset_actions(self):
60
+ self.control.reset()
61
+
62
+ def do_process_actions(self, event):
63
+ self.control.process_event(event)
64
+
65
+ def check_collision_y(self):
66
+ pass
67
+
68
+ def check_collision_x(self):
69
+ pass
70
+
71
+ def update(self, dt):
72
+ super().update(dt)
73
+ self.input_velocity.update(0,0)
74
+ self.velocity *= self.friction
75
+
76
+ if abs(self.velocity.x) <= 0.01:
77
+ self.velocity.x = 0
78
+ if self.control.is_active("left"):
79
+ self.input_velocity[0] = -self.acceleration
80
+ if self.control.is_active("right"):
81
+ self.input_velocity[0] = self.acceleration
82
+ if self.control.is_active("up"):
83
+ self.input_velocity[1] = -self.acceleration
84
+ if self.control.is_active("down"):
85
+ self.input_velocity[1] = self.acceleration
86
+
87
+ if self.input_velocity:
88
+ self.input_velocity.normalize_ip()
89
+
90
+ self.velocity += self.input_velocity * self.acceleration
91
+ self.velocity.clamp_magnitude_ip(self.speed)
92
+
93
+
94
+ self.rect.x += self.velocity.x * dt
95
+ self.check_collision_x()
96
+ self.rect.y += self.velocity.y * dt
97
+ self.check_collision_y()
@@ -1,12 +1,11 @@
1
1
  import batFramework as bf
2
2
  from typing import Callable, Union, Self,Any
3
3
 
4
-
5
4
  class Timer:
6
5
  _count: int = 0
7
6
  _available_ids: set[int] = set()
8
7
 
9
- def __init__(self, duration: Union[float, int], end_callback: Callable[[],Any], loop: bool = False, register: str = "global") -> None:
8
+ def __init__(self, duration: float, end_callback: Callable[[], Any], loop: int = 0, register: str = "global") -> None:
10
9
  if Timer._available_ids:
11
10
  self.uid = Timer._available_ids.pop()
12
11
  else:
@@ -14,85 +13,133 @@ class Timer:
14
13
  Timer._count += 1
15
14
 
16
15
  self.register = register
17
- self.duration: int | float = duration
16
+ self.duration: float = duration
18
17
  self.end_callback = end_callback
19
18
 
20
- self.elapsed_time: float = -1
19
+ self.elapsed_time: float = 0
21
20
  self.is_over: bool = False
22
- self.is_looping: bool = loop
21
+ self.loop: int = loop # Number of loops (-1 for infinite)
23
22
  self.is_paused: bool = False
24
23
  self.do_delete: bool = False
24
+ self.is_stopped: bool = True
25
25
 
26
- def __bool__(self)->bool:
27
- return self.elapsed_time==-1 or self.is_over
26
+ def __bool__(self) -> bool:
27
+ return self.elapsed_time != -1 and self.is_over
28
28
 
29
29
  def __str__(self) -> str:
30
- return f"Timer ({self.uid}) {self.elapsed_time}/{self.duration} | {'loop ' if self.is_looping else ''} {'(D) ' if self.do_delete else ''}"
30
+ loop_info = "infinite" if self.loop == -1 else f"{self.loop} loops left"
31
+ return f"Timer ({self.uid}) {self.elapsed_time}/{self.duration} | {loop_info} {'(D) ' if self.do_delete else ''}"
31
32
 
32
33
  def stop(self) -> Self:
33
- self.elapsed_time = -1
34
- self.is_over = False
34
+ """
35
+ Cancels all progression and stops the timer.
36
+ Does not mark it for deletion and does not call the end_callback.
37
+ Prevents automatic restart if looping.
38
+ """
39
+ self.is_stopped = True
35
40
  self.is_paused = False
41
+ self.is_over = False
42
+ self.elapsed_time = 0
36
43
  return self
37
44
 
38
45
  def start(self, force: bool = False) -> Self:
39
- if self.elapsed_time != -1 and not force:
46
+ """
47
+ Starts the timer only if not already started (unless force is used, which resets it).
48
+ """
49
+ if self.elapsed_time > 0 and not force:
40
50
  return self
41
- if not bf.TimeManager().add_timer(self,self.register):
51
+ if not bf.TimeManager().add_timer(self, self.register):
42
52
  return self
43
53
  self.elapsed_time = 0
44
54
  self.is_paused = False
45
55
  self.is_over = False
56
+ self.is_stopped = False
46
57
  return self
47
58
 
48
59
  def pause(self) -> Self:
60
+ """
61
+ Momentarily stops the timer until resume is called.
62
+ """
49
63
  self.is_paused = True
50
64
  return self
51
65
 
52
66
  def resume(self) -> Self:
67
+ """
68
+ Resumes from a paused state.
69
+ """
53
70
  self.is_paused = False
54
71
  return self
55
72
 
56
73
  def delete(self) -> Self:
74
+ """
75
+ Marks the timer for deletion.
76
+ """
57
77
  self.do_delete = True
58
78
  return self
59
79
 
60
80
  def has_started(self) -> bool:
61
- return self.elapsed_time != -1
81
+ """
82
+ Returns True if the timer has started.
83
+ """
84
+ return not self.is_stopped
62
85
 
63
86
  def get_progression(self) -> float:
64
- if self.elapsed_time < 0:
87
+ """
88
+ Returns the progression of the timer (0 to 1) as a float.
89
+ """
90
+ if self.is_stopped:
65
91
  return 0
66
92
  if self.elapsed_time >= self.duration:
67
93
  return 1
68
94
  return self.elapsed_time / self.duration
69
95
 
70
96
  def update(self, dt) -> None:
71
- if self.elapsed_time < 0 or self.is_paused or self.is_over:
97
+ if self.is_stopped or self.is_paused or self.is_over:
72
98
  return
73
99
  self.elapsed_time += dt
74
- # print("update :",self.elapsed_time,self.duration)
75
100
  if self.get_progression() == 1:
76
101
  self.end()
77
102
 
78
103
  def end(self):
104
+ """
105
+ Ends the timer progression (calls the end_callback function).
106
+ Is called automatically once the timer is over.
107
+ Will not mark the timer for deletion.
108
+ If it is looping, it will restart the timer **only if it wasn't stopped**.
109
+ """
110
+ self.is_over = True
79
111
  if self.end_callback:
80
112
  self.end_callback()
81
- self.elapsed_time = -1
82
- self.is_over = True
83
- if self.is_looping:
113
+
114
+ # Handle looping
115
+ if self.loop == -1: # Infinite looping
116
+ self.elapsed_time = 0
84
117
  self.start()
85
118
  return
119
+ elif self.loop > 0: # Decrease loop count and restart
120
+ self.loop -= 1
121
+ self.elapsed_time = 0
122
+ self.start()
123
+ return
124
+
125
+ # Stop the timer if no loops are left
126
+ self.is_stopped = True
86
127
 
87
128
  def should_delete(self) -> bool:
129
+ """
130
+ Method that returns if the timer is to be deleted.
131
+ Required for timer management.
132
+ """
88
133
  return self.is_over or self.do_delete
89
134
 
90
135
  def _release_id(self):
91
136
  Timer._available_ids.add(self.uid)
92
137
 
93
-
94
138
  class SceneTimer(Timer):
95
- def __init__(self, duration: float | int, end_callback, loop: bool = False, scene_name:str = "global") -> None:
139
+ """
140
+ A timer that is only updated while the given scene is active (being updated)
141
+ """
142
+ def __init__(self, duration: float | int, end_callback, loop: int = 0, scene_name:str = "global") -> None:
96
143
  super().__init__(duration, end_callback, loop, scene_name)
97
144
 
98
145
  class TimeManager(metaclass=bf.Singleton):
@@ -125,6 +172,13 @@ class TimeManager(metaclass=bf.Singleton):
125
172
  if name not in self.registers:
126
173
  self.registers[name] = TimeManager.TimerRegister(active)
127
174
 
175
+ def remove_register(self, name):
176
+ if name not in self.registers:
177
+ return
178
+
179
+ self.registers.pop(name)
180
+
181
+
128
182
  def add_timer(self, timer, register="global") -> bool:
129
183
  if register in self.registers:
130
184
  self.registers[register].add_timer(timer)
@@ -136,7 +190,7 @@ class TimeManager(metaclass=bf.Singleton):
136
190
  return [t for t in self.registers.values() if t.active]
137
191
 
138
192
  def update(self, dt):
139
- for register_name, register in self.registers.items():
193
+ for register in self.registers.values():
140
194
  if register.active:
141
195
  register.update(dt)
142
196
 
@@ -10,39 +10,23 @@ Both surfaces to transition need to be the same size
10
10
 
11
11
  class Transition:
12
12
  def __init__(
13
- self, duration: float, easing_function: bf.easing = bf.easing.LINEAR
13
+ self, duration: float=1, easing: bf.easing = bf.easing.LINEAR
14
14
  ) -> None:
15
15
  """
16
16
  duration : time in seconds
17
17
  easing function : controls the progression rate
18
18
  """
19
19
  self.duration: float = duration
20
- self.controller = bf.EasingController(
21
- easing_function,
22
- duration,
23
- update_callback=self.update,
24
- end_callback=self.end,
20
+ self.controller = bf.EasingController( # main controller for the transition progression
21
+ duration,easing,
22
+ update_callback=self.update,end_callback=self.end,
25
23
  )
26
- self.start_callback : Callable[[],Any] = None
27
- self.update_callback : Callable[[float],Any]= None
28
- self.end_callback : Callable[[],Any]= None
29
24
  self.source: pygame.Surface = None
30
25
  self.dest: pygame.Surface = None
26
+ self.is_over : bool = False # this flag tells the manager the transition is over
31
27
 
32
28
  def __repr__(self) -> str:
33
- return f"Transition {self.__class__},{self.duration}"
34
-
35
- def set_start_callback(self, func : Callable[[],Any]) -> Self:
36
- self.start_callback = func
37
- return self
38
-
39
- def set_update_callback(self, func : Callable[[float],Any]) -> Self:
40
- self.update_callback = func
41
- return self
42
-
43
- def set_end_callback(self, func : Callable[[],Any]) -> Self:
44
- self.end_callback = func
45
- return self
29
+ return f"Transition ({self.__class__},{self.duration})"
46
30
 
47
31
  def set_source(self, surface: pygame.Surface) -> None:
48
32
  self.source = surface
@@ -51,127 +35,77 @@ class Transition:
51
35
  self.dest = surface
52
36
 
53
37
  def start(self):
54
- if self.controller.has_started():
38
+ if self.controller.has_started(): # can't start again while it's in progress
55
39
  return
56
- if self.duration:
40
+ if self.duration: # start the transition
57
41
  self.controller.start()
58
- if self.start_callback:
59
- self.start_callback()
60
42
  return
61
43
 
44
+ # if no duration the transition is instantaneous
62
45
  self.controller.start()
63
- if self.start_callback:
64
- self.start_callback()
65
46
  self.controller.end()
66
- self.update(1)
47
+ self.update(1)# to prevent weird behaviour, update once with progression at max value
67
48
  self.end()
68
49
 
69
50
  def update(self, progression: float) -> None:
70
- if self.update_callback:
71
- self.update_callback(progression)
51
+ pass
72
52
 
73
53
  def end(self):
74
54
  self.controller.stop()
75
- if self.end_callback:
76
- self.end_callback()
55
+ self.is_over = True
77
56
 
78
57
  def draw(self, surface: pygame.Surface) -> None:
79
58
  pass
80
59
 
81
- def skip(self, no_callback: bool = False):
82
- self.controller.stop()
83
- if self.end_callback and (no_callback == False):
84
- self.end_callback()
60
+ def skip(self):
61
+ self.end()
62
+
85
63
 
86
64
 
87
65
  class FadeColor(Transition):
88
- def __init__(
89
- self,
90
- color: tuple,
91
- middle_duration: float,
92
- first_duration: float | None = None,
93
- second_duration: float | None = None,
94
- easing_function: bf.easing = bf.easing.LINEAR,
95
- ) -> None:
96
- super().__init__(0, easing_function)
97
- if first_duration is None:
98
- first_duration = middle_duration
99
- if second_duration is None:
100
- second_duration = middle_duration
101
-
102
- self.first = Fade(first_duration)
103
- self.second = Fade(second_duration)
66
+ def __init__(self,duration:float,color=(0,0,0),color_start:float=0.3,color_end:float=0.7, easing = bf.easing.LINEAR):
67
+ super().__init__(duration, easing)
104
68
  self.color = color
105
- self.middle_duration = middle_duration
106
- self.index = 0
107
-
108
- self.timer = bf.Timer(middle_duration, self.transition_to_second)
109
- self.first.set_end_callback(self.transition_to_middle)
110
- self.second.set_end_callback(self.transition_to_end)
111
-
112
- def transition_to_middle(self):
113
- self.next_step(self.timer.start)
114
-
115
- def transition_to_second(self):
116
- self.next_step(self.second.start)
117
-
118
- def transition_to_end(self):
119
- self.next_step(self.end)
120
-
121
- def next_step(self, callback : Callable[[],Any]=None) -> None:
122
- self.index += 1
123
- if callback:
124
- callback()
125
-
126
- def set_source(self, surface) -> None:
127
- super().set_source(surface)
128
- self.first.set_source(surface)
129
-
130
- def set_dest(self, surface) -> None:
131
- super().set_dest(surface)
132
- self.second.set_dest(surface)
69
+ self.color_start = color_start
70
+ self.color_end = color_end
133
71
 
134
72
  def start(self):
135
- if self.start_callback:
136
- self.start_callback()
137
-
73
+ super().start()
138
74
  self.color_surf = pygame.Surface(self.source.get_size())
139
75
  self.color_surf.fill(self.color)
140
76
 
141
- self.first.set_dest(self.color_surf)
142
- self.second.set_source(self.color_surf)
143
- self.first.start()
144
-
145
77
  def draw(self, surface):
146
- if self.index == 0:
147
- self.first.draw(surface)
148
- elif self.index == 1:
149
- surface.blit(self.color_surf, (0, 0))
150
- else:
151
- self.second.draw(surface)
152
-
153
- def skip(self, no_callback: bool = False):
154
- if not no_callback and self.end_callback:
155
- self.end_callback()
156
-
157
- self.first.controller.stop()
158
- self.timer.stop()
159
- self.second.controller.stop()
78
+ v = self.controller.get_value()
79
+ if v < self.color_start:
80
+ v = v/(self.color_start)
81
+ self.color_surf.set_alpha(255*v)
82
+ surface.blit(self.source)
83
+ surface.blit(self.color_surf)
160
84
 
85
+ elif v < self.color_end:
86
+ self.color_surf.set_alpha(255)
87
+ surface.blit(self.color_surf)
161
88
 
89
+ else:
90
+ v = (v-self.color_end)/(1-self.color_end)
91
+ surface.blit(self.color_surf)
92
+ self.dest.set_alpha(255*v)
93
+ surface.blit(self.dest)
162
94
 
163
95
  class Fade(Transition):
164
96
  def end(self):
165
97
  self.dest.set_alpha(255)
166
98
  return super().end()
167
99
 
100
+ def start(self):
101
+ super().start()
102
+
168
103
  def draw(self, surface):
169
104
  dest_alpha = 255 * self.controller.get_value()
170
105
  self.dest.set_alpha(dest_alpha)
171
106
  surface.blit(self.source, (0, 0))
172
107
  surface.blit(self.dest, (0, 0))
173
108
 
174
-
175
109
  class GlideRight(Transition):
176
110
  def draw(self, surface):
177
111
  width = surface.get_width()
batFramework/utils.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import pygame
2
2
  import batFramework as bf
3
+ import math
3
4
  import random
4
5
  from .enums import *
5
6
  import re
@@ -8,7 +9,7 @@ from functools import cache
8
9
  if TYPE_CHECKING:
9
10
  from .drawable import Drawable
10
11
  from .entity import Entity
11
-
12
+ from pygame.math import Vector2
12
13
 
13
14
 
14
15
  class Singleton(type):
@@ -236,12 +237,129 @@ class Utils:
236
237
 
237
238
  Args:
238
239
  margin (int): Margin from the screen edges, where the point won't be generated.
239
- If margin is less than 0 or greater than the screen resolution, returns (0, 0).
240
+ If margin is less than 0 or greater than half the screen resolution, returns (0, 0).
240
241
 
241
242
  Returns:
242
243
  tuple[int, int]: A tuple representing a random point (x, y) on the screen within the screen
243
244
  resolution minus the margin.
244
245
  """
245
- if margin < 0 or margin > bf.const.RESOLUTION[0] or margin > bf.const.RESOLUTION[1]:
246
+ if margin < 0 or margin > bf.const.RESOLUTION[0]//2 or margin > bf.const.RESOLUTION[1]//2:
246
247
  return 0, 0
247
248
  return random.randint(margin, bf.const.RESOLUTION[0] - margin), random.randint(margin, bf.const.RESOLUTION[1] - margin)
249
+
250
+ @staticmethod
251
+ def distance_point(a:tuple[float,float],b:tuple[float,float]):
252
+ return math.sqrt((a[0]-b[0]) ** 2 + (a[1]-b[1])**2)
253
+
254
+ @staticmethod
255
+ def rotate_point(point: Vector2, angle: float, center: Vector2) -> Vector2:
256
+ """Rotate a point around a center by angle (in degrees)."""
257
+ rad = math.radians(angle)
258
+ translated = point - center
259
+ rotated = Vector2(
260
+ translated.x * math.cos(rad) - translated.y * math.sin(rad),
261
+ translated.x * math.sin(rad) + translated.y * math.cos(rad)
262
+ )
263
+ return rotated + center
264
+
265
+
266
+ def draw_triangle(surface:pygame.Surface, color, rect:pygame.FRect|pygame.Rect, direction:bf.enums.direction=bf.enums.direction.RIGHT,width:int=0):
267
+ """
268
+ Draw a filled triangle inside a rectangle on a Pygame surface, pointing in the specified direction.
269
+
270
+ Args:
271
+ surface: The Pygame surface to draw on.
272
+ color: The color of the triangle (e.g., (255, 0, 0) for red).
273
+ rect: A pygame.Rect object defining the rectangle's position and size.
274
+ direction: A string ('up', 'down', 'left', 'right') indicating the triangle's orientation.
275
+ """
276
+ # Define the three vertices of the triangle based on direction
277
+ rect = rect.copy()
278
+ rect.inflate_ip(-1,-1)
279
+ if direction == direction.UP:
280
+ points = [
281
+ (rect.left, rect.bottom), # Bottom-left corner
282
+ (rect.right, rect.bottom), # Bottom-right corner
283
+ (rect.centerx, rect.top) # Top center (apex)
284
+ ]
285
+ elif direction == direction.DOWN:
286
+ points = [
287
+ (rect.left, rect.top), # Top-left corner
288
+ (rect.right, rect.top), # Top-right corner
289
+ (rect.centerx, rect.bottom) # Bottom center (apex)
290
+ ]
291
+ elif direction == direction.LEFT:
292
+ points = [
293
+ (rect.right, rect.top), # Top-right corner
294
+ (rect.right, rect.bottom), # Bottom-right corner
295
+ (rect.left, rect.centery) # Left center (apex)
296
+ ]
297
+ elif direction == direction.RIGHT:
298
+ points = [
299
+ (rect.left, rect.top), # Top-left corner
300
+ (rect.left, rect.bottom), # Bottom-left corner
301
+ (rect.right, rect.centery) # Right center (apex)
302
+ ]
303
+ else:
304
+ raise ValueError("Invalid direction")
305
+
306
+ # Draw the filled triangle
307
+ pygame.draw.polygon(surface, color, points,width=width)
308
+
309
+ def draw_arc_by_points(surface, color, start_pos, end_pos, tightness=0.5, width=1, resolution=0.5):
310
+ """
311
+ Draw a smooth circular arc connecting start_pos and end_pos.
312
+ `tightness` controls curvature: 0 is straight line, 1 is semicircle, higher = more bulge.
313
+ Negative tightness flips the bulge direction.
314
+
315
+ Args:
316
+ surface - pygame Surface
317
+ color - RGB or RGBA
318
+ start_pos - (x, y)
319
+ end_pos - (x, y)
320
+ tightness - curvature control, 0 = straight, 1 = half circle
321
+ width - line width
322
+ resolution - approx pixels per segment
323
+ Returns:
324
+ pygame.Rect bounding the drawn arc
325
+ """
326
+ p0 = pygame.Vector2(start_pos)
327
+ p1 = pygame.Vector2(end_pos)
328
+ chord = p1 - p0
329
+ if chord.length_squared() == 0:
330
+ return pygame.draw.circle(surface, color, p0, width // 2)
331
+
332
+ # Midpoint and perpendicular
333
+ mid = (p0 + p1) * 0.5
334
+ perp = pygame.Vector2(-chord.y, chord.x).normalize()
335
+
336
+ # Distance of center from midpoint, based on tightness
337
+ h = chord.length() * tightness
338
+ center = mid + perp * h
339
+
340
+ # Radius and angles
341
+ r = (p0 - center).length()
342
+ ang0 = math.atan2(p0.y - center.y, p0.x - center.x)
343
+ ang1 = math.atan2(p1.y - center.y, p1.x - center.x)
344
+
345
+ # Normalize sweep direction based on sign of tightness
346
+ sweep = ang1 - ang0
347
+ if tightness > 0 and sweep < 0:
348
+ sweep += 2 * math.pi
349
+ elif tightness < 0 and sweep > 0:
350
+ sweep -= 2 * math.pi
351
+
352
+ # Number of points
353
+ arc_len = abs(sweep * r)
354
+ segs = max(2, int(arc_len / max(resolution, 1)))
355
+
356
+ points = []
357
+ for i in range(segs + 1):
358
+ t = i / segs
359
+ a = ang0 + sweep * t
360
+ points.append((
361
+ center.x + math.cos(a) * r,
362
+ center.y + math.sin(a) * r
363
+ ))
364
+
365
+ return pygame.draw.lines(surface, color, False, points, width)
@@ -1,15 +1,36 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: batframework
3
- Version: 1.0.9a6
3
+ Version: 1.0.9a8
4
4
  Summary: Pygame framework for making games easier.
5
5
  Author-email: Turan Baturay <baturayturan@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) [2023] [TURAN BATURAY]
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
6
27
  Project-URL: Homepage, https://github.com/TuranBaturay/batFramework
7
28
  Classifier: Programming Language :: Python :: 3
8
29
  Classifier: License :: OSI Approved :: MIT License
9
30
  Classifier: Operating System :: OS Independent
10
31
  Requires-Python: >=3.11
11
32
  Description-Content-Type: text/markdown
12
- License-File: LICENCE
33
+ License-File: LICENSE
13
34
  Requires-Dist: pygame-ce
14
35
 
15
36
  # batFramework