четверг, 19 января 2012 г.

Пишем игру под 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

58 комментариев:

  1. не работает(((
    выдает ошибку

    The aplication Def(process Game.main)has stopped unexpectedly.Try again.

    Def(process Game.main) у Вас - towe.def

    ОтветитьУдалить
  2. Спасибо за статью, на днях попробую все набрать и протестировать.
    Заметил что что-то не так в столкновениях. Допустим возьмем координаты пули по х, левая = 20, правая = 30, и врага, левая = 31, правая = 51. То получится по формуле |20 - 31| <= (10 + 20)/2 выходит 11 <= 15, тоесть столкновение есть, хотя на самом деле его нету. В других примерах я видел используют класс Rect там есть функция intersects которая определяет пересекаются ли квадраты.

    ОтветитьУдалить
    Ответы
    1. В расчетах все правильно, если и есть погрешность то очень маленькая, а по поводу intersects, этой функции в android SDK нет, по этмоу пришлось изгаляться.

      Удалить
  3. почему нинзя такой толстый?)) а вообще спасибо тебе, добрый человек

    ОтветитьУдалить
    Ответы
    1. Сильно много пива пьют) И на онлайн игры подсели. Пожалуйста :)

      Удалить
  4. Если свернуть нажав на хоме и потом опять на него , вылетает с ошибкой

    ОтветитьУдалить
    Ответы
    1. Эту проблему не учел, как будет время напишу костыль что бы не было ошибок. Ну или перепишу так что бы все запускалось :)

      Удалить
  5. Больше всего интересует колизия(столкновения итд) Даже на последнем видео видно, шарик пролетает под юпкой призрака. Но в любом случае гг , это замечательно.

    ОтветитьУдалить
    Ответы
    1. Это из за того что размер я не отрегулиловал, если поставить чуть больше размер квадрата у врага то будет нормально убивать.

      Удалить
  6. Ужас, руки отбивать таким программерам-костылеписателям

    ПыСы написать функцию определяющую пересечение прямоугольников - дело 10 минут, причем сдесь SDK?

    ОтветитьУдалить
  7. Что графика и идея потырена с cocos2d-x.org можно было и упомянуть

    ОтветитьУдалить
    Ответы
    1. Извините конечно но какая разница какую я идею реализовываю? Разве я на этом зарабатываю? Я написал пособие по разработке, а чья идея уже не имеет значения.

      Удалить
  8. Может стоило бы "наплодить сущностей" и все же создать класс спрайтов, от которого уже наследовать пули, врагов и тд. ?

    ОтветитьУдалить
    Ответы
    1. Создать базовый класс от которого будет наследоваться все спрайты? Там проблемы с столкновениями тогда были бы. Попробуй сделать так и посмотри что будет когда два объекта столкнуться.

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

      Удалить
  9. И еще, Глеб, как можно с Вами связаться по поводу вопросов регистрации на маркете и монетизации приложений?

    ОтветитьУдалить
  10. на базе твеой игры сделал игру. спасибо, Мужик))
    ты, кстати, andEngine не используешь?

    ОтветитьУдалить
    Ответы
    1. Пожалуйста (:
      andAngine хотел начать учить, но прыгнул на openGL и все, теперь юзаю эту платформу)

      Удалить
  11. Очень нужна ваша помощь по поводу этой игры. Как можно с вами связатся?

    ОтветитьУдалить
  12. функция intersects есть :) это же базовая функция)

    Rect.intersects(Rect a, Rect b)

    Rect - это android.graphics.Rect

    А вообще, мой совет - это сразу брать какой-нибудь движок и делать игру на нем, потому что он как раз лишает тебя возни с низкоуровневым кодом и остается только реализовать саму логику.
    Великолепные Angry Birds сделаны на простом движке Box2D. У него подробный мануал и кроме того есть библиотеки не только на Java, но и на других языках - и это очень удобно в том случае, если захочется вдруг поменять платформу (например, перелезть с андроида на айфон или виндоузфон)
    Ну, Box2D это лишь пример.

    ОтветитьУдалить
    Ответы
    1. Функция есть, но она мне не подходила, вот я свою и сделал) А по поводу движка, соглашусь (: Вещь очень удобная, все делается в раз пять быстрее, но я это как пример писал, в дальнейшем будет время напишу игру на боксе, или на опенГЛ, как карта ляжет так и сделаю)

      Удалить
  13. в упор не понимаю строчки (e.x >= 1000 || e.x <= 1000) в onDraw() это условие ведь должно всегда выполняться?

    ОтветитьУдалить
    Ответы
    1. Там ошибка, все вы правильно поняли, нада поменять местами условия.

      Удалить
  14. можете сказать какая у вас версия eclipse у меня стоит sdk eclipse с adt от сюда http://developer.android.com/sdk/index.html
    и у меня нет android proect а на эти строки показывает ошибки
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(new GameView(this));
    подскажите пожалуйста что делать

    ОтветитьУдалить
    Ответы
    1. Как понять - "и у меня нет android proect "?

      По поводу строк сделайте Project - Clean, и если не помогло тогда впишите в манифест:
      в настройках активити

      android:screenOrientation="landscape"
      android:configChanges="keyboardHidden|orientation|screenSize"

      Удалить
    2. ну а в каком делать у меня есть seampelproject applicaionproject и testpoject и ещё
      что значит Запускаем Eclipse — File — Android Project — Defens — Main.java.

      Удалить
    3. Это значит что вам нужно запустить Eclipse, дальше нажать в меню вверху раздел File в нем выбрать Create android project и дальше создать проект с именем Defens и с файлом Main.java

      Удалить
  15. private List ball = new ArrayList(); private Player player;
    Bitmap players;



    GameView.java — Конструктор GameView
    players= BitmapFactory.decodeResource(getResources(), R.drawable.player2);
    player= new Player(this, guns);


    GameView,java
    /**Функция рисующая все спрайты и фон*/
    protected void onDraw(Canvas canvas) {
    canvas.drawColor(Color.WHITE);

    Iterator 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);
    }

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

    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. там в самом конце статьи есть исходный код, скачайте и посмотрите, тут вам объяснить я никак не смогу, сильно много вопросов.

      Удалить
    2. а можете дать ссылку на то как открыть исходники а то по тому что я нашёл в интернете он у меня открывается с ошибкой

      Удалить
    3. http://dl.dropbox.com/u/61086119/TowerDefense.rar

      Удалить
  16. мне не ссылку на исходники мне как их открыть в eclipse
    а то у меня hellow world вылазит и ничего не могу с этим поделать

    ОтветитьУдалить
    Ответы
    1. File->Import->General->Existing Projects into Workspace, Next
      Select root directory: /path/to/project
      Projects->Select All
      UNCHECK both "Copy projects into workspace" and "Add project to working sets"
      Finish

      Удалить
    2. спасибо разобрался
      но есть ещё три вопроса
      как поставить картинку на фон
      как добавить графический элемент счёта и запоминания рекордов
      и как добавить меню к приложению из исходников
      если сможете пожалуйста ответьте на nikfominyh@mail.ru

      Удалить
  17. спасибо большое вам без вас бы не разобрался и ещё а вы когда нибудь писали большие игры

    ОтветитьУдалить
  18. Глеб, здравствуйте! отличный урок! все понятно! попробовал по пунктам все прописать сам и в эмуляторе работает на ура, НО на устройстве устанавливается и при попытке запустить - вылетает ошибка. Я так подозреваю ошибка где-то в потоке - но хз где подскажите... ваш исходник работает - но различия кода исходника и то что написал не вижу. В чем может быть дело?

    ОтветитьУдалить
    Ответы
    1. Здравствуйте. Скорей всего из за различий версий андроида на устройстве и в эмуляторе, других идей у меня нет.

      Удалить
  19. Не могли бы вы описать как будет выглядеть функция отслеживания столкновений, если бы нужно было отслеживать столкновение Player and Enemy?

    ОтветитьУдалить
    Ответы
    1. Примерно вот так вот http://pastebin.com/pzrtppp7

      Удалить
    2. Спасибо за метод, но он не помог, столкновения не происходят =\

      Удалить
    3. Должно срабатывать так как я этот способ использовал при написании игры, там как раз все так же написано и работает на ура.

      Вам нужно столкновение с одним енеми или с множеством?

      Удалить
    4. Их множество, суть игры в том, что плеер должен ловить этих самых Enemy, но вот столкновения почему то не выходят.
      public void testCollision(){
      Iterator i = drop.iterator(); //Это допустим Enemy
      while(i.hasNext()){
      Drops drops = i.next();
      if(Math.abs(drops.x-player.x)<=(drops.width + player.width)/2f
      && Math.abs(drops.y - player.y) <= (drops.height +
      drops.height)/2f){
      i.remove();
      }
      }
      }
      Игра сама, что то наподобие "Волк и яйца".

      Удалить
  20. А откуда бралась высота и ширина спрайтов?
    Просто размеры самих изображений и размеры в программе (width/height) отличаются.

    ОтветитьУдалить
    Ответы
    1. Собственно сама высота и ширина картинки, размеры картинки всегда колеблятся от размера экрана, так что конечно же размеры всегда будут разные.

      Удалить
  21. Есть ещё один вопрос, о том где и как был объявлен "p - player"
    для этого метода http://pastebin.com/pzrtppp7

    ОтветитьУдалить
  22. Спасибо за ответы!
    А чем именно вам не подошёл метод intersect(Rect) и что работало не так?
    Просто по вашему методу у меня так и ничего не получилось, пытаюсь с помощью стандартных функций(intersect) реализовать столкновения, но здесь тоже "провал" .

    ОтветитьУдалить
    Ответы
    1. Эта функция у меня не работала, и не хотела проверять на столкновения по этому я решил написать свою) Такой вариант оказался намного проще.

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

      Удалить
    3. Увы нет времени на просмотры и отлаживание чужого кода, но я могу посоветовать создать тестовый проект в котором создайте один объект типа плеер, а во втором спауните врагов, собственно скачайте проект в конце статьи и посмотрите как реализовано в нем, может тогда поймете как оно работает. Сорри что не могу посмотреть код но на самом деле проблемно с временем.

      Удалить
    4. И на том большое спасибо, собственно по вашей статье пишу дипломный проект, и вы во многом помогли, огромное спасибо!

      Удалить
    5. Пожалуйста, рад помочь (:

      Удалить
  23. Не могли бы вы подсказать как осуществить вывод очков на экран и возможно как сохранять их?
    Ну или подкиньте какие то статьи по этому поводу.
    Был бы очень признателен!)

    ОтветитьУдалить
  24. А вот цикл в цикле - это разве правильное решение? Более оптимального нет? например если я хочу что бы снаряд при столкновении взрывался и задевал цели вокруг - надо делать еще один цикл проверки?

    ОтветитьУдалить
  25. Можно ли как то сделать, что бы например появлялось 3 разных вида духов?
    И как приблизительно это реализовать?

    ОтветитьУдалить