Я прочитал много разных туториалов по разработке игр под эту платформу, читал и создание на основе движков и с нуля но толком разобрать некоторые детали так и не смог. Сегодня я хочу Вам рассказать о примитивах использования класса SurfaceView.
Статьи по теме:
И так, задача:
Для начала просто нарисуем ic_launcher.png на нашем Surface.
Для этого — создаем проект — Eclipse — New — Android project — MyFirstGame — Main.java.
Заходим в res/layout/main.xml и пишем вот такое:
Вместо package пишите тот пак который у вас в проекте.
Открываем создавшийся файл Main.java и вносим в него одну строку, между super.onCreate() и setContentView().
Вот как оно будет выглядеть полностью:
Main.java
Дальше нам нужно создать класс GameView.java который будет производить отрисовку нашей графики на сцене и унаследуем его от класса SurfaceView, для того что бы производить
GameView.java
Для отрисовки мы вызываем метод OnDraw() который реализуется непосредственно программистом в коде игры. Что бы представить как работает Canvas — можете представить его как доску на которой можно рисовать то, что Вам вздумается. Для того что бы рисовать на сцене (Canvas) — получаем команду рисовать, при помощи функции OnDraw().
Рисование будет происходить в отдельном потоке для того что бы при отрисовке в нужный момент мы могли получить картинку для этого мы блокируем Canvas, а для продолжения рисования разблокируем его.
Мы должны принять во внимание, что во время отрисовки у нас есть ресурс который заблокирован и мы теряем производительность и очень важно, свести к минимуму время блокировки. Пока что у Вас должно получиться только вот такое:
Добавляем анимацию
Теперь можно добавить немного анимации, пусть наша картинка движется в каком-то направлении, я хочу что бы она медленно передвигалась вправо. Вот сейчас мы это и сделаем.
Игра это цикл повторений. Основные направления деятельности:
1. Физика обновления, это обновление данных игры, как, например х и у координаты позиции для маленьких символов.
2. Рисование, это отрисовка картинки, которую вы видите на экране. При вызове этого метода оно дает вам восприятие анимации.
Мы собираемся выполнить цикл игры в отдельном потоке. В одном потоке мы создаем обновления и рисование, а в основном потоке мы обрабатываем события. Для этого создаем еще один файл GameManager.java и вставляем в него следующий код:
GameManager.java
Работает поле флаг, позволяющий остановить цикл игры. Внутри цикла мы вызываем метод OnDraw() о котором мы узнали в прошлом уроке. В этом случае для простоты мы делаем обновление и рисование в OnDraw() методе. Мы используем синхронизации, чтобы избежать других потоков, что бы потоки не конфликтовали.
В SurfaceView мы просто добавим интовое поле int х, координата по которой будет двигаться наша картинка Кроме того, в методе OnDraw() мы увеличиваем х на 1, если он не достиг правой границы, конечно же мы это будет делать на нашей сцене, а значит в файле GameView.java. Вот какой вид будет иметь GameView.java
GameView.java
Мы ограничили отрисовку до 10 кадров в секунду, что составляет 100 мс (миллисекунды). Мы используем метод sleep() за оставшееся время, чтобы получить 100 мс. Если цикл занимает больше, чем 100 мс мы спим 10 мс в любом случае, наше приложение будет требовать слишком много памяти процессора. Немного усложним код, в GameManager.java заменяем старый код на этот:
GameManager.java
В итоге должно получиться то же самое изображение только оно движется в правую сторону.
Исходные коды
Статьи по теме:
- Пишем игру под Android: Часть 1 — Рисуем картинки на SurfaceView
- Пишем игру под Android: Часть 2 — Создаем первый спрайт
- Пишем игру под Android: Часть 3 — Спрайтовая анимация, работа с несколькими спрайтами
- Пишем игру под Android: Часть 4 — onTouchEvent и определение столкновений
- Пишем игру под Android: Часть 5 — Создание полноценной 2D игры
- Пишем игру под Android: Часть 6: Добавление звука
- Пишем игру под Android: Часть 7: Меню для игры и окно приветствия
- Пишем игру под Android: Часть 8: Фоновая музыка в игре
И так, задача:
Для начала просто нарисуем ic_launcher.png на нашем Surface.
Для этого — создаем проект — Eclipse — New — Android project — MyFirstGame — Main.java.
Заходим в res/layout/main.xml и пишем вот такое:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<package.GameView
android:id="@+id/game"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
Вместо package пишите тот пак который у вас в проекте.
Открываем создавшийся файл Main.java и вносим в него одну строку, между super.onCreate() и setContentView().
//эта строка позволяет спрятать верхнее меню с экрана телефона
requestWindowFeature(Window.FEATURE_NO_TITLE);
Вот как оно будет выглядеть полностью:
Main.java
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
public class Main extends Activity {
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(new GameView(this));
}
}
Дальше нам нужно создать класс GameView.java который будет производить отрисовку нашей графики на сцене и унаследуем его от класса SurfaceView, для того что бы производить
GameView.java
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.view.View;
public class GameView extends SurfaceView {
/**Загружаемая картинка*/
private Bitmap bmp;
/**Наше поле рисования*/
private SurfaceHolder holder;
//конструктор
public GameView(Context context)
{
super(context);
holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback()
{
public void surfaceDestroyed(SurfaceHolder holder)
{
}
@Override
public void surfaceCreated(SurfaceHolder holder)
{
Canvas c = holder.lockCanvas(null);
onDraw(c);
holder.unlockCanvasAndPost(c);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
}
});
bmp = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
}
//Рисуем нашу картинку на черном фоне
protected void onDraw(Canvas canvas)
{
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(bmp, 10, 10, null);
}
}
Для отрисовки мы вызываем метод OnDraw() который реализуется непосредственно программистом в коде игры. Что бы представить как работает Canvas — можете представить его как доску на которой можно рисовать то, что Вам вздумается. Для того что бы рисовать на сцене (Canvas) — получаем команду рисовать, при помощи функции OnDraw().
Рисование будет происходить в отдельном потоке для того что бы при отрисовке в нужный момент мы могли получить картинку для этого мы блокируем Canvas, а для продолжения рисования разблокируем его.
Мы должны принять во внимание, что во время отрисовки у нас есть ресурс который заблокирован и мы теряем производительность и очень важно, свести к минимуму время блокировки. Пока что у Вас должно получиться только вот такое:
Добавляем анимацию
Теперь можно добавить немного анимации, пусть наша картинка движется в каком-то направлении, я хочу что бы она медленно передвигалась вправо. Вот сейчас мы это и сделаем.
Игра это цикл повторений. Основные направления деятельности:
1. Физика обновления, это обновление данных игры, как, например х и у координаты позиции для маленьких символов.
2. Рисование, это отрисовка картинки, которую вы видите на экране. При вызове этого метода оно дает вам восприятие анимации.
Мы собираемся выполнить цикл игры в отдельном потоке. В одном потоке мы создаем обновления и рисование, а в основном потоке мы обрабатываем события. Для этого создаем еще один файл GameManager.java и вставляем в него следующий код:
GameManager.java
import android.graphics.Canvas;
public class GameManager extends Thread {
/**Объект класса*/
private GameView view;
/**Переменная задавания состояния потока рисования*/
private boolean running = false;
/**Конструктор класса*/
public GameManager(GameView view)
{
this.view = view;
}
/**Задание состояния потока*/
public void setRunning(boolean run)
{
running = run;
}
/** Действия, выполняемые в потоке */
public void run() {
while (running) {
Canvas c = null;
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.onDraw(c);
}
} finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
}
}
}
Работает поле флаг, позволяющий остановить цикл игры. Внутри цикла мы вызываем метод OnDraw() о котором мы узнали в прошлом уроке. В этом случае для простоты мы делаем обновление и рисование в OnDraw() методе. Мы используем синхронизации, чтобы избежать других потоков, что бы потоки не конфликтовали.
В SurfaceView мы просто добавим интовое поле int х, координата по которой будет двигаться наша картинка Кроме того, в методе OnDraw() мы увеличиваем х на 1, если он не достиг правой границы, конечно же мы это будет делать на нашей сцене, а значит в файле GameView.java. Вот какой вид будет иметь GameView.java
GameView.java
public class GameView extends SurfaceView {
/**Загружаемая картинка*/
private Bitmap bmp;
/**Наше поле рисования*/
private SurfaceHolder holder;
/**Объект класса GameManager*/
private GameManager gameLoopThread;
/** Координата движения по Х=0*/
private int x = 0;
/**Скорость изображения = 1*/
private int xSpeed = 1;
public GameView(Context context)
{
super(context);
gameLoopThread = new GameManager(this);
holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback()
{
/** Уничтожение области рисования */
public void surfaceDestroyed(SurfaceHolder holder)
{
boolean retry = true;
gameLoopThread.setRunning(false);
while (retry) {
try {
gameLoopThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
/** Создание области рисования */
public void surfaceCreated(SurfaceHolder holder)
{
gameLoopThread.setRunning(true);
gameLoopThread.start();
}
/** Изменение области рисования */
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
}
});
bmp = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
}
@Override
protected void onDraw(Canvas canvas)
{
if (x == getWidth() - bmp.getWidth())
{
xSpeed = -1;
}
if (x == 0)
{
xSpeed = 1;
}
x = x + xSpeed;
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(bmp, x , 10, null);
}
}
Мы ограничили отрисовку до 10 кадров в секунду, что составляет 100 мс (миллисекунды). Мы используем метод sleep() за оставшееся время, чтобы получить 100 мс. Если цикл занимает больше, чем 100 мс мы спим 10 мс в любом случае, наше приложение будет требовать слишком много памяти процессора. Немного усложним код, в GameManager.java заменяем старый код на этот:
GameManager.java
public class GameManager extends Thread
{
/**Наша скорость в мс = 10*/
static final long FPS = 10;
/**Объект класса GameView*/
private GameView view;
/**Задаем состояние потока*/
private boolean running = false;
/**Конструктор класса*/
public GameManager(GameView view)
{
this.view = view;
}
/**Задание состояния потока*/
public void setRunning(boolean run)
{
running = run;
}
/** Действия, выполняемые в потоке */
@Override
public void run()
{
long ticksPS = 1000 / FPS;
long startTime;
long sleepTime;
while (running) {
Canvas c = null;
startTime = System.currentTimeMillis();
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.onDraw(c);
}
} finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
sleepTime = ticksPS-(System.currentTimeMillis() - startTime);
try {
if (sleepTime > 0)
sleep(sleepTime);
else
sleep(10);
} catch (Exception e) {}
}
}
}
В итоге должно получиться то же самое изображение только оно движется в правую сторону.
Исходные коды
можно исходники?
ОтветитьУдалитьДобавил, ниже картинки
ОтветитьУдалитьВ последнем GameView.java gameLoopThread = new GameView(this); конструктор не тот должен от GameManager быть. А за статью спасибо )
ОтветитьУдалитьисправил, спасибо :)
УдалитьПомогите пожалуйста! Сделал вроде все как у Вас. Пока картинка была неподвижна она выводилась, а после того, как переделал дальше стал выводиться только черный экран.
ОтветитьУдалитьВозможно удалил отрисовку в onDraw() или не прописал её в run() методе
Удалить"Вместо package пишите тот пак который у вас в проекте." я не знаю что такое "пак" - поэтому скачал исходники, а там все по другому вообще в этом файле.
ОтветитьУдалить"Дальше нам нужно создать класс GameView.java который будет производить отрисовку нашей графики на сцене и унаследуем его от класса SurfaceView, для того что бы производить "
как его создать что то я не понял, в папке layouts?
пока завис на этом))
Создаешь файл GameView.java и он наследует класс SurfaceView. В исходниках все ж понятно вро де бы расписывал.
Удалитьскачал исходники, вставил себе. эклипс ругается что надо убрать @Override, когда убираю то запускается но просто черный экран. в чем дело?
ОтветитьУдалитьЧтобы Эклипс не ругался на @Override зайдите в настройки проекта (Project->Properties) в левой панели открывшегося окна выберите Java Compiler , справа выберите уровень компилятора (Compiler compliance level) 1.6 или выше.
Удалитьперенесите все действовать с холдером в другой конструктор, у менять используется конструктор с двуия параметрами, а конструктор их примера не вызввается вообще.
ОтветитьУдалитьВ начале статьи автор предлагает создать разметку в xml ресурсах, но далее он её не использует, а создает экран непосредственно из объекта класса GameView (setContentView(new GameView(this));)
Удалитьпри этом действительно будет вызываться конструктор с одним параметром принимающим только контекст (в данном случае указатель this).
Но если ваш GameView создается действительно из xml ресурса (setContentView(R.layout.main)), то обязательно будет вызываться конструктор с двумя параметрами, во втором параметре передаются атрибуты заданные в xml разметке. А если в разметке используются стиль, то будет вызван конструктор с тремя параметрами (последним будет стиль).
Поэтому проверьте свой setContentView.
Спасибо за статью, получилось с первого раза! =)
ОтветитьУдалитьОчень много лишнего для столь простых результатов, а не существует более простых сред разработак под андроид ?
ОтветитьУдалитьЕсть IDEA и NetBeans
УдалитьДля джавы вложенные функции - это нормальная практика?
ОтветитьУдалить"автор" (если его можно так сказать) просто взял нормально описывающий весь процесс текст (http://megadarja.blogspot.com/2009/03/android-1-surface.html), переписал его, испоганил и запутал и выложил под своим авторством. Молодей, нуб!
ОтветитьУдалитьКод писал я свой, то что написано у дарьи совсем другое. Если посмотреть внимательно то похожи только названия файлов, но это во всех туториалах так, сперва разберитесь в себе уважаемый, а потом говорите за других.
УдалитьТочно, толком по коду ничего не описывается, что где. Плагиат, переменные только разные.
УдалитьДа даже если автор и заимствовал, в чем я сомневаюсь, то ничего плохого не вижу, загуглил инфу, выпал этот блог. Какие проблемы?
УдалитьНе утруждайтесь) Код я писал свой, то что он похож на код Дарьи не значит что я скопипастил его себе, попробуйте написать по другому и тогда я скажу что вы написали его сами.
УдалитьЯ сделал чуть сложнее: на MainActivity кнопка, которая запускает GameActivity. Может, проблема из-за этого? Не знаю...
ОтветитьУдалитьВсе работает на эмуляторе, но при нажатии кнопки "Назад" (иногда) или "Home" (всегда), программа завершается аварийно:
09-22 18:51:16.586: E/AndroidRuntime(948): FATAL EXCEPTION: Thread-99
09-22 18:51:16.586: E/AndroidRuntime(948): java.lang.NullPointerException
09-22 18:51:16.586: E/AndroidRuntime(948): at com.var.bubbles.drawing.GameView.onDraw(GameView.java:71)
09-22 18:51:16.586: E/AndroidRuntime(948): at com.var.bubbles.drawing.GameManager.run(GameManager.java:38)
Проясните, плз., где происходит обработка этих событий.
Как сделать,так чтобы картинка передвигалась верх и вниз?
ОтветитьУдалитьВ следующем уроке посмотрите, там нужно вместо нуля в drawBitmap поставить координату y.
УдалитьСложновато конечно сразу для понимания, тк не все объяснено(код комментирован блоками). То есть статья рассчитана не на новичка, а на человека знающего этот метод и работавшего с ним. Но я все равно рад этой статье, самая понятная среди тех, которые мне попадались. Месяц назад я ее не осилил, купил книгу по андроид сдк, но не полную. Методов отрисовки там не было. Спасибо за статью, очень полезная:)
ОтветитьУдалитьЧестно скажу что с андроидом работаю давно, и не могу определить какая сложность описанного, но стараюсь писать как можно понятней для человека который хотя бы имеет понятие как писать код и имеет общие понятия про разработку под андроид.
Удалить>Вместо package пишите тот пак который у вас в проекте.
ОтветитьУдалитьНе очень понял что за package и где его найти.
Заранее спасибо.
Не могли бы вы посоветовать литературу по Android в частности в области графики?
ОтветитьУдалитьhttp://www.amazon.com/dp/1430230428/?tag=stackoverfl08-20
УдалитьОт создателей proAndroid. ОЧень хорошая книга. Где скачать не знаю, так что гуглите)
Спасибо большое, давно за статьями вашими слежу. Думаю нагуглю :)
УдалитьКак я полагаю имеет значение в каком порядке в методе onDraw мы рисуем объекты (при наложение одного на другой объект который нарисован ранее будет перекрываться объектом который рисуется после него).
ОтветитьУдалитьНо вот у меня возникла проблема, когда канвасов становится больше чем 5-6 при добавлении нового, некоторые из старых канвасов просто не отрисовываются =\
В данном уроке XML файл вообще не нужен. Может автор объяснит, с какой целью он тут и как он используется???
ОтветитьУдалитьАналогично и в "исходниках"
Указанный XML отвечает за разметку окна игры.
УдалитьP.S. Как аноним анониму - додумались, что в коде примера XML не задействован, так включите голову погуглить, зачем он нужен.
Я начинающий. Мне интересно как можно из меню изменить например переменные X и Y в GameView? Я только изменил с "private int x" на "public int x", а дальше пока не знаю как получить к нему доступ.
ОтветитьУдалитьВам нужно менять не тип переменной, а сам функционал в коде, и тогда будет результат
Удалитьу меня вообще не запускаеться. все как ошибку выделяет
ОтветитьУдалитьНе открываются исходники в 2019( Что делать?
ОтветитьУдалитьОстается вариант только повторить все что было написанно самому. У меня к сожалению исходников не осталось что бы из перезалить, это было очень давно...
УдалитьСделал всё как написано, но выдаёт ошибку, что у меня нет файла с изображением, хотя он есть. Что делать то?
ОтветитьУдалитьTakipçi mi satın almak istiyorsunuz hemen tıklayın ve fiyatlara göz atın: takipçi satın al
ОтветитьУдалитьAlternatif: takipçi satın al
En iyi instagram takipçi satın al sitesi https://atakanmedya.com
ОтветитьУдалить