Hey everybody! Long time no post!
There are lots of things to discuss, so first let me just say that I lost my day job way back at the end of June. It hasn't been easy, and its been a bit stressful, but thanks to a combination of good credit, planning, and a social safety net we have survived pretty well these last 2 months. Job prospects in the area are looking pretty poor, so in the meantime I decided to make an app.
I present you with Cubix Rube. Also available on the Amazon App store. This is a pretty simple and clean Rubik's Cube simulator that I did just for fun, education, and a little bit of ad supported income.
About 2 weeks ago I decided I wanted to learn how to solve a Rubik's Cube, and being the engineer that I am that almost immediately became how to make one in Unity as well. Turns out, not as easy as I initially thought, but not as hard as it could be. I used all freely available tools to create this app: Blender for the 3D models and materials, Unity for the engine, and Unity Assets for a few things that I didn't know how to make myself, such as the buttons, skyboxes, and a serialization system to store your saved cubes on exit.
First, I created each individual piece of the cube in Blender. This was honestly the easiest step. I used a cube primitive and then colored each face accordingly, and then exported each box out to FBX. This allowed unity to import the 3D model as well as the material colors.
Then, in Unity I constructed the cube assembly using the cubes I had created in blender. I also structured the cube such that there was one "Cube" game object, and as children of it there were 8 rotation centers, oriented with their Y axis perpendicular to the face that would rotate around those axis. There 6 for each external face, and then 1 each to rotate the entire cube around (so that you could turn it upside-down).
This is my first Unity project that utilized raycasting for object selection.. As a child of each rotation center is a selector box which covers all the cubes on each face. When the user clicks on a box, a script on the box gets a list of all the game objects its currently colliding with. My game controller script then accesses that list, and makes all the cubes inside the selector children of the rotation center. Then the user presses a button to rotate that rotation center game object (and all of its children) either left or right. I found this to be a bit ambiguous though, since sometimes the buttons would spin the face in the opposite of the expected direction. So eventually I replaced them with the glowing orange arrows you see in the screenshot below.
Once I had my selection method set, it was fairly straightforward to write a scrambling subroutine that would generate a random number of moves (between a set floor and ceiling). For each move it would select a random selector and rotate it 90 degrees. The biggest problem I ran into during this phase of development was the rotation subroutine running on the next selector before the current face was finished rotating. I ended up with a giant pile of cubes in no particular orientation. Just a jumbled mess. The solution involved rewriting my rotation subroutine as a coroutine, so that when the scramble coroutine called it, the rotation would finish before the scramble coroutine would proceed. I spent literally an entire day on this issue before I found the solution. I was not a happy camper.
After the scrambler and the rotation subroutines were finished it was fairly easy to add a move counter. One thing my app has that other rubiks cube apps don't is a live updating cube map. This was created using 6 cameras and render textures. I didn't want the normal light from the main camera to effect the lighting on the cube map, so I had to make it so each of the 6 map cameras ignored the main camera. Likewise, the main camera needed to ignore the lights from the 6 map cameras. A simple script was written which I'll share with you here:
We basically create a list of all lights we want the camera to ignore. For the main camera it was a list of the 6 lights that would light each individual face for the cube map. For the 6 cube map cameras, we only needed to ignore the light on the main camera.
The script on the main camera would turn off all the other lights before the rendering of the frame happened. Then after rendering it would turn them all back on. It should be noted that this is the rendering pass for this particular camera. Effectively, when the camera goes to render a frame, all the lights we want it to ignore are turned off. Results being that the main camera never sees the lights for the cube map, and the cube map cameras that fill the render textures never see the main light when the texture is updated. This results in a cube map with nice even lighting, and a main camera with nice dynamic lighting. The only other problem with this method is that rendering 7 cameras per frame is very costly as far as system resources go, and could chug a mobile platform. Luckily, there is an easy method to call a render pass per camera manually, so the cube map is only updated in the frame after each turn of the cube. Framerate drops for a single frame, and is completely invisible to the user.
The next challenge was in creating an algorithm that would recognize when the cube puzzle was solved. This might seem easy, since one method would be to just compare the locations of the individual faces against a preset "win" location. That works, but only for one specific orientation of the cube. So I'd then have to create a "win" preset for all possible combinations of the solved faces (for example, red face oriented front, or top, or bottom, etc). I decided this would ultimately not be a very elegant solution, and would require a lot of work.
When I created each individual cube, I named it in such a way that I had all the colors in the name. For example, the corner cube with Red, White, and Blue faces on it was named "RWB Cube". This naming convention allowed me to easily check inside each face selector, and count the total number of each color cube currently showing on that face. If any of the checked colors had 9 cubes, I knew that face was solved. So after every turn, we check each face for 9 of the same color, and if all 6 faces have 9 of one color (regardless of which color it is), we know that the puzzle has been solved by the user. This is a better solution because its relatively short code (28 lines total), and it doesn't care about the specific orientation of each colored face.
Another thing this project gave me the opportunity to do was finally learn how to use the new Unity UI system to properly place all my UI elements. I never really fully understood it before, but I do now. The documentation on the UI system has really improved, so all it took was rereading it for everything to click. This is the first app I've made that has properly scaled and positioned UI elements regardless of screen size, orientation, or resolution.
Well, that's about it. If you have any questions about my specific methods, feel free to leave a comment and I'll try and get back to you as soon as I can. All in all, between all scripts, this app is only about 600 lines of code, including extra line breaks for clarity and readability.
Please check it out on the play store link above, or on the Amazon app store, if you prefer to get your apps from there. There is a short ad that plays at the start of every new game, except for when you first start the app. Since I am unemployed currently, any little trickle of income this produces for me is going to be very helpful. Sorry iPhone people, its not presently available on the Apple store because Apple charges a relative arm and a leg in order to publish on their store ($100). Amazon cost 0, and Google only cost 25.
Future planned features: In App purchase to remove ads. I'm thinking 99 cents.
Things I wish I'd thought of before I got halfway through development and would have to do a total rewrite to implement: custom images for each face of the cube. It isn't impossible, but it would take a whole lot more work that I'm not really willing to put into this little vanity project.
Anyway, have a play and let me know what you think. I'm open to suggestions for improvements. Happy cubing everyone!