Привет! В этом сообщении я освещу некоторые вопросы связанные с загрузкой и освобождением ресурсов игры и на итог мы напишем шаблон класса который будет организовывать работу с этими ресурсами.
Ресурсами игры считается вся используемая графика, музыка, звуки и шрифты.
Многие из вас, только начиная работать с AndEngine, действовали по шаблону взятому из примеров: создавали активити, прописывали в ней при необходимости поля текстур, регионов и спрайтов; заполняли поля в onCreateResources(); собирали сцену в onCreateScene() и радовались жизни =) Проблемы начинались тогда, когда объект игры представлял собой не просто спрайт, а комплексный объект использующий свои дополнительные свойства и методы, содержащий более одного спрайта и т.д. С подходом описанным выше, приходиться либо объявлять поля активити как protected/public или передавать текстуры через конструктор объекта. Как результат:
- грязный код,
- активити заполнено полями которые этой активти не используются,
- множество параметров в конструкторах объектов,
- сложности в отслеживании неиспользуемых ресурсов.
Нам было бы гораздо удобнее иметь некий менеджер, который бы давал нам необходимые текстуры и вызывался тогда когда он нам нужен без его глобального указателя, чтобы этот указатель тоже не таскать за собой через конструкторы объектов, при этом, чтобы не нарушать lifecycle игры, ресурсы все-таки должны грузиться в onCreateResources().
Вы уже наверняка догадались, что этим условиям идеально подходит синглтон.
Сразу скажу, что решение вопроса доступа к ресурсам через синглтон целиком и полностью зависит от того, как организованы ваши игровые объекты. Т.е. игровым объектом может быть простое расширение спрайта (Object extended Sprite), а может быть и объект содержащий несколько спрайтов. Здесь я приведу код простого ResourceManager-а просто для того. чтобы обозначить сам подход к реализации.
Как им пользоваться:
В onCreateResources() нашей активити c начала инициализируем ResourceManager, а затем грузим всю нашу графику, звуки, шрифты и т.д.:
Где-то в коде..
Вы можете спросить - и чо же такого в нем менеджерского, чтобы выделять этот код в отдельный класс? Ответ на этот вопрос кроется в организации структуры игры. Если ваша игра состоит только из одной сцены (а такого не бывает ;-)), то менеджер не нужен. Но в самом простом случае мы имеем как минимум две сцены: 1. Главное меню 2. Сцена с самой игрой. Нам не нужны ресурсы игры когда мы находимся в главном меню и наоборот - в игре нам не нужны ресурсы главного меню. Так вот, чтобы загружать/выгружать нужные/ненужные ресурсы и поможет наш менеджер.
PS. Продумывайте какие ресурсы будут задействованы во время главного геймплея. Все ресурсы необходимо грузить до того как игра началась. Если в процессе игры приложение начнет что-то загружать - это немедленно отразится на производительности.
Ресурсами игры считается вся используемая графика, музыка, звуки и шрифты.
Многие из вас, только начиная работать с AndEngine, действовали по шаблону взятому из примеров: создавали активити, прописывали в ней при необходимости поля текстур, регионов и спрайтов; заполняли поля в onCreateResources(); собирали сцену в onCreateScene() и радовались жизни =) Проблемы начинались тогда, когда объект игры представлял собой не просто спрайт, а комплексный объект использующий свои дополнительные свойства и методы, содержащий более одного спрайта и т.д. С подходом описанным выше, приходиться либо объявлять поля активити как protected/public или передавать текстуры через конструктор объекта. Как результат:
- грязный код,
- активити заполнено полями которые этой активти не используются,
- множество параметров в конструкторах объектов,
- сложности в отслеживании неиспользуемых ресурсов.
Нам было бы гораздо удобнее иметь некий менеджер, который бы давал нам необходимые текстуры и вызывался тогда когда он нам нужен без его глобального указателя, чтобы этот указатель тоже не таскать за собой через конструкторы объектов, при этом, чтобы не нарушать lifecycle игры, ресурсы все-таки должны грузиться в onCreateResources().
Вы уже наверняка догадались, что этим условиям идеально подходит синглтон.
Сразу скажу, что решение вопроса доступа к ресурсам через синглтон целиком и полностью зависит от того, как организованы ваши игровые объекты. Т.е. игровым объектом может быть простое расширение спрайта (Object extended Sprite), а может быть и объект содержащий несколько спрайтов. Здесь я приведу код простого ResourceManager-а просто для того. чтобы обозначить сам подход к реализации.
package com.expedition107.evileye;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import org.andengine.audio.music.Music;
import org.andengine.audio.music.MusicFactory;
import org.andengine.audio.sound.Sound;
import org.andengine.audio.sound.SoundFactory;
import org.andengine.engine.Engine;
import org.andengine.entity.scene.Scene;
import org.andengine.opengl.font.Font;
import org.andengine.opengl.font.FontFactory;
import org.andengine.opengl.texture.TextureOptions;
import org.andengine.opengl.texture.bitmap.BitmapTexture;
import org.andengine.opengl.texture.region.ITextureRegion;
import org.andengine.opengl.texture.region.TextureRegionFactory;
import org.andengine.util.adt.color.Color;
import org.andengine.util.adt.io.in.IInputStreamOpener;
import android.content.Context;
import android.graphics.Typeface;
public class ResourceManager {
private static ResourceManager Instance;
//Поля для доступа к "движковым" переменным
private Context currentActivity;
private Engine engine;
//текстуры
public BitmapTexture mBackgroundBitmapTexture;
public BitmapTexture mCloudsBitmapTexture;
public BitmapTexture mHeroBitmapTexture;
//регионы
public ITextureRegion bgrTextureRegion;
public ITextureRegion heroTextureRegion;
public ITextureRegion cloudTextureRegion;
public ITextureRegion arrowTextureRegion;
//музыка и звуки
public Music bgrMusic;
public ArrayList<Sound> sounds = new ArrayList<Sound>();
//шрифт
public Font font;
public synchronized static ResourceManager getInstance(){
if(Instance == null){
Instance = new ResourceManager();
}
return Instance;
}
//для того чтобы наш менеджер заработал мы должны его проинициализировать
public synchronized void init(Context pContext, Engine pEngine){
currentActivity = pContext;
engine = pEngine;
}
public synchronized void loadGameSceneTextures(){
try {
mBackgroundBitmapTexture = new BitmapTexture(engine.getTextureManager(), new IInputStreamOpener() {
@Override
public InputStream open() throws IOException {
return currentActivity.getAssets().open("gfx/background.png");
}
}, TextureOptions.BILINEAR);
mBackgroundBitmapTexture.load();
bgrTextureRegion = TextureRegionFactory.extractFromTexture(mBackgroundBitmapTexture);
mCloudsBitmapTexture = new BitmapTexture(engine.getTextureManager(), new IInputStreamOpener() {
@Override
public InputStream open() throws IOException {
return currentActivity.getAssets().open("gfx/clouds.png");
}
}, TextureOptions.BILINEAR);
mCloudsBitmapTexture.load();
cloudTextureRegion = TextureRegionFactory.extractFromTexture(mCloudsBitmapTexture);
mHeroBitmapTexture = new BitmapTexture(engine.getTextureManager(), new IInputStreamOpener() {
@Override
public InputStream open() throws IOException {
return currentActivity.getAssets().open("gfx/hero.png");
}
}, TextureOptions.BILINEAR);
mHeroBitmapTexture.load();
heroTextureRegion = TextureRegionFactory.extractFromTexture(mHeroBitmapTexture, 0, 0, 100, 100);
arrowTextureRegion = TextureRegionFactory.extractFromTexture(mHeroBitmapTexture, 110, 0, 50, 5);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public synchronized void unloadGameSceneTextures(){
mBackgroundBitmapTexture.unload();
mCloudsBitmapTexture.unload();
mHeroBitmapTexture.unload();
System.gc();
}
public synchronized void loadSounds(){
SoundFactory.setAssetBasePath("sounds/");
try {
sounds.add(SoundFactory.createSoundFromAsset(getInstance().engine.getSoundManager(), getInstance().currentActivity, "hit1sound.mp3"));
sounds.add(SoundFactory.createSoundFromAsset(getInstance().engine.getSoundManager(), getInstance().currentActivity, "hit2sound.mp3"));
sounds.add(SoundFactory.createSoundFromAsset(getInstance().engine.getSoundManager(), getInstance().currentActivity, "hit3sound.mp3"));
sounds.add(SoundFactory.createSoundFromAsset(getInstance().engine.getSoundManager(), getInstance().currentActivity, "hit4sound.mp3"));
sounds.add(SoundFactory.createSoundFromAsset(getInstance().engine.getSoundManager(), getInstance().currentActivity, "hero_die_sound.mp3"));
sounds.add(SoundFactory.createSoundFromAsset(getInstance().engine.getSoundManager(), getInstance().currentActivity, "shootsound.mp3"));
} catch (final IOException e) {
}
}
public synchronized void unloadSounds(){
while (getInstance().sounds.size() > 0){
if (!getInstance().sounds.get(0).isReleased()){
getInstance().sounds.get(0).release();
}
getInstance().sounds.remove(0);
}
}
public synchronized void loadMusic(){
MusicFactory.setAssetBasePath("music/");
try {
bgrMusic = MusicFactory.createMusicFromAsset(getInstance().engine.getMusicManager(), getInstance().currentActivity, "mfx/bgrmusic1.mp3");
} catch (final IOException e) {
}
}
public synchronized void unloadMusic(){
if (!bgrMusic.isReleased())
bgrMusic.release();
}
public synchronized void loadFont(){
FontFactory.setAssetBasePath("fonts/");
font = FontFactory.create(getInstance().engine.getFontManager(), getInstance().engine.getTextureManager(), 256, 256, Typeface.create(Typeface.DEFAULT, Typeface.NORMAL), 32f, true);
font.load();
}
public synchronized void unloadFont(){
font.unload();
}
}
Как им пользоваться:
В onCreateResources() нашей активити c начала инициализируем ResourceManager, а затем грузим всю нашу графику, звуки, шрифты и т.д.:
@Override
public void onCreateResources() {
ResourceManager.getInstance().init(this, mEngine);
ResourceManager.getInstance().loadGameSceneTextures();
ResourceManager.getInstance().loadSounds();
ResourceManager.getInstance().loadMusic();
ResourceManager.getInstance().loadFonts();
}
Где-то в коде..
... ResourceManager rm = ResourceManager.getInstance(); mArrowSprite = new Sprite(pX, pY, rm.arrowTextureRegion, rm.engine.getVertexBufferObjectManager()); ...
Вы можете спросить - и чо же такого в нем менеджерского, чтобы выделять этот код в отдельный класс? Ответ на этот вопрос кроется в организации структуры игры. Если ваша игра состоит только из одной сцены (а такого не бывает ;-)), то менеджер не нужен. Но в самом простом случае мы имеем как минимум две сцены: 1. Главное меню 2. Сцена с самой игрой. Нам не нужны ресурсы игры когда мы находимся в главном меню и наоборот - в игре нам не нужны ресурсы главного меню. Так вот, чтобы загружать/выгружать нужные/ненужные ресурсы и поможет наш менеджер.
PS. Продумывайте какие ресурсы будут задействованы во время главного геймплея. Все ресурсы необходимо грузить до того как игра началась. Если в процессе игры приложение начнет что-то загружать - это немедленно отразится на производительности.