воскресенье, 17 февраля 2013 г.

Менеджер ресурсов

Привет! В этом сообщении я освещу некоторые вопросы связанные с загрузкой и освобождением ресурсов игры и на итог мы напишем шаблон класса который будет организовывать работу с этими ресурсами.

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


Комментариев нет:

Отправить комментарий