2014/12/02

Sudoku In Space 2.0!

We've made a lot of improvements to Sudoku In Space based on the awesome suggestions we've had from our users. The app has been very well received and we believe it is worthy of the extra time we've spent on making it as perfect as possible. We've had great reviews from sites and from our users. Here's a small sample:
  • "If you’re looking for a new way to play Sudoku, or if you've never played before because it hasn't appealed to you, try this. You might be pleasantly surprised, like I was! Score: A"
    Nerdy But Flirty
  • "...pretty much blows the traditional paper and pen version out of the water. With this version of Sudoku you'll be treated to all kinds of fun and vivid colors, music, sound effects, and graphics, something you're probably not used to when it comes to Sudoku."
    App Picker
  • "Sudoku in Space is the sudoku game for people who thought they couldn't play sudoku!...If you've always struggled with learning how to play Sudoku or you’re looking for a new way to play, you’ll definitely enjoy Sudoku in Space."
    In Our Spare Time
  • "My Go-to Game!! Before this game, I had never played an electronic version of Sudoku. WOW!! The format for this game allows you to switch between Numbers with Colors, or just Colors.(I like Just Colors, as it helps me to see patterns easier) The format for "penciling in" those spots that you are unsure of is perfect for not confusing you as you narrow down the right option. Also, Sudoku in Space has so many challenging stages to beat that I have been playing a few rounds a day, and have barely made a dent them!! This game has so many well thought out features, it has surprisingly become a part of my daily fun." - 5 out of 5 stars - Loren Marvin (via Google Play)
  • "I have been looking over the market place for three years for a simple, non-adfilled, non spyware junk color Sudoku game and despite there being 50 some odd apps out there, they all sucked. This app has a simple interface, a notation function, several hundred puzzles of varying difficulty, and a decent interface. I can't believe it took 5 years of android smart phone dominance to get such a simple game in the market, but Napland Games has finally delivered." - 5 out of 5 stars - Ryan (Amazon App Store)
  • Innovative and Compelling Design ★★★★★
    by Dan_T_C - Version - 1.01.04 - Oct 16, 2014 (iTunes App Store)
    Sudoku in space has an atmosphere like no other, making an otherwise boring mathematical game visually pleasing and mentally stimulating. The substitution of colors for numbers makes the concept of sudoku much more evident and easy for a large variety of players to understand! The animations are fantastic - you can actually see lines drawn to confirm the validity of your answer, which will effectively explain your success or your error. The large amount of levels provides for hours of gameplay at varying difficulty settings. I strongly recommend this app for younger sudoku players and amateurs!


So, what's new in Sudoku In Space version 2? Tons!

  • We've added a cute and fun story of Allen the Alien's adventure through space. It's your goal to solve puzzles and collect items to help him get back home to Andromeda Prime. The story extends beyond that in a second chapter. You'll have to play to find out what happens.
  • We took a lot of the graphics and polished them up to make them shinier and have more character. Most changes are minute, but you may notice that the numbers are a bit easier to read.
  • We added some more music tracks. People responded really well to our selection of Adi Goldstein's music. So we added some more of his beautiful tracks to the mix. 
  • Another awesome improvement we made is that you can now switch to "speed input" mode. Our initial mode of input was geared toward newcomers to Sudoku and we believe it works well for them. However, it can be a bit slow and tedious for more experienced players. So now you can switch on speed input and you stay on fill-in mode, pencil-in mode, or clear mode and you can change colors however you want. So if you know where all the greens need to go just select fill-in mode (the green check), the green color button, and then keep tapping the cells on the board you want to fill in until you're done! This has cut my personal time by about 1/3. I think many of you will really enjoy this feature.
  • We've also added a bunch of more puzzles. Everyone gets 300 more puzzles for free and we put in puzzle packs that you can buy. The puzzle packs each contain 300 more puzzles. One pack for each difficulty level other than beginner. 
There are also a lot of little improvements that will make your Sudoku playing experience even more enjoyable. You'll have to play to find out what those are though! 


We've had a lot of fun making Sudoku In Space and love seeing so many people getting enjoyment out of it. Now we just need to climb the charts so that others can find the game more easily! In that regards please share the game with your friends. Your support means a lot to us. Links to all of the stores can be found here: www.NaplandGames.com/SudokuInSpace


Now we're off to the next project which will be my first game built using Unity 3D. They finally have their UI system available and I've really been enjoying it. Even with my limited experience the UI system in Unity will save me a ton of time. I've been playing around with it and it took me about a day to set up a really good standard template that has UI and features I regularly need. 


I'll also be starting work on a big update to Wordspionage soon. We plan to add chat, quick match, and a few other options that have been on the list for a long time. If you're interested in helping beta test then please contact me.


I hope everyone had a safe and fulfilling Thanksgiving holiday. We're so stuffed all we can do is play Sudoku In Space and Wordspionage.


Thanks for reading!

2014/11/01

Adventures in Alternative App Markets

This past month I have been focusing a lot on getting our latest game, Sudoku In Space, on as many app markets as possible. I found this great article on Gamasutra, by Arturs Sosins, detailing his experience and stats for downloads on a variety of Android markets. And I realized we were really missing out on a lot of possible exposure.

Unfortunately, our installs have not yet been as many as what Arturs experienced, but his game, Sushi The Fish, is casual-arcade whereas Sudoku In Space is a more niche puzzle game (although we're trying to push it into the casual category more). I'll make a full report of our experience in January when we have enough numbers to make comparison to Arturs' numbers and I'll be using Google Play downloads as a baseline factor so that we can do a more meaningful comparison.

Here's Arturs' numbers for a quick reference:
MarketsAfter Month 1After Month 2Increase%
Slideme1548181927117.5
GetJar866943778.9
Vodafone128325197153.9
AndroidPit387638100
Opera2932310.3
TStore24431979.1
Soc.io181915.55
Google Play175336211.8
1mobile111219
Insyde63024400
Amazon51914280
Applandic2200
AppsLib1100
Camangi1100
Yandex5500
Nokia101000
Appsberry2200
andapponline0000
aptoide0000
mobogenie0000
cnet0000
Total:27113392+68125.12



On to the experience!

We found that most of the stores do not offer an experience for the developer that is anything near what Google Play offers, a couple come close. We started working on the list from the top. One of our main challenges is that Sudoku In Space offers IAP through Google Play and only some of these stores will allow it, but most will not consider your game for promotion if you do so. I tinkered around with AGK's java setup and found that it would take me a long time to set up OpenIAB (an alternative SDK that allows you to easily set up IAP for a bunch of different stores). I asked the folks at AGK if this were a possibility anytime in the future and they hope to look at it after v2 is completed. Since I couldn't wait that long we decided to release 2 versions of Sudoku In Space on the stores, paid and free with ads. Currently we only offer ad removal as an IAP, but very soon we are expanding the game to have more puzzle packs which will require more IAP and similarly drive up the price of the paid versions.

The next challenge was getting information from many of these markets so that we could use a deep link URL to link players directly to the paid version of the app in the same market that they downloaded the free version from. Some sites offered up this information readily, some responded to our question by email, some I figured out on my own, and some still have yet to reply after 2 weeks. Interestingly, many of them use market:// URL schemes exactly like Google Play, so that made life a little easier, though I think I'd rather they used their own URL scheme to ensure that the player is directed to the same store that they downloaded the free version from.

Now we've got 2 versions of the app and I've weeded out the stores that fail to give any response to emails. Next up we needed to determine how to get paid and set up payment info. This weeded out a few more stores for us because, apparently, our bank doesn't accept international wire transfers (who would have thought?).

Stores unresponsive to emails:
Vodafone, AndroidPit, 1Mobile, Camangi, Andapponline
Stores that only pay through international wire transfer:
Vodafone, T-store, Yandex

The biggest losses are Yandex and T-Store which are both fairly large markets and we were hoping to use T-Store to help us with our distribution in S.Korea as the app is fully translated into Korean. T-Store was very responsive to emails.

With a few stores removed from the list and information on how to deep link into their market apps we started down the road of getting the app up on as many stores as possible. First we started with GetJar and Opera because they were very easy. The websites are barebones for the developer portals, but they work. So far our download experience on both sites has not been great. The sites report any clicks to the download links. So what we thought were 100s of downloads turned into very few actual installs. I can't explain this. Our permissions are bare minimum and the APK is under 15MB. So why would folks click to download but never install? Unfortunately, we'll never know, but it appears GetJar and Opera get a lot of junk clicks. I'm glad I got these two first because I was not using unique app IDs for Chartboost for each store so that we could accurately track installs. I quickly factored that in so we had more accurate analytics, but I left GetJar and Opera builds alone for the sake of time and Google Play provides enough information so I can compare later.

SlideME
After those two I started working on the other stores. SlideME was a pleasure to work with and we are fortunate enough that they started featuring our app about a week after we uploaded it. From Oct 24th to Oct 28th we've seen an average of about 50 installs per day from them. Their site reports quite a few more downloads, but we're focusing on installs because those are the ones that will earn us money. Their support has been responsive to our questions and putting the app up was pretty simple.

Soc.io
This store has offered us really nice support. Unfortunately, I still can't get their upload forms to work. They seem to think it is something to do with my ISP, but I don't know how my ISP could be causing their forms to show images from their website header in the fields where it should be showing my screenshots I'm uploading... At any rate, they eventually offered to submit it manually and we're now waiting on their review.

Nokia
This site is well documented and really needs it because their site is a bit confusing. All in all it was not hard to figure out and get our app up there. They are pretty slow to do their QA review. We uploaded on the 16th and it is now 12 days later. If we exclude weekend days then it is 8 days. They say it will appear on the store 5-7 business days after QA review. Trying to wait patiently...

Aptoide
Support from Aptoide has been very responsive, but we've had a lot of issues. We went through a certification process which I had hoped would improve our desirability to their users and speed up the publishing process. Their forms were confusing. They show multiple language support and although your images disappear from the form when changing to a different translation you cannot upload different images. It took a week of back and forth with their support to finally get the app to show up on their market app. It is still showing that one of the screenshots is missing. Also the app has been published for a week there and still has yet to see a single download. They've given us some market credits to spend on their promotion / ad campaigns. Hopefully that will result in something as this one was a lot of effort.

Mobogenie
Easy upload. They don't appear to do paid apps. Still waiting on email response from them about that or if we can use Google IAB. I'm imagining we can, but I like to confirm. They offer a free promotional package worth "$20,000" if you implement their SDK, but they don't give you the opportunity to find the SDK anywhere and don't respond to emails. We'll see how it goes.

CNet
This seems more like a marketing site to advertise your app than it is an actual Android market. No downloads after a week. The good thing is that it is easy to put up your app. Just add the Google Play link and it is there. Seems like you either need to get an editor's review or pay for advertisement to get anywhere. Even massively popular games like Plants vs. Zombie 2 has only 1,800 downloads via CNet. Pretty much every game that has downloads there has editors reviews. Interestingly one of the most downloaded apps is 1Mobile which is another Android market.

Appseeq
Real easy to set up. Again this seems more like a site that you need to advertise on to get anywhere. This only links to Google Play so it quick and simple.

In January I plan to follow up on this article with some hard stats and a baseline comparison to Arturs' data. At this point I'd say that every Android developer should at least be putting their app up on SlideMe. It seems to have a lot of reach and visibility for small fries like us is actually possible.

Until next time!
Thanks for reading!


2014/10/01

Sudoku In Space for a Healthy Mind


Ever since I was young I found myself fascinated with puzzles. Mainly the type of puzzles where you put things in some sort of order. I didn't really realize how much I enjoy a simple relaxing puzzle until I finished making Sudoku In Space and started playing it. It has also made me realize my love of puzzles and why I'm gravitating toward making puzzle games.

Puzzles can be frustrating, but at the same time they can be relaxing. Once you get the hang of how to do a puzzle it can provide a meditative experience. Sudoku is a great puzzle for this type of response. It doesn't take a lot to learn how to solve a Sudoku puzzle, but it does require a lot of your focus and attention. I tend to focus so well on the puzzle that I often don't think of much else. In this respect it has been great once again playing Sudoku. It creates a nice, simple, and healthy escape. Once I near the completion of a Sudoku puzzle any frustration from the process melts away and I get a nice reward from it.

It's impossible to control the world and put much order to it. Most people really enjoy order and avoid chaos as it can be a source of stress. Even if you're not aware of it, nearly all of us are looking to put order to our worlds and avoid chaos and disorder. I feel that Sudoku is a great opportunity to experience this. You can't control the world but you can solve a Sudoku puzzle.

Sudoku provides a therapeutic relief of stress by allowing you to put something into order. Sudoku In Space attempts to take this further by putting you in an outer space atmosphere with really great relaxing music by Adi Goldstein, twinkling special effects, and unintrusive sound effects. Allen the Alien welcomes you to his world of puzzles in space and I find it hard to stay away too long. Soon we plan to add a story for you to adventure with Allen through the universe so that the game provides a more unique and immersive experience than any Sudoku game has ever attempted.

While exploring my renewed interest in Sudoku I've found a plethora of articles on the benefits of Sudoku for your mind. My favorite, the meditative experience, is discussed above, but there are many others which I feel worth mentioning here:


  • Sudoku can improve your memory - Since memory and logic work together so closely Sudoku enables you to use exercise them in a fun way. The memory component of Sudoku is not very taxing. You're able to visually examine the game board for the missing numbers (or colors!), but whether you know it or not, you're still exercising your memory. Especially when you're eliminating colors or numbers as possibilities for a cell. As you become more familiar with Sudoku I'm sure you'll find yourself relying on your memory more and more instead of penciling in guesses all too often.
  • As with most puzzles Sudoku stimulates your mind. The way Sudoku does this is it gets you to constantly think logically about where the next number or color should be placed. Also, you might not be aware, but when solving a Sudoku puzzle you're building an algorithm in your mind of how to solve the puzzle. The more you play the better this algorithm will become and the stronger your logic can become in every day tasks.
  • Sudoku also will help decrease your decision making time. After playing a few puzzles you will notice that the time to solve the puzzle is taking less and you're able to make decisions quicker. At the same time your concentration level is increasing because the game requires most, if not all, of your attention. Your re-focus skills are also being sharpened because when you put down the game you can quickly jump back into it and keep going where you left off. The more you do this the more you'll notice the benefit.
  • For a long time I have known that puzzles and other brain exercises help in reducing the chance of Alzheimer's. However, I was not aware that games like Sudoku have been found very beneficial in strengthening your "cognitive reserve" which can help to lessen the effects of schizophrenia, bipolar disorder, and depression (see Sudoku to Beat Schizophrenia - D. Bradley). It has also been claimed that Sudoku may even increase your IQ (see Solving Sudoku Helps Improve IQ). I'm amazed that puzzles can have this great of an effect on the mind.


One of the design goals of Sudoku In Space was to make Sudoku more appealing to children and people who don't like numbers. We provide a small set of 4x4 puzzles so that someone who's never played can get some experience without being overwhelmed. The space theme, bright colors and cheerful effects are there to give the game a bit more interest than the typical plain Sudoku game where it is just numbers in a grid. I really wanted to try to bring color Sudoku to the forefront with this game, because I know that, for many, just seeing numbers in a game can be a complete turn off. I've been pleasantly surprised at how many people are playing with just the colors. It has also been interesting to me personally to see how much faster I can solve a puzzle when the numbers are on because I have both numbers and colors to rely on.

With so many games out there that simply provide distractions, I strongly believe that Sudoku In Space supplies players with more than just a distraction - it strengthens your mind! There's nothing like improving yourself while having fun. It's one of my favorite things to do and I hope to share that passion with the world as Napland Games creates more puzzle games in the future. I have many puzzle games planned for you all and I hope that they will be just as interesting (if not more) than Sudoku In Space. The next game I'm working on right now is a "Simon Says" / copy cat style game where you're tasked with disarming time bombs. I'm not certain that the cognitive benefits will be as great as they are with Sudoku In Space, but I hope that we can provide a fun and challenging experience that also helps to strengthen your mind.

As always, thanks for reading!

Play Sudoku In Space for FREE:
Google Play
iTunes

2014/09/04

Asynchronous Animations



The more animation and movement you have in your games, the more flashy and appealing they can be to players. In this tutorial I'll be showing you how to build animations that run in their own self-contained functions.

This allows them to be interrupted at any time, allows for simultaneous user input and anything else you wish to occur in your game loop without having to wait for the animation script to finish. In our most recent game, Sudoku In Space, we make extensive use of this for background animations, main menu animations, and particle animations when you make a completion move. Since we use a timer in Sudoku In Space, it is important to not prevent the user from interacting with the game while an animation is happening for a row, column, or block completion.

To keep things simple this tutorial will only show the background animations for the larger stars that wink in and out. If you're interested in seeing how I did the particle animations just let me know and I'll do it in a future continuation of this article.

First, we'll need a user defined type and a variable of that type. In Sudoku In Space I actually made this variable global so that all of our screens could have the background animation, however, it is not necessary if you are using the animation in only one area of your game since the function to handle the animation has a feedback loop. A feedback loop is when a function accepts input of a specific variable and then outputs to that same variable.

The following setup will make a star appear in a random location on the screen, color it one of three different colors at random, and make it grow and then shrink.


Our type:
type star_type 
    max_W# as float
    dir as integer
    spriteID as integer
    abs_max_W# as float
    abs_min_W# as float
    time as integer
endtype
  • max_W# will contain a random size that we want the star to grow to. 
  • dir is the direction of the sprite's growth. 
  • spriteID is the sprite ID of the star. 
  • abs_max_W# is the maximum width the star should ever grow to so that the sprite's image doesn't become fuzzy or larger than we want it to appear. 
  • abs_min_W# is the minimum width the star should ever shrink to and serves as a starting and stopping point for the shrink/grow animation. 
  • time will allow us to control when the star reappears. 

Now let's look at the self-contained feedback loop function:
function HandleStar(star as star_type , fFT# , tT#)    // fFT# is frame time as obtained from GetFrameTime()
 // in the main loop
    // tT# is the total time we want the growing or shrinking to take
 
    //the depth of the sprite
    depth = 2000 
 
    // If the direction is 0 then this is the first time we're 
    // running the function so we need to set direction to 1 (grow).
    if star.dir = 0
        star.dir = 1
    endif
 
    //Instantiate the sprite if it doesn't already exist.
    if GetSpriteExists(star.spriteID) = 0
        img = LoadImage(“star.png”)
        star.spriteID = CreateSprite(img) 
 
        // We'll allow the star to grow up to 5%
        // of the screen width.
        star.abs_max_W# = 5.0
 
        // The star image was designed for an 800px wide screen
        // We want to prevent it from being smaller
        // than 10px at 800px resolution
        star.abs_min_W# = 100.0 * 10.0 / 800.0
 
        // Initialize the size at the minimum size
        // so that it will grow
        SetSpriteSize(star.spriteID , star.abs_min_W# , -1)
 
        // Initialize the max_W# variable to a negative so that
        // we can set it to a random value
        star.max_W# = -1.0
    endif
 
 
    // Initialize the animation variables – this happens every time the 
    // star is shrunk to its minimum size as well
    // as the first run of the function
    //
    // If the max_W# variable is negative then we need to
    // determine a random size we'll allow the star to grow to
    // as well set it's initial position, color, and angle
 
    if star.max_W# < 0.001
        // Determine a random value for the max width to grow to.
        // Here I multiply the maximum and minimum by 100 so that I can 
        // make use of the random() function which only works on integers
        // and then divide by 100. This way the star can have a max width
        // with fractional percentages.
        r_low = floor(100.0 * (star.abs_max_W# - 0.4 * star.abs_max_W#))
        r_high = floor(100.0 * (star.abs_max_W#))
        star.max_W# = random(r_low , r_high) / 100.0
 
        // Similar is done to find the initial position
        r_x_low = 100
        r_x_high = floor(100.0 * (100.0 - star.max_W#))
        x# = random(r_x_low , r_x_high) / 100.0
 
        r_y_low = 100
        maxY# = 100.0 – GetSpriteHeight(star.spriteID)
        r_y_high = floor(100.0 * maxY#)
        
        y# = random(r_y_low , r_y_high) / 100.0
 
        // Now we can position the star
        SetSpritePosition(star.spriteID , x# , y#)
 
        // Give it a random angle for more randomness
        r_angle = random(1,360)
        SetSpriteAngle(star.spriteID , r_angle)
 
        // Set the time at which we initialized the star and make it visible.
        star.time = GetMilliseconds()
        SetSpriteVisible(star.spriteID , 1)
 
        // Set random color
        r_color_num = random(1,4)
        if r_color_num = 1
            color$ = "200,255,255" //blue-ish
        elseif r_color_num = 2
            color$ = "255,255,200" //purple-ish
        else
            color$ = “255,255,255” //white
        endif
 
        // This is a home brewed function I use that makes it a
        // Little easier to set sprite colors –
        // included after this function
        _SetSpriteColor(star.spriteID , color$)
    endif
 
 
 
    // Now we can actually animate the growth and shrinking of the star.
 
    // Determine the distance between the random width we grow to
    // and the minimum allowable width.
    dist# = star.max_W# - star.abs_min_W#
 
    // Get the current widht of the star so we can see if it should still 
    // grow or shrink
    current_W# = GetSpriteWidth(star.spriteID)
 
    // Initialize this to a negative so that we can skip the sizing of 
    // the star when we're pausing between appearances.
    new_W# = -1.0
 
    // If the direction is 1 then we are growing the star
    if star.dir = 1 
 
        // If the current width is less than the maximum 
        // width we want the star to grow to then grow it!
        // Otherwise stop growing and negate the direction
        // So that it will start shrinking.
        if current_W# < star.max_W#
            // Here we use a linear equation based on frame time (fFT#)
            // to increase the size of the sprite over time tT#
            new_W# = current_W# + dist# * fFT# / tT#
        else
            // We're done growing, so negate the direction
            new_W# = star.max_W#
            star.dir = star.dir * -1 
        endif
 
 
    elseif star.dir = -1  //Otherwise we're shrinking the star
 
        // If the current width of the star is still greater
        // than the allowable minimum width then grow it.
        // Otherwise stop.
        if current_W# > star.abs_min_W#
            // Use the same linear equation for growth based on frame time,
            // but negate the distance variable
            // so we're making the width smaller.
            new_W# = current_W# - dist# * fFT# / tT#
 
        else
 
            // We're done growing so set the new width to the minimum width
            // Doing this helps if a large number of frames were skipped.
            new_W# = star.abs_min_W#
 
            // Hide the star for now because we're going to wait to make
            // it reappear
            SetSpriteVisible(star.spriteID , 0)
 
            // Now when it is time, set max_W# to a negative to 
            // reinitialize the star's animation (random max w, color, position)
            // The calculation here is tT# in seconds which needed to be 
            // converted to milliseconds – so basically we're adding 3 seconds
            // before the star will restart the animation.
            if GetMilliseconds() > star.time + 3000.0 * tT#
                star.max_W# = -1.0
                star.dir = star.dir * -1
                
            endif
        endif
    endif
 
    // Only grow and reposition the star if new_W# if positive.
    // This allows us to skip over the sizing when the star is waiting 
    // to be reinitialized
    if new_W# > 0.0
        x# = GetSpriteXByOffset(star.spriteID)
        y# = GetSpriteYByOffset(star.spriteID)
        SetSpriteSize(star.spriteID , new_W# , -1)
        SetSpritePositionByOffset(star.spriteID , x# , y#)
    endif
endfunction star


This is used to color sprites with only a comma separated string for the R, G, and B values.
function _SetSpriteColor(iID , sColor$) 
     if GetSpriteExists(iID) = 1
          r = val(GetStringToken(sColor$ , "," , 1))
          g = val(GetStringToken(sColor$ , "," , 2))
          b = val(GetStringToken(sColor$ , "," , 3))
          SetSpriteColor(iID , r , g , b , 255)
     endif
endfunction


Now all that needs to be done is run this function in a loop like so:
myStar as star_typeshrink_grow_time# = 0.50
 
do
    ft# = GetFrameTime()
    myStar = HandleStar(myStar , ft# , shrink_grow_time#)
    Sync()
loop


Hopefully you can also see how this can be extended for more complex animations where there are various states for the animation. One example you can see is in Sudoku In Space on the home screen. Our game character, Allen the Alien, flies onto the screen, appears to hover with some up and down tweening, and continues to do so until the player presses the play button, at which time he flies away. I also built that animation so it can be interrupted and the player does not need to wait for Allen to be in the center of the screen before they can tap a button. Tapping a button will simply allow Allen to keep moving across the screen until he's gone.

In Sudoku In Space we take advantage of this method for many of the animations so that user input is not hampered. Please take a look at the game to see these animations in action. The most complex of them are the particle animations for when you complete a puzzle. If you'd like to see an example of that in a future tutorial, just let me know (naplandgames@gmail.com)! Sudoku In Space is free on Google Play and the App Store.

2014/08/01

Loading Animations and Staggering Asset Creation

Original article in the TGC Newsletter can be viewed here.
agk mastery
In our last game, Shape Time, I ended up deciding to load quite a few assets at launch time. I implemented a loading animation, but it seemed to actually slow down the loading process a bit. In our next game I wanted to optimize this as much as possible to improve loading time. The reason the loading animation was affecting the speed of loading assets was that I was calling Sync() as frequently as possible so that the animation would update. Here's an example how it was done (the loading animation in this case is a sprite with frames):

for num = 1 to 10
 fileName$ = “atlas” + str(num) + “.png”
 LoadImage(i , fileName$)
 Sync()
next num

Loading...
This would certainly animate the loading sprite just fine, but I knew there had to be a better, more universal way. The problem is that Sync() is limited by the frame rate of the device. So if you've got a device that is capable of only 30FPS (fairly common in with mobile devices) then you are limiting your loading sequence to one file every 30th of a second. This is not that bad for files, but if you want to display the animation while other, possibly faster processes take place, then you're likely slowing them down. 
Let's look at an example of a script that might be slowed by a Sync() call:

for num = 1 to 100
 if num = 1
  img = LoadImage(“yourImage.png”)
  CreateSprite(num , img)
  SetSpriteSize(num , 10 , -1)
  SetSpriteDepth(num , 100)
 else
  CloneSprite(num , 1)
 endif
 SetSpritePosition(random(0,90) , random(0,90))
next num

This script is pretty fast, but it might take up to a second or more on mobile devices. If we add a Sync() call to this routine so we can show a loading animation then the routine will be much slower because the Sync() call will render the screen and limit the engine to whatever the available FPS is. At 30FPS this script would require 3.33 seconds to run. Without Sync() it may take considerably less time, however, that would be dependent the device.
So how do we speed this up? The answer is to only call Sync() only when it is time that you expect to have a frame drawn to the screen. The example below uses a literal 1/60 of a second between Sync() calls. There are more advance ways to determine the expected time to call Sync(), but this is sufficient for showing a loading animation. The example simply rotates a loading image for simplicity.

global gLoading = 0
global gLoading_LastSync# = 0.0 
function ToggleLoadingAnim(iOnOff)
    if GetSpriteExists(gLoading) = 0
        gLoading = LoadSprite(“loading.png”)
        SetSpriteSize(gLoading , 10  , -1)
 SetSpriteDepth(gLoading , 1)
    endif
    if iOnOff = 1
        SetSpritePositionByOffset(gLoading , 50 , 50)
        gLoading_LastSync# = 0.0
    else
        SetSpritePositionByOffset(gLoading , -500 , -500)
    endif
endfunction 
function LoadingAnim_Sync()
    time_now# = timer()
    dT# = time_now# - gLoading_LastSync#
    if dT# > 0.017
        ft# = GetFrameTime()
        gLoading_LastSync# = time_now#
        t# = 3.0
        angle# = GetSpriteAngle(gLoading) + 360.0 * ft# / t#
        angle_int = round(angle#)
        angle_int = mod(angle_int , 360)
        SetSpriteAngle(gLoading , angle_int)
        Sync()
    endif
endfunction

The code consists of 2 globals and 2 simple functions. ToggleLoadingAnim() is used to set up the sprite and globals, position the sprite, and make it visible. This should be called before any loop that you want to overlay the loading animation on. You also call this to hide the animation. Inside of the loop you will use LoadingAnim_Sync() in place of a standard Sync() call. So our original code that loaded 100 sprites becomes this:

ToggleLoadingAnim(1)
for num = 1 to 100
 if num = 1
  img = LoadImage(“yourImage.png”)
  CreateSprite(num , img)
  SetSpriteSize(num , 10 , -1)
  SetSpriteDepth(num , 100)
 else
  CloneSprite(num , 1)
 endif
 SetSpritePosition(random(0,90) , random(0,90))
 LoadingAnim_Sync()
next num
ToggleLoadingAnim(0)

That's it! Now you can load up lots of assets and have a nice loading animation so that the user doesn't wonder what's going on. Remember, folks don't like to wait even a second for something to happen these days, so make sure to use a method like this to let them know your game is still running. Now if we could only thread the loading animation so that we can draw to the screen while loading large files. Another thing you might want to consider is that you might get large values out of GetFrameTime() while loading files. So you might want to limit the ft# variable when you're translating a sprite (moving or rotating). Either way you'll get a bit of jitter in your sprite's translation because Sync() just can't be called fast enough due to the time it took to load a file. 
Users don't want to wait long for your game to load, so it's important to only load at launch time the assets you need to display the title screen / main menu of your game. After that you can stagger the creation of assets by creating only a handful every frame and then if they aren't all loaded when they're actually needed, you can finish up loading them and show a loading animation. In our next game I did this for our level selection menu. 
The level selection menu needed 200 buttons, 3 star sprites for each button, and 2 text objects for each button – that's a total of 800 sprites and 400 text objects! This was just way too slow to load on demand. So what I did is create a handful of these objects every frame during the Sync() calls. This way the buttons could be loaded while the player is looking at the main menu which incorporates a short animation of about 2 seconds. Let's examine how we can do something like this.
Make an array that will hold the object IDs for the level buttons and a global to hold an image ID:

#constant LVL_BTN_COUNT 200
dim _lvl_btns[LVL_BTN_COUNT]
_lvl_btns[0] = 0
global gLvlBtnImg as integer

Create a new function that includes the Sync() command and will create a handful of objects each frame:

function LvlBtn_Sync()
 //we'll store the count of created objects here
 count = _lvl_btns[0]
 if count < LVL_BTN_COUNT
  skip_objects = 0
  if GetImageExists(gLvlBtnImg) = 0
   gLvlBtnImg = LoadImage(“mybutton.png”)
   //since we're loading an image this frame
   //let's not make any sprites or text
   skip_objects = 1
  endif
    if skip_objects = 0
   baseSprite = _lvl_btns[1]
   needBaseSprite = 0
   if GetSpriteExists(baseSprite) = 0
    needBaseSprite = 1
    endif
   for i = 1 to 10
      if needBaseSprite = 1
    needBaseSprite = 0
    thisSprite = CreateSprite(gLvlBtnImg)
    SetSpriteSize(thisSprite , 10 , 5)
    SetSpritePosition(thisSprite , -1000 , -1000)
    SetSpriteDepth(thisSprite , 10)
   else
    thisSprite = CloneSprite(baseSprite)
   endif
   _lvl_btns[count] = thisSprite
   inc _lvl_btns[0]
  next i 
  endif
 endif
 Sync()
endfunction

Now we're creating those 200 sprites in 20 frames. At 30FPS we'll have created them in less than 1 second! They should all be prepared by the time we need to show the level selection menu, but just in case, you should also have a catch that ensures they're all created when the user goes to access the level selection screen. If they aren't all made then you can show the loading animation to tell the user the app is working on something.
This method can be used in a variety of ways. For example, while the user is playing one level you can slowly start loading the assets for the next level in each frame. This should be done with care so that it doesn't drag down performance when the action of the game is happening. As long as the sprites are off-screen they won't be rendered and won't cause a drag on the engine.
Next time I'll discuss methods on how to “thread” animations in a loop so that you can easily contain different animations in separate functions. Oftentimes we need to have a lot of things going on simultaneously and they cannot interrupt each other or user input. Thanks for reading!

2014/07/04

App usage analytics and a postmortem of Shape Time

Originally published in the TGC Newsletter - July 1, 2014

agk mastery

App usage analytics and a postmortem of Shape Time

So, you've got what you think is a great idea for a game. You've put it together, people on your development team and beta testers have enjoyed it. Other than a simple download count, how do you know if others are enjoying it (or not)? Players don't often leave feedback unless they really like or really dislike your game. So how do we get information about the majority of people playing our game? Are people playing 10 levels and then hitting a wall which they aren't interested in attempting to pass? App usage analytics are key to finding this information. 

Analysis
With our most recent game, Shape Time, we thought we were doing so much right. We really liked the simple, minimalistic graphics (following in the style of Dots and Threes!, the gameplay was very challenging as we intended, the mechanics worked well, and the game tested very well with a small beta group (about a dozen people). 

After launch our very first review on the Play Store was bad. We implemented an “energy” mechanic where you only had 5 lives and after using them up would have to wait 15 minutes for a new one. This didn't go over well. Although we planned it out as best as possible, it just seemed the game didn't have enough flare to justify this mechanic. 

We watched our app data and saw that so many players were getting very low points. In beta testing we estimated that 75 points was low enough for the first badge reward. After launch most players were maxing out at about half of this. This indicates to us that the gameplay mechanic may be too challenging for users. Also we could tell that they only ran the game a handful of times. What this means is that the game just isn't compelling enough and when they run out of lives they just never come back.

We had a few professional reviews that were good, but didn't go into great depth. Eventually we received an in-depth review that was quite unfavorable. It pointed out many of the issues that the data was pointing to: The gameplay mechanic was too difficult, the graphics impressed low quality instead of minimalistic style, the in app purchases were too unfavorable. So we had to act and are now giving the game a fairly thorough overhaul to try and save what we think is a good game.

How did we get all of this data and what data did we track?

ShapeTime
We used simple HTTP commands in AGK to send off data to our server whenever certain conditions would happen in the game. In previous articles I've discussed how to set up leaderboards as well as send files from your app to a server. The same method for leaderboards is what is used to send app usage data to a server. There's just more data to send! With Shape Time we use two different database tables. One table contains data for each game session. The other table contains overall statistics for each user. Both tables share a user ID for each player so that they can easily be cross referenced in reports. Each time a player completes a game session or makes a purchase, we then send data off to the server. If you are not expecting feedback from the server then you can just send out the data with an async HTTP command (we did this with purchases). If you are waiting for data back then still use the async HTTP command, but wait for the data to return as was illustrated in the leaderboards tutorial. We do this for all the session data because we are also checking for a high score at the same time.

The data we tracked was straightforward. For sessions we wanted to know how long each game lasted, how many powerups were used, and what the score was. For users we wanted to know how many sessions they had played, their high score, their purchases, and their general geographic location (country and region). The information that can be inferred from just this simple data is great.  Geographic information lets us know if non-english speaking players are having a greater difficulty playing the game (this was not our case). Most importantly we could see that players on average were only getting a high score of around 25 points. Only 17% of our players were actually using the powerups (the game has explanations of these when they are first rewarded). We could see the average game session time was only about 2 minutes which was much lower than the beta test group.

The data was so disappointing that we were likely going to abandon the game and call it a loss. We tested it out well, we liked the mechanics, we knew that the high scores achieved in-house and by beta testers were achievable (my wife still has the all time high score of 182 points with very few players even coming close – my best is 129), we liked the music and graphics. Therefore we were ready to call it a loss until we received a brutally honest professional review. 

The review pointed out what the data was telling us and it also gave us hope. It made suggestions on how we could improve the game. After matching that information up to the data we feel that Shape Time could still possibly be a success. 

The gameplay mechanics were possibly too challenging: it is possible that the way the bar moves in the game (it snaps to the closest edge) is an extra challenge for players to overcome. Though beta testers were quite comfortable with it after a play or two we can understand how the snapping may actually feel more like a restriction and hindrance than assistance as it was meant to be. The snapping feature is now optional and we're allowing for more margin of error in matching the shapes up with the bar.
The minimalistic style was viewed as too basic and lacking depth. When designing the game we thought it would be best to keep it free of extra distractions. This meant we wanted things to look flat and simple and at the same time we didn't want a lot of special effects going on. Unfortunately this ended up being interpreted as plain and boring. We're now adding a lot more style to the game and are learning that we really need to spend a lot of time on making the graphics flashy. The graphics style will now be a crayon drawing style with the elements looking like construction paper cutouts. We're adding some characters to the game (the brush and clock powerup icons will now be cartoon characters) and giving them animations. We're also adding particle animations to give the game greater flare and interest.
The life restriction mechanic and power up rewarding were unfortunately too restrictive. We hoped the game was interesting and addictive enough to justify in app purchases to help lift these restrictions, but it is not. The game will no longer have any restriction on game play and powerups will be rewarded much more generously. We've removed all in-app purchases except for ad removal (we need some mechanic for monetization!).

Beyond challenging, but rewarding, gameplay

What is it that makes some games such a huge success? What makes a game addictive? We should have realized this sooner, but didn't. 


Games that have characters in them are much larger successes than ones that don't. Candy Crush Saga, Clash of Clans, Temple Run, Flappy Bird, and even Threes! (the tiles have little faces on them) all use characters in the games. Characters can be extremely important because they immediately evoke an emotional response. A smile on a character's face is immediately recognized by our brains and sets off endorphins. Make the characters cute and happy and most people will automatically have a good response to them. This puts them in the mood to have fun. The brain essentially says “There's something happy. I'm going to share in its good time.”

People are accustomed to high levels of stimulus. Watch any recent children's movies, the stimulus level is very high. The most successful movies like Godzilla, Iron Man, etc. also have a high level of stimulus. Play Candy Crush Saga for a bit, the stimulus level is also extremely high. There are a lot of very minor animations (flare effects, characters moving slightly) occurring alongside the major animations (candy movement, row/column clearing). When you perform a good action such as clearing a row of candy there is an explosion of stimuli on the screen.
Sparkle
This heightens the player's emotional state and leaves them wanting more. There is a difficult balance to achieve between stimulating animations and overload. I think the main key here is that you can actually have a lot of very minor animations like particle effects and animations that don't actually change the shape of sprites (Crayon Physics has a neat stop-motion style animation on a lot of things). Most of these things we don't actively pay attention to in games, but they heighten the brain's awareness which makes people more susceptible to rewards and punishments. Try tuning off the TV on a kid when they are watching a cartoon and you can see this effect in action. Make sure to reward players often and provide a lot of stimulus with rewards. Remember you're competing for their emotions.

Once your players have an emotional attachment to a game you can then start introducing mechanics that will force them to make a choice: keep playing or quit. If you're able to create a strong enough of an attachment so that they want (and feel they need) to keep playing then and only then do you have a chance at asking them for money. This is really difficult to do. Many players are immediately turned off by this mechanic and the reason is that they haven't attained enough of an attachment to make it worthwhile. So do this with extreme caution. Also keep in mind that the majority of your players will not purchase anything until they have played many times. So if you do have some restrictive element in your game you need to do a great job of making them want to return. Unfortunately we did not do this with Shape Time and we don't really see it as a possibility in the future with a game that has such short gameplay.

App usage analytics, even the most basic ones, can be a great learning tool to help you understand how to make better games when there is an absence of user feedback. Even successful games should make use of analytics (indeed the most successful do). Analytics provides us with a way to learn what our users are doing when the majority of them are silent. Games need to provide a high level of stimulus to be considered entertaining enough to get players to come back for more. Follow the leaders and don't just emulate their game mechanics, but also look closely at the little things they do in games. These are often unnoticeable, but aggregate into a great user experience. Don't be afraid of failure. No one succeeds on their first attempts. Success even on the 10th attempt is extremely good luck. Failure can be great because it gives you the opportunity to learn. Leverage it for all it is worth. 

I'd love to hear your feedback on the new version of Shape Time. We will announce the release in the Showcase forum as soon as it is ready. Thanks for reading!

2014/06/16

AGK Tutorial - App Data

Originally published in the TGC Newsletter - June 1, 2014
agk mastery

The question of transferring data from your mobile app to a server was brought up by Parry on the AGK forums and I thought I'd share my process for getting app data from a device. The problem is that Android and iOS put app data in a protected folder on the device. If you want to root or jailbreak the device it is easy to get at, but this process can be dangerous and complex. If you just need a peek at your app's data then there is an easier way: add all of the files to a ZIP file and send it to a server.

Setup

To start you'll need a server. You can use your hosted account or a server of your own. If you're on Windows I highly suggest using XAMPP. The setup is quick and they provide ample information on how to get things going (like allowing outside connections to your server). The next thing you'll want to do is set up some PHP code on the server to accept files as well as the code in AGK to send the files. 

AGK code for sending file:
fileID = OpenToWrite("test.txt" , 0)
WriteLine(fileID , "this is a test")
CloseFile(fileID)
cid = CreateHTTPConnection()
SetHTTPHost(cid, "1.1.1.1" , 0) 
SendHTTPFile(cid , "uploadfile.php" , "" , "test.txt")
SetPrintSize(1)
repeat
    if GetHTTPResponseReady( cid ) = 1
        print (GetHTTPResponse( cid ))
    else
        print ( GetHTTPFileProgress( cid ))
    endif
    Sync()
until done = 1
CloseHTTPConnection(cid)
DeleteHTTPConnection(cid)
END
Ensure that the IP Address 1.1.1.1 is replaced with your own IP address or domain (do not include HTTP://).

PHP code for receiving file:
<?PHP
if ($_FILES["myfile"]["error"] > 0){
        echo "Error: " . $_FILES["myfile"]["error"] . "\n";
}
else {
    echo "Upload: " . $_FILES["myfile"]["name"] . "\n";
    echo "Type: " . $_FILES["myfile"]["type"] . "\n";
    echo "Size: " . ($_FILES["myfile"]["size"] / 1024) . " Kb\n";
    echo "Stored in: " . $_FILES["myfile"]["tmp_name"] . " \n";  
    if (file_exists("upload/" . $_FILES["myfile"]["name"]))
    {
        echo $_FILES["myfile"]["name"] . " already exists. \n\n";
    }
    else
    {
        move_uploaded_file($_FILES["myfile"]["tmp_name"], "upload/" . $_FILES["myfile"]["name"]);
        echo "Stored in: " . "upload/" . $_FILES["myfile"]["name"] . " \n\n";
    }
}
?> 

It's as simple as that. Just make sure that the "upload" directory is created on your server before trying to send the file. Now that you have all of that set up and tested we're on to a simple set of code that will get all of the files out of your app's directory:

AGK Code:
function ZipDirectory(sZipFileName$)
 if GetFileExists(sZipFileName$) = 1
  DeleteFile(sZipFileName$)  endif
 doOnce = 0
 makeZip = 0
 repeat
  if doOnce = 0
   doOnce = 1
   thisFile$ = GetFirstFile()
  else
   thisFile$ = GetNextFile()
  endif
  if thisFile$ = “”
   done = 1
  else
   if makeZip = 0
    makeZip = 1
    zipFileID = CreateZip(sZipFileName$)
   endif
   if GetFileExists(thisFile$) = 1
    zipLocation$ = GetFolder() + “/” + thisFile$
    AddZipEntry( zipFileID , thisFile$ ,  zipLocation$)
   endif
  endif
 until done = 1
 if zipFileID > 0
  CloseZip(zipFileID)
 endif
endfunction
As you may imagine you can also do this for every directory and subdirectory for you app's media folder. It is important to note that the directory you are working with is the write directory for the AGK app and not the read directory. The read directory for apps is where all of the media files exist that you package in with your app. On Android and iOS you do not have access to this directory without rooting/jailbreaking the device. This shouldn't matter because these files are the same that you packed with the app and you already have those. The write directory is a “sandbox” area where the app is allowed to write data. This is where any file you create with the app will be stored such as data files, debug logs, etc. 
This method is extremely helpful when developing your app and when it is actually live. When developing your app you can use this to zip up any debug logs and quickly send them to yourself. When the app is live this can be used to send error reports to you when the user experiences issues. In Wordspionage we use this to send error logs whenever a fatal error occurs. This was extremely helpful in the first few months of our release because a few bugs appeared that did not appear in our extensive beta testing. It allowed us to receive data from our users automatically so they didn't need to be bothered or actually need to hit a button to send the report. When these error reports are sent to us they also include a screen shot of the game so we can see any visual clues to the issue. This method was paramount to smoothing out the flaws in the game.