Improving the visuals

Improving the visuals

We hope you had a Merry Christmas and we wish you a Happy New Year! Santa came to us with lots of new goodies to improve the visuals of our game, so here is a quick recap of some of the things we've been working on during our time of silence these past few weeks.

Sky is the limit!
The default Unreal sky was nice and all, but we wanted something better, so we got ourselves a new sky with much better lighting and a perfect mood for our environment. If only we could get a new sky in real life to replace the cloudy one we've been having these past few days!

Thou shall float no more!
Something we've always had in plan but didn't have the chance to get to was to add distant surroundings, like hills and mountains, so our little game world wouldn't feel like it's floating in the air anymore. As a Christmas treat for us, we got ourselves some time to finally polish this part of the visuals and we love the results - here's hoping you will too!

It's what's on the inside that counts!
We know we haven't yet released screenshots of the cabin's interior, but now that we've improved it and added new details, we're happy to finally let you have a look! Below you can see the upstairs bedroom, so inviting you'd almost want to take a nap!

But it sure helps when the exterior is also beautiful!
And the biggest improvement we've made was on the cabin exterior. Again, something we always had in plan and now we've finally got around to doing it. Looking at it now makes us wonder how we lasted so long with the previous version!

These are a few of the things we've been working on up to this year's end. We still have a bunch of things to do, each of them making the game better and pretier. While we do get tingly feelings whenever we think we're getting closer and closer to the release, we still want to stay grounded and make sure we make a game we can be fully proud of!

Sorry for the upbeat tone in this post, it's just that we still have some Christmas joy in us and we're really happy with how the game is coming along! Thank you for reading and we wish you a Happy 2017!

Release details

Release details

The date
When we announced Life Pictures and several times since then we stated we're pushing for a release in the later part of this year. Unfortunately, the time has come to reassess the amount of work and polish that we want to put into the game and the conclusion is that, as much as we would've wanted to, we cannot release the game this year. Not with the quality we want to put in it. And to release it with a lower quality is not even a possibility for us.

So, when are you going to be able to play Life Pictures? In the first quarter of 2017. We know, it's not a very narrow time frame, but we'll do our best to finish it as fast as possible without compromising quality.

Remaining work
With having finished to write the story, we now have to start the recording sessions. The journal has more than 6000 words and our plan is to have it all fully narrated, leading to about 40 minutes of narration. It will take some time and some takes to get them all right.

We also have some few mechanics to implement, like piecing together the pieces of papers you collect from the memory glimpses and unlocking entries in the journal through taking pictures. And on the art side we have some assets and effects to add in the game, as well as going over everything again for a final polish pass.

And, after the above are finished, we'll go into full bug solving mode, preparing the game for the final release and making sure the platform specifics work correctly, including achievements, cloud saving, controller support and Steam trading cards.

The price
In the end we'll have a 2-3 hours long game that takes place in a cabin and the forest surrounding it and that explores the memories of an old man related to the 3 women he loved in his life. No monsters, no aliens, no conspiracies, just a simple and beautiful life story about love and meaning. And, of course, you'll be able to take pictures of everything you can see using your in-game camera.

What will this experience cost you? We don't want to overprice our game, so it will definitely be under 10$. By how much exactly we'll let you know closer to the release date, when we'll also detail the release discount and its duration and any sales we'll plan for the first year of the game's life, so you'll know exactly when it's best for you to buy it.

In closing, since we're fully focusing on finishing the game in the coming months and since a lot of the details about the game have already been disclosed, we don't know how often we'll release updates on the game. We'll still post new screenshots and articles as new info becomes relevant, but we don't know if we'll be able to do that every week until release. Our priority from now on is finishing the game as we envisioned it and getting it in your hands as fast as we can!

An old man's journal

An old man's journal

In this week's update on the game, we're showing you a sample of the journal in Life Pictures. The journal is where our main character writes the things that he remembers. When starting the journey, his vague memories translate only to sketches in his journal, but as you take pictures and uncover more about his life, he will remember things more accurately. Helping him fill out all the pages in his journal is the ultimate goal of Life Pictures. We hope you enjoy this small sample below, representing the first things the old man remembers as you begin playing! Thank you for reading!

    I was raised by my grandparents. They lived in a place that would now seem out of this world. A place where everything was green, where grass and trees were the norm, not some oasis of nature in a city suffocated by steel and glass. I loved the forest, I loved exploring, running around, being as free as I would ever be in life. I love to remember those days. I wish that when I die, if there is anything beyond, I'd go to a place where I’m surrounded only by nature, trees as far as the eyes can see. I wouldn’t miss the city, I never felt like I belonged. In my heart, I was always the kid running around in the forest.
    When I was old enough to go to school, I had to leave my grandparents and move to the city with my father. It took me a long time to get used to the people, the noise, the air. I longed for the next visit to my grandparents, but when they passed away, I had no place to escape anymore. The only good thing my father did for me was to buy that cabin in the woods. I used to go there as often as I could, it was the only place where I could really breathe. For a long time, it was my real home.

The road to getting Greenlit

The road to getting Greenlit

Since we've just recently been Greenlit by the community - yay! - today's post will focus on our Greenlight campaign, stats and opinions.

Preparing the campaign
First thing we knew we wanted to have ready before kicking off the campaign was a new trailer featuring more gameplay and more about the girls. We also prepared a game description that tried to both explain our game and to make our readers curious enough to want to try it. And finally we've selected our best screenshots to use on the Steam Greenlight page.

First day
If you followed our progress so far, you may know we have more than a couple of months since we've announced the game and posted regular updates, trying to increase visibility for our game. And it paid off when we launched the Greenlight campaign, getting us 294 yes votes in the first day. While some of those were organic votes through Steam, most were from our twitter and facebook followers and friends. This gave us a decent start and a decent amount of comments, which are very important since a lot of voters tend to check the comments section to see other people's feedback.

First week
In the following days we continued to tweet about our campaign, asking nicely for people's support. We also posted the campaign on the Unreal forums, tried to find people passionate about the genre on reddit and on Steam forums and did our best to get featured on a bunch of Greenlight collections. The numbers continued to add up nicely, we ended the week with 433 votes, but there was clearly a slowdown.  As you may notice, if in the first day we had 1125 unique visitors, by the end of the week we only had an extra 714 visitors. Which is really low and started to worry us. We knew from other developers that Steam visibility is very low after the second or third day, but we didn't expect it to go so low so fast. And the major issue was that our social media didn't seem to attract too many visitors - we'll talk more about the reasons in the conclusions section of this post.

