actor.py 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. # coding: utf-8
  2. import io
  3. import os
  4. import typing
  5. import ntpath
  6. import pyglet
  7. from PIL import Image
  8. import cocos
  9. from cocos import collision_model
  10. from cocos import euclid
  11. from synergine2.config import Config
  12. from synergine2.log import get_logger
  13. from synergine2.simulation import Subject
  14. from synergine2_cocos2d.animation import AnimatedInterface
  15. from synergine2_cocos2d.util import PathManager
  16. from synergine2_xyz.image import ImageCacheManager
  17. class Actor(AnimatedInterface, cocos.sprite.Sprite):
  18. animation_image_paths = {} # type: typing.Dict[str, typing.List[str]]
  19. def __init__(
  20. self,
  21. image_path: str,
  22. subject: Subject,
  23. position=(0, 0),
  24. rotation=0,
  25. scale=1,
  26. opacity=255,
  27. color=(255, 255, 255),
  28. anchor=None,
  29. properties: dict=None,
  30. config: Config=None,
  31. **kwargs
  32. ):
  33. # Note: Parameter required, but we want to modify little as possible parent init
  34. assert config, "Config is a required parameter"
  35. self.config = config
  36. self.path_manager = PathManager(config.resolve('global.include_path.graphics'))
  37. self.animation_images = {} # type: typing.Dict[str, typing.List[pyglet.image.TextureRegion]] # nopep8
  38. default_texture = self._get_default_image_texture()
  39. super().__init__(
  40. default_texture,
  41. position,
  42. rotation,
  43. scale,
  44. opacity,
  45. color,
  46. anchor,
  47. **kwargs
  48. )
  49. self.logger = get_logger('Actor', config)
  50. self.subject = subject
  51. self.cshape = None # type: collision_model.AARectShape
  52. self.update_cshape()
  53. self.default_texture = default_texture
  54. self.need_update_cshape = False
  55. self.properties = properties or {}
  56. self._freeze = False
  57. self.animation_textures_cache = {} # type: typing.Dict[str, typing.List[pyglet.image.TextureRegion]] # nopep8
  58. self.mode_texture_cache = {} # type: typing.Dict[str, pyglet.image.TextureRegion] # nopep8
  59. self.default_image_path = image_path
  60. self.image_cache_manager = self.get_image_cache_manager()
  61. self.image_cache_manager.build()
  62. self.build_textures_cache()
  63. def get_image_cache_manager(self) -> ImageCacheManager:
  64. return ImageCacheManager(self, self.config)
  65. def _get_default_image_texture(self) -> pyglet.image.AbstractImage:
  66. """
  67. Build and return teh default actor image texture.
  68. :return: default actor image texture
  69. """
  70. cache_dir = self.config.resolve('global.cache_dir_path')
  71. mode = self.get_default_mode()
  72. image_path = self.get_mode_image_path(mode)
  73. final_image_path = self.path_manager.path(image_path)
  74. image = Image.open(final_image_path)
  75. image_path = '{}_default_image.png'
  76. final_image_path = os.path.join(cache_dir, image_path)
  77. image.save(final_image_path)
  78. return pyglet.image.load(final_image_path)
  79. def get_default_mode(self) -> str:
  80. raise NotImplementedError()
  81. def get_mode_image_path(self, mode: str) -> str:
  82. raise NotImplementedError()
  83. def get_modes(self) -> typing.List[str]:
  84. raise NotImplementedError()
  85. def get_mode_appliable_images(self, mode: str) -> typing.List[Image.Image]:
  86. return []
  87. def get_animation_appliable_images(
  88. self,
  89. animation_name: str,
  90. animation_position: int,
  91. ) -> typing.List[Image.Image]:
  92. return []
  93. def freeze(self) -> None:
  94. """
  95. Set object to freeze mode: No visual modification can be done anymore
  96. """
  97. self._freeze = True
  98. def stop_actions(self, action_types: typing.Tuple[typing.Type[cocos.actions.Action], ...]) -> None:
  99. for action in self.actions:
  100. if isinstance(action, action_types):
  101. self.remove_action(action)
  102. def update_cshape(self) -> None:
  103. self.cshape = collision_model.AARectShape(
  104. euclid.Vector2(self.position[0], self.position[1]),
  105. self.width // 2,
  106. self.height // 2,
  107. )
  108. self.need_update_cshape = False
  109. def update_position(self, new_position: euclid.Vector2) -> None:
  110. if self._freeze:
  111. return
  112. self.position = new_position
  113. self.cshape.center = new_position # Note: if remove: strange behaviour: drag change actor position with anomaly
  114. def build_textures_cache(self) -> None:
  115. self.build_modes_texture_cache()
  116. self.build_animation_textures_cache()
  117. def build_modes_texture_cache(self) -> None:
  118. cache_dir = self.config.resolve('global.cache_dir_path')
  119. for mode in self.get_modes():
  120. mode_image_path = self.path_manager.path(self.get_mode_image_path(mode))
  121. with open(mode_image_path, 'rb') as base_image_file:
  122. base_image = Image.open(base_image_file)
  123. for default_appliable_image in self.get_mode_appliable_images(mode):
  124. base_image.paste(
  125. default_appliable_image,
  126. (0, 0),
  127. default_appliable_image,
  128. )
  129. final_name = '{}_mode_{}.png'.format(
  130. str(self.subject.id),
  131. mode,
  132. )
  133. final_path = os.path.join(cache_dir, final_name)
  134. base_image.save(final_path)
  135. self.mode_texture_cache[mode] = pyglet.image.load(final_path)
  136. def build_animation_textures_cache(self) -> None:
  137. cache_dir = self.config.resolve('global.cache_dir_path')
  138. for animation_name in self.animation_image_paths.keys():
  139. texture_regions = []
  140. animation_images = \
  141. self.image_cache_manager.animation_cache.get_animation_images(
  142. animation_name)
  143. for i, animation_image in enumerate(animation_images):
  144. cache_image_name = '{}_animation_{}_{}.png'.format(
  145. self.subject.id,
  146. animation_name,
  147. i,
  148. )
  149. cache_image_path = os.path.join(cache_dir, cache_image_name)
  150. animation_image.save(cache_image_path)
  151. self.animation_textures_cache.setdefault(animation_name, [])\
  152. .append(pyglet.image.load(cache_image_path))
  153. def get_images_for_animation(
  154. self,
  155. animation_name: str,
  156. ) -> typing.List[pyglet.image.TextureRegion]:
  157. return self.animation_textures_cache[animation_name]
  158. def get_inanimate_image(self) -> pyglet.image.TextureRegion:
  159. return self.get_current_mode_texture()
  160. def get_current_mode_texture(self) -> pyglet.image.TextureRegion:
  161. try:
  162. return self.mode_texture_cache[self.mode]
  163. except KeyError:
  164. self.logger.debug(
  165. 'No texture for mode "{}" for actor "{}", available: ({})'.format(
  166. self.mode,
  167. self.__class__.__name__,
  168. ', '.join(self.mode_texture_cache.keys()),
  169. ),
  170. )
  171. return self.mode_texture_cache[self.get_default_mode()]
  172. def reset_default_texture(self) -> None:
  173. if self._freeze:
  174. return
  175. self.image = self.get_current_mode_texture()
  176. def update_image(self, new_image: pyglet.image.AbstractImage):
  177. if self._freeze:
  178. return
  179. self.image = new_image
  180. self.image_anchor = new_image.width // 2, new_image.height // 2