jueves, 27 de octubre de 2011

Añadir gitweb a nuestro servidor Git gestionado por Gitolite



Después de configurar nuestro servidor Git gestionado por Gitolite, tal y como comentaba en mi anterior post "Instalación de un Servidor Git gestionado con Gitolite", he decidido añadirle la posibilidad de ver los repositorios mediante el frontend gitweb.

Para esta configuración partiré de la máquina virtual que ya tenemos creada y  detallaré los pasos para configurar-la y dejar configurado el acceso mediante gitweb.

Configurar usuario git


Para que gitweb pueda funcionar correctamente es necesario que nuestro usuario git (que es el encargado de los repositorios) tenga los mismos permisos que www-data, para que este pueda leer y tratar los repositorios, para ello tendremos que hacer lo siguiente

root@gitserver:~# usermod -a -G git www-data

Configurar Gitolite


Hay que configurar una variable de gitolite para que funcione correctamente, para ello hemos de editar el fichero .gitolite.rc y modificar la variable $REPO_UMASK para que contenga el valor 0027.


vi /home/git/.gitolite.rc


También hay que cambiar los permisos del fichero projects.list y del directorio repositories, para que el servidor apache pueda leer el contenido y mostrarlo en la interfaz gitweb.


chmod -R g+rx /home/git/repositories
chmod g+r /home/git/projects.list


Instalación y configuración de gitweb

Como partimos de la distribución Debian, la forma más fácil de instalar gitweb es mediante el comando apt-get. De paso instalaremos el paquete que nos permitirá ver el código marcado según el lenguaje que utilicemos.

apt-get install hightlight gitweb

Ahora hay que configurar gitweb para que apunte a nuestros repositorios. Para ello modificar las variables del fichero /etc/gitweb.conf 

  • cambiar projectroot a "/home/git/repositories"

  • cambiar projects_list a "/home/git/projects.list"


Ahora podemos reiniciar el servidor web y conectarnos a nuestro gitweb apuntando en nuestro navegador a http://gitserver/gitweb  (donde gitserver es la ip de tu maquina)

apache2ctl restart.

Configurar git-daemon para acceso anónimo solo de lectura


Si lo deseamos,  podemos configurar un git de tal forma que existan proyectos que puedan ser clonados de forma anónima, sin necesidad de configurar claves RSA. Para ello tendremos que configurar el servicio que se encarga de ello. De forma sencilla instalaremos el git-daemon.

apt-get install git-daemon-run


Después hay que editar el fichero /etc/sv/git-daemon/run , añadir el -ugitdaemon:git y modificar el base path. El contenido del fichero ha de ser el siguiente.
root@gitserver:/home/git# cat /etc/sv/git-daemon/run
#!/bin/sh
exec 2>&1
echo 'git-daemon starting.'
exec chpst -ugitdaemon:git \
"$(git --exec-path)"/git-daemon --verbose --reuseaddr \
--base-path=/home/git/repositories/ /home/git/repositories/


Reiniciamos el git-daemon

root@gitserver:/home/git# sv restart git-daemon
ok: run: git-daemon: (pid 3707) 0s


Y ya tendremos listo nuestro servicio git-daemon funcionando correctamente, por lo que ya podremos clonar los repositorios de forma anonima utilizando como url
git://gitserver/testing.git

Modificar el aspecto visual de gitweb


El siguiente apartado es totalmente opcional, pero muy aconsejable si queremos que nuestra interfaz de gitweb tenga un aspecto más amigable y similar a github. (Popular gestor de repositorios, gratuito para proyectos opensource y de pago para privados)

Editar el fichero /etc/gitweb.conf y añadir estas lineas al final


$projects_list_description_width = 100;

# Enable blame, pickaxe search, snapshop, search, and grep
# support, but still allow individual projects to turn them off.
# These are features that users can use to interact with your Git trees. They
# consume some CPU whenever a user uses them, so you can turn them off if you
# need to. Note that the 'override' option means that you can override the
# setting on a per-repository basis.
$feature{'blame'}{'default'} = [1];
$feature{'blame'}{'override'} = 1;

$feature{'pickaxe'}{'default'} = [1];
$feature{'pickaxe'}{'override'} = 1;

$feature{'snapshot'}{'default'} = [1];
$feature{'snapshot'}{'override'} = 1;

$feature{'search'}{'default'} = [1];

$feature{'grep'}{'default'} = [1];
$feature{'grep'}{'override'} = 1;

$feature{'show-sizes'}{'default'} = [1];
$feature{'show-sizes'}{'override'} = 1;

$feature{'avatar'}{'default'} = ['gravatar'];
$feature{'avatar'}{'override'} = 1;

$feature{'highlight'}{'default'} = [1];
$feature{'highlight'}{'override'} = 1;


Modificar el aspecto visual mediante el tema gitweb-theme.  Descargamos el zip y lo descomprimimos en el directorio temporal.


root@gitserver:/tmp/kogakure-gitweb-theme-ccb4b48# mv /usr/share/gitweb/gitweb.js /usr/share/gitweb/gitweb.js.orig
root@gitserver:/tmp/kogakure-gitweb-theme-ccb4b48# mv /usr/share/gitweb/gitweb.css /usr/share/gitweb/gitweb.css.orig
root@gitserver:/tmp/kogakure-gitweb-theme-ccb4b48# cp gitweb.css /usr/share/gitweb/
root@gitserver:/tmp/kogakure-gitweb-theme-ccb4b48# cp gitweb.js /usr/share/gitweb/


Por último, comentaros que aquí tenéis la imagen para VirtualBox.


http://www.megaupload.com/?d=41QOJL6W
http://www.megaupload.com/?d=UDH6Z22N
http://www.megaupload.com/?d=KZJ0B116
http://www.megaupload.com/?d=YK10YPYT
http://www.megaupload.com/?d=4R5FHN57
http://www.megaupload.com/?d=K3WHU28B

lunes, 24 de octubre de 2011

Instalación de un servidor Git gestionado con Gitolite



