مدیریت تغییرات ورودی

کروم‌بوک‌ها گزینه‌های ورودی مختلفی را در اختیار کاربران قرار می‌دهند: صفحه‌کلید، ماوس، ترک‌پد، صفحه‌های لمسی، قلم، MIDI و دسته‌های بازی/گیم‌پد آبی. این یعنی یک دستگاه می‌تواند به یک ایستگاه دی‌جی، بوم نقاشی یک هنرمند یا پلتفرم مورد علاقه یک گیمر برای بازی‌های استریم AAA تبدیل شود.

به عنوان یک توسعه‌دهنده، این به شما این فرصت را می‌دهد که تجربیات برنامه‌ای متنوع و هیجان‌انگیزی را برای کاربران خود ایجاد کنید که از دستگاه‌های ورودی که از قبل در دسترس دارند - از یک صفحه‌کلید متصل گرفته تا یک قلم و یک کنترلر بازی Stadia - بهره می‌برند. با این حال، همه این امکانات همچنین مستلزم آن است که شما در مورد رابط کاربری خود فکر کنید تا تجربه برنامه شما روان و منطقی باشد. این امر به ویژه در صورتی صادق است که برنامه یا بازی شما با در نظر گرفتن تلفن‌های همراه طراحی شده باشد. به عنوان مثال، اگر بازی شما دارای یک جوی‌استیک لمسی روی صفحه برای تلفن‌ها است، احتمالاً می‌خواهید آن را وقتی کاربر با صفحه‌کلید بازی می‌کند، پنهان کنید.

در این صفحه، نکات اصلی که باید هنگام فکر کردن به منابع ورودی متعدد و استراتژی‌های رسیدگی به آنها در نظر داشته باشید را خواهید یافت.

کشف روش‌های ورودی پشتیبانی‌شده توسط کاربر

در حالت ایده‌آل، برنامه شما به طور یکپارچه به هر ورودی که کاربر انتخاب می‌کند پاسخ می‌دهد. اغلب این کار ساده است و نیازی به ارائه اطلاعات اضافی به کاربر ندارد. به عنوان مثال، یک دکمه باید در صورتی که کاربر با ماوس، ترک‌پد، صفحه لمسی، قلم و غیره روی آن کلیک کند، کار کند و نیازی نیست به کاربر بگویید که می‌تواند از این دستگاه‌های مختلف برای فعال کردن دکمه استفاده کند.

با این حال، موقعیت‌هایی وجود دارد که روش ورودی می‌تواند تجربه کاربر را بهبود بخشد و ممکن است منطقی باشد که به آنها اطلاع دهید که برنامه شما از آن پشتیبانی می‌کند. چند مثال:

  • یک برنامه پخش رسانه ممکن است از میانبرهای صفحه کلید مفید زیادی پشتیبانی کند که کاربر ممکن است نتواند به راحتی آنها را حدس بزند.
  • اگر یک اپلیکیشن دی‌جی ساخته‌اید، ممکن است کاربر در ابتدا از صفحه لمسی استفاده کند و متوجه نشود که شما به او اجازه داده‌اید از کیبورد/ترک‌پد خود برای دسترسی لمسی به برخی از ویژگی‌ها استفاده کند. به همین ترتیب، ممکن است متوجه نشود که شما از تعدادی کنترلر دی‌جی MIDI پشتیبانی می‌کنید و تشویق او به بررسی سخت‌افزارهای پشتیبانی‌شده می‌تواند تجربه دی‌جی کردن واقعی‌تری را رقم بزند.
  • ممکن است بازی شما با صفحه لمسی و کیبورد/ماوس عالی باشد، اما کاربران ممکن است متوجه نشوند که از تعدادی دسته بازی بلوتوثی نیز پشتیبانی می‌کند. اتصال یکی از این دسته‌ها می‌تواند رضایت و تعامل کاربر را افزایش دهد.

شما می‌توانید به کاربران کمک کنید تا گزینه‌های ورودی را با پیام‌رسانی در زمان مناسب کشف کنند. پیاده‌سازی این روش برای هر برنامه متفاوت خواهد بود. برخی از مثال‌ها عبارتند از:

  • پاپ‌آپ‌های ویژه یا پاپ‌آپ‌های ویژه روز
  • گزینه‌های پیکربندی در پنل‌های تنظیمات می‌توانند به صورت غیرفعال به کاربران نشان دهند که پشتیبانی وجود دارد. به عنوان مثال، تب «کنترلر بازی» در پنل تنظیمات یک بازی نشان می‌دهد که از کنترل‌کننده‌ها پشتیبانی می‌شود.
  • پیام‌های متنی. به عنوان مثال، اگر یک صفحه کلید فیزیکی را تشخیص دهید و متوجه شوید که کاربر با استفاده از ماوس یا صفحه لمسی روی عملی کلیک می‌کند، ممکن است بخواهید یک اشاره مفید نشان دهید که پیشنهاد یک میانبر صفحه کلید را می‌دهد.
  • فهرست میانبرهای صفحه‌کلید. هنگامی که یک صفحه‌کلید فیزیکی شناسایی می‌شود، ارائه نشانه‌ای در رابط کاربری مبنی بر نحوه نمایش فهرست میانبرهای صفحه‌کلید موجود، دو هدف را دنبال می‌کند: تبلیغ وجود پشتیبانی از صفحه‌کلید و ارائه راهی آسان برای کاربران جهت مشاهده و به خاطر سپردن میانبرهای پشتیبانی‌شده.

برچسب‌گذاری/طرح‌بندی رابط کاربری برای تنوع ورودی

در حالت ایده‌آل، اگر از دستگاه ورودی متفاوتی استفاده شود، رابط بصری شما نباید نیاز به تغییر زیادی داشته باشد، همه ورودی‌های ممکن باید «درست کار کنند». با این حال، استثنائات مهمی وجود دارد. دو مورد از موارد اصلی، رابط کاربری مخصوص لمس و اعلان‌های روی صفحه هستند.

رابط کاربری مخصوص لمس

هر زمان که برنامه شما دارای عناصر رابط کاربری مخصوص لمس است، مثلاً یک جوی‌استیک روی صفحه در یک بازی، باید در نظر بگیرید که تجربه کاربری در صورت عدم استفاده از لمس چگونه خواهد بود. در برخی از بازی‌های موبایل، بخش قابل توجهی از صفحه نمایش توسط کنترل‌هایی اشغال می‌شود که برای بازی مبتنی بر لمس ضروری هستند، اما اگر کاربر از گیم‌پد یا صفحه کلید برای بازی استفاده کند، غیرضروری هستند. برنامه یا بازی شما باید منطقی را برای تشخیص اینکه کدام روش ورودی به طور فعال استفاده می‌شود و تنظیم رابط کاربری بر اساس آن ارائه دهد. برای مثال‌هایی از نحوه انجام این کار، به بخش پیاده‌سازی در زیر مراجعه کنید.

رابط کاربری بازی‌های اتومبیل‌رانی - یکی با کنترل‌های روی صفحه و دیگری با صفحه‌کلید

دستورات روی صفحه

ممکن است برنامه شما پیام‌های مفیدی روی صفحه نمایش به کاربران ارائه دهد. مراقب باشید که این پیام‌ها به دستگاه ورودی وابسته نباشند. برای مثال:

  • کشیدن انگشت به…
  • برای بستن، روی هر قسمتی ضربه بزنید
  • بزرگنمایی با دو انگشت
  • برای… «X» را فشار دهید.
  • برای فعال کردن، فشار طولانی دهید

برخی از برنامه‌ها ممکن است بتوانند متن‌بندی خود را طوری تنظیم کنند که مستقل از ورودی باشد. این روش در جایی که منطقی باشد ترجیح داده می‌شود، اما در بسیاری از موارد، خاص بودن مهم است و ممکن است لازم باشد بسته به روش ورودی مورد استفاده، پیام‌های متفاوتی نشان دهید، به خصوص در حالت‌های آموزشی مانند اولین اجرای برنامه‌ها.

ملاحظات چندزبانه

اگر برنامه شما از چندین زبان پشتیبانی می‌کند، باید معماری رشته خود را به طور کامل بررسی کنید. برای مثال، اگر از ۳ حالت ورودی و ۵ زبان پشتیبانی می‌کنید، این می‌تواند به معنای ۱۵ نسخه مختلف از هر پیام رابط کاربری باشد. این امر باعث افزایش میزان کار مورد نیاز برای افزودن ویژگی‌های جدید و افزایش احتمال خطاهای املایی می‌شود.

یک رویکرد این است که اکشن‌های رابط کاربری را به عنوان مجموعه‌ای جداگانه از رشته‌ها در نظر بگیرید. برای مثال، اگر اکشن «بستن» را به عنوان متغیر رشته‌ای جداگانه با انواع ورودی خاص مانند: «برای بستن، روی هر جایی ضربه بزنید»، «برای بستن، 'Esc' را فشار دهید»، «برای بستن، روی هر جایی کلیک کنید»، «برای بستن، هر دکمه‌ای را فشار دهید»، تعریف کنید، آنگاه تمام پیام‌های رابط کاربری شما که باید به کاربر بگویند چگونه چیزی را ببندد، می‌توانند از این متغیر رشته‌ای «بستن» استفاده کنند. هنگامی که روش ورودی تغییر می‌کند، به سادگی مقدار این متغیر واحد را تغییر دهید.

صفحه کلید نرم افزاری / ورودی IME

به یاد داشته باشید که اگر کاربر از برنامه‌ای بدون صفحه‌کلید فیزیکی استفاده می‌کند، ورودی متن می‌تواند از طریق صفحه‌کلید روی صفحه نمایش انجام شود. حتماً بررسی کنید که عناصر رابط کاربری ضروری هنگام نمایش صفحه‌کلید روی صفحه نمایش مسدود نشوند. برای اطلاعات بیشتر به مستندات قابلیت مشاهده IME اندروید مراجعه کنید.

پیاده‌سازی

در بیشتر موارد، برنامه‌ها یا بازی‌ها باید به تمام ورودی‌های معتبر، صرف نظر از آنچه روی صفحه نمایش داده می‌شود، به درستی پاسخ دهند. اگر کاربری به مدت 10 دقیقه از صفحه لمسی استفاده می‌کند، اما ناگهان به استفاده از صفحه کلید روی می‌آورد، ورودی صفحه کلید باید صرف نظر از دستورات یا کنترل‌های بصری روی صفحه کار کند. به عبارت دیگر، عملکرد باید بر تصاویر/متن اولویت داشته باشد. این به تضمین قابل استفاده بودن برنامه/بازی شما حتی اگر منطق تشخیص ورودی شما خطا داشته باشد یا وضعیت غیرمنتظره‌ای پیش بیاید، کمک می‌کند.

مرحله بعدی نمایش رابط کاربری صحیح برای روش ورودی فعلی است. چگونه این را به طور دقیق تشخیص می‌دهید؟ اگر کاربران هنگام استفاده از برنامه شما بین روش‌های ورودی مختلف جابجا شوند چه اتفاقی می‌افتد؟ اگر آنها همزمان از چندین روش استفاده کنند چه؟

ماشین وضعیت اولویت‌بندی‌شده بر اساس رویدادهای دریافتی

یک رویکرد، پیگیری «وضعیت ورودی فعال» فعلی - که نشان‌دهنده‌ی درخواست‌های ورودی نمایش داده شده روی صفحه به کاربر است - با پیگیری رویدادهای ورودی واقعی که توسط برنامه دریافت می‌شوند و انتقال بین حالت‌ها با استفاده از منطق اولویت‌بندی شده است.

اولویت‌بندی کنید

چرا باید حالت‌های ورودی را اولویت‌بندی کرد؟ کاربران به روش‌های مختلفی با دستگاه‌های خود تعامل دارند و برنامه شما باید از انتخاب آنها پشتیبانی کند. به عنوان مثال، اگر کاربری تصمیم بگیرد همزمان از صفحه لمسی و ماوس بلوتوث استفاده کند، باید از این انتخاب پشتیبانی شود. اما کدام دستورات و کنترل‌های ورودی روی صفحه را باید نمایش دهید؟ ماوس یا لمس؟

تعریف هر مجموعه از دستورات به عنوان یک «وضعیت ورودی» و سپس اولویت‌بندی آنها می‌تواند به تصمیم‌گیری در این مورد به روشی منسجم کمک کند.

رویدادهای ورودی دریافتی، وضعیت را تعیین می‌کنند

چرا فقط بر اساس رویدادهای ورودی دریافتی عمل کنیم؟ ممکن است فکر کنید که می‌توانید اتصالات بلوتوث را پیگیری کنید تا مشخص شود که آیا یک کنترلر بلوتوث متصل شده است یا اتصالات USB را برای دستگاه‌های USB مشاهده کنید. این روش به دو دلیل اصلی توصیه نمی‌شود.

اول از همه، اطلاعاتی که می‌توانید بر اساس متغیرهای API در مورد دستگاه‌های متصل حدس بزنید، ثابت نیست و تعداد دستگاه‌های بلوتوث/سخت‌افزار/قلم به طور مداوم در حال افزایش است.

دلیل دوم برای استفاده از رویدادهای دریافتی به جای وضعیت اتصال این است که کاربران ممکن است ماوس، دسته بلوتوث، دسته MIDI و غیره را متصل کرده باشند اما به طور فعال از آن برای تعامل با برنامه یا بازی شما استفاده نکنند.

با پاسخ دادن به رویدادهای ورودی که به طور فعال توسط برنامه شما دریافت شده‌اند، اطمینان حاصل می‌کنید که به اقدامات واقعی کاربران خود در زمان واقعی پاسخ می‌دهید و سعی نمی‌کنید با اطلاعات ناقص، نیت آنها را حدس بزنید.

مثال: بازی با پشتیبانی از لمس، صفحه کلید/ماوس و دسته بازی

تصور کنید که یک بازی مسابقه‌ای ماشین برای گوشی‌های لمسی ساخته‌اید. بازیکنان می‌توانند سرعت خود را افزایش یا کاهش دهند، به راست یا چپ بپیچند یا از نیترو برای افزایش سرعت استفاده کنند.

رابط کاربری لمسی فعلی شامل یک جوی‌استیک روی صفحه در پایین سمت چپ صفحه برای کنترل سرعت و جهت، و یک دکمه در پایین سمت راست برای نیترو است. کاربر می‌تواند کپسول‌های نیترو را در مسیر جمع‌آوری کند و وقتی نوار نیترو در پایین صفحه پر شد، پیامی بالای دکمه ظاهر می‌شود که می‌گوید «برای نیترو فشار دهید!». اگر این اولین بازی کاربر باشد یا مدتی هیچ ورودی دریافت نکند، متن «آموزشی» بالای جوی‌استیک ظاهر می‌شود که به کاربر نحوه حرکت ماشین را نشان می‌دهد.

اگر می‌خواهید پشتیبانی از کیبورد و دسته بازی بلوتوثی را اضافه کنید، از کجا شروع می‌کنید؟

بازی ماشین سواری با کنترل لمسی

حالت‌های ورودی

با شناسایی تمام حالت‌های ورودی که بازی شما ممکن است در آنها اجرا شود شروع کنید و سپس تمام پارامترهایی را که می‌خواهید در هر حالت تغییر دهید، فهرست کنید.

لمس کردن صفحه کلید/ماوس کنترل کننده بازی

واکنش نشان می‌دهد

تمام ورودی‌ها

تمام ورودی‌ها

تمام ورودی‌ها

کنترل‌های روی صفحه

- جوی‌استیک روی صفحه نمایش
- دکمه نیترو

- بدون جوی‌استیک
- دکمه نیترو ندارد

- بدون جوی‌استیک
- دکمه نیترو ندارد

متن

برای نیترو ضربه بزنید!

برای نیترو، «N» را فشار دهید!

برای نیترو، «A» را فشار دهید!

آموزش

تصویر جوی‌استیک برای سرعت/جهت

تصویر کلیدهای جهت‌نما و WASD برای سرعت/جهت

تصویر دسته بازی برای سرعت/جهت

وضعیت «ورودی فعال» را پیگیری کنید و سپس رابط کاربری را در صورت نیاز، بر اساس آن وضعیت، به‌روزرسانی کنید.

نکته: به یاد داشته باشید: بازی/برنامه شما باید به تمام متدهای ورودی، صرف نظر از وضعیت، پاسخ دهد. برای مثال، اگر کاربر با صفحه لمسی در حال رانندگی است، اما N را روی صفحه کلید فشار می‌دهد - عمل نیترو باید فعال شود.

تغییرات وضعیت اولویت‌بندی‌شده

برخی از کاربران ممکن است همزمان از صفحه لمسی و ورودی صفحه کلید استفاده کنند. برخی ممکن است استفاده از بازی/برنامه شما را روی مبل در حالت تبلت شروع کنند و سپس به استفاده از صفحه کلید روی میز تغییر دهند. برخی دیگر ممکن است دسته‌های بازی را در اواسط بازی متصل یا جدا کنند.

در حالت ایده‌آل، شما نمی‌خواهید عناصر رابط کاربری نادرستی داشته باشید - مانند اینکه به کاربر بگویید هنگام استفاده از صفحه لمسی، کلید n را فشار دهد. در عین حال، در مورد کاربرانی که همزمان از چندین دستگاه ورودی مانند صفحه لمسی و صفحه کلید استفاده می‌کنند، شما نمی‌خواهید رابط کاربری دائماً بین دو حالت در حال تغییر باشد.

یک راه برای مدیریت این مشکل، تعیین اولویت‌های نوع ورودی و ایجاد تأخیر بین تغییرات حالت است. برای بازی ماشین بالا، همیشه می‌خواهید مطمئن شوید که جوی‌استیک روی صفحه نمایش در هر زمان که رویدادهای لمسی روی صفحه نمایش دریافت می‌شوند، قابل مشاهده باشد، در غیر این صورت ممکن است بازی برای کاربر غیرقابل استفاده به نظر برسد. این کار باعث می‌شود صفحه لمسی به دستگاهی با بالاترین اولویت تبدیل شود.

اگر رویدادهای صفحه کلید و صفحه لمسی به طور همزمان دریافت می‌شدند، بازی باید در حالت صفحه لمسی باقی می‌ماند - هرچند همچنان به ورودی صفحه کلید واکنش نشان می‌داد. اگر پس از ۵ ثانیه هیچ ورودی صفحه لمسی دریافت نمی‌شد و رویدادهای صفحه کلید همچنان دریافت می‌شدند، شاید کنترل‌های روی صفحه محو می‌شدند و بازی به حالت صفحه کلید منتقل می‌شد.

ورودی کنترلر بازی از الگوی مشابهی پیروی می‌کند: وضعیت رابط کاربری کنترلر نسبت به کیبورد/ماوس و لمس، اولویت پایین‌تری خواهد داشت و فقط در صورتی ظاهر می‌شود که ورودی کنترلر بازی و نه سایر اشکال ورودی دریافت شود. بازی همیشه به ورودی کنترلر پاسخ می‌دهد.

در زیر یک نمودار حالت و یک جدول انتقال برای این مثال آورده شده است. این ایده را با برنامه یا بازی خود تطبیق دهید.

دستگاه حالت اولویت‌بندی‌شده - صفحه لمسی، صفحه‌کلید/ماوس، دسته بازی

صفحه لمسی شماره ۱ کیبورد شماره ۲ #3 گیم‌پد

به شماره ۱ بروید

ناموجود

- ورودی لمسی دریافت شد
- فوراً به حالت ورودی لمسی بروید

- ورودی لمسی دریافت شد
- فوراً به حالت ورودی لمسی بروید

به شماره ۲ بروید

- بدون لمس برای 5s
- ورودی صفحه کلید دریافت شد
- انتقال به حالت ورودی صفحه کلید

ناموجود

- ورودی صفحه کلید دریافت شد
(بلافاصله به حالت ورودی صفحه کلید بروید)

به شماره ۳ بروید

- بدون لمس برای 5s
- نداشتن کیبورد برای مدل 5s
- ورودی گیم‌پد دریافت شد
- به حالت ورودی گیم‌پد بروید

- نداشتن کیبورد برای مدل 5s
- ورودی گیم‌پد دریافت شد
- به حالت ورودی گیم‌پد بروید

ناموجود

نکته: توجه کنید که چگونه اولویت‌بندی به روشن شدن اینکه کدام نوع ورودی باید غالب باشد کمک می‌کند. وضعیت ورودی فوراً در اولویت «بالاتر» می‌رود:

۳. دسته بازی -> ۲. صفحه کلید -> ۱. لمسی

به محض اینکه دستگاهی با اولویت بالاتر استفاده شود، اما به آرامی در اولویت «پایین‌تر» حرکت می‌کند، تنها پس از یک دوره تأخیر و تنها در صورتی که دستگاه با اولویت پایین‌تر به طور فعال در حال استفاده باشد.

رویدادهای ورودی

در اینجا چند نمونه کد از نحوه تشخیص رویدادهای ورودی از انواع مختلف دستگاه‌های ورودی با استفاده از APIهای استاندارد اندروید ارائه شده است. از این رویدادها برای هدایت ماشین حالت خود، مانند بالا، استفاده کنید. شما باید مفهوم کلی را با انواع رویدادهای ورودی مورد انتظار و برنامه یا بازی خود تطبیق دهید.

دکمه‌های کیبورد و کنترلر

// Drive the state machine based on the received input type
// onKeyDown drives the state machine, but does not trigger game actions
// Both keyboard and game controller events come through as key events
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    if (event != null) {
        // Check input source
        val outputMessage = when (event.source) {
            SOURCE_KEYBOARD -> {
                MyStateMachine.KeyboardEventReceived()
                "Keyboard event"
            }
            SOURCE_GAMEPAD -> {
                MyStateMachine.ControllerEventReceived()
                "Game controller event"
            }
            else -> "Other key event: ${event.source}"
        }
        // Do something based on source type
        findViewById(R.id.text_message).text = outputMessage
    }
    // Pass event up to system
    return super.onKeyDown(keyCode, event)
}
// Trigger game events based on key release
// Both keyboard and game controller events come through as key events
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
   when(keyCode) {
       KeyEvent.KEYCODE_N -> {
           MyStateMachine.keyboardEventReceived()
           engageNitro()
           return true // event handled here, return true
       }
   }
   // If event not handled, pass up to system
   return super.onKeyUp(keyCode, event)
}

نکته: برای KeyEvents، می‌توانید از onKeyDown() یا onKeyUp() استفاده کنید. در اینجا، onKeyDown() برای کنترل ماشین حالت و onKeyUp() برای فعال کردن رویدادهای بازی استفاده می‌شود.

اگر کاربر دکمه‌ای را فشار داده و نگه دارد، onKeyUp() فقط یک بار در هر فشار کلید فعال می‌شود در حالی که onKeyDown() چندین بار فراخوانی می‌شود. اگر می‌خواهید به فشار پایین واکنش نشان دهید، باید رویدادهای بازی را در onKeyDown() مدیریت کنید و منطقی را برای رسیدگی به رویدادهای مکرر پیاده‌سازی کنید. برای اطلاعات بیشتر به مستندات مدیریت عملکردهای صفحه کلید مراجعه کنید.

لمسی و قلم

// Touch and stylus events come through as touch events
override fun onTouchEvent(event: MotionEvent?): Boolean {
   if (event != null) {
       // Get tool type
       val pointerIndex = event.action and ACTION_POINTER_INDEX_MASK shr ACTION_POINTER_INDEX_SHIFT
       val toolType = event.getToolType(pointerIndex)

       // Check tool type
       val outputMessage = when (toolType) {
           TOOL_TYPE_FINGER -> {
               MyStateMachine.TouchEventReceived()
               "Touch event"
           }
           TOOL_TYPE_STYLUS -> {
                MyStateMachine.StylusEventReceived()
               "Stylus event"
           }
           else -> "Other touch event: ${toolType}"
       }

       // Do something based on tool type, return true if event handled
       findViewById(R.id.text_message).text = outputMessage
   }
   // If event not handled, pass up to system
   return super.onGenericMotionEvent(event)
}

ماوس و جوی‌استیک

// Mouse and joystick events come through as generic events
override fun onGenericMotionEvent(event: MotionEvent?): Boolean {
   if (event != null) {
       // Check input source
       val outputMessage = when (event.source) {
           SOURCE_JOYSTICK -> {
                MyStateMachine.ControllerEventReceived()
               "Controller event"
           }
           SOURCE_MOUSE -> {
                MyStateMachine.MouseEventReceived()
               "Mouse event"
           }
           else -> "Other generic event: ${event.source}"
       }
       // Do something based on source type, return true if event handled
       findViewById(R.id.text_message).text = outputMessage
   }
   // If event not handled, pass up to system
   return super.onGenericMotionEvent(event)
}