batframework 1.0.9a11__py3-none-any.whl → 1.1.0__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 (76) hide show
  1. batFramework/__init__.py +52 -76
  2. batFramework/action.py +99 -126
  3. batFramework/actionContainer.py +9 -53
  4. batFramework/animatedSprite.py +114 -56
  5. batFramework/audioManager.py +36 -82
  6. batFramework/camera.py +69 -263
  7. batFramework/constants.py +53 -29
  8. batFramework/cutscene.py +109 -243
  9. batFramework/cutsceneBlocks.py +176 -0
  10. batFramework/debugger.py +48 -0
  11. batFramework/dynamicEntity.py +9 -16
  12. batFramework/easing.py +71 -0
  13. batFramework/entity.py +85 -92
  14. batFramework/gui/__init__.py +3 -14
  15. batFramework/gui/button.py +78 -12
  16. batFramework/gui/constraints.py +204 -0
  17. batFramework/gui/container.py +31 -188
  18. batFramework/gui/debugger.py +43 -126
  19. batFramework/gui/frame.py +19 -0
  20. batFramework/gui/image.py +20 -55
  21. batFramework/gui/indicator.py +22 -95
  22. batFramework/gui/interactiveWidget.py +12 -229
  23. batFramework/gui/label.py +77 -311
  24. batFramework/gui/layout.py +66 -414
  25. batFramework/gui/root.py +35 -203
  26. batFramework/gui/shape.py +57 -247
  27. batFramework/gui/toggle.py +48 -114
  28. batFramework/gui/widget.py +243 -457
  29. batFramework/manager.py +29 -113
  30. batFramework/particles.py +77 -0
  31. batFramework/scene.py +217 -22
  32. batFramework/sceneManager.py +129 -161
  33. batFramework/stateMachine.py +8 -11
  34. batFramework/time.py +75 -0
  35. batFramework/transition.py +124 -129
  36. batFramework/transitionManager.py +0 -0
  37. batFramework/triggerZone.py +4 -4
  38. batFramework/utils.py +144 -266
  39. {batframework-1.0.9a11.dist-info → batframework-1.1.0.dist-info}/METADATA +24 -22
  40. batframework-1.1.0.dist-info/RECORD +43 -0
  41. batFramework/animation.py +0 -77
  42. batFramework/baseScene.py +0 -240
  43. batFramework/cutsceneManager.py +0 -34
  44. batFramework/drawable.py +0 -77
  45. batFramework/easingController.py +0 -58
  46. batFramework/enums.py +0 -135
  47. batFramework/fontManager.py +0 -65
  48. batFramework/gui/animatedLabel.py +0 -89
  49. batFramework/gui/clickableWidget.py +0 -244
  50. batFramework/gui/constraints/__init__.py +0 -1
  51. batFramework/gui/constraints/constraints.py +0 -980
  52. batFramework/gui/draggableWidget.py +0 -44
  53. batFramework/gui/meter.py +0 -96
  54. batFramework/gui/radioButton.py +0 -35
  55. batFramework/gui/selector.py +0 -250
  56. batFramework/gui/slider.py +0 -397
  57. batFramework/gui/style.py +0 -10
  58. batFramework/gui/styleManager.py +0 -54
  59. batFramework/gui/syncedVar.py +0 -49
  60. batFramework/gui/textInput.py +0 -306
  61. batFramework/gui/tooltip.py +0 -30
  62. batFramework/particle.py +0 -118
  63. batFramework/propertyEaser.py +0 -79
  64. batFramework/renderGroup.py +0 -34
  65. batFramework/resourceManager.py +0 -130
  66. batFramework/sceneLayer.py +0 -138
  67. batFramework/scrollingSprite.py +0 -115
  68. batFramework/sprite.py +0 -51
  69. batFramework/templates/__init__.py +0 -1
  70. batFramework/templates/controller.py +0 -97
  71. batFramework/tileset.py +0 -46
  72. batFramework/timeManager.py +0 -213
  73. batframework-1.0.9a11.dist-info/RECORD +0 -67
  74. {batframework-1.0.9a11.dist-info → batframework-1.1.0.dist-info}/LICENSE +0 -0
  75. {batframework-1.0.9a11.dist-info → batframework-1.1.0.dist-info}/WHEEL +0 -0
  76. {batframework-1.0.9a11.dist-info → batframework-1.1.0.dist-info}/top_level.txt +0 -0
batFramework/__init__.py CHANGED
@@ -1,90 +1,66 @@
1
- import os
2
1
  import pygame
3
- import json
4
- import batFramework as bf
5
2
  from .constants import Constants as const
3
+ import os
4
+ import json
5
+ initialized = False
6
+
7
+ def init(
8
+ resolution:tuple[int,int],
9
+ flags:int=0,
10
+ vsync:int = 0,
11
+ default_text_size=None,
12
+ default_font=None,
13
+ resource_path:str|None=None,
14
+ window_title:str="BatFramework Project",
15
+ fps_limit : int = 0
16
+ ):
17
+ global initialized
18
+ if not initialized:
19
+ pygame.init()
20
+ pygame.display.set_caption(window_title)
21
+
22
+ # Initialize display
23
+ const.init_screen(resolution,flags,vsync)
24
+ const.set_fps_limit(fps_limit)
25
+
26
+ # Initialize default text size
27
+ if default_text_size: const.set_default_text_size(default_text_size)
28
+ # Initialize resource path for game data
29
+ if resource_path: const.set_resource_path(resource_path)
30
+
31
+ # Initialize default font cache
32
+ from .utils import Utils
33
+ if default_font is None or isinstance(default_font,str):
34
+ Utils.init_font(default_font)
35
+ else:
36
+ raise ValueError(f"default_font '{default_font}' can be either string or None")
37
+
38
+ f = list(Utils.FONTS[None].values())[0]
39
+ print(f"Set default font to : {f.name} {'' if default_font is not None else '(default value)'}")
40
+ initialized = True
41
+
42
+ from .constants import Colors as color
43
+ from .constants import Axis as axis
6
44
  from .utils import Singleton
7
- from .enums import *
8
- from .resourceManager import ResourceManager
9
- from .fontManager import FontManager
10
45
  from .utils import Utils as utils
11
- from .tileset import Tileset
12
- from .timeManager import TimeManager,Timer,SceneTimer
13
- from .easingController import EasingController
14
- from .propertyEaser import PropertyEaser
15
- from .cutsceneManager import CutsceneManager
16
- import batFramework.cutscene as cutscene
46
+ from .time import Time, Timer
47
+ from .cutscene import Cutscene,CutsceneManager
48
+ from .cutsceneBlocks import *
49
+ from .easing import Easing, EasingAnimation
17
50
  from .audioManager import AudioManager
18
- import batFramework.transition as transition
51
+ from .utils import Layout, Alignment, Direction
52
+ from .transition import *
19
53
  from .action import Action
20
- from .actionContainer import *
54
+ from .actionContainer import ActionContainer
21
55
  from .camera import Camera
22
56
  from .entity import Entity
23
- from .drawable import Drawable
24
- from .renderGroup import RenderGroup
25
57
  from .dynamicEntity import DynamicEntity
26
- from .sprite import Sprite
27
- from .scrollingSprite import ScrollingSprite
28
- from .particle import *
29
- from .animation import Animation
30
- from .animatedSprite import AnimatedSprite
58
+ from .animatedSprite import AnimatedSprite, AnimState
31
59
  from .stateMachine import State, StateMachine
32
- from .sceneLayer import SceneLayer
60
+ from .particles import Particle, ParticleManager
61
+ # from .debugger import Debugger
33
62
  from .scene import Scene
34
- from .baseScene import BaseScene
35
- import batFramework.gui as gui
63
+ from .gui import *
36
64
  from .sceneManager import SceneManager
37
65
  from .manager import Manager
38
- from .templates import *
39
-
40
-
41
- def init_screen(resolution: tuple[int, int], flags: int = 0, vsync: int = 0):
42
- const.set_resolution(resolution)
43
- const.FLAGS = flags
44
- const.VSYNC = vsync
45
- const.SCREEN = pygame.display.set_mode(
46
- const.RESOLUTION, const.FLAGS, vsync=const.VSYNC
47
- )
48
- print(
49
- f"Window : {resolution[0]}x{resolution[1]}"
50
- )
51
66
 
52
-
53
- def print_version():
54
- # Dynamically construct the path to version.json
55
- version_file = os.path.join(os.path.dirname(__file__), "version.json")
56
- try:
57
- with open(version_file, "r") as f:
58
- version_data = json.load(f)
59
- version = version_data.get("version", "unknown")
60
- print(f"BatFramework version: {version}")
61
- except FileNotFoundError:
62
- print(f"Version file not found: {version_file}")
63
- except json.JSONDecodeError:
64
- print(f"Error decoding version file: {version_file}")
65
-
66
- def init(
67
- resolution: tuple[int, int],
68
- flags: int = 0,
69
- window_caption: str = "BatFramework Project",
70
- resource_path: str | None = None,
71
- default_font_size=None,
72
- default_font=None,
73
- fps_limit: int = 0,
74
- vsync: int = 0,
75
- ):
76
- print_version()
77
- pygame.display.set_caption(window_caption)
78
- init_screen(resolution, flags, vsync)
79
- pygame.mixer.init()
80
-
81
- ResourceManager().set_resource_path(
82
- resource_path if resource_path is not None else "."
83
- )
84
- if resource_path is not None:
85
- ResourceManager().load_resources(ResourceManager().RESOURCE_PATH)
86
- if default_font_size is not None:
87
- FontManager().set_default_text_size(default_font_size)
88
- FontManager().init_font(default_font)
89
- const.BF_INITIALIZED = True
90
- const.set_fps_limit(fps_limit)
batFramework/action.py CHANGED
@@ -1,8 +1,8 @@
1
- from typing import Any, Self
1
+ from typing import Any
2
2
  from enum import Enum
3
3
  import pygame
4
- from .enums import actionType
5
4
 
5
+ ActionType = Enum("type", "INSTANTANEOUS CONTINUOUS HOLDING")
6
6
 
7
7
  class Action:
8
8
  def __init__(self, name: str) -> None:
@@ -12,28 +12,34 @@ class Action:
12
12
  Args:
13
13
  name (str): The name of the action.
14
14
  """
15
- self.name: str = name
16
- self.active: bool = False
17
- self.data: dict = {}
18
- self.consume_event: bool = False
19
- self._type: actionType = actionType.INSTANTANEOUS
20
- self._key_control: set = set()
21
- self._mouse_control: set = set()
22
- self._event_control: set = set()
23
- self._gamepad_button_control: set = set()
24
- self._gamepad_axis_control: set = set()
15
+ self._name = name
16
+ self._active = False
17
+ self._type = ActionType.INSTANTANEOUS
18
+ self._key_control = set()
19
+ self._mouse_control = set()
20
+ self._gamepad_button_control = set()
21
+ self._gamepad_axis_control = set()
25
22
  self._holding = set()
23
+ self._unique = True
24
+ self.data : Any = None
26
25
 
27
- def set_consume_event(self, val: bool) -> Self:
26
+ def set_unique(self, val: bool) -> None:
28
27
  """
29
28
  Set whether this action is unique (exclusive).
30
- When in an action Container, unique actions -when active - break the propagation of their event to other actions.
31
29
 
32
30
  Args:
33
31
  val (bool): True if the action is unique, False otherwise.
34
32
  """
35
- self.consume_event = val
36
- return self
33
+ self._unique = val
34
+
35
+ def is_active(self) -> bool:
36
+ """
37
+ Check if the action is currently active.
38
+
39
+ Returns:
40
+ bool: True if the action is active, False otherwise.
41
+ """
42
+ return self._active
37
43
 
38
44
  def set_active(self, value: bool) -> None:
39
45
  """
@@ -42,18 +48,10 @@ class Action:
42
48
  Args:
43
49
  value (bool): True to activate the action, False to deactivate it.
44
50
  """
45
- self.active = value
46
-
47
-
48
- def add_event_control(self, *events) -> Self:
49
- self._event_control.update(events)
50
- return self
51
-
52
- def remove_event_control(self, *events) -> Self:
53
- self._event_control = self._event_control - events
54
- return self
51
+ self._active = value
52
+ self._holding = set()
55
53
 
56
- def add_key_control(self, *keys) -> Self:
54
+ def add_key_control(self, *keys) -> 'Action':
57
55
  """
58
56
  Add key controls to the action.
59
57
 
@@ -66,51 +64,29 @@ class Action:
66
64
  self._key_control.update(keys)
67
65
  return self
68
66
 
69
- def remove_key_control(self, *keys: int) -> Self:
67
+ def add_mouse_control(self, *mouse_buttons) -> 'Action':
70
68
  """
71
- Remove key controls to the action.
69
+ Add mouse control to the action.
72
70
 
73
71
  Args:
74
- *keys (int): Key codes to control this action.
72
+ *mouse_buttons (int): Mouse button codes to control this action.
75
73
 
76
74
  Returns:
77
75
  Action: The updated Action object for method chaining.
78
76
  """
79
- self._key_control = self._key_control - set(keys)
77
+ self._mouse_control.update(mouse_buttons)
80
78
  return self
81
79
 
82
- def replace_key_control(self, key, new_key) -> Self:
83
- if not key in self._key_control:
84
- return self
85
- self.remove_key_control(key)
86
- self.add_key_control(new_key)
87
- return self
88
-
89
- def add_mouse_control(self, *mouse: int) -> Self:
80
+ def get_name(self) -> str:
90
81
  """
91
- Add mouse control to the action.
92
-
93
- Args:
94
- *mouse_buttons (int): Mouse button codes to control this action.
82
+ Get the name of the action.
95
83
 
96
84
  Returns:
97
- Action: The updated Action object for method chaining.
85
+ str: The name of the action.
98
86
  """
99
- self._mouse_control.update(mouse)
100
- return self
101
-
102
- def remove_mouse_control(self, *mouse: int) -> Self:
103
- self._mouse_control = self._mouse_control - set(mouse)
104
- return self
87
+ return self._name
105
88
 
106
- def replace_mouse_control(self, mouse, new_mouse) -> Self:
107
- if not mouse in self._mouse_control:
108
- return self
109
- self.remove_mouse_control(mouse)
110
- self.add_mouse_control(new_mouse)
111
- return self
112
-
113
- def set_continuous(self) -> Self:
89
+ def set_continuous(self) -> 'Action':
114
90
  """
115
91
  Set the action type to continuous.
116
92
 
@@ -118,7 +94,7 @@ class Action:
118
94
  Action: The updated Action object for method chaining.
119
95
  """
120
96
  self._holding = set()
121
- self._type = actionType.CONTINUOUS
97
+ self._type = ActionType.CONTINUOUS
122
98
  return self
123
99
 
124
100
  def is_continuous(self) -> bool:
@@ -128,16 +104,16 @@ class Action:
128
104
  Returns:
129
105
  bool: True if the action type is continuous, False otherwise.
130
106
  """
131
- return self._type == actionType.CONTINUOUS
107
+ return self._type == ActionType.CONTINUOUS
132
108
 
133
- def set_instantaneous(self) -> Self:
109
+ def set_instantaneous(self) -> 'Action':
134
110
  """
135
111
  Set the action type to instantaneous.
136
112
 
137
113
  Returns:
138
114
  Action: The updated Action object for method chaining.
139
115
  """
140
- self._type = actionType.INSTANTANEOUS
116
+ self._type = ActionType.INSTANTANEOUS
141
117
  self._holding = set()
142
118
  return self
143
119
 
@@ -148,16 +124,16 @@ class Action:
148
124
  Returns:
149
125
  bool: True if the action type is instantaneous, False otherwise.
150
126
  """
151
- return self._type == actionType.INSTANTANEOUS
127
+ return self._type == ActionType.INSTANTANEOUS
152
128
 
153
- def set_holding(self) -> Self:
129
+ def set_holding(self) -> 'Action':
154
130
  """
155
131
  Set the action type to holding.
156
132
 
157
133
  Returns:
158
134
  Action: The updated Action object for method chaining.
159
135
  """
160
- self._type = actionType.HOLDING
136
+ self._type = ActionType.HOLDING
161
137
  return self
162
138
 
163
139
  def is_holding_type(self) -> bool:
@@ -167,17 +143,13 @@ class Action:
167
143
  Returns:
168
144
  bool: True if the action type is holding, False otherwise.
169
145
  """
170
- return self._type == actionType.HOLDING
146
+ return self._type == ActionType.HOLDING
171
147
 
172
- def process_update(self, event: pygame.Event) -> None:
173
- if (
174
- event.type == pygame.MOUSEMOTION
175
- and self._type == actionType.HOLDING
176
- and pygame.MOUSEMOTION in self._mouse_control
177
- ) or self._event_control:
178
- self.data = event.dict
148
+ def process_update(self,event:pygame.Event)->None:
149
+ if self.is_active() and event.type == pygame.MOUSEMOTION and self.is_holding_type() and pygame.MOUSEMOTION in self._mouse_control:
150
+ self.data = {"pos":event.pos,"rel":event.rel}
179
151
 
180
- def process_activate(self, event: pygame.event.Event):
152
+ def process_activate(self, event: pygame.event.Event) -> bool:
181
153
  """
182
154
  Process activation of the action based on a pygame event.
183
155
 
@@ -187,93 +159,94 @@ class Action:
187
159
  Returns:
188
160
  bool: True if the action was activated by the event, False otherwise.
189
161
  """
190
-
191
162
  if event.type == pygame.KEYDOWN and event.key in self._key_control:
192
- self._activate_action(event.key)
163
+ self._active = True
164
+ if self.is_holding_type():
165
+ self._holding.add(event.key)
166
+ return True
193
167
 
194
- elif (
195
- event.type == pygame.MOUSEBUTTONDOWN and event.button in self._mouse_control
196
- ):
197
- self._activate_action(event.button)
168
+ if event.type == pygame.MOUSEBUTTONDOWN and event.button in self._mouse_control:
169
+ self._active = True
170
+ if self.is_holding_type():
171
+ self._holding.add(event.button)
172
+ return True
198
173
 
199
- elif event.type == pygame.MOUSEMOTION and event.type in self._mouse_control:
200
- self._activate_action(event.type)
174
+ if event.type == pygame.MOUSEMOTION and event.type in self._mouse_control:
175
+ self._active = True
176
+ if self.is_holding_type():
177
+ self._holding.add(event.type)
178
+ return True
201
179
 
202
- elif event.type in self._event_control:
203
- self._activate_action(event.type)
204
- self.data = event.dict
205
- else:
206
- return
207
- if self.consume_event:
208
- event.consumed = True
180
+ return False
209
181
 
210
- def _activate_action(self, control):
211
- self.active = True
212
- if self._type == actionType.HOLDING:
213
- self._holding.add(control)
214
-
215
- def process_deactivate(self, event: pygame.event.Event):
182
+ def process_deactivate(self, event: pygame.event.Event) -> bool:
216
183
  """
217
184
  Process deactivation of the action based on a pygame event.
218
185
 
219
186
  Args:
220
187
  event (pygame.event.Event): The pygame event to process.
221
188
 
189
+ Returns:
190
+ bool: True if the action was deactivated by the event, False otherwise.
222
191
  """
223
- if self._type == actionType.HOLDING:
192
+ if self._type == ActionType.HOLDING:
224
193
  if event.type == pygame.KEYUP and event.key in self._key_control:
225
- self._deactivate_action(event.key)
194
+ if event.key in self._holding:
195
+ self._holding.remove(event.key)
196
+ if not self._holding:
197
+ self._active = False
198
+ return True
226
199
  elif (
227
200
  event.type == pygame.MOUSEBUTTONUP
228
201
  and event.button in self._mouse_control
229
202
  ):
230
- self._deactivate_action(event.button)
231
- elif event.type == pygame.MOUSEMOTION and event.type in self._mouse_control:
232
- self._deactivate_action(event.type)
233
- elif event.type in self._event_control:
234
- self._deactivate_action(event.type)
235
- else:
236
- event.consumed = False
237
-
238
- if self.consume_event:
239
- event.consumed = True
203
+ if event.button in self._holding:
204
+ self._holding.remove(event.button)
205
+ if not self._holding:
206
+ self._active = False
207
+ return True
208
+ elif (
209
+ event.type == pygame.MOUSEMOTION
210
+ and event.type in self._mouse_control
211
+ ):
212
+ self.value = None
213
+ if event.type in self._holding:
214
+ self._holding.remove(event.type)
215
+ if not self._holding:
216
+ self._active = False
217
+ return True
240
218
 
241
- def _deactivate_action(self, control) -> bool:
242
- if control in self._holding:
243
- self._holding.remove(control)
244
- if not self._holding:
245
- self.active = False
219
+ return False
246
220
 
247
- def process_event(self, event: pygame.event.Event):
221
+ def process_event(self, event: pygame.event.Event) -> bool:
248
222
  """
249
223
  Process a pygame event and update the action's state.
250
224
 
251
225
  Args:
252
226
  event (pygame.event.Event): The pygame event to process.
253
- """
254
227
 
255
- if event.consumed:
256
- return
257
- if not self.active:
258
- self.process_activate(event)
228
+ Returns:
229
+ bool: True if the action state changed, False otherwise.
230
+ """
231
+ if not self._active:
232
+ res = self.process_activate(event)
259
233
  else:
260
- self.process_deactivate(event)
261
- if self.active:
262
- self.process_update(event)
263
- return
234
+ res = self.process_deactivate(event)
235
+ self.process_update(event)
236
+ return res
264
237
 
265
238
  def reset(self) -> None:
266
239
  """
267
240
  Reset the action's state to the default state.
268
241
  """
269
- if self._type in {actionType.CONTINUOUS, actionType.HOLDING}:
242
+ if self._type in {ActionType.CONTINUOUS, ActionType.HOLDING}:
270
243
  return
271
- elif self._type == actionType.INSTANTANEOUS:
272
- self.active = False
244
+ elif self._type == ActionType.INSTANTANEOUS:
245
+ self._active = False
273
246
 
274
247
  def hard_reset(self) -> None:
275
248
  """
276
249
  Hard reset the action, deactivating it and clearing any holding controls.
277
250
  """
278
- self.active = False
251
+ self._active = False
279
252
  self._holding = set()
@@ -1,44 +1,32 @@
1
1
  import batFramework as bf
2
- import pygame
3
2
 
4
3
 
5
4
  class ActionContainer:
6
5
  def __init__(self, *actions: list[bf.Action]) -> None:
7
6
  self._actions: dict[str, bf.Action] = {}
8
7
  if actions:
9
- self.add_actions(*actions)
10
-
11
- def __iter__(self):
12
- return iter(self._actions.values())
8
+ self.add_action(*actions)
13
9
 
14
10
  def clear(self):
15
11
  self._actions = {}
16
12
 
17
- def add_actions(self, *actions: bf.Action):
13
+ def add_action(self, *actions: bf.Action):
18
14
  for action in actions:
19
- self._actions[action.name] = action
15
+ self._actions[action.get_name()] = action
20
16
 
21
- def get(self, name: str) -> bf.Action:
17
+ def get(self,name:str)->bf.Action:
22
18
  return self._actions.get(name)
23
19
 
24
- def has_action(self, name: str):
20
+ def has_action(self, name:str):
25
21
  return name in self._actions
26
22
 
27
- def get_all(self) -> list[bf.Action]:
28
- return self._actions
29
-
30
- def is_active(self, *names: str) -> bool:
31
- return all(
32
- self._actions.get(name).active if name in self._actions else False
33
- for name in names
34
- )
23
+ def is_active(self, *names:str)->bool:
24
+ return all(self._actions.get(name).is_active() if name in self._actions else False for name in names)
35
25
 
36
26
  def process_event(self, event):
37
- if event.consumed:
38
- return
39
27
  for action in self._actions.values():
40
- action.process_event(event)
41
- if event.consumed == True:
28
+ a = action.process_event(event)
29
+ if a and action._unique:
42
30
  break
43
31
 
44
32
  def reset(self):
@@ -48,35 +36,3 @@ class ActionContainer:
48
36
  def hard_reset(self):
49
37
  for action in self._actions.values():
50
38
  action.hard_reset()
51
-
52
-
53
- class DirectionalKeyControls(ActionContainer):
54
- def __init__(self):
55
- super().__init__(
56
- bf.Action("up").add_key_control(pygame.K_UP).set_holding(),
57
- bf.Action("down").add_key_control(pygame.K_DOWN).set_holding(),
58
- bf.Action("left").add_key_control(pygame.K_LEFT).set_holding(),
59
- bf.Action("right").add_key_control(pygame.K_RIGHT).set_holding(),
60
- )
61
-
62
-
63
- class WASDControls(ActionContainer):
64
- def __init__(self):
65
- super().__init__(
66
- bf.Action("up").add_key_control(pygame.K_w).set_holding(),
67
- bf.Action("down").add_key_control(pygame.K_s).set_holding(),
68
- bf.Action("left").add_key_control(pygame.K_a).set_holding(),
69
- bf.Action("right").add_key_control(pygame.K_d).set_holding(),
70
- )
71
-
72
-
73
- class HybridControls(ActionContainer):
74
- def __init__(self):
75
- super().__init__(
76
- bf.Action("up").add_key_control(pygame.K_UP, pygame.K_w).set_holding(),
77
- bf.Action("down").add_key_control(pygame.K_DOWN, pygame.K_s).set_holding(),
78
- bf.Action("left").add_key_control(pygame.K_LEFT, pygame.K_a).set_holding(),
79
- bf.Action("right")
80
- .add_key_control(pygame.K_RIGHT, pygame.K_r)
81
- .set_holding(),
82
- )