Привет! В этом сообщении я освещу некоторые вопросы связанные с загрузкой и освобождением ресурсов игры и на итог мы напишем шаблон класса который будет организовывать работу с этими ресурсами.
Ресурсами игры считается вся используемая графика, музыка, звуки и шрифты.
Многие из вас, только начиная работать с 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. Продумывайте какие ресурсы будут задействованы во время главного геймплея. Все ресурсы необходимо грузить до того как игра началась. Если в процессе игры приложение начнет что-то загружать - это немедленно отразится на производительности.