A postmortem of my 48 hour Android project that fizzled

This post is in two parts: The first half is a “Here’s how the world burned” and the second half (noted by a series of '------'s) is a list of half-baked suggestions followed by a feature request. That feature request is very important for 2D and at least somewhat helpful for many people. Let’s begin the downward spiral.

I had just finished a 14 day (10 hours each) crunch on a single illustration. I’m not even an illustrator. The illustration was for an art competition and I decided I’d use the deadline as a motivator. The goal being to learn my entire 3D pipeline. Modeling, Texturing, Materials, Particles, Lighting, Concept Art, Narrative, ect. It worked. But I had lost my passion and I had forgotten just about everything to do with the game I’m developing so I knew I needed to take a quick break before jumping back on the wagon as it were.

**
Enter: The rebound game! A 2D sprite based Snake game!**

Every time I have an idea for a game I add it to a card in Trello. I picked one out that I thought I could do in 2 days. The target platform was Android and I’d need to configure my computer and learn more about the platform. Thankfully that was easy. I’ve already developed two games and an application for Android and I felt certain in my abilities. The Android / Mobile UE4 documentation was as easy to follow as could be. Adjusting my project settings couldn’t have been simpler thanks to easy to understand tooltips and a solid comprehension of everything my game would ever be.

Enter: The slippery scope of an Engine that wouldn’t
*
Now I don’t mean for this to be an insulting post. UDK was my first 3D engine and UE4 is my favorite tool for game creation. But my Android experience comes from GameMaker, something I mention only because it’s my point of reference when I say that I expect a feature and it’s missing / incomplete. Let’s continue. *

I started off by planning out every function, actor, ect. I’d need in order to make this work. From the beginning there were problems. I imported my sprite texture. I changed the settings to match the sprite settings used in a similarly used sprite in the Match 3 example. Thankfully I noticed that something was off. The colors were being crunched and I was losing definition. My sprite had, broadly, 3 hues and 3 - 5 shades per hue. What was happening is each hue lost all but the darkest and lightest value, and even those were made to be closer. I just kept tweaking settings in the texture until it eventually looked correct. 7ish minutes to make my sprite import correctly. Rough start, but perhaps I should have looked into Paper2D documentation as I’m now guessing there’s a “how to import sprite texture” guideline or tool.

At that point in time I’d only just re-enabled Paper 2D. My game was originally only going to have a 100x100 centimeter plane with sprites in materials for the snake’s head. The body was just going to be a colored unlit emissive material. Saves texture space. So why use Paper2D? That changed once I realized I couldn’t use Perspective with an FOV of 10 and expect pixel perfect sprites. It was at THIS point in time that I began looking into the UE4 examples. I wanted to know how Epic made their stuff pixel perfect in a 10 FOV environment (or whatever was used). I looked at Match 3 as the 2nd example and immediately switched to orthogonal. I only used FOV in the beginning because a previous attempt to use Orthogonal in 4.11 resulted in horrible lighting artifacts and my mouse cursor to world location stopped working properly. Neither of these would be a problem for Snake. I took extensive notes on the Match 3 example.

So to make that WAY more clear:

  1. I disabled just about everything including 99% of plugins

0.5 I had to murder my .uproject because my plugins were now causing UE4 to complain about them missing.

0.75 Now UE4 has to compile 3000+ shaders, reset my Engine settings, ect.

  1. I made a plane and a super simple emissive material.

  2. I noticed that my plane, when moving, was VERY aliased. I couldn’t solve this without iterating on my camera’s distance and wasn’t certain if it would hold up under different display conditions.

  3. I read in an Answerhub that my camera being 100000000 units distance away from the things I was rendering would likely cause problems.

  4. I decided to look into the examples in the launcher. Nearly all of them are targeting 4.10 and thus have to be downloaded and then upgraded. Thankfully I already had them from said 4.11 project so I didn’t even need to download them (due to said “nothing changed since 4.10” thing). All except Match 3. I’d never looked into it so I downloaded it.

  5. I looked at Crazy Geo (?) first. Nothing useful to me there. It wasn’t forced to have everything in the same viewport and it was a game based on reaction and surprise so they just made a 90 degree camera really close to the game that follows you around.

  6. Match 3 finished downloading. Decided to check it out next because it was made for Android. MUCH better. Many notes. I’d already watched the learning video when it originally came out (but didn’t take notes because I wasn’t expecting to go back to mobile game development)

  7. Imported a sprite based on those notes

  8. Realized I didn’t have Paper2D enabled and laughed because I was trying for about 5 minutes to figure out why I couldn’t make a Paper2D Sprite blueprint

So, here we are. So far it’s alright - nothing’s actually that bad. APK size once installed is massive but because UE4 is a cool engine I can let users install it to their SD card if they want and that’ll help. 4.16 was just announced and it’ll reduce APK size even further. Nonetheless I’ve spent about an extra 6 hours trying to get my game’s install size to be smaller because my “super awful test Walmart phone” doesn’t have more than 30MB to spare or an SD card slot. I decide that, given the Unity Mobile data (yeah, oddly that’s what google gave me when I asked for pretty graphs and percentages of the specs of every Android phone that’s connected to the Play Store in the past year) I had it didn’t even matter as devices with 3.5 inch screens aren’t as prominent as they used to be. I still had my Moto G (4.5" 1280p) and my G3 (5" 1280p) and could simulate a smaller screen using UE4 and setting the Window Size (aka pixel resolution) to a scale based on my monitor’s size, pixels in the monitor, and size of the screen I was targeting. Not great and I ultimately canned it because the resolution was much too low to even be remotely comparable. Again, so far I’m not too badly injured. It’s day 2 already, though. I was supposed to be finished by now and I’ve not even gotten my snake to do anything but move.

**Enter: The horrible realization that my movement system is garbage **

At a macro level this is simple to explain: I have a Grid blueprint. It has a 2D Array of every possible location the snake and its tail parts can be. The snake is constantly moving because I want it to look smooth and not simply “jump” from grid to grid. The Grid tells the Snake which direction it’s going (Up down left right but Vector) and a Grid Location in World Units. When the Snake meets that Grid location it’s given the opportunity to change directions for a single frame (the frame it meets it on) then the cycle repeats. In the Snake Pawn I have a simple loop:

  1. Calculate Movement: If you move forward where will you be? Is the GridUpdate location between your current location and the calculated one? If so, by how many world units have you “exceeded” that coordinate?
  2. If the player wants to change directions I simply take that “exceeded amount” and apply it to the direction they’re moving now. Nothing is lost or gained. Instead of 13 “left” they go 13 “up”

The major problem here is that it relies too much on that single frame and this is meant to be a multiplayer game. There are more problems but I won’t explain too heavily. I’d implemented a “Queue” system among other things but ultimately my entire movement system was flawed, from the way the player input was designed to the way the movement was handled. I fixed it by redesigning it. Took a few hours because I’m doing something a little more fancy than your typical Snake game but I digress.

It was at this point that I realized I had more problems to solve. Complex ones. Things I hadn’t considered as thoroughly as I should have. I won’t discuss them as they aren’t relevant to what happened aside from my dwindling motivation as the scope grew larger and the challenges continued in the direction of things relating not to my game itself, but the environment (hardware) in which it would run. Display sizes and aspect ratios aren’t that difficult a problem to solve. I’ve done it previously but this game was different for 3 reasons:

  1. UE4 isn’t what I’m used to. I’m used to just adjusting the Viewport x/y (Like Orthogonal Width except you input both) or the Final Render Image X,Y (You can start with 128,128 and scale it up with Nearest Neighbor to 512x512 and output that to the screen if you want to)

  2. My previous games have been Portrait, not Landscape. This is important because whichever side is the Up/Down axis is where the Ad Banners are displayed. When it’s the “longer” of the two resolutions it doesn’t matter as much because you usually have nearly twice as many pixels to work with on that side anyway.

  3. My previous games either didn’t take place on one screen or were specifically designed so that the displays with more resolution simply got a bigger output render and different aspect ratios simply showed more scenery instead of useful information. I’d designed them from the beginning so that my “worst case” display was the canvas by which I designed the game and everything else simply got a prettier version of that same game. I couldn’t do that this time. I didn’t have access to the render images, render size, or even the ability to scale, per pixel, my output render.

