First of all, we need Android Studio opened and check that all we've got on MainActivity.java (or whatever name you gave your main activity) looks like this, which it should if you've followed this guides so far.
MainActivity.java
package ve.com.biocraft.biocraft; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } }
And now we're gonna change some things, I'll be updating the code little by little. Let's begin by adding a new View, a View is a class that allows event handling (like touch events) and provides a space to draw on. For our purposes, we'll be extending Android's SurfaceView. We'll also need to implement a Callback to gain access to changes in our SurfaceView, such as when it is destroyed or the orientation changes.
So, we click on File > New and select Java Class, I'll be naming it GamePanel.java. This is the code.
GamePanel.java
package ve.com.biocraft.biocraft; import android.content.Context; import android.graphics.Canvas; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; public class GamePanel extends SurfaceView implements SurfaceHolder.Callback { public GamePanel(Context context) { super(context); // Adding callback (this) to surface holder to catch events getHolder().addCallback(this); // Make GamePanel able to focus so it can handle events setFocusable(true); } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { } @Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); } @Override protected void onDraw(Canvas canvas) { } }
This is a simple code to override the methods we'll be needing. Not really much to explain. Now let's create another Java Class that we'll call MainThread.java and will extend Android's Thread class. This will be our Game Loop.
MainThread.java
package ve.com.biocraft.biocraft; import android.util.Log; import android.view.SurfaceHolder; public class MainThread extends Thread { // Constant for logging private static final String TAG = MainThread.class.getSimpleName(); private SurfaceHolder surfaceHolder; private GamePanel gamePanel; // This is the flag to check if the game is running private boolean running; // This is the void that sets the flag to either true or false public void setRunning(boolean running) { this.running = running; } public MainThread(SurfaceHolder surfaceHolder, GamePanel gamePanel) { super(); this.surfaceHolder = surfaceHolder; this.gamePanel = gamePanel; } @Override public void run() { Log.d(TAG, "Starting Game Loop"); while (running) { // Update Game State // Render } } }
Not much to say here either, we simply override the run() method and create a running flag, which while true will do an infinite loop, of nothing, for now xD. We also created a constant string named TAG which we'll be using for logging purposes.
As it stands, we still have to start this thread, so let's make some changes in our GamePanel class. We'll also need a way to end the game, we'll go 100% simple and end it whenever the user touches the screen. Since we'll need to change multiple methods, here's the full code.
GamePanel.java
package ve.com.biocraft.biocraft; import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; public class GamePanel extends SurfaceView implements SurfaceHolder.Callback { private MainThread thread; public GamePanel(Context context) { super(context); // Adding callback (this) to surface holder to catch events getHolder().addCallback(this); // Create the Game Loop (the thread) thread = new MainThread(getHolder(), this); // Make GamePanel able to focus so it can handle events setFocusable(true); } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { // When the surface is created we set the running flag to true thread.setRunning(true); // And we start the Game Loop thread.start(); } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { boolean retry = true; while (retry) { try { thread.join(); retry = false; } catch (InterruptedException e) { // Try again to shut down the thread } } } @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { thread.setRunning(false); ((Activity)getContext()).finish(); } return super.onTouchEvent(event); } @Override protected void onDraw(Canvas canvas) { } }
We made a couple changes, first we declared the thread as a private attribute of our GamePanel, then we instantiated it. On the surfaceCreated() method we set the running flag to true and start the thread. We also added some code to the surfaceDestroyed() method that will ensure the thread shuts down cleanly. Finally, we changed the onTouchEvent(event) method, what it does right now is check if the event is the start of a pressed gesture, if it is, it ends the game loop.
Ok, by now we have our View and our Game Loop, but we still need to tell our MainActivity to use that view! Let's get to it!
MainActivity.java
package ve.com.biocraft.biocraft; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.Window; import android.view.WindowManager; public class MainActivity extends AppCompatActivity { // Constant for logging private static final String TAG = MainActivity.class.getSimpleName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Title OFF, we don't want it getSupportActionBar().hide(); // Making App fullscreen getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); // Set our GamePanel as the View setContentView(new GamePanel(this)); Log.d(TAG, "View added!"); } @Override protected void onDestroy() { Log.d(TAG, "Destroying!!!"); super.onDestroy(); } @Override protected void onStop() { Log.d(TAG, "Stopping..."); super.onStop(); } }
Ok, here we added a logging constant again, we will always use the name TAG for those. We set the app title off, put on fullscreen mode and set our GamePanel as the app view. We also override the methods onStop() and onDestroy() and make sure we log them. We could run this app right now to check the logs, but it won't display anything but a black screen, where's the fun in that? xD
Displaying an image using Android is quite simple, really. To dumb it down even more, we'll draw in on the top left corner, coordinates (0, 0). I'm gonna draw a guy from my game, you can use this fella from our Game Idea post, here it is:
First thing is adding it to our app. Just copy the image into the /res/drawable directory. (You should be able to drag-and-drop it directly on Android Studio). Now let's make some changes. First we're going to add a new method to GamePanel.java.
GamePanel.java
public void render(Canvas canvas) { canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.lyon), 0, 0, null); }
This new method we'll be using to draw our image on the top left corner.
Note: R.drawable.lyon is MY image, if yours is, say, "image.png", you need to change it to R.drawable.image.
And at long last, we'll update the run() method from MainThread.java so that it calls for our render().
MainThread.java
@Override public void run() { Canvas canvas; Log.d(TAG, "Starting Game Loop"); while (running) { canvas = null; // Try to lock the canvas for pixel editing try { canvas = this.surfaceHolder.lockCanvas(); synchronized (surfaceHolder) { // Update Game State // Render this.gamePanel.render(canvas); } } finally { // Catching exceptions if (canvas != null) { surfaceHolder.unlockCanvasAndPost(canvas); } } } }
Alright, here we are declaring the canvas we'll be using for drawing. A canvas is the surface's bitmap onto which we can draw. This is very simple, every time the game loop executes itself, we get hold of the canvas and draw our image on the left corner. Time to run the code and see our guy displayed endless times until we touch the screen. It should look like this (on an emulator).
What have we done so far?
- Create a fullscreen app
- Have a thread to control the Game Loop
- Listening to basic events
- Shutting down cleanly
- Displaying a simple image
Well, displaying something endless times is probably not the best idea, so the next part in our game building tutorial will be discussing FPS! Don't miss it in the next post in the series: Part 5- FPS. I'll try my best to have it done by tomorrow, because I probably won't have time to do much during the weekend. Have a great day and thank you all very much for reading!!!
Remember, sharing is caring! Use those social buttons, it's free! :P
very nice blog, i have also found one good link here.
ReplyDeleteLoop Statements In Java
Thanks! :) I'm glad you like it.
DeleteGood find there! If someone has any doubts about loops, they should absolutely check that link!
It's important to know the very basics before going in a big project :)