Android version
In this assignment, you will port your Mario game to the Android platform.
- If you have an Android mobile device, enable "Developer options".
Use a search engine to learn how. It's usually something like this:
Go to settings->About phone, and tap seven times on the build number.
Then, enable debugging, and plug your phone into a USB port on your computer.
- On your desktop or laptop computer, download and install Android Developer Studio.
(Android Developer Studio is an IDE. The ban on IDEs is now over, so you may start using an IDE.)
Please note that Android Developer Studio is still under heavy development,
so these instructions will probably be outdated very quickly.
As that happens, if you update these instructions and send them to me, others will probably be grateful.
- Start a new project.
When it asked me what devices to target, I chose "Phone and Tablet".
When it asked me to pick an API level, I picked the largest one that still targeted about 90% of devices.
When it asked me to pick an activity, I chose "Empty Activity" because it looked like the simplest one.
- Test it.
You should be able to press the run button, , and it should run the program on your phone.
(Of course, it doesn't do anything interesting yet.)
If you want, it will give you the option to download an emulator for your phone so you don't have to have your phone plugged in to run tests.
- Install the Android SDK for your phone's operating system.
In Android Studio, click on this button, .
Put a check by the operating system of your phone and click OK.
- We will start with a simple Android game.
Add these two images to the project:
Right-click on "res", click "new->Image Asset".
(The first pull-down menu will say "Launcher Icons". I don't know what that's about, but I just left that alone and it worked for me.)
Give your image a name, like "bird1".
Change the Asset type to "Image".
After Path, click on the "..." button and browse for your image.
Change shape to "None". (This should get rid of the square shape and enable transparency.)
Then, click "Next" and "Finish".
- Edit the code.
Find and edit the java code for your app (under App->java).
Replace it with the code below.
(The parts in red depend on the name you picked for your project, so you probably want to leave those parts the way they were generated by the wizard.
The parts in green depend on the name you picked for your images.)
package com.gashler.mike.snappy;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class Snappy extends AppCompatActivity
{
Model model;
GameView view;
GameController controller;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
model = new Model();
view = new GameView(this, model);
controller = new GameController(model, view);
setContentView(view);
}
@Override
protected void onPostCreate(Bundle savedInstanceState)
{
super.onPostCreate(savedInstanceState);
}
@Override
protected void onResume()
{
super.onResume();
controller.resume();
}
@Override
protected void onPause()
{
super.onPause();
controller.pause();
}
static class Model
{
boolean flap = false;
boolean flapped_recently = false;
float x = 200.0f;
float y = 200.0f;
float yVelocity = -8.0f;
void update()
{
if(flap)
{
yVelocity = -24.0f;
flap = false;
}
// Fall
yVelocity += 1.8f;
y += yVelocity;
// Bounce on the bottom of the screen
if(y > 700.0f)
{
y = 700.0f;
yVelocity = -6.0f;
}
}
}
static class GameView extends SurfaceView
{
SurfaceHolder ourHolder;
Canvas canvas;
Paint paint;
Model model;
GameController controller;
Bitmap bird1;
Bitmap bird2;
public GameView(Context context, Model m)
{
super(context);
model = m;
// Initialize ourHolder and paint objects
ourHolder = getHolder();
paint = new Paint();
// Load the images
bird1 = BitmapFactory.decodeResource(this.getResources(),
R.mipmap.bird1);
bird2 = BitmapFactory.decodeResource(this.getResources(),
R.mipmap.bird2);
}
void setController(GameController c)
{
controller = c;
}
public void update()
{
if (!ourHolder.getSurface().isValid())
return;
canvas = ourHolder.lockCanvas();
// Draw the background color
canvas.drawColor(Color.argb(255, 128, 200, 200));
// Draw the score
/*paint.setColor(Color.argb(255, 200, 128, 0));
paint.setTextSize(45);
canvas.drawText("Score:" + score, 25, 35, paint);*/
// Draw the turtle
if(model.flapped_recently)
canvas.drawBitmap(bird2, model.x, model.y, paint);
else
canvas.drawBitmap(bird1, model.x, model.y, paint);
ourHolder.unlockCanvasAndPost(canvas);
}
// The SurfaceView class (which GameView extends) already
// implements onTouchListener, so we override this method
// and pass the event to the controller.
@Override
public boolean onTouchEvent(MotionEvent motionEvent)
{
controller.onTouchEvent(motionEvent);
return true;
}
}
static class GameController implements Runnable
{
volatile boolean playing;
Thread gameThread = null;
Model model;
GameView view;
GameController(Model m, GameView v)
{
model = m;
view = v;
view.setController(this);
playing = true;
}
void update()
{
}
@Override
public void run()
{
while(playing)
{
//long time = System.currentTimeMillis();
this.update();
model.update();
view.update();
try {
Thread.sleep(20);
} catch(Exception e) {
Log.e("Error:", "sleeping");
System.exit(1);
}
}
}
void onTouchEvent(MotionEvent motionEvent)
{
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: // Player touched the screen
model.flap = true;
model.flapped_recently = true;
break;
case MotionEvent.ACTION_UP: // Player withdrew finger
model.flapped_recently = false;
break;
}
}
// Shut down the game thread.
public void pause() {
playing = false;
try {
gameThread.join();
} catch (InterruptedException e) {
Log.e("Error:", "joining thread");
System.exit(1);
}
}
// Restart the game thread.
public void resume() {
playing = true;
gameThread = new Thread(this);
gameThread.start();
}
}
}
- Note the differences with your Desktop game:
- Instead of extending JFrame, the main class extends AppCompatActivity.
And, instead of extending JPanel, the view extends SurfaceView.
Since Android is a different platform, it has its own class library that differs from the usual Java class library.
- Instead of using 3 separate files, I put the model, view, and controller classes all inside of the main class.
Yes, you can do that in Java.
It would probably be cleaner to put them in separate files, but I was feeling lazy when I wrote this code.
When you declare a class inside of another class, you typically want to declare the inner class "static",
or else Java implicitly adds some overhead bloat to all of the objects of that class.
- The main loop runs in its own thread so that the game can be easily paused and resumed by killing or restarting that thread.
This thread is implemented in GameController.run.
- Instead of loading the images from a file, they are encoded as a "resource",
which basically means it is embedded somewhere inside the jar file.
(A jar file is a renamed zip archive that contains the Java class files as well as other files your program might need.)
- Turn this into a Mario game.
You can just use most of the code from your other projects, but you may need to bring it over in pieces so you can keep it working.
For controls, I recommend dividing the screen into 4 quadrants:
Touch bottom-left = left arrow
Touch bottom-right = right arrow
Touch top-left = jump
Touch top-right = throw fireball
The class forum is available for questions, but I will not be covering Android development in class.
|