Por motivos de trabajo, tengo la posibilidad de realizar un cambio en el repositorio de control de versiones para el departamento. Por lo que voy a plantear la posibilidad de migrar todo el código desde StarTeam (un SCM desfasado que nadie conoce) a Git (sin duda uno de los mejores SCM que hay actualmente).

Para ello voy a montar una maquina virtual con una distribución linux debian, a la que voy a instalar los siguientes componentes.

  • git. Sistema de control de versiones.

  • gitolite. Sistema para gestionar los repositorios mediante una única cuenta.


Al final del tutorial dispondréis de una imagen preparada y lista para funcionar con todos los pasos ya realizados. Esta imagen será realizada con VirtualBox un sistema de virtualización gratuito disponible para todos los sistemas (Linux/Mac/Windows)

Lo primero que necesitamos es disponer de un linux funcionando. Para este servidor utilizaré un cd de instalación de debian netinst versión 6.0.3  e instalaré solo lo indispensable para que podamos disponer de un servidor mínimo y funcional. Queda fuera de este tutorial la instalación de debian desde una imagen, por lo que procederé a detallar los pasos una vez disponemos de un sistema linux funcionando correctamente.

Instalación de Git


 Primero instalamos las herramientas de git

apt-get install git

Creación de usuario git


Crearemos un usuario con username "git" , que será el encargado de gestionar los repositorios.

sudo adduser --system --shell /bin/bash --gecos 'git version control' --group --disabled-password --home /home/git git

Generación de Clave RSA del Administrador del repositorio

Antes de instalar gitolite, es necesario que creemos una clave RSA para el usuario que administrará los repositorios GIT. En mi caso, voy a administrarlo mediante una máquina windows, por lo que utilizaré PuTTY. (Queda fuera de este tutorial la instalación y configuración de PuTTY, pero es tan fácil como descargar el fichero putty-installer y ejecutarlo).

A continuación generaremos una clave SSH-2 RSA mediante PuTTY Key Generator. Le daremos a Generate y guardaremos las claves, public key y private key. Tendremos que guardar estas claves.



A continuación subiremos el fichero public key a nuestro servidor (al directorio temporal por ejemplo). El fichero gitserver.pub corresponde a lo que hay dentro del cuadrado (ssh-rsa AAA....)

 Instalación de gitolite


La instalación de gitolite puede llevarse a cabo desde debian de la siguiente forma.
apt-get install gitolite
Pero para esta instalación prefiero instalar la última versión de gitolite, descargandola desde la página web https://github.com/sitaramc/gitolite

Para ello entraremos como usuario root, y nos cambiaremos al usuario git (recordar que el usuario git no tiene password a menos que se lo hayamos dado).

root@gitserver:~# su - git

Añadimos al PATH el directorio $HOME/bin mediante la modificación del .bash_profile con nuestro editor favorito, el contenido ha de contener la siguiente línea.

export PATH=/home/git/bin:$PATH
git@gitserver:~$ cd sitaramc-gitolite/src/
git@gitserver:~/sitaramc-gitolite/src$ ./gl-system-install
using default values for EUID=105:
/home/git/bin /home/git/share/gitolite/conf /home/git/share/gitolite/hooks


Después ejecutaremos el gl-setup desde el path (no desde el directorio src)

git@gitserver:~$ gl-setup /tmp/
gitserver.pub
.ICE-unix/
sitaramc-gitolite-v2.1-31-gf0cedeb.zip
.X11-unix/
git@gitserver:~$ gl-setup /tmp/gitserver.pub
The default settings in the rc file (/home/git/.gitolite.rc) are fine for most
people but if you wish to make any changes, you can do so now.

hit enter...
creating gitolite-admin...
Initialized empty Git repository in /home/git/repositories/gitolite-admin.git/
creating testing...
Initialized empty Git repository in /home/git/repositories/testing.git/
[master (root-commit) ce2e627] start
2 files changed, 6 insertions(+), 0 deletions(-)
create mode 100644 conf/gitolite.conf
create mode 100644 keydir/gitserver.pub


A partir de ahora ya tenemos disponible un servidor GIT con autentificación, para gestionarlo hemos de clonar el siguiente repositorio con la clave privada generada anteriormente (como usuario administrador)
git clone git@server:gitolite-admin
En mi caso, como utilizo una máquina virtual, he de clonar la IP 10.16.77.96, por lo que el cuadro de dialogo del TortoiseGit (el client GIT que utilizo para windows) queda de la siguiente forma.



Si quieres saber como se gestionan los usuarios/grupos en gitolite, puedes consultar mi post "PROXIMAMENTE" donde detallo como se gestionan los repositorios, usuarios y grupos.

Por último, aquí os dejo la imagen VirtualBox con un servidor GIT funcionando con Gitolite. (La imagen se ha subido a MegaUpload para ahorrarme mucho ancho de banda)


http://www.megaupload.com/?d=8ZL5QOOU
http://www.megaupload.com/?d=L4OW5CVC
http://www.megaupload.com/?d=UM14HWG9
http://www.megaupload.com/?d=TSWJK2F8
http://www.megaupload.com/?d=5ZU9ARKY
http://www.megaupload.com/?d=Q7SUT2TZ


Recuerda dejar un comentario y compartir el artículo si te ha sido útil.

 

Por último, os recomiendo visitar mi último post donde añado gitweb al servidor. Añadir gitweb a nuestro servidor Git gestionado por Gitolite

martes, 18 de octubre de 2011

Kobold2D, un nuevo Framework para iOs



Hoy voy a presentaros un nuevo framework de desarrollo de juegos para iOs, pero antes voy a introduciros un poco por que he optado por elegir este framework.

Para mi primer juego desarrollado para iOs y Mac  opté por utilizar Cocos2D, debido a su enorme popularidad, su facilidad de uso y su madurez. Gracias a esto pude lanzar mi primera aplicación para iPad y Mac "Adivina el personaje", que podeis ojear en su pagina oficial. www.jandusoft.com

