tag:blogger.com,1999:blog-22131313138911535492024-03-13T18:11:43.074-06:00Tochas Studios BlogWe make cool games!Anonymoushttp://www.blogger.com/profile/07554397703411919488noreply@blogger.comBlogger5125tag:blogger.com,1999:blog-2213131313891153549.post-74266815046053298152016-03-23T17:04:00.002-06:002016-03-23T17:04:23.887-06:00Me Caga Man<div style="text-align: justify;">
<br /></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmR57wklmvP9PzblsQQPYvYvjrszExzS-YkSAAddSc5FEpGJzCwVgz4-exdlE0FcMi9Q-dzqp1ekzD_aoDFn5jP9oNCKxpPNfnHzd67iNhmQ_eK3ipEUiRA1mviNP40ntVSSDC-UEK_A5y/s1600/Screen+Shot+2016-03-23+at+4.39.22+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmR57wklmvP9PzblsQQPYvYvjrszExzS-YkSAAddSc5FEpGJzCwVgz4-exdlE0FcMi9Q-dzqp1ekzD_aoDFn5jP9oNCKxpPNfnHzd67iNhmQ_eK3ipEUiRA1mviNP40ntVSSDC-UEK_A5y/s200/Screen+Shot+2016-03-23+at+4.39.22+PM.png" width="200" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
If you prefer to read the spanish version of this, click <a href="http://rodrigofloresmag.blogspot.mx/2016/03/me-caga-man-espanol.html">here</a>.</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div style="text-align: justify;">
This article is intended to give some context around “Me Caga Man”, so anyone who interacts with it knows why it is how it is and doesn’t get any wrong ideas about the Tochas Studios team. Having said that, let now proceed to give some details on why does it exists. TL;DR, it’s the result of a two days game jam.</div>
<a name='more'></a><br />
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
“Me Caga Man” (in english it would be something like “I Hate Everything Man”, or at least thats the general idea) is the working title to the “game” developed during an in house, two days, game jam. I’m writing the word game with quote marks because the end result of the jam, according to the next definition of a game (which isn’t my favorite but in this case is very convenient), doesn’t qualify as a game. Lets understand a game as:</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<span class="Apple-tab-span" style="white-space: pre;"> </span>“… a closed formal system that subjectively represents a subset of reality.”</div>
<div style="text-align: justify;">
<span class="Apple-tab-span" style="white-space: pre;"> </span>(What is a Game?, Chris Crawford, The Art of Computer Game Design, 1982.)</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Being the closed set of rules the ones that make the “formal system”. So with that definition in mind, </div>
Me Caga Man would be more like a sandbox or a demo.<br />
<div style="text-align: justify;">
<br />
<br /></div>
<div style="text-align: justify;">
<div style="text-align: center;">
<iframe seamless="" src="https://bandcamp.com/EmbeddedPlayer/album=791993076/size=large/bgcol=333333/linkcol=4ec5ec/artwork=small/transparent=true/" style="border: 0; height: 241px; width: 400px;"><a href="http://rodrigoflores.bandcamp.com/album/me-caga-man-ost">Me Caga Man OST by Rodrigo Flores</a></iframe>
</div>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
We aimed to create an RPG where the player had to accomplish a set of missions that represented ordinary life. Things like: going to the grocery store, going to the barber shop, going to the work place, etc. The missions would be completed by only getting from one point to another. All in an open world scenario representing a small city.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
At the beginning of the game, the player would choose his character traits. Simple stuff such as favorite food, bike vs car, etc; general interest. During the game, the player would see in the GUI something called the angry-o-meter (in spanish we called it “emputómetro” which would be better translated as “pissed-off-meter”, but it ended being angry-o-meter).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpUnV8cVJ-kM-8Pl8NEe1EQNpBj6e0U7G2ZEGTqZUXR-bH5QO9QrrJ2ujLWTuDcW8g4ljiQXAYu6GsCystCdSrdOSKH7k5loAWIWZMNd-k3yIuwW9KtaciDvG0XnkBwgFW-EvESufRRXot/s1600/Screen+Shot+2016-03-23+at+4.40.11+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="62" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpUnV8cVJ-kM-8Pl8NEe1EQNpBj6e0U7G2ZEGTqZUXR-bH5QO9QrrJ2ujLWTuDcW8g4ljiQXAYu6GsCystCdSrdOSKH7k5loAWIWZMNd-k3yIuwW9KtaciDvG0XnkBwgFW-EvESufRRXot/s320/Screen+Shot+2016-03-23+at+4.40.11+PM.png" width="320" /></a></div>
<br /></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Said meter would increase each time the player encounters something that’s against (everything different) his/hers previously selected traits. Once the angry-o-meter reaches its limit, the game would enter “Rage Mode”. In rage mode, the player would lose control of the avatar and would have to complete a mini game to lower the avatar rage. There wouldn’t exist any kind of power up during rage mode, we would only see how the game character would be out of control with his angry face.</div>
<div style="text-align: justify;">
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3cbcTAbjZI53PNJdI-AOErKBxHAdAh63No9-2e2LRyGPM-agfHuLbc65OolyofLdZsDhSPa8Sg5NrwPg86k81uypLT1bBqggNwJZgI1FHgB0iRXbi1E6g9kR3rRIXacHKSKa8KMNxa_HK/s1600/Screen+Shot+2016-03-23+at+4.40.38+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3cbcTAbjZI53PNJdI-AOErKBxHAdAh63No9-2e2LRyGPM-agfHuLbc65OolyofLdZsDhSPa8Sg5NrwPg86k81uypLT1bBqggNwJZgI1FHgB0iRXbi1E6g9kR3rRIXacHKSKa8KMNxa_HK/s1600/Screen+Shot+2016-03-23+at+4.40.38+PM.png" /></a></div>
<br />
<br /></div>
<div style="text-align: justify;">
The mini game contained in the rage mode would be some sort of skill game. Some kind of time coordination game.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Once the rage mode gets beaten, the game would return to its “normal” state. The only drawbacks would be: the resulting character location might be somewhere inconvenient for the previously selected traits. And the angry-o-meter would fill more easily.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
The game victory condition would be to complete 15 days (an arbitrary number) without entering “Eternal Rage Mode”, which would be the point when the angry-o-meter reaches its limit with every single event. </div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
That was the general idea of the game.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
After two days of development, the final result ended lacking a lot of those elements. But the experience allowed us, as a team and in the personal fields, to experiment and test our work methodology. </div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Here’s the <a href="http://tochasstudios.com/jams/MeCagaMan/">link</a> to “play” Me Caga Man. If you want to know more about us, here’s a little <a href="http://blog.tochasstudios.com/2016/01/about-us-our-story.html">bio</a>.<br />
<br />
<br />
<br /></div>
RFMChttp://www.blogger.com/profile/09728631124575006859noreply@blogger.com2tag:blogger.com,1999:blog-2213131313891153549.post-38907844195236637582016-01-19T17:44:00.001-06:002016-01-19T18:12:57.157-06:00Defining Difficulty in SodaCity<h2>
Defining Difficulty in SodaCity</h2>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Probably one of the hardest things to decide in any game, would be the game difficulty. When we were designing the levels for SodaCity, we had a hard time deciding whether we liked or not the resulting difficulty. We wanted the game to be a challenging one, but we knew that just by being hard, a game won’t necessarily be good.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
We were inspired by old school games, which were extremely difficult, but we didn’t based our design in them; so it wasn’t enough just comparing our gameplay to those games. </div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
There were various factors determining the difficulty in the gameplay: we had the already programed AI, with each one of its parameters; the damage per bullet for each one of the different weapons; the number of enemies at any given time and the hit points for both the enemies and the player. We could move everything and play with the numbers until we felt like the difficulty was good. But that would require a lot of time and maybe when we decided onto something, we would have developed balancing or testing immunity.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
We came up with some basic concepts of what we wanted to achieve, so everything we moved, had to take us closer to them.<br />
<a name='more'></a></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
We wanted the game to have the following characteristics:</div>
<div style="text-align: justify;">
<br /></div>
<br />
<h4 style="text-align: justify;">
1 - It had to feel fair.</h4>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Not necessarily from the player’s point of view, but creating equality between the playable character and his enemies. As all the characters are from the same species (soda cans), whatever might be lethal for an enemy, had to be the same way for the player. This point doesn’t apply for the bosses as we wanted them to have a classic “Bossy” feel. I will talk about that in a moment.</div>
<div style="text-align: justify;">
<br /></div>
<h4 style="text-align: justify;">
2 - It had to feel risky/dangerous.</h4>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
This one goes in hand with point #1. You can only inflict damage to the enemies while being vulnerable. SodaCity is a 2.5D shooter, you can only shoot left or right and your movement is constrained to: right, left, up and down. So when you have a clear shoot you are also being a clear target. </div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
We also wanted the gameplay to feel fast. So we couldn’t force the player having to shoot ten times at an enemy in order to defeat him. We wanted it to be one or two shots average. This would also be true for the player. If you receive a couple of shots, you’re down; and for the most powerful weapons, it would only require one shot.</div>
<div style="text-align: justify;">
<br /></div>
<h4 style="text-align: justify;">
3 - It had to feel fresh/dynamic.</h4>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
As we had only a limited set of parameters to give each level a sense of uniqueness, we couldn’t afford them being hard scripted. We didn’t want to depend on pattern memorization to finish the game. The game isn’t entirely random but some things were left random to make each play through slightly different. We set enemy spawners that would pick from different enemy presets; those presets defined the hit points, armor and type of weapons. We limited some presets so they could appear only once in each enemy “wave” in order to control the amount of challenge; but, at which point, or from where powerful enemies would appear, is going to be different each time.</div>
<div style="text-align: justify;">
<br /></div>
<h4 style="text-align: justify;">
4 - It had to be grind free.</h4>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
We didn’t want that the game’s amount of challenge relied on how many times a player had to play a particular level. We implemented upgrades to the weapons so you could give your performance a boost if you were feeling stuck. Also, the things you could buy in the in-game store, had to be affordable. Sure, there are some items that given their power you want them to be behind a high price paywall; but we tried to avoid that those prices suggested tedious replays. The prices were balanced with the amount of reward you received during specific phases of the game.</div>
<div style="text-align: justify;">
<br /></div>
<h4 style="text-align: justify;">
5 - It had to give a sense of progression.</h4>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
This is also related to the previous point. Even if you’re defeated, you’ll receive in-game currency depending on your score. You can upgrade your current weapons, or get some new, to help yourself in each level.</div>
<div style="text-align: justify;">
<br /></div>
<h4 style="text-align: justify;">
6 - The game couldn’t feel dull.</h4>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Each level had to be as fresh and challenging as possible. If a level felt to easy, or unenjoyable, we had to make some changes. The firsts levels couldn’t feel pointless, they had to make clear that the game wasn’t going to be easy. As the game progresses and enemies appear with more powerful weapons, we had to make sure that the game experience wouldn’t turn into something completely different.</div>
<div style="text-align: justify;">
<br /></div>
<h4 style="text-align: justify;">
7 - The bosses had to be menacing.</h4>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
A lot of the games that inspired SodaCity were games that I never finished. Always because of some boss. They are the only enemies that have different capabilities than the player. They could wipe you easily and resist more damage than you. As all the classic bosses, the bosses from SodaCity have their weaknesses; which were not designed but emerged from the gameplay.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
These were the major concepts that we wanted to define the gameplay in SodaCity. This was crucial in being able to define the game difficulty we felt comfortable with. </div>
<div style="text-align: justify;">
This may sound dumb, as these are things that a lot of developers might be looking for in their games. But sometimes these concepts are only general aspirations. Understanding what each concept specifically meant to us, made it easier to apply it to our game. </div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Of course, the people who play the game will decide if the degree of challenge is fair for them. They will let us know if the game is being clear with its intentions.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Even if we are sure of the game we made (or how much we tested) there will be details that we oversaw, and they will alter the general experience. But Thanks to the feedback, we can make the modifications that guarantee that the game, plays and feel, as we originally intended.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
If you want to know more about us or how SodaCity came to be, you can read it <a href="http://tochasstudios.blogspot.mx/2016/01/about-us-our-story.html">here</a>. Also you can check out SodaCity at <a href="http://store.steampowered.com/app/424210/">Steam</a>.</div>
RFMChttp://www.blogger.com/profile/09728631124575006859noreply@blogger.com1tag:blogger.com,1999:blog-2213131313891153549.post-5170806453934117912016-01-13T18:26:00.000-06:002016-01-18T16:16:12.036-06:00About us, our story.<div style="text-align: center;">
<h2>
<b>About us, our story</b></h2>
</div>
<br />
My name is Rodrigo Flores, I’m a musician and game developer. I’m co-founder of Tochas Studios, a small game studio in Mexico City.<br />
<br />
I’m going to talk about ourselves.<br />
<a name='more'></a><br />
<br />
We decided to start making games during the mobile gaming gold rush, in late 2011. <br />
While we were having breakfast, we discussed about the big opportunity releasing mobile games was. It was the perfect time, and we had everything to start. I was 21 at the time and had been playing and studying music for a while; my brother, Armando Flores, was 19, and was not sure about what he wanted to do in life but he liked drawing; and my cousin, Alejandro Santiago, who was 26, had been programing since he was a teenager. It was easy: you draw, you program and I make the noise.<br />
<br />
Of course the three of us were playing video games since before we could talk. They are a very important part of our lives and we wanted to make games for a living.<br />
<br />
So, we wrote a bunch of game ideas on napkins, mostly games we wanted to clone in order to learn and see how capable we were at making them. We had some skills, determination and ignorance; but we knew that just because we had played some games before, didn’t guaranteed we were going to be good at making them.<br />
<br />
I don’t remember why, but we decided to do a beat ‘em up game based on “Cartoon Wars: Blade” and “Super Crazy Wars”, games we had played recently in our mobile devices. As we only wanted to focus on making the game, we picked as our main character a soda can without a name that was created by Alejandro around 2000 based on Jeff Lew’s Killer bean. We named the soda can Mr. Redd and SodaCity Madness was born. The goal was to put it together as fast and cheap as possible.<br />
<br />
We made SodaCity Madness in 5 months. I had played only a few mobile games, most of them had stick men as main characters and “flash” looks. So we thought that we only needed to put together something playable, endless, free and with ads. However it looked or whatever it was about, didn’t matter.<br />
<br />
<center>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjbWfbMP9xAp5pZtr_3VtCHNxSZ7CTB5xt6hiQdygtsmgJwa-8MG4PhT5Z1tA5AxpupTUTRl8jCkJzuHxcHsVa0ycZEGQYCXcnt8diql8m7YH-AJdaK6P9hpBeiczUPAGCCx5VMTCtNrh9/s1600/SCM+main+menu.png" imageanchor="1"><img border="0" height="166" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjbWfbMP9xAp5pZtr_3VtCHNxSZ7CTB5xt6hiQdygtsmgJwa-8MG4PhT5Z1tA5AxpupTUTRl8jCkJzuHxcHsVa0ycZEGQYCXcnt8diql8m7YH-AJdaK6P9hpBeiczUPAGCCx5VMTCtNrh9/s200/SCM+main+menu.png" width="249" /></a><br />
SodaCity Madness Main menu.<br />
<br />
</center>
SodaCity Madness was a success in everything we wanted to achieve. It was playable, endless, free and had ads. It looked, sounded and played really bad. It had like 1000 downloads and it generated 1 dollar in a micro transaction Alejandro did in order to test they were working. We showed ourselves that we were capable of, strictly speaking, making a game.<br />
<br />
<center>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQ5iza3QRuUlQl8usoetBv2FhIX5_QC4cR_M7nEM050mbRu5krr-yDx-n23tpllS1EMvlspxbaY1WB7_7UZ713oEGtTV24MNnsmY9SLzMv6c7jKsGDhm0x0fm4pi74c13ZBzf4qTsRLcjB/s1600/SCM+Final.PNG" imageanchor="1"><img border="0" height="166" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQ5iza3QRuUlQl8usoetBv2FhIX5_QC4cR_M7nEM050mbRu5krr-yDx-n23tpllS1EMvlspxbaY1WB7_7UZ713oEGtTV24MNnsmY9SLzMv6c7jKsGDhm0x0fm4pi74c13ZBzf4qTsRLcjB/s320/SCM+Final.PNG" width="249" /></a><br />
SodaCity Madness in game screen.<br />
<br />
</center>
We were happy, we had our first game self published and in doing so we learned some of the industry hard truths. So we decided it was time to make the game we really wanted to make. But not the space open world strategy MMORPG we all want to make. We wanted to make SodaCity Madness without rushing and removing content because it would take some time to make. So we used our recently obtained knowledge about game design and re-designed SodaCity Madness.<br />
<br />
Originally we intended to update the already released game, so we calculated a couple of months of work. After a couple of months, we were re-doing almost everything. Alejandro started using Unity, Armando was drawing everything from scratch and I was making new sounds and songs for all the new content. We realized we had done to many changes to call it an update. It was a different game all together. We called it only SodaCity.<br />
<br />
We wanted SodaCity to be a game similar to the games we enjoyed from our early days. I wanted it to feel as I remember games like Mega Man X, Battletoads & Double Dragon and TMNT: Turtles in Time. We didn’t designed SodaCity based on those games, we were inspired by them. Every time we had to make a decision we tried to make the one that gave us a similar feel to what we remembered. We didn’t want to create a copycat, we wanted SodaCity to be a different game, while still giving that old school sensation.<br />
<br />
We planned to release SodaCity in late 2013. By mid 2013 we adjusted the release to a more realistic release date, early 2014. By early 2014 we knew the game was going to be ready for late 2014. In late 2014, the release was just a couple of months away. We finished SodaCity in september 2015.<br />
<br />
<center>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhM_LA7TWtsSV1kpdHvEDi1x4f2LtOk56OHl5tL9XPyjsNlCFUWvSN0a_XiwSr-LI__qeycUDP9Cjzg9Vrc13PmDLRqyLYy-r6d3TdyqmFovLVL1MEoR6rNL8Si3C2_fJTSQkYz5BbEzHz/s1600/SC+main+menu.png" imageanchor="1"><img border="0" height="177" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhM_LA7TWtsSV1kpdHvEDi1x4f2LtOk56OHl5tL9XPyjsNlCFUWvSN0a_XiwSr-LI__qeycUDP9Cjzg9Vrc13PmDLRqyLYy-r6d3TdyqmFovLVL1MEoR6rNL8Si3C2_fJTSQkYz5BbEzHz/s320/SC+main+menu.png" width="305" /></a><br />
SodaCity's main menu.<br />
<br />
</center>
It was a lot of time. I didn’t know what to answer to the “How’s the game going?” or “Why don’t you just finish the game already?” type of questions. I knew how much was pending, I knew what we were doing day after day. I just didn’t know how to explain it. A lot of days were about decision making. There were days were we had to cut something that took months to achieve just because it didn’t feel good. Some things needed a lot of re-doing.<br />
<br />
<center>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6BTjcr71fMPNeKoDizbp8YLgjeJibpWZjWX5u4E-psV0iFG-l_X_RgcAizxFVOs2GZOWDLLkLjWG74FVGpwb7kG1RK5bsNPI6m-4y3oOo7Ll27Vk3n9ZfI_xCsre-GeOrkCbnIJhr0YTx/s1600/delete.gif" imageanchor="1"><img border="0" height="133" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6BTjcr71fMPNeKoDizbp8YLgjeJibpWZjWX5u4E-psV0iFG-l_X_RgcAizxFVOs2GZOWDLLkLjWG74FVGpwb7kG1RK5bsNPI6m-4y3oOo7Ll27Vk3n9ZfI_xCsre-GeOrkCbnIJhr0YTx/s320/delete.gif" width="286" /></a><br />
SodaCity re-doing process<br />
<br />
</center>
We knew our game was a little retro action game, and it was hard for us that it was taking way to long to accomplish. But we wanted to make the best game we could. And all the time it took was in order to achieve that. Every single thing that was removed, changed or added to the original design was with the intention of making a game we could feel proud of. We had to be the first ones to believe in our game.<br />
<br />
<center>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmLy91ceMIbvTQp-YDKSYNif9KUZdARIybLeiKh9QX61Wja_zIfYO81bYU6r7FoBreynWIPNxi8QLN4s3wl0_K5ocLuPuICHTJOidSZB6Gw8SgNjvcpXzhLpB9de7PKwMxmsY58n5b_msq/s1600/China+town+2.png" imageanchor="1"><img border="0" height="166" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmLy91ceMIbvTQp-YDKSYNif9KUZdARIybLeiKh9QX61Wja_zIfYO81bYU6r7FoBreynWIPNxi8QLN4s3wl0_K5ocLuPuICHTJOidSZB6Gw8SgNjvcpXzhLpB9de7PKwMxmsY58n5b_msq/s320/China+town+2.png" width="295" /></a> <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVax97LlXtjjwHYDegek-VOc0aAqD_IWhwhhoSxGU25JioZOG5Sb3q64NRo8bbldcbzwTr4fwNvYhRiEh3cE3RvKabMu7bZUmTr_HhZpNUy178Et7Js6EFdLnOlNbhKS2GCBVP9_TiNZGp/s1600/Villa.png" imageanchor="1"><img border="0" height="166" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVax97LlXtjjwHYDegek-VOc0aAqD_IWhwhhoSxGU25JioZOG5Sb3q64NRo8bbldcbzwTr4fwNvYhRiEh3cE3RvKabMu7bZUmTr_HhZpNUy178Et7Js6EFdLnOlNbhKS2GCBVP9_TiNZGp/s320/Villa.png" width="295" /></a>SodaCity gameplay<br />
<br />
</center>
I can proudly say we achieved what we were aiming at. We believe in and enjoy our game. We know we have technical limitations and that there is a lot we can learn, but that didn’t stop us in making a good game. It might not be for everyone and all the tastes; surely there is people more experienced in making games, and of course there are games beyond our capabilities. But we are on our own way to find out how far we can go.<br />
<br />
Now we have to learn a very important part of this industry, we have to be able to sell what we do. We have to reach the people who might like what we do. As we released SodaCity, I have learned that there is a lot of room for improvement, that there are some people who take it for what it is and some people that wish it was different; but whatever their opinions are, I can find something that helps me improve my craft.<br />
<br />
<center>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpC4WArczgSFj4Lycs_4CR9JQdDwV-aF1daFZrywFTcgfwPOw9tz67ASbHnDb7GNgek7R2U_i1Yjb_jKkhbwsKrQl29owuzeqZJ2eqeyWTamOS4HTaNqAX8wyR4lwAX4Oy8-V3woJj39V1/s1600/Sodacity_team_drop.gif" imageanchor="1"><img border="0" height="166" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpC4WArczgSFj4Lycs_4CR9JQdDwV-aF1daFZrywFTcgfwPOw9tz67ASbHnDb7GNgek7R2U_i1Yjb_jKkhbwsKrQl29owuzeqZJ2eqeyWTamOS4HTaNqAX8wyR4lwAX4Oy8-V3woJj39V1/s320/Sodacity_team_drop.gif" width="295" /></a><br />
<br />
</center>
I have also learned that if a very limited number of people have seen our game, it just means that I have to push it further. When I’m satisfied with the amount of people aware of our game, then I can claim it as a success or failure.<br />
<br />
We hope to reach a certain number of players, units sold and revenue. We hope people to like it, and if they do (or don’t), we will listen and learn from their opinions. But all of that won’t change our opinion about our game. We know what we wanted to do, and we know what we did. And we are happy with it.<br />
<br />
Thats where we are now. In the path of finding our place at this industry.<br />
<br />
Thanks for reading. If you have a question or commentary, please let me know. You can check out SodaCity on <a href="http://store.steampowered.com/app/424210/">Steam</a>, <a href="https://indiegamestand.com/store/2218/sodacity/">IndieGameStand</a>, <a href="https://playfield.io/sodacity">playfield</a> and <a href="http://tochasstudios.itch.io/sodacity">itch.io</a>. Please let us know your feedback and opinions about the game.RFMChttp://www.blogger.com/profile/09728631124575006859noreply@blogger.com2tag:blogger.com,1999:blog-2213131313891153549.post-85978833130054642512015-11-14T20:14:00.000-06:002015-11-14T20:29:37.886-06:00Why You should avoid the Update method!<a href="http://tochasstudios.blogspot.mx/2015/11/why-you-should-avoid-update-method.html#conclusion">TL;DR link</a><br />
<br />
<div style="text-align: justify;">
While working on SodaCity at first there were only a couple of dozen of objects in the scene but as the complexity grew, more and more objects were at the scene.</div>
<div style="text-align: justify;">
At the end of the project even a few thousands of objects were active, each one doing its things, enemies, bullets, trace effects, smoke particles, background animations, environment SFX, you name it.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
At some point I started to see a decay in performance and I couldn't get to the source of the problem because the Unity Profiler was accounting a lot of the CPU usage to the 'Other' category.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Usually when you start your way into Unity3D, books, blogs, tutorials you are told how to use the main methods Unity uses <a href="http://docs.unity3d.com/ScriptReference/MonoBehaviour.Awake.html" target="_blank">Awake</a>, <a href="http://docs.unity3d.com/ScriptReference/MonoBehaviour.Start.html" target="_blank">Start</a>, <a href="http://docs.unity3d.com/ScriptReference/MonoBehaviour.Update.html" target="_blank">Update</a>, <a href="http://docs.unity3d.com/ScriptReference/MonoBehaviour.LateUpdate.html" target="_blank">LateUpdate </a>and <a href="http://docs.unity3d.com/ScriptReference/MonoBehaviour.OnDestroy.html" target="_blank">OnDestroy</a>. But not really often they mention the overhead this causes.</div>
<div style="text-align: justify;">
Because <a href="http://docs.unity3d.com/ScriptReference/MonoBehaviour.html" target="_blank">MonoBehavior </a>does not provide abstract or virtual methods for you to override, the only way it knows if a given behavior needs a particular method to be invoked it needs to use <a href="http://jacksondunstan.com/articles/2972" target="_blank">Reflection </a>to figure it out. Besides I believe this causes overhead as it needs to get a hold of the reflected method and call invoke over the given object, etc.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
So I decided to put my theory to the test.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
You can download the test project from <a href="https://www.dropbox.com/s/5inm1pxbdl5uby3/PerformanceTest.unitypackage?dl=0" target="_blank">here</a>.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
To see details on the tests performed, continue reading...</div>
<a name='more'></a><br />
<br />
<b style="background-color: #f4cccc; color: #cc0000; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 14.85px; line-height: 20.79px; text-align: justify;">These tests were made with Unity 4.3</b><br />
<br />
<div style="text-align: justify;">
<b>Note:</b> I do recommend to <b>disable VSync </b>to be able to get the maximum number of FPS for each test.</div>
<div style="text-align: justify;">
<i>Edit->Project->Quality</i></div>
<i><br /></i>
<br />
<h2>
The Setup</h2>
<div style="text-align: justify;">
It is a simple scene, no Jebediah sprites this time, it just has a camera, a FPS counter and a Setup object with 5 child objects that are going to be used as the <b>TestManagers</b>.</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJsK9aZb_pCQNBppA4nfXJGXSPFyVr4IDgVEZJ9KH57kcz8G-Y9yx-N-30NezNnirt2GjLFy6VPoGN0jQwI9TswAVsEhVGhTuoZAXoZgthfgBtSuUgAOC_6g3fzx8gjVuIxgo-W6AInTWD/s1600/01_Setup.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJsK9aZb_pCQNBppA4nfXJGXSPFyVr4IDgVEZJ9KH57kcz8G-Y9yx-N-30NezNnirt2GjLFy6VPoGN0jQwI9TswAVsEhVGhTuoZAXoZgthfgBtSuUgAOC_6g3fzx8gjVuIxgo-W6AInTWD/s1600/01_Setup.png" /></a></div>
<br />
<div style="text-align: justify;">
The <b>'Setup'</b> object will have references to the five <b>TestManagers</b> and will be responsible to show simple GUI Buttons and handle mouse events to activate / deactivate the corresponding TestManagers. It will also set the <b>FPS</b> cap to <b>300</b>.</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGWnp47JNk3d2J8Dl7is1AUeEal0hZhJIlDmzR-wWilesyZjqXReylPdztGRnayaQ6QmTGzaXOLY0Urk7bARo6HI0VolK4upuCVzaTV8Y0Hu_TxbXXua94-pbYAyGYkqoUP5BcFlYLhfFD/s1600/02_Setup_Settings.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="298" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGWnp47JNk3d2J8Dl7is1AUeEal0hZhJIlDmzR-wWilesyZjqXReylPdztGRnayaQ6QmTGzaXOLY0Urk7bARo6HI0VolK4upuCVzaTV8Y0Hu_TxbXXua94-pbYAyGYkqoUP5BcFlYLhfFD/s320/02_Setup_Settings.png" width="320" /></a></div>
<br />
<div style="text-align: justify;">
When run it will show the blue screen, the FPS counter at the top left corner and the five buttons. </div>
<div style="text-align: justify;">
Because when the 'Setup' object is loaded it disables the child test objects so the FPS counter should be around <b>300fps</b>.</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhv_4b6u1L1qJlx6tzv9GT9NEqn97QY3K-8AKbPvmpnsWYD9ll6XSdknz6BsvE_kGtBNFiRoGIY-VK_ZcnAQpBlz8ONzBUDAirRxXzAdAnU055N61gdjn9Yeq1fTUg_8LMuv6BEKWScI5RD/s1600/03_RunTest_Start.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="216" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhv_4b6u1L1qJlx6tzv9GT9NEqn97QY3K-8AKbPvmpnsWYD9ll6XSdknz6BsvE_kGtBNFiRoGIY-VK_ZcnAQpBlz8ONzBUDAirRxXzAdAnU055N61gdjn9Yeq1fTUg_8LMuv6BEKWScI5RD/s640/03_RunTest_Start.png" width="640" /></a></div>
<br />
<div style="text-align: justify;">
The Profiler should show that most of the frame time is spent waiting for the target FPS.</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZ4qcZ8sVnRmEXTf7WTGcreU0YBL34y5Atbxp23_pqeBp2PjGp__0_0U6bM3QGj6WRBYVvmm5xetFNadof4Ce_CN8T_xch-pub6M-FY3goD5Le0O9SzNO9ef8tEUgdYnNdyJAohqxK-rMd/s1600/04_RunTest_ProfilerBaseline_annotations.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="380" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZ4qcZ8sVnRmEXTf7WTGcreU0YBL34y5Atbxp23_pqeBp2PjGp__0_0U6bM3QGj6WRBYVvmm5xetFNadof4Ce_CN8T_xch-pub6M-FY3goD5Le0O9SzNO9ef8tEUgdYnNdyJAohqxK-rMd/s640/04_RunTest_ProfilerBaseline_annotations.png" width="640" /></a></div>
<br />
<br />
<h2>
The Test </h2>
<div style="text-align: justify;">
The code to test is simple, and uses methods that are common for most game sub-systems operations.</div>
<br />
<pre style="background: #000; color: #f8f8f8;"><span style="color: #e28964;">for</span>(<span style="color: #99cf50;">int</span> i=<span style="color: #3387cc;">0</span>; i< <span style="color: #3387cc;">10</span>; i++){
this<span style="color: #3e87e3;">.v1</span> = new Vector3(Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f));
this<span style="color: #3e87e3;">.v2</span> = new Vector3(Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f));
this<span style="color: #3e87e3;">.v3</span> = new Vector3(Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f));
Vector3 delta = this<span style="color: #3e87e3;">.v2</span> - this<span style="color: #3e87e3;">.v1</span>;
delta.<span style="color: #dad085;">Normalize</span>();
this<span style="color: #3e87e3;">.v3</span> += delta * this<span style="color: #3e87e3;">.magnitude</span>;
}
</pre>
<br />
<div style="text-align: justify;">
It will randomly generate three Vector3 then will calculate the delta between the first two then normalize that delta then scale it by a fixed magnitude and assign it to the third vector. All that 10 times per call/invocation.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
The different tests will handle this same algorithm in various ways in order to measure the impact of each implementation.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
To create a reasonable load each <b>TestManager </b>will create <b>1000 objects</b> from the specified <b>prefab</b>.</div>
<div style="text-align: justify;">
<br /></div>
<h3>
Test #1 - Update Method</h3>
<div style="text-align: justify;">
This test will use the default Update method invoked by the UnityEngine itself for every <a href="http://docs.unity3d.com/ScriptReference/GameObject.html" target="_blank">GameObject </a>with the '<i>UpdateTest</i>' behavior attached.
</div>
<br />
<pre style="background: #000; color: #f8f8f8;"><span style="color: #e28964;">using</span> UnityEngine;
<span style="color: #e28964;">using</span> System.Collections;
public <span style="color: #99cf50;">class</span> <span style="text-decoration: underline;">UpdateTest</span> : MonoBehaviour {
private Vector3 v1;
private Vector3 v2;
private float magnitude;
private Vector3 v3;
void Awake(){
this.magnitude = Random.Range(1f,100f);
}
<span style="color: #aeaeae; font-style: italic;">// Update is called once per frame</span>
<span style="color: #99cf50;">void</span> <span style="color: #89bdff;">Update</span> () {
for(int i=0; i< 10; i++){
this<span style="color: #3e87e3;">.v1</span> = new Vector3(Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f));
this<span style="color: #3e87e3;">.v2</span> = new Vector3(Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f));
this<span style="color: #3e87e3;">.v3</span> = new Vector3(Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f));
Vector3 delta = this<span style="color: #3e87e3;">.v2</span> - this<span style="color: #3e87e3;">.v1</span>;
delta.<span style="color: #dad085;">Normalize</span>();
this<span style="color: #3e87e3;">.v3</span> += delta * this<span style="color: #3e87e3;">.magnitude</span>;
}
}
}
</pre>
<br />
<div style="text-align: justify;">
This behavior will be attached to a prefab.</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgA13bkWcLn-SFSg6Z26YdcQGpUaOg9QBxQVuCv_MYdFkxYrIHQesZAfdqHAdS7Yly2Xv4xuZyTGk0d79l-cSwbrAFzDmdvCTntWTaRGkYTwXyzP5MpcgZBKJhIIQSz5-61SS4TuVD3H7W6/s1600/Test01_Update_02_Prefab.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="213" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgA13bkWcLn-SFSg6Z26YdcQGpUaOg9QBxQVuCv_MYdFkxYrIHQesZAfdqHAdS7Yly2Xv4xuZyTGk0d79l-cSwbrAFzDmdvCTntWTaRGkYTwXyzP5MpcgZBKJhIIQSz5-61SS4TuVD3H7W6/s320/Test01_Update_02_Prefab.png" width="320" /></a></div>
<br />
<div style="text-align: justify;">
And this prefab will be linked to the <i>UpdateTestManager </i>object.</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMJf0rsOCxPlgBv95-j1NoIY5nQLy9Inq7bwjZbUdXAFpMOHMKsnliyBMNO766Nu81GlW_jpFFDwduVNaNR_Rb0JuhHfArjjBw58kBuQWAGVvOYdoLI9Vu4TfSyS_yeUvBIc4_XY651Yy3/s1600/Test01_Update_01_Settings.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="273" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMJf0rsOCxPlgBv95-j1NoIY5nQLy9Inq7bwjZbUdXAFpMOHMKsnliyBMNO766Nu81GlW_jpFFDwduVNaNR_Rb0JuhHfArjjBw58kBuQWAGVvOYdoLI9Vu4TfSyS_yeUvBIc4_XY651Yy3/s320/Test01_Update_01_Settings.png" width="320" /></a></div>
<br />
<pre style="background: #000; color: #f8f8f8;"><span style="color: #e28964;">using</span> UnityEngine;
<span style="color: #e28964;">using</span> System.Collections;
public <span style="color: #99cf50;">class</span> <span style="text-decoration: underline;">UpdateTestManager</span> : MonoBehaviour {
public UpdateTest Prefab;
public int Count = 100;
void Awake(){
Transform current = this.transform;
for(int i=0; i< this.Count; i++){
var obj = (UpdateTest)Instantiate(this.Prefab);
obj.name = string.Format(<span style="color: #65b042;">"{0}_{1:00}"</span>, this.Prefab.name, i);
obj.transform.parent = current;
}
}
}
</pre>
<br />
<div style="text-align: justify;">
When run and the <i>UpdateTest </i>is selected the FPS counter will drop considerably.</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLWk1nUIP1rF3rZO665JqbroKGUwo4V2YNVgjAjX3gI8P-Ww39yzBhZOlL7tpH_H75U0ysKOO0roRZdJ1pjQkYUQCuOYjSpQZwogoa7cLkenAWJCvbcaSh6rZ1umj9R8WMiBnNeE9Fpg1Z/s1600/Test01_Update_03_FPS.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="366" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLWk1nUIP1rF3rZO665JqbroKGUwo4V2YNVgjAjX3gI8P-Ww39yzBhZOlL7tpH_H75U0ysKOO0roRZdJ1pjQkYUQCuOYjSpQZwogoa7cLkenAWJCvbcaSh6rZ1umj9R8WMiBnNeE9Fpg1Z/s640/Test01_Update_03_FPS.png" width="640" /></a></div>
<br />
At the profiler we can identify that <b><i>UpdateTest.Update()</i></b> is being called a thousand times it is taking the 79.7% of the frame execution time. Also take notice of the <b><i>Overhead</i> </b>"method" is shown consuming 15.8% of the frame time.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9BEkQvXQEO6diTjfSaB_jKeyZPXChYKmZhUt9drqukieFQiion_V7NL-1dO8udkK5ROO8lDWNW6Jr5x6AZDjYkYqHOj_T-p3_A5kPNQenn_D-Jw-wjKrOjpthmQAbPUwerua7OFoZEsz7/s1600/Test01_Update_03_Profiler.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="284" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9BEkQvXQEO6diTjfSaB_jKeyZPXChYKmZhUt9drqukieFQiion_V7NL-1dO8udkK5ROO8lDWNW6Jr5x6AZDjYkYqHOj_T-p3_A5kPNQenn_D-Jw-wjKrOjpthmQAbPUwerua7OFoZEsz7/s640/Test01_Update_03_Profiler.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<h3>
Test #2 - Method Invoke</h3>
<div style="text-align: justify;">
In this test the '<i>MethodInvocationTestManager</i>' will invoke the '<i>DoStuff</i>'' method of the 1000 '<i>MethodInvocationTest</i>' objects it created.</div>
<br />
<pre style="background: #000; color: #f8f8f8;"><span style="color: #e28964;">using</span> UnityEngine;
<span style="color: #e28964;">using</span> System.Collections;
public <span style="color: #99cf50;">class</span> <span style="text-decoration: underline;">MethodInvocationTest</span> : MonoBehaviour {
private Vector3 v1;
private Vector3 v2;
private float magnitude;
private Vector3 v3;
void Awake(){
this.magnitude = Random.Range(1f,100f);
}
public <span style="color: #99cf50;">void</span> <span style="color: #89bdff;">DoStuff</span>(){
for(int i=0; i< 10; i++){
this<span style="color: #3e87e3;">.v1</span> = new Vector3(Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f));
this<span style="color: #3e87e3;">.v2</span> = new Vector3(Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f));
this<span style="color: #3e87e3;">.v3</span> = new Vector3(Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f));
Vector3 delta = this<span style="color: #3e87e3;">.v2</span> - this<span style="color: #3e87e3;">.v1</span>;
delta.<span style="color: #dad085;">Normalize</span>();
this<span style="color: #3e87e3;">.v3</span> += delta * this<span style="color: #3e87e3;">.magnitude</span>;
}
}
}
</pre>
<br />
<div style="text-align: justify;">
This behavior will be attached to a prefab.</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1yi03ed50tCdGuxuQs5g7AI3Bo5Iv9MHz16PxvaoGksoNUG3pPhwWXrRX-yVgv1Md9S-bD_Am_IVjsk8uCOItIy4B9De8viPLlpJeVtpWn1ZPvfoHQE6MN4wScZFnnBsTyFFWsS1ARId2/s1600/Test02_MethodInvocation_02_Prefab.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="210" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1yi03ed50tCdGuxuQs5g7AI3Bo5Iv9MHz16PxvaoGksoNUG3pPhwWXrRX-yVgv1Md9S-bD_Am_IVjsk8uCOItIy4B9De8viPLlpJeVtpWn1ZPvfoHQE6MN4wScZFnnBsTyFFWsS1ARId2/s320/Test02_MethodInvocation_02_Prefab.png" width="320" /></a></div>
<br />
<div style="text-align: justify;">
This prefab will be linked to the '<i>MethodInvocationTestManager</i>' object.</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgr6a7VyXJ6enDXi0fku6Zd291Sbgg3Het3BCLi7oDISSH5U-A-hOAEslI8hhXPR2KYRFInOgC-TFnT1EcO4uy3t0OfMSl7UtWHNw4hEDvxhwPwW0TmzSvuEM7c9JISHo7wXFH1drY9syDc/s1600/Test02_MethodInvocation_01_Settings.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="236" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgr6a7VyXJ6enDXi0fku6Zd291Sbgg3Het3BCLi7oDISSH5U-A-hOAEslI8hhXPR2KYRFInOgC-TFnT1EcO4uy3t0OfMSl7UtWHNw4hEDvxhwPwW0TmzSvuEM7c9JISHo7wXFH1drY9syDc/s320/Test02_MethodInvocation_01_Settings.png" width="320" /></a></div>
<br />
<pre style="background: #000; color: #f8f8f8;"><span style="color: #e28964;">using</span> UnityEngine;
<span style="color: #e28964;">using</span> System.Collections;
public <span style="color: #99cf50;">class</span> <span style="text-decoration: underline;">MethodInvocationTestManager</span> : MonoBehaviour {
public MethodInvocationTest Prefab;
public int Count = 1000;
private MethodInvocationTest[] items;
void Awake(){
Transform current = this.transform;
this.items = new MethodInvocationTest[this.Count];
for(int i=0; i< this.Count; i++){
var obj = (MethodInvocationTest)Instantiate(this.Prefab);
obj.name = string.Format(<span style="color: #65b042;">"{0}_{1:00}"</span>, this.Prefab.name, i);
obj.transform.parent = current;
this.items[i] = obj;
}
}
<span style="color: #99cf50;">void</span> <span style="color: #89bdff;">Update</span> () {
for(int i=0; i< items<span style="color: #3e87e3;">.Length</span>; i++)
this<span style="color: #3e87e3;">.items</span>[i].<span style="color: #dad085;">DoStuff</span>();
}
}
</pre>
<br />
<div style="text-align: justify;">
When run and the <i>MethodInvocationTest </i>is selected the FPS counter will drop but will keep slightly above the '<i>UpdateTest'</i>.</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEis9Xa5UrWCdENQFwLwo8dZyGWz1JalXqQrkHwjDKovIfGSyhVd3-6H0qa3FsK_EdtCO0cVR-w8d19Of_xDGpuXysgIhx9yCWmOrF5NGgSXxCVl6ennWsc7Wqx6skRi_hpsD91xMw2iuylv/s1600/Test02_MethodInvocation_03_FPS.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="362" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEis9Xa5UrWCdENQFwLwo8dZyGWz1JalXqQrkHwjDKovIfGSyhVd3-6H0qa3FsK_EdtCO0cVR-w8d19Of_xDGpuXysgIhx9yCWmOrF5NGgSXxCVl6ennWsc7Wqx6skRi_hpsD91xMw2iuylv/s640/Test02_MethodInvocation_03_FPS.png" width="640" /></a></div>
<br />
<div style="text-align: justify;">
The profiler will show only one call to '<b>MethodInvocationTestManager.Update()</b>' method taking most of the frame execution time. But the <b>Overhead</b> "method" will only consume 1.6% of the frame execution time. This implementation reduces the <b>Overhead </b>execution time by 90% of the '<i>UpdateTest</i>'.</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_YWucunEhyphenhyphenbjdB0AA3SMgaETSErcTevnHGjBsyAmvWtIrdD684ErdZ9sFlFT9LJcOXFbs4gkTCcq7v9kW2all5VXPHS4PwPEDMNCCfPiebsetgGMMIJFftd2CE6CfHOtLQi6oqSvdLhtQ/s1600/Test02_MethodInvocation_04_Profiler.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="282" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_YWucunEhyphenhyphenbjdB0AA3SMgaETSErcTevnHGjBsyAmvWtIrdD684ErdZ9sFlFT9LJcOXFbs4gkTCcq7v9kW2all5VXPHS4PwPEDMNCCfPiebsetgGMMIJFftd2CE6CfHOtLQi6oqSvdLhtQ/s640/Test02_MethodInvocation_04_Profiler.png" width="640" /></a></div>
<br />
<h3>
Test #3 - Managed </h3>
<div style="text-align: justify;">
In this test the, in the spirit of trying to remove the overhead of invoking a method within the update loop, the '<i>ManagedTestManager</i>' will actually execute the algorithm and will use the '<i>ManagedTest</i>' objects only as data repositories.</div>
<br />
<pre style="background: #000; color: #f8f8f8;"><span style="color: #e28964;">using</span> UnityEngine;
<span style="color: #e28964;">using</span> System.Collections;
public <span style="color: #99cf50;">class</span> <span style="text-decoration: underline;">ManagedTestManager</span> : MonoBehaviour {
public ManagedTest Prefab;
public int Count = 1000;
private ManagedTest[] items;
void Awake(){
Transform current = this.transform;
this.items = new ManagedTest[this.Count];
for(int i=0; i< this.Count; i++){
var obj = (ManagedTest)Instantiate(this.Prefab);
obj.name = string.Format(<span style="color: #65b042;">"{0}_{1:00}"</span>, this.Prefab.name, i);
obj.transform.parent = current;
this.items[i] = obj;
}
}
<span style="color: #99cf50;">void</span> <span style="color: #89bdff;">Update</span>(){
ManagedTest obj = null;
int x = 0;
for(int i=0; i< items<span style="color: #3e87e3;">.Length</span>; i++){
obj = this<span style="color: #3e87e3;">.items</span>[i];
for(x=0; x< 10; x++){
obj<span style="color: #3e87e3;">.v1</span> = new Vector3(Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f));
obj<span style="color: #3e87e3;">.v2</span> = new Vector3(Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f));
obj<span style="color: #3e87e3;">.v3</span> = new Vector3(Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f),Random.<span style="color: #dad085;">Range</span>(1f,100f));
Vector3 delta = obj<span style="color: #3e87e3;">.v2</span> - obj<span style="color: #3e87e3;">.v1</span>;
delta.<span style="color: #dad085;">Normalize</span>();
obj<span style="color: #3e87e3;">.v3</span> += delta * obj<span style="color: #3e87e3;">.magnitude</span>;
}
}
}
}
</pre>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2PSYAScRsO1e7YEZA8OOZ_6yeb9aMOJqFl4J2Br2whcb4nBWrByZm9mH3FBsMRBhMbcKKJo4C5B9Yb2KOR2gh54WdAbaC8SVioUMATpLs2-VBjMOFkGUdLHSDvUanq3jh2KaIDPs0V_Gi/s1600/Test03_Managed_01_Settings.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2PSYAScRsO1e7YEZA8OOZ_6yeb9aMOJqFl4J2Br2whcb4nBWrByZm9mH3FBsMRBhMbcKKJo4C5B9Yb2KOR2gh54WdAbaC8SVioUMATpLs2-VBjMOFkGUdLHSDvUanq3jh2KaIDPs0V_Gi/s320/Test03_Managed_01_Settings.png" width="320" /></a></div>
<br />
<br />
<div style="text-align: justify;">
When run the FPS counter shows numbers similar to the '<i>MethodInvocationTest</i>' thus not showing a real improvement over the previous method.</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisxSmeGofOgcDpYNoot8DslkHKFB36-Z4dioHTGlZTAkE7qwl0_csRKybKLvKzsljbbbui-i3F8WFqzRwpC2HIg9TF19_JkmuNpO2sQZjpKzYcJDLv_hzRnOlEPglFCJdNCBTdnQp7G8kz/s1600/Test03_Managed_03_FPS.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisxSmeGofOgcDpYNoot8DslkHKFB36-Z4dioHTGlZTAkE7qwl0_csRKybKLvKzsljbbbui-i3F8WFqzRwpC2HIg9TF19_JkmuNpO2sQZjpKzYcJDLv_hzRnOlEPglFCJdNCBTdnQp7G8kz/s640/Test03_Managed_03_FPS.png" width="640" /></a></div>
<br />
<div style="text-align: justify;">
The profiler will still show the <b>Overhead </b>"method" at a 1.5% of the frame execution time.</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTBDTSDIVNxYiQG9PUngIGEsz_jwjHrubqxhepgmXMRWjEDkJNYW8qZXW8BO9bk8yawX__b44ok7YtrPPZg0PqPZIU2ytL7s_iovsCQ45Anl6DKGuh1N9sI9uFqHtjhYERFPKQBZiV2i0A/s1600/Test03_Managed_04_Profiler.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="282" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTBDTSDIVNxYiQG9PUngIGEsz_jwjHrubqxhepgmXMRWjEDkJNYW8qZXW8BO9bk8yawX__b44ok7YtrPPZg0PqPZIU2ytL7s_iovsCQ45Anl6DKGuh1N9sI9uFqHtjhYERFPKQBZiV2i0A/s640/Test03_Managed_04_Profiler.png" width="640" /></a></div>
<br />
<h3>
Test #4 - MethodInvoke from a Coroutine </h3>
<div style="text-align: justify;">
In this test the '<i>CoroutineMethodInvocationManager</i>' will create a <a href="http://docs.unity3d.com/ScriptReference/Coroutine.html" target="_blank">Coroutine </a>when enabled and will loop over the 1000 '<i>MethodInvocationTest</i>' it created each frame, The objective here is to measure if by moving the code out of the "<i>Update</i>" method altogether improves performance.</div>
<br />
<pre style="background: #000; color: #f8f8f8;">private IEnumerator <span style="color: #89bdff;">doStuff</span> () {
while(Application<span style="color: #3e87e3;">.isPlaying</span>){
yield return null;
for(int i=0; i< items<span style="color: #3e87e3;">.Length</span>; i++)
this<span style="color: #3e87e3;">.items</span>[i].<span style="color: #dad085;">DoStuff</span>();
}
}
</pre>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhK2TmUex6RSYSafgf4uZXD0peNvv35FoFLWUiPOD_2flgpHLaRsncVP2eglBJOHuAq8FyFtpx1ZXq1kUcRlSx0XQhIY-TWConI2AMC2sAk7ECXO0uHcxenXgFeN5tRnyta7yxDFSNGhk8l/s1600/Test04_CoroutineMethodInvocation_01_Settings.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="274" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhK2TmUex6RSYSafgf4uZXD0peNvv35FoFLWUiPOD_2flgpHLaRsncVP2eglBJOHuAq8FyFtpx1ZXq1kUcRlSx0XQhIY-TWConI2AMC2sAk7ECXO0uHcxenXgFeN5tRnyta7yxDFSNGhk8l/s320/Test04_CoroutineMethodInvocation_01_Settings.png" width="320" /></a></div>
<br />
<div style="text-align: justify;">
When run the FPS counter shows numbers similar to the '<i>MethodInvocationTest</i>' and '<i>ManagedTest</i>' thus not showing a real improvement over the previous methods.</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdieAA_ducATAo4poWqZTIdHwuxhOkpdHDc77wN9Kk22AbhVvtJoFjEXfB0oZI-nicBnzklx8Q-G7506AEmYay9fsORYS4eDTzXWaWrdoIjk0POd6Nx6TM9dTzNGBmVIBAJGhniQ4-i1ow/s1600/Test04_CoroutineMethodInvocation_03_FPS.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="361" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdieAA_ducATAo4poWqZTIdHwuxhOkpdHDc77wN9Kk22AbhVvtJoFjEXfB0oZI-nicBnzklx8Q-G7506AEmYay9fsORYS4eDTzXWaWrdoIjk0POd6Nx6TM9dTzNGBmVIBAJGhniQ4-i1ow/s640/Test04_CoroutineMethodInvocation_03_FPS.png" width="640" /></a></div>
<br />
<div style="text-align: justify;">
In the profiler the mostly the same numbers can be observed, but it shows an increment over the <b>Overhead </b>"method" indicating that it is more expensive to have a every-frame-yielding coroutine that having only one '<i>Update</i>' method call.</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguCjoZhhnnmVpsRvWRF8uwCJg0fJYXnkSz8vKKO0k_BmOqaMKq91j2s9BYdnMuRDXHoY0xjyQbhyphenhypheniV0PtWQd5Z14BQS-drlYd7-eOpooZWuO0G1v5L5anBsMu_vlo0Zko_MaSkc624iCka/s1600/Test04_CoroutineMethodInvocation_04_Profiler.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="284" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguCjoZhhnnmVpsRvWRF8uwCJg0fJYXnkSz8vKKO0k_BmOqaMKq91j2s9BYdnMuRDXHoY0xjyQbhyphenhypheniV0PtWQd5Z14BQS-drlYd7-eOpooZWuO0G1v5L5anBsMu_vlo0Zko_MaSkc624iCka/s640/Test04_CoroutineMethodInvocation_04_Profiler.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<h3>
Test #5 - MethodInvoke from a Coroutine with Batching</h3>
<div style="text-align: justify;">
Basically this is the same test as the <b>Test#4</b>, but having a Coroutine object already created give us more flexibility and opportunities to enhance the overall performance.</div>
<div style="text-align: justify;">
We will doing this by processing the entire 1000 objects in batches of 500.</div>
<br />
<pre style="background: #000; color: #f8f8f8;"> private IEnumerator <span style="color: #89bdff;">doStuffWithDelay</span>(){
while(Application<span style="color: #3e87e3;">.isPlaying</span>){
yield return null;
for(int i=0; i< items<span style="color: #3e87e3;">.Length</span>; i++){
this<span style="color: #3e87e3;">.items</span>[i].<span style="color: #dad085;">DoStuff</span>();
if(i >0 && i % this<span style="color: #3e87e3;">.BatchSize</span> == 0)
yield return null;
}
}
}
</pre>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVMEEes3rJcRIKSmTSafrzl_JerWezMtCP4DQtWM4k7vqJxMyff_P4xQcWwwtqMIsebslump9pMehTgax28v_tcWIx6CZctgqYWnHHba61czHpcsnd_UbFirsFpgEcr6KK-rdpPWGw8fSJ/s1600/Test05_CoroutineMethodInvocationDelay_01_Settings.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="268" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVMEEes3rJcRIKSmTSafrzl_JerWezMtCP4DQtWM4k7vqJxMyff_P4xQcWwwtqMIsebslump9pMehTgax28v_tcWIx6CZctgqYWnHHba61czHpcsnd_UbFirsFpgEcr6KK-rdpPWGw8fSJ/s320/Test05_CoroutineMethodInvocationDelay_01_Settings.png" width="320" /></a></div>
<br />
<div style="text-align: justify;">
When run we can observe an amazing increase in the FPS counter.</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggLmdMG8-j7SxZXbMHo8g6WE6nyWt6woNLWAr44g-swa_xmLGyaRlDZi5_NXq2_Br_VpPPc2XN70md3B_o7cxEqAMoCSk6hmdDxv0zq85T6xjIZLtUGjUIcuvcLsp6iY5QYFmqDQmPqlBK/s1600/Test05_CoroutineMethodInvocationDelay_03_FPS.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="364" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggLmdMG8-j7SxZXbMHo8g6WE6nyWt6woNLWAr44g-swa_xmLGyaRlDZi5_NXq2_Br_VpPPc2XN70md3B_o7cxEqAMoCSk6hmdDxv0zq85T6xjIZLtUGjUIcuvcLsp6iY5QYFmqDQmPqlBK/s640/Test05_CoroutineMethodInvocationDelay_03_FPS.png" width="640" /></a></div>
<br />
<div style="text-align: justify;">
The profiler still will show an small increase on the <b>Overhead </b>"method" but the gains in FPS surely are worth of it.</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-GFiSq_Y-i3mhz-dE_vPjBG7eCnXGUgiKnk8JsBE84B_fbOHjrKSejQf3bTuBTBZAT5_MfQjfDKhmvwIh4q_sGHPnAs_TOGoFrzrOd0TRBATk9EHEF9MOfFJHKGivorCpZURjlsEuLiS1/s1600/Test05_CoroutineMethodInvocationDelay_04_Profiler.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="280" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-GFiSq_Y-i3mhz-dE_vPjBG7eCnXGUgiKnk8JsBE84B_fbOHjrKSejQf3bTuBTBZAT5_MfQjfDKhmvwIh4q_sGHPnAs_TOGoFrzrOd0TRBATk9EHEF9MOfFJHKGivorCpZURjlsEuLiS1/s640/Test05_CoroutineMethodInvocationDelay_04_Profiler.png" width="640" /></a></div>
<h2 id="conclusion">
Conclusion</h2>
<div>
<div style="text-align: justify;">
For <b>collection of objects</b> like enemies, bullets, explosions, drops, props, etc. It is highly recommended to <b><span style="color: #e06666;">avoid using the '<i>Update</i>' method</span> </b>on behaviors attached on this kind of objects. </div>
</div>
<div>
<div style="text-align: justify;">
<br /></div>
</div>
<div>
<div style="text-align: justify;">
Instead this behaviors should subscribe themselves to a <b>manager</b> that will handle the '<i>Update</i>' execution of the desired <b>behavior methods</b>. (<b>Test #2</b>)</div>
</div>
<div>
<div style="text-align: justify;">
<br /></div>
</div>
<div>
<div style="text-align: justify;">
For <b>non sensible objects</b> like props, particles, standard npcs and such it is recommended to <b>batch </b>the <b>execution </b>of the <b>behavior methods.</b> Running at a lesser update rate that the rest of the game engine but allowing more sensible code to be run in that time, improving the overall FPS.</div>
</div>
<div>
<div style="text-align: justify;">
This batching can be fixed, item count based, time based or any reasonable combination. keeping in mind that using coroutines will increase the overhead a little. (<b>Test #5</b>)</div>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Please feel free to <a href="https://www.dropbox.com/s/5inm1pxbdl5uby3/PerformanceTest.unitypackage?dl=0" target="_blank">download the test project</a>, run your own tests, make changes and provide feedback.</div>
<div style="text-align: justify;">
Leave your findings, questions or doubts in the comments below.</div>
Anonymoushttp://www.blogger.com/profile/07554397703411919488noreply@blogger.com3tag:blogger.com,1999:blog-2213131313891153549.post-46089063480982947152015-11-11T23:14:00.000-06:002015-11-14T20:26:09.462-06:00Optimize Memory Usage and Performance when using Coroutines in Unity3d<h2 style="text-align: justify;">
Introduction</h2>
<div style="text-align: justify;">
Hi!</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
I am Alejandro Santiago, co-founder of Tochas Studios. Main, and only programmer, in the team.</div>
<div style="text-align: justify;">
It took us three years to make SodaCity, our latest and biggest project so far, during that time we missed a few things, broke some others, overall learned a few tricks here and there. In this blog I will try to elaborate on those findings so anyone that stumbles upon this posts can learn a few things from us too and avoid the downsides.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
As I am the 'tech' guy in the team most of my posts will cover technical topics.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
In this first post I will address a small trick I came to find regarding the use of coroutines in Unity3d.</div>
<div style="text-align: justify;">
<br /></div>
<h2 style="text-align: justify;">
Optimize Memory Usage and Performance when using Coroutines in Unity3d</h2>
<div>
<a href="http://tochasstudios.blogspot.mx/2015/11/cache-coroutines.html#conclusion" target="_blank">TL;DR</a></div>
<div>
<br /></div>
<div style="text-align: justify;">
<i>This topic was discussed at Unity forums in the following thread <a href="http://forum.unity3d.com/threads/c-coroutine-waitforseconds-garbage-collection-tip.224878/" target="_blank">C# Coroutine WaitForSeconds Garbage Collection tip</a></i></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
The <a href="https://unity3d.com/learn/tutorials/modules/intermediate/scripting/coroutines" target="_blank">Coroutines</a> in Unity offer great advantages for building complex behaviors and it use can span from a couple of objects to thousands as they can be used for AI agents, fade effects, motion controllers, bullets, particles, UI components, etc.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Guides on how to use coroutines and even nested coroutines are common, easy to find and digest.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<a href="https://unity3d.com/learn/tutorials/modules/intermediate/scripting/coroutines">https://unity3d.com/learn/tutorials/modules/intermediate/scripting/coroutines</a></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
But in all references I came across the same principle was applied.</div>
<div style="text-align: justify;">
<br /></div>
<pre style="background: #000; color: #f8f8f8;">IEnumerator <span style="color: #89bdff;">MyCoroutine</span> (Transform target)
{
while(Vector3.<span style="color: #dad085;">Distance</span>(transform<span style="color: #3e87e3;">.position</span>, target<span style="color: #3e87e3;">.position</span>) > 0.05f)
{
transform<span style="color: #3e87e3;">.position</span> = Vector3.<span style="color: #dad085;">Lerp</span>(transform<span style="color: #3e87e3;">.position</span>, target<span style="color: #3e87e3;">.position</span>, smoothing * Time<span style="color: #3e87e3;">.deltaTime</span>);
yield return null;
}
print("Reached the target.");
yield return new WaitForSeconds(3f);
print("MyCoroutine is now finished.");
}
</pre>
<br />
<div style="text-align: justify;">
Yielding a <b><span style="color: red;">new</span> </b><a href="http://docs.unity3d.com/ScriptReference/YieldInstruction.html" target="_blank">YieldInstruction</a> <b>every time</b> a delay or pause in the coroutine execution is needed.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
As the coroutine can be <b>executed every frame</b> or <b>multiple times per second</b> and the behavior can be attached to <b>multiple objects</b> (ex. Bullets or Enemies) this can potentially cause <b>several </b>even thousand of <b>YieldInstructions </b>to be <b>created </b>each frame.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
This poses a problem with the <a href="https://msdn.microsoft.com/en-us/library/0xy59wtx(v=vs.110).aspx" target="_blank">GC</a>, as we all know the <a href="http://docs.unity3d.com/Manual/UnderstandingAutomaticMemoryManagement.html" target="_blank">GC can cause jitters and heavy frame drops </a>each time it does its thing.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
To avoid the GC problem it is recommended to <a href="https://unity3d.com/learn/tutorials/modules/beginner/live-training-archive/object-pooling" target="_blank">pool objects</a>. with almost every optimization guide I came across they referred to it as <a href="http://docs.unity3d.com/ScriptReference/GameObject.html" target="_blank">GameObject</a> pooling. But I never came across a YieldInstruction pooling.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
As <b>SodaCity</b> code grew in size and complexity each build was using more and more coroutines within more and more behaviors and objects. That had me worried as this could cause problems in the long run, So I decided to try to cache <a href="http://docs.unity3d.com/ScriptReference/YieldInstruction.html" target="_blank">YieldInstruction</a> objects and see what happened.</div>
<div style="text-align: justify;">
<br /></div>
<h3 style="text-align: justify;">
The results are as follows</h3>
<h4 style="text-align: center;">
<b><span style="color: orange; font-size: large;"><br /></span></b></h4>
<h4 style="text-align: center;">
<b><span style="color: orange; font-size: large;">Yes! You can and should pool or cache your YieldInstruction objects.</span></b></h4>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
As all of the yielders provided by Unity do not expose members to allow changing values of already created objects, simply caching the references and reusing them at the instance level wouldn't do the trick (for most cases).</div>
<br />
<pre style="background: #000; color: #f8f8f8;">WaitForSeconds shortWait = <span style="color: #e28964;">new</span> WaitForSeconds(<span style="color: #3387cc;">0.1f</span>);
WaitForSeconds longWait = <span style="color: #e28964;">new</span> WaitForSeconds(<span style="color: #3387cc;">5.0f</span>);
IEnumerator <span style="color: #89bdff;">myEvenAwesomerCoroutine</span>()
{
while (true)
{
if (iNeedToDoStuffFast)
{
doAwesomeStuffReallyFast();
yield return shortWait;
}
else{
dontDoMuch();
yield return longWait;
}
}
}
</pre>
<br />
<div style="text-align: justify;">
The solution I am proposing is to use a generic <a href="https://msdn.microsoft.com/en-us/library/xfhwa508(v=vs.110).aspx" target="_blank">Dictionary</a> within a <b>Yielders </b>static class to allow <b>cache </b>and <b>reuse</b> of <b>yielder objects </b>at the application/game level.</div>
<br />
<pre style="background: #000; color: #f8f8f8;"><span style="color: #e28964;">using</span> UnityEngine;
<span style="color: #e28964;">using</span> System.Collections;
<span style="color: #e28964;">using</span> System.Collections.Generic;
public <span style="color: #99cf50;">static</span> <span style="color: #99cf50;">class</span> <span style="text-decoration: underline;">Yielders</span> {
static Dictionary<float, WaitForSeconds> _timeInterval = new Dictionary<float, WaitForSeconds>(100);
static WaitForEndOfFrame _endOfFrame = new WaitForEndOfFrame();
public static WaitForEndOfFrame EndOfFrame {
get{ return _endOfFrame;}
}
<span style="color: #99cf50;">static</span> WaitForFixedUpdate _fixedUpdate = <span style="color: #e28964;">new</span> WaitForFixedUpdate();
public <span style="color: #99cf50;">static</span> WaitForFixedUpdate FixedUpdate{
get{ return _fixedUpdate; }
}
public <span style="color: #99cf50;">static</span> WaitForSeconds <span style="color: #89bdff;">Get</span>(float seconds){
if(!_timeInterval.<span style="color: #dad085;">ContainsKey</span>(seconds))
_timeInterval.<span style="color: #dad085;">Add</span>(seconds, new WaitForSeconds(seconds));
return _timeInterval[seconds];
}
}
</pre>
<br />
<div style="text-align: justify;">
This can be done because it seems Coroutine objects only use YieldInstructions as an exit condition or objective, and each Coroutine object handle its current state independently of the current YieldInstruction yielding the execution or the GameObject the behavior is attached to.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
This allows to globally <b>pre-cache</b> a single <a href="http://docs.unity3d.com/ScriptReference/WaitForEndOfFrame.html" target="_blank">WaitForEndOfFrame</a> and a single <a href="http://docs.unity3d.com/ScriptReference/WaitForFixedUpdate.html" target="_blank">WaitForFixedUpdate</a> object ready to be used by any coroutine in any object, even simultaneously.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
In the case of <a href="http://docs.unity3d.com/ScriptReference/WaitForSeconds.html" target="_blank">WaitForSeconds</a> we need to use the Dictionary using the float 'waitForSeconds' value as a key. This will allow to reuse a WaitForSeconds for a specific time interval.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
To validate this theory and measure the real gains or losses for each method I made a small test project. It can be <b>downloaded </b>from <a href="https://dl.dropboxusercontent.com/u/72963294/Unity/CoroutineTest.unitypackage" target="_blank">here</a>.<br />
<br />
To see details on the tests performed, continue reading...</div>
<div style="text-align: justify;">
<br />
<a name='more'></a><br /></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<span style="background-color: #f4cccc; color: #cc0000;"><b>These tests were made with Unity 4.3</b></span></div>
<div style="text-align: justify;">
<span style="background-color: #f4cccc; color: #cc0000;"><b><br /></b></span></div>
<h3 style="text-align: justify;">
The Setup</h3>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1EEoAkKhGxa82fsCh6EZFepExQnZAGBbHhta5WP-HTllU2p0lssUnBWxPK6f92RgTKQz35tMJFCd6DJjcBBllWRo13NpSjNbrA8c8mXNK51Irk0b7WElBjjjPl8K0Ir1EipSclwb4MSR3/s1600/01_Setup.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="288" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1EEoAkKhGxa82fsCh6EZFepExQnZAGBbHhta5WP-HTllU2p0lssUnBWxPK6f92RgTKQz35tMJFCd6DJjcBBllWRo13NpSjNbrA8c8mXNK51Irk0b7WElBjjjPl8K0Ir1EipSclwb4MSR3/s640/01_Setup.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both;">
The scene is simple, it has a camera and <b>12 spawners,</b> this will instantiate <b>prefabs</b> that use <b>Coroutines </b>to increase the load and catch alloc and cpu spikes while profiling.</div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
Each Spawner has a <b>CoroutineUser prefab</b> and will create <b>100 of them</b>. (Totaling in 1200 CoroutineUser objects)</div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwK3_cw01NuJ-JnlhEI5LLTk3wfahO9hdqgAIbXGcD-cDmXBdlwIOpIUObyrNgDIveo6r7HRqepcAa40XiVbAQriMloCCbqBM_yPRsfQgUQgdMYP92seIV91Vx8nTBh7BrZKwHNB2f4Qls/s1600/02_Spawners.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="267" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwK3_cw01NuJ-JnlhEI5LLTk3wfahO9hdqgAIbXGcD-cDmXBdlwIOpIUObyrNgDIveo6r7HRqepcAa40XiVbAQriMloCCbqBM_yPRsfQgUQgdMYP92seIV91Vx8nTBh7BrZKwHNB2f4Qls/s320/02_Spawners.png" width="320" /></a></div>
<br />
<div style="text-align: justify;">
To create a few dynamism within the test there are <b>three prefabs.</b></div>
<div style="text-align: justify;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhaTCy2L9CPzYoOSauxGDj0lOqYmruVheuJELrRARRrAIzXzBAeSmalqEm6iI1jXbGzEvtCUazHhhojm6peCo58Sv5lUbYVuTFqIqDzg09Mv82t1qRZ7mQuqK9XMKLtYsxdT2UeH2EKvR5/s1600/03_CoroutineUser1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhaTCy2L9CPzYoOSauxGDj0lOqYmruVheuJELrRARRrAIzXzBAeSmalqEm6iI1jXbGzEvtCUazHhhojm6peCo58Sv5lUbYVuTFqIqDzg09Mv82t1qRZ7mQuqK9XMKLtYsxdT2UeH2EKvR5/s320/03_CoroutineUser1.png" width="267" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOs9d7ph2e59_wh5Ilw99KrL-qNrhiUKpAVcTPXTddLcVyK_7gh-WqCbqREFIgkCG_LRgZcmbDHigvZZ29-e3h_Yyx-yXu95kaAsLnvffP7OwEA-FgbDCjeSL3RaNCx72Hzh5P_n1hCf93/s1600/03_CoroutineUser2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOs9d7ph2e59_wh5Ilw99KrL-qNrhiUKpAVcTPXTddLcVyK_7gh-WqCbqREFIgkCG_LRgZcmbDHigvZZ29-e3h_Yyx-yXu95kaAsLnvffP7OwEA-FgbDCjeSL3RaNCx72Hzh5P_n1hCf93/s320/03_CoroutineUser2.png" width="280" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOxns_mY_woIWvAPshOKOxThBr3k_yqEDZUVP1dbFc5qLrCj9IgwHQVsQFg1Ed6s-HqNVV3RzyEseb2Al_-vDiskbecUDKUYIgIBA8PULRmmkQj_FR8WBklapPZWa9HqhLOEo6YecdlaEz/s1600/03_CoroutineUser3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOxns_mY_woIWvAPshOKOxThBr3k_yqEDZUVP1dbFc5qLrCj9IgwHQVsQFg1Ed6s-HqNVV3RzyEseb2Al_-vDiskbecUDKUYIgIBA8PULRmmkQj_FR8WBklapPZWa9HqhLOEo6YecdlaEz/s320/03_CoroutineUser3.png" width="278" /></a></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Each CoroutineUser has a sprite component attached and will change its color every "Delay" seconds.</div>
<br />
<pre style="background: #000; color: #f8f8f8;"><span style="color: #aeaeae; font-style: italic;">//#define USE_YIELDERS</span>
<span style="color: #aeaeae; font-style: italic;">//#define USE_WAITER</span>
<span style="color: #e28964;">using</span> UnityEngine;
<span style="color: #e28964;">using</span> System.Collections;
public <span style="color: #99cf50;">class</span> <span style="text-decoration: underline;">CoroutineUser</span> : MonoBehaviour {
private SpriteRenderer Renderer;
public float offset = 0.0f;
public float delay = 5.0f;
int idx = 0;
private Color[] Colors = new Color[]{ Color.white, Color.red, Color.green, Color.blue};
public Transform CurrentTransform {get; private set;}
<span style="color: #99cf50;">void</span> <span style="color: #89bdff;">Awake</span>(){
this<span style="color: #3e87e3;">.Renderer</span> = this<span style="color: #3e87e3;">.GetComponent</span><SpriteRenderer>();
this<span style="color: #3e87e3;">.CurrentTransform</span> = this<span style="color: #3e87e3;">.transform</span>;
}
IEnumerator <span style="color: #89bdff;">Start</span>(){
if(this<span style="color: #3e87e3;">.offset</span> > 0.0f)
<span style="color: #8996a8;">#<span style="color: #afc4db;">if</span> USE_YIELDERS && !USE_WAITER</span>
yield return Yielders.<span style="color: #dad085;">Get</span>(this<span style="color: #3e87e3;">.offset</span>);
<span style="color: #8996a8;">#<span style="color: #afc4db;">endif</span></span>
<span style="color: #8996a8;">#<span style="color: #afc4db;">if</span> !USE_YIELDERS && USE_WAITER</span>
yield return Waiter.<span style="color: #dad085;">Wait</span>(this, this<span style="color: #3e87e3;">.offset</span>);
<span style="color: #8996a8;">#<span style="color: #afc4db;">endif</span></span>
<span style="color: #8996a8;">#<span style="color: #afc4db;">if</span> !USE_YIELDERS && !USE_WAITER</span>
yield return new WaitForSeconds(this<span style="color: #3e87e3;">.offset</span>);
<span style="color: #8996a8;">#<span style="color: #afc4db;">endif</span></span>
while(Application<span style="color: #3e87e3;">.isPlaying</span>){
<span style="color: #8996a8;">#<span style="color: #afc4db;">if</span> USE_YIELDERS && !USE_WAITER</span>
yield return Yielders.<span style="color: #dad085;">Get</span>(this<span style="color: #3e87e3;">.delay</span>);
<span style="color: #8996a8;">#<span style="color: #afc4db;">endif</span></span>
<span style="color: #8996a8;">#<span style="color: #afc4db;">if</span> !USE_YIELDERS && USE_WAITER</span>
yield return Waiter.<span style="color: #dad085;">Wait</span>(this, this<span style="color: #3e87e3;">.delay</span>);
<span style="color: #8996a8;">#<span style="color: #afc4db;">endif</span></span>
<span style="color: #8996a8;">#<span style="color: #afc4db;">if</span> !USE_YIELDERS && !USE_WAITER</span>
yield return new WaitForSeconds(this<span style="color: #3e87e3;">.delay</span>);
<span style="color: #8996a8;">#<span style="color: #afc4db;">endif</span></span>
this<span style="color: #3e87e3;">.idx</span> = (this<span style="color: #3e87e3;">.idx</span> + 1) % this<span style="color: #3e87e3;">.Colors</span><span style="color: #3e87e3;">.Length</span>;
this<span style="color: #3e87e3;">.Renderer</span><span style="color: #3e87e3;">.color</span> = this<span style="color: #3e87e3;">.Colors</span>[this<span style="color: #3e87e3;">.idx</span>];
}
}
}
</pre>
<br />
<div style="text-align: justify;">
With the use of <a href="https://msdn.microsoft.com/en-us/library/ed8yd1ha.aspx" target="_blank">preprocessor symbols</a> we can change the test to be run, thus only changing the lines corresponding with the "Wait" part of the process.</div>
<h3 style="text-align: justify;">
The Test</h3>
<div style="text-align: justify;">
When run, the screen will be "mostly" covered by the same sprite, starting all with white tint and every "x" seconds a row will switch colors.</div>
<div style="text-align: justify;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpptrnpPhYSIwyIiOSmYsyL5Sk2NW0TOeOmEabMI3m5rgXUWCx2RqUoMsYVgkXX3cDZYVFgYiUMPYqcAOkTg5QJ4Mr2BJlZt-M1-Sc0lulSOCLh1hLKQUGPnJpgzc4hBMJQJ2Hsvn4SJWd/s1600/04_TestScreen.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="372" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpptrnpPhYSIwyIiOSmYsyL5Sk2NW0TOeOmEabMI3m5rgXUWCx2RqUoMsYVgkXX3cDZYVFgYiUMPYqcAOkTg5QJ4Mr2BJlZt-M1-Sc0lulSOCLh1hLKQUGPnJpgzc4hBMJQJ2Hsvn4SJWd/s640/04_TestScreen.png" width="640" /></a></div>
<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhasKTRXM-vefV43RetjuDc9qaJdaDmDTbDQBQWAWXmYxbJddgjjyJc99Atbvbxz_HsHdAjl59MS8q3ALvnheom4jUDqST9ptlS0DUiicAo_jkFaq2v7VA_mjJ3O65-sHteixklJIardauE/s1600/04_DeepProfile.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="67" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhasKTRXM-vefV43RetjuDc9qaJdaDmDTbDQBQWAWXmYxbJddgjjyJc99Atbvbxz_HsHdAjl59MS8q3ALvnheom4jUDqST9ptlS0DUiicAo_jkFaq2v7VA_mjJ3O65-sHteixklJIardauE/s320/04_DeepProfile.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">For best results I do encourage making a "deep profile"</td></tr>
</tbody></table>
<div style="text-align: justify;">
<b>Note:</b> I do recommend to <b>disable VSync</b> to be able to spot more easily the cpu spikes at the <b>profiler</b>.</div>
<div style="text-align: justify;">
<i>Edit->Project->Quality</i></div>
<div>
<div>
<h3 style="text-align: justify;">
The Results</h3>
</div>
<div>
<h4 style="text-align: justify;">
new WaitForSeconds(this.Delay);</h4>
</div>
<div>
<div style="text-align: justify;">
<b>Creating new Yielders will cause allocs</b> on the frame the instruction was created, and it will be disposed right after the yield statement has finished thus <b>generating GC calls </b>every once in a while.</div>
</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbrTRTBkA3P3eQq39pBHLuTQh5Djui5IfAFeZpbRBo_OBdUwf3z19Z2hyNZHDYBakt2nPZ8dl2xuukDGFlWFIJdkOEhbvDlss_dVp60gg7PTeCwqeUWfid5J4deZHGBaHpmOSdsS3MjyTw/s1600/05_Test1_newWaitForSeconds.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="188" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbrTRTBkA3P3eQq39pBHLuTQh5Djui5IfAFeZpbRBo_OBdUwf3z19Z2hyNZHDYBakt2nPZ8dl2xuukDGFlWFIJdkOEhbvDlss_dVp60gg7PTeCwqeUWfid5J4deZHGBaHpmOSdsS3MjyTw/s640/05_Test1_newWaitForSeconds.png" width="640" /></a></div>
<div>
<br /></div>
<div>
<div>
<div style="text-align: justify;">
As can be seen in the profiler window, the <b>1200 calls</b> to CoroutineUser.Start.Iterator.MoveNext() will cause <b>allocs of 14.1KB</b> and 1200 WaitForSeconds..ctor() can be seen within the stack hierarchy.</div>
</div>
<div>
<h4 style="text-align: justify;">
Cache YieldInstruction objects</h4>
</div>
<div>
<div style="text-align: justify;">
<b>Caching the YieldInstructions within a static dictionary will cause allocs the first frame the Yielder is needed</b> and subsequently will reuse those objects.</div>
</div>
<div>
<div style="text-align: justify;">
Having a strong reference to those objects will <b>keep them away from the GC</b> and can be purged at will ensuring <b>GC calls to be made in safe zones</b>, like before/after Application.LoadLevelAsync for example.</div>
</div>
</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_pFwOL4JW-XSEtSwIQEGtRzSPAHD98XYPearY_HHGAe4rLT1r4VdOy-QtJHhXK3H4avxjQi-8-RTK6Nn-xzCRyHhyphenhyphenCqIsGYP6GDGJczujY5c3NQZ-xArVV4iMPlkchXfXLxHCX6-kMfNl/s1600/05_Test2_CachedYielders.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="188" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_pFwOL4JW-XSEtSwIQEGtRzSPAHD98XYPearY_HHGAe4rLT1r4VdOy-QtJHhXK3H4avxjQi-8-RTK6Nn-xzCRyHhyphenhyphenCqIsGYP6GDGJczujY5c3NQZ-xArVV4iMPlkchXfXLxHCX6-kMfNl/s640/05_Test2_CachedYielders.png" width="640" /></a></div>
<div>
<br /></div>
<div>
<div>
<div style="text-align: justify;">
In the profiler window can be seen the <b>1200 calls</b> to CoroutineUser.Start.Iterator.MoveNext() and the <b>0KB allocs</b>.</div>
</div>
<div>
<div style="text-align: justify;">
also no .ctor() can be seen within the stack hierarchy.</div>
</div>
<div>
<h4 style="text-align: justify;">
Nested StartCoroutine</h4>
</div>
<div>
<div style="text-align: justify;">
<b>Starting a new "nested" Coroutine within a Coroutine </b>(Coroutineception!) as powerful as it can be is a double edged sword and must be used carefully. <b>This will create a new Coroutine object and all the overhead Unity needs to do to handle a new Coroutine</b>.</div>
</div>
</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjL7xLuo3S8GmmYWLElBRqMcrSRlebB6EiHgsR7M-SxeKS3-EPv0HnTePECq6AX644Dgmc_6L7L0qJ3X9iNDP9kuz84O-grXCw7uWQ4F4HTrNbbTur4zTTRusfKgdL-hkb5BJRkuplarF4E/s1600/05_Test3_NestedStartCoroutine.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="188" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjL7xLuo3S8GmmYWLElBRqMcrSRlebB6EiHgsR7M-SxeKS3-EPv0HnTePECq6AX644Dgmc_6L7L0qJ3X9iNDP9kuz84O-grXCw7uWQ4F4HTrNbbTur4zTTRusfKgdL-hkb5BJRkuplarF4E/s640/05_Test3_NestedStartCoroutine.png" width="640" /></a></div>
<div>
<br /></div>
<div>
<div>
<div style="text-align: justify;">
As can be seen at this profiler window, each frame the <b>1200 CoroutineUser objects </b>need to wait will cause <b>allocs of 57.4KB</b>, Iterator<WaitRoutine>..ctor() can be seen within the stack hierarchy.</div>
</div>
<div>
due to <b>the size of the allocs this method will cause GC calls to be more often</b>.<br />
<br /></div>
<div>
<h2 style="text-align: justify;" id="conclusion">
Conclusion</h2>
</div>
<div>
<div style="text-align: justify;">
The standard new WaitForSeconds() method it is not so bad, causing about 0.01175KB of garbage per YieldInstruction.</div>
</div>
<div>
<div style="text-align: justify;">
<br /></div>
</div>
<div>
<div style="text-align: justify;">
<b>Caching YieldInstructions </b>as exposed at the beginning of the post <b>can help reduce the GC load.</b></div>
<div style="text-align: justify;">
<b><br /></b></div>
</div>
</div>
<div>
<div>
<div style="text-align: justify;">
In "not-so-"extreme environments saving a few KB here and there can be helpful, so caching them globally is appropriate as multiple objects can reuse the same YieldInstruction. Helpful for custom particles and particle engines, spawned objects like bullets, explosions, enemies, making tweens. </div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<b>Proper handling of the bucket used to cache them is required.</b></div>
</div>
<div>
<div style="text-align: justify;">
In this case a dictionary do not pose an issue as the key is a float and do not cause issues mostly caused by string keys.</div>
</div>
<div>
<div style="text-align: justify;">
The dictionary size can be constrained and optimized further like, only allowing a certain float precision as the key, self purging stale (unused) objects and so on</div>
</div>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
I applied this technique to all the Coroutines used by SodaCity code and it heavily improve the game's performance, Allowing us to have <b>more objects on screen </b>and be more <b>bold </b>in the <b>refresh rate </b>of some <b>game sub-systems</b> like AI, depth sorting and custom game particles.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Please feel free to <a href="https://dl.dropboxusercontent.com/u/72963294/Unity/CoroutineTest.unitypackage" target="_blank">download the test project</a>, run your own tests, make changes and provide feedback.</div>
<div style="text-align: justify;">
Leave your findings, questions or doubts in the comments below.</div>
<br /></div>
Anonymoushttp://www.blogger.com/profile/07554397703411919488noreply@blogger.com0