Second week
At the end of the first week, when things started to slow down, the awesome community managers at Unreal came to our help and retweeted our latest trailer and Greenlight campaign. Which did wonders on twitter, with more than 27000 impressions, 121 likes and 51 retweets. Unfortunately, those didn't translate very well to votes, we probably had around 30-40 votes tops coming in from that bump in our visibility. Again, social media didn't attract too many visitors to our page. But, every little bit counts! And to help raise more interest, we kicked off a contest where you have to guess a movie title from the game to get a free Steam key. Which also helped a bit, but not as much as we had hoped. By the end of the second week we had a total of 505 votes, so just an extra 72 votes during a whole week. Despair started to get some serious roots in our hearts, especially considering that we'd kind of used our tricks: Unreal already helped us and our contest was up and running with little results.

Third week
If we thought the second week went bad, we were in for a big surprise in our third week. Well, two big surprises actually, but let's focus on the first one, which was that now we had days with only one or two votes, while days with 5-6 votes were the highlights of the week. To say that things were going poorly is an understatement. And just as the number of votes dropped, so did our morale. As the end of the week was nearing, we only had 20 extra votes. The occasional positive feedback was a nice consolation, but nothing we did seemed to work.

Epic Friday
And then the awesome people at Unreal featured us in their monthly #EpicFriday games reel! Did we already say they're awesome? Good, because we'll mention it as often as we can! Besides making us feel great, we suspect that reel had a big impact in us getting Greenlit just a few days later. Our votes didn't seem to be enough, we weren't high enough in the charts, so Unreal featuring us is the only explanation we can find, besides maybe someone at Steam really liking our game! So, yeah, that was the very happy conclusion to what had become a depressing campaign!

The first thing we want to note, as stated above, is that social media coverage doesn't translate well to votes on Steam Greenlight. That probably has to do with the fact that the Steam login system is very discouraging to use for anyone that isn't sitting in front of their PC already logged in the client. It's a chore to login from the browser or even more so from mobile, considering you have to go through writing your credentials, getting a security code, inputting it and only then getting to the page you requested. In an age where people rarely have the patience to read anything else but the title of an article and usually you only get a like or share for anything you post, having such a difficult login process definitely doesn't help driving people to your Greenlight page. We're not saying we have a solution for this, we're just stating the obvious.

But, of course, this doesn't mean you should stop using social media to raise visibility for your game. Quite the contrary, it's your most valuable tool! But you shouldn't limit yourself to only this. While it didn't have a major impact for us, things like a contest, getting included in Steam collections, starting up discussion on the Steam forums and so on, are very important. And we'll say it again and again, every little bit counts, every vote is valuable! You never know when someone tells his like-minded friends or has a huge audience somewhere and shares your game.

As a piece of advice that we'd wish we heard more often: don't despair! Even if things look bad, even if votes don't seem to add up anymore, just keep doing everything you can, even if it doesn't seem to work. Sooner or later, if the game looks like it could shape up into a decent, polished game, you'll get Greenlit. Hell, even if it looks like an amateur work, you might still get Greenlit! So just keep tweeting, facebooking, steaming, redditing, foruming and whatever other words that don't exist! And interact with people on Steam, reply to comments, start discussions, don't engage negative feedback with negative responses, be humble and remember that people's time is very valuable and limited, so be grateful when they spend it giving you any kind of feedback!

P.S. We still have some Steam keys in our contest, so if you want to play Life Pictures for free when it releases, head on to the Steam discussion and guess a movie!

Why the girls are static

Why the girls are static

Yes, the girls in Life Pictures are static, they don't have animations. And that's a design decision. In this post we'll try to explain why the girls are static as best as we can, hoping that by the end you'll understand and agree that it's the best choice - or at least a decently good one.

Life Pictures is about an old man reminiscing about his life. He doesn't remember things very precisely and the order of events is blurry to him. He knows that he had met 3 women in his life. Who was the first and who was the last? That's up to you, the player, to discover.

To remember his past, our protagonist uses his album with pictures of his life - hence the name of the game. Each picture takes him back to his past, but since everything is a blur in his mind, he doesn't remember many details, neither about places nor people. He knows how these 3 women looked only from the pictures he has. He vaguely remembers the feelings he had for them and the important bits of what they were like as persons: passionate, joyful, loyal.

As the player, through the eyes of our protagonist, you enter through these pictures and see into his memories. Everything seems to take place in the same location, but that is only because the old man can't remember where things have happened and his mind conveniently put everything together in one place - a place that most likely isn't even real, but just a mashup of various memories blended together in this peaceful and serene location.

As you get closer to the stream of water, a memory will materialize in the form of the passionate redhead in his life. He begins to remember. What you see is a still, created in his mind from a picture of her and his vague memories. She is only there to be observed, to be remembered. You cannot talk to her, you cannot interact with her. She isn't alive, she's just a snapshot of the old man's memory of her. If you get close enough, you'll trigger more memories in the old man's mind. Events of his time with her. Happiness. Sadness. Resolution. Each of these events shows a different still of this woman, a memory deeply etched into his mind. Your purpose is to help him remember and to piece together the details.

Just as our protagonist keeps trying to remember the same things over and over, so will you as the player go over the same memories trying to find every detail. You use your camera to take pictures of various objects that cause the old man to remember some detail about that object. You can find memory glimpses and piecing them together will reveal even more about his past. And ultimately, you can help him find peace, by finding out something he believed in and showing him a life made only from the memories that are in tune with that belief.

So, you see, our game, as we stated so many times before, is not about dialogues and interactions, but about immersion, nostalgia, taking it slow, pondering, philosophy. It's about the joy of exploring, inspecting, looking for details and piecing together a story. If the girls in the game were animated, it would create the feeling of interactivity, the desire to talk to them, to influence their actions. If you add an animation, then it becomes quickly repetitive and the more animations you add, the more the need for interactivity and complex behaviors increases. Which is not what this small game is about. The girls are there to serve as visual testimony for our protagonist's memories.

We hope the information in this post helped you get a better idea of what Life Pictures is about and your purpose in the game. It's up to you how deep you dig to uncover everything about this old man's life. An ordinary life, with love and loneliness, suffering and happiness, but a beautiful life nonetheless, as all lives that are truly lived are.

Thank you for reading! And if you haven't already, please vote for us on Steam Greenlight!

Guess the movie

Guess the movie

With our Steam Greenlight campaign in full motion, we thought there's no better time like today to organize a little contest. The prize: free Steam keys for Life Pictures, when it releases, of course! The rules? Very simple! But first a little context.

Our main character loves movies and books. Time loops, memories and time travel are his favorite topics. If there's a dash of romance in there, even better! It's no surprise then that in his house there is a nice collection of movies that he keeps rewatching on his old school VCR. He purposefully put all his favorite movie on video tapes so he can use this relic from his past in combination with an old TV like the one he had when he was a kid. I guess as we grow old we tend to go back more and more to what made us happy when we were young or even children.

So, getting back to our contest, here are the simple rules:
  • Guess one movie from the screenshot above
  • Write your answer on this Steam discussion board, including the movie name and the corresponding videotape label from the screenshot
  • Writing your guess anywhere else will not be taken into account
  • You also need to provide a link to the IMDB movie page
  • If you guess more than one movie, only the first one will be taken into account
  • Whoever guesses a movie first wins a free Steam key for Life Pictures, as long as that person hasn't correctly guessed another movie before
  • For each movie, only the first person to guess it is awarded a free Steam key
  • The Steam key will be sent to the Steam account that wrote the answer on the Steam discussion board, when Life Pictures will be available on Steam
  • Winners will be posted on the Steam discussion board, along with the already guessed movies

Here is an example of how an entry should look like:

Simple? We sure hope so and we can't wait to see your guesses. With Shuffle out of the way, there are 25 movies just waiting for you to guess them and get your free Steam key! So go on to the Steam discussion board and post your answer there!

Steam Greenlight

Steam Greenlight

This is it! Our second time we feel like appearing naked on a stage with the risk of people laughing at us. The first time was when we announced Life Pictures. Thankfully, that time the response was pretty amazing and encouraging, so fingers crossed the same happens this time!

Life Pictures is on Steam Greenlight! Please vote for us and share the word with your friends, your enemies and everybody else in between!
To celebrate the occasion, we released a new trailer, this time focusing on the girls and parts of the gameplay. We hope you enjoy it!

Thank you for reading and please vote for us on Steam Greenlight!

Making the girls - iClone and Unreal equals love

Making the girls - iClone and Unreal equals love

When we started making Life Pictures, one of the biggest unanswered questions for us what were we going to do about the girls. As you may know, character design is difficult, especially if you don't have someone in the team with a lot of experience in character modelling. And considering how vital the girls are in our game, it was important to come up with a good solution. That our small indie team had a small budget to work with was also a factor.

The solution for us was iClone Character Creator, a very user friendly tool for making human models. With it, we could create great looking characters without having to hire a more experienced artist, at a fraction of the cost. While iClone isn't a very well known tool for artists, especially because they prefer using traditional, less automated tools, like 3D Studio Max and Maya, we're very happy with our choice and, more importantly, very proud of the results. We're sure a more experienced eye can find many issues with our models, but in the end, we think indie game development is about making the best game you can within your constraints.

In what follows, we're going to have a look at the process of creating one of the girls in Life Pictures. This isn't a technical dive or a how-to post, we're just showing the stages of design and integration. If you're interested in details about any step, remember we're always around on facebook and twitter, so feel free to ask away.

Starting with the default model in iClone Character Creator, the first step was to modify everything related to the body, until we reached a satisfactory result, a girl that gives the feeling of someone warm, joyful and playful. This step included changing the body proportions and, more importantly, all the facial features. And, of course, we added and modified the hair.

Once we were done with the body, it was time to dress up our girl - after all, it's not that kind of game. We relied on the content iClone Character Creator provides in terms of clothes. Below you can see the progress from the default model to our final model and the clothed version.

At this point we were done with Character Creator and it was time to send our character to iClone, which is the parent tool specialized mainly in animations. Here we're creating the pose for our girl. Since Life Pictures revolves around the snapshots in time of our character's memories, you'll experience the girls as if frozen in time.

As soon as the pose was done, we exported the character to Unreal, using 3DXchange to convert from iClone's proprietary format to fbx. When importing the fbx in Unreal, things didn't look too great. But that's because there was still a lot of work to be done, especially on the materials side. 

We used Unreal's skin shader material, custom hair and eyes materials and a bunch of different clothes materials. The difference from the imported materials was gigantic and lead to the final model you can now see in the game - well, actually, you can only see it in the screenshots for now, but you will be able to see it in the game when it releases.

One last incredibly important step was adjusting the facial expression. The pose we did in iClone only included the body, we preferred playing with the facial morph targets directly in Unreal. And it was the final touch needed so that our girls express a range of emotions like joy, playfulness or sadness. Below you can see the results, from the pose to the imported and the final version of our girl.

And, to get a feel for the details in our character, check out the collage below, showing close-ups of various model features and materials.

That was just a glimpse of the many hours we put in making the girls of Life Pictures. We hope you'll enjoy seeing them in the game! When, you ask? Soon, if we don't burn ourselves out before releasing! One of the reasons we wanted to keep our team small and our costs low was so that we could each invest as much time as we needed to come up with results that we're happy with, in everything we work on. While we're pushing for a 2016 release, it's more important for us to finish the game that we envision!

Thank you for reading!

Audio Design: Crackling bushes, chirping birds

Audio Design: Crackling bushes, chirping birds

In the previous post we talked about our approach for a character footsteps sound system using Unreal Engine. In this post we'll go over how we made the forest bushes make noise when the character is moving through them and how we set up the rest of the environment sounds.

Movement Sound Trigger Component
To detect when the character is touching a foliage mesh we used a static collision shape that doesn't generate collision events but only overlap events with the character capsule shape. This should be pretty cheap for the size of our terrain even if we have a lot of them, especially because we can set the collision to only check overlap events with a Pawn and mark them as static. This sound trigger component should allow us to easily set noise sounds for different foliage meshes in the level with different shapes and also allow us the future possibility of using compound collision shapes to represent more complex geometry if we needed.

So we implemented a component called "BP_MoveSoundTriggerComponent" that would bind to its actor's OnActorBeginOverlap and OnActorEndOverlap events to listen to when a character would enter/exit the collision shape:
Overlap Events
The collision shape is specified on the actor on which this component will be attached to. So the component requires an audio component and a collision shape on the attached actor. 

Let's see an overview of what this component does:
  • it listens to the overlap events to know when it's overlapping with a character - or any other component extending the NavMovementComponent
  • while a character is inside the collision shape it will "track" its movement speed factor to interpolate the owner audio component volume based on the movement speed
  • if the movement speed is above a minimum threshold it plays the assigned looping noise sound, otherwise it fades it out
Zooming in on the "Node_HandleActorBeginOverlap" blueprint node in the screenshot above, we get the reference of the overlapped "NavMovementComponent" from our character and we call our "SetSoundUpdateState" function to enable a Timer that will update the volume based on this character's move speed while inside the collision shape:
The "SetSoundUpdateState" function is for encapsulating the logic required to start/stop the Unreal Timer we used as a lazy update function:
SetSoundUpdateState function
You might wonder why we didn't use the "Event Tick" of the component for its core logic. We actually disabled the "Start With Tick Enabled" because all the instances of this component will actually have the native "Tick" function called by the engine every frame, no matter if you triggered an overlap event with its collision shape or not. Running any simple form of logic in the Tick for a high number of instances can quickly become a performance problem. So instead of investing precious CPU time on iterating the Tick for each component we decided to use a "Timer" that can be like an on-demand "Tick". For what this component does we don't need the timer to execute every frame, so we also added a parameter "soundUpdateIntervalSeconds" to tweak the time interval at which this function will be called.

When the looping timer is set it will repeatedly call the function "UpdateMovementSound" which is the core logic of this component. To get a better overview of this function we'll split its blueprint graph in half. The first thing it does it interpolates the volume of its audio component based on the character move speed factor:

UpdateMovementSound - Part 1
The function "GetMoveSpeedFactor" just calculates the speed factor taking into account the maximum speed that the component requires to actually play the noise sound at the maximum volume:
GetMoveSpeedFactor function
You probably noticed the function call "GetGroundMoveSpeed" in the above "GetMoveSpeedFactor" function screenshot. That's a simple utility function we talked about in our previous post.

Then in the second half of the "UpdateMovementSound" function we check if we should start or stop the looping noise sound based on the current move speed of the character. The character must have a moving speed higher than the specified "minMoveSpeedForStartSound" threshold:
UpdateMovementSound - Part 2
The "SetSoundPlayState" is a function to encapsulate the logic for playing or stopping the audio. The sound will be stopped with a fade-out when the character leaves the collision shape of the component. An example of why the fade-out is useful is if you would be running with the character through a bush it would generate a sudden noise at its maximum volume and then quickly fade out as you exited the bush, still hearing the noise just a bit behind you as you move away from it. It gives a nice natural feel as if the bush would calm down its moving branches after it had been suddenly rattled.

The looping update timer will also get stopped, as you can see in the initial screenshot where we initialized the overlap events. The "SetSoundPlayState" function looks something like this:
SetSoundPlayState function

This is the core logic of the "BP_MoveSoundTriggerComponent". A known limitation of this simple approach is that it can handle only one character component triggering the overlap events. But for our current needs it is more than enough. To handle multiple characters triggering the overlap events you can easily extend this implementation and store each character that triggered "OnActorBeginOverlap" in a TMap container, using our Blueprint "TMap" wrapper presented in the previous article. Then, in the update timer logic iterate through all the characters in the container to sum all the moving speeds to calculate a single global moving speed factor. This way the more characters move inside a collision shape the louder the noise should be. Of course, each time a character triggers the "OnActorEndOverlap" when exiting the collision area you should remove him from the "TMap" container.

To use this component, we created an actor with an audio component and a collision shape on which we attached and configured this "BP_MoveSoundTriggerComponent". As a collision shape we used primitives like the SphereCollision because they're very cheap to handle by the physics engine and they're a more than acceptable approximation of the shape we needed. The actor doesn't require any extra blueprint logic implemented to work. We called this type of actor: foliage sound trigger actor.

Using a custom editor tool we implemented, we could then automatically spawn these actors throughout our landscape at the position of a desired type of foliage mesh, get its bounds and automatically adjust the scale of the sound trigger actor collision shape to approximate the mesh bounds.

The Sound Cue we used for a bush noise looks something like this:

We used more variations of the same bush noise and we also applied a little randomized modulation effect to give it a more natural diversification.

And this is how we could bring some "life" into various foliage meshes in our forest using sound. Below are the links from where you can copy the blueprints shown above to help you put things together easier:

Environmental sounds
Let's have a quick look at how we create a nice audio ambient in our game environment, that makes you want to pause and just listen to mother nature. This isn't anything complicated or new, but we want to go over the steps in case someone might find them useful.

First of all, we have a looping ambient sound, consisting of chirping, wind and mild foliage noises, the kind of sounds you'd expect to hear when in the middle of a forest. This sound is pretty long, around 2 minutes, so it's unlikely anyone can notice that it loops. It plays as a 2D sound, so you can hear it from all over the world.

Then we have a few 3D sounds with chirping, to create the feeling that you get closer to some birds as you move. There are no actual birds in the game though, it's all just an illusion made to immerse you in the environment. We also have a few barely audible 3D sounds of bees and insects, in a more open area of the game.

Another important sound is the one for the flowing stream of water. This is also a 3D sound. To emulate the water flowing all over the length of the stream, we placed several positional sounds with their falloff areas intersecting each other, but not the areas where there is no falloff, as in the screenshot below. This creates a continuous flowing sound that gets louder as you get closer to the stream and fades out nicely as you move farther.

Our collectibles also have 3D sounds attached to them. And, of course, music is of vital importance to our game's mood, but there is no technical info to be shared here - just find some great music and play it in tune with the feelings you want to stir in your players.

This concludes our Audio Design posts. If you want to hear some of the systems and sounds we talked about, check out our videos, in case you've missed them. Thank you for reading!

Audio Design: One footstep at a time

Audio Design : One footstep at a time

In Life Pictures you find yourself in a surreal place where you can wander through the forest and absorb the nature's sounds. Every step you make hinders for a very short moment the environment's tranquility with the noise you make confirming your physical presence into this place. And this is because of sound. Sound is very important for creating a good player experience and immersing the player in the world.

Let's see how we managed to give this wonderful place a bit of life by using sound. In this week's post we're going to dive into some technical details about how we made the character footsteps system. In future posts we'll talk about how the bushes crackle when the character goes through them and the sounds setup for the rest of the nature elements.
* Mild warning! A lot of blueprinting ahead!

Character footsteps system
While moving, the first person character must make footsteps noise depending on the surface he's walking on and it must be at a specific rate depending on if he's walking or running. So to plan this ahead, this is what we need to do in high-level steps:
  1. if the character is moving and touching the ground, trigger an event for each step he makes; repeat until the character stops or isn't touching the ground anymore.
  2. for every footstep event do a line trace at the bottom of the character capsule, at the imaginary feet position
  3. from the line trace collision info get the physical material of the surface the line intersected and play the footstep sound corresponding to that physical material
  4. rinse and repeat
To integrate all this in our character blueprint in a more decoupled and reusable way, we created a separate actor blueprint to encapsulate all the logic required for doing the above steps. Let's call this actor "BP_FootstepsSoundController" and see how we implemented the above steps.

Triggering footsteps events
In this step we want to trigger an event for each step the character makes as long as he is moving and touching the ground. This logic needs to be executed every frame. So we have to check if the character is moving and touching the ground in a blueprint Tick event like so:
Step 1 - Part 1
In the above blueprint, "OwnerMovementComponent" is a cached reference to the Movement Component of the owner actor, which in our case is our character controller blueprint where we'll integrate this "BP_FootstepsSoundController" actor.

You probably noticed that before doing any other checks in the "Event Tick", we call a function "UpdateFootstepsPlayRate". By setting the "WalkSpeed" and "RunSpeed" of our character on this controller blueprint and then setting the desired "walkFootstepsRateFactor" and "runFootstepsRateFactor" we can easily tweak how fast the footstep sounds should be triggered when the character is walking and running. This function does the smooth interpolation between walking and running accordingly:
UpdateFootstepsPlayRate function
The function "GetGroundMoveSpeed" is a pretty simple custom Blueprint Utility function we made to calculate the ground relative speed of a movement component ignoring the Z axis movement and looks something like this:
GetGroundMoveSpeed function
So getting back to the initial screenshot Step 1 - Part 1, once the movement and ground touch conditions are true, we have to trigger an event for each theoretical footstep the character makes while he's moving. An easy way to trigger an event at a specific time interval and in a loopable manner is to use a Timeline component. Timelines are very useful for a lot of things, allowing you to easily design time-based animations and play them based on various in-game events. In our case, we used the Timeline component to create an event track named "Footstep Event" on which we defined key-frames for when a footstep sound should be played, within an interval of 1 second:
Timeline Footsteps Event Track
As you can see we've set one footstep event at 0.25s and one at 0.75s thus making them equally apart and loopable. By making the timeline length of 1 second we can then easily scale it using the Set Play Rate node to have extra control over how far apart the footsteps events will be triggered. For example when running, we can scale the timeline down by increasing the play rate from 1.0 to 1.5 thus making the footsteps events trigger 50% faster. We saw this in the UpdateFootstepsPlayRate function we discussed about earlier.

So how does this timeline actually help us with these events? Well, on its own the Timeline component doesn't do much. It just plays its animation timeline in a loop. While the character is moving and is touching the ground we should start playing this Timeline Component but only if it isn't already playing:
Step 1 - Part 2
As you noticed the timeline node automatically generated an output pin with the name of our previously created event track "Footstep Event". This pin will be executed every time an event is triggered on our timeline while it's looping.

But what happens if suddenly the character stops moving or he jumps and he doesn't touch the ground anymore? By continuing the "Branch" node from the Step 1 - Part 1 screenshot we showed earlier, on the "False" pin we should stop playing the timeline to avoid hearing footsteps while in the air, by calling our "StopFootstepsTimeline" function:
StopFootstepsTimeline function
The next time the timeline is restarted it will resume where it left off which is exactly what we want because if you move the character in small start/stop cycles you get the impression that you can move him one step at a time which is a nice detail to have.

So putting everything together for this step, we get this in our "BP_FootstepsSoundController" blueprint "Event Tick":
Step 1
Playing the right sound
In steps 2 and 3 of our plan, we need to detect which sound we have to play based on what our character's feet are touching. To do that, for every footstep event we do a line trace at the bottom of the character capsule and from the collision info we get the physical material of the surface the line intersected and play the footstep sound corresponding to that physical material.

Now that we have our timeline component triggering the footstep events, we can call a function to handle the logic of "querying" the surface the character is currently on by using a line trace and playing the corresponding footstep sound:

Let's split the "QueryGroundAndPlaySound" function in half to get a better view of what it does:
QueryGroundAndPlaySound function - Part 1  
This is where we actually use the line trace node and if we get a hit result we "break" the collision hit info to obtain the required "Physical Material" of the surface the line intersected.

To create the link between each physical material in the level and the corresponding footstep sound, we implemented a custom wrapper for Unreal's TMap data container so we can use it in Blueprints and store the objects as <key, value> pairs like this: <UObject, UObject>. But if you don't want to dive into C++ code, something similar can also be achieved by using an array of structs that contain the <key, value> pair of objects. The downside is that the physical material lookup in the array for every line trace will be of O(n) complexity and the more physical materials you use, it won't scale well performance wise. If you do want to dive into a bit of C++ code, you can check out below our simple "TMap" wrapper for blueprints.

* "UObjectsMap" header:

USTRUCT(BlueprintType) struct FObjMapKeyValStruct { GENERATED_USTRUCT_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite) UObject* keyObj; UPROPERTY(EditAnywhere, BlueprintReadWrite) UObject* valueObj; }; /** * TMap<UObject*, UObject*> type * You can use this in Blueprints to map various UObjects as key and * value pairs allowing you to map just about any Blueprint type * instance in a dictionary-like data structure. */ UCLASS(BlueprintType) class LIFEPICTURESRELEASE_API UObjectsMap : public UObject { GENERATED_BODY() public: TMap<UObject*, UObject*> nativeMap; UFUNCTION(BlueprintCallable, Category = "MobilityBlueprintUtils|ObjectsMap") void Add(UObject* keyObj, UObject* valueObj); UFUNCTION(BlueprintCallable, Category = "MobilityBlueprintUtils|ObjectsMap") int32 Remove(UObject* keyObj); UFUNCTION(BlueprintCallable, Category = "MobilityBlueprintUtils|ObjectsMap") bool Contains(UObject* keyObj); UFUNCTION(BlueprintCallable, Category = "MobilityBlueprintUtils|ObjectsMap") UObject* FindValue(UObject* keyObj); UFUNCTION(BlueprintCallable, Category = "MobilityBlueprintUtils|ObjectsMap") UObject* FindKey(UObject* valueObj); UFUNCTION(BlueprintCallable, BlueprintPure, Category = "MobilityBlueprintUtils|ObjectsMap") int32 Num() { return nativeMap.Num(); } UFUNCTION(BlueprintCallable, Category="MobilityBlueprintUtils|Data Structures") static UObjectsMap* CreateObjectsMap(); UFUNCTION(BlueprintCallable, Category = "MobilityBlueprintUtils|Data Structures") static UObjectsMap* CreateObjectsMapFrom(TArray<FObjMapKeyValStruct> keyValueArray); };

* "UObjectsMap" implementation:

void UObjectsMap::Add(UObject* keyObj, UObject* valueObj) { nativeMap.Add(keyObj, valueObj); } int32 UObjectsMap::Remove(UObject* keyObj) { return nativeMap.Remove(keyObj); } bool UObjectsMap::Contains(UObject* keyObj) { return nativeMap.Contains(keyObj); } UObject* UObjectsMap::FindValue(UObject* keyObj) { UObject** valueObjMapRef = nativeMap.Find(keyObj); return (valueObjMapRef != nullptr) ? *valueObjMapRef : nullptr; } UObject* UObjectsMap::FindKey(UObject* valueObj) { auto* keyObjMapRef = nativeMap.FindKey(valueObj); return (keyObjMapRef != nullptr) ? *keyObjMapRef : nullptr; } UObjectsMap* UObjectsMap::CreateObjectsMap() { return NewObject<UObjectsMap>(); } UObjectsMap* UObjectsMap::CreateObjectsMapFrom(TArray<FObjMapKeyValStruct> keyValueArray) { UObjectsMap* objMap = CreateObjectsMap(); for (auto keyValuePair : keyValueArray) objMap->nativeMap.Add(keyValuePair.keyObj, keyValuePair.valueObj); return objMap; }

To create an instance of this wrapper in blueprints you will have 2 methods available as you noticed in the above code: either directly populate it from an array of our custom <key, value> pairs struct, using the "CreateObjectsMapFrom" node, or creating an empty instance, using the "CreateObjectsMap" node.

Getting back to our previously shown "QueryGroundAndPlaySound" function, as you can see in the image below, each time we get a collision we check if the Physical Material is in our TMap container "footstepsSoundsMap" and if it is, we get the corresponding sound to play at the collision location, otherwise we play a default generic forest road footstep sound - the "DefaultFootstepsSoundCue" node:
QueryGroundAndPlaySound function - Part 2  
For footsteps sounds we didn't use a single sound for a physical material because it would have been unnaturally weird and annoying to hear the exact same sound repeating. So we used a variation of 3-4 sounds from where we randomly pick a different one each time we play the SoundCue by using Unreal's SoundCue Random node:

And this covers the core logic of the "BP_FootstepsSoundController" blueprint actor. In case you missed them above, below are the links from where you can copy the blueprints code:
  • "BP_FootstepsSoundController" blueprint "Tick" event
  • "UpdateFootstepsPlayRate" function
  • "GetGroundMoveSpeed" function
  • "StopFootstepsTimeline" function
  • "QueryGroundAndPlaySound" function

We can now easily integrate this actor as a child actor in any Character blueprint to generate footsteps sounds in a pretty configurable way. Of course the system can be much more complex, but this was more than enough for our needs. To integrate our footsteps controller we just have to drag and drop it in the FirstPersonCharacter blueprint components hierarchy, which should look something like this:

After we configure the footsteps sounds and the corresponding physical materials on the controller, in the "FirstPersonCharacter" blueprint all we have to do is just apply the character "WalkSpeed" and "RunSpeed" in the "BeginPlay" event and then let it do its thing:

Hope you find this useful for your future stealth/action/assassination games where you want to hear your character's footsteps while dancing in the blood puddles of his victims.

Thank you for reading and happy blueprinting!

Taking beautiful pictures

Taking beautiful pictures

Last week we looked into a bunch of technical aspects regarding the photo camera in Life Pictures. Now it's time to take a closer look at how you'll be using the camera in the game.

Memories and time
We mentioned several times until now that taking a picture and keeping it will take you to another memory further along the timeline. What does this mean and how it actually works? When you begin the game you're alone. You take a stroll through the woods and you'll meet one of the 3 girls in Life Pictures. If you interact with her and take a picture, you'll be taken to a memory of your relationship with her. The picture can be of anything, not necessarily the girl, although we suspect you won't be able to help yourself for the first several times.

In the new memory with the girl you've interacted with, you can take a new picture any time you want and time will again advance to another memory. While you're in a relationship with a girl, you don't have to interact with her again - advancing time will take you through the memories of that relationship. After the relationship has played out, you'll be able to interact with another of the remaining girls and revisit the memories of your time with her.

If you're alone and you take a picture without interacting with any girl, you'll continue alone, advancing time much further along. You'll have a new chance to meet a girl or to continue being alone. How you play out your lives is up to you! We encourage you to try as many combinations as you want, so you can find out more about your real life. Can you piece it together?

Camera functions
When taking a picture, you have several functions to help you get the best result. You can zoom in and out, to get the best viewing distance to your subject. You can adjust your height, perfect for some under shots for example. The coolest feature is that you can change the focus area. This starts from focus to infinity and moves closer to you. When the focus area gets small enough, it goes back away from you for a short distance, giving you a lot of control of what you can focus on. Last, but not least, you have the useful guidelines to help you get the best composition for your shots. You can, of course, hide them at any time, to view only your subject without all the clutter. There's a battery indicator too, but we'll let you figure out how that works - no, don't worry, you won't need to recharge anything.

Pictures and albums
Once you take a picture, you can preview it and if you choose to keep it instead of discarding it, time will advance. Where does the picture go? To your Life Pictures album of course. As we mentioned before, you can have many lives in this game. Each life has its own pictures album, which you can view at any time to remember your experiences and choices. While events repeat themselves between the various lives, your choices influence the order in which they happen and that makes all the difference. We can't go into too many details without spoiling anything, but think about this: having a child at 20 is very different than having the same child at 40. Change the order of major events in our lives and that will make for a very different experience. Spoiler: the child was only an example, there won't be any children in Life Pictures.

We went into a lot of technical stuff in last week's post, so we'll only talk about one more thing now: how we made the focus effect. It's very simple, using only what Unreal puts right in front of us. On our camera component, in the post process settings, we activate the depth of field, using the BokehDOF. Below you can see the values we start with when the camera is set to focus on infinity. As we focus closer, we change the values for the scale, focal distance and focal region. The scale goes up pretty quickly from 0 to 2 and stays there. The focal region decreases fast, then decreases slowly and right at the end goes up just a tiny bit, for a nice increase in control. The focal distance is a bit more interesting, first it stays constant, then increases and in the later part of the curve decreases. Better have a look at the curves below, it's harder to explain in words.

What do the focal region and focal distance curves achieve? Imagine the focus region is a cube which at first starts from your position and goes really far - this would be the infinity focus. Then the cube gets smaller, still starting at your position and stretching closer and closer. When it hits a certain size, the cube also starts moving away from you. When the cube gets really small, it continues shrinking only slightly, while it keeps moving away from you. When it reaches minimal size, the cube starts getting closer to you, increasing slightly in size. The last part helps so you can move a minimal sized focal region closer or farther away from you.

This article pretty much wraps up the information about the photo camera in Life Pictures. We hope you'll have fun with it and take some great pictures when the game releases! Thank you for reading!

Technical dive into our photo camera

Technical dive into our photo camera

As we mentioned in the first post about Life Pictures, the photo camera is a vital part of how you interact with the game. You can take a photo of a moment that catches your eye and if you save it in your album, you'll advance the time to a different memory in your life. But how is this "time travelling" photo camera working behind the scenes?
* Mild warning: technical gibberish and source code ahead!

Basic Setup
The first most basic thing a photo camera needs to do is capture a photo. The easiest way of doing this in Unreal is to use the SceneCaptureComponent2D. It basically acts like a camera component which under the hood sets up a viewport used by the engine to render the scene into a render target texture instead of the screen target buffers. By default, in UE 4.10.4, this component captures the scene without any of the postprocess effects because the "Capture Scene" parameter is set to "SceneColor (HDR)". But in our case we needed to capture the final scene render so this setting should be set to "Final Color (LDR with PostProcess)". You'll notice in the screenshot below that we also unchecked the "Capture Every Frame" flag because we obviously need only one frame to be captured.

Once we have this component set on an actor in the scene, we also need to tell it where exactly should the final rendered scene be outputted and thus we need to set a render target texture in the "Texture Target" parameter. To set it up quickly you can create it directly as an asset,  but we needed it to be created dynamically at run-time so we went the Blueprints way. But, currently there's no blueprint node that allows us to dynamically create an UTextureRenderTarget2D.

Unreal has a nice way of letting you call static C++ functions from Blueprint Function Libraries in the form of Blueprint nodes, so this wasn't a big problem but more of a research issue. We implemented a custom function that creates a 2D render target texture and returns its pointer in blueprints:

* header declaration:

UFUNCTION(BlueprintCallable, BlueprintPure, Category="MobilityBlueprintUtils|Textures") static UTextureRenderTarget2D* CreateRenderTarget2DTexture(int32 width, int32 height, bool makeHDR);

* cpp implementation:

UTextureRenderTarget2D* UMobilityBlueprintUtils::CreateRenderTarget2DTexture(int32 width, int32 height, bool makeHDR) { UTextureRenderTarget2D *rtResult = NewObject<UTextureRenderTarget2D>(); if (makeHDR) rtResult->InitAutoFormat(width, height); else rtResult->InitCustomFormat(width, height, PF_B8G8R8A8, false); int32 resSize = rtResult->GetResourceSize(EResourceSizeMode::Inclusive); return rtResult; }

And the new custom Blueprint node generated by the engine for this function after you compile the code looks something like this:

Now we have an actor with a SceneCaptureComponent2D that can render the scene into a 2D render target texture. If you remember, previously we disabled the "Capture Every Frame" from this component to avoid rendering the scene every frame. Well, unfortunately, unchecking this flag only partially does what you'd expect. If you move the position of the actor or this component it will render the scene again because it detects that its transform is dirty. So to actually make sure this component renders the scene only on demand, we set its "Visible" property to false and each time we want to capture a frame we do the following:

As you noticed in the above blueprint, calling the "UpdateContent" function will force a scene render and after that we disable the visibility flag back to avoid subsequent rendering of the scene into the render target texture.

Congrats! With this simple setup described above you have the basic functionality of a photo camera: capturing the scene.

Our photo camera tends to bit a more complex under the hood and it's actually a separate Camera actor that renders the scene when the player is in the photo camera mode allowing us to apply different post-process effects and play with various camera settings like the FOV. The scene capture component mentioned earlier that's attached to this camera actor is only used when we need to capture a photo. To switch from the first person character camera to our photo camera we used the SetViewTargetWithBlend node.

Saving the photo
All things aside, what is a captured photo if we can't save it or load it back? Unfortunately, our current version of UE 4.10.4 doesn't offer an out of the box way of saving a render target texture to disk from Blueprints. But, it does have a lot of low level oomph that gives you the flexibility of doing this nonetheless. So after building up a good chunk of sweat from diving through the engine code and trying multiple ways of achieving this we managed to come up with yet another blueprint library function that we can call from Blueprints:

bool UMobilityBlueprintUtils::SaveRenderTargetToFile(UTextureRenderTarget2D *rt, FIntPoint newImageSize, const FString& fileDestination, bool applyCropIfResized/* = false*/, EUtilsImageCompressionType compressionType /*= EUtilsImageCompressionType::VE_PNG*/, int32 compressionQuality/*= 0*/) { // Get game thread reference for the render target. FTextureRenderTargetResource *rtResource = rt->GameThread_GetRenderTargetResource(); TArray<FColor> rtPixelsArray; TArray<FColor>* newPixelData = &rtPixelsArray; rtPixelsArray.AddUninitialized(rt->GetSurfaceWidth() * rt->GetSurfaceHeight()); FReadSurfaceDataFlags readPixelFlags(RCM_UNorm); rtResource->ReadPixels(rtPixelsArray, readPixelFlags); // Check if we need to prepare data for resizing the render target texture data. TArray<FColor> dstPixelData; if (newImageSize.X == 0 || newImageSize.Y == 0) { newImageSize.X = rt->GetSurfaceWidth(); newImageSize.Y = rt->GetSurfaceHeight(); } else if (newImageSize.X != rt->GetSurfaceWidth() || newImageSize.Y != rt->GetSurfaceHeight()) { dstPixelData.AddUninitialized(newImageSize.X * newImageSize.Y); if (applyCropIfResized) { FImageUtils::CropAndScaleImage(rt->GetSurfaceWidth(), rt->GetSurfaceHeight(), newImageSize.X, newImageSize.Y, rtPixelsArray, dstPixelData); } else { FImageUtils::ImageResize(rt->GetSurfaceWidth(), rt->GetSurfaceHeight(), rtPixelsArray, newImageSize.X, newImageSize.Y, dstPixelData, true); } newPixelData = &dstPixelData; } // Compress image and save to disk bool imageSavedOk = false; TSharedPtr<IImageWrapper> imageCompressor; IImageWrapperModule* imageWrapperModule = FModuleManager::LoadModulePtr<IImageWrapperModule>(FName("ImageWrapper")); if (imageWrapperModule != nullptr) { EImageFormat::Type targetImageFormat = (EImageFormat::Type)compressionType; imageCompressor = imageWrapperModule->CreateImageWrapper(targetImageFormat); } int32 inRawDataSize = sizeof(FColor) * newImageSize.X * newImageSize.Y; if (imageCompressor.IsValid() && imageCompressor->SetRaw((void*)((*newPixelData).GetData()), inRawDataSize, newImageSize.X, newImageSize.Y, ERGBFormat::BGRA, 8)) { const TArray<uint8>& compressedData = imageCompressor->GetCompressed(compressionQuality); imageSavedOk = FFileHelper::SaveArrayToFile(compressedData, *fileDestination); } return imageSavedOk; }

It's a big function so let's brake it down in chunks and try to understand this dark Unreal magic.

The first thing we need to do is tell the engine to give us a pointer to our render target texture's corresponding resource from the render thread. We need that pointer so we can use it on the main game thread, which is the actual thread we're running this function on:

// Get game thread reference for the render target. FTextureRenderTargetResource *rtResource = rt->GameThread_GetRenderTargetResource();

The render target texture is a special texture that resides in the GPU memory. Normally we can't read pixels from a texture like this directly from CPU executed code. In Unreal, when you create a render target texture, by default it also allocates a secondary corresponding resource of type FTextureRenderTargetResource on the main memory. So what we need to do is read the pixels array using the "FTextureRenderTargetResource" that acts as a bridge between the actual render target texture from the rendering thread and the main thread from where we call our function.

That's why below we use the function "rtResource->ReadPixels(...)" to fetch the render target pixel data to our "rtPixelsArray". The "newPixelData" is just a pointer to the same "rtPixelsArray" that we need later for doing some extra changes to the pixels data structure like resizing or cropping the texture.

TArray<FColor> rtPixelsArray; TArray<FColor>* newPixelData = &rtPixelsArray; rtPixelsArray.AddUninitialized(rt->GetSurfaceWidth() * rt->GetSurfaceHeight()); FReadSurfaceDataFlags readPixelFlags(RCM_UNorm); rtResource->ReadPixels(rtPixelsArray, readPixelFlags);

The code below is just for some extra steps that optionally change the size of the image we're saving on the fly using some useful Unreal utility methods we found by digging in the engine source code, in the FImageUtils class:

// Check if we need to prepare data for resizing the render target texture data. TArray<FColor> dstPixelData; if (newImageSize.X == 0 || newImageSize.Y == 0) { newImageSize.X = rt->GetSurfaceWidth(); newImageSize.Y = rt->GetSurfaceHeight(); } else if (newImageSize.X != rt->GetSurfaceWidth() || newImageSize.Y != rt->GetSurfaceHeight()) { dstPixelData.AddUninitialized(newImageSize.X * newImageSize.Y); if (applyCropIfResized) { FImageUtils::CropAndScaleImage(rt->GetSurfaceWidth(), rt->GetSurfaceHeight(), newImageSize.X, newImageSize.Y, rtPixelsArray, dstPixelData); } else { FImageUtils::ImageResize(rt->GetSurfaceWidth(), rt->GetSurfaceHeight(), rtPixelsArray, newImageSize.X, newImageSize.Y, dstPixelData, true); } newPixelData = &dstPixelData; }

Once we have the final modified pixels array in "newPixelData", we have to use Unreal's FModuleManager  to get a reference to its "ImageWrapper" module which is actually a factory for various types of image compressors. With it we can create an image wrapper for our specified "targetImageFormat" to get an instance of the corresponding "imageCompressor":

bool imageSavedOk = false; TSharedPtr<IImageWrapper> imageCompressor; IImageWrapperModule* imageWrapperModule = FModuleManager::LoadModulePtr<IImageWrapperModule>(FName("ImageWrapper")); if (imageWrapperModule != nullptr) { EImageFormat::Type targetImageFormat = (EImageFormat::Type)compressionType; imageCompressor = imageWrapperModule->CreateImageWrapper(targetImageFormat); }

This is where things get a bit more ugly C++ looking, but it's pretty straightforward once you parse through all the parenthesis. The image compressor isn't documented at all and the only way to find out how it should be used is by searching in the engine source code. So, after much digging, we found that you must calculate the bytes of raw pixel data you want to compress - see "inRawDataSize" below - and use the "SetRaw(...)" function to set the raw pixels array, image size, and pixel storage format on the compressor.

Once this method returns ok, we can retrieve the compressed image data array by using the "GetCompressed(...)" function which we then save to our destination file. Luckily, saving the binary file to disk was more straightforward by using FFileHelper:

int32 inRawDataSize = sizeof(FColor) * newImageSize.X * newImageSize.Y; if (imageCompressor.IsValid() && imageCompressor->SetRaw((void*)((*newPixelData).GetData()), inRawDataSize, newImageSize.X, newImageSize.Y, ERGBFormat::BGRA, 8)) { const TArray<uint8>& compressedData = imageCompressor->GetCompressed(compressionQuality); imageSavedOk = FFileHelper::SaveArrayToFile(compressedData, *fileDestination); }

Cool! After much sweat and engine code digging we finally saved a photo to disk - either in PNG or JPG format. You can end up taking a lot of photos if you play all the possible combination of choices and, to avoid around 2 GB of space for the game saves, we picked the JPG save format in the end.

Loading the photo
So, how do we bring the saved photo back into a texture at run-time? Because we'll eventually need to view the photos in a photo album, right?

This part should be a little easier to read now that you're warmed up from the above code. The main steps of the load function are:
  • setup the image compressor same as we did for when we saved the photo
  • load the raw binary data from the file using "LoadFileToArray(...)" 
  • feed the raw data to the image compressor using "SetCompressed(...)"
  • use the compressor "GetRaw(...)" function to get the uncompressed pixels array
  • create an UTexture2D object with the corresponding image size
  • upload the raw pixels data to the texture's memory block
  • rejoice with the returned texture pointer in Blueprints

And here's the function in all its glory with some extra safety checks and logs for each step mentioned above:

UTexture2D* UMobilityBlueprintUtils::LoadTextureFromFile(const FString& filePath, EUtilsImageCompressionType compressionType /*= EUtilsImageCompressionType::VE_PNG*/) { UTexture2D* loadedTex = nullptr; if (!FPlatformFileManager::Get().GetPlatformFile().FileExists(*filePath)) return nullptr; // Get ImageWrapperModule instance and corresponding image wrapper API. IImageWrapperModule& imgModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper")); EImageFormat::Type targetImageFormat = (EImageFormat::Type)compressionType; IImageWrapperPtr imgWrapperPtr = imgModule.CreateImageWrapper(targetImageFormat); if (!imgWrapperPtr.IsValid()) { UE_LOG(LogTemp, Warning, TEXT("[MobilityBlueprintUtils] LoadTextureFromFile: " + "failed to create ImageWrapper to load image!")); return nullptr; } TArray<uint8> rawFileData; if (!FFileHelper::LoadFileToArray(rawFileData, *filePath)) { UE_LOG(LogTemp, Warning, TEXT("[MobilityBlueprintUtils] LoadTextureFromFile: " + "failed to load raw data from specified path: %s"), *filePath); return nullptr; } if (!imgWrapperPtr->SetCompressed(rawFileData.GetData(), rawFileData.Num())) { UE_LOG(LogTemp, Warning, TEXT("[MobilityBlueprintUtils] LoadTextureFromFile: " + "ImageWrapper->SetCompressed failed for file: %s"), *filePath); return nullptr; } const TArray<uint8>* uncompressedData; if (!imgWrapperPtr->GetRaw(ERGBFormat::BGRA, 8, uncompressedData)) { UE_LOG(LogTemp, Warning, TEXT("[MobilityBlueprintUtils] LoadTextureFromFile: " + "ImageWrapper->GetRaw failed for file: %s"), *filePath); return nullptr; } // Create transient texture object loadedTex = UTexture2D::CreateTransient(imgWrapperPtr->GetWidth(), imgWrapperPtr->GetHeight(), EPixelFormat::PF_B8G8R8A8); if (!loadedTex) { UE_LOG(LogTemp, Warning, TEXT("[MobilityBlueprintUtils] LoadTextureFromFile: " + "failed to created UTexture2D for PNG file: %s"), *filePath); return nullptr; } // Upload image data to texture in the top most mip level. // Get the pointer to the texture mip level 0 raw container and lock it for read/write operations // because the texture is used in the render thread and we could cause memory corruptions because // of threading race conditions. // Note: For UTexture2D textures that were created from an Unreal editor asset file, the // "Lock(LOCK_READ_WRITE)" function will internally detach the texture data container from the // asset to avoid messing up the data in case you also import a new file at run-time while in // the editor and it will get reattached when using the "Unlock()" function. void* textureData = loadedTex->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE); // Copy the raw image data to the address from the above pointer. FMemory::Memcpy(textureData, uncompressedData->GetData(), uncompressedData->Num()); // Unlock the texture data container. (will invalidate the above data container pointer) loadedTex->PlatformData->Mips[0].BulkData.Unlock(); // Update the texture GPU resource with the new data. loadedTex->UpdateResource(); return loadedTex; }

Now that you have this function available in Blueprints, you can load a photo, set it on a material and presto! You have your loaded photo rendered in-game wherever you apply the material. Nothing fancy, but quite handy to have around.

We hope this "little" post helps someone that might be at the starting line with UE4. We sure could've used it a while back. Thank you for reading!