Monday, October 25, 2010

Zmiany

Zastanawiałem się ostatnio co dalej zrobić z blogiem po zakończeniu konkursu (czas ten nieubłaganie nadchodzi). Napisałem już kilka postów i szkoda byłoby je zwyczajnie wyrzucić do kosza. Tym bardziej, że Android jako platforma daje ogromne możliwości jeśli chodzi o reklamę i rozprzestrzenianie swoich produkcji. Pozwala także (przy odrobinie szczęścia) odnieść  pewne korzyści materialne.

Jako, że polubiłem pisanie prostych aplikacji / gier na Androida (i jeszcze przez jakiś czas chcę się w to pobawić) postanowiłem, że ów bloga będę wykorzystywał właśnie jako kanał dystrybucji informacji o bieżących wydaniach, aktualizacjach itd..

Za tym posunięciem kryje się też pewien chytry plan, którego pewnie niektórzy już się domyślają. Chcę sprawdzić w jaki sposób użytkownicy odbierają to co robię (Android market świetnie nadaje się do tego celu).  Wystawiam więc swoje produkcje do oceny ;) Albo trafię w gusta, albo nie. Przy okazji chcę sprawdzić kilka teorii marketingowo biznesowych. Niektóre z nich już się sprawdziły, pozostałe czekają na wdrożenia. Tak czy inaczej zabawa będzie przednia :)

Aby tego dokonać i dotrzeć do szerszego grona użytkowników konieczna jest jednak zmiana języka na blogu. Od tej pory większość postów będę pisał po angielsku (mniej lub bardziej poprawnie :)). Struktura bloga też pewnie ulegnie zmianie.

Jak już wspomniałem wcześniej wszystkie gry powstają na bazie kasuroid. Jest on ciągle rozwijany (w miarę zapotrzebowania na nowe ficzery, na razie jednak tylko lokalnie). Konto na code.google jest (i ma być!) ciągle aktywne. Planuję co jakiś czas (na razie bliżej nieokreślony) aktualizować źródła. Kodu publikowanych gier / aplikacji na razie nie zamierzam upubliczniać. W przyszłości jednak i tego posunięcia nie wykluczam.

Dla rozluźnienia powstanie też pewnie kilka tutoriali (postaram się do nich trochę bardziej przyłożyć) na temat stosowanych przeze mnie rozwiązań. Na razie jednak ich pisanie nie ma najwyższego priorytetu.

Zapowiada się interesująco :)

Monday, October 18, 2010

Witaj świecie!

Uff, nie było mnie tutaj przez jakiś czas. Nie znaczy to jednak, że się obijałem (no, może trochę :)). Otóż postanowiłem skupić się na czymś co mnie odrobinę bardziej bawi i interesuje. Poza tym pisanie (dobrych!) postów pochłania sporo czasu, a tego ostatnio mocno mi brakuje.

Jaki jest więc efekt mojej absencji na blogu? Ano stwierdziłem, że napiszę sobie gierkę i umieszczę ją w Android markecie, aby wybadać "rynek" ;)

Muszę przyznać, że google zrobił kawał dobrej roboty udostępniając w naprawdę bardzo przyjazny sposób swój market. Co trzeba zrobić, aby móc rejestrować swoje aplikacje?

1. Zarejestrować się (jednorazowo!) jako developer uiszczając $25.
2. Napisać aplikację i wrzucić ją do marketu :)

I tyle. Nie ma żadnej certyfikacji ze strony googla (co powoduje jednocześnie zalew marketu często niezbyt użytecznymi aplikacjami - to jest jednak temat mocno dyskusyjny i nie będę jego podejmował).

Niestety z Polski nie można sprzedawać swoich aplikacji (sprawa w toku). Można jednak udostępniać je za darmo, co też i ja uczyniłem.

Udostępnianie aplikacji za darmo można zrekompensować sobie umieszczając reklamy w swojej grze. Również spróbowałem tego rozwiązania (niestety nie bez bólu, ale w końcu się udało - może opiszę to później).