I came up with ideas. I spent ages in Google. I tried drawing everything into a Render Target. I wasn’t able to get anywhere near the desired “Draw a pixel at 15, 7 on this 32x32 Render Target” - most of the time nothing would draw. The few times something DID draw it was multiple pixels. And it didn’t even matter because Nearest Neighbor scaling isn’t available for Render Targets. I thought about trying to use a 2D Render Target (the thing that’s like a camera taking an image of your scene and then you can take that “Final Render” and draw it) but my scope was already too out of control at this point. I’d have to make 1x1 pixel sprites and work with those or use 2x2 (better for textures because power of 2) but sacrifice screen space on many devices as I’d be upscaling a 64x64 resolution image instead of a 32x32. I didn’t even know if it would support Nearest Neighbor. If it didn’t that wouldn’t work either. I also didn’t know if this would increase the rendering cost, even if my “real camera” was pointed at nothing. I wasn’t even happy with this solution either. I wanted pixel art. Not colored grids.

So I made the decision to kill the project. And it genuinely hurt. 7 days on a 2 day project. Not even half finished. I half heartedly thought of solutions while I grieved and felt like I’d never achieve anything or finish anything. I thought of how I could learn C++, figure out how the features I needed worked, and add the features I needed to the engine via a plugin or a fork. I considered learning a different tool to complete the project. I considered lowering my quality standards for the final product. I considered a lot. But in the end, I moved on to my main project and decided I’d make a post about the past 7 days in hopes that my failure could become a point of growth rather than one of loss.


To add, in no particular order, a further list of pain points:

Iteration times are bad. I can’t Launch to my phone* so I have to build, connect my phone, run the .bat, and then test. That process takes about 17 minutes. My cpu isn’t the greatest and I’m compressing the files during cook so I’m guessing this could be made far easier. *It’s a .obb error but I’m guessing I just get past this by checking the box in engine to skip the checks or package as a .pak only ; not sure, didn’t realize this until writing this just now.

Multiplayer testing requires standalone processes in editor because otherwise all of my controllers are sending their input values as if they’re Controller 0. No clue why.

I have to have an EXTENSIVE blacklist file made via navigating my Engine folder and I’m STILL not sure if I did it right. My directories are ‘’ but the examples are all ‘/’

Example:
My Directory: D:\Program Files\Epic Games\UE_4.15\Engine\Content\MobileResources
What I typed in: …/…/…/Engine/Content/MobileResources/

Point is, I’m not sure why the Engine doesn’t check if I’m referencing this stuff before including it. Or simply not including it and then telling me when I ask for a resource that doesn’t exist where that resource is so that I can simply include it myself. “Hey you’re asking for a font but it’s not being packaged. The font you’re asking for is located here “” and you can include it by adding it to an array in your project settings” ; Maybe this is part of the changes being made in 4.16 ? I just don’t understand why I can check a box that says “Don’t include Engine Content” and STILL get Engine content.

Why is there a PNG image I can’t open called main.obb.png that’s 10MB big? Is it an encryption key? Does it even make it into the installed package? Idk, I just know it’s taking up space D:

Why isn’t Firebase integrated? Why isn’t Google Play fully integrated (Sessions, Cloud Saves, ect.)?

