Звуки и музыка

Апрель 15th, 2011 автор: Oleg Antipov

Вот решил выложить свои классы по работе со звуком и музыкой. Они у меня уже не менялись где-то четыре игры подряд, так что можно считать, что их функционал подходить под большинство игр. Классы используют выкладываемые ранее модули ageMath и ageVector (они уже включены в исходники примера в конце поста).

Звуковой класс может воспроизводить только одиночные звуки. Петли и зацикленные эффекты планируется добавить в будущем. Просто пока мне это было не нужно в предыдущих играх :) класс может также воспроизводить объемный звук, учитывая расстояние до объекта и его положение относительно слушателя.

Музыкальный класс может воспроизводить зациклено музыкальные треки а также обеспечивать плавную смену музыки путём затухания.

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


Смотрим пример (жмём на жёлтые кружочки, чтобы было весело):

Использовать класс sounds весьма просто. Для начала надо в функции init написать те, звуки которые будут загружены:

//Здесь втыкаем свои звуки
snd['mysnd'] = new mysnd();

потом надо инициализировать класс в основной программе:

sounds.init(500,400);

Вот вообщем-то и всё! Теперь мы можем вызывать воспроизведение звука где нам это необходимо:

sounds.PlaySnd('mysnd',1,b1);

первый параметр это имя звука, которое мы задали в функции init. Второй – это сила звука, для взрывов например можно ставить 2 или 3. Последний параметр это эмиттер звука. Его координаты будут использованы для расчёта затухания звука, а также его панорамирования. Если задать только имя звука – он будет воспроизведён без учёта положения. Все звуки воспроизводятся относительно координат sounds.cenX и sounds.cenY. По умолчанию это центр экрана (все расчёты ведутся в глобальных координатах Stage). Также можно привязать слушателя к конкретному объекту, для этого необходимо будет менять cenX и cenY в основной программе (не забывая переводить координаты в глобальную систему координат).

Громкость звуков можно менять с помощью переменной sounds.vol. Отключить все звуки можно задав переменной sounds.sndEnable=false.

Класс sounds:

// Управление пространственными звуками в игре
package main {
	import flash.media.SoundTransform;
	import flash.display.*;
	import flash.utils.*;
	import flash.geom.*;
 
	dynamic public class sounds extends Object 
	{
		static var snd:Object;	// хэш со звуками
		static var stageRadius:Number; // примерный радиус видимой области
		static public var vol:Number=0.9; // громкость
		static public var sndEnable:Boolean=true; // включён ли звук
 
		//Координаты слушателя
		static public var cenX:Number=0;
		static public var cenY:Number=0;
 
		//внутренние переменные
		static var v:ageVector= new ageVector();
		static var p:Point;
 
		//Инициализация класса. Передаём в функцию размеры флешки
		static public function init(stageWidth:Number, stageHeight:Number) 
		{
			// создадим хеш звуков
			snd = new Object();
 
			//Здесь втыкаем свои звуки
			snd['mysnd'] = new mysnd();
 
			// вычислим примерный радиус видимой области экрана
			stageRadius = (stageWidth)/1.4;			
 
			//Центр по умолчанию в центре экрана. Можно привязать к какому-либо объекту
			cenX=stageWidth*0.5;
			cenY=stageHeight*0.5;
 
		}
 
 
		// Воспроизводит звук с учетом пространства
		// snd_name - имя звука
		// dv - множитель для громкости		
		// obj - эмиттер звука
		static public function PlaySnd(snd_name:String,dv:Number=1.0,obj:*=null):void 
		{
			var t:SoundTransform;
 
			//Если звук отключён - выходим
			if(!sndEnable || vol<0.001) return;
 
			//Без объёмного звука
			if(obj==null)
			{
 
				t = new SoundTransform();
				t.volume = vol;
				snd[snd_name].play(0, 0, t);
 
			}
			else
			{
 
				t= new SoundTransform();
 
				//Все расчёты ведём в глобальной системе координат
				p=obj.parent.localToGlobal(new Point(obj.x,obj.y));
 
				v.x=p.x-cenX;
				v.y=p.y-cenY;
 
				// Ставим громкость в зависимости от расстояния
				t.volume = ageMath.trimToRange(vol * ageMath.RemapVal(v.len(),0,dv * stageRadius,1,0),0,1);
 
				// стерео в зависимости от положения источника звука
				t.pan = ageMath.trimToRange(ageMath.RemapVal(v.x,-stageRadius/2,stageRadius/2,-1,1),-1,1);
 
				// воспроизводим
				snd[snd_name].play(0, 0, t);
			}
		}
 
	}
}

Класс музыки musicEnv инициализируется аналогичным образом. В init прописываем все треки которые будут использованы в нашей игре.

//Здесь втыкаем свои муз треки
mus['mus1'] = new mus1();
mus['mus2'] = new mus2();
mus['mus3'] = new mus3();

далее в основной программе инициализируем класс с музыкой:

musicEnv.init();

и его уже можно использовать! Включаем одну музыку:

musicEnv.fadeTo('mus1');

потом плавно с затуханием меняем на другую:

musicEnv.fadeTo('mus2');

а так можно остановить музыку:

musicEnv.played(false);

чтобы отключить музыку в программе необходимо также использовать переменную sounds.sndEnable:

musicEnv.musEnable=false;
musicEnv.played(false);

включить музыку обратно:

musicEnv.musEnable=true;
musicEnv.played(true);

звук музыки регулируется с помощью переменной sounds.vol, скорость фейдинга – sounds.volFadeSpeed

Класс musicEnv:

// Управление пространственными звуками в игре
package main {
	import flash.media.SoundTransform;
	import flash.display.*;
	import flash.geom.*;
	import flash.utils.*;
	import flash.events.*;
	import flash.media.*;
	dynamic public class musicEnv extends Object 
	{
		static var mus:Object;	// хэш с музыкой
		static public var vol:Number=0.9; // громкость
		static public var volFadeSpeed:Number=0.01; //Скорость фейдинга
		static public var musEnable:Boolean=true; // включёна ли музыка
 
		static public var curMusName:String=""; // Имя текущего трека
 
 
		//внутренние переменные
		static var curCh:SoundChannel; //Текущий канал
		static var offCh:SoundChannel; //Затухающий канал
		static var curVol:Number=0;
		static var curPos:Number=0;
		static var offVol:Number=0;
 
		static var isplayed:Boolean=false;
 
		static var timerVolFader:Timer= new Timer(10);
 
		static var t1:SoundTransform = new SoundTransform();
		static var t2:SoundTransform = new SoundTransform();
 
		static public function init() 
		{
			timerVolFader.stop();
 
			// создадим хеш с музыкой
			mus = new Object();
 
			//Здесь втыкаем свои муз треки
			mus['mus1'] = new mus1();
			mus['mus2'] = new mus2();
			mus['mus3'] = new mus3();
		}
 
		//Плавная смена трека на musname
		static public function fadeTo(musname:String) 
		{
			curMusName=musname;
 
 
			//Если музыка отключена - выходим
			if(!musEnable) return;
 
			isplayed=true;
 
			var t:SoundTransform;
			t = new SoundTransform();
			t.volume = 0;
 
			if(offCh) offCh.stop();
			offCh=curCh;
 
			curCh=mus[musname].play(0, int.MAX_VALUE, t);
 
			curVol=0;
			offVol=vol;
 
			if(!timerVolFader.running)
			{
				timerVolFader.addEventListener(TimerEvent.TIMER, timerVolFader_Timer, false, 0, true);
				timerVolFader.start();
			}
 
		}
 
		//Остановить или запустить воспроизведение музыки
		static public function played(flg:Boolean):void 
		{
			if(flg==isplayed) return;
 
			if(flg)
			{
				if(curMusName!="") fadeTo(curMusName);
			}
			else
			{
				isplayed=false;
 
				if(curCh) 
				{
					curCh.stop();
					curPos=curCh.position;
				}
				if(offCh) offCh.stop();
				offVol=0;
				curVol=vol;
 
				if(timerVolFader.running)
				{
					timerVolFader.stop();
					timerVolFader.removeEventListener(TimerEvent.TIMER, timerVolFader_Timer);
				}
			}
		}
 
		//Плавный фейдинг музыки
		static public function timerVolFader_Timer(event:TimerEvent):void 
		{
 
 
			if(offCh!=null)
			{
				if(offVol>0)
					offVol -= volFadeSpeed;
				else
					offVol=0;
 
				t1.volume = offVol;
				offCh.soundTransform=t1;
			}
 
			if(curCh!=null && (offVol<vol/2 || offCh==null))
			{
				if(curVol<vol)
					curVol += volFadeSpeed;
				else
					curVol=vol;
 
				t2.volume = curVol;
				curCh.soundTransform=t2;
			}
 
 
			//Если достигли порогового значения громкости
			if(curVol>vol)
			{
				if(offCh!=null) offCh.stop();
				timerVolFader.stop();
				timerVolFader.removeEventListener(TimerEvent.TIMER, timerVolFader_Timer);
			}
		}
	}
}

Вот вообщем то и всё. Думаю новичкам класс может пригодиться. Исходники примера тут: http://www.anegmetex.com/devblog/files/SoundAndMusic.rar

Категория: Полезные классы

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

  1. Александр

    Огромное спасибо! Стереоэффект и зависимость от расстояния – просто и со вкусом.

  2. RaymondGames

    Делал как то так же, только у меня один единственный SoundSystem и туда можно addSound() addMusic() ну а потом конечно playSound() playMusic(). Ну и мне больше по душе жесткий Stop() либо Pause() еще можно Shut() тогда звук всё еще играет но просто не слышно до вызова Scream(). Кстати о конструкции Object[var_name] я вспомнил совсем недавно во время рефакторинга – очень удобная штука. Кстати зачем совать в инит конкретику если её можно вынести за пределы этого контроллера? Мне больше понравилась конструкция public function addSound(sound_name:String, sound_res:Class):void{mSounds[sound_name]=new sound_res();} Ресурсы ложишь во время инита игры допустим или левела. Там уже пишешь pSoundSystem.addSound(’boom’, sound_boom); Чисто для удобство реиспользования технологии. В любом случае спасибо за статью, кстати пока у меня рефакторинг надо поднять тему менеджжера кеш объектов. А то у меня до сих пор индексы (хоть и в векторе).

  3. Oleg Antipov

    >Кстати зачем совать в инит конкретику

    ну динамически добавлять мне звуки нет необходимости, так что так проще, меньше букавок и всё в одном месте. Хотя конечно с точки зрения архитектуры не очень. Но мне важней первое :smile:

  4. Alxs

    Привет Олег! Спасибо за шаровый класс… В “хозяйстве” пригодится … ;-) Напрямую я его вряд ли стану использовать, но как “пища для ума”, для написания своего “саунд менеджера”, оч. поможет.

  5. Вадим

    Олег подскажите как с вами связаться, и насколько реально у вас заказать игру.
    пишите на эмэй если будет время.

  6. Oleg Antipov

    отписал на емайл

  7. Dulea

    Олег, здравствуйте.
    А не подскажете, почему могут быть ошибки такого рода?

    Dropbox\BL 5.0\main\sounds.as, Line 71
    1178: Attempted access of inaccessible property x through a reference with static type ageVector.

    Dropbox\BL 5.0\main\sounds.as, Line 75 1195: Attempted access of inaccessible method len through a reference with static type ageVector.

    И.т.д.

  8. Oleg Antipov

    ageVector подключен?

  9. Dulea

    Да, конечно.
    Ваш исходник из этой статьи запускается без проблем.
    Ну я в принципе закомментировал позиционирование звука. (теперь ageVector и ageMath не используются)
    В любом случае, спасибо за класс. Он небольшой и будет интересно в нем разобраться.

  10. Rage

    Олег, спасибо! Прекрасный класс! Жду-не дождусь, когда вы добавите зацикливающиеся звуки

  11. paulp

    Супер, то что нужно, большое спасибо.

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

 

Games For Free    Game Is The Best    Best Pokemon Game    Cat Video