html текст
All interests
  • All interests
  • Design
  • Food
  • Gadgets
  • Humor
  • News
  • Photo
  • Travel
  • Video
Click to see the next recommended page
Like it
Don't like
Add to Favorites

Пишем игру под Android: Часть 5 - Создание полноценной 2D игры

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

Сегодня я хочу Вам рассказать как написать игру под android исходя из уроков которые я писал здесь.

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


  1. Пишем игру под Android: Часть 1 — Рисуем картинки на SurfaceView
  2. Пишем игру под Android: Часть 2 — Создаем первый спрайт
  3. Пишем игру под Android: Часть 3 — Спрайтовая анимация, работа с несколькими спрайтами
  4. Пишем игру под Android: Часть 4 — onTouchEvent и определение столкновений
  5. Пишем игру под Android: Часть 5 — Создание полноценной 2D игры
  6. Пишем игру под Android: Часть 6: Добавление звука
  7. Пишем игру под Android: Часть 7: Меню для игры и окно приветствия
  8. Пишем игру под Android: Часть 8: Фоновая музыка в игре


Если уже читали эти статьи и знаете что и как, тогда добро пожаловать под кат. Будем писать игру.

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


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

Пример такой игры можно посмотреть в 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(player, 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(). И в surfaceCreated() добавляем thred.start() для запуска отрисовки объектов.

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


Исходные коды & Скачать с Git

Читать дальше
Twitter
Одноклассники
Мой Мир

материал с dajver.blogspot.ru

8

      Add

      You can create thematic collections and keep, for instance, all recipes in one place so you will never lose them.

      No images found
      Previous Next 0 / 0
      500
      • Advertisement
      • Animals
      • Architecture
      • Art
      • Auto
      • Aviation
      • Books
      • Cartoons
      • Celebrities
      • Children
      • Culture
      • Design
      • Economics
      • Education
      • Entertainment
      • Fashion
      • Fitness
      • Food
      • Gadgets
      • Games
      • Health
      • History
      • Hobby
      • Humor
      • Interior
      • Moto
      • Movies
      • Music
      • Nature
      • News
      • Photo
      • Pictures
      • Politics
      • Psychology
      • Science
      • Society
      • Sport
      • Technology
      • Travel
      • Video
      • Weapons
      • Web
      • Work
        Submit
        Valid formats are JPG, PNG, GIF.
        Not more than 5 Мb, please.
        30
        surfingbird.ru/site/
        RSS format guidelines
        500
        • Advertisement
        • Animals
        • Architecture
        • Art
        • Auto
        • Aviation
        • Books
        • Cartoons
        • Celebrities
        • Children
        • Culture
        • Design
        • Economics
        • Education
        • Entertainment
        • Fashion
        • Fitness
        • Food
        • Gadgets
        • Games
        • Health
        • History
        • Hobby
        • Humor
        • Interior
        • Moto
        • Movies
        • Music
        • Nature
        • News
        • Photo
        • Pictures
        • Politics
        • Psychology
        • Science
        • Society
        • Sport
        • Technology
        • Travel
        • Video
        • Weapons
        • Web
        • Work

          Submit

          Thank you! Wait for moderation.

          Тебе это не нравится?

          You can block the domain, tag, user or channel, and we'll stop recommend it to you. You can always unblock them in your settings.

          • Chrono
          • домен blogspot.ru
          • домен dajver.blogspot.ru

          Get a link

          Спасибо, твоя жалоба принята.

          Log on to Surfingbird

          Recover
          Sign up

          or

          Welcome to Surfingbird.com!

          You'll find thousands of interesting pages, photos, and videos inside.
          Join!

          • Personal
            recommendations

          • Stash
            interesting and useful stuff

          • Anywhere,
            anytime

          Do we already know you? Login or restore the password.

          Close

          Add to collection

             

            Facebook

            Ваш профиль на рассмотрении, обновите страницу через несколько секунд

            Facebook

            К сожалению, вы не попадаете под условия акции