Tuesday, 10 February 2015

How to change screen resolution in Adobe AIR [only Windows]

As you know we can't change resolution size using Adobe AIR. And that is a problem in old hardware where fill rate was low.

I got the answer to most of my problems:
QRes.exe!
http://qres.software.informer.com/1.1/

With that tool you can change the screen resolution in windows using command line.

Example:
"QRes.exe /x:800 /y:600" for 800x600

The idea would be to have that EXE somewhere in the project folder.:
  1. On start, check current resolution and store it.
  2.  Call the other stored resolution we had stored it in options menu or wherever (can be also a fixed resolution of course).
  3.  We send as arguments to that executable and change the resolution to whatever we want (and it's supported by both GPU and screen).
  4.  Play the game / app.
  5.  Finally, on exit, restore the original resolution before closing the app.
 
That's a solution I am going to apply to my current game (Vortex Attack), needed for some of the old hardware I'm taking to the fairs... The problem will come whenever the app crashes or the user ALT-F4 (force quit) the app... Any ideas for that? Is there any event we can use for those two?

Cheers!

Monday, 26 May 2014

Static vs non-static: vars vs consts in Adobe AIR.

I was cleaning the engine that makes Beekyr a good performing game in all platforms. And I just thought , I could try to make it perform even more by converting my static classes and vars to non static.

I got the most bizarre results....

The tests consist in accessing a variable (or const) 1 million times.


Code:
package kaleidoEngine.parts.tests
{
    import citrus.core.CitrusEngine;
    import flash.display.Sprite;
    import flash.utils.getTimer;
    import game.GameVars;
    import game.Main;
    import kaleidoEngine.parts.data.EngineVars;
    /**
     * ...
     * @author Jaime Dominguez
     */
    public class staticPerformance extends Sprite
    {
        private var resultsProperties:Array;
        private var resultsStatics:Array;
        public var tempInt:int;
        public function staticPerformance()
        {
            testA(10000000);
            testB(10000000);
          
            row("*****END TEST*******");
          
        }
      
        private function testB(_REPS:Number):void
        {
            var _game:Main = CitrusEngine.getInstance() as Main;
            var beforeTime:int;
            var afterTime:int;
            var REPS:int = _REPS;
            var i:int;
            var inte:int;
            var c:Class;
            var num:Number;
          
            var resultA:int ;
            var resultB:int ;
      
            tempInt = GameVars.DIFFICULTY_LEVEL;
            var _tempInt:int = GameVars.DIFFICULTY_LEVEL;
            beforeTime = getTimer();
            for (i = 0; i < REPS; ++i)
            {
                num = GameVars.DIFFICULTY_LEVEL;
            }
            afterTime = getTimer();
            resultA = (afterTime-beforeTime);
            row("STATIC VAR INT:" + (afterTime-beforeTime))
          
            beforeTime = getTimer();
            for (i = 0; i < REPS; ++i)
            {
                inte = tempInt;
            }
            afterTime = getTimer();
            resultB = (afterTime-beforeTime);
            row("CLASS VAR INT:" +( afterTime-beforeTime))
          
            beforeTime = getTimer();
            for (i = 0; i < REPS; ++i)
            {
                inte = _tempInt;
            }
            afterTime = getTimer();
            resultB = (afterTime-beforeTime);
            row("FUNCTION CONST INT:" +( afterTime-beforeTime))
          
            if (resultA > resultB) row ("LOCAL VAR INT IS FASTER");
            else row ("NON-LOCAL VAR INT IS FASTER");
          
          
        }
      
        public function row(text:*):void {
            trace ("[TESTING] " + text);
        }
      
        public function testA(_REPS:uint):void{
          

            var _game:Main = CitrusEngine.getInstance() as Main;
            var beforeTime:int;
            var afterTime:int;
            var REPS:int = _REPS;
            var i:int;
            var inte:int;
            var c:Class;
            var num:Number;

            row("*****Testing ["+_REPS+"]*******");

            resultsProperties = new Array();
            resultsStatics = new Array();
          
            beforeTime = getTimer();
            for (i = 0; i < REPS; ++i)
            {
                num = _game.gameVars.CONST_NUM;
            }
            afterTime = getTimer();
            resultsProperties.push(afterTime-beforeTime)
          
            beforeTime = getTimer();
            for (i = 0; i < REPS; ++i)
            {
                num = _game.gameVars.CONST_INT;
            }
            afterTime = getTimer();
            resultsProperties.push(afterTime-beforeTime)
          
            beforeTime = getTimer();
            for (i = 0; i < REPS; ++i)
            {
                num = _game.gameVars.VAR_NUM;
            }
            afterTime = getTimer();
            resultsProperties.push(afterTime-beforeTime)

            beforeTime = getTimer();
            for (i = 0; i < REPS; ++i)
            {
                inte = _game.gameVars.VAR_INT;
            }
            afterTime = getTimer();
            resultsProperties.push(afterTime-beforeTime)

            row ("------STATICS------");
          
            beforeTime = getTimer();
            for (i = 0; i < REPS; ++i)
            {
                num = GameVars.ST_CONST_NUM;
            }
            afterTime = getTimer();
            resultsStatics.push(afterTime-beforeTime);
          
            beforeTime = getTimer();
            for (i = 0; i < REPS; ++i)
            {
                num = GameVars.ST_CONST_INT;
            }
            afterTime = getTimer();
            resultsStatics.push(afterTime-beforeTime);
          
            beforeTime = getTimer();
            for (i = 0; i < REPS; ++i)
            {
                num = GameVars.ST_VAR_NUM;
            }
            afterTime = getTimer();
            resultsStatics.push(afterTime-beforeTime);
      
            beforeTime = getTimer();
            for (i = 0; i < REPS; ++i)
            {
                inte = GameVars.ST_VAR_INT;
            }
            afterTime = getTimer();
            resultsStatics.push(afterTime-beforeTime);
          
   
            //row("RESULTS :    PROPERTY   VS  STATIC ");
            //row("CONST NUM:" +resultsProperties[0] +" vs. " + resultsStatics[0] )
            if (resultsProperties[0] > resultsStatics[0]) row("CONST NUM: STATIC FASTER");
            else row("CONST NUM: PROPERTY FASTER");
            //row("-----------------");
            //row("CONST INT:" +resultsProperties[1] +" vs. " + resultsStatics[1] )
            if (resultsProperties[1] > resultsStatics[1]) row("CONST INT: STATIC FASTER");
            else row("CONST INT: PROPERTY FASTER");
            //row("-----------------");
            //row("VAR NUM:" +resultsProperties[2] +" vs. " + resultsStatics[2] )
            if (resultsProperties[2] > resultsStatics[2]) row("VAR NUM: STATIC FASTER");
            else row("VAR NUM: PROPERTY FASTER");
            //row("-----------------");
            //row("VAR INT:" +resultsProperties[3] +" vs. " + resultsStatics[3] )
            if (resultsProperties[3] > resultsStatics[3]) row("VAR INT: STATIC FASTER");
            else row("VAR INT: PROPERTY FASTER");
            //row("-----------------");

          
        }
    }

}


You will need to create a GameVars class to host all variables
And a _game class that has gameVars as GameVars


Results....

Test 1:

Int / Number (they take same time but only sometimes int is faster):

public const int : 7 ms; (this looks pretty fast!)
static public const int : 82 ms;


Number:
public var Number : 97 ms;
static public var Number : 86 ms;

Int:
public var int : 84 ms;
static public var int : 86 ms;


Test 2:        
If I copy a static var (int) to a local var int
static public var int : 89 ms (external to from that object)
public var int : 87 ms (class var)

 


So results are:
Test 1:
The fastest are NON-STATIC CONSTS

Test 2:
If copy static var to a local class or function var and then re-use that new var, makes a very little performance improvement... and only sometimes.

My conclusion is that only non-static consts are really fast. The rest are kind of the same.

Please do your tests and let us know, I'm not sure If I made some mistake because it makes little sense to me....




Tuesday, 20 May 2014

Registering as Apple iOS Developer

Hello guys I went through hell of creating a new account , registering as iOS developer and setting up my first AIR app. So this steps are valid for Adobe AIR it might be useful for more people).