A oto link Nuclear Balls, gdzie można podejrzeć statystyki gry. Gra jest bardzo prosta. Aby przejść poziom należy zniszczyć wszystkie kulki metodą reakcji łańcuchowej. Obecnie mam już opracowaną kolejną wersję (1.1) gry. Możliwe, że zamieszczę ją jeszcze dzisiaj w markecie. Postaram się także wrzucić filmik z gameplaya.

Btw. Nuclear Balls powstała w oparciu o źródła kasuroid. W planie kolejne tytuły :)

Wednesday, October 6, 2010

Trochę zabawy

Wpis ten będzie stosunkowo krótki. Jakoś nie mam weny do pisania dzisiaj :P Dodałem dwa nowe testy: "Stars" oraz "Andro Stars". Narodziły się one podczas rozmyślań nad Rendererem oraz TextureManagerem.
Zmieniłem także odrobinę główną pętlę programu, aby zapewnić bardziej deterministyczne aktualizacje fizyki gry. Szerszy opis na ten temat można znaleźć np. tu: http://www.koonsolo.com/news/dewitters-gameloop/.

Obecnie jest mały bajzel we wspomnianych testach (kodziłem dość szybko i spontanicznie ;P). Jak znajdę dłuższą chwilę to posprzątam i wydzielę generyczne części.

Czas na filmiki. Prezentują one stary, poczciwy efekt przelatywania przez kosmos.

video 

 W lewym górnym rogu ekranu dostępne jest proste menu, które umożliwia zatrzymanie (pauza) i wznowienie animacji. Nie mogło się oczywiście obejść bez wersji androidowej:

video

Sunday, October 3, 2010

Kapryśny Android 2

Może nie tyle kapryśny android co kapryśny proces inicjalizacji całego frameworka. Okazało się, że przez kilka/kilkanaście pierwszych przebiegów runSlice (głównej metody Corea) Renderer rzuca wyjątkiem, tj. uchwyt do ekranu (lub płótna - Canvas - jak kto woli) jest nullem. Problem uchował się do dzisiaj, gdyż podczas rysowania obiektów dodawałem zabezpieczenie tj. "if canvas != null then render()". Kolejne zmiany w kodzie ujawniły robaka :P Poniżej o co tak naprawdę chodzi :)

Jak już wspomniałem w jednym ze wcześniejszym wpisów cały proces inicjalizacji kasuroid przeniosłem z view do głównego activity. Przypomnę, że kasuroid w trakcie startowania uruchamia wątek odpowiedzialny za aktualizację i rysowanie gry. Renderer aby spełniać swe zadanie musi wiedzieć gdzie rysować scenę. Ekran (Canvas) tworzony jest podczas inicjalizowania view głównego activity. Problem pojawia się w momencie, kiedy którykolwiek z elementów kasuroid zaczyna odwoływać się do ekranu gdy ten jeszcze nie został utworzony.

Oczywiście najprostszym sposobem jest wstawienie wspomnianego zabezpieczenia. Jednak jest to tylko ukrycie błędu w kodzie. Innym (lepszym) rozwiązaniem jest rozpoczęcie rysowania dopiero gdy ekran został poprawnie utworzony. Z pomocą przychodzą callbacki SurfaceHolder, które wołane są odpowiednio podczas tworzenia, zmiany oraz niszczenia ekranu. Teraz wystarczy we wspomnianych callbackach powiadomić Core o stanie ekranu. Aby to umożliwić rozszerzyłem interfejs Corea o metody:
  • screenCreated - wołana podczas tworzenia ekranu
  • screenChanged - wołana gdy ekran zmienia swoje parametry (np. podczas obrotu urządzenia)
  • screenDestroyed - wołana gdy ekran został właśnie zniszczony (np. podczas zamykania aplikacji)
Metody te ustawiają jedynie (przynajmniej na razie) zmienną mScreenExists na true (screenCreated, screenChanged) lub na false (screenDestroyed)