It feels like UE4’s mobile target is iOS and Android isn’t very well supported. When I look into scaling my game to match the device display what do I get? “Use Device Profiles like we did to make the game run on the iPad 2 and iPhone 4s” but Android isn’t like that. I understand that there are profile rules but ultimately the feature I’m being told to use isn’t the tool to solve my problem.


Feature Request: I need direct access to the render buffer, kind of.

  1. I have a “Render Output” which is the final image going to my screen. I’m assuming that Ads will be drawn over this but in the event they aren’t here’s my solution:

  2. Make a “Render Target” that’s whatever pixel resolution I want it to be. I give it a name or an Index to reference it. I draw whatever I want onto it, can clear it, ect. Upon creating it I set the Resize Filter to Nearest.

  3. I open up the “Render Output” class (like GameMode or GameInstance and is level specific and can be overridden just like those) and uncheck the box labelled “Works by itself”

  4. I now do things like this: (Conscious effort to psuedo code)

// Begin Play > ResizeRenderTarget(32,32, Render_Output) (Decided this was a horrible idea)

Somewhere else, like a Draw Event or w.e for the sake of optimization:

SetRenderTarget(Grid_RT)
Draw_Texture(x,y,Texture)
Set_Pixel(x,y,Color,Blendmode) (probably an array to save draw calls)

SetRenderTarget(Render_Output)
Clear_Texture(Color) //Could be RGBA or just RGB (or the HSV equivalents) ALSO I’m not suggesting clearing this every time. In fact, a “Clear_Texture(x1,y1,x2,y2, Color, Texture)” would be great
Draw_Texture_Scaled(x1,y1,x2,y2 Grid_RT) //This is drawing the Texture as if it were a box. This could be a percent where 100 is current size, a Scale where 1 is current size, pixels, ect.)
Draw_Texture(x,y, AdBanner) (again, preferably not handled by this. Preferably just drawn over everything at Device Resolution outside of this entire process)
Draw_Texture(x,y, UMGBuffer) (Same as above, albeit I could see the use in having access to these, even individually, but diminishing returns for the effort imo)

Ideally you simply update each individual Render Target when you need to and, regardless of what you do, the Render_Output is always rendered to the screen at the end of every frame. If I had a grid of walls and a snake that moves I’d have 2 different render targets so that I don’t have to update the walls every time the player moves. But if Texture memory was low maybe the only option would be to use 1 render target and constantly refresh it. Maybe you can only store an 8x8 so you have to refresh 3/4ths of the grid. Idk. The MAJOR and IMPORTANT unsung addition here is that you specify the pixel x,y - not the UV 0 - 1 , 0 -1 . Also, for 2D art in general, not just Pixel Art, it’s fairly important that Nearest Neighbor filtering is possible! And that these render targets aren’t written to the disk (I mean I guess they could be) for the sake of not murdering non-flash hard drives.

Additionally, a function which returns an array of pixels that match a color would be great.
In example: I have a pure black texture and I draw 5 pure white pixels onto it. This function would be able to tell me the location in pixel space of those pixels via a 2D Integer Array (because there’s no such thing as a half pixel). Or an array of Structs where each struct is just “Int Pixel X and Int Pixel Y”. I presume this would have to be a latent operation and maybe it’s best to never use this information for gameplay purposes. I did note that my attempt to do this in a material ended poorly (as noted above). I tried to make a line of white pixels going along the U and a line of white pixels going along the V and then only output the one they have in common and, simply put, it didn’t work. I had to crank up the inaccuracy threshold to .001 and it only worked 1 out of every 1000 attempts or so. If latency could be within 16ms or so (aka less than a frame) that wouldn’t be a problem. If the accuracy could be guaranteed then that wouldn’t be a problem either. I dunno.

Anyway. I’ll stop here. Mostly because I’m expecting to find out that all of this is already possible and I’m dumb af. Or that I’ve gone over the text limit.