[Java] Создание простейшей 2D игры на Android - Форум Cheat-Master.ru
  • Страница 1 из 2
  • 1
  • 2
  • »
Модератор форума: Alowir, Sanoxxx  
[Java] Создание простейшей 2D игры на Android
karabawka Дата: Суббота, 02.11.2013, 19:30 | Сообщение # 1
Главный предприниматель
Сообщений: 1121
Статус: Offline
Создание простой 2D игры на Android

Источник:habrahabr.ru
Автор:Dajver


Очень понравилась статья, поэтому решил опубликовать ее здесь (все равно уроков мало интересных и толковых, в основном все слизано без объяснений). Будет полезно для тех, кто хочет попробовать создавать игрушки и приложения для платформы android. Процесс создания простой игры описан в деталях. Все по-порядку wink © Karabawka


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

Когда я писал эту «игру» у меня возникала масса вопросов по поводу зацикливания спрайтов так что бы они появлялись через определенное время, так же были проблемы с обнаружением столкновений двух спрайтов и более, все эти вопросы я сегодня хочу осветить в этом посте так как в интернете я не нашел нормального ответа на мои вопросы и пришлось делать самому. Пост ни на что не претендует, я новичок в разработке игр под 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 и записываем в него следующее:

Код
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, этот класс будет определять координаты полета, скорость полета и другие параметры пули. И так, создали файл, и пишем в него следующее:

Код
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().

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


Прикрепления: 0212716.jpg (9.3 Kb) · 1504058.png (0.9 Kb) · 6129656.png (1.8 Kb) · 2436723.png (1.1 Kb) · 9286637.png (10.4 Kb)


Сообщение отредактировал karabawka - Суббота, 02.11.2013, 19:47
Бобёрчик Дата: Суббота, 02.11.2013, 19:42 | Сообщение # 2
Самый главный бобёр
Сообщений: 95
Статус: Offline
Лойс однозначно
Прикрепления: 1177000.png (4.9 Kb)


Что я не делал. Как ни старался помочь. Всё вышло мне боком от тех людей которым доверяю. В итоге всё что я делал, было зря..
[CM]Duksxamik Дата: Среда, 06.11.2013, 23:18 | Сообщение # 3
Читор
Сообщений: 62
Статус: Offline
super
Wallper Дата: Четверг, 14.11.2013, 08:31 | Сообщение # 4
Новичок
Сообщений: 9
Статус: Offline
Спасибо, не думал что сам смогу написать игрушку на JAVA)
karabawka Дата: Четверг, 14.11.2013, 16:05 | Сообщение # 5
Главный предприниматель
Сообщений: 1121
Статус: Offline
Цитата Wallper ()
Спасибо, не думал что сам смогу написать игрушку на JAVA)


ни капли не сам! lol
Kanoo Дата: Четверг, 14.11.2013, 22:11 | Сообщение # 6
Бывалый
Сообщений: 324
Статус: Offline
Нечего себе! surprised

Мой бот на ловлю машин(Arizona)
https://yadi.sk/d/8f0T4vnKPtqsFQ
http://rgho.st/8ypsgBTjx
Pыбак Дата: Воскресенье, 02.02.2014, 11:58 | Сообщение # 7
Боец
Сообщений: 96
Статус: Offline
Будeм пробывать biggrin
stc Дата: Пятница, 21.02.2014, 00:34 | Сообщение # 8
Боец
Сообщений: 65
Статус: Offline
+ без споров

Если помог,то отблагодарите,это же не сложно.
Radiance Дата: Пятница, 21.03.2014, 11:51 | Сообщение # 9
Боец
Сообщений: 209
Статус: Offline
Статья не плохая, но всё же от явы у меня вечно бомбит. Ну вот не хочет она ко мне личиком поворачиваться, и всё тут.
А так щас под ведроиды, ифоны можно и на С кодить вполне успешно, единственный минус - будет занимать чуть больше места, да и с этим справиться легко)



Радик камбек :D

TIGGGER Дата: Понедельник, 23.06.2014, 12:08 | Сообщение # 10
Бывалый
Сообщений: 346
Статус: Offline
Круто так уметь я на 2 пункте психанул и забил

Markus_Gulliver Дата: Воскресенье, 13.07.2014, 01:21 | Сообщение # 11
Боец
Сообщений: 216
Статус: Offline
Вау, однозначно лайк!!!
ChelovekAnekdot Дата: Воскресенье, 13.07.2014, 18:46 | Сообщение # 12
Боец
Сообщений: 54
Статус: Offline
Прикольно
[WS]Grenhelgets Дата: Понедельник, 01.12.2014, 17:45 | Сообщение # 13
Боец
Статус: Offline
Добавь в скайп nicelse.ws предложение есть.
GlaroX Дата: Четверг, 24.11.2016, 03:07 | Сообщение # 14
Боец
Сообщений: 201
Статус: Offline
Отличный разбор немного изучаю!

ChesterHelium Дата: Воскресенье, 20.08.2017, 13:57 | Сообщение # 15
Боец
Сообщений: 212
Статус: Offline
Цитата _xEn0n ()
Жаль, что даже на такую простенькую игру новичку придется убить много времени. Эти языки программирования...


Не особо то и много времени
Тут часа 2 от силы склейка, ибо тут тупо все расписано налегке, даже обычный юзер с 1 курса программирования поймет




Сидим в VK с разных профилей в одном браузере
Бесплатно отправляем смс-сообщения
Обход подписи в 255 символов на SAMP-RP
Способ накрутки личных сообщений в ВК
Халявные дедики
[DELPHI] - Выключение компьютера по Button'у
[DELPHI] - Запрос логина и пароля на форме
Делаем убийственные .BAT файлы
Скрипт для e-mail рассылки до 100к в сутки
Обход авторизации на бесплатных точках WiFi
Флэшка - убийца для быстрого сноса винды
Расширение для Google Chrome - Бесконечный набор сообщения (Бесконечный карандаш)
Заливаем фотографии / смотрим истории в instagram с компьютера
Получаем письма с Америки, Канады, Аляски и т.д. с оф. нашивками пожарных

  • Страница 1 из 2
  • 1
  • 2
  • »
Поиск:
Статистика Форума