I was writing these steps while I was doing them but I did some wrong so I had to erase many steps and start again from some point....  I think I have fixed the list now.

You can follow the steps. If there is something wrong or missing, let me know.


  1. Make an apple account
  2. Register as developer
  3. Pay £60
  4. Wait for a bit.
  5. Get stuck in a loop. (I need to register but I am already registered)
  6. Ask for help? Doesn't work as I need a mac.
  7. Got a mac, carry on.
  8. [OSX] Create CSR: "certificateSigningRequest.certSigning" from KeyChain Access (MORE INFO HERE)
  9. [developer.apple.com] Create a certificate for 'iOS - App developent'. Uploading the file from previous step. And download the certificate file.
  10. [OSX] Convert it to P12 using the keychain access in OSX. (you will need this file when testing to the device and also you need another different P12 for publishing)
  11. [developer.apple.com] Create the app ID following these steps:http://www.adobe.com/devnet/air/articles/packaging-air-apps-ios.html
  12. [developer.apple.com] VERY IMPORTANT: DO NOT ADD THE PREFIX AIR. to the App ID. I found that in some websites and that is wrong.
  13. [iTunes software]. Connect the phone and copy de UID (click on serial number)
  14. [developer.apple.com] Register a new device.
  15. [developer.apple.com] Create a new provisioning Profile. Development for testing.
  16. [developer.apple.com] You will need to tick who's going to be testing and where. Then download a .mobileprovision file.
  17. [WINDOWS / OSX] Set up flash develop or whatever you use.
  18. Compile using Ad-Hoc or testing.



Wednesday, 7 May 2014

How to implement FreshPlanet In App Purchase ANE in Adobe AIR

First thing I have to admit I went really crazy looking for solutions online, none of them were right.

I ended up inventing my own solution after being able to implement pozirks ANE successfully (for Android). I decided to use some stuff from here and there and I managed to make it work...

So here is the chest with the gold: here it goes what it's missing in all other pages that talk about this ANE.... The descriptor XML file configuration....


Application.XML file needs to have this lines among the rest of your items please place them in their own places:


<manifestAdditions>
<![CDATA[<manifest android:installLocation="preferExternal">
<uses-sdk android:minSdkVersion="8" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.android.vending.BILLING" />

<application android:enabled="true">
<activity android:name="com.freshplanet.inapppurchase.activities.BillingActivity" android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen" android:background="#30000000" />
</application>
</manifest>]]>
</manifestAdditions>

and in extensions:
 
     
<extensionID> com.freshplanet.AirInAppPurchase </extensionID>
 


That works pretty well.
You should be able to make it work after this.... But if you have troubles , let me know and I will publish the .AS file using this ANE.






Wednesday, 30 April 2014

ATF experiments in Adobe AIR (in Android)

After we developed out latest app PerfectBrew for Android (helps you making teas!)
We realized it takes ages to load (well... 10 seconds!) but we want it faster. We had one unique texture atlas with PNG (2048x2048), fully crowded with everything....
 
In order to be able to expand our app later on we have decided to split it in 3 atlases (same contents for now)
  • 1 PNG (1024x2048)
  • 1 PNG (1024x1024)
  • 1 ATF (1024x2048)
We wanted to make sure we use ATF as much as possible without breaking the graphics (as it doesnt support preMultiplied Atlas!)

We compressed out PNG to ATF with:
png2atf.exe -c e -e -q 10 -4

And here are the results:

1 big PNG:
Loading time: 9-10 seconds.
VRAM used (right after loading) 3 PNGS: 31Mb

3 PNGs:
Loading time: 9-10 seconds.
VRAM used (right after loading) 3 PNGS: 37Mb

2 PNGs + 1 ATF:
Loading time: 8-9 seconds. 
VRAM used (right after loading) 2 PNGS + 1 ATF: 29Mb

We couldn't speed up the load unless we had some textures loaded AFTER the app started. But this was just an experiment where we forced the program to load everything at start.

We used ATFs in Beekyr (a ton of textures there!) and it was a lot more noticeable as there are a lot of ATFs in the game.


UPDATE:
I tried adding the -r argument and it makes a difference:
It generates very light ATF files that get loaded really fast when we execute the apps. But the images can look very blocky... To avoid this blockyness you need to remove the -q parameter:
png2atf.exe -c e -e -r -4


One thing that doesnt make a difference is when we pack the app into APK it doesnt make much of a difference in file space as APK is a compressed format.

I hope this can be useful for somebody!

Thursday, 20 June 2013

Enable Nape debugging with CitrusEngine.

After initializing the engine you need to enable a onEnterFrame listener to constantly refresh it all the debug images:

This is how I have done it:


           var params:Object = new Object();

           nape = new Nape("nape", params);
           nape.touchable = false;
           enableDebug();
           add(nape);
           


        private function enableDebug():void
        {
            debug = new ShapeDebug(GameVars.SCREEN_X,GameVars.SCREEN_Y);
            debug.drawBodies = true;
            debug.drawSensorArbiters = true;
            debug.drawConstraints = true;
            Starling.current.nativeOverlay.addChild(debug.display);
            stage.addEventListener(Event.ENTER_FRAME, loop );
        }

        public function loop( evt:Event ) : void {
            debug.clear();
            debug.draw(nape.space);
            debug.flush();
        }
It should be easy to adapt it to your needs. It's pretty straight forward!

Monday, 17 June 2013

Optimizing performance when developing high responsive apps: Pools

Everything needs to be pooled.

This means that you create objects when they don't exist. When an enemy or bullet get destroyed it will only get destroyed visually, not in the logics of the game.

The engine places that enemy or bullet in a place in the screen that is not possible to interact. Then disable it and wait until a new enemy of the same kind comes into the screen , then we recycle the object and reuse it as new by refreshing or resiting all its variables.

Why this? Creating destroying objects can be very heavy on the CPU when done several times in a frame (or loop). So by avoiding the creation of new objects we will have a smoother game play.

When creating objects we enable themt and when we don't need them, we can just disable them. In my particular game I've created this function to be used with CitrusObjects (from CitrusEngine).
       private function enableObject(status:Boolean):void
        {
            view.pauseAnimation(status); //textures will not update.
            view.visible = status; //will hide the object
            updateCallEnabled = status;//will stop updating each frame.
            _inUse = status;
            set_bodyEnabled(status); //disable collisions
        }

I hope it helps!