Форум Flasher.ru
Ближайшие курсы в Школе RealTime
Список интенсивных курсов: [см.]  
  
Специальные предложения: [см.]  
  
 
Блоги Правила Справка Пользователи Календарь Поиск рулит! Сообщения за день Все разделы прочитаны
 

Вернуться   Форум Flasher.ru > Блоги > Jarproger

Оценить эту запись

Удобная работа с мышью и клавиатурой - класс Input.as

Запись от Jarproger размещена 31.01.2012 в 22:53

Наверно многие из вас сталкивались с тем, что в языке ActionScript 3 при создании игры типа "аркада" возникает ряд проблем с клавиатурой.

Во-первых, нету, как в AS 2, удобной функции key.isDown(). А создавать переменные-триггеры для каждой клавиши - это очень не удобно и не правильно. Я решил эту проблему, создав класс KeyCheck, который вешает на себя события нажатия и отпускания клавиш и запоминает их состояния в массивах. Я уже писал в блоге про этот класс.

Во-вторых, событие KEY_UP может и не прийти - например, если пользователь вызвал контекстное меню (!!!) и там отпустил клавишу, то событие не отправиться и для программы клавиша будет нажата. Аналогично с переключением между окнами, например, alt-tab. Кроме того, с мышью также возникают проблемы и я столкнулся с ними при написании игрового инвентаря - если объект "взять", а потом вызвать контекстное меню, там отпустить, то затем он "прилипнет" к мышке.

Я написал более универсальный класс Input.as, который решает все эти проблемы - отслеживает открытие контекстного меню, потерю фокуса окна и сбрасывает состояние мыши и клавиатуры.
Использовать его предельно просто:

Код AS3:
public var inp:Input;//Создаём объект
inp = new Input(this);//В конструкторе главного класса передаём ссылку на него
//..И всё - далее вы можете в любой момент узнать текущее состояние клавиатуры и мыши
inp.code[65] - вернёт true, если сейчас нажата и удерживается клавиша 'a'
inp.isMouseDown - определит состояние левой кнопки мыши мыши
Добавлю, что вы можете использовать мой класс в своих проектах с любой целью
Вложения
Тип файла: zip Input.zip (1.8 Кб, 203 просмотров)
Всего комментариев 20

Комментарии

Старый 31.01.2012 23:09 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Интересно. Но спорно, что хорошо, что code[x] - read/write.
Мне кажется, клавиатура и мышь одна. Неплохо бы сделать класс статичным, а ещё, до кучи, пусть рассылает события нажатия кнопок и отпускания их. А то помесь выходит - и Input используй, и по старинке.
Старый 31.01.2012 23:22 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
Солидарен с предыдущим оратором. Без событий смысл теряется. А вот на счет старики - не согласен. Ибо хочешь сделать статическим - помести в статическую переменную, а хочешь передать в другой апп
-домен - передай объект по ссылке.
Старый 01.02.2012 00:02 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Не вижу ситуации, в котором может потребоваться передать этот класс в другой апп-домен. Если swf'ке нужна работа с клавиатурой - у него будет свой Input. Максимум, что можно выжать отсюда - это чтобы один Input был для всех апп. доменов, но ё-мое... что-то не представляю себе ситуации, когда это реально нужно.
Старый 01.02.2012 00:08 expl вне форума
expl
Со времен перехода на as3 не понятно зачем они isKeyDown выпилили. Тоже такие классы делал,
только до
Код AS3:
/**
		 * Аналогично - если пользотватель переключился на другое окно
		 */
		private function onFocusOut(event:Event):void {
не додумывался, а надо было бы отжимать при расфокусировке.
Старый 01.02.2012 11:04 Jarproger вне форума
Jarproger
 
Аватар для Jarproger
Т.е. code[x] лучше сделать только read, через геттеры?
Статичным никак не получиться, т.к. он должен обрабатывать stage... Хотя можно передать в него ссылку на root и запомнить в статической переменной. Тут вы правы, доработаю.
А что за аппдомен?
Старый 01.02.2012 11:29 gloomyBrain вне форума
gloomyBrain
 
Аватар для gloomyBrain
Цитата:
Если swf'ке нужна работа с клавиатурой - у него будет свой Input
Дружище, ты не прав. Так получается что у родительской swf-ки будет один набор нажатых клавиш, а у подгруженной - другой. Input у них должен быть один и тот же. А чтобы этого добиться как раз и нужно передать его по ссылке в нужный ApplicationDomain (@Jarproger - это имелось ввиду под "апп-доменом")
Старый 01.02.2012 11:56 expl вне форума
expl
Цитата:
Т.е. code[x] лучше сделать только read, через геттеры?
Вариант
Код AS3:
public function isKeyDown(keyCode:int):Boolean
просто более типобезопасный и, главное, боллее понятный (как его использовать и что вернёт ясно), чем
Код AS3:
public var code:Array;
Тут без доков не сразу догадаешься, что если написать code[32], то оно вернет булевское значение нажата ли клавиша.
И да, пользователи класса могут подумать что можно для каких-то целей менять этот массив или присваивать (хотя мозги у разработчиков иногда включаются и такие заблуждения маловероятны)

Чисто из-за этих соображений
Обновил(-а) expl 01.02.2012 в 12:03
Старый 01.02.2012 12:00 HardCoder вне форума
HardCoder
 
Аватар для HardCoder
Цитата:
Статичным никак не получиться, т.к. он должен обрабатывать stage...
Сделайте синглтон.
Старый 01.02.2012 13:24 ProxyGreen вне форума
ProxyGreen
 
Аватар для ProxyGreen
Хе хе интересная штука, допилил её немножко в меру способностей. Добавил события, геттеры keyStatus() и mouseStatus(), в keyStatus() можно передать как кейкод, так и латинскую букву т.к. кейКоды вроде совпадают с латинскими заглавными чарКодами. Ещё есть стек нажатых кнопок клавиатуры, чтобы можно было какие нибудь простые комбоудары или последовательности различать. Ещё для каждой клавиши диспатчится своё собственное событие, можно например подписаться только на нажатие "w" без всяких свитчев. События нажатия клавиши теперь диспатчится только один раз, а не бешено как нативное кейДовн. Ну и init()/uninit() добавил для красоты.
Ещё хочется отметить что плюшка с контекстным меню у меня совершенно не работает.

Не знаю как тут файлы аттачить, поэтому так выложу:

Код AS3:
package{
import flash.display.InteractiveObject;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.ContextMenuEvent;
import flash.events.EventDispatcher;
import flash.events.KeyboardEvent;
import flash.ui.ContextMenu;
import flash.events.MouseEvent;
 
/**
 * Input - класс для удобной рабты с клавиатурой и мышью
 * @usage использование очень простое, не смотря на внутреннюю реализацию
   public var inp:Input;//Создаём объект
   inp = new Input(this);//В конструкторе главного класса передаём ссылку на него
   //..И всё - далее вы можете в любой момент узнать текущее состояние клавиатуры и мыши
   inp.code[65] - вернёт true, если сейчас нажата и удерживается клавиша 'a'
   inp.isMouseDown - определит состояние мыши
 * 
 * @version 2.1
 * @author Yarick
 * @editor ProxyGreen
 * @copy класс является свободно распространяемым, также вы можете использовать его в любых целях
 */
 
public class Input extends EventDispatcher {
 
	public static const INPUT_EVENT:String = 'input_event';
 
	public static const KEY_EVENT:String = 'key_event';
	public static const KEY_DOWN:String = 'key_down';
	public static const KEY_UP:String = 'key_up';
 
	public static const MOUSE_EVENT:String = 'mouse_event';
	public static const MOUSE_DOWN:String = 'mouse_down';
	public static const MOUSE_UP:String = 'mouse_up';
 
	public static const CONTEXT_MENU:String = 'context_menu';
	public static const FOCUS_OUT:String = 'focus_out';
 
	protected var code:Object = new Object();//Массив клавиш [код клавиши - нажата ли(t/f)]
	protected var keyStack:Array = new Array(); //Последовательность нажатых клавиш
	protected var keyStackSize:uint = 0;
	protected var isMouseDown:Boolean = false;//Нажата ли левая кнопка мыши
 
	protected var target:InteractiveObject;
	/**
	 * Конструктор
	 * @param	root Главный класс
	 */
	public function Input(root:Sprite, stackSize:uint=0) {//Конструктор
 
		keyStackSize = stackSize;
		target = root.stage;
 
		init();
 
		//Отслеживание контексного меню
		//Не работает :(
		/*var menu:ContextMenu = new ContextMenu();
		root.contextMenu = menu;
		menu.addEventListener(ContextMenuEvent.MENU_SELECT, onContextMenu);*/
	}
 
	public function init():void {
		//Обработка событий клавиатуры
		target.addEventListener(KeyboardEvent.KEY_DOWN,onKeyDown);
		target.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
 
		//Для потери фокуса окна приложения
		target.addEventListener(Event.DEACTIVATE, onFocusOut); 
 
		//Обработка событий мыши
		target.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); 
		target.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); 
	}
 
	public function uninit():void {
		resetStatus();
 
		//Обработка событий клавиатуры			
		target.removeEventListener(KeyboardEvent.KEY_DOWN,onKeyDown);
		target.removeEventListener(KeyboardEvent.KEY_UP, onKeyUp);
 
		//Для потери фокуса окна приложения
		target.removeEventListener(Event.DEACTIVATE, onFocusOut); 
 
		//Обработка событий мыши
		target.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); 
		target.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); 
	}
 
	public function mouseStatus():Boolean {
		return isMouseDown;
	}
 
	//Можно задать код клавиши или латинускую букву, например "w" или " " для пробела.
	public function keyStatus(key:Object):Boolean {
		var index:uint = 0;
		if (key is uint) {
			index = (key as uint);
		}else if (key is String) {
			index = (key as String).toUpperCase().charCodeAt(0);
		}else {
			throw new Error('uncorrect keyCode');
		}
 
		if (code[index]) {
			return code[index];
		}else {
			return false;
		}
	}
 
	//Управление стеком клавиш
	public function clearKeyStack():void {
		keyStack = new Array();
	}
 
	public function getKeyStack():Array {
		return keyStack;
	}
 
	//добавить клавишу в стек
	protected function addKeyToStack(key:uint):void {
		keyStack.push(key);
		if (keyStack.length > keyStackSize) {
			keyStack.shift();
		}
	}
 
	//Теперь генерируются события от конкретной клавиши, т.е. можно подписаться на событие нажатия клавиши "W"
	//используя событие KEY_DOWN+'87', аналогично с отпусканием клавиши. Так-же событие генерируется один раз при нажатии.
	private function onKeyDown(event:KeyboardEvent):void {
		if (!keyStatus(event.keyCode)) {
 
			code[event.keyCode] = true;
 
			addKeyToStack(event.keyCode);
 
			dispatchEvent(new Event(INPUT_EVENT, true, true));
			dispatchEvent(new Event(KEY_EVENT, true, true));
			dispatchEvent(new Event(KEY_DOWN, true, true));
			dispatchEvent(new Event(KEY_DOWN+event.keyCode, true, true));
 
		}
		//Если хотите узнать код клавиши - просто раскомментируйте код ниже
		//trace(event.keyCode);
	}
	private function onKeyUp(event:KeyboardEvent):void {
		code[event.keyCode] = false;
		dispatchEvent(new Event(INPUT_EVENT, true, true));
		dispatchEvent(new Event(KEY_EVENT, true, true));
		dispatchEvent(new Event(KEY_UP, true, true));
		dispatchEvent(new Event(KEY_UP+event.keyCode, true, true));
	}
 
	/**
	 * События мыши
	 * @param	event
	 */
	private function onMouseDown(event:MouseEvent):void {
		isMouseDown = true;
		dispatchEvent(new Event(INPUT_EVENT, true, true));
		dispatchEvent(new Event(MOUSE_EVENT, true, true));
		dispatchEvent(new Event(MOUSE_DOWN, true, true));
	}
	private function onMouseUp(event:MouseEvent):void {
		isMouseDown = false;
		dispatchEvent(new Event(INPUT_EVENT, true, true));
		dispatchEvent(new Event(MOUSE_EVENT, true, true));
		dispatchEvent(new Event(MOUSE_UP, true, true));
	}
 
 
 
	/**
	 * Обработка события, когда пользователь вызывает контектное меню
	 * Нужна для решения проблемы:
	 * Если пользователь нажимает кнопку и вызывает контексное меню
	 * и там отжимает кнопку, то событие KEY_UP не происходит, и когда он возвращается с отжатой кнопкой, 
	 * программа продолжает её "нажимать". Это особенно критично в играх-аркадах, герой движется без остановки
	 * @param	event
	 */
	private function onContextMenu(event:Event):void {
		//Если пользователь открыл контексное меню, мы "отжимаем" все клавиши
		trace('contextMenu');
		resetStatus();
		dispatchEvent(new Event(CONTEXT_MENU, true, true));
	}
 
	/**
	 * Аналогично - если пользотватель переключился на другое окно
	 */
	private function onFocusOut(event:Event):void {
		//Если пользователь переключился на другое окно, также отжимаем клавиши
		resetStatus();
		dispatchEvent(new Event(FOCUS_OUT, true, true));
	}
 
	protected function resetStatus():void {
		for (var keyCode:String in code) {
			if (code[keyCode]) {
				onKeyUp(new KeyboardEvent(KeyboardEvent.KEY_UP, true, false, 0, keyCode as uint));
			}
		}
 
		isMouseDown = false;
		dispatchEvent(new Event(INPUT_EVENT, true, true));
	}
}
}
Буду рад если вы исправите какие-нибудь баги, глюки и логические несоответствия.
Старый 01.02.2012 13:42 olexandr вне форума
olexandr
 
Аватар для olexandr
посмотрел пример и решил добавить в свой подобный класс отслеживание мыши и контекстМеню
но поскольку для работы с контекстным меню я пользуюсь другим классом, то создание нового меню, как здесь меня не устраивает. получается нет способа получить ссылку на текущее контекстное меню?
Старый 01.02.2012 14:01 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Цитата:
Дружище, ты не прав. Так получается что у родительской swf-ки будет один набор нажатых клавиш, а у подгруженной - другой.
Хм, да, согласен. Не подумал, что флешка может подгрузиться, пока зажата какая-то клавиша )
Старый 01.02.2012 14:48 expl вне форума
expl
Цитата:
Добавил события, геттеры keyStatus() и mouseStatus(), в keyStatus() можно передать как кейкод, так и латинскую букву...
Вот так всегда: начинается с хорошей идеи, а потом "Астапа понесло"
Старый 01.02.2012 18:45 dimarik вне форума
dimarik
 
Аватар для dimarik
А зачем вы вешаете листенеры на каждый root.stage? Stage один у нас. Каждое последующее добавление бессмысленно.

Нужно проверять наличие root.stage. Если SWF подгружается, то никак нельзя исполнять этот код из конструктора "главного класса".

Неплохо было бы сначала проверить доступность stage по соображениям безопасности.

А если у дитя "главного класса" свое контекстное меню будет?
Старый 02.02.2012 13:55 alatar вне форума
alatar
 
Аватар для alatar
Зачем вообще передавать ему root (собственно любой спрайт), если все равно слушатели вешаются на stage. Тогда и добавьте в конструктор Stage вместо Sprite. Или вешайте слушателей на переданный root, тогда будет смысл, если нужно слушать клавиатуру для конкретного InteractiveObject.
Старый 02.02.2012 14:21 Inet_PC вне форума
Inet_PC
 
Аватар для Inet_PC
Цитата:
А зачем вы вешаете листенеры на каждый root.stage? Stage один у нас.
А в Air вроде не один, или я ошибаюсь?
Старый 02.02.2012 15:50 Jewelz вне форума
Jewelz
 
Аватар для Jewelz
воможно со stage3D путаете
Старый 02.02.2012 16:00 alatar вне форума
alatar
 
Аватар для alatar
В air по одному для каждого NativeWindow, т.е. в приложении их может быть несколько.
Старый 02.02.2012 16:11 Inet_PC вне форума
Inet_PC
 
Аватар для Inet_PC
Цитата:
В air по одному для каждого NativeWindow, т.е. в приложении их может быть несколько.
Значит не ошибаюсь.
Старый 02.02.2012 23:32 dimarik вне форума
dimarik
 
Аватар для dimarik
Цитата:
Зачем вообще передавать ему root...
alatar, коллега, именно эта мысль должна была возникнуть после моего поста.

Inet_PC, вероятно, не ошибаетесь. Из предыдущего вывода напрашивается мысль об идентификации предложенного Stage. А я не в курсе как реагировать предложенной автором логике на смену NativeWindow.

От себя. hasEventListener нам ничего не скажет о том, что подписали ли мы именно этот хэндлер. Разумно хранить состояние подписки внутри хеш-таблицы класса.
Старый 03.02.2012 17:20 Dima_DPE вне форума
Dima_DPE
Не делай синглетон
 

 


Часовой пояс GMT +4, время: 12:38.


Copyright © 1999-2008 Flasher.ru. All rights reserved.
Работает на vBulletin®. Copyright ©2000 - 2024, Jelsoft Enterprises Ltd. Перевод: zCarot
Администрация сайта не несёт ответственности за любую предоставленную посетителями информацию. Подробнее см. Правила.