Показаны сообщения с ярлыком 2D-графика. Показать все сообщения
Показаны сообщения с ярлыком 2D-графика. Показать все сообщения

понедельник, 19 декабря 2011 г.

Пишем игру для Android с помощью AndEngine и Physics Box2D Extension

В жизни каждого программиста наступает время, когда ему надоедает писать унылые формы и обработчики и душа просит настоящего творчества.. Например, написать игру.
Давайте посмотрим, как это делается для нашей любимой платформы Android.
Всякая игра базируется на наборе логики, которая реализует поведение игровых объектов на экране, т.н. "графическом движке". В комплексе с этой логикй работает игровая физика, "звуковой" движок, и масса другого, очень непростого кода. Если у нас нет лишних полгода жизни или полмиллиона баксов, лучше не пытаться реализовать эту логику самому. Благо под Android создано уже несколько достаточно мощных "двигателей". Один из них мы сегодня и применим. Это AndEngine - свободно распространяемый 2D игровой движок, базирующийся на OpenGL.
Чтобы было интереснее и ближе к "жизни", мы используем в нашем приложении расширение Physics Box2D, которое позволит нам реализовать в игре гравитацию. Наша игра будет простой но вполне играбильной: мы будем складывать конструкции из деталей, которые будут появляться "из воздуха".
Итак, приступим.

пятница, 26 августа 2011 г.

Android: делаем холст для рисования

Допустим вы решили сделать маленький Paint для Android. Например, для развития художественных талантов у своего ребёнка :). Основой такого приложения будет холст: некая View, которая может "ощущать" прикосновения и отображать на себе путь пальца. Для реализации таких функций создаём класс, расширяющий View и переопределяем у него два метода: onTouchEvent (позволит перехватывать движения пальца) и onDraw (позволит рисовать).

  1. public class PaintView extends View {
  2.  
  3.   
  4.   private int linecolor, alpha, lwidth;
  5.   private int WIDTH;
  6.   private int HEIGHT;
  7.   MainActivity act;
  8.  
  9.   public PaintView(MainActivity act, int linecolor, int alpha, int lwidth, int width, int height) {
  10.     super(act);
  11.     this.act = act;
  12.     this.linecolor = linecolor;
  13.     this.alpha = alpha;
  14.     this.WIDTH = width;
  15.     this.HEIGHT = height;
  16.     this.lwidth = lwidth;
  17.   }
  18.   
  19.   @Override
  20.   public boolean onTouchEvent(MotionEvent e) {
  21.  
  22.     int action = e.getAction();
  23.  
  24.     if (action == MotionEvent.ACTION_UP) {
  25.       act.addPoint(null);
  26.  
  27.     } else if (action == MotionEvent.ACTION_MOVE || action == MotionEvent.ACTION_DOWN) {
  28.       Point point = new Point(Math.round(e.getX()), Math.round(e.getY()), linecolor, alpha, lwidth);
  29.       act.addPoint(point);
  30.       postInvalidate();
  31.     }
  32.  
  33.     return true;
  34.   }
  35.  
  36.   @Override
  37.   protected void onDraw(Canvas c) {
  38.     Point curr = null;
  39.     for (Point data : act.getPoints()) {
  40.       if (curr != null && data != null) {
  41.         Paint paint = new Paint();
  42.         paint.setColor(data.getColor());
  43.         paint.setAlpha(data.getAlpha());
  44.         if (data.getWidth()==1) {
  45.           c.drawLine(curr.getX(), curr.getY(), data.getX(), data.getY(), paint);
  46.         } else {
  47.           c.drawRect(curr.getX(), curr.getY(), data.getX()+data.getWidth(), data.getY()+data.getWidth(), paint);
  48.         }
  49.       }
  50.       curr = data;
  51.     }
  52.   }
  53.  
  54.   @Override
  55.   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  56.     super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  57.     this.setMeasuredDimension(WIDTH, HEIGHT);
  58.   }
  59. }
Рассмотрим подробнее метод onTouchEvent. Для событий прикосновения и перемещения мы получаем координаты точки на экране и создаём экземпляр Point, добавляя его в ArrayList с помощью метода addPoint(point) в MainActivity. Почему не хранить коллекцию точек прямо в классе? Так проще обрабатывать события, требующие перерисовки холста. Просто создаём новый холст, а данные для отрисовки он возьмёт в MainActivity.
Класс Point выглядит просто:
public class Point {
  private int x, y, color, alpha, width;

  public Point(int x, int y, int color, int alpha, int width) {
    this.x = x;
    this.y = y;
    this.color = color;
    this.alpha = alpha;
    this.width = width;
  }

  public int getX() {
    return x;
  }

  public int getY() {
    return y;
  }

  public int getColor() {
    return color;
  }

  public int getAlpha() {
    return alpha;
  }

  public int getWidth() {
    return width;
  }
}

...это обычное хранилище координат точки и её характеристик.
Вызываем наш холст из MainActivity следующим образом:
PaintView paint = new PaintView(this, initColor, alpha, linew, w, h);
Тут:
this - экземпляр MainActivity, из которого, собственно делаем вызов
initColor, alpha, linew - настройки цвета, прозрачности и толщины линии
w и h - ширина и высота доступной для рисования области в пикселях. Получаем её так:
    Resources res = getResources();
    int h = res.getDisplayMetrics().heightPixels;
    int w = res.getDisplayMetrics().widthPixels;

Созданный экземпляр холста может быть добавлен в нашу структуру View с помощью метода addView родительского контейнера а контейнер верхнего уровня устанавливаем как контент окна методом setContentView.
Останется только добавить в наше приложение панель с кнопками для выбора цвета, толщины линий и т.п. и простенькая "рисовалка" готова :).

среда, 6 июля 2011 г.

Строим диаграмму в Android-приложении

Допустим, вы решили сделать виджет, который будет отображать текущий курс валют. Вы идёте на любой открытый источник, берёте данные и выводите клиенту. Ничего сложного. Но и ничего особенного - таких виджетов миллион. Вы идёте дальше: принимаете решение хранить полученные данные за предыдущие дни и отображать клиенту информацию в виде диаграммы. Вот тут-то мы и сталкиваемся с реализацией 2D-графики в Android.
Чтобы создать объект изображения, нужно переопределить View. Назовём наш объект ChartView:

  1. public class ChartView extends View {
  2.   private int width = 10;
  3.   ArrayList<ChartItem> charts;
  4.   int max = 0;
  5.   ShapeDrawable background;
  6.   
  7.   public ChartView(Context context, ArrayList<ChartItem> charts, int top, int bgColor) {
  8.     super(context);
  9.     Resources res = getResources();
  10.     int ws = res.getDisplayMetrics().widthPixels;
  11.     
  12.     ArrayList<ChartItem> gencharts = new ArrayList<ChartItem>();
  13.     for (ChartItem ci : charts) {
  14.       int[] points = scaleTo(ci.getPoints(), top);
  15.       ArrayList<ShapeDrawable> myDraws = new ArrayList<ShapeDrawable>();
  16.       for (int i=0; i<points.length; i++) {
  17.         int h = points[i];
  18.         ShapeDrawable mDrawable = new ShapeDrawable(new RectShape());
  19.         mDrawable.getPaint().setColor(ci.getColor());
  20.         mDrawable.getPaint().setAlpha(ci.getAlpha());
  21.         mDrawable.setBounds(width*i, top-h, width*(i+1), top);
  22.         mDrawable.getPaint().setShader(new LinearGradient(width*i, top-h, width*(i+1), top-h, ci.getColor(), Color.WHITE, Shader.TileMode.REPEAT));
  23.         myDraws.add(mDrawable);
  24.       }
  25.       ci.setDraws(myDraws);
  26.       gencharts.add(ci);
  27.     }
  28.     this.charts = gencharts;
  29.     background = new ShapeDrawable(new RectShape());
  30.     background.setBounds(0, 0, ws, top);
  31.     background.getPaint().setColor(bgColor);
  32.   }
  33.  
  34.   @Override
  35.   protected void onDraw(Canvas canvas) {
  36.     background.draw(canvas);
  37.     for (ChartItem ci : charts) {
  38.       ArrayList<ShapeDrawable> draws = ci.getDraws();
  39.       for (int i=0; i<draws.size(); i++) {
  40.         ShapeDrawable d = draws.get(i);
  41.         d.draw(canvas);
  42.       }
  43.     }
  44.   }
  45.   
  46.   private int getMax(int[] ii) {
  47.     for (int i : ii) {
  48.       if (max<i) max = i;
  49.     }
  50.     return max;
  51.   }
  52.   
  53.   private int[] scaleTo(int[] ii, int vmax) {
  54.     int[] scaled = new int[ii.length];
  55.     double k = (double) vmax / getMax(ii);
  56.     for(int i=0; i<ii.length; i++) {
  57.       scaled[i] = (int) (ii[i]*k);
  58.     }
  59.     return scaled;
  60.   }
  61. }
Рассмотрим наш класс подробнее.
В конструктор обязательно нужно передавать Context - его мы сразу же отдадим в конструктор предка. Кроме этого в конструктор мы передаём массив объектов ChartItem, которые представляют данные для каждой из диаграмм, которые мы собираемся построить на нашем изображении. Следующие два параметра общие для всех диаграмм на изображении: максимальная высота и фон. 
В строках 9-10 получаем текущую ширину экрана, чтобы полностью закрасить её фоном. 
Все графические примитивы создаются как объекты ShapeDrawable. В конструкторе мы устанавливаем параметры для прямоугольников, составляющих столбцы диаграмм: ширину, высоту, цвет. Делаем их симпатичнее с помощью градиента (строка 22). Все созданные объекты должны быть нарисованы на канве, для чего переопределяем метод protected void onDraw(Canvas canvas) и в нём для всех ShapeDrawable вызываем метод draw(canvas) (строки 35-44). Делать это нужно в правильном порядке: следующий объект будет рисоваться поверх предыдущего. 
Определённая сложность есть в расчёте высоты столбцов диаграммы: нужно чтобы они поместились в максимальную высоту и при этом сохранили пропорции. Это решаем с помощью метода  private int[] scaleTo(int[] ii, int vmax) в который передаём массив данных для конкретной диаграммы и максимальную высоту. На выходе получаем уже масштабированный массив (строки 53-60). 
Добавляя в setContentView вашего Activity созданный экземпляр СhartView, получаете результат вроде того, что на картинке. В данном случае выбран серый фон и две диаграммы с прозрачностью 50% синего и зелёного цвета.