A tak wyglądają callbacki:
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
    Core.getInstance().screenChanged(holder, width, height);
    Debug.inf(getClass().getName(), "surfaceChanged");
}

@Override
public void surfaceCreated(SurfaceHolder holder) 
{
    Core.getInstance().screenCreated(holder);
    Debug.inf(getClass().getName(), "surfaceCreated");
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) 
{
    Core.getInstance().screenDestroyed(holder);
    Debug.inf(getClass().getName(), "surfaceDestroyed");
}
W zależności od ustawienia zmiennej mScreenExists w Core, runSlice będzie wykonany lub nie. W zasadzie mam pomysł jak inaczej mógłbym to rozwiązać, ale na razie zostawiam tak jak jest (na refaktoryzację przyjdzie jeszcze czas).

Friday, October 1, 2010

Licznik ramek

Skoro temat timerów jest jeszcze całkiem świeży jest to doskonały czas na napisanie licznika ramek, czyli.. "FPS countera" :)

Zastanawiałem się czy nie zrobić fpsa na bazie klasy Timer, ale ostatecznie stwierdziłem, że kasuroid będzie potrzebował co najwyżej jednego takiego licznika. Nie ma więc sensu tworzyć obiekt klasy Timer. Jest za to sens stworzenia statycznej klasy Fps, zliczającej poszczególne ramki, a następnie wyliczającej liczbę ramek na sekundę.

W klasie Fps wyróżnić można następujące metody:
public static void reset()
{
    mLastTime = 0;
    mCurrentTime = 0;
    mFrames = 0;
    mElapsedTime = 0;
    mFps = 0;
}
Jak nazwa wskazuje resetuje ona wszystkie zmienne potrzebne do wyliczenia fps.
public static void update()
{
    mFrames++;
    mCurrentTime = System.currentTimeMillis();
    mElapsedTime = mCurrentTime - mLastTime;
    if (mElapsedTime >= (long)mFrameLength)
    {
        mFps = (int)((float)mFrames * mFrameLength / (float)mElapsedTime);
        mFrames = 0;
        mLastTime = mCurrentTime;
    }
}
Aktualizuje czasy poszczególnych ramek oraz oblicza aktualne fps.
public static int get()
{
    return mFps;
}
Zwraca (w formacie całkowitym) liczbę ramek na sekundę.

Metodę reset woła Core podczas inicjalizacji. Aby fps był poprawnie liczony Fps.update() musi być wywoływany w każdym przebiegu głównej pętli tj. w runSlice. Zatem, runSlice wygląda teraz tak:
protected int runSlice()
{
    int ret = RetCode.SUCCESS;

    try
    {
        synchronized (mLock) 
        {
            mRenderer.lock();

            // Update all active timers.
            ret = mTimerManager.updateAll();
            if (ret != RetCode.SUCCESS)
            {
                Debug.err(getClass().getName(), "Problem with updating timers!");
                mRenderer.unlock();
                return ret;
            }

            GameState currentState = null;
            if (mGameStatesStack.isEmpty() == false)
            {
                currentState = mGameStatesStack.peek();
                ret = currentState.update();
                if (ret != RetCode.SUCCESS)
                {
                    Debug.err(getClass().getName(), "Problem with updating the state!");
                    mRenderer.unlock();
                    return ret;
                }

                ret = currentState.render();
                if (ret != RetCode.SUCCESS)
                {
                    Debug.err(getClass().getName(), "Problem with rendering the state!");
                    mRenderer.unlock();
                    return ret;
                }
            }

            Fps.update();
        }
    }
    finally 
    {
        // An exception occured, but try to unlock the canvas.
        mRenderer.unlock();
    }

    return ret;
}
runSlice zmienia się wraz z rozwojem kasuroid. Aby zademonstrować działanie Fpsa zaktualizowałem także test "Two timers" o wyświetlanie aktualnego fpsa. Warto pobawić się "sleepTime" Corea i zobaczyć jak zmienia sie fps.
kasuroid zaczyna w końcu raczkować :) Do następnego!