actor.py 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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.simulation import Subject
  13. from synergine2_cocos2d.animation import AnimatedInterface
  14. from synergine2_cocos2d.util import PathManager
  15. from synergine2_xyz.image import ImageCacheManager
  16. class Actor(AnimatedInterface, cocos.sprite.Sprite):
  17. animation_image_paths = {} # type: typing.Dict[str, typing.List[str]]
  18. def __init__(
  19. self,
  20. image_path: str,
  21. subject: Subject,
  22. position=(0, 0),
  23. rotation=0,
  24. scale=1,
  25. opacity=255,
  26. color=(255, 255, 255),
  27. anchor=None,
  28. properties: dict=None,
  29. config: Config=None,
  30. **kwargs
  31. ):
  32. # Note: Parameter required, but we want to modify little as possible parent init
  33. assert config, "Config is a required parameter"
  34. self.config = config
  35. self.path_manager = PathManager(config.resolve('global.include_path.graphics'))
  36. default_image_path = self.build_default_image(
  37. subject.id,
  38. self.path_manager.path(image_path),
  39. )
  40. image = pyglet.image.load(os.path.abspath(default_image_path))
  41. self.animation_images = {} # type: typing.Dict[str, typing.List[pyglet.image.TextureRegion]] # nopep8
  42. super().__init__(
  43. image,
  44. position,
  45. rotation,
  46. scale,
  47. opacity,
  48. color,
  49. anchor,
  50. **kwargs
  51. )
  52. self.subject = subject
  53. self.cshape = None # type: collision_model.AARectShape
  54. self.update_cshape()
  55. self.build_animation_images(subject.id)
  56. self.current_image = image
  57. self.need_update_cshape = False
  58. self.properties = properties or {}
  59. self._freeze = False
  60. self.default_image_path = image_path
  61. self.image_cache = self.get_image_cache_manager()
  62. self.image_cache.build()
  63. def get_image_cache_manager(self) -> ImageCacheManager:
  64. return ImageCacheManager(self, self.config)
  65. def build_default_image(self, subject_id: int, base_image_path: str) -> str:
  66. cache_dir = self.config.resolve('global.cache_dir_path')
  67. with open(base_image_path, 'rb') as base_image_file:
  68. base_image = Image.open(base_image_file)
  69. for default_appliable_image in self.get_default_appliable_images():
  70. base_image.paste(
  71. default_appliable_image,
  72. (0, 0),
  73. default_appliable_image,
  74. )
  75. # FIXME NOW: nom des image utilise au dessus
  76. final_name = '_'.join([
  77. str(subject_id),
  78. ntpath.basename(base_image_path),
  79. ])
  80. final_path = os.path.join(cache_dir, final_name)
  81. base_image.save(final_path)
  82. return final_path
  83. def get_default_appliable_images(self) -> typing.List[Image.Image]:
  84. return []
  85. def get_animation_appliable_images(
  86. self,
  87. animation_name: str,
  88. animation_position: int,
  89. ) -> typing.List[Image.Image]:
  90. return []
  91. def freeze(self) -> None:
  92. """
  93. Set object to freeze mode: No visual modification can be done anymore
  94. """
  95. self._freeze = True
  96. def stop_actions(self, action_types: typing.Tuple[typing.Type[cocos.actions.Action], ...]) -> None:
  97. for action in self.actions:
  98. if isinstance(action, action_types):
  99. self.remove_action(action)
  100. def update_cshape(self) -> None:
  101. self.cshape = collision_model.AARectShape(
  102. euclid.Vector2(self.position[0], self.position[1]),
  103. self.width // 2,
  104. self.height // 2,
  105. )
  106. self.need_update_cshape = False
  107. def update_position(self, new_position: euclid.Vector2) -> None:
  108. if self._freeze:
  109. return
  110. self.position = new_position
  111. self.cshape.center = new_position # Note: if remove: strange behaviour: drag change actor position with anomaly
  112. def build_animation_images(self, subject_id: int) -> None:
  113. """
  114. Fill self.animation_images with self.animation_image_paths
  115. :return: None
  116. """
  117. cache_dir = self.config.resolve('global.cache_dir_path')
  118. for animation_name, animation_image_paths in self.animation_image_paths.items():
  119. self.animation_images[animation_name] = []
  120. for i, animation_image_path in enumerate(animation_image_paths):
  121. final_image_path = self.path_manager.path(animation_image_path)
  122. final_image = Image.open(final_image_path)
  123. for appliable_image in self.get_animation_appliable_images(
  124. animation_name,
  125. i,
  126. ):
  127. final_image.paste(
  128. appliable_image,
  129. (0, 0),
  130. appliable_image,
  131. )
  132. # FIXME NOW: nom des image utilise au dessus
  133. final_name = '_'.join([
  134. str(subject_id),
  135. ntpath.basename(final_image_path),
  136. ])
  137. final_path = os.path.join(cache_dir, final_name)
  138. final_image.save(final_path)
  139. self.animation_images[animation_name].append(
  140. pyglet.image.load(
  141. final_path,
  142. )
  143. )
  144. def get_images_for_animation(self, animation_name: str) -> typing.List[pyglet.image.TextureRegion]:
  145. return self.animation_images.get(animation_name)
  146. def get_inanimate_image(self) -> pyglet.image.TextureRegion:
  147. return self.current_image
  148. def update_image(self, new_image: pyglet.image.TextureRegion):
  149. if self._freeze:
  150. return
  151. self.image = new_image
  152. self.image_anchor = new_image.width // 2, new_image.height // 2