Tutoriais

Programando com o celular

Esse tutorial mostra como desenvolver um joguinho de celular sem usar nenhum computador.
Não é tão simples assim, como você já deve imaginar.

AIDE

O primeiro passo é instalar o app AIDE.
Esse app compila e gera arquivos apk
O AIDE não está mais disponível na play store, mas deve ser possível encontrá-lo no site apkpure

Novo projeto

Depois de instalar, use o AIDE para criar um novo projeto.
Do tipo "new android app (gradle xml)"
O nome do projeto deve ser HarryLeap e o "package name" deve ser harry.leap

O AIDE gera os códigos do helloworld e exibe na tela o arquivo main.xml e o arquivo MainActivity.java
Para fechar (sem alterar nada) clique sobre o nome do arquivo que está sendo exibido na tela e clique em close.
O botao azul no canto de baixo é bem útil e importante.

Gerando um apk

Encontre o arquivo MainActivity.java e adicione
import android.view.View;
após os outros imports.

Substitua a linha
setContentView(R.layout.main);
por
setContentView(new View(this));
Se você fez tudo certo até aqui, é possível gerar um app.
Clique no triângulo no canto superior direito da tela.
Siga as instruções que aparecem na tela até concluir a instalação.
Depois clique em abrir.

Outro ícone

A gente vai gerar um outro ícone do app.
Para isso instale o app "pixel station" (deve estar disponível na play store).
Se não estiver procure e instale algum outro editor de pixel art.

O harry pode ser composto de 4 retas (palitos) e um ponto (circulo).



Salve como harry e coloque o arquivo harry.png na pasta app/src/main/res/drawable
(crie a pasta drawable se ela não existir)!

Vamos editar o arquivo AndroidManifest.xml para setar a imagem como ícone do app.

Onde está:
android:icon="@drawable/ic_launcher"
Altere apara;
android:icon="@drawable/harry"

AndroidManifest

Vamos editar mais o arquivo AndroidManifest
Ele é como um 'arquivo de configuração'.
Ele não contém código java, mas é importante.

Em dois lugares nesse arquivo
substitua:
"@string/app_name"
por:
"harry leap"

Onde está:
"@style/AppTheme"
Substitua por:
"@android:style/Theme.Light.NoTitleBar"

E inclua a linha:
android:screenOrientation="landscape"
entre
<activity
e
android:label

(isso serve para configurar que nosso app será modo paisagem)

O arquivo fica assim:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="harry.leap" >

	<application
		android:allowBackup="true"
		android:icon="@drawable/harry"
		android:label="harry leap"
		android:theme="@android:style/Theme.Light.NoTitleBar"
		android:resizeableActivity = "true">
		<activity
			android:screenOrientation="landscape"
			android:name=".MainActivity"
			android:label="harry leap">
				<intent-filter>
					<action android:name="android.intent.action.MAIN" />

					<category android:name="android.intent.category.LAUNCHER" />
				</intent-filter>
		</activity>
	</application>

</manifest>


Se você instalar com essas modificações, você percebe algumas diferenças.

Excluir arquivos desnecessários

Agora, antes de começarmos a escrever código java, vamos excluir uns arquivos desnecessários.
Delete TODAS as pastas que estão dentro da pasta app/src/main/res/
Deixe APENAS a pasta drawable.

Código java

Vamos começar a lidar com código java.
Abra o arquivo MainActivity.java e substitua os import com asterisco por:
import android.app.Activity;
import android.os.Bundle;

A gente vai escrever o código do jogo.
Código java.
O código vai dentro de uma classe chamada HarryLeap
Essa classe "extende" a classe "View"

Para comecarmos, o código fica assim:
package harry.leap;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.content.Context;

public class MainActivity extends Activity {
	@Override protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(new HarryLeap(this));
	}
}

class HarryLeap extends View {
	public HarryLeap(Context c) {
		super(c);
	}
}

Exibindo na tela

É possivel exibir formas na tela.
Programando o método onDraw.
@Override protected void onDraw(Canvas canvas) {
	super.onDraw(canvas);
		Paint p=new Paint();
		canvas.drawRect(50,200,400,600,p);
	}
Na chamada da função drawRect
os números são coordenadas.
Coordenadas do retângulo desenhado na tela.

Para colocar uma imagem png na tela usa-se o comando drawBitmap.
É preciso adicionar os imports:
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
É preciso carregar o png dentro de uma variável do tipo bitmap.
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);
		harry=Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(),R.drawable.harry), 64, 64, true); 
	}
O código até aqui fica assim:
package harry.leap;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

public class MainActivity extends Activity {
	@Override protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(new HarryLeap(this));
	}
}

class HarryLeap extends View {
	Bitmap harry;
	public HarryLeap(Context c) {
		super(c);
	}
	@Override protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		Paint p=new Paint();
		canvas.drawRect(50,200,400,600,p);
		canvas.drawBitmap(harry,500,300,p);
	}
	@Override protected void onSizeChanged(int width, int height, int oldw, int oldh) {
		super.onSizeChanged(width, height, oldw, oldh);
		harry=Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(),R.drawable.harry), 64, 64, true); 
	}
}
Mas aí tem um problema:
O tamanho da tela e o grau de zoom!!!

Em alguns celulares o código acima exibe o harry grande, na parte de baixo da tela.
Em outros celulares o harry aparece pequenininho no alto da tela...

A gente quer que fique igual para todos usuarios em qualquer celular.
O problema é que alguns usuários definem zoom no celular.

Para resolver essa questão dá para usar proporção.
E deixar a tela igual em todo celular!

Para isso é preciso as variáveis screenWidth,screenHeight e fractionScreenSize
float screenWidth,screenHeight,fractionScreenSize;
	@Override protected void onSizeChanged(int width, int height, int oldw, int oldh) {
		super.onSizeChanged(width, height, oldw, oldh);
		screenWidth=width;
		screenHeight=height;
		fractionScreenSize=width/500;
		harry=Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(),R.drawable.harry), (int)(fractionScreenSize*64), (int)(fractionScreenSize*64), true);
	}
Vamos desenhar um chão e um céu:
package harry.leap;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;

public class MainActivity extends Activity {
	@Override protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(new HarryLeap(this));
	}
}

class HarryLeap extends View {
	Bitmap harry;
	float screenWidth,screenHeight,fractionScreenSize;
	public HarryLeap(Context c) {
		super(c);
	}
	@Override protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		Paint p=new Paint();
		p.setColor(Color.rgb(222,222,222));
		canvas.drawRect(0,0,screenWidth,(float)(screenHeight*.7),p);
		p.setColor(Color.rgb(144,144,144));
		canvas.drawRect(0,(float)(screenHeight*.7),screenWidth,screenHeight,p);
		canvas.drawBitmap(harry,(float)(screenWidth*.1),(float)(screenHeight*.5),p);
	}
	@Override protected void onSizeChanged(int width, int height, int oldw, int oldh) {
		super.onSizeChanged(width, height, oldw, oldh);
		screenWidth=width;
		screenHeight=height;
		fractionScreenSize=width/500;
		harry=Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(),R.drawable.harry), (int)(fractionScreenSize*64), (int)(fractionScreenSize*64), true); 
	}
}

onTouch

Agora vamos comecar a programar o que acontece quando o user toca a tela:

Criar uma variável chamada "alturaPulo"
class HarryLeap extends View {
	Bitmap harry;
	int alturaPulo;
	float screenWidth,screenHeight,fractionScreenSize;
	public HarryLeap(Context c) {
		super(c);
	}
Importar a biblioteca "MotionEvent":
import android.view.MotionEvent;
Tratar o toque do usuário:
	@Override public boolean onTouchEvent(MotionEvent event) {
		if(event.getAction()==MotionEvent.ACTION_DOWN)
			if(alturaPulo==0)alturaPulo=100;
			else alturaPulo=0;
		invalidate();
		return super.onTouchEvent(event);
	}
A chamada à função invalidate() é a chamada à função onDraw.

Timer e TimerTask

Vamos então usar "Timer" e "TimerTask" para fazer um obstáculo se mover em direção ao harry:
package harry.leap;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.view.MotionEvent;
import java.util.TimerTask;
import java.util.Timer;

public class MainActivity extends Activity {
	@Override protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(new HarryLeap(this));
	}
}

class HarryLeap extends View {
	Bitmap harry;
	int alturaPulo,obstaculo1,obstaculo2,obstaculo3;
	float screenWidth,screenHeight,fractionScreenSize;
	TimerTask gameLoop=new TimerTask() { 
		public void run() {
			obstaculo1-=3;
			obstaculo2-=4;
			obstaculo3-=5;
			if(obstaculo1<-30)obstaculo1=480;
			if(obstaculo2<-30)obstaculo2=530;
			if(obstaculo3<-30)obstaculo3=580;
			invalidate();
		}
	};
	public HarryLeap(Context c) {
		super(c);
	}
	@Override protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		Paint p=new Paint();
		p.setColor(Color.rgb(222,222,222));
		canvas.drawRect(0,0,screenWidth,(float)(screenHeight*.7),p);
		p.setColor(Color.rgb(144,144,144));
		canvas.drawRect(0,(float)(screenHeight*.7),screenWidth,screenHeight,p);
		canvas.drawBitmap(harry,(float)(screenWidth*.1),(float)(screenHeight*.5)-(int)(alturaPulo*fractionScreenSize),p);
		p.setColor(Color.rgb(44,44,44));
		canvas.drawCircle(obstaculo1*fractionScreenSize*2,(float)(screenHeight*.62),36,p);
		canvas.drawCircle(obstaculo2*fractionScreenSize*2,(float)(screenHeight*.62),36,p);
		canvas.drawCircle(obstaculo3*fractionScreenSize*2,(float)(screenHeight*.62),36,p);
	}
	@Override protected void onSizeChanged(int width, int height, int oldw, int oldh) {
		super.onSizeChanged(width, height, oldw, oldh);
		screenWidth=width;
		screenHeight=height;
		fractionScreenSize=width/500;
		harry=Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(),R.drawable.harry), (int)(fractionScreenSize*64), (int)(fractionScreenSize*64), true);
		new Timer().schedule(gameLoop,0,36);
	}
	@Override public boolean onTouchEvent(MotionEvent event) {
		if(event.getAction()==MotionEvent.ACTION_DOWN)
			if(alturaPulo==0)alturaPulo=100;
			else alturaPulo=0;
		invalidate();
		return super.onTouchEvent(event);
	}
}

Fazendo o harry pular

Monitorando a variável alturaPulo a gente faz a animação do pulo do harry:
class HarryLeap extends View {
	Bitmap harry;
	int alturaPulo,obstaculo1,obstaculo2,obstaculo3;
	float screenWidth,screenHeight,fractionScreenSize;
	TimerTask gameLoop=new TimerTask() { 
		public void run() {
			if(alturaPulo%4==1)alturaPulo+=4;
			if(alturaPulo%4==3)alturaPulo-=4;
			if(alturaPulo>40)alturaPulo=39;
			if(alturaPulo<4)alturaPulo=0;
			obstaculo1-=3;
			obstaculo2-=4;
			obstaculo3-=5;
			if(obstaculo1<-30)obstaculo1=480;
			if(obstaculo2<-30)obstaculo2=530;
			if(obstaculo3<-30)obstaculo3=580;
			invalidate();
		}
	};
	public HarryLeap(Context c) {
		super(c);
	}
	@Override protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		Paint p=new Paint();
		p.setColor(Color.rgb(222,222,222));
		canvas.drawRect(0,0,screenWidth,(float)(screenHeight*.7),p);
		p.setColor(Color.rgb(144,144,144));
		canvas.drawRect(0,(float)(screenHeight*.7),screenWidth,screenHeight,p);
		canvas.drawBitmap(harry,(float)(screenWidth*.1),(float)(screenHeight*.5)-(int)(alturaPulo*fractionScreenSize*3),p);
		p.setColor(Color.rgb(44,44,44));
		canvas.drawCircle(obstaculo1*fractionScreenSize*2,(float)(screenHeight*.62),36,p);
		canvas.drawCircle(obstaculo2*fractionScreenSize*2,(float)(screenHeight*.62),36,p);
		canvas.drawCircle(obstaculo3*fractionScreenSize*2,(float)(screenHeight*.62),36,p);
	}
	@Override protected void onSizeChanged(int width, int height, int oldw, int oldh) {
		super.onSizeChanged(width, height, oldw, oldh);
		screenWidth=width;
		screenHeight=height;
		fractionScreenSize=width/500;
		harry=Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(),R.drawable.harry), (int)(fractionScreenSize*64), (int)(fractionScreenSize*64), true);
		new Timer().schedule(gameLoop,0,36);
	}
	@Override public boolean onTouchEvent(MotionEvent event) {
		if(event.getAction()==MotionEvent.ACTION_DOWN)
			if(alturaPulo==0)alturaPulo=5;
			//else alturaPulo=0;
		invalidate();
		return super.onTouchEvent(event);
	}
}

Código completo

O Código completo tratando a colisão entre harry e os obstáculos fica assim:
package harry.leap;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.view.MotionEvent;
import java.util.TimerTask;
import java.util.Timer;

public class MainActivity extends Activity {
	@Override protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(new HarryLeap(this));
	}
}

class HarryLeap extends View {
	Bitmap harry;
	int alturaPulo,obstaculo1,obstaculo2,obstaculo3;
	float screenWidth,screenHeight,fractionScreenSize;
	void init() {
		obstaculo1=700;
		obstaculo2=710;
		obstaculo3=720;
	}
	TimerTask gameLoop=new TimerTask() { 
		public void run() {
			if(alturaPulo%4==1)alturaPulo+=4;
			if(alturaPulo%4==3)alturaPulo-=4;
			if(alturaPulo>40)alturaPulo=39;
			if(alturaPulo<4)alturaPulo=0;
			obstaculo1-=3;
			obstaculo2-=4;
			obstaculo3-=5;
			if(obstaculo1<-30)obstaculo1=480;
			if(obstaculo2<-30)obstaculo2=530;
			if(obstaculo3<-30)obstaculo3=580;
			if(alturaPulo<10&&obstaculo1<46&&obstaculo1>38)init();
			if(alturaPulo<10&&obstaculo2<46&&obstaculo2>38)init();
			if(alturaPulo<10&&obstaculo3<46&&obstaculo3>38)init();
			invalidate();
		}
	};
	public HarryLeap(Context c) {
		super(c);
	}
	@Override protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		Paint p=new Paint();
		p.setColor(Color.rgb(222,222,222));
		canvas.drawRect(0,0,screenWidth,(float)(screenHeight*.7),p);
		p.setColor(Color.rgb(144,144,144));
		canvas.drawRect(0,(float)(screenHeight*.7),screenWidth,screenHeight,p);
		canvas.drawBitmap(harry,(float)(screenWidth*.1),(float)(screenHeight*.5)-(int)(alturaPulo*fractionScreenSize*3),p);
		p.setColor(Color.rgb(44,44,44));
		canvas.drawCircle(obstaculo1*fractionScreenSize*2,(float)(screenHeight*.62),36,p);
		canvas.drawCircle(obstaculo2*fractionScreenSize*2,(float)(screenHeight*.62),36,p);
		canvas.drawCircle(obstaculo3*fractionScreenSize*2,(float)(screenHeight*.62),36,p);
	}
	@Override protected void onSizeChanged(int width, int height, int oldw, int oldh) {
		super.onSizeChanged(width, height, oldw, oldh);
		screenWidth=width;
		screenHeight=height;
		fractionScreenSize=width/500;
		harry=Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(),R.drawable.harry), (int)(fractionScreenSize*64), (int)(fractionScreenSize*64), true);
		init();
		new Timer().schedule(gameLoop,0,36);
	}
	@Override public boolean onTouchEvent(MotionEvent event) {
		if(alturaPulo==0)altura=5;
		return super.onTouchEvent(event);
	}
}

Compartilhar o joguinho

Para compartilhar o jogo dá pra usar o app apkExtractor (que está disponível na play store)

Arquivos do Projeto

Código fonte do projeto Harry leap.