1+ /*
2+ Example file for olcUTIL_Animate2D.h
3+
4+ License (OLC-3)
5+ ~~~~~~~~~~~~~~~
6+
7+ Copyright 2018 - 2022 OneLoneCoder.com
8+
9+ Redistribution and use in source and binary forms, with or without
10+ modification, are permitted provided that the following conditions
11+ are met:
12+
13+ 1. Redistributions or derivations of source code must retain the above
14+ copyright notice, this list of conditions and the following disclaimer.
15+
16+ 2. Redistributions or derivative works in binary form must reproduce
17+ the above copyright notice. This list of conditions and the following
18+ disclaimer must be reproduced in the documentation and/or other
19+ materials provided with the distribution.
20+
21+ 3. Neither the name of the copyright holder nor the names of its
22+ contributors may be used to endorse or promote products derived
23+ from this software without specific prior written permission.
24+
25+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36+
37+ Links
38+ ~~~~~
39+ YouTube: https://www.youtube.com/javidx9
40+ Discord: https://discord.gg/WhwHUMV
41+ Twitter: https://www.twitter.com/javidx9
42+ Twitch: https://www.twitch.tv/javidx9
43+ GitHub: https://www.github.com/onelonecoder
44+ Homepage: https://www.onelonecoder.com
45+
46+ Author
47+ ~~~~~~
48+ David Barr, aka javidx9, ©OneLoneCoder 2019, 2020, 2021, 2022
49+
50+ */
51+
52+
53+ #define OLC_PGE_APPLICATION
54+ #include " olcPixelGameEngine.h"
55+
56+ #include " utilities/olcUTIL_Animate2D.h"
57+
58+ class TEST_Animate2D : public olc ::PixelGameEngine
59+ {
60+ public:
61+ TEST_Animate2D ()
62+ {
63+ sAppName = " Animate2D Utility Test" ;
64+ }
65+
66+ // These are the states the dude can exist in, we'll need these for physics
67+ // and animation. The physics side of things moves the dude around according to state,
68+ // while the animator chooses the frames based upon state and time
69+ enum class DudeState : uint8_t
70+ {
71+ WALK_N , WALK_S , WALK_E , WALK_W , IDLE_STAND , LAUGH , CHEER , YES , NO
72+ };
73+
74+ // !! - IMPORTANT - !!
75+ // The animation set
76+ olc::utils::Animate2D::Animation<DudeState> animDude;
77+
78+ // One big sprite containing all the graphics
79+ olc::Renderable gfxAll;
80+
81+ // Small object to reprsent a dude walking around doing things
82+ struct sWalkingDude
83+ {
84+ // Which dude overall out of graophic?
85+ int32_t id = 0 ;
86+ // Where are they?
87+ olc::vf2d pos;
88+ // What are they doing?
89+ DudeState UserState = DudeState::IDLE_STAND ;
90+ // For how long should they do it?
91+ float fTick = 0 .0f ;
92+
93+ // !! - IMPORTANT - !!
94+ // Animation Token - links this object's state to animator. Note
95+ // there is no 'ownership' or memory issues here, this is a token
96+ // that the animator can use to quickly get to where it needs
97+ // in order to return frames upon request for this object.
98+ olc::utils::Animate2D::AnimationState animstate;
99+ };
100+
101+ // Introducing.... The dudes!
102+ size_t nDudes = 500 ;
103+ std::vector<sWalkingDude > vDudes;
104+
105+ public:
106+ bool OnUserCreate () override
107+ {
108+ // For this appliaction I have a single image that contains
109+ // 28x2 unique characters, each character contains 8 animations of 3
110+ // frames each. Each frame is 26x36 pixels
111+ gfxAll.Load (" ./assets/MegaSprite1.png" );
112+
113+ // Thats A LOT of individual graphics, but they all follow a similar pattern
114+ // because the asset was created usefully (take note certain popular asset creators)
115+ // which means we can reuse the animation without needing to define it
116+ // individually for all the "dudes" - the "cookie cutter" approach
117+
118+ // Manually construct sequences - gfxAll could in fact be nullptr for this
119+ // application, but ive kept it here for convenience
120+ olc::utils::Animate2D::FrameSequence anim_fs_walk_s;
121+ anim_fs_walk_s.AddFrame ({ &gfxAll, {{0 ,0 }, {26 ,36 }} });
122+ anim_fs_walk_s.AddFrame ({ &gfxAll, {{26 ,0 }, {26 ,36 }} });
123+ anim_fs_walk_s.AddFrame ({ &gfxAll, {{52 ,0 }, {26 ,36 }} });
124+
125+ olc::utils::Animate2D::FrameSequence anim_fs_walk_w;
126+ anim_fs_walk_w.AddFrame ({ &gfxAll, {{ 0 ,36 }, {26 ,36 }} });
127+ anim_fs_walk_w.AddFrame ({ &gfxAll, {{26 ,36 }, {26 ,36 }} });
128+ anim_fs_walk_w.AddFrame ({ &gfxAll, {{52 ,36 }, {26 ,36 }} });
129+
130+ olc::utils::Animate2D::FrameSequence anim_fs_walk_e;
131+ anim_fs_walk_e.AddFrame ({ &gfxAll, {{ 0 ,72 }, {26 ,36 }} });
132+ anim_fs_walk_e.AddFrame ({ &gfxAll, {{26 ,72 }, {26 ,36 }} });
133+ anim_fs_walk_e.AddFrame ({ &gfxAll, {{52 ,72 }, {26 ,36 }} });
134+
135+ olc::utils::Animate2D::FrameSequence anim_fs_walk_n;
136+ anim_fs_walk_n.AddFrame ({ &gfxAll, {{ 0 ,108 }, {26 ,36 }} });
137+ anim_fs_walk_n.AddFrame ({ &gfxAll, {{26 ,108 }, {26 ,36 }} });
138+ anim_fs_walk_n.AddFrame ({ &gfxAll, {{52 ,108 }, {26 ,36 }} });
139+
140+ olc::utils::Animate2D::FrameSequence anim_fs_yes;
141+ anim_fs_yes.AddFrame ({ &gfxAll, {{ 0 ,144 }, {26 ,36 }} });
142+ anim_fs_yes.AddFrame ({ &gfxAll, {{26 ,144 }, {26 ,36 }} });
143+ anim_fs_yes.AddFrame ({ &gfxAll, {{52 ,144 }, {26 ,36 }} });
144+
145+ olc::utils::Animate2D::FrameSequence anim_fs_no;
146+ anim_fs_no.AddFrame ({ &gfxAll, {{ 0 ,180 }, {26 ,36 }} });
147+ anim_fs_no.AddFrame ({ &gfxAll, {{26 ,180 }, {26 ,36 }} });
148+ anim_fs_no.AddFrame ({ &gfxAll, {{52 ,180 }, {26 ,36 }} });
149+
150+ olc::utils::Animate2D::FrameSequence anim_fs_laugh;
151+ anim_fs_laugh.AddFrame ({ &gfxAll, {{ 0 ,216 }, {26 ,36 }} });
152+ anim_fs_laugh.AddFrame ({ &gfxAll, {{26 ,216 }, {26 ,36 }} });
153+ anim_fs_laugh.AddFrame ({ &gfxAll, {{52 ,216 }, {26 ,36 }} });
154+
155+ olc::utils::Animate2D::FrameSequence anim_fs_cheer;
156+ anim_fs_cheer.AddFrame ({ &gfxAll, {{ 0 ,252 }, {26 ,36 }} });
157+ anim_fs_cheer.AddFrame ({ &gfxAll, {{26 ,252 }, {26 ,36 }} });
158+ anim_fs_cheer.AddFrame ({ &gfxAll, {{52 ,252 }, {26 ,36 }} });
159+
160+ // A special "idle" animation just consists of a single frame
161+ olc::utils::Animate2D::FrameSequence anim_fs_idle;
162+ anim_fs_idle.AddFrame ({ &gfxAll, {{26 ,0 }, {26 ,36 }} });
163+
164+ // We have constructed teh individual sequences, now its time
165+ // to add them to an animation, along with a state name/enum.
166+ //
167+ // I have chosen to use the enum shown earlier. You could use
168+ // std::string too, which is conveninent if you need to display
169+ // the states, though potentially far less performant
170+
171+ animDude.AddState (DudeState::WALK_S , anim_fs_walk_s);
172+ animDude.AddState (DudeState::WALK_W , anim_fs_walk_w);
173+ animDude.AddState (DudeState::WALK_E , anim_fs_walk_e);
174+ animDude.AddState (DudeState::WALK_N , anim_fs_walk_n);
175+ animDude.AddState (DudeState::IDLE_STAND , anim_fs_idle);
176+ animDude.AddState (DudeState::YES , anim_fs_yes);
177+ animDude.AddState (DudeState::NO , anim_fs_no);
178+ animDude.AddState (DudeState::LAUGH , anim_fs_laugh);
179+ animDude.AddState (DudeState::CHEER , anim_fs_cheer);
180+
181+ // Initialise the dudes
182+ for (size_t n = 0 ; n < nDudes; n++)
183+ {
184+ sWalkingDude dude;
185+
186+ // Random dude
187+ dude.id = rand () % (28 * 2 );
188+
189+ // Begin in idle state, at random location
190+ dude.UserState = DudeState::IDLE_STAND ;
191+ dude.pos = { float (rand () % ScreenWidth ()), float (rand () % ScreenHeight ()) };
192+
193+ // The animation token needs to be updated too
194+ animDude.ChangeState (dude.animstate , dude.UserState );
195+
196+ vDudes.push_back (dude);
197+ }
198+ return true ;
199+ }
200+
201+ bool OnUserUpdate (float fElapsedTime ) override
202+ {
203+ // Update Dudes
204+ float fSpeed = 32 .0f ;
205+
206+ for (auto & dude : vDudes)
207+ {
208+ // If a dude's tick reaches 0, it will select a new state
209+ dude.fTick -= fElapsedTime ;
210+ if (dude.fTick < 0 .0f )
211+ {
212+ // Choose one out of the 9 randomly
213+ int nAction = rand () % 9 ;
214+
215+ // Choose for how long it should do it in seconds
216+ dude.fTick = (float (rand ()) / float (RAND_MAX )) * 5 .0f ;
217+
218+ // Assign the state depending on the dice roll - (since enum
219+ // we could cast here too, but, well, meh...)
220+ if (nAction == 0 ) dude.UserState = DudeState::IDLE_STAND ;
221+ if (nAction == 1 ) dude.UserState = DudeState::WALK_S ;
222+ if (nAction == 2 ) dude.UserState = DudeState::WALK_N ;
223+ if (nAction == 3 ) dude.UserState = DudeState::WALK_E ;
224+ if (nAction == 4 ) dude.UserState = DudeState::WALK_W ;
225+ if (nAction == 5 ) dude.UserState = DudeState::YES ;
226+ if (nAction == 6 ) dude.UserState = DudeState::NO ;
227+ if (nAction == 7 ) dude.UserState = DudeState::LAUGH ;
228+ if (nAction == 8 ) dude.UserState = DudeState::CHEER ;
229+
230+ // State has changed, so update animation token
231+ // !! - IMPORTANT - !!
232+ animDude.ChangeState (dude.animstate , dude.UserState );
233+ }
234+
235+ // Update "physics", if walking move in that direction at speed
236+ if (dude.UserState == DudeState::WALK_S ) dude.pos += olc::vf2d (0 , +1 ) * fSpeed * fElapsedTime ;
237+ if (dude.UserState == DudeState::WALK_N ) dude.pos += olc::vf2d (0 , -1 ) * fSpeed * fElapsedTime ;
238+ if (dude.UserState == DudeState::WALK_E ) dude.pos += olc::vf2d (+1 , 0 ) * fSpeed * fElapsedTime ;
239+ if (dude.UserState == DudeState::WALK_W ) dude.pos += olc::vf2d (-1 , 0 ) * fSpeed * fElapsedTime ;
240+
241+ // If walk off screen, wrap around to other side
242+ if (dude.pos .x > ScreenWidth ()) dude.pos .x -= ScreenWidth ();
243+ if (dude.pos .y > ScreenHeight ()) dude.pos .x -= ScreenHeight ();
244+ if (dude.pos .x < 0 ) dude.pos .x += ScreenWidth ();
245+ if (dude.pos .y < 0 ) dude.pos .y += ScreenHeight ();
246+
247+ // Update animation token every frame
248+ // !! - IMPORTANT - !!
249+ animDude.UpdateState (dude.animstate , fElapsedTime );
250+ }
251+
252+ // Render Dudes
253+ for (const auto & dude : vDudes)
254+ {
255+ // Get the frame, this contains both source image and source location rectangle
256+ // !! - IMPORTANT - !!
257+ const auto & frame = animDude.GetFrame (dude.animstate );
258+
259+ // Thats cool, but there are 28x2 dudes on the sprite sheet, so using the ID, construct
260+ // an offset to the correct dude
261+ olc::vi2d vOffset = { (dude.id % 28 ) * 78 , (dude.id / 28 ) * 288 };
262+
263+ // Use DrawPartialDecal to chop out the correct dude frm the image source
264+ DrawPartialDecal (dude.pos , frame.GetSourceImage ()->Decal (), frame.GetSourceRect ().pos + vOffset, frame.GetSourceRect ().size );
265+ }
266+
267+ // That's it
268+ return true ;
269+ }
270+ };
271+
272+ int main ()
273+ {
274+ TEST_Animate2D demo;
275+ if (demo.Construct (640 , 480 , 2 , 2 ))
276+ demo.Start ();
277+ return 0 ;
278+ }
0 commit comments