Пишем движок игры под Android – tutorial часть 4 (слои и сцена)

Привет! Продолжаю писать наш игровой движок для Android.

Вот здесь мы вроде как разобрались со спрайтами.

Однако если у нас что-то сложнее чем два спрайта, то просто так их расставлять не очень удобно. Поэтому давайте введем такое понятие как сцена. Сцена в нашем понимании — это ни что иное, как набор всех объектов на игровом экране. При этом на сцене может быть несколько слоев. Ну например представьте, что вы пишите скроллер. Тогда логично было бы разместить фон на одном слое, вас с вашими противниками на другом, а сверху, например добавить движущиеся полупрозрачные облака. Значит получится что-то вроде слоев в фотошопе. Этого функционала нам, как начинающим игрописателям пока что думаю будет достаточно.

Итак, начнем со слоев. Создаем новый класс mLayer и попробуем разобраться как он у нас будет работать.

package ru.davidmd.myengine;

import java.util.LinkedList;

import android.graphics.Paint;

/**
 * @author davidmd
 * @version 0.1 Слой на который можно добавлять объекты {@link mBasic}
 *          Используется в классе сцены {@link mScene}
 */
public class mLayer {
	LinkedList<mBasic> data = new LinkedList<mBasic>();

	/**
	 * номер слоя в стопке слоев на сцене
	 */
	int level;

	/**
	 * Кисть, которой должен отрисовываться слой
	 */
	public Paint p;

	private boolean processing=false;

	/**
	 * Конструктор принимает в качестве параметра номер слоя
	 *
	 * @param lev
	 *            номер слоя на сцене
	 */
	public mLayer(int lev) {
		processing = true;
		level = lev;
		p = new Paint();
		processing = false;
	}

	/**
	 * добавление на слой объектов
	 *
	 * @param item
	 *            - графический объект
	 */
	public void add(mBasic item) {
		processing = true;
		data.add(item);
		processing = false;
	}

	/**
	 * @return возвращает количество объектов на слое
	 */
	public int getSize() {
		return data.size();
	}

	/**
	 * Возвращает объект <b>mBasic</b> c номером <b>i</b>
	 *
	 * @param i
	 *            номер объекта
	 * @return объект со слоя с номером <b>i</b>
	 */
	public mBasic get(int i) {
		return data.get(i);
	}

	/**
	 * Удаляет объект с номером i со слоя
	 *
	 * @param i
	 */
	public void delete(int i) {
		processing = true;
		data.remove(i);
		processing = false;
	}

	/**
	 * Очищает текущий солой
	 */
	public void clear() {
		processing = true;
		data.clear();
		processing = false;
	}

	/**
	 * обновляет все объекты на слое
	 */
	public void update() {
		processing = true;
		for (mBasic a : data) {
			if (a != null)
				a.update();
		}
		processing = false;
	}

	/**
	 * Изменение всех спрайтов на сцене
	 * @param newx - множитель ширины
	 * @param newy - множитель высоты
	 */
	public void resize(float newx, float newy)
	{
		processing = true;
		for (mBasic a: data){
			if (a!=null&&a.type==mBasic.TYPE_SIMPLESPRITE)
			{
				((mSimpleSprite)a).resize(newx, newy);
			}
		}
		processing = false;
	}

	/**
	 * выбирает первый объект на слое, в который попадает точка с кооординатами
	 * (f,g)
	 *
	 * @param f
	 *            координата по x
	 * @param g
	 *            координата по y
	 * @return объект со слоя
	 */
	public mBasic select(float f, float g) {
		mBasic tmp = null;
		for (int i = 0; i < data.size(); i++) {
			if (data.get(i) != null && data.get(i).isSelected(f, g)) {
				tmp = data.get(i);
				break;
			}
		}
		return tmp;
	}

	public boolean isProcessing() {
		return processing;
	}
}

Класс построен с использованием стандартного LinkedList. То есть связного списка. Вообще-то говорят, что работа с объектами подобного рода (LinkedList, ArrayList, Vector) значительно замедляет скорость выполнения кода на любой Java машине и Android здесь не исключение. Однако поскольку динамических массивов похожих например на паскалевские в Java как-то совсем нет, работа с указателями напрямую здесь тоже оказывается табу (меня это в свое время сильно удивило), то другого выхода я не вижу. Разве что попробовать реализовать свой собственный динамический массив куцыми средствами, которые дает нам Java. Но поскольку такой код вряд ли будет работать быстро, было решено использовать именно этот класс, несмотря на то что он не является потоко-безопасным (т.е. если мы одновременно попытаемся выполнить какие-то действия с этим объектом из разных потоков никто не знает к чему это может привести 🙂 Поэтому не стоит забывать об этом при реализации).

Итак, что у нас тут имеется? А имеется, собственно, следующее: сам связаный список data элементами которого являются объекты экземпляры mBasic (а значит и всех унаследованных от него классов). Затем идет переменная layer — это просто номер слоя на сцене. И для большей гибкости у каждого слоя есть кисть p, которой этот слой впоследствии будет отрисовываться. Также на всякий случай есть логическая переменная processing — которая есть ни что иное, как флаг, говорящий о том, что в список data вносятся какие-то изменения (незабываем про его потоко-небезопасность). За состоянием этого флага следить будем самостоятельно. То есть в каждом методе где мы что-то добавляем в список или удаляем из него, первой строкой в этот флаг записывается истина, а последней строкой — ложь. Ну например в конструкторе public mLayer(int lev) именно так и происходит. Помимо этого реализованы методы для добавления объекта на сцену, удаления объекта со сцены, получения количества объектов на сцене и очистки всей сцены.

Метод update() вызывает одноименный метод для всех объектов на сцене, а метод resize() изменяет размер всех спрайтов на ней (кстати не плохо бы сделать чтоб изменялись размеры всех объектов в принципе). Ну и последнее, что у нас здесь есть важного — это метод select(float x, float y) — который возвращает первый объект в который попадает точка с координатами (x,y).

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

package ru.davidmd.myengine;

/**
 * @author davidmd
 * @version 0.1 Сцена Набор слоев на каждый из которых можно добавлять
 *          графические примитивы При этом имеет активный слой с которым счас
 *          ведется работа и пассивные на которые можно переключиться
 */
public class mScene {

	/**
	 * Массив слоев на сцене
	 */
	mLayer[] layers;

	/**
	 * номер текущего слоя
	 */
	private int curLay;

	public static final int ORIENTATION_VERT=0;
	public static final int ORIENTATION_HOR=1;

	private int orient;
	/**
	 * Количество слоев на сцене
	 */
	private int layCount;

	/**
	 * Верхний слой на сцене
	 */
	public int TopLay;

	/**
	 * нижний слой на сцене
	 */
	public int BotLay = 0;

	/**
	 * высота и ширина сцены
	 */
	public int width, height;

	/**
	 * Конструктор Создание новой сцены
	 *
	 * @param width
	 *            - ширина
	 * @param height
	 *            - высота
	 * @param layerCount
	 *            - количество слоев
	 */
	public mScene(int width, int height, int layerCount) {
		this.width = width;
		this.height = height;
		if (this.width>this.height){orient=mScene.ORIENTATION_HOR;}
		else
		{orient = mScene.ORIENTATION_VERT;}
		layers = new mLayer[layerCount];
		curLay = 0;
		TopLay = layerCount - 1;
		for (int i = BotLay; i < layerCount; i++) {
			layers[i] = new mLayer(i);
		}
		layCount = layerCount;
	}

	/**
	 * Установить новую ширину и высоту сцены
	 *
	 * @param w
	 *            - новая ширина
	 * @param h
	 *            - новая высота
	 */
	public void setWH(int w, int h) {
		this.width = w;
		this.height = h;
	}

	/**
	 * Установить текущий слой
	 *
	 * @param i
	 */
	public void setCurLay(int i) {
		if (i <= TopLay)
			curLay = i;
		else
			curLay = TopLay;
	}

	/**
	 * Добавить объект на текущий слой сцены
	 *
	 * @param item
	 */
	public void addItem(mBasic item) {
		layers[curLay].add(item);
	}

	/**
	 * @return Возвращает номер текущего слоя
	 */
	public int getCurlayNum() {
		return this.curLay;
	}

	/**
	 * @return возвращает текущий слой
	 */
	public mLayer getCurLay() {
		return layers[this.curLay];
	}

	/**
	 * Очищает текущий слой
	 */
	public void clear() {
		this.layers[this.curLay].clear();
	}

	/**
	 * @return возвращает количество слоев на сцене
	 */
	public int getLayCount() {
		return layCount;
	}

	/**
	 * удаляет с текущего слоя объект с номером i
	 *
	 * @param i
	 */
	public void delete(int i) {
		this.layers[this.curLay].delete(i);
	}

	/**
	 * пзменение размера всех спрайтов на сцене
	 * @param newx - множитель по ширине
	 * @param newy - множитель по высоте
	 */
	public void resize(float newx, float newy)
	{
		for (mLayer a: layers)
		{
			a.resize(newx, newy);
		}
	}

	public mLayer getLayerByNum(int num)
	{
		if (num<this.layCount)
		{
			return layers[num];
		}
		return null;
	}

	/**
	 * @return Возвращает ориентацию экрана
	 * 1 - если горизонтальная
	 * 0 - если вертикальная
	 * ориетация расчитывается исходя из того
	 * что больше высота или ширина сцены
	 */
	public int getOrient() {
		return orient;
	}

	/**
	 * @return возвращает ширину сцены в пиксеах
	 */
	public int getWidth() {
		return width;
	}

	/**
	 * @return Возвращает высоту сцены в пикселах
	 */
	public int getHeight() {
		return height;
	}

	/**
	 * Выбирает обхъект c текущего слоя сцены который содержит точку с
	 * координатами f и g
	 *
	 * @see ru.davidmd.myengine.mLayer
	 * @param f
	 *            - координата по
	 * @param g
	 *            - координата по
	 * @return возвращает объект или null если подходящего объекта на слое не
	 *         нашлось
	 */
	public mBasic selectSprite(float f, float g) {
		mBasic sel = null;
		sel = this.layers[curLay].select(f, g);
		return sel;
	}

	/**
	 * Обновляет все соержимое сцены
	 */
	public void update() {
		for (mLayer l : layers) {
			l.update();
		}
	}
}

Как уже можно было догадаться количество слоев на сцене задается во время ее создания. Также во время создания задается ширина и высота сцены в пикселах это width и height. Как их использовать я пока не понял, но пусть на всякий случай будут :-). Сами слои — это массив layers. переменная curLay — это текущий слой, а orient — ориентация экрана (тоже задел на будущее). Переменные TopLay и BotLay — просто номера самого верхнего и самого нижнего слоя.

Теперь посмотрим на методы.  Ну во-первых метод добавления элемента на сцену public void addItem(mBasic item) — занимается тем, что добавляет элементы на текущий слой. Метод setCurLay(int i) устанавливает текущий слой. getCurlayNum() — возвращает номер текущего слоя а getCurLay() — сам этот слой. Также  присутствуют и другие методы.

Думаю код достаточно прозрачен и разобраться что к чему труда не составит 🙂

Если появились какие-то вопросы,  пишите в комментах. Код классов можно скачать тут myengine. Всем пока! Пошел я спать 🙂

Вам понравилось? Было полезно? Поделитесь!

Опубликовать в Facebook
Опубликовать в Google Buzz
Опубликовать в Google Plus
Опубликовать в LiveJournal
Опубликовать в Мой Мир
Опубликовать в Одноклассники
Опубликовать в Яндекс
Запись опубликована в рубрике Android, Программирование с метками , , , , , , , . Добавьте в закладки постоянную ссылку.

15 комментариев на «Пишем движок игры под Android – tutorial часть 4 (слои и сцена)»

  1. Вадим говорит:

    Здравствуйте. А что будет, если я вместо переменной processing сделаю методы synchronized, как рассказывают вот тут. Сработает ли такой фокус?

    • davidmd говорит:

      Конечно сработает. Правда если все правильно настроить, то проблем не должно быть даже если убрать переменную processing 🙂 Я когда это писал еще не знал про то как правильно синхронизировать потоки 🙂

  2. Сапфил говорит:

    Вот! Вот тут комментов по слою предостаточно. Все по полочкам. Спасибо.

    Но я все равно же не смогу без своего имхо. Собственно вот:
    Переименовал переменную level в layerNumber. В свое время я использовал понятие level как почти что самый верхний уровень программы-игры. Он включал в себя и обработку графики и физики и звук. Выше был только главный mainGameManager.
    Опять же как и в первом туторе — требую enum, стуча туфлей по столу :D. Было бы логично не просто пронумеровать слои, а осмысленно их назвать. стандартно в аркаде, например, есть 4 слоя — задник, который не двигается. Тупо некоторая картинка во весь экран. Задник, который двигается при перемещении игрока по уровню. Третий слой — активный — собственно в нем и происходит игра, а в остальных слоях — просто красоты. Ну и верхний слой с облачками или еще чем-то таким, что рисуется поверх всего. Например это крона какого то дерева. А под ней в активном слое есть монетка, но мы ее не видим из-за кроны дерева. Задумка типа такая у нас.

  3. Сапфил говорит:

    В тексте поправьте вот эту фразу:
    Ну во-первых метод добавления элемента на сцену public void setCurLay(int i) — занимается тем, что добавляет элементы на текущий слой..
    Там конечно же должен стоять метод public void addItem(mBasic item)

    Еще. Может быть в стандартных средствах есть какой-то способ узнать текущую ориентацию? )

  4. Игорь говорит:

    Извините за может быть простой вопрос, но можно ли добавить на сцену объект типа ImageButton?
    И если можно, то не могли бы вы в кратце объяснить как это реализовать?

  5. Вячеслав говорит:

    «Вообще-то говорят, что работа с объектами подобного рода (LinkedList, ArrayList, Vector) значительно замедляет скорость выполнения кода на любой Java машине и Android здесь не исключение. Однако поскольку динамических массивов похожих например на паскалевские в Java как-то совсем нет, работа с указателями напрямую здесь тоже оказывается табу (меня это в свое время сильно удивило), то другого выхода я не вижу. Разве что попробовать реализовать свой собственный динамический массив куцыми средствами, которые дает нам Java. Но поскольку такой код вряд ли будет работать быстро, было решено использовать именно этот класс»
    Вообще-то LinkedList это реализация связанного списка, что понятно уже по его названию. Произвольный доступ (по номеру элемента) в таком списке хоть и возможен, но происходит медленно, перебором до нужного элемента. В вашем случае лучше было бы использовать ArrayList — это как раз и есть динамичный массив (при необходимости его размер увеличивается, так-же можно задать начальный размер, но не обязательно) Произвольный доступ к элементам в нём происходит намного быстрее связного списка.

    • davidmd говорит:

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

  6. Серг говорит:

    а где картинки?и документация с примером создания игры на движке?

  7. Анатолий говорит:

    Скажиье где 5й урок??

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *