Bastien Sevajol 6 年前
父节点
当前提交
2471b7cad7

二进制
medias/images/actors/man.png 查看文件


二进制
medias/images/actors/man_weap1.png 查看文件


二进制
medias/images/actors/man_weap1_firing1.png 查看文件


二进制
medias/images/actors/man_weap1_firing2.png 查看文件


二进制
medias/images/actors/man_weap1_firing3.png 查看文件


二进制
medias/images/actors/src/man_weap1_firing.xcf 查看文件


+ 4 - 0
opencombat/exception.py 查看文件

@@ -7,3 +7,7 @@ class OpenCombatException(Exception):
7 7
 
8 8
 class UnknownWeapon(OpenCombatException):
9 9
     pass
10
+
11
+
12
+class UnknownAnimationIndex(OpenCombatException):
13
+    pass

+ 102 - 6
opencombat/gui/actor.py 查看文件

@@ -1,19 +1,26 @@
1 1
 # coding: utf-8
2 2
 import typing
3 3
 
4
+import time
5
+
6
+import pyglet
4 7
 from PIL import Image
5 8
 from synergine2.config import Config
6 9
 from synergine2.simulation import Subject
7 10
 from synergine2_cocos2d.actor import Actor
8 11
 
9
-from opencombat.exception import UnknownWeapon
12
+from opencombat.exception import UnknownWeapon, UnknownAnimationIndex
10 13
 from opencombat.gui.animation import ANIMATION_CRAWL
11 14
 from opencombat.gui.animation import ANIMATION_WALK
12 15
 from opencombat.gui.const import POSITION_MAN_STAND_UP
13 16
 from opencombat.gui.const import POSITION_MAN_CRAWLING
17
+from opencombat.gui.image import TileImageCache
14 18
 from opencombat.gui.weapon import RIFFLE
15 19
 from opencombat.gui.weapon import WeaponImageApplier
16 20
 
21
+if typing.TYPE_CHECKING:
22
+    from opencombat.gui.fire import GuiFiringEvent
23
+
17 24
 
18 25
 class BaseActor(Actor):
19 26
     position_matching = {
@@ -31,6 +38,17 @@ class BaseActor(Actor):
31 38
         self.weapon_image_applier = WeaponImageApplier(config, self)
32 39
         super().__init__(image_path, subject=subject, config=config)
33 40
 
41
+        # Firing
42
+        self.last_firing_time = 0
43
+        self.firing_change_image_gap = 0.05  # seconds
44
+        self.firing_images_cache = {}  # type: TODO
45
+
46
+        self.default_image_path = image_path
47
+
48
+        self.image_cache = TileImageCache(self, self.config)
49
+        self.image_cache.build()
50
+        pass
51
+
34 52
     @property
35 53
     def mode(self) -> str:
36 54
         return self._mode
@@ -44,9 +62,9 @@ class BaseActor(Actor):
44 62
             return []
45 63
 
46 64
         return [
47
-            self.weapon_image_applier.get_default_image_for_weapon(
65
+            self.weapon_image_applier.get_image_for_weapon(
48 66
                 self.mode,
49
-                self.weapons[0],
67
+                self.weapons[0],  # FIXME
50 68
             )
51 69
         ]
52 70
 
@@ -71,8 +89,81 @@ class BaseActor(Actor):
71 89
         except UnknownWeapon:
72 90
             return []
73 91
 
74
-    def firing(self) -> None:
75
-        pass
92
+    def get_modes(self) -> typing.List[str]:
93
+        return [POSITION_MAN_STAND_UP]
94
+
95
+    # def build_firing_images(self) -> None:
96
+    #     cache_dir = self.config.resolve('global.cache_dir_path')
97
+    #     for mode in self.get_modes():
98
+    #         for weapon in self.weapons:
99
+    #             images = self.weapon_image_applier.get_firing_image(
100
+    #                 mode=mode,
101
+    #                 weapon_type=weapon,
102
+    #             )
103
+    #
104
+    #             for position in range(len(images)):
105
+    #                 pass
106
+    #
107
+    #
108
+    #     for animation_name, animation_image_paths in self.animation_image_paths.items():
109
+    #         self.animation_images[animation_name] = []
110
+    #         for i, animation_image_path in enumerate(animation_image_paths):
111
+    #             final_image_path = self.path_manager.path(animation_image_path)
112
+    #             final_image = Image.open(final_image_path)
113
+    #
114
+    #             for appliable_image in self.get_animation_appliable_images(
115
+    #                 animation_name,
116
+    #                 i,
117
+    #             ):
118
+    #                 final_image.paste(
119
+    #                     appliable_image,
120
+    #                     (0, 0),
121
+    #                     appliable_image,
122
+    #                 )
123
+    #
124
+    #             # FIXME NOW: nom des image utilise au dessus
125
+    #             final_name = '_'.join([
126
+    #                 str(subject_id),
127
+    #                 ntpath.basename(final_image_path),
128
+    #             ])
129
+    #             final_path = os.path.join(cache_dir, final_name)
130
+    #
131
+    #             final_image.save(final_path)
132
+    #
133
+    #             self.animation_images[animation_name].append(
134
+    #                 pyglet.image.load(
135
+    #                     final_path,
136
+    #                 )
137
+    #             )
138
+
139
+    def firing(self, firing: 'GuiFiringEvent') -> None:
140
+        # FIXME: move some code ?
141
+        now = time.time()
142
+        if now - self.last_firing_time >= self.firing_change_image_gap:
143
+            self.last_firing_time = now
144
+            firing.increment_animation_index()
145
+
146
+            try:
147
+                image = self.image_cache.firing_cache.get(
148
+                    mode=self.mode,
149
+                    weapon=firing.weapon,
150
+                    position=firing.animation_index,
151
+                )
152
+            except UnknownAnimationIndex:
153
+                image = self.image_cache.firing_cache.get(
154
+                    mode=self.mode,
155
+                    weapon=firing.weapon,
156
+                    position=0,
157
+                )
158
+                firing.reset_animation_index()
159
+
160
+            # FIXME cache: prepare before firing
161
+            import uuid
162
+            tmp_path = '/tmp/{}.png'.format(str(uuid.uuid4()))
163
+            image.save(tmp_path)
164
+            pyglet_image = pyglet.image.load(tmp_path)
165
+
166
+            self.update_image(pyglet_image.get_texture())
76 167
 
77 168
 
78 169
 class Man(BaseActor):
@@ -108,7 +199,7 @@ class Man(BaseActor):
108 199
         return [RIFFLE]
109 200
 
110 201
 
111
-class HeavyVehicle(Actor):
202
+class HeavyVehicle(BaseActor):
112 203
     animation_image_paths = {
113 204
         ANIMATION_WALK: [
114 205
             'actors/tank1.png',
@@ -124,3 +215,8 @@ class HeavyVehicle(Actor):
124 215
         subject: Subject,
125 216
     ) -> None:
126 217
         super().__init__('actors/tank1.png', subject=subject, config=config)
218
+
219
+    @property
220
+    def weapons(self) -> typing.List[str]:
221
+        # TODO BS 2018-01-26: Will be managed by complex part of code
222
+        return [RIFFLE]

+ 9 - 1
opencombat/gui/base.py 查看文件

@@ -17,6 +17,7 @@ from synergine2_cocos2d.interaction import InteractionManager
17 17
 from synergine2_cocos2d.middleware import MapMiddleware
18 18
 from synergine2_cocos2d.util import PathManager
19 19
 
20
+from opencombat.gui.fire import GuiFiringEvent
20 21
 from opencombat.simulation.interior import InteriorManager
21 22
 from opencombat.simulation.tmx import TileMap
22 23
 from opencombat.user_action import UserAction
@@ -346,8 +347,14 @@ class Game(TMXGui):
346 347
         def gunshot_sound():
347 348
             self.sound_lib.get_sound('gunshot_default').play()
348 349
 
350
+        firing_event = GuiFiringEvent(shooter_actor, event.weapon_type)
351
+        original_actor_image = shooter_actor.image
352
+
349 353
         def actor_firing():
350
-            shooter_actor.firing(event.weapon_type)
354
+            shooter_actor.firing(firing_event)
355
+
356
+        def actor_end_firing():
357
+            shooter_actor.update_image(original_actor_image)
351 358
 
352 359
         # To avoid all in same time
353 360
         # TODO BS 2018-01-24: This should be unecessary when core events sending will be
@@ -368,6 +375,7 @@ class Game(TMXGui):
368 375
             actor_firing,
369 376
             duration=0.2,  # TODO BS 2018-01-25: Wil depend of weapon type
370 377
             delay=delay,
378
+            end_callback=actor_end_firing,
371 379
         )
372 380
 
373 381
     def subject_die(self, event: DieEvent) -> None:

+ 26 - 0
opencombat/gui/fire.py 查看文件

@@ -1,6 +1,8 @@
1 1
 # coding: utf-8
2 2
 import typing
3 3
 
4
+from opencombat.gui.actor import BaseActor
5
+from opencombat.simulation.event import DEFAULT_WEAPON_TYPE
4 6
 from opencombat.simulation.fire import RequestFireBehaviour
5 7
 from synergine2_cocos2d.interaction import BaseActorInteraction
6 8
 from opencombat.user_action import UserAction
@@ -87,3 +89,27 @@ class BaseFireActorInteraction(BaseActorInteraction):
87 89
 class FireActorInteraction(BaseFireActorInteraction):
88 90
     gui_action = UserAction.ORDER_FIRE
89 91
     color = (255, 0, 0)
92
+
93
+
94
+class GuiFiringEvent(object):
95
+    def __init__(
96
+        self,
97
+        actor: BaseActor,
98
+        weapon: str,
99
+    ) -> None:
100
+        self.actor = actor
101
+        self.weapon = weapon
102
+        self._animation_index = -1
103
+
104
+        if weapon == DEFAULT_WEAPON_TYPE:
105
+            self.weapon = self.actor.weapons[0]
106
+
107
+    @property
108
+    def animation_index(self) -> int:
109
+        return self._animation_index
110
+
111
+    def increment_animation_index(self) -> None:
112
+        self._animation_index += 1
113
+
114
+    def reset_animation_index(self) -> None:
115
+        self._animation_index = -1

+ 100 - 0
opencombat/gui/image.py 查看文件

@@ -0,0 +1,100 @@
1
+# coding: utf-8
2
+import io
3
+import typing
4
+
5
+from PIL import Image
6
+from synergine2.config import Config
7
+from synergine2_cocos2d.actor import Actor
8
+from synergine2_cocos2d.util import PathManager
9
+
10
+from opencombat.exception import UnknownAnimationIndex
11
+
12
+
13
+class ImageCache(object):
14
+    def __init__(self) -> None:
15
+        self.cache = {}
16
+
17
+
18
+class FiringCache(ImageCache):
19
+    def add(
20
+        self,
21
+        mode: str,
22
+        weapon: str,
23
+        image: Image.Image,
24
+    ) -> None:
25
+        self.cache.setdefault(mode, {}).setdefault(weapon, []).append(image)
26
+
27
+    def get(
28
+        self,
29
+        mode: str,
30
+        weapon: str,
31
+        position: int,
32
+    ) -> Image.Image:
33
+        try:
34
+            return self.cache[mode][weapon][position]
35
+        except KeyError:
36
+            raise Exception('TODO')
37
+        except IndexError:
38
+            raise UnknownAnimationIndex(
39
+                'Unknown animation index "{}" for mode "{}" and weapon "{}"'.format(
40
+                    position,
41
+                    mode,
42
+                    weapon,
43
+                ),
44
+            )
45
+
46
+
47
+class ImageCache(object):
48
+    # FIXME: Move into synergine
49
+    def __init__(
50
+        self,
51
+        actor: Actor,
52
+        config: Config,
53
+    ) -> None:
54
+        self.config = config
55
+        self.actor = actor
56
+
57
+    def build(self) -> None:
58
+        pass
59
+
60
+
61
+class TileImageCache(ImageCache):
62
+    def __init__(
63
+        self,
64
+        actor: Actor,
65
+        config: Config,
66
+    ) -> None:
67
+        super().__init__(actor, config)
68
+        self.firing_cache = FiringCache()
69
+        from opencombat.gui.actor import BaseActor
70
+        self.actor = typing.cast(BaseActor, self.actor)
71
+        self.path_manager = PathManager(
72
+            self.config.resolve('global.include_path.graphics'),
73
+        )
74
+
75
+    def build(self) -> None:
76
+        super().build()
77
+        self.build_firing()
78
+
79
+    def build_firing(self) -> None:
80
+        for mode in self.actor.get_modes():
81
+            mode_image_path = self.actor.default_image_path  # FIXME !
82
+            mode_image = Image.open(self.path_manager.path(mode_image_path))
83
+
84
+            for weapon in self.actor.weapons:
85
+                images = self.actor.weapon_image_applier.get_firing_image(
86
+                    mode=mode,
87
+                    weapon_type=weapon,
88
+                )
89
+
90
+                for position in range(len(images)):
91
+                    position_image = images[position]
92
+
93
+                    final_image = mode_image.copy()
94
+                    final_image.paste(
95
+                        position_image,
96
+                        (0, 0),
97
+                        position_image,
98
+                    )
99
+
100
+                    self.firing_cache.add(mode, weapon, final_image)

+ 38 - 3
opencombat/gui/weapon.py 查看文件

@@ -1,7 +1,9 @@
1 1
 # coding: utf-8
2 2
 import typing
3 3
 
4
+import pyglet
4 5
 from PIL import Image
6
+from pyglet.image import ImageData
5 7
 from synergine2.config import Config
6 8
 from synergine2_cocos2d.util import PathManager
7 9
 
@@ -26,11 +28,13 @@ class WeaponImageApplier(ImageApplier):
26 28
         actor: 'BaseActor',
27 29
     ) -> None:
28 30
         self.actor = actor
29
-        self._images_scheme = self.get_images_scheme()
31
+        self._images_scheme = self.get_rest_images_scheme()
32
+        self._firing_images_scheme = self.get_firing_images_scheme()
30 33
         self.path_manager = PathManager(config.resolve('global.include_path.graphics'))
31 34
         self._cache = {}  # type: typing.Dict[str, Image.Image]
35
+        self._firing_cache = {}  # type: typing.Dict[str, Image.Image]
32 36
 
33
-    def get_images_scheme(self) -> typing.Dict[str, typing.Dict[str, typing.List[str]]]:
37
+    def get_rest_images_scheme(self) -> typing.Dict[str, typing.Dict[str, typing.List[str]]]:
34 38
         return {
35 39
             POSITION_MAN_STAND_UP: {
36 40
                 RIFFLE: [
@@ -48,7 +52,18 @@ class WeaponImageApplier(ImageApplier):
48 52
             }
49 53
         }
50 54
 
51
-    def get_default_image_for_weapon(self, mode: str, weapon_type: str) -> Image.Image:
55
+    def get_firing_images_scheme(self) -> typing.Dict[str, typing.Dict[str, typing.List[str]]]:
56
+        return {
57
+            POSITION_MAN_STAND_UP: {
58
+                RIFFLE: [
59
+                    'actors/man_weap1_firing1.png',
60
+                    'actors/man_weap1_firing2.png',
61
+                    'actors/man_weap1_firing3.png',
62
+                ],
63
+            },
64
+        }
65
+
66
+    def get_image_for_weapon(self, mode: str, weapon_type: str) -> Image.Image:
52 67
         try:
53 68
             image_file_path = self.path_manager.path(
54 69
                 self._images_scheme[mode][weapon_type][0],
@@ -63,6 +78,26 @@ class WeaponImageApplier(ImageApplier):
63 78
                 'Unknown weapon "{}" for mode "{}"'.format(weapon_type, mode),
64 79
             )
65 80
 
81
+    def get_firing_image(
82
+        self, mode: str,
83
+        weapon_type: str,
84
+    ) -> typing.List[Image.Image]:
85
+        images = []
86
+        try:
87
+            image_file_paths = self._firing_images_scheme[mode][weapon_type]
88
+            for image_file_path in image_file_paths:
89
+                final_path = self.path_manager.path(image_file_path)
90
+                try:
91
+                    images.append(self._firing_cache[final_path])
92
+                except KeyError:
93
+                    self._firing_cache[image_file_path] = Image.open(final_path)
94
+                    images.append(self._firing_cache[image_file_path])
95
+            return images
96
+        except KeyError:
97
+            raise UnknownWeapon(
98
+                'Unknown weapon "{}" for mode "{}"'.format(weapon_type, mode),
99
+            )
100
+
66 101
     def get_animation_image_for_weapon(
67 102
         self,
68 103
         mode: str,