Доброго дня всем!
Когда я писал эту «игру» у меня возникала масса вопросов по поводу зацикливания спрайтов так что бы они появлялись через определенное время, так же были проблемы с обнаружением столкновений двух спрайтов и более, все эти вопросы я сегодня хочу осветить в этом посте так как в интернете я не нашел нормального ответа на мои вопросы и пришлось делать самому. Пост ни на что не претендует, я новичок в разработке игр под android и пишу я для новичков в данной отрасли. Кому стало интересно прошу под кат.
Игра должна представлять из себя поле (сцену) на котором располагается ниндзя и призраки. Нинзя должен защищать свою базу от этих призраков стреляя по ним.
Пример такой игры можно посмотреть в android market'e. Хотя я сильно замахнулся, у нас будет только похожая идея.
Вот как будет выглядеть игра:

Создаем проект. Запускаем Eclipse — File — Android Project — Defens — Main.java.
Открываем наш файл Main.java и изменяем весь код на код который ниже:
Main.java
Код ниже говорит нашей главной функции что запускать нужно не *.xml файл темы, а класс который у нас является самой сценой.
Дальше Вам нужно создать класс GameView.java который будет служить для нас главным классом на котором будет производится прорисовка всех объектов. Так же в этом классе будет находится и наш поток в котором будет обрабатываться прорисовка объектов в потоке для уменьшения нагрузки игры на процессор. Вот как будет выглядеть класс когда на сцене у нас ничего не происходит:
GameView.java
Из комментариев надеюсь понятно какая функция что делает. Этот класс является базовым по этому в нем мы будем производиться все действия (функции) которые будут происходить в игре, но для начало нам нужно сделать еще несколько классов Переходи к следующему пункту — создание спрайтов.
Спрайты это маленькие картинки в 2D-играх, которые передвигаются. Это могут быть человечки, боеприпасы или даже облака. В этой игре мы будем иметь три различных типа спрайта: Нинзя
, призрак
, и снаряд
.
Сейчас мы будем использовать не анимированные спрайты но в будущем я вставлю спрайты в проэкт, если тянет научиться делать спрайты прошу во второй урок по созданию игры под android.
Теперь загрузите эти картинки в папку res/drawable для того, чтобы Eclipse мог увидеть эти картинки и вставить в Ваш проект.
Следующий рисунок должен визуально помочь понять как будет располагаться игрок на экране.

Скучная картинка… Давайте лучше создадим этого самого игрока.
Нам нужно разместить спрайт на экране, ка�� это сделать? Создаем класс Player.java и записываем в него следующее:
Player.java
Все очень просто и понятно, наш игрок будет стоять на месте и ничего не делать, кроме как стрелять по врагу но стрельба будет реализована в классе пуля (снаряд), который будем делать дальше.
Создаем еще один файл классов и назовем его Bullet.java, этот класс будет определять координаты полета, скорость полета и другие параметры пули. И так, создали файл, и пишем в него следующее:
Bullet.java
Из комментариев должно быть понятно что пуля выполняет только одно действие — она должна лететь по направлению указанному игроком.
Для того что бы нарисовать эти два класса которые мы создали, нам нужно отредактировать код в классе GameView.java, добавить несколько методов которые будут возвращать нам наши рисунки. Полностью весь код я писать не буду, буду приводить только код нужных мне методов.
Для начала нам нужно создать объекты классов Bullet и Player для того что бы отобразить их на экране, для этого создадим список пуль, что бы они у нас никогда не заканчивались, и обычный объект класса игрока.
Шапка GameView
Дальше нам нужно присвоить картинки нашим классам, находим конструктор GameView и вставляем в самый конец две строчки:
GameView.java — Конструктор GameView
И в методе onDraw(Canvas c); делаем видимыми эти спрайты. Проходим по всей коллекции наших элементов сгенерировавшихся в списке.
GameView,java
А для того что бы пули начали вылетать при нажатии на экран, нужно создать метод createSprites(); который будет возвращать наш спрайт.
GameView.java
Ну и в конце концов создаем еще один метод — onTouch(); который собственно будет отлавливать все касания по экрану и устремлять пулю в ту точку где было нажатия на экран.
GameView.java
Если хотите сделать что бы нажатие обрабатывалось не единоразово, т.е. 1 нажатие — 1 пуля, а 1 нажатие — и пока не отпустишь оно будет стрелять, нужно удалить if(e.getAction() == MotionEvent.ACTION_DOWN) { }
и оставить только ball.add(createSprite(R.drawable.bullet));.
Все, запускаем нашу игру и пробуем стрелять. Должно выйти вот такое:
Для того что бы нам не было скучно играться, нужно создать врагов. Для этого нам придется создать еще один класс который будет называться Enemy.java и который будет уметь отображать и направлять нашего врага на нашу базу. Класс довольно простой по этому смотрим код ниже:
Enemy.java
И так что происходит в этом классе? Рассказываю: мы объявили жизненно важные переменные для нашего врага, высота ширина и координаты. Для размещения их на сцене я использовал класс Random() для того что бы когда они будут появляться на сцене, появлялись на все в одной точке, а в разных точках и на разных координатах. Скорость так же является у нас рандомной что бы каждый враг шел с разной скоростью, скорость у нас начинается с 0 и заканчивается 10, 10 — максимальная скорость которой может достигнуть враг. Двигаться они будут с права налево, для того что бы они не были сразу видны на сцене я закинул их на 900 пикселей за видимость экрана. Так что пока они дойдут можно уже будет подготовиться по полной к атаке.
Дальше нам нужно отобразить врага на сцене, для этого в классе GameView.java делаем следующее:
Создаем список врагов для того что бы они никогда не заканчивались и создаем битмап который будет содержать спрайт:
Шапка GameView
Далее создаем новый поток для задания скорости появления врагов на экране:
Шапка GameView
И имплементируем класс Runuble, вот как должна выглядеть инициализация класса GameView:
Теперь у Вас еклипс требует создать метод run(), создайте его, он будет иметь следующий вид:
В самом низу класса GameView
Здесь мы создаем поток который будет создавать спрайт от 0 до 2000 милисекунд или каждые 0, 1 или 2 секунды.
Теперь в конструкторе в самом конце пишем инициализируем наш спрайт с классом для отображения на сцене:
Конструктор GameView
Ну и конечно же нам нужно объявить эти методы в onDraw(); Вот значит и пишем в нем следующее:
Метод onDraw() в GameView
Снова проходим по коллекции врагов с помощью итератора и проверяем — если враг зашел за предел в 1000 пикселей — удаляем его, так как если мы не будем удалять у нас пямять закакается и телефон зависнет, а нам такие проблемы не нужны. Все игра готова для запуска.
Запускаем нашу игру и что мы увидим? А вот что:
Но что я вижу? О нет!!! Пули никак не убивают наших призраков что же делать? А я Вам скажу что делать, нам нужно создать метод который будет образовывать вокруг каждого спрайта — прямоугольник и будет сравнивать их на коллизии. Следующая тема будет об этом.
И так, у нас есть спрайт, у нас есть сцена, у нас все это даже движется красиво, но какая польза от всего этого когда у нас на сцене ничего не происходит кроме хождения туда сюда этих спрайтов?
С этой функцией я навозился по полной, даже как-то так выходило что психовал и уходил гулять по улице)) Самый трудный метод, хотя выглядеть совершенно безобидно…
Ладно, давайте уже создадим этот метод и не будем много разглагольствовать… Где то в конце класса GameView создаем метод testCollision() и пишем следующий код:
В самом низу класса GameView.java
И так, что у нас происходит в этом методе? Мы создаем один итератор и запускаем цикл для просмотра всей коллекции спрайтов, и говорим что каждый следующий спрайт пули будет первым.
Дальше создаем еще один итератор с другим списком спрайтов и снова переопределяем и говорим что каждый следующий спрайт врага будет первым. И создаем оператор ветвления — if() который собственно и проверяет на столкновения наши спрайты. В нем я использовал математическую функцию модуль (abs) которая возвращает мне абсолютное целое от двух прямоугольников.
Внутри ифа происходит сравнения двух прямоугольников Модуль от (Пуля по координате Х минус координата врага по координате Х меньше либо равен ширина пули плюс ширина врага / 2 (делим на два для нахождения центра прямоугольника)) и (Модуль от (Пуля по координате У минус координата врага по координате У меньше либо равен ширина пули плюс ширина врага / 2 (делим на два для нахождения центра прямоугольника)));
И в конце всего, если пуля таки достала до врага — мы удаляем его со сцены с концами.
Ну и для того что бы эта функция стала работать записываем её в метод run() который находится в классе GameThread, ниже нашего метода рисования onDraw().
Вот что у нас получается после запуска приложения:
Когда я писал эту «игру» у меня возникала масса вопросов по поводу зацикливания спрайтов так что бы они появлялись через определенное время, так же были проблемы с обнаружением столкновений двух спрайтов и более, все эти вопросы я сегодня хочу осветить в этом посте так как в интернете я не нашел нормального ответа на мои вопросы и пришлось делать самому. Пост ни на что не претендует, я новичок в разработке игр под android и пишу я для новичков в данной отрасли. Кому стало интересно прошу под кат.
Постановка задачи:
Игра должна представлять из себя поле (сцену) на котором располагается ниндзя и призраки. Нинзя должен защищать свою базу от этих призраков стреляя по ним.
Пример такой игры можно посмотреть в android market'e. Хотя я сильно замахнулся, у нас будет только похожая идея.
Вот как будет выглядеть игра:

Начало разработки
Создаем проект. Запускаем 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-играх, которые передвигаются. Это могут быть человечки, боеприпасы или даже облака. В этой игре мы будем иметь три различных типа спрайта: Нинзя
, призрак
, и снаряд
. Сейчас мы будем использовать не анимированные спрайты но в будущем я вставлю спрайты в проэкт, если тянет научиться делать спрайты прошу во второй урок по созданию игры под android.
Теперь загрузите эти картинки в папку res/drawable для того, чтобы Eclipse мог увидеть эти картинки и вставить в Ваш проект.
Следующий рисунок должен визуально помочь понять как будет располагаться игрок на экране.

Скучная картинка… Давайте лучше создадим этого самого игрока.
Нам нужно разместить спрайт на экране, ка�� это сделать? Создаем класс 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().
Вот что у нас получается после запуска приложения: