Bagaimana ide itu muncul
Ide untuk membuat game seperti itu muncul tepat saat hackathon berlangsung. Formatnya diasumsikan ada satu hari kerja untuk pembangunan, yaitu 8 jam. Untuk membuat prototipe tepat waktu, saya memilih Android SDK. Mungkin mesin game akan lebih cocok, tapi saya tidak memahaminya.
Konsep mengendalikan dengan bantuan emosi disarankan oleh permainan lain: di sana, gerakan karakter dapat diatur dengan mengubah volume suara Anda. Mungkin seseorang telah menggunakan emosi untuk mengontrol permainan. Tapi saya tahu beberapa contoh seperti itu, jadi saya memilih format ini.
Waspadai video keras!
Menyiapkan lingkungan pengembangan
Kami hanya membutuhkan Android Studio di komputer. Jika tidak ada perangkat Android asli untuk dijalankan, Anda dapat menggunakan emulator dengan webcam diaktifkan .
Buat proyek dengan ML Kit
ML Kit adalah alat yang hebat untuk mengesankan juri hackathon: Anda menggunakan AI dalam prototipe! Secara umum, ini membantu untuk menyematkan solusi berdasarkan pembelajaran mesin ke dalam proyek, misalnya, fungsionalitas untuk mengidentifikasi objek dalam bingkai, terjemahan, dan pengenalan teks.
Penting bagi kami bahwa ML Kit memiliki API offline gratis untuk mengenali senyum dan mata terbuka atau tertutup.
Sebelumnya, untuk membuat proyek apa pun dengan ML Kit, Anda harus mendaftar terlebih dahulu di konsol Firebase . Langkah ini sekarang dapat dilewati untuk fungsionalitas offline.
Aplikasi Android
Hapus yang tidak perlu
Agar tidak menulis logika untuk bekerja dengan kamera dari awal, mari kita ambil sampel resmi dan hapus darinya apa yang tidak kita butuhkan.
Pertama, unduh contoh dan coba jalankan. Jelajahi mode Deteksi wajah: ini akan terlihat seperti pratinjau artikel.
Manifesto
Mari mulai mengedit AndroidManifest.xml. Hapus semua tag aktivitas kecuali yang pertama. Dan sebagai gantinya kami akan meletakkan CameraXLivePreviewActivity untuk segera memulai dari kamera. Dalam nilai atribut android: value, kita hanya menyisakan wajah untuk mengecualikan sumber daya yang tidak diperlukan dari APK.
<meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES"
android:value="face"/>
<activity
android:name=".CameraXLivePreviewActivity"
android:exported="true"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
Perbedaan langkah penuh.
Kamera
Mari menghemat waktu - kami tidak akan menghapus file yang tidak perlu, sebagai gantinya kami akan fokus pada elemen layar CameraXLivePreviewActivity.
- Pada baris 117, setel mode deteksi wajah:
private String selectedModel = FACE_DETECTION; - Pada baris 118, hidupkan kamera depan:
private int lensFacing = CameraSelector.LENS_FACING_FRONT; - Di akhir metode onCreate pada baris 198-199, sembunyikan pengaturan
findViewById( R.id.settings_button ).setVisibility( View.GONE ); findViewById( R.id.control ).setVisibility( View.GONE );
Kita bisa berhenti disini. Tetapi jika rendering FPS dan grid wajah secara visual mengganggu, Anda dapat mematikannya seperti ini:
- Di file VisionProcessorBase.java, hapus baris 213-215 untuk menyembunyikan FPS:
graphicOverlay.add( new InferenceInfoGraphic( graphicOverlay, currentLatencyMs, shouldShowFps ? framesPerSecond : null)); - Di file FaceDetectorProcessor.java, hapus baris 75β78 untuk menyembunyikan mesh wajah:
for (Face face : faces) { graphicOverlay.add(new FaceGraphic(graphicOverlay, face)); logExtrasForTesting(face); }
Perbedaan langkah penuh.
Mengenali emosi
Deteksi senyum dinonaktifkan secara default, tetapi mudah untuk memulainya. Bukan tanpa alasan kami mengambil kode contoh sebagai dasar! Mari pilih parameter yang kita butuhkan ke dalam kelas terpisah dan nyatakan antarmuka pendengar:
FaceDetectorProcessor.java
// FaceDetectorProcessor.java
public class FaceDetectorProcessor extends VisionProcessorBase<List<Face>> {
public static class Emotion {
public final float smileProbability;
public final float leftEyeOpenProbability;
public final float rightEyeOpenProbability;
public Emotion(float smileProbability, float leftEyeOpenProbability, float rightEyeOpenProbability) {
this.smileProbability = smileProbability;
this.leftEyeOpenProbability = leftEyeOpenProbability;
this.rightEyeOpenProbability = rightEyeOpenProbability;
}
}
public interface EmotionListener {
void onEmotion(Emotion emotion);
}
private EmotionListener listener;
public void setListener(EmotionListener listener) {
this.listener = listener;
}
@Override
protected void onSuccess(@NonNull List<Face> faces, @NonNull GraphicOverlay graphicOverlay) {
if (!faces.isEmpty() && listener != null) {
Face face = faces.get(0);
if (face.getSmilingProbability() != null &&
face.getLeftEyeOpenProbability() != null && face.getRightEyeOpenProbability() != null) {
listener.onEmotion(new Emotion(
face.getSmilingProbability(),
face.getLeftEyeOpenProbability(),
face.getRightEyeOpenProbability()
));
}
}
}
}Untuk mengaktifkan klasifikasi emosi, atur FaceDetectorProcessor di kelas CameraXLivePreviewActivity dan berlangganan untuk menerima status emosi. Kemudian kami mengubah probabilitas menjadi flag boolean. Untuk pengujian, mari tambahkan TextView ke layout, di mana kita akan menampilkan emosi melalui emotikon.
Perbedaan langkah penuh.
Bagilah dan mainkan
Karena kami membuat game, kami membutuhkan tempat untuk menggambar elemen. Mari kita asumsikan bahwa ini berjalan di telepon dalam mode potret. Jadi, mari bagi layar menjadi dua bagian: kamera di atas dan game di bawah.
Mengontrol karakter dengan senyuman itu sulit, dan selain itu, hanya ada sedikit waktu di hackathon untuk menerapkan mekanisme tingkat lanjut. Oleh karena itu, karakter kita akan mengumpulkan nishtyaks di jalan, baik di atas lapangan bermain, atau di bawah. Kami akan menambahkan tindakan dengan mata tertutup atau terbuka sebagai komplikasi dari permainan: jika Anda menangkap nishtyak dengan mata tertutup, poinnya menjadi dua kali lipat (
Jika Anda ingin menerapkan gameplay yang berbeda, maka saya dapat menyarankan beberapa opsi menarik:
- Guitar Hero / Just Dance - analog, di mana Anda perlu menunjukkan emosi tertentu pada musik;
- perlombaan dengan mengatasi rintangan, di mana Anda harus mencapai garis finish dalam waktu tertentu atau tanpa menabrak;
- penembak di mana pemain mengedipkan mata dan menembak musuh.
Kami akan menampilkan game dalam Tampilan Android khusus - di sana, dalam metode onDraw, kami akan menggambar karakter di Kanvas. Dalam prototipe pertama, kami akan membatasi diri pada primitif geometris.
Pemain
Karakter kita adalah persegi. Selama inisialisasi, kami akan menyetel ukuran dan posisinya ke kiri, karena akan ada di tempatnya. Posisi sumbu Y akan bergantung pada senyuman pemain. Semua nilai absolut akan dihitung relatif terhadap ukuran area permainan. Ini lebih mudah daripada memilih ukuran tertentu - dan pada perangkat baru kami akan mendapatkan tampilan yang dapat diterima.
private var playerSize = 0
private var playerRect = RectF()
// View
private fun initializePlayer() {
playerSize = height / 4
playerRect.left = playerSize / 2f
playerRect.right = playerRect.left + playerSize
}
//
private var flags: EmotionFlags
//
private fun movePlayer() {
playerRect.top = getObjectYTopForLine(playerSize, isTopLine = flags.isSmile).toFloat()
playerRect.bottom = playerRect.top + playerSize
}
// top size,
//
private fun getObjectYTopForLine(size: Int, isTopLine: Boolean): Int {
return if (isTopLine) {
width / 2 - width / 4 - size / 2
} else {
width / 2 + width / 4 - size / 2
}
}
// paint ,
private val playerPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL
color = Color.BLUE
}
// Canvas
private fun drawPlayer(canvas: Canvas) {
canvas.drawRect(playerRect, playerPaint)
}
kue
Karakter kita "berlari" dan mencoba menangkap kue untuk mencetak poin sebanyak mungkin. Kami menggunakan teknik standar dengan transisi ke sistem referensi relatif terhadap pemain: dia akan berdiri diam, dan kue akan terbang ke arahnya. Jika kuadrat kue berpotongan dengan kuadrat pemain, maka poinnya dihitung. Dan jika pada saat yang sama setidaknya satu mata pengguna ditutup - dua poin Β― \ _ (γ) _ / Β―
Juga di alam semesta kita hanya akan ada satu kue
//
private fun initializeCake() {
cakeSize = height / 8
moveCakeToStartPoint()
}
private fun moveCakeToStartPoint() {
//
cakeRect.left = width + width * Random.nextFloat()
cakeRect.right = cakeRect.left + cakeSize
//
val isTopLine = Random.nextBoolean()
cakeRect.top = getObjectYTopForLine(cakeSize, isTopLine).toFloat()
cakeRect.bottom = cakeRect.top + cakeSize
}
//
private fun moveCake() {
val currentTime = System.currentTimeMillis()
val deltaTime = currentTime - previousTimestamp
val deltaX = cakeSpeed * width * deltaTime
cakeRect.left -= deltaX
cakeRect.right = cakeRect.left + cakeSize
previousTimestamp = currentTime
}
// ,
private fun checkPlayerCaughtCake() {
if (RectF.intersects(playerRect, cakeRect)) {
score += if (flags.isLeftEyeOpen && flags.isRightEyeOpen) 1 else 2
moveCakeToStartPoint()
}
}
// ,
private fun checkCakeIsOutOfScreenStart() {
if (cakeRect.right < 0) {
moveCakeToStartPoint()
}
}
Apa yang terjadi
Mari buat tampilan poin menjadi sangat sederhana. Kami akan menampilkan nomor di tengah layar. Anda hanya perlu memperhitungkan tinggi teks dan menjorok ke atas untuk kecantikan.
private val scorePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.GREEN
textSize = context.resources.getDimension(R.dimen.score_size)
}
private var score: Int = 0
private var scorePoint = PointF()
private fun initializeScore() {
val bounds = Rect()
scorePaint.getTextBounds("0", 0, 1, bounds)
val scoreMargin = resources.getDimension(R.dimen.score_margin)
scorePoint = PointF(width / 2f, scoreMargin + bounds.height())
score = 0
}
Mari kita lihat mainan apa yang kita buat:
Perbedaan langkah penuh.
Grafonium
Agar tidak malu menampilkan game pada presentasi hackathon, yuk tambahkan sedikit grafonium!
Gambar-gambar
Kami melanjutkan dari fakta bahwa kami tidak dapat menggambar grafik yang mengesankan. Untungnya, ada situs dengan aset permainan gratis. Saya menyukai yang ini , meskipun sekarang tidak tersedia secara langsung karena alasan yang tidak saya ketahui.
Animasi
Kami menggambar di kanvas, yang berarti kami perlu menerapkan animasinya sendiri. Jika ada gambar dengan animasi maka akan mudah untuk diprogram. Kami memperkenalkan kelas untuk objek dengan mengubah gambar.
class AnimatedGameObject(
private val bitmaps: List<Bitmap>,
private val duration: Long
) {
fun getBitmap(timeInMillis: Long): Bitmap {
val mod = timeInMillis % duration
val index = (mod / duration.toFloat()) * bitmaps.size
return bitmaps[index.toInt()]
}
}
Untuk mendapatkan efek gerakan, latar belakang juga harus dianimasikan. Memiliki serangkaian bingkai latar belakang dalam memori adalah cerita yang terus berlanjut. Oleh karena itu, mari kita lakukan dengan lebih licik: kita akan menggambar satu gambar dengan pergeseran waktu. Garis besar ide:
Selesaikan perbedaan langkah.
Hasil akhir
Sulit untuk menyebutnya mahakarya, tapi tidak masalah untuk prototipe di malam hari. Kode dapat ditemukan di sini . Berjalan secara lokal tanpa kejahatan tambahan.
Sebagai kesimpulan, saya akan menambahkan bahwa Deteksi Wajah ML Kit dapat berguna untuk skenario lain.
Misalnya, untuk mengambil foto narsis yang sempurna dengan teman: Anda dapat menganalisis semua orang dalam bingkai dan memastikan bahwa semua orang tersenyum dan membuka mata. Mendeteksi banyak wajah dalam aliran video berhasil di luar kotak, jadi tugasnya tidak sulit.
Dengan menggunakan pengenalan kontur wajah dari modul Deteksi Wajah, dimungkinkan untuk mereplikasi topeng yang sekarang populer di hampir semua aplikasi kamera. Dan jika Anda menambahkan interaktivitas - melalui definisi senyuman dan kedipan - maka menggunakannya akan sangat menyenangkan.
Fungsi ini - pembentukan wajah - dapat digunakan lebih dari sekadar hiburan. Mereka yang telah mencoba memotong foto untuk dokumen sendiri akan menghargainya. Kami mengambil kontur wajah, secara otomatis memotong foto dengan rasio aspek yang diinginkan dan posisi kepala yang benar. Sensor giroskop akan membantu menentukan sudut pengambilan gambar yang benar.