Para mi próximo juego también voy a utilizar Cocos2D debido a que ya estoy acostumbrado a utilizarlo y no tengo mucho tiempo libre para dedicar a aprender otro framework, como CoronaSDK, que me llama bastante, sobre todo por la posibilidad de portar aplicaciones a Android.

Pero hace poco, descubrí que hay otro framework que está siendo desarrollado por un conocido en el mundo de cocos2d, Kobold2D es el nombre de este nuevo framework, desarrollado por Steffen Itterheim , autor del libro "Learn Cocos2D and iOS game developer".

Kobold2D es un cocos2d hypervitaminado, con muchísimos utilidades para poder desarrollar juegos para plataformas iOs y Mac. Entre ellas cabe destacar las siguientes.

  • Una misma "Template" para desarrollar para iOs como para Mac. Olvídate de como se ha de configurar Xcode para tener más de un target. (Cosa que no es nada facil si estas empezando en este mundillo)

  • Proyectos de ejemplo con diferentes funcionalidades con los que poder empezar a aprender y trabajar. Podrás compilar y ver el código de un pinball, de un juego completo, de un ejemplo en cocos3d, etc...

  • Posibilidad de actualizar la versión de Kobold2D de forma automática. Kobold2D tiene un instalador y un migrador de proyectos a nuevas versiones, por lo que actualizar a la última versión ya no será nunca más un suplicio y perdida de tiempo. Y nos permitirá estar siempre a la última.

  • Incluir librerias muy utilizadas en el desarrollo de juegos. Como pueden ser, cocos2d, cocos3d, Box2d, chipmunk, cocosdenshion, sneakyinput, cocos2d-extensions

  • Poder configurar el proyecto mediante Lua y ahorrarnos el tener que picar mucho código para configurar el entorno.


Aquí tenéis algunas imagenes de dos utilidades incluidas en kobold2d.



Por otra parte, Kobold2D se encuentra constantemente mejorando gracias al gran trabajo de Steffen, y estoy seguro que con el tiempo será uno de los frameworks más utilizados para programar juegos para iOs.

Estos son algunos de los ejemplos de utilidades que kobold2d tiene a disposición de sus usuarios.

  • Posibilidad de configurar varias plataformas de publicidad. (admob y iAd de momento), con solo modificar un fichero de configuración.

  • KKInput, aunque el nombre parezca rídiculo para los hispanoparlantes, esta clase nos ayudará muchísimo a gestionar los eventos de input del sistema, y sobretodo no tener que andar configurando diferentes formas de gestionar los inputs en caso de ser Mac o iOs, ya que esta clase se encarga de encapsular el tratamiento de los mismos.


Por otra parte, Steffen está abierto a cualquier sugerencia, y es posible contactar con él para dejar nuestro feedback, yo por ejemplo he contactado con el varias veces mediante su pagina de "feedback" y en todas las ocasiones mis sugerencias se han tomado en cuenta. La página para sugerir mejoras es la siguiente :http://kobold2d.uservoice.com/forums/134154-general

Y estas son algunas de las mejoras que he solicitado y se han llevado a cavo.

Espero sinceramente que este proyecto siga adelante, y no me estrañaría que algún día de estos, steffen fichara para Zinga u otra compañia como ya hizo en su día Enrique Quesada (Project leader the cocos2d-iphone). Yo por mi parte, ya he optado por utilizar Kobold2D para mi próximo proyecto, y si quereis empezar en el mundo de desarrollo de juegos para iOs os recomiendo que también lo utiliceis.

lunes, 17 de octubre de 2011

Rediseño del Blog



Como podeis observar, he optado por rediseñar la página por segunda vez.

El objectivo de este segundo diseño es el facilitarme la tarea de publicar posts en el blog, por lo que intentaré ser mas regular en la publicación de los mismos.

Entre las novedades que encontrareis, vereis que ahora todos los post son más fáciles de "compartir", así que os animo a que compartais este post y los que vengan en vuestra red social favorita.

Si detectais algún problema en la web, o tenéis alguna sugerencia, podeis poneros en contacto conmigo mediante el formulario de contacto.

Por último, os quiero recomendar que os creéis una cuenta en http://www.gravatar.com . Para el que no lo sepa, gravatar es portal que se encarga de almacenar un "avatar" (imagen de usuario) y lo asocia a una dirección de correo (o varias), de esta forma al añadir un comentario en el blog (y otros blogs) con solo la dirección de correo podemos mostrar vuestro "avatar", sin tener que daros de alta en el portal ni dar ningún tipo de información/permiso.

Y por último, me gustaría saber que os parece el nuevo diseño. Estaré atento a vuestros comentarios.

lunes, 12 de septiembre de 2011

Adivina el personaje camino de la Mac App Store

Si señoras y señores, despues de pasarme 2 semanas he conseguido portar mi primer juego de iPad a laMac App Store.

[yframe url='http://www.youtube.com/watch?v=8dSDBbQkwwk']

Y no es otro que ¡Adivina el personaje!. El problema de que haya tardado tanto es que mi juego tenía un bug que no se reproducia en iOs, pero que al pasar a Mac, se reproducía constantemente, y esto hacía que el juego reventara con un Segmentation Fault.

Al final he conseguido solucionarlo, y no solo eso sino que la versión de iOs ha sufrido un cambio importante. Ahora es posible tachar los personajes, cosa que en la primera versión no se podía hacer.

La versión de Mac todavía no se encuentra disponible en la App Store, cuando se encuentre ya os contaré.

Por otra parte, le tengo bastante cariño a este juego, ya que es el primero que he echo, y todavía no lo voy a dejar por terminado. Tengo planes de mejorar el aspecto visual (uno de los aspectos mas criticables) y espero que el grafista con el que me he puesto en contacto no me deje atrás.

Otro de los aspectos que quiero mejorar son los idiomas, me gustaría poder traducir el juego al Francés, Aleman, Italiano y puede que algún que otro idioma más, pero para ello voy a contactar con páginas de reviews a ver si me sponsorizan la traducción.

No espero ganar mucho dinero con este juego, pero sacarlo adelante me ha ayudado a  aclarar algunas ideas, tomar contactos con gente genial y lo que es más importante, ver que Si soy capaz de hacer algo si me lo propongo.

Este puede ser el principio de un nuevo developer Indie, o al menos eso es lo que yo quiero.

Desde aquí os animo a todos a intentarlo. 

miércoles, 6 de julio de 2011

Marketing Freemium App Fail

 



Ya ha pasado un més desde que publiqué la entrada Potenciar ventas mediante Marketing y ya he podido observar los resultados del mismo.

En resumen: Ha sido una perdida de dinero

Pero esta experiencia me ha servido para determinar el motivo del fallo en la estratégia de marketing. Pueden que existan otros motivos, pero para mí, el que más me ha perjudicado, es el modelo Freemium.

PRIMERA PRUEBA

La mayoría de portales de publicidad cargan al usuario mediante la fórmula del CTR (Click Through Rate), o lo que es lo mismo,  coste por click. Eso quiere decir que cada vez que un usuario realiza un click, nos cobran dinero. Y precisamente por esto, no se puede realizar una campaña de marketing basada en el CTRcon aplicaciones Freemium, ya que las aplicaciones son gratuitas (aunque tengan InApp) y eso hace que estemos perdiendo dinero cada vez que un usuario hace click en nuestro banner (a menos que compre upgrades en el juego, pero por experiencia propia, estos suelen ser el 4% de los que se bajan la aplicación).

Antes de realizar la campaña de marketing, este paso lo tenía medianamente claro, pero aún así quise hacer la prueba, así que contraté los servicios de https://www.admob.com/ para tal tarea.



 

Como podeis observar, solo realizé una campaña de 4 días en admob (tenía un vale de 80$ que tenía que gastar, así que no me ha costado dinero de verdad, solo he tenido que agotar la mitad de mi vale)

Si realizamos cálculos, veremos que se han clickeado 252 veces de 51.234 impresiones, eso hace una media de 0.49%, o lo que es lo mismo, de cada 200 impresiones solo 1 persona realiza el click, en este gráfico tambien podemos ver que el coste de cada click corresponde a 0.16$ , puede parecer barato, pero no lo es, como podeis observar en solo 4 días se han gastado los 40$ que había puesto de límite (gracias a que había puesto un límite diario de 10$).

Si la aplicación fuera de pago, tendría que ver cuanta gente realmente compra el juego mediante esta publicidad, pero al ser una versión Freemium, (y sabiendo que solo el 4% de la gente que la baja lo compra), significa que como mucho solo un 4% de los 252 clicks (suponiendo que todos se bajan la aplicación, cosa que dudo) pagarían por los In-App, y eso suponen unas 10 ventas REALES. Si de cada venta Real, gano aproximadamente 1$, significa que he perdido 40-10 = 30$ por esta campaña. Como podeís observar, esto ya lo tenía mas o menos claro, así que corté rápidamente el marketing con admob, para no seguir perdiendo dinero.

SEGUNDA PRUEBA

De manera paralela, decidí adquirir dos espacios publicitarios en dos páginas web. Mediante la adquisición de dos banners. Uno en ipadizate y otro en ipadwallpaper (una página que desconocia). Para realizar la compra de los banners, utilizé la página buysellads y adquirí los dos por un precio de 299$ (249$ ipadizate y 50$ ipadwallpaper), siendo estos los resultados.



 

Lo primero que cabe destacar, es que he tenido un CTR muy bajo, de solo el 0.08%, siendo el CTR de admob del 0.49%. Y lo segundo es que ipadizate me ha conseguido solo 483 clicks con un precio de 249$, mientras que ipadwallpapers me ha conseguido 345 clicks con un precio de 50$.

No puedo quejarme del soporte de ipadizate (ya me avisaron que tenía un CTR muy bajo, y que debía contratar un desarollador de banners para aumentar el CTR, cosa que hice, pero no sirvió para nada), pero en mi caso los costes de banner no compensan (y menos aún con el modelo freemium).

Si hacemos los calculos del CPC (Coste por click), veremos que esta opción me ha salido más cara que admob.

CPC TOTAL = 299$/828 clicks = 0.36$/click

CPC Ipadizate = 249$/483 clicks = 0.51$/click

CPC ipadWallpapers = 50$/345 clicks = 0.14$/click

En este caso, solo ipadwallpapers se ha comportado mejor que admob, pero con un tiempo de 30 días, cosa que admob ha conseguido en solo 4 días. Por lo que para futuras campañas elegiré admob.

Para terminar, comentaré que mi próximo juego no será Freemium, utilizaré admob para promocionarlo y volveré a ver las graficas. En ese momento volveré a realizar otro post contando mi experiencia.

Y recuerda, si tienes un juego Freemium, NO GASTES DINERO EN MARKETING.

Si te ha gustado este post, o crees que puede serle util a alguien, acuerdate de compartirlo mediante el botón de addthis. 

sábado, 4 de junio de 2011

Potenciar ventas mediante Marketing

 



Hoy 4 de Junio, ha sido aceptada mi nueva versión de ¡Adivina el Personaje!, mi primera aplicación para iPad.

En esta nueva versión, he añadido dos mejoras considerables.

  1. Multijugador mediante GameCenter

  2. Logros


Para esta segunda versión, he querido estudiar la evolución de las ventas en función del marketing asociado. 

Como podeis observar en mi anterior post "Modelo de negocio FREEMIUM en App Store", mi primera aplicación ha tenido bastante éxito gracias al modelo utilizado.

En esta ocasión voy a intentar ver la evolución de la misma una vez aplicados varios espacios publicitarios. Para ello voy a enfocar mi campaña de marketing en dos pilares.

  1. Adquisición de un espacio publicitario en http://buysellads.com/ con un presupuesto de 300$

  2. Creación de una campaña en admob, con un presupuesto de 100$


Como podeis observar, realizar una campaña de 400$ no es una campaña nada despreciable para un desarrollador Indie, por lo que en caso de no ser satisfactoria, habré perdido 400$ de mis ingresos (que he obtenido gracias a mi primera aplicación, y que podría haber invertido en adquirir un iPad 2 o un iPhone).

Pero quiero aprender a rentabilizar las aplicaciones que haga, y si no se arriesga no se gana. Por otra parteSI NO TE ANUNCIAS, NO EXISTES por lo que es imposible que la gente descargue tu aplicacion si no la encuentra. Y recordemos que la iTunes Store dispone de muchísimas aplicaciones.

Para el primer caso, he adquirido dos espacios publicitarios durante 1 mes con una estimación de 915.000 impresiones sin costes por click (da igual el número de clicks que se realicen) por lo que el presupuesto no se verá incrementado.

El primero de ellos está enfocado para el mercado americano. Mediante un banner en la página http://ipadwallpaper.org/


 


El segundo banner, está enfocado al mercado español. Mediante un banner en http://ipadwallpaper.org/

 

 



Para la campaña admob, he decidido enfocarla al mercado americando/canadiense. Aquí podeis ver la configuración actual.



 




Así pues, estos son los canales de voy a utilizar para mejorar las ventas de mi aplicación. Dentro de un més, os entregaré los resultados de la misma. Estad atentos al blog, o añadirme al twitter/facebook.



jueves, 26 de mayo de 2011

Modelo de negocio FREEMIUM en App Store

 



En el siguiente artículo, voy a explicar un caso real de un modelo de negocio que está de moda en la App Store, grácias a este modelo de negocio mi primera aplicación para iPad ha conseguido

15.000 Descargas de ¡Adivina el personaje! llegando a posicionarse Top número 2 en españa. (Como podeis ver en la imagen de la entrada del blog)

Sí señores, en apenas un mes y 5 días, mi primera aplicación para iPad ha conseguido la friolera de 15.000 descargas, eso quiere decir que 15.000 personas han descargado mi aplicación y han jugado como mínimo una vez. (bueno, no son 15.000, pero poco queda ya)

Aquí tenéis un grafo que os lo demuestra.



 

Como podeis imaginar, me siento muy orgulloso de haber conseguido estas cifras, no esperaba que fuera tan descargado mi primer juego para iOS.

Pero para que se haya dado esta situación, ha influido mucho el modelo de negocio que he elegido. En mi caso, he optado por elegir el modelo Freemium,  para el que no lo sepa, Freemium es un modelo de negocios que consiste en ofrecer un servicio gratuito con opciones avanzadas de pago.

A continuación os explicaré el motivo por el cual he elegido este modelo de negocio en mi primera aplicación para iPad.

Mucha gente opta por sacar el mismo juego en dos versiones:

  • Una versión lite gratuita (con algún tipo de limitación, o con publicidad)

  • Una versión de pago


Pero en mi caso, yo he querido evitar esto por 2 motivos principalmente:

  • Tengo que realizar dos proyectos con dos identificadores diferentes (no es el doble de faena, pero lleva bastante tiempo compilar el proyecto/subirlo al itunesconnect/etc..)

  • Las versiones van por separado, puedes ser el top número 1 de la aplicación gratuita y no salir en los resultados de la aplicación de pago.


Es por estos motivos por el cual he elegido el modelo Freemium, realizando una sola versión gratuita, que permita a la gente probarla y en caso de querer comprarla, realizar el pago mediante In-App purchase. Además, al ser mi primera aplicación, me interesa más que la gente la descargue y darme a conocer como programador para iOs.

Podría resumir las ventajas en los siguientes puntos:

  • Solo es necesario mantener una aplicación.

  • Al ser gratuita, mucha más gente la descargará


Para fomentar la compra de la aplicación, mucha gente se limita a mostrar publicidad en la versión gratuita y desbloquearla en la versión de pago. El problema de este modelo, es que los usuarios pueden desconectar la wifi y no recibir ningún anuncio, por lo que nunca mostraría publicidad y no percibiríamos nada por la aplicación que tanto nos ha costado programar.

Para solucionar este problema, se me ocurrió la idea de limitar las partidas diarias, y así hice. Configuré mi aplicación para que esta solo funcionara durante 3 partidas diarias, de esta forma si un usuario quiere jugar más, ha de comprar la aplicación, o esperar a que pase un día.

Desgraciadamente, el modelo Freemium no es el modelo perfecto, ya que tiene puntos que no interesan.

  • La principal, y la más molesta, es que NO SE PUEDEN REALIZAR CÓDIGOS PROMOCIONALES. Para el que no sepa lo que es, un código promocional sirve para descargar una aplicación de pago gratuitamente pero al ser una aplicación gratuita (con In-App ) no se pueden realizar códigos promocionales y estos son muy útiles para realizar márketing. (Enviar códigos a blogs, publicarlos mediante twitter, etc...)

  • No puedes realizar campañas de REBAJAS. Si bajas el precio al  In-App, nadie se va a enterar, ya que estos precios no se ven casi nada en el iTunes Store.

  • La compra "Impulsiva" se ve eliminada, muchos usuarios compran impulsivamente, si les das la opción de probar el juego antes puede que no les guste y no te lo compren, por lo que puede ser algo negativo. En mi caso, solo el 2.5% de las descargas se han materializado en ventas.


Por estos motivos, mi próxima aplicación para iOs, será lanzada en formato tradicional (una aplicación de pago y otra lite) y compararé los resultados de la misma con la versión Freemium. Estar atentos porque así tendreis un feedback de estos dos modelos de negocio en la App Store.

Por último, quisiera agradecer al equipo de iPadSfera por la review que me hiciero, ya que gracias a ellos el mercado español se disparó, haciendo que llegara al top #2.

Para el que no conozca ¡Adivina el personaje!, puede descargarlo desde itunes, puede consultar la review que me hicieron en iPadSfera o consultar la web oficial del juego http://www.jandusoft.com

sábado, 16 de abril de 2011

¡Adivina el personaje! mi primer juego para iPad



Mi primera aplicación para iPad ya está disponible para bajar de la app store. Os recomiendo a todos que descarguéis esta aplicación totalmente gratis.

¡Adivina el personaje! es un juego de adivinanza de dos jugadores. 

Cada jugador dispone de un tablero con 24 personajes animados.

El juego empieza con un personaje aleatorio para cada uno de los jugadores. 

El objetivo del juego es adivinar el personaje que tiene el jugador contrario. 

Los jugadores van realizando preguntas como ¿Lleva gafas? para ir descartando candidatos. 

Pueden jugar dos jugadores en un mismo iPad o un jugador contra la máquina.

Podesi ver unas fotos a continuación, pero os aconsejo que mireis la página principal del juego.

http://www.jandusoft.com

 

 

  

martes, 22 de febrero de 2011

CCSequenceHelper, CCSequence con NSMutableArray



Hoy quiero compartir con todos vosotros un pequeño fragmento de código que os puede ser útil en algún momento de vuestro desarrollo para iOS.

Si eres programador de cocos2d, sabrás que existen lasAnimaciones, (llamadasCCAnimation), y que puedes crear una secuencia de animaciones mediante la clase CCSequence.

El problema de esta clase es que solo puedes pasar una lista fija de animaciones, mediante el método 







(id) actions:



Sabemos que este método tiene como argumentos una lista de animaciones acabadas con nil, pero ¿Que pasa si no sabemos cuantas acciones queremos añadir?, o si estas acciones dependen de un bucle que tengamos.

Para solucionar este problema, voy a mostraros una solución basada en el uso de un NSMutableArray.

El objetivo es poder crear una secuencia de animaciones de forma dinámica, añadirlas a una variable de tipo NSMutableArray y pasar esta variable a una función que nos cree una secuencia. 

Para implementar esta solución, he decidido crear una clase Helper, denominada CCSequenceHelper. La utilización de la misma es muy fácil, simplemente creamos un NSMutableArray y añadimos CCAnimations a el (en este ejemplo, utilizo una animación especial que se encarga de ejecutar un método mediante un selector CCCallFuncND)



-(void) muestraAnimacionPreguntar:(NSMutableArray*) carasOcultar
{
int count = [carasOcultar count];
NSNumber *number;
NSMutableArray * secuencias = [NSMutableArray arrayWithCapacity:count+1];

for (int i = 0; i < count ;i++)
{
number = ((NSNumber*)[carasOcultar objectAtIndex:i]);
CCCallFunc * showX = [CCCallFuncND actionWithTarget:self selector:@selector(muestraCancelaCara:data:) data:(void*)[number intValue]];
showX.duration = 1;
[secuencias addObject:showX];
}
[secuencias addObject:[CCCallFunc actionWithTarget:self selector:@selector(endAnimacionPreguntar)]];
[self runAction:[CCSequenceHelper actionMutableArray:secuencias]];
}


y aquí la implementación de la clase Helper.


//
// CCSequenceHelper.h
//
// Created by Jose Antonio Andújar Clavell on 09/02/11.
// Alias "jandujar"
//
// More snippets on http://www.jandujar.com
//
// License http://creativecommons.org/licenses/by/3.0/
//
// Based upon code by daemonk
// http://www.cocos2d-iphone.org/forum/topic/2547
//

#import
#import "cocos2d.h"

@interface CCSequenceHelper : CCSequence {

}

+(id) actionMutableArray: (NSMutableArray*) _actionList;
@end



//
// CCSequenceHelper.m
// Created by Jose Antonio Andújar Clavell on 09/02/11.
// Alias "jandujar"
//
// More snippets on http://www.jandujar.com
//
// License http://creativecommons.org/licenses/by/3.0/
//
// Based upon code by daemonk
// http://www.cocos2d-iphone.org/forum/topic/2547
//

#import "CCSequenceHelper.h"


@implementation CCSequenceHelper

+(id) actionMutableArray: (NSMutableArray*) _actionList {
CCFiniteTimeAction *now;
CCFiniteTimeAction *prev = [_actionList objectAtIndex:0];

for (int i = 1 ; i < [_actionList count] ; i++) {
now = [_actionList objectAtIndex:i];
prev = [CCSequence actionOne: prev two: now];
}

return prev;
}

@end


Si os ha sido útil este fragmento de código, os animo a compartirlo. Saludos!!!

jueves, 10 de febrero de 2011

CCMenuItem con Imagen, Texto y tamaño dinámico

Hoy toca ser un poco productivos.

En esta ocasión, he decidido crear una clase para crear botones de menú de tamaño dinámico, que a su vez pueden tener un texto en su interior, con la fuente que se quiera. 

Seguramente no abreis entendido nada de lo que he dicho anteriormente, así que mostraré un video para que veais como funciona.

[yframe url='http://www.youtube.com/watch?v=eX_LKX71hpM&feature=mfu_in_order&list=UL']

Como desarrollador de iphone / ipad en algún momento necesitaras crear botones de menú. Para realizar estos botones, lo que se suele hacer es crear botones con photoshop/gimp de diferentes tamaños/colores. Este proceso (además de ser un engorro), nos acarrea ciertos problemas, ya que si disponemos de muchos botones, podemos quedarnos fácilmente sin memoria, o consumir mucha mas de la necesaria.

Para estos casos, he creado la clase JACDynamicButton que a su vez, utiliza una modificación de la clase Scale9Spriteobtenida del foro de cocos2d

Para este proceso es necesario que tengamos una o varias imagenes de tamaño 64x64 (se podría modificar, pero yo he optado por este tamaño, ya que es múltiplo de 2 y corresponde a una textura válida para iOS) como las siguientes.



A simple vista, puede parecer que es la misma imagen, pero no lo son, una tiene aura y servirá para saber cuando está pulsado el botón.

Mediante estas dos imagenes, podremos crear botones como los siguientes.



Por ejemplo, para realizar estos botones, solo ha sido necesario este fragmento de código.


JACDynamicButton* button1 = [JACDynamicButton itemWithText:@"Hello" font:@"Arial" fontSize:30 minSize:CGSizeMake(100,100) normalImage:@"boton_verde.png" selectedImage:@"boton_verde_sel.png" target:self selector:@selector(doMenu:)];
JACDynamicButton* button2 = [JACDynamicButton itemWithText:@"Hello Large" font:@"Arial" fontSize:30 minSize:CGSizeMake(200,50) normalImage:@"boton_verde.png" selectedImage:@"boton_verde_sel.png" target:self selector:@selector(doMenu:)];


Como podeis observar, se utiliza una fuente Arial con tamaño 30, esto se podría cambiar y se pide el tamaño mínimo del botón (que usualmente será el tamaño del mismo, a menos que la fuente nos obligue a hacer el botón mas grande) 

Y este es el código utilizado.


//
// JACDynamicButton.h
// JACDynamicButton
//
// Created by Jose Antonio Andújar Clavell on 09/02/11.
// Alias "jandujar"
//
// More snippets on http://www.jandujar.com
//
// License http://creativecommons.org/licenses/by/3.0/

#import "cocos2d.h"

@interface JACDynamicButton : CCMenuItemSprite {

}

+(id) itemWithText: (NSString*)text font:(NSString*)f fontSize:(int)fs minSize:(CGSize)ms normalImage:(NSString*)ni selectedImage:(NSString*)si target:(id) t selector:(SEL) s;

@end



//
// JACDynamicButton.m
// JACDynamicButton
//
// Created by Jose Antonio Andújar Clavell on 09/02/11.
// Alias "jandujar"
//
// More snippets on http://www.jandujar.com
//
// License http://creativecommons.org/licenses/by/3.0/

#import "JACDynamicButton.h"
#import "Scale9Sprite.h"

@implementation JACDynamicButton

+(id) itemWithText: (NSString*)text font:(NSString*)f fontSize:(int)fs minSize:(CGSize)ms normalImage:(NSString*)ni selectedImage:(NSString*)si target:(id) t selector:(SEL) s
{
//Create the label
CCLabelTTF *label = [CCLabelTTF labelWithString:text fontName:f fontSize:fs];

//Calculate the Dynamic Button size
int minWidth = label.contentSize.width>ms.width?label.contentSize.width:ms.width;
int minHeight = label.contentSize.height>ms.height?label.contentSize.height:ms.height;

CGSize size=CGSizeMake(minWidth+30,minHeight+24);

[label setPosition:CGPointMake(size.width*0.5, size.height*0.5)];

//Sprite normal
Scale9Sprite *normalSprite = [[[Scale9Sprite alloc] initWithFile:ni centreRegion:CGRectMake(20, 20, 24, 24)] autorelease];
[normalSprite setContentSize:size];

//Sprite selected
Scale9Sprite *selectedSprite = nil;
if(si){
selectedSprite = [[[Scale9Sprite alloc] initWithFile:si centreRegion:CGRectMake(20, 20, 24, 24)] autorelease];
[selectedSprite setContentSize:size];
}

//Create the CCMenuItemSprite
CCMenuItemSprite* returnItem;
returnItem = [self itemFromNormalSprite:normalSprite selectedSprite:selectedSprite target:t selector:s];


[returnItem addChild:label];

NSLog(@"Size = %f %f", returnItem.contentSize.width,returnItem.contentSize.height);

return returnItem;
}


@end

 Por último, comentaros que teneis a vuestra disposición el código fuente de este ejemplo, así como el fichero PSD para que podais modificar el color del botón a vuestro antojo.

martes, 1 de febrero de 2011

Implementación de Selector de Menu para Cocos2D

Hoy voy a mostrar un pequeño fragmento de código que he desarrollado para mi primera aplicación de iPad, la cual estoy desarrollando.

Este pequeño snippet os permitirá crear un menu con N elementos separados en M filas. Para el ejemplo que os he puesto he creado una lista de 9 botones agrupados en 2 filas.

Pero como una imagen vale mas que 1000 palabras, aquí os paso un video que os mostrará el funcionamiento del mismo.

[yframe url='http://www.youtube.com/watch?v=FcK1uQXfLGs&feature=channel_video_title']

Por último, os adjunto el código fuente del selector de menu, solo necesitareis importar las clasesJACSelector.h y JACSelector.m a vuestro proyecto.

Para cualquier duda, dejar un comentario.

 


//
// JACSelector.h
// SelectorMenu
//
// Created by Jose Antonio Andújar Clavell on 01/02/11.
// Alias "jandujar"
//
// More snippets on http://www.jandujar.com
//
// License http://creativecommons.org/licenses/by/3.0/

#import "cocos2d.h"


@interface JACSelector : CCLayer
{
tCCMenuState state; // State of our menu grid. (Eg. waiting, tracking touch, cancelled, etc)
CCMenuItem *selectedItem; // Menu item that was selected/active.

CGPoint padding; // Padding in between menu items.
CGPoint menuOrigin; // Origin position of the entire menu grid.
CGPoint touchOrigin; // Where the touch action began.
CGPoint touchStop; // Where the touch action stopped.
CGPoint relativePosition; // Position of the menu when touch action began

float fMaxY; // Max offset of menu position

bool bMoving; // Is the menu currently moving?

float fMoveDelta; // Distance from origin of touch and current frame.
float fAnimSpeed; // 0.0-1.0 value determining how slow/fast to animate the paging.
}

+(id) menuWithArray:(NSMutableArray*)items rows:(int)rows position:(CGPoint)pos padding:(CGPoint)pad ;

-(id) initWithArray:(NSMutableArray*)items rows:(int)rows position:(CGPoint)pos padding:(CGPoint)pad ;

-(void) buildMenuSelector:(int)rows itemWidth:(int)iw itemHeight:(int)ih;

-(CCMenuItem*) GetItemWithinTouch:(UITouch*)touch;
- (CGPoint) GetRelativePosition:(float)offset;
- (void) checkLimits;



@property (nonatomic, readwrite) CGPoint padding;
@property (nonatomic, readwrite) CGPoint menuOrigin;
@property (nonatomic, readwrite) CGPoint touchOrigin;
@property (nonatomic, readwrite) CGPoint touchStop;
@property (nonatomic, readwrite) CGPoint relativePosition;
@property (nonatomic, readwrite) bool bMoving;
@property (nonatomic, readwrite) float fMoveDelta;
@property (nonatomic, readwrite) float fAnimSpeed;
@property (nonatomic, readwrite) float fMaxY;

@end



//
// JACSelector.m
// SelectorMenu
//
// Created by Jose Antonio Andújar Clavell on 01/02/11.
// Alias "jandujar"
//
// More snippets on http://www.jandujar.com
//
// License http://creativecommons.org/licenses/by/3.0/

#import "JACSelector.h"


@implementation JACSelector

@synthesize padding;
@synthesize menuOrigin;
@synthesize touchOrigin;
@synthesize touchStop;
@synthesize relativePosition;
@synthesize bMoving;
@synthesize fMoveDelta;
@synthesize fAnimSpeed;
@synthesize fMaxY;

+(id) menuWithArray:(NSMutableArray*)items rows:(int)rows position:(CGPoint)pos padding:(CGPoint)pad
{
return [[self alloc] initWithArray:items rows:rows position:pos padding:pad];
}

-(id) initWithArray:(NSMutableArray*)items rows:(int)rows position:(CGPoint)pos padding:(CGPoint)pad
{
if ((self = [super init]))
{
self.isTouchEnabled = YES;

CCMenuItem* item =[items objectAtIndex:0];


int itemWidth = item.contentSizeInPixels.width;
int itemHeight = item.contentSizeInPixels.height;

CGSize size = CGSizeMake(itemWidth+2*pad.x,rows*(itemHeight +pad.y) +pad.y);

[self setContentSize:size];

int z = 1;
for (id item in items)
{
[self addChild:item z:z tag:z];
++z;
}

padding = pad;
bMoving = false;
menuOrigin = pos;
fAnimSpeed = 1;

[self buildMenuSelector:rows itemWidth:itemWidth itemHeight:itemHeight];

self.position = menuOrigin;
}

return self;
}

-(void) dealloc
{
[super dealloc];
}

-(void) buildMenuSelector:(int)rows itemWidth:(int)iw itemHeight:(int)ih
{
int row = 0;
int i=0;
fMaxY = menuOrigin.y;
for (CCMenuItem* item in self.children)
{
// Calculate the position of our menu item.
item.position = CGPointMake( padding.x + iw/2,
self.contentSize.height +ih/2 - (row+1)*((ih) + padding.y));

if(row%rows==(rows-1)){
fMaxY = -item.position.y + rows*(ih + padding.y) -ih/2 + menuOrigin.y ;
}
++row;
i++;
}
}

-(void) addChild:(CCMenuItem*)child z:(int)z tag:(int)aTag
{
return [super addChild:child z:z tag:aTag];
}

-(CCMenuItem*) GetItemWithinTouch:(UITouch*)touch
{
// Get the location of touch.
CGPoint touchLocation = [[CCDirector sharedDirector] convertToGL: [touch locationInView: [touch view]]];

// Parse our menu items and see if our touch exists within one.
for (CCMenuItem* item in [self children])
{
CGPoint local = [item convertToNodeSpace:touchLocation];

CGRect r = [item rect];
r.origin = CGPointZero;

// If the touch was within this item. Return the item.
if (CGRectContainsPoint(r, local))
{
return item;
}
}

// Didn't touch an item.
return nil;
}

-(void) registerWithTouchDispatcher
{
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:INT_MIN+1 swallowsTouches:YES];
}

-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
// Convert and store the location the touch began at.
touchOrigin = [[CCDirector sharedDirector] convertToGL:[touch locationInView:[touch view]]];
relativePosition = CGPointMake(self.position.x, self.position.y);

// If we weren't in "waiting" state bail out.
if (state != kCCMenuStateWaiting)
{
return NO;
}

// Activate the menu item if we are touching one.
selectedItem = [self GetItemWithinTouch:touch];
[selectedItem selected];


state = kCCMenuStateTrackingTouch;
return YES;
}

// Touch has ended. Process sliding of menu or press of menu item.
-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
// User has been sliding the menu.
if( bMoving )
{
bMoving = false;

[self checkLimits];
[selectedItem unselected];

}
// User wasn't sliding menu and simply tapped the screen. Activate the menu item.
else
{
[selectedItem unselected];
[selectedItem activate];
}

// Back to waiting state.
state = kCCMenuStateWaiting;
}

// Check the limits of the menu Selector
- (void) checkLimits
{
if (self.position.y < (menuOrigin.y)) {
// Perform the action
id action = [CCMoveTo actionWithDuration:(fAnimSpeed*0.5) position:CGPointMake(self.position.x, menuOrigin.y)];
[self runAction:action];
}else if (self.position.y > (fMaxY)) {
// Perform the action
id action = [CCMoveTo actionWithDuration:(fAnimSpeed*0.5) position:CGPointMake(self.position.x, fMaxY)];
[self runAction:action];
}

}

-(void) ccTouchCancelled:(UITouch *)touch withEvent:(UIEvent *)event
{
[selectedItem unselected];

state = kCCMenuStateWaiting;
}

-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{
// Calculate the current touch point during the move.
touchStop = [[CCDirector sharedDirector] convertToGL:[touch locationInView:[touch view]]];

// Distance between the origin of the touch and current touch point.
fMoveDelta = touchStop.y - touchOrigin.y;

// Set our position.
[self setPosition:[self GetRelativePosition:fMoveDelta]];
bMoving = true;
}

- (CGPoint) GetRelativePosition:(float)offset
{
return CGPointMake(relativePosition.x,relativePosition.y+offset);
}


#pragma mark Clipping logic


- (void) visit {
if (!self.visible)
return;

glPushMatrix();

glEnable(GL_SCISSOR_TEST);

glScissor(menuOrigin.x, menuOrigin.y, self.contentSize.width , self.contentSize.height);
//glScissor(menuOrigin.x, menuOrigin.y, 100 , 100);


[super visit];

glDisable(GL_SCISSOR_TEST);
glPopMatrix();
}

@end