Как написать игру на java на android

Время на прочтение
10 мин

Количество просмотров 370K

Доброго дня всем!

Когда я писал эту «игру» у меня возникала масса вопросов по поводу зацикливания спрайтов так что бы они появлялись через определенное время, так же были проблемы с обнаружением столкновений двух спрайтов и более, все эти вопросы я сегодня хочу осветить в этом посте так как в интернете я не нашел нормального ответа на мои вопросы и пришлось делать самому. Пост ни на что не претендует, я новичок в разработке игр под android и пишу я для новичков в данной отрасли. Кому стало интересно прошу под кат.

Постановка задачи:

Игра должна представлять из себя поле (сцену) на котором располагается ниндзя и призраки. Нинзя должен защищать свою базу от этих призраков стреляя по ним.

Пример такой игры можно посмотреть в android market’e. Хотя я сильно замахнулся, у нас будет только похожая идея.

Вот как будет выглядеть игра:
image

Начало разработки

Создаем проект. Запускаем Eclipse — File — Android Project — Defens — Main.java.

Открываем наш файл Main.java и изменяем весь код на код который ниже:

Main.java

public class Main extends Activity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // если хотим, чтобы приложение постоянно имело портретную ориентацию
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

        // если хотим, чтобы приложение было полноэкранным
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

        // и без заголовка
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        
        setContentView(new GameView(this));
    }
}

Код ниже говорит нашей главной функции что запускать нужно не *.xml файл темы, а класс который у нас является самой сценой.

setContentView(new GameView(this));

Дальше Вам нужно создать класс GameView.java который будет служить для нас главным классом на котором будет производится прорисовка всех объектов. Так же в этом классе будет находится и наш поток в котором будет обрабатываться прорисовка объектов в потоке для уменьшения нагрузки игры на процессор. Вот как будет выглядеть класс когда на сцене у нас ничего не происходит:

GameView.java

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import towe.def.GameView.GameThread;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class GameView extends SurfaceView
{
	/**Объект класса GameLoopThread*/
	private GameThread mThread;
	
	public int shotX;
    public int shotY; 
    
    /**Переменная запускающая поток рисования*/
    private boolean running = false;
	
  //-------------Start of GameThread--------------------------------------------------\
    
	public class GameThread extends Thread
	{
		/**Объект класса*/
	    private GameView view;	 
	    
	    /**Конструктор класса*/
	    public GameThread(GameView view) 
	    {
	          this.view = view;
	    }

	    /**Задание состояния потока*/
	    public void setRunning(boolean run) 
	    {
	          running = run;
	    }

	    /** Действия, выполняемые в потоке */
	    public void run()
	    {
	        while (running)
	        {
	            Canvas canvas = null;
	            try
	            {
	                // подготовка Canvas-а
	                canvas = view.getHolder().lockCanvas();
	                synchronized (view.getHolder())
	                {
	                    // собственно рисование
	                    onDraw(canvas);
	                }
	            }
	            catch (Exception e) { }
	            finally
	            {
	                if (canvas != null)
	                {
	                	view.getHolder().unlockCanvasAndPost(canvas);
	                }
	            }
	        }
	    }
}

	//-------------End of GameThread--------------------------------------------------\
	
	public GameView(Context context) 
	{
		super(context);
		
		mThread = new GameThread(this);
        
        /*Рисуем все наши объекты и все все все*/
        getHolder().addCallback(new SurfaceHolder.Callback() 
        {
      	  	 /*** Уничтожение области рисования */
               public void surfaceDestroyed(SurfaceHolder holder) 
               {
            	   boolean retry = true;
            	    mThread.setRunning(false);
            	    while (retry)
            	    {
            	        try
            	        {
            	            // ожидание завершение потока
            	            mThread.join();
            	            retry = false;
            	        }
            	        catch (InterruptedException e) { }
            	    }
               }

               /** Создание области рисования */
               public void surfaceCreated(SurfaceHolder holder) 
               {
            	   mThread.setRunning(true);
            	   mThread.start();
               }

               /** Изменение области рисования */
               public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) 
               {
               }
        });
	}
	
	 /**Функция рисующая все спрайты и фон*/
    protected void onDraw(Canvas canvas) {     	
          canvas.drawColor(Color.WHITE);
    }
}

Из комментариев надеюсь понятно какая функция что делает. Этот класс является базовым по этому в нем мы будем производиться все действия (функции) которые будут происходить в игре, но для начало нам нужно сделать еще несколько классов Переходи к следующему пункту — создание спрайтов.

Создание спрайтов

Спрайты это маленькие картинки в 2D-играх, которые передвигаются. Это могут быть человечки, боеприпасы или даже облака. В этой игре мы будем иметь три различных типа спрайта: Нинзя image, призрак image, и снаряд image.

Сейчас мы будем использовать не анимированные спрайты но в будущем я вставлю спрайты в проэкт, если тянет научиться делать спрайты прошу во второй урок по созданию игры под android.

Теперь загрузите эти картинки в папку res/drawable для того, чтобы Eclipse мог увидеть эти картинки и вставить в Ваш проект.

Следующий рисунок должен визуально помочь понять как будет располагаться игрок на экране.
image
Скучная картинка… Давайте лучше создадим этого самого игрока.

Нам нужно разместить спрайт на экране, как это сделать? Создаем класс Player.java и записываем в него следующее:

Player.java

import android.graphics.Bitmap;
import android.graphics.Canvas;

public class Player
{
        /**Объект главного класса*/
	GameView gameView;
         
        //спрайт
	Bitmap bmp;

	//х и у координаты рисунка
	int x;
	int y;

        //конструктор	
	public Player(GameView gameView, Bitmap bmp)
	{
		this.gameView = gameView;
		this.bmp = bmp;                    //возвращаем рисунок
		
		this.x = 0;                        //отступ по х нет
		this.y = gameView.getHeight() / 2; //делаем по центру
	}

	//рисуем наш спрайт
	public void onDraw(Canvas c)
	{
		c.drawBitmap(bmp, x, y, null);
	}
}

Все очень просто и понятно, наш игрок будет стоять на месте и ничего не делать, кроме как стрелять по врагу но стрельба будет реализована в классе пуля (снаряд), который будем делать дальше.

Создаем еще один файл классов и назовем его Bullet.java, этот класс будет определять координаты полета, скорость полета и другие параметры пули. И так, создали файл, и пишем в него следующее:

Bullet.java

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;

public class Bullet
{
	/**Картинка*/
    private Bitmap bmp;
    
    /**Позиция*/
    public int x;
    public int y;
    
    /**Скорость по Х=15*/
    private int mSpeed=25;
    
    public double angle;
    
    /**Ширина*/
    public int width;
    
    /**Ввыоста*/
    public  int height;
    
	public GameView gameView;
      
       /**Конструктор*/
       public Bullet(GameView gameView, Bitmap bmp) {
             this.gameView=gameView;
             this.bmp=bmp;
             
             this.x = 0;            //позиция по Х
             this.y = 120;          //позиция по У
             this.width = 27;       //ширина снаряда
             this.height = 40;      //высота снаряда
             
             //угол полета пули в зависипости от координаты косания к экрану
             angle = Math.atan((double)(y - gameView.shotY) / (x - gameView.shotX)); 
       }
 
       /**Перемещение объекта, его направление*/
       private void update() {           
    	   x += mSpeed * Math.cos(angle);         //движение по Х со скоростью mSpeed и углу заданном координатой angle
    	   y += mSpeed * Math.sin(angle);         // движение по У -//-
       }

      /**Рисуем наши спрайты*/
       public void onDraw(Canvas canvas) {
            update();                              //говорим что эту функцию нам нужно вызывать для работы класса
            canvas.drawBitmap(bmp, x, y, null);
       }
}

Из комментариев должно быть понятно что пуля выполняет только одно действие — она должна лететь по направлению указанному игроком.

Рисуем спрайты на сцене

Для того что бы нарисовать эти два класса которые мы создали, нам нужно отредактировать код в классе GameView.java, добавить несколько методов которые будут возвращать нам наши рисунки. Полностью весь код я писать не буду, буду приводить только код нужных мне методов.

Для начала нам нужно создать объекты классов Bullet и Player для того что бы отобразить их на экране, для этого создадим список пуль, что бы они у нас никогда не заканчивались, и обычный объект класса игрока.

Шапка GameView

private List<Bullet> ball = new ArrayList<Bullet>();	
private Player player;

Bitmap players;

Дальше нам нужно присвоить картинки нашим классам, находим конструктор GameView и вставляем в самый конец две строчки:

GameView.java — Конструктор GameView

players= BitmapFactory.decodeResource(getResources(), R.drawable.player2);
player= new Player(this, guns);

И в методе onDraw(Canvas c); делаем видимыми эти спрайты. Проходим по всей коллекции наших элементов сгенерировавшихся в списке.

GameView,java

 /**Функция рисующая все спрайты и фон*/
    protected void onDraw(Canvas canvas) {     	
          canvas.drawColor(Color.WHITE);
          
          Iterator<Bullet> j = ball.iterator();
          while(j.hasNext()) {
        	  Bullet b = j.next();
        	  if(b.x >= 1000 || b.x <= 1000) {
        		  b.onDraw(canvas);
        	  } else {
        		  j.remove();
        	  }
          }
          canvas.drawBitmap(guns, 5, 120, null);
    }

А для того что бы пули начали вылетать при нажатии на экран, нужно создать метод createSprites(); который будет возвращать наш спрайт.

GameView.java

 public Bullet createSprite(int resouce) {
    	 Bitmap bmp = BitmapFactory.decodeResource(getResources(), resouce);
    	 return new Bullet(this, bmp);
    }

Ну и в конце концов создаем еще один метод — onTouch(); который собственно будет отлавливать все касания по экрану и устремлять пулю в ту точку где было нажатия на экран.

GameView.java

public boolean onTouchEvent(MotionEvent e) 
    {
    	shotX = (int) e.getX();
    	shotY = (int) e.getY();
    	
    	if(e.getAction() == MotionEvent.ACTION_DOWN)
    	ball.add(createSprite(R.drawable.bullet));
    	
		return true;
    }

Если хотите сделать что бы нажатие обрабатывалось не единоразово, т.е. 1 нажатие — 1 пуля, а 1 нажатие — и пока не отпустишь оно будет стрелять, нужно удалить if(e.getAction() == MotionEvent.ACTION_DOWN) { }
и оставить только ball.add(createSprite(R.drawable.bullet));.

Все, запускаем нашу игру и пробуем стрелять. Должно выйти вот такое:

Враги

Для того что бы нам не было скучно играться, нужно создать врагов. Для этого нам придется создать еще один класс который будет называться Enemy.java и который будет уметь отображать и направлять нашего врага на нашу базу. Класс довольно простой по этому смотрим код ниже:

Enemy.java

import java.util.Random;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;

public class Enemy 
{
	/**Х и У коорданаты*/
	public int x; 
	public int y; 
	
	/**Скорость*/
	public int speed;
	
	/**Выосота и ширина спрайта*/
	public int width;
	public int height;
	
	public GameView gameView;
	public Bitmap bmp;
	
	/**Конструктор класса*/
	public Enemy(GameView gameView, Bitmap bmp){
		this.gameView = gameView;
		this.bmp = bmp;
		
		Random rnd = new Random();
		this.x = 900;
		this.y = rnd.nextInt(300);
		this.speed = rnd.nextInt(10);
		
        this.width = 9;
        this.height = 8;
	}
	
	public void update(){
		x -= speed;
	}
	
	public void onDraw(Canvas c){
		update();
		c.drawBitmap(bmp, x, y, null);
	}
}

И так что происходит в этом классе? Рассказываю: мы объявили жизненно важные переменные для нашего врага, высота ширина и координаты. Для размещения их на сцене я использовал класс Random() для того что бы когда они будут появляться на сцене, появлялись на все в одной точке, а в разных точках и на разных координатах. Скорость так же является у нас рандомной что бы каждый враг шел с разной скоростью, скорость у нас начинается с 0 и заканчивается 10, 10 — максимальная скорость которой может достигнуть враг. Двигаться они будут с права налево, для того что бы они не были сразу видны на сцене я закинул их на 900 пикселей за видимость экрана. Так что пока они дойдут можно уже будет подготовиться по полной к атаке.

Дальше нам нужно отобразить врага на сцене, для этого в классе GameView.java делаем следующее:

Создаем список врагов для того что бы они никогда не заканчивались и создаем битмап который будет содержать спрайт:

Шапка GameView

private List<Enemy> enemy = new ArrayList<Enemy>();

Bitmap enemies;

Далее создаем новый поток для задания скорости появления врагов на экране:

Шапка GameView

private Thread thred = new Thread(this);

И имплементируем класс Runuble, вот как должна выглядеть инициализация класса GameView:

public class GameView extends SurfaceView implements Runnable

Теперь у Вас еклипс требует создать метод run(), создайте его, он будет иметь следующий вид:

В самом низу класса GameView

public void run() {
		while(true) {
			Random rnd = new Random();
			try {
				Thread.sleep(rnd.nextInt(2000));  
				enemy.add(new Enemy(this, enemies));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

Здесь мы создаем поток который будет создавать спрайт от 0 до 2000 милисекунд или каждые 0, 1 или 2 секунды.

Теперь в конструкторе в самом конце пишем инициализируем наш спрайт с классом для отображения на сцене:

Конструктор GameView

enemies = BitmapFactory.decodeResource(getResources(), R.drawable.target);       
enemy.add(new Enemy(this, enemies));

Ну и конечно же нам нужно объявить эти методы в onDraw(); Вот значит и пишем в нем следующее:

Метод onDraw() в GameView

Iterator<Enemy> i = enemy.iterator();
          while(i.hasNext()) {
        	  Enemy e = i.next();
        	  if(e.x >= 1000 || e.x <= 1000) {
        		  e.onDraw(canvas);
        	  } else {
        		  i.remove();
        	  }
          }

Снова проходим по коллекции врагов с помощью итератора и проверяем — если враг зашел за предел в 1000 пикселей — удаляем его, так как если мы не будем удалять у нас пямять закакается и телефон зависнет, а нам такие проблемы не нужны. Все игра готова для запуска.

Запускаем нашу игру и что мы увидим? А вот что:

Но что я вижу? О нет!!! Пули никак не убивают наших призраков что же делать? А я Вам скажу что делать, нам нужно создать метод который будет образовывать вокруг каждого спрайта — прямоугольник и будет сравнивать их на коллизии. Следующая тема будет об этом.

Обнаружение столкновений

И так, у нас есть спрайт, у нас есть сцена, у нас все это даже движется красиво, но какая польза от всего этого когда у нас на сцене ничего не происходит кроме хождения туда сюда этих спрайтов?

С этой функцией я навозился по полной, даже как-то так выходило что психовал и уходил гулять по улице)) Самый трудный метод, хотя выглядеть совершенно безобидно…

Ладно, давайте уже создадим этот метод и не будем много разглагольствовать… Где то в конце класса GameView создаем метод testCollision() и пишем следующий код:

В самом низу класса GameView.java

/*Проверка на столкновения*/
    private void testCollision() {
		Iterator<Bullet> b = ball.iterator();
		while(b.hasNext()) {
			Bullet balls = b.next();
			Iterator<Enemy> i = enemy.iterator();
			while(i.hasNext()) {
	        	  Enemy enemies = i.next();
	        	  
	        	 if ((Math.abs(balls.x - enemies.x) <= (balls.width + enemies.width) / 2f)
	        			 && (Math.abs(balls.y - enemies.y) <= (balls.height + enemies.height) / 2f)) {
	        			   i.remove();
	        			   b.remove();
	        	 }
			}
        }
	}

И так, что у нас происходит в этом методе? Мы создаем один итератор и запускаем цикл для просмотра всей коллекции спрайтов, и говорим что каждый следующий спрайт пули будет первым.

Дальше создаем еще один итератор с другим списком спрайтов и снова переопределяем и говорим что каждый следующий спрайт врага будет первым. И создаем оператор ветвления — if() который собственно и проверяет на столкновения наши спрайты. В нем я использовал математическую функцию модуль (abs) которая возвращает мне абсолютное целое от двух прямоугольников.

Внутри ифа происходит сравнения двух прямоугольников Модуль от (Пуля по координате Х минус координата врага по координате Х меньше либо равен ширина пули плюс ширина врага / 2 (делим на два для нахождения центра прямоугольника)) и (Модуль от (Пуля по координате У минус координата врага по координате У меньше либо равен ширина пули плюс ширина врага / 2 (делим на два для нахождения центра прямоугольника)));

И в конце всего, если пуля таки достала до врага — мы удаляем его со сцены с концами.

Ну и для того что бы эта функция стала работать записываем её в метод run() который находится в классе GameThread, ниже нашего метода рисования onDraw().

Вот что у нас получается после запуска приложения:

Есть много способов создать игру для Android, и один из важных способов – сделать это с нуля в Android Studio с Java. Это дает вам максимальный контроль над тем, как вы хотите, чтобы ваша игра выглядела и вела себя, и этот процесс научит вас навыкам, которые вы можете использовать и в ряде других сценариев – независимо от того, создаете ли вы экран-заставку для приложения или просто хотите добавить анимацию. Имея это в виду, это руководство покажет вам, как создать простую 2D-игру с помощью Android Studio и Java. Вы можете найти весь код и ресурсы на Github, если хотите продолжить.

Настройка

Чтобы создать нашу игру, нам нужно будет иметь дело с несколькими конкретными концепциями: игровые циклы, потоки и холсты. Для начала запустите Android Studio. Если он у вас не установлен, ознакомьтесь с нашим полным введением в Android Studio, в котором описывается процесс установки. Теперь начните новый проект и убедитесь, что вы выбрали шаблон «Пустое действие». Это игра, поэтому, конечно, вам не нужны такие элементы, как кнопка FAB, усложняющая дело.

Первое, что вам нужно сделать, это изменить AppCompatActivity на Activity. Это означает, что мы не будем использовать функции панели действий.

Как написать свою первую игру для Android на Java

Точно так же мы хотим сделать нашу игру полноэкранной. Добавьте следующий код в onCreate() перед вызовом setContentView ():

getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                          WindowManager.LayoutParams.FLAG_FULLSCREEN);

Обратите внимание: если вы напишете какой-то код, и он будет подчеркнут красным, это, вероятно, означает, что вам нужно импортировать класс. Другими словами, вам нужно сообщить Android Studio, что вы хотите использовать определенные операторы и сделать их доступными. Если вы просто щелкните в любом месте подчеркнутого слова, а затем нажмите Alt + Enter, это будет сделано за вас автоматически!

Создание вашего игрового представления

Вы можете привыкнуть к приложениям, которые используют XML-скрипт для определения макета представлений, таких как кнопки, изображения и метки. Это то, что для нас делает строка setContentView .

Но опять же, это игра, означающая, что в ней не нужны окна браузера или прокручивающиеся представления ресайклера. Вместо этого мы хотим показать холст. В Android Studio холст такой же, как и в искусстве: это среда, на которой мы можем рисовать.

Так что измените эту строку, чтобы она читалась так:

setContentView(

Вы обнаружите, что это снова подчеркнуто красным. Но теперь, если вы нажмете Alt + Enter, у вас не будет возможности импортировать класс. Вместо этого у вас есть возможность создать класс. Другими словами, мы собираемся создать наш собственный класс, который будет определять, что будет происходить на холсте. Это то, что позволит нам рисовать на экране, а не просто показывать готовые виды.

Итак, щелкните правой кнопкой мыши имя пакета в иерархии слева и выберите «Создать»> «Класс». Теперь вам будет представлено окно для создания вашего класса, и вы назовете его GameView. В SuperClass напишите: android.view.SurfaceView, что означает, что класс унаследует методы – свои возможности – от SurfaceView.

Как написать свою первую игру для Android на Java
Как написать свою первую игру для Android на Java

В поле Interface (s) вы напишите android.view.SurfaceHolder.Callback. Как и в случае с любым другим классом, теперь нам нужно создать наш конструктор. Используйте этот код:

private

Каждый раз, когда наш класс вызывается для создания нового объекта (в данном случае нашей поверхности), он запускает конструктор и создает новую поверхность. Строка super вызывает суперкласс, и в нашем случае это SurfaceView.

Добавляя обратный вызов, мы можем перехватывать события.

Теперь переопределите некоторые методы:

@Override

Это в основном позволяет нам переопределять (отсюда и название) методы суперкласса (SurfaceView). Теперь в вашем коде больше не должно быть красных подчеркиваний. Ницца.

Как написать свою первую игру для Android на Java

Вы только что создали новый класс, и каждый раз, когда мы обращаемся к нему, он будет создавать основу для рисования вашей игры. Классы создают объекты, и нам нужен еще один.

Создание тем

Наш новый класс будет называться MainThread. И его работа будет заключаться в создании потока. Поток по сути похож на параллельную ветвь кода, которая может выполняться одновременно с основной частью вашего кода. У вас может быть много потоков, работающих одновременно, что позволяет выполнять задачи одновременно, а не придерживаться строгой последовательности. Это важно для игры, потому что нам нужно следить за тем, чтобы она продолжала работать бесперебойно, даже когда много чего происходит.

Создайте новый класс так же, как и раньше, и на этот раз он расширит Thread. В конструкторе мы просто вызываем super (). Помните, что это суперкласс, Thread, который может сделать за нас всю тяжелую работу. Это похоже на создание программы для мытья посуды, которая просто вызывает washMachine () .

Как написать свою первую игру для Android на Java

Когда этот класс вызывается, он создает отдельный поток, который работает как ответвление от главного. И это из здесь, что мы хотим создать нашу Gameview. Это означает, что нам также нужно ссылаться на класс GameView, и мы также используем SurfaceHolder, который содержит холст. Итак, если холст – это поверхность, SurfaceHolder – это мольберт. И GameView – это то, что объединяет все воедино.

Полная вещь должна выглядеть так:

public

Schweet. Теперь у нас есть GameView и ветка!

Создание игрового цикла

Теперь у нас есть сырье, необходимое для создания игры, но ничего не происходит. Вот здесь и появляется игровой цикл. По сути, это цикл кода, который проходит по кругу и проверяет входные данные и переменные перед отрисовкой экрана. Наша цель – сделать это как можно более последовательным, чтобы не было заиканий или сбоев в частоте кадров, которые я рассмотрю немного позже.

Как написать свою первую игру для Android на Java

На данный момент мы все еще находимся в классе MainThread и собираемся переопределить метод из суперкласса. Этот запущен .

И это выглядит примерно так:

@Override

Вы увидите много подчеркивания, поэтому нам нужно добавить еще несколько переменных и ссылок. Вернитесь наверх и добавьте:

private

Не забудьте импортировать Canvas. На самом деле мы будем рисовать холст. Что касается «lockCanvas», это важно, потому что это то, что по сути замораживает холст, чтобы мы могли рисовать на нем. Это важно, потому что в противном случае у вас могло бы быть несколько потоков, пытающихся использовать его одновременно. Просто знайте, что для редактирования холста вы должны сначала заблокировать холст.

Обновление – это метод, который мы собираемся создать, и именно здесь позже произойдет самое интересное.

Попробовать и поймать то время просто требования Java, которые показывают, что мы готовы попробовать и обрабатывать исключения (ошибки), которые могут возникнуть, если полотно не готово и т.д.

Наконец, мы хотим иметь возможность запускать наш поток, когда нам это нужно. Для этого нам понадобится еще один метод, который позволит нам привести в движение вещи. Для этого и предназначена текущая переменная (обратите внимание, что логическое значение – это тип переменной, которая всегда может быть истинной или ложной). Добавьте этот метод в класс MainThread :

public void setRunning(boolean isRunning) 

Но на этом этапе следует выделить одну вещь, а именно обновление. Это потому, что мы еще не создали метод обновления. Так что вернитесь в GameView и добавьте метод.

public void update() 

Нам также нужно запустить ветку! Мы собираемся сделать это в нашем методе surfaceCreated :

@Override

Нам также нужно остановить поток, когда поверхность разрушена. Как вы уже догадались, мы обрабатываем это в методе surfaceDestroyed. Но поскольку на самом деле может потребоваться несколько попыток для остановки потока, мы собираемся поместить это в цикл и снова использовать try and catch. Вот так:

@Override

И, наконец, перейдите к конструктору и убедитесь, что вы создали новый экземпляр вашего потока, иначе вы получите ужасное исключение нулевого указателя! А затем мы сделаем GameView настраиваемым, то есть он может обрабатывать события.

thread = 

Теперь вы, наконец, можете протестировать эту штуку! Правильно, нажмите «Выполнить», и он действительно должен работать без ошибок. Приготовьтесь к тому, чтобы вас сдул!

Как написать свою первую игру для Android на Java

Это … это … пустой экран! Весь этот код. Для пустого экрана. Но это пустой экран возможностей. Ваша поверхность настроена и работает с игровым циклом для обработки событий. Теперь все, что осталось, – это заставить все происходить. Даже не имеет значения, если вы не выполнили все в руководстве до этого момента. Дело в том, что вы можете просто переработать этот код, чтобы начать создавать великолепные игры!

Делаем графику

Итак, теперь у нас есть пустой экран, на котором можно рисовать, все, что нам нужно сделать, это нарисовать на нем. К счастью, это простая часть. Все, что вам нужно сделать, это переопределить метод рисования в нашем классе GameView, а затем добавить несколько красивых картинок:

@Override

Запустите это, и у вас должен появиться красивый красный квадрат в верхнем левом углу белого экрана. Это, безусловно, улучшение.

Как написать свою первую игру для Android на Java

Теоретически вы можете создать практически всю свою игру, вставив ее в этот метод (и переопределив onTouchEvent для обработки ввода), но это не очень хороший способ действовать. Размещение нового Paint внутри нашего цикла значительно замедлит работу, и даже если мы разместим это в другом месте, добавление слишком большого количества кода к методу рисования будет некрасивым и трудным для понимания.

Вместо этого имеет смысл обрабатывать игровые объекты с их собственными классами. Мы начнем с того, который показывает персонажа, и этот класс будет называться CharacterSprite. Давай, сделай это.

Этот класс будет рисовать спрайт на холсте и будет выглядеть так:

public

Теперь, чтобы использовать это, вам нужно сначала загрузить растровое изображение, а затем вызвать класс из GameView. Добавьте ссылку на частный CharacterSprite characterSprite, а затем в методе surfaceCreated добавьте строку:

characterSprite = 

Как видите, загружаемое растровое изображение хранится в ресурсах и называется avdgreen (оно было из предыдущей игры). Теперь все, что вам нужно сделать, это передать это растровое изображение новому классу в методе рисования с помощью:

characterSprite.draw(canvas);

Теперь нажмите «Выполнить», и на экране должно появиться изображение! Это Бибу. Я рисовал его в школьных учебниках.

Как написать свою первую игру для Android на Java

Что, если бы мы хотели заставить этого человечка двигаться? Все просто: мы просто создаем переменные x и y для его позиций, а затем изменяем эти значения в методе обновления .

Поэтому добавьте ссылки в свой CharacterSprite, а затем нарисуйте растровое изображение в точках x, y. Создайте здесь метод обновления, а пока мы просто попробуем:

y++;

При каждом запуске игрового цикла мы перемещаем персонажа вниз по экрану. Помните, что координаты y отсчитываются сверху, поэтому 0 – это верх экрана. Конечно, нам нужно вызвать метод обновления в CharacterSprite из метода обновления в GameView .

Как написать свою первую игру для Android на Java

Снова нажмите кнопку воспроизведения, и теперь вы увидите, что ваше изображение медленно перемещается по экрану. Мы еще не выиграли никаких игровых наград, но это только начало!

Хорошо, чтобы сделать вещи немного интереснее, я просто опущу здесь код «надувного мяча». Это заставит нашу графику отскакивать от краев экрана, как в старых заставках Windows. Вы знаете, странно гипнотические.

public void update() 

Вам также нужно будет определить эти переменные:

private

оптимизация

Здесь есть еще много чего, от обработки ввода игрока до масштабирования изображений и управления одновременным перемещением множества персонажей по экрану. Прямо сейчас персонаж подпрыгивает, но если вы присмотритесь, то заметите легкое заикание. Это не страшно, но то, что вы можете увидеть это невооруженным глазом, является своего рода предупреждающим знаком. Скорость эмулятора также сильно различается по сравнению с физическим устройством. Теперь представьте, что происходит, когда у вас есть тонны происходит на экране сразу!

Есть несколько решений этой проблемы. Для начала я хочу создать частное целое число в MainThread и вызвать его targetFPS. Он будет иметь значение 60. Я собираюсь попытаться заставить мою игру работать на этой скорости, а пока я буду проверять, есть ли это. Для этого мне также нужен частный двойник под названием averageFPS .

Я также собираюсь обновить метод запуска, чтобы измерить, сколько времени занимает каждый игровой цикл, а затем временно приостановить этот игровой цикл, если он опережает targetFPS. Мы тогда будем рассчитать, сколько времени в настоящее время взял, а затем распечатать, что таким образом мы можем увидеть его в журнале.

@Override

Теперь наша игра пытается заблокировать частоту кадров до 60, и вы должны обнаружить, что она обычно показывает довольно стабильные 58-62 кадра в секунду на современном устройстве. На эмуляторе вы можете получить другой результат.

Как написать свою первую игру для Android на Java

Попробуйте изменить это 60 на 30 и посмотрите, что произойдет. Игра замедляется, и он должен теперь прочитать 30 в вашем LogCat.

Заключительные мысли

Есть еще кое-что, что мы можем сделать для оптимизации производительности. Там отличный блог на эту тему здесь. Постарайтесь воздержаться от создания новых экземпляров Paint или растровых изображений внутри цикла и выполнить всю инициализацию снаружи до начала игры.

Как написать свою первую игру для Android на Java

Если вы планируете создать следующую популярную игру для Android, в наши дни, безусловно, есть более простые и эффективные способы сделать это. Но определенно существуют сценарии использования для рисования на холсте, и это очень полезный навык, который можно добавить в свой репертуар. Я надеюсь, что это руководство мне немного помогло, и желаю удачи в ваших будущих начинаниях по кодированию!

Далее – Руководство по Java для новичков

Источник записи: https://www.androidauthority.com

There are plenty of ways to create a game for Android and one important way is to do it from scratch in Android Studio with Java. This gives you the maximum control over how you want your game to look and behave and the process will teach you skills you can use in a range of other scenarios too – whether you’re creating a splash screen for an app or you just want to add some animations. With that in mind, this tutorial is going to show you how to create a simple 2D game using Android Studio and the Java. You can find all the code and resources at Github if you want to follow along.

Setting up

In order to create our game, we’re going to need to deal with a few specific concepts: game loops, threads and canvases. To begin with, start up Android Studio. If you don’t have it installed then check out our full introduction to Android Studio, which goes over the installation process. Now start a new project and make sure you choose the ‘Empty Activity’ template. This is a game, so of course you don’t need elements like the FAB button complicating matters.

The first thing you want to do is to change AppCompatActivity to Activity. This means we won’t be using the action bar features.

Similarly, we also want to make our game full screen. Add the following code to onCreate() before the call to setContentView():

Code

getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                          WindowManager.LayoutParams.FLAG_FULLSCREEN);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);

Note that if you write out some code and it gets underlined in red, that probably means you need to import a class. In other words, you need to tell Android Studio that you wish to use certain statements and make them available. If you just click anywhere on the underlined word and then hit Alt+Enter, then that will be done for you automatically!

Creating your game view

You may be used to apps that use an XML script to define the layout of views like buttons, images and labels. This is what the line setContentView is doing for us.

But again, this is a game meaning it doesn’t need to have browser windows or scrolling recycler views. Instead of that, we want to show a canvas instead. In Android Studio a canvas is just the same as it is in art: it’s a medium that we can draw on.

So change that line to read as so:

Code

setContentView(new GameView(this))

You’ll find that this is once again underlined red. But now if you press Alt+Enter, you don’t have the option to import the class. Instead, you have the option to create a class. In other words, we’re about to make our own class that will define what’s going to go on the canvas. This is what will allow us to draw to the screen, rather than just showing ready-made views.

So right click on the package name in your hierarchy over on the left and choose New > Class. You’ll now be presented with a window to create your class and you’re going to call it GameView. Under SuperClass, write: android.view.SurfaceView which means that the class will inherit methods – its capabilities – from SurfaceView.

In the Interface(s) box, you’ll write android.view.SurfaceHolder.Callback. As with any class, we now need to create our constructor. Use this code:

Code

private MainThread thread;

public GameView(Context context) {
    super(context);

    getHolder().addCallback(this);
}

Each time our class is called to make a new object (in this case our surface), it will run the constructor and it will create a new surface. The line ‘super’ calls the superclass and in our case, that is the SurfaceView.

By adding Callback, we’re able to intercept events.

Now override some methods:

Code

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

}

@Override
public void surfaceCreated(SurfaceHolder holder) {

}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {

}

These basically allow us to override (hence the name) methods in the superclass (SurfaceView). You should now have no more red underlines in your code. Nice.

You just created a new class and each time we refer to that, it will build the canvas for your game to get painted onto. Classes create objects and we need one more.

Creating threads

Our new class is going to be called MainThread. And its job will be to create a thread. A thread is essentially like a parallel fork of code that can run simultaneously alongside the main part of your code. You can have lots of threads running all at once, thereby allowing things to occur simultaneously rather than adhering to a strict sequence. This is important for a game, because we need to make sure that it keeps on running smoothly, even when a lot is going on.

Create your new class just as you did before and this time it is going to extend Thread. In the constructor we’re just going to call super(). Remember, that’s the super class, which is Thread, and which can do all the heavy lifting for us. This is like creating a program to wash the dishes that just calls washingMachine().

When this class is called, it’s going to create a separate thread that runs as an offshoot of the main thing. And it’s from here that we want to create our GameView. That means we also need to reference the GameView class and we’re also using SurfaceHolder which is contains the canvas. So if the canvas is the surface, SurfaceHolder is the easel. And GameView is what puts it all together.

The full thing should look like so:

Code

public class MainThread extends Thread {
    private SurfaceHolder surfaceHolder;
    private GameView gameView;

    public MainThread(SurfaceHolder surfaceHolder, GameView gameView) {

        super();
        this.surfaceHolder = surfaceHolder;
        this.gameView = gameView;

    }
}

Schweet. We now have a GameView and a thread!

Creating the game loop

We now have the raw materials we need to make our game, but nothing is happening. This is where the game loop comes in. Basically, this is a loop of code that goes round and round and checks inputs and variables before drawing the screen. Our aim is to make this as consistent as possible, so that there are no stutters or hiccups in the framerate, which I’ll explore a little later.

For now, we’re still in the MainThread class and we’re going to override a method from the superclass. This one is run.

And it goes a little something like this:

Code

@Override
public void run() {
    while (running) {
        canvas = null;

        try {
            canvas = this.surfaceHolder.lockCanvas();
            synchronized(surfaceHolder) {
                this.gameView.update();
                this.gameView.draw(canvas);
            }
        } catch (Exception e) {} finally {
            if (canvas != null) {
                try {
                    surfaceHolder.unlockCanvasAndPost(canvas);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

You’ll see a lot of underlining, so we need to add some more variables and references. Head back to the top and add:

Code

private SurfaceHolder surfaceHolder;
private GameView gameView;
private boolean running;
public static Canvas canvas;

Remember to import Canvas. Canvas is the thing we will actually be drawing on. As for ‘lockCanvas’, this is important because it is what essentially freezes the canvas to allow us to draw on it. That’s important because otherwise, you could have multiple threads attempting to draw on it at once. Just know that in order to edit the canvas, you must first lock the canvas.

Update is a method that we are going to create and this is where the fun stuff will happen later on.

The try and catch meanwhile are simply requirements of Java that show we’re willing to try and handle exceptions (errors) that might occur if the canvas isn’t ready etc.

Finally, we want to be able to start our thread when we need it. To do this, we’ll need another method here that allows us to set things in motion. That’s what the running variable is for (note that a Boolean is a type of variable that is only ever true or false). Add this method to the MainThread class:

Code

public void setRunning(boolean isRunning) {
    running = isRunning;
}

But at this point, one thing should still be highlighted and that’s update. This is because we haven’t created the update method yet. So pop back into GameView and now add  method.

Code

public void update() {

}

We also need to start the thread! We’re going to do this in our surfaceCreated method:

Code

@Override
public void surfaceCreated(SurfaceHolder holder) {
    thread.setRunning(true);
    thread.start();

}

We also need to stop the thread when the surface is destroyed. As you might have guessed, we handle this in the surfaceDestroyed method. But seeing as it can actually take multiple attempts to stop a thread, we’re going to put this in a loop and use try and catch again. Like so:

Code

@Override
public void surfaceDestroyed(SurfaceHolder holder) {   
    boolean retry = true;   
    while (retry) {       
        try {           
            thread.setRunning(false);           
            thread.join();              
        } catch (InterruptedException e) {       
            e.printStackTrace();   
        }   
        retry = false;
    }
}

And finally, head up to the constructor and make sure to create the new instance of your thread, otherwise you’ll get the dreaded null pointer exception! And then we’re going to make GameView focusable, meaning it can handle events.

Code

thread = new MainThread(getHolder(), this);
setFocusable(true);

Now you can finally actually test this thing! That’s right, click run and it should actually run without any errors. Prepare to be blown away!

It’s… it’s… a blank screen! All that code. For a blank screen. But, this is a blank screen of opportunity. You’ve got your surface up and running with a game loop to handle events. Now all that’s left is make stuff happen. It doesn’t even matter if you didn’t follow everything in the tutorial up to this point. Point is, you can simply recycle this code to start making glorious games!

Doing a graphics

Right, now we have a blank screen to draw on, all we need to do is draw on it. Fortunately, that’s the simple part. All you need to do is to override the draw method in our GameView class and then add some pretty pictures:

Code

@Override
public void draw(Canvas canvas) {         
    super.draw(canvas);       
    if (canvas != null) {           
        canvas.drawColor(Color.WHITE);           
        Paint paint = new Paint();           
        paint.setColor(Color.rgb(250, 0, 0));           
        canvas.drawRect(100, 100, 200, 200, paint);       
    }   
}

Run this and you should now have a pretty red square in the top left of an otherwise-white screen. This is certainly an improvement.

You could theoretically create pretty much your entire game by sticking it inside this method (and overriding onTouchEvent to handle input) but that wouldn’t be a terribly good way to go about things. Placing new Paint inside our loop will slow things down considerably and even if we put this elsewhere, adding too much code to the draw method would get ugly and difficult to follow.

Instead, it makes a lot more sense to handle game objects with their own classes. We’re going to start with one that shows a character and this class will be called CharacterSprite. Go ahead and make that.

This class is going to draw a sprite onto the canvas and will look like so

Code

public class CharacterSprite {
    private Bitmap image;

    public CharacterSprite(Bitmap bmp) {
        image = bmp;           
    }

    public void draw(Canvas canvas) {
        canvas.drawBitmap(image, 100, 100, null);
    }
}

Now to use this, you’ll need to load the bitmap first and then call the class from GameView. Add a reference to private CharacterSprite characterSprite and then in the surfaceCreated method, add the line:

Code

characterSprite = new CharacterSprite(BitmapFactory.decodeResource(getResources(),R.drawable.avdgreen));

As you can see, the bitmap we’re loading is stored in resources and is called avdgreen (it was from a previous game). Now all you need to do is pass that bitmap to the new class in the draw method with:

Code

characterSprite.draw(canvas);

Now click run and you should see your graphic appear on your screen! This is BeeBoo. I used to draw him in my school textbooks.

What if we wanted to make this little guy move? Simple: we just create x and y variables for his positions and then change these values in an update method.

So add the references to your CharacterSprite and then then draw your bitmap at x, y. Create the update method here and for now we’re just going to try:

Each time the game loop runs, we’ll move the character down the screen. Remember, y coordinates are measured from the top so 0 is the top of the screen.  Of course we need to call the update method in CharacterSprite from the update method in GameView.

Press play again and now you’ll see that your image slowly traces down the screen. We’re not winning any game awards just yet but it’s a start!

Okay, to make things slightly more interesting, I’m just going to drop some ‘bouncy ball’ code here. This will make our graphic bounce around the screen off the edges, like those old Windows screensavers. You know, the strangely hypnotic ones.

Code

public void update() {
    x += xVelocity;
    y += yVelocity;
    if ((x & gt; screenWidth - image.getWidth()) || (x & lt; 0)) {
        xVelocity = xVelocity * -1;
    }
    if ((y & gt; screenHeight - image.getHeight()) || (y & lt; 0)) {
        yVelocity = yVelocity * -1;
    }

}

You will also need to define these variables:

Code

private int xVelocity = 10;
private int yVelocity = 5;
private int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
private int screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels;

Optimization

There is plenty more to delve into here, from handling player input, to scaling images, to managing having lots of characters all moving around the screen at once. Right now, the character is bouncing but if you look very closely there is slight stuttering. It’s not terrible but the fact that you can see it with the naked eye is something of a warning sign. The speed also varies a lot on the emulator compared to a physical device. Now imagine what happens when you have tons going on on the screen at once!

There are a few solutions to this problem. What I want to do to start with, is to create a private integer in MainThread and call that targetFPS. This will have the value of 60. I’m going to try and get my game to run at this speed and meanwhile, I’ll be checking to ensure it is. For that, I also want a private double called averageFPS.

I’m also going to update the run method in order to measure how long each game loop is taking and then to pause that game loop temporarily if it is ahead of the targetFPS. We’re then going to calculate how long it now took and then print that so we can see it in the log.

Code

@Override
public void run() {    
    long startTime;   
    long timeMillis;   
    long waitTime;   
    long totalTime = 0;   
    int frameCount = 0;   
    long targetTime = 1000 / targetFPS;
      
    while (running) {       
        startTime = System.nanoTime();       
        canvas = null;
              
        try {           
            canvas = this.surfaceHolder.lockCanvas();           
            synchronized(surfaceHolder) {               
                this.gameView.update();               
                this.gameView.draw(canvas);           
            }       
        } catch (Exception e) {       }       
        finally {           
            if (canvas != null)            {               
                try {                   
                    surfaceHolder.unlockCanvasAndPost(canvas);               
                }               
                catch (Exception e) {
                    e.printStackTrace();
                }           
            }       
        }
               
        timeMillis = (System.nanoTime() - startTime) / 1000000;       
        waitTime = targetTime - timeMillis;
               
        try {           
            this.sleep(waitTime);       
        } catch (Exception e) {}
               
        totalTime += System.nanoTime() - startTime;       
        frameCount++;       
        if (frameCount == targetFPS)        {           
            averageFPS = 1000 / ((totalTime / frameCount) / 1000000);           
            frameCount = 0;           
            totalTime = 0;           
            System.out.println(averageFPS);       
        }   
    }

}

Now our game is attempting to lock it’s FPS to 60 and you should find that it generally measures a fairly steady 58-62 FPS on a modern device. On the emulator though you might get a different result.

Try changing that 60 to 30 and see what happens. The game slows down and it should now read 30 in your logcat.

Closing Thoughts

There are some other things we can do to optimize performance too. There’s a great blog post on the subject here. Try to refrain from ever creating new instances of Paint or bitmaps inside the loop and do all initializing outside before the game begins.

If you’re planning on creating the next hit Android game then there are certainly easier and more efficient ways to go about it these days. But there are definitely still use-case scenarios for being able to draw onto a canvas and it’s a highly useful skill to add to your repertoire. I hope this guide has helped somewhat and wish you the best of luck in your upcoming coding ventures!

There are plenty of ways to create a game for Android and one important way is to do it from scratch in Android Studio with Java. This gives you the maximum control over how you want your game to look and behave and the process will teach you skills you can use in a range of other scenarios too – whether you’re creating a splash screen for an app or you just want to add some animations. With that in mind, this tutorial is going to show you how to create a simple 2D game using Android Studio and the Java. You can find all the code and resources at Github if you want to follow along.

Setting up

In order to create our game, we’re going to need to deal with a few specific concepts: game loops, threads and canvases. To begin with, start up Android Studio. If you don’t have it installed then check out our full introduction to Android Studio, which goes over the installation process. Now start a new project and make sure you choose the ‘Empty Activity’ template. This is a game, so of course you don’t need elements like the FAB button complicating matters.

The first thing you want to do is to change AppCompatActivity to Activity. This means we won’t be using the action bar features.

Similarly, we also want to make our game full screen. Add the following code to onCreate() before the call to setContentView():

Code

getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                          WindowManager.LayoutParams.FLAG_FULLSCREEN);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);

Note that if you write out some code and it gets underlined in red, that probably means you need to import a class. In other words, you need to tell Android Studio that you wish to use certain statements and make them available. If you just click anywhere on the underlined word and then hit Alt+Enter, then that will be done for you automatically!

Creating your game view

You may be used to apps that use an XML script to define the layout of views like buttons, images and labels. This is what the line setContentView is doing for us.

But again, this is a game meaning it doesn’t need to have browser windows or scrolling recycler views. Instead of that, we want to show a canvas instead. In Android Studio a canvas is just the same as it is in art: it’s a medium that we can draw on.

So change that line to read as so:

Code

setContentView(new GameView(this))

You’ll find that this is once again underlined red. But now if you press Alt+Enter, you don’t have the option to import the class. Instead, you have the option to create a class. In other words, we’re about to make our own class that will define what’s going to go on the canvas. This is what will allow us to draw to the screen, rather than just showing ready-made views.

So right click on the package name in your hierarchy over on the left and choose New > Class. You’ll now be presented with a window to create your class and you’re going to call it GameView. Under SuperClass, write: android.view.SurfaceView which means that the class will inherit methods – its capabilities – from SurfaceView.

In the Interface(s) box, you’ll write android.view.SurfaceHolder.Callback. As with any class, we now need to create our constructor. Use this code:

Code

private MainThread thread;

public GameView(Context context) {
    super(context);

    getHolder().addCallback(this);
}

Each time our class is called to make a new object (in this case our surface), it will run the constructor and it will create a new surface. The line ‘super’ calls the superclass and in our case, that is the SurfaceView.

By adding Callback, we’re able to intercept events.

Now override some methods:

Code

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

}

@Override
public void surfaceCreated(SurfaceHolder holder) {

}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {

}

These basically allow us to override (hence the name) methods in the superclass (SurfaceView). You should now have no more red underlines in your code. Nice.

You just created a new class and each time we refer to that, it will build the canvas for your game to get painted onto. Classes create objects and we need one more.

Creating threads

Our new class is going to be called MainThread. And its job will be to create a thread. A thread is essentially like a parallel fork of code that can run simultaneously alongside the main part of your code. You can have lots of threads running all at once, thereby allowing things to occur simultaneously rather than adhering to a strict sequence. This is important for a game, because we need to make sure that it keeps on running smoothly, even when a lot is going on.

Create your new class just as you did before and this time it is going to extend Thread. In the constructor we’re just going to call super(). Remember, that’s the super class, which is Thread, and which can do all the heavy lifting for us. This is like creating a program to wash the dishes that just calls washingMachine().

When this class is called, it’s going to create a separate thread that runs as an offshoot of the main thing. And it’s from here that we want to create our GameView. That means we also need to reference the GameView class and we’re also using SurfaceHolder which is contains the canvas. So if the canvas is the surface, SurfaceHolder is the easel. And GameView is what puts it all together.

The full thing should look like so:

Code

public class MainThread extends Thread {
    private SurfaceHolder surfaceHolder;
    private GameView gameView;

    public MainThread(SurfaceHolder surfaceHolder, GameView gameView) {

        super();
        this.surfaceHolder = surfaceHolder;
        this.gameView = gameView;

    }
}

Schweet. We now have a GameView and a thread!

Creating the game loop

We now have the raw materials we need to make our game, but nothing is happening. This is where the game loop comes in. Basically, this is a loop of code that goes round and round and checks inputs and variables before drawing the screen. Our aim is to make this as consistent as possible, so that there are no stutters or hiccups in the framerate, which I’ll explore a little later.

For now, we’re still in the MainThread class and we’re going to override a method from the superclass. This one is run.

And it goes a little something like this:

Code

@Override
public void run() {
    while (running) {
        canvas = null;

        try {
            canvas = this.surfaceHolder.lockCanvas();
            synchronized(surfaceHolder) {
                this.gameView.update();
                this.gameView.draw(canvas);
            }
        } catch (Exception e) {} finally {
            if (canvas != null) {
                try {
                    surfaceHolder.unlockCanvasAndPost(canvas);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

You’ll see a lot of underlining, so we need to add some more variables and references. Head back to the top and add:

Code

private SurfaceHolder surfaceHolder;
private GameView gameView;
private boolean running;
public static Canvas canvas;

Remember to import Canvas. Canvas is the thing we will actually be drawing on. As for ‘lockCanvas’, this is important because it is what essentially freezes the canvas to allow us to draw on it. That’s important because otherwise, you could have multiple threads attempting to draw on it at once. Just know that in order to edit the canvas, you must first lock the canvas.

Update is a method that we are going to create and this is where the fun stuff will happen later on.

The try and catch meanwhile are simply requirements of Java that show we’re willing to try and handle exceptions (errors) that might occur if the canvas isn’t ready etc.

Finally, we want to be able to start our thread when we need it. To do this, we’ll need another method here that allows us to set things in motion. That’s what the running variable is for (note that a Boolean is a type of variable that is only ever true or false). Add this method to the MainThread class:

Code

public void setRunning(boolean isRunning) {
    running = isRunning;
}

But at this point, one thing should still be highlighted and that’s update. This is because we haven’t created the update method yet. So pop back into GameView and now add  method.

Code

public void update() {

}

We also need to start the thread! We’re going to do this in our surfaceCreated method:

Code

@Override
public void surfaceCreated(SurfaceHolder holder) {
    thread.setRunning(true);
    thread.start();

}

We also need to stop the thread when the surface is destroyed. As you might have guessed, we handle this in the surfaceDestroyed method. But seeing as it can actually take multiple attempts to stop a thread, we’re going to put this in a loop and use try and catch again. Like so:

Code

@Override
public void surfaceDestroyed(SurfaceHolder holder) {   
    boolean retry = true;   
    while (retry) {       
        try {           
            thread.setRunning(false);           
            thread.join();              
        } catch (InterruptedException e) {       
            e.printStackTrace();   
        }   
        retry = false;
    }
}

And finally, head up to the constructor and make sure to create the new instance of your thread, otherwise you’ll get the dreaded null pointer exception! And then we’re going to make GameView focusable, meaning it can handle events.

Code

thread = new MainThread(getHolder(), this);
setFocusable(true);

Now you can finally actually test this thing! That’s right, click run and it should actually run without any errors. Prepare to be blown away!

It’s… it’s… a blank screen! All that code. For a blank screen. But, this is a blank screen of opportunity. You’ve got your surface up and running with a game loop to handle events. Now all that’s left is make stuff happen. It doesn’t even matter if you didn’t follow everything in the tutorial up to this point. Point is, you can simply recycle this code to start making glorious games!

Doing a graphics

Right, now we have a blank screen to draw on, all we need to do is draw on it. Fortunately, that’s the simple part. All you need to do is to override the draw method in our GameView class and then add some pretty pictures:

Code

@Override
public void draw(Canvas canvas) {         
    super.draw(canvas);       
    if (canvas != null) {           
        canvas.drawColor(Color.WHITE);           
        Paint paint = new Paint();           
        paint.setColor(Color.rgb(250, 0, 0));           
        canvas.drawRect(100, 100, 200, 200, paint);       
    }   
}

Run this and you should now have a pretty red square in the top left of an otherwise-white screen. This is certainly an improvement.

You could theoretically create pretty much your entire game by sticking it inside this method (and overriding onTouchEvent to handle input) but that wouldn’t be a terribly good way to go about things. Placing new Paint inside our loop will slow things down considerably and even if we put this elsewhere, adding too much code to the draw method would get ugly and difficult to follow.

Instead, it makes a lot more sense to handle game objects with their own classes. We’re going to start with one that shows a character and this class will be called CharacterSprite. Go ahead and make that.

This class is going to draw a sprite onto the canvas and will look like so

Code

public class CharacterSprite {
    private Bitmap image;

    public CharacterSprite(Bitmap bmp) {
        image = bmp;           
    }

    public void draw(Canvas canvas) {
        canvas.drawBitmap(image, 100, 100, null);
    }
}

Now to use this, you’ll need to load the bitmap first and then call the class from GameView. Add a reference to private CharacterSprite characterSprite and then in the surfaceCreated method, add the line:

Code

characterSprite = new CharacterSprite(BitmapFactory.decodeResource(getResources(),R.drawable.avdgreen));

As you can see, the bitmap we’re loading is stored in resources and is called avdgreen (it was from a previous game). Now all you need to do is pass that bitmap to the new class in the draw method with:

Code

characterSprite.draw(canvas);

Now click run and you should see your graphic appear on your screen! This is BeeBoo. I used to draw him in my school textbooks.

What if we wanted to make this little guy move? Simple: we just create x and y variables for his positions and then change these values in an update method.

So add the references to your CharacterSprite and then then draw your bitmap at x, y. Create the update method here and for now we’re just going to try:

Each time the game loop runs, we’ll move the character down the screen. Remember, y coordinates are measured from the top so 0 is the top of the screen.  Of course we need to call the update method in CharacterSprite from the update method in GameView.

Press play again and now you’ll see that your image slowly traces down the screen. We’re not winning any game awards just yet but it’s a start!

Okay, to make things slightly more interesting, I’m just going to drop some ‘bouncy ball’ code here. This will make our graphic bounce around the screen off the edges, like those old Windows screensavers. You know, the strangely hypnotic ones.

Code

public void update() {
    x += xVelocity;
    y += yVelocity;
    if ((x & gt; screenWidth - image.getWidth()) || (x & lt; 0)) {
        xVelocity = xVelocity * -1;
    }
    if ((y & gt; screenHeight - image.getHeight()) || (y & lt; 0)) {
        yVelocity = yVelocity * -1;
    }

}

You will also need to define these variables:

Code

private int xVelocity = 10;
private int yVelocity = 5;
private int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
private int screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels;

Optimization

There is plenty more to delve into here, from handling player input, to scaling images, to managing having lots of characters all moving around the screen at once. Right now, the character is bouncing but if you look very closely there is slight stuttering. It’s not terrible but the fact that you can see it with the naked eye is something of a warning sign. The speed also varies a lot on the emulator compared to a physical device. Now imagine what happens when you have tons going on on the screen at once!

There are a few solutions to this problem. What I want to do to start with, is to create a private integer in MainThread and call that targetFPS. This will have the value of 60. I’m going to try and get my game to run at this speed and meanwhile, I’ll be checking to ensure it is. For that, I also want a private double called averageFPS.

I’m also going to update the run method in order to measure how long each game loop is taking and then to pause that game loop temporarily if it is ahead of the targetFPS. We’re then going to calculate how long it now took and then print that so we can see it in the log.

Code

@Override
public void run() {    
    long startTime;   
    long timeMillis;   
    long waitTime;   
    long totalTime = 0;   
    int frameCount = 0;   
    long targetTime = 1000 / targetFPS;
      
    while (running) {       
        startTime = System.nanoTime();       
        canvas = null;
              
        try {           
            canvas = this.surfaceHolder.lockCanvas();           
            synchronized(surfaceHolder) {               
                this.gameView.update();               
                this.gameView.draw(canvas);           
            }       
        } catch (Exception e) {       }       
        finally {           
            if (canvas != null)            {               
                try {                   
                    surfaceHolder.unlockCanvasAndPost(canvas);               
                }               
                catch (Exception e) {
                    e.printStackTrace();
                }           
            }       
        }
               
        timeMillis = (System.nanoTime() - startTime) / 1000000;       
        waitTime = targetTime - timeMillis;
               
        try {           
            this.sleep(waitTime);       
        } catch (Exception e) {}
               
        totalTime += System.nanoTime() - startTime;       
        frameCount++;       
        if (frameCount == targetFPS)        {           
            averageFPS = 1000 / ((totalTime / frameCount) / 1000000);           
            frameCount = 0;           
            totalTime = 0;           
            System.out.println(averageFPS);       
        }   
    }

}

Now our game is attempting to lock it’s FPS to 60 and you should find that it generally measures a fairly steady 58-62 FPS on a modern device. On the emulator though you might get a different result.

Try changing that 60 to 30 and see what happens. The game slows down and it should now read 30 in your logcat.

Closing Thoughts

There are some other things we can do to optimize performance too. There’s a great blog post on the subject here. Try to refrain from ever creating new instances of Paint or bitmaps inside the loop and do all initializing outside before the game begins.

If you’re planning on creating the next hit Android game then there are certainly easier and more efficient ways to go about it these days. But there are definitely still use-case scenarios for being able to draw onto a canvas and it’s a highly useful skill to add to your repertoire. I hope this guide has helped somewhat and wish you the best of luck in your upcoming coding ventures!

Разработка типичной игры

Создание игр — это увлекательное занятие для программиста. Но все игры строятся по общему сценарию. Есть несколько экранов для взаимодействия с пользователем:

Экран-заставка
Заставка с изображением логотипа компании, сценки из игры, версии и т.д. Появляется на несколько секунд, а затем исчезает. Может воспроизводиться фоновая музыка
Меню
Второй экран, на котором пользователь может переходить по пунктам меню, например, Играть, Счёт, Настройки, Справка
Основной экран
Здесь собственно происходит игровой процесс
Настройки
Экран настроек параметров игры
Счет
Окно, на которое можно перейти из меню. Здесь можно отображать статистику достижений, таблицу рекордов
Справка
Выводится инструкция по игре: описание, управление, советы

Это типичный пример стандартной игры. Естественно, вы можете отказаться от каких-то экранов или, наоборот, добавить еще, но в целом картина ясна. Таким образом нам нужно реализовать шесть активностей:

  • SplashActivity — активность по умолчанию. Выводится на несколько секунд. После нее должна выводиться активность MenuActivity
  • MenuActivity — содержит кнопки, картинки и другие элементы, с помощью которых можно запустить другие активности
  • GameActivity — основной экран игры, где выводится графика, идет подсчет очков и т.д.
  • SettingsActivity — сохраняем различные настройки дли игры
  • ScoresActivity — загружает данные о достижениях игроков и выводит его пользователю для просмотра
  • HelpActivity — выводит справочную информацию. Если текст большой, то нужно также предусмотреть прокрутку

У каждого класса активности должен быть собственный файл разметки со своим набором элементов. Ваша задача научиться управлять состоянием приложения, пользоваться настройками, запускать нужные активности.

Также удобно создать ещё один базовый класс активности с общедоступными компонентами, например, BaseActivity.

Контекст приложения

Контекст приложения — это основа работы любого приложения, в том числе и игры. Он служит для получения доступа к настройкам и ресурсам, используемым несколькими экземплярами активностей.

Получить доступ к контексту приложения текущего процесса можно через метод getApplicationContext():


Context context = getApplicationContext();

Поскольку класс Activity происходит от класса Context, вы можете использовать ключевое слово this вместо явного указания контекста приложения.

Разобравшись с функциональностью будущей игры, можно приступить к её реализации.

Мы знаем, что класс Activity — это основной класс любого приложения на Android. Значит, нам понадобится пять различных классов Activity для нашей цели. В процессе игры пользователь будет переходить из одной активности в другую (кроме первой активности, где будет предусмотрен автоматический переход).

Есть несколько вариантов запуска активностей

  • Через указание в файле манифеста — так запускается активность по умолчанию. В нашем случае это Splash-заставка
  • C помощью контекста приложения при помощи startActivity()
  • Запуск дочерней активности из родительской активности

Заставка-экран

Сама заставка должна появиться на несколько секунд. Обычно на неё помещают картинку, название игры, номер версии, автора и т.д. В некоторых случаях уместно использовать небольшую анимацию.

Меню игры

Экран меню игры служит для навигации по различным экранам игры. Меню появляется сразу после экрана-заставки и предоставляет пользователю выбор дальнейших действий — перейти сразу к игре, прочитать инструкцию, посмотреть таблицу рекордов и так далее.

Экран справки

На данном экране пользователь может ознакомиться с правилами игры. Поэтому здесь нужно отобразить текст справки с возможностью прокрутки.

Счет или таблица рекордов

В таблице рекордов пользователь может видеть текущий игровой счёт с перечнем игроков, заработавших наибольшее количество очков. Иными словами, здесь выводится статистика лучших игровых достижений или вывод текущего счета.

Экран настроек

На этом экране пользователь может редактировать и сохранять различные параметры, например, своё имя или аватар.

Основной экран

Здесь происходит основное действие игры. Если игра завершена, то нужно осуществить переход к экрану таблицы рекордов, чтобы посмотреть на результат игры.

Реализация прототипа приложения

Создадим новый проект с активностью SplashActivity, которая будет вызываться первой. Далее нам нужно создать файлы макетов для каждой активности. Так как первой у нас будет вызываться заставка, то переименуйте файл main_activity.xml в splash.xml. Затем можно сделать пять копий splash.xml и дать им новые имена: game.xml, help.xml, menu.xml, scores.xml и settings.xml.

Чтобы не путаться в экранах, рекомендую создать строковые ресурсы с именами экранов и сопоставить их с нужными экранами в TextView. Создаём ресурсы:


<string name="splash">Splash Screen</string>
<string name="game">Game Screen</string>
<string name="menu">Menu Screen</string>
<string name="help">Help Screen</string>
<string name="scrores">Scores Screen</string>
<string name="settings">Settings Screen</string>

Теперь открываем каждый файл разметки и меняем строчку android:text=»@string/hello» на android:text=»@string/splash» и т.п. После этой операции вы не будете путаться в экранах.

Сразу можно позаботиться о новом значке для игры и добавить его в папку drawable.

Мы уже договорились, что у нас будет основной класс BaseActivity. Создайте файл класса BaseActivity.java и вставьте минимальный код:


package ru.alexanderklimov.tomorpussy;

import android.app.Activity;

public class BaseActivity extends Activity {
	public static final String GAME_PREFERENCES = "GamePrefs";
}

Вернитесь к файлу SplashActivity.java и расширьте его из класса BaseActivity вместо класса Activity.

Скопируйте активность SplashActivity и создайте новые классы MenuActivity, HelpActivity, ScoresActivity, SettingsActivity и GameActivity.

В каждом созданном классе нужно заменить в строчке setContentView(R.layout.splash); ресурс макета на соответствующий, например, для экрана настроек, это будет R.layout.settings.

Далее необходимо прописать все созданные классы в манифесте приложения.

Для проверки можно запустить проект на эмуляторе. Если все сделано правильно, то в окне эмулятора появится экран Заставки, который содержит текст Splash Screen.

Создание экрана-заставки

Наша задача — создать экран, который исчезнет сам после короткой паузы. Для игры можно использовать анимационную заставку, а также выводить информацию о версии, разработчике и т.д. Напоминаю, что для макета заставки у нас используется файл splash.xml в папке layout.

После анимации нужно запустить новый экран с меню и закрыть текущий экран.


startActivity(new Intent(SplashActivity.this,
		MenuActivity.class));
SplashActivity.this.finish();
Реклама

Содержание

  • Начало
  • Типичный сценарий
  • Import Android
  • Пример разработки простой 2D-игрушки Андроид
  • MainActivity, GameView, SpaceBody

Android Studio – официальная среда разработки приложений под ОС Андроид. Также она доступна пользователям Windows, Linux и Mac OS X. Мы расскажем, как создать в этой среде простую 2D-игру без применения специальных движков. Однако прежде чем приступать к работе, нужно в достаточной степени изучить саму программу.

Начало

Пользователи среды могут программировать на языках Java, C++ и Kotlin. Планируется, что последний со временем полностью заменит привычный Java, который пока остается основным. Для работы потребуется от 3 (минимум) до 8 Гб (желательно) оперативной памяти плюс дополнительный гигабайт для Android Emulator. Свободного места на жестком диске должно быть не меньше, чем 2 Гб.

Процесс установки Андроид Студио мало чем отличается от других программ

Если используется Microsoft Windows, подойдут версии 2003, Vista, 7–10. Для OS X нужен Mac от 10.8.5 до 10.13 / 10.14 (High Sierra/Mojave). Для Linux – KDE или GNOME. Изготовление приложения проходит в несколько этапов:

  • создаем новый проект;
  • разрабатываем пользовательский интерфейс;
  • добавляем навигацию, действия, дополнительные опции;
  • тестируем программу в эмуляторе.

Если собираетесь работать с Java, обязательно установите последнюю версию JDK. Скачать ее можно на официальном сайте. Помимо главной программы, для работы также потребуются элементы Андроид SDK – скрипты, библиотеки, документы, файлы. Эти компоненты будут скачаны автоматически. В установочный комплект также входит Андроид Emulator.

Проверяйте количество свободного места на диске

Следующим шагом станет определение адресов Студио и СДК. Их предлагается установить в отдельные папки. Перед началом инсталляции стоит убедиться, что на выбранном диске достаточно места. Сама Studio требует не так много свободного пространства, а вот элементы SDK занимают больше 3 Гб. Это минимум, так как затем потребуется дополнительная площадь для обновлений.

Каждое приложение, сделанное под Андроид, должно состоять из четырех точек входа:

  • Service. Компонент, обеспечивающий работу в фоновом режиме. Он отвечает за выполнение удаленных и длительных операций при выключенном визуальном интерфейсе.
  • Activity. Элементы интерактивного управления. Через класс Intent передается информация о намерениях пользователя. Активности устроены по подобию веб-страниц. Intent выполняет функцию ссылок между ними. Запускается приложение посредством activity Main.
  • Broadcast receiver. «Широковещательный приемник» передает намерения одновременно разным участникам.
  • Content provider. «Поставщик содержимого» передает нужную информацию из БД SQLite, файловой системы и других хранилищ.

Разработка приложения начинается с нового проекта. В меню последовательно выбираем Tools, Android, SDK Manager. В нашем примере последней версией является Андроид API 26. Выбирайте новейшую версию, поставив напротив нее галочку, и приступайте к скачиванию.

Выбирайте новейшую версию Андроид SDK

После нажатия New project появится форма нового проекта. В поле Application name выбираем FirstGame, Company domain – оставим без изменения. Путь к проекту Project location должен быть целиком на английском языке. В следующем окне оставьте галочку только напротив Phone and Tablet.

В этом окне определяется версия ОС для мобильных и планшетов

Теперь выберем версию ОС, с которой сможет запускаться игра. Чем ниже она будет, тем больше пользователей получат доступ к приложению. С другой стороны, разработчику тогда доступно меньше опций. Поочередно выбираем Empty Activity, Next, Next, Finish. Проект готов к работе.

С каждым запуском Студио открывается вкладка «Совет дня» (Tip of the day). Для начинающих программистов от этой опции мало толку, но по мере знакомства со средой рекомендации начнут казаться интересными и полезными. Вообще, для новичков многое будет выглядеть таинственно и даже страшновато. Не стоит бояться трудностей. Накапливайте опыт и непонятное быстро станет простым и ясным. В конце концов, это не изучение языка программирования.

Типичный сценарий

Простая игра строится по определенной схеме. Для взаимодействия с пользователем предусмотрены следующие элементы:

  • Основной дисплей. На нем разворачиваются события. По завершении процесса рекомендуется реализовать переход к таблице рекордов, если результат оказался достойным этой «доски почета».
  • Меню. С помощью этого инструмента выбирают действия и делают настройки. Он обеспечивает переход к другим элементам. Меню обычно появляется сразу после заставки. Пользователю предлагается выбор дальнейших действий: приступить к игровому процессу, ознакомиться с инструкцией и правилами, просмотреть текущие рекорды и т. д.
  • Заставка. Представляет собой краткий анонс или рекламу с изображением логотипа. Демонстрируется в начале или во время пауз. Приветствуется использование хотя бы простой анимации.
  • Справка. В этом разделе меню описывают правила. Если текст не помещается в окне целиком, необходимо обеспечить возможность прокрутки.
  • Счет. Здесь отображаются текущие рекорды участников. В данном разделе можно просмотреть список в любой момент, не дожидаясь, когда сам там окажется. Также здесь бывает доступна информация о текущем игровом счете, но последний обычно можно наблюдать на основном дисплее.
  • Настройки. Пользователь должен иметь возможность поменять игровые параметры, свой аватар и логин или зарегистрироваться в качестве нового участника.

Это основные инструменты стандартного игрового сценария. При разработке приложения какие-то можно убирать или добавлять дополнительные. Чтобы обеспечить появление этих шести экранов, нужно реализовать соответствующее число активностей.

В MenuActivity будут содержаться кнопки и другие элементы, запускающие остальные подпрограммы. SplashActivity потребуется для вывода заставки. Через несколько секунд автоматически запустится MenuActivity. Остальные активности: GameActivity (основной), SettingsActivity (настройки), ScoresActivity (счет) и HelpActivity (справка с возможностью прокрутки).

Рекомендуется также задать базовый class BaseActivity, включающий общедоступные компоненты. Для каждой Activity требуется отдельный разметочный файл с набором нужных элементов. Первоначальной задачей разработчика является освоение работы с активностями. Для получения доступа к ресурсам и настройкам, используемым Activity, нужно сделать контекст приложения. Здесь прибегают к помощи метода getApplicationContext().

Import Android

Import Android – опция, позволяющая автоматически обновлять библиотеки (public static, public void, public class, override public void и др). Такая потребность часто возникает при использовании фрагментов кода. Можно воспользоваться традиционной комбинацией Import Android – Alt + Enter.

Этим простым методом обновления импорта public static, override public void, public void и прочих нужных для работы вещей воспользоваться несложно. Однако существует и более интересный вариант – автоматический Import Android.  Для его реализации нужно последовательно выбрать в меню File, Settings, Edito, AutoImport. Остается поставить флажки напротив нужных пунктов. Теперь Import Android будет обновляться самостоятельно.

Автоматический Import Android позволяет быстро обновлять public static, public void и другие инструменты

Пример разработки простой 2D-игрушки Андроид

Наша игра Android Studio развивается по известному сюжету. Пользователь управляет космическим кораблем, уворачивающимся от метеоритов (астероидов). Последние падают с верхней части экрана, корабль – движется внизу вправо или влево, в зависимости от решений участника. При столкновении аппарата с космическим объектом объявляется Game Over.

Начнем с открытия проекта. Для этого последовательно выберем в меню программы File, New, New Project. Придумываем проекту название, вводим домен и место, где будет храниться папка. Окно, появившееся после нажатия Next, лучше оставить без изменений. В следующем выбираем Empty Activity и движемся дальше. Кликнув по клавише Finish, мы получим готовый проект.

Следующим шагом станет скачивание необходимых картинок и копирование их в папку drawable. Это изображения корабля и метеоров. После этого нужно создать layout. Открываем Text в activity_main.xml и вставляем следующий код:

Код для layout

MainActivity, GameView, SpaceBody

Для редактирования класса MainActivity меняем определение, придав ему следующий вид: public class MainActivity extends AppCompatActivity implements View.OnTouchListener {. После этого нужно задать перемены для нажатия левой (public static boolean isLeftPressed = false) и правой (public static boolean isRightPressed = false) кнопок. Следующие действия мы расписывать не будем. В итоге MainActivity должен принять следующий вид:

Код для MainActivity

Разобравшись с классом MainActivity, переходим к GameView. В определение добавляем extends SurfaceView implements Runnable. Теперь нужно задать разрешение. У современных гаджетов разные параметры. Дисплей старого мобильника не может сравниться с новым большим планшетом.

Чтобы добиться одинакового изображения на любом устройстве, поделим монитор на одинаковые «клетки» 20х28 (первый показатель – горизонталь). Если эти части будут распределены неравномерно, картинка получится сжатой или растянутой. Задаем переменные:

Переменные для «уравнивания» графики

Для метода run() устанавливается бесконечный цикл, стартующий с update(). Задачей последнего является вычисление новых координат космического корабля. По окончании расчетов на экране будет сформирован сам аппарат (draw()). Control() завершает цикл, обеспечивая паузу на 17 миллисекунд. Затем снова запускается run(). Выглядеть это будет так:

Бесконечный цикл для run()

Чтобы появился сам корабль и астероиды, нужен родительский class SpaceBody. Зададим переменные и методы:

Код для родительского класса SpaceBody

Теперь отдельный класс Ship для корабля:

Код космического корабля

После этого останется произвести компиляцию и запуск программы. На дисплее Android Studio должен возникнуть корабль, который можно кнопками перемещать вправо и влево. Следующим шагом станет добавление астероидов. Для этого разработаем class Asteroid, тоже являющийся дочерним для SpaceBody. Зададим переменные:

Код для метеоров

Суть в том, чтобы астероиды произвольно возникали в разных точках «потолка» экрана и двигались с непредсказуемой скоростью. Мы задали код, определяющий столкновение метеоров с кораблем. Добавим астероиды в GameView:

Вписываем астероиды в GameView

На этом изготовление элементарной 2D-игры можно считать завершенным. Остается скомпилировать и запустить программу. Ничто не мешает добавлять в нее новые опции. Например, стрельбу по метеорам или постоянное ускорение их движения. Но это уже тема отдельного разговора.

img

Недавно мы делали веб-игру про сбор пирамиды. Там мы использовали 3Д-движок и симуляцию физики. И в целом получилась залипательная веб-игра. Вот предыдущие этапы: 

  1. Сделали трёхмерную браузерную игру, где нужно ставить блоки друг на друга и набрать как можно больше очков.
  2. Адаптировали игру под мобильные телефоны, чтобы тачскрин нормально обрабатывал все нажатия и жесты.

Ключевое слово — веб: игра работает только в браузере и только при наличии интернета. На этот раз мы превратим страницу с игрой в полноценное приложение для Android. При этом мы не будем пользоваться онлайн-конструкторами, а сделаем всё по-настоящему — в среде разработки и с кодом на Java.

Наш план таков: 

  1. Подготовить файлы для упаковки в игру: скачать скрипты из интернета, перепривязать их к нашей игре на компьютере.
  2. Сделать новый проект в Android Studio.
  3. В проекте сделать WebView — это виртуальное окно браузера внутри приложения. В нём будет работать игра.
  4. Настроить WebView так, чтобы он поддерживал нашу игру и все нужные функции.
  5. Упаковать получившийся проект в виде приложения для Android.

Результат можно скачать сразу: вот приложение, которое получилось у нас таким нехитрым способом:

 ⭐️ Скачать apk 

Подготовка

Главное, что нам понадобится из инструментов, — официальная среда разработки Android Studio. У нас есть про неё отдельная статья: что это такое, зачем нужно и как установить. Качаем с официального сайта и устанавливаем.

Так как наша игра должна работать без интернета, то это значит, что все ресурсы тоже должны быть доступны локально, прямо с телефона. В HTML-файле мы подключаем два внешних скрипта — их тоже нужно будет скачать:

<!-- подключаем Three.js -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/r124/three.min.js'></script>
<!-- подключаем Cannon.js -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js'></script>
<!-- подключаем наш скрипт -->
<script src="./script.js"></script>

Для этого копируем адрес скрипта из кода страницы, вставляем в браузер и сохраняем то, что открылось, как js-файл.

Скрипты кладём в ту же папку, что и остальные файлы с игрой. Вот что должно по итогу у нас получиться с файлами:

Делаем сами себе игру для Android

После этого исправляем пути к скриптам в html-файле, чтобы они подтягивались из той же папки, а не из интернета:

<!-- подключаем Three.js -->
<script src='three.min.js'></script>
<!-- подключаем Cannon.js -->
<script src='cannon.min.js'></script>
<!-- подключаем наш скрипт -->
<script src="script.js"></script>

Сохраняем страницу и открываем её в браузере. Если мы всё сделали правильно, то «Пирамида» запустится как обычно — с красивой графикой и реакцией на нажатия.

Создаём новый проект в Android Studio

Запускаем Android Studio и выбираем Empty Activity:

Делаем сами себе игру для Android

После этого выбираем язык Java, а всё остальное оставляем без изменений:

Делаем сами себе игру для Android

Если это ваш первый запуск, программа начнёт качать разные служебные файлы — это нормально, нужно просто немного подождать.

Делаем сами себе игру для Android

Когда всё загрузится и запустится, перед нами появится окно с новой программой в Android Studio. 

Добавляем файлы

Чтобы программа смогла загрузить в себя все файлы от игры, нам нужно создать в проекте специальную папку — assets — и скопировать в неё всё, что у нас есть. Для этого нужно пройти несколько неочевидных шагов: 

  1. Создать папку внутри проекта в Android Studio.
  2. Найти папку на диске.
  3. Скопировать в папку все нужные файлы.

Создаём папку так: в левой верхней части щёлкаем правой кнопкой мыши по папке app и в появившемся меню выбираем New → Folder → Assets Folder:

Делаем сами себе игру для Android

Перед нами появится окно, которое спрашивает, к чему будет относиться папка. Ничего не меняем и просто нажимаем Finish:

Делаем сами себе игру для Android

Теперь щёлкаем правой кнопкой мыши на появившейся папке и выбираем Open in → Explorer:

Делаем сами себе игру для Android

Перед нами появится окно проводника с нашей папкой assets. Заходим в неё и копируем туда все игровые файлы, которые мы собрали в самом начале:

Делаем сами себе игру для Android

Смотрим в панель файлов Android Studio, чтобы убедиться, что всё получилось и система увидела наши файлы:

Делаем сами себе игру для Android

Пишем код

Нам было бы здорово видеть и дизайн, и код, поэтому выбираем слева в колонке файлов res → layouts → activity_main.xml и переключаем вид в режим Split в правом верхнем углу:

Делаем сами себе игру для Android

В этом же файле activity_main.xml есть блок, который начинается с команды <TextView — удаляем весь блок (название и 7 команд ниже) и вместо него пишем <WebView>. Среда разработки умная, поэтому, как только мы начнём писать код, она автоматически предложит нам создать новый блок. Нажимаем энтер, когда появится подсказка:

Делаем сами себе игру для Android

Вот команды, которые нужно добавить в этот файл:

<WebView
android:layout_width="match_parent"   
android:layout_height="match_parent"  
android:id="@+id/webview"></WebView>

В итоге у нас должен получиться такой блок:

Делаем сами себе игру для Android

Нажимаем ⌘+S или Ctrl+S, чтобы всё сохранить .

Теперь переходим к другому файлу — MainActivity.java — и добавляем в него такой код:

Делаем сами себе игру для Android

setContentView(R.layout.activity_main);
WebView webView=findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl("javascript:addLayer(x, z, width, depth, direction)");
webView.loadUrl("javascript:generateBox(x, y, z, width, depth, falls)");
webView.loadUrl("javascript:addOverhang(x, z, width, depth)");
webView.loadUrl("javascript:cutBox(topLayer, overlap, size, delta)");
webView.loadUrl("javascript:init()");
webView.loadUrl("javascript:startGame()");
webView.loadUrl("javascript:eventHandler()");
webView.loadUrl("javascript:splitBlockAndAddNextOneIfOverlaps()");
webView.loadUrl("javascript:missedTheSpot()");
webView.loadUrl("javascript:animation(time)");
webView.loadUrl("javascript:updatePhysics(timePassed)");
webView.loadUrl("javascript:window.addEventListener()");

webView.loadUrl("file:///android_asset/index.html");

Смысл тут в том, что мы сначала создаём новый элемент — просмотрщик веб-контента, потом разрешаем ему выполнять скрипты, а затем перечисляем все функции, которые у нас объявлены в основном скрипте вместе с параметрами вызова. Это нужно для того, чтобы Java знала, что это можно выполнять.

Нажимаем Shift+F10, чтобы запустить приложение в эмуляторе, — видим, что справа появился виртуальный телефон с началом нашей игры, но ничего не двигается. Это связано с тем, что встроенный эмулятор плохо работает с трёхмерной графикой и не может показать всё, что мы от него хотим. Главное, зачем нам это было нужно, — убедиться, что программа нашла все наши файлы, загрузила их и скрипт тоже заработал.

Делаем сами себе игру для Android

Компилируем приложение

Если нам нужен apk-файл, который можно установить на телефон, чтобы проверить игру по-настоящему, нам надо скомпилировать весь проект. Для этого выбираем в верхнем меню Build → Build Bundle(s) / APK(s) → Build APK(s):

Делаем сами себе игру для Android

Когда всё будет готово, внизу появится уведомление, что файл готов. Нажимаем на locate, чтобы перейти к файлу в проводнике:

Делаем сами себе игру для Android

Делаем сами себе игру для Android

Этот файл можно скачать себе на телефон с Android, установить его и поиграть в «Пирамиду» даже без интернета.

Вёрстка:

Кирилл Климентьев

Понравилась статья? Поделить с друзьями:
  • Как написать игру на html5
  • Как написать игру на html
  • Как написать игру на delphi
  • Как написать игру на c visual studio
  • Как написать игру морской бой на python