Wie ist die Idee entstanden?
Die Idee, ein solches Spiel zu entwickeln, kam direkt während des Hackathons. Das Format ging davon aus, dass es einen Arbeitstag für die Entwicklung gab, dh 8 Stunden. Um rechtzeitig einen Prototyp zu erstellen, habe ich mich für das Android SDK entschieden. Vielleicht wären Game Engines besser geeignet, aber ich verstehe sie nicht.
Das Konzept der Steuerung mit Hilfe von Emotionen wurde von einem anderen Spiel vorgeschlagen: Dort können die Bewegungen des Charakters durch Ändern der Lautstärke Ihrer Stimme eingestellt werden. Vielleicht hat jemand bereits Emotionen in der Spielsteuerung eingesetzt. Aber ich kenne nur wenige solcher Beispiele, deshalb habe ich mich für dieses Format entschieden.
Vorsicht vor lauten Videos!
Einrichten der Entwicklungsumgebung
Wir brauchen nur Android Studio auf dem Computer. Wenn kein echtes Android- Gerät ausgeführt werden kann, können Sie einen Emulator mit aktivierter Webcam verwenden .
Erstellen Sie ein Projekt mit ML Kit
ML Kit ist ein großartiges Werkzeug, um die Hackathon-Jury zu beeindrucken: Sie verwenden KI in einem Prototyp! Im Allgemeinen hilft es, Lösungen, die auf maschinellem Lernen basieren, in Projekte einzubetten, z. B. Funktionen zum Bestimmen von Objekten in einem Rahmen, Übersetzung und Texterkennung.
Für uns ist es wichtig, dass ML Kit über eine kostenlose Offline-API zum Erkennen von Lächeln und offenen oder geschlossenen Augen verfügt.
Zuvor mussten Sie sich zunächst in der Firebase-Konsole registrieren, um ein Projekt mit ML Kit zu erstellen . Dieser Schritt kann jetzt für die Offline-Funktionalität übersprungen werden.
Android App
Unnötiges entfernen
Um die Logik für die Arbeit mit der Kamera nicht von Grund auf neu zu schreiben, nehmen wir das offizielle Beispiel und entfernen das, was wir nicht benötigen.
Laden Sie zuerst das Beispiel herunter und versuchen Sie es auszuführen. Entdecken Sie den Gesichtserkennungsmodus: Er sieht aus wie die Artikelvorschau.
Manifest
Beginnen wir mit der Bearbeitung von AndroidManifest.xml. Entfernen Sie alle Aktivitäts-Tags mit Ausnahme des ersten. Und an seine Stelle setzen wir CameraXLivePreviewActivity, um sofort von der Kamera aus zu starten. Im Wert des Attributs android: value lassen wir nur Gesicht, um unnötige Ressourcen von der APK auszuschließen.
<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>
Vollschrittdiff.
Kamera
Sparen wir Zeit - wir löschen keine unnötigen Dateien, sondern konzentrieren uns auf die Elemente des CameraXLivePreviewActivity-Bildschirms.
- Stellen Sie in Zeile 117 den Gesichtserkennungsmodus ein:
private String selectedModel = FACE_DETECTION; - Schalten Sie in Zeile 118 die Frontkamera ein:
private int lensFacing = CameraSelector.LENS_FACING_FRONT; - Blenden Sie am Ende der onCreate-Methode in den Zeilen 198-199 die Einstellungen aus
findViewById( R.id.settings_button ).setVisibility( View.GONE ); findViewById( R.id.control ).setVisibility( View.GONE );
Wir können hier aufhören. Wenn FPS-Rendering und Gesichtsraster jedoch visuell ablenken, können Sie sie folgendermaßen deaktivieren:
- Löschen Sie in der Datei VisionProcessorBase.java die Zeilen 213-215, um die FPS auszublenden:
graphicOverlay.add( new InferenceInfoGraphic( graphicOverlay, currentLatencyMs, shouldShowFps ? framesPerSecond : null)); - Löschen Sie in der Datei FaceDetectorProcessor.java die Zeilen 75–78, um das Gesichtsnetz auszublenden:
for (Face face : faces) { graphicOverlay.add(new FaceGraphic(graphicOverlay, face)); logExtrasForTesting(face); }
Vollschrittdiff.
Emotionen erkennen
Die Lächelnerkennung ist standardmäßig deaktiviert, der Einstieg ist jedoch einfach. Nicht umsonst haben wir den Beispielcode zugrunde gelegt! Lassen Sie uns die benötigten Parameter in einer separaten Klasse auswählen und die Listener-Schnittstelle deklarieren:
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()
));
}
}
}
}Um die Emotionsklassifizierung zu aktivieren, richten Sie den FaceDetectorProcessor in der CameraXLivePreviewActivity-Klasse ein und abonnieren Sie, um den Emotionsstatus zu erhalten. Dann transformieren wir die Wahrscheinlichkeiten in boolesche Flags. Fügen Sie zum Testen dem Layout eine Textansicht hinzu, in der wir Emotionen durch Emoticons anzeigen.
Vollschrittdiff.
Teilen und spielen
Da wir ein Spiel machen, brauchen wir einen Ort, an dem wir die Elemente zeichnen können. Nehmen wir an, dass es auf dem Telefon im Hochformat ausgeführt wird. Teilen wir den Bildschirm also in zwei Teile: die Kamera oben und das Spiel unten.
Es ist schwierig, einen Charakter mit einem Lächeln zu kontrollieren, und außerdem bleibt beim Hackathon wenig Zeit, um fortschrittliche Mechanik zu implementieren. Daher wird unser Charakter auf dem Weg Nishtyaks sammeln, entweder oben auf dem Spielfeld oder unten. Wir werden Aktionen mit geschlossenen oder offenen Augen als Komplikation des Spiels hinzufügen: Wenn Sie einen Nishtyak mit einem geschlossenen Auge fangen, werden die Punkte verdoppelt (
Wenn Sie ein anderes Gameplay implementieren möchten, kann ich einige interessante Optionen vorschlagen:
- Guitar Hero / Just Dance - analog, wo Sie der Musik eine bestimmte Emotion zeigen müssen;
- ein Rennen mit Überwindung von Hindernissen, bei dem Sie in einer bestimmten Zeit oder ohne Sturz die Ziellinie erreichen müssen;
- Schütze, wo der Spieler zwinkert und den Feind erschießt.
Wir werden das Spiel in einer benutzerdefinierten Android-Ansicht anzeigen - dort werden wir in der onDraw-Methode einen Charakter auf Canvas zeichnen. Im ersten Prototyp beschränken wir uns auf geometrische Grundelemente.
Spieler
Unser Charakter ist ein Quadrat. Während der Initialisierung werden Größe und Position links eingestellt, da sie vorhanden sind. Die Position der Y-Achse hängt vom Lächeln des Spielers ab. Alle absoluten Werte werden relativ zur Größe des Spielbereichs berechnet. Es ist einfacher als die Auswahl bestimmter Größen - und wir erhalten ein akzeptables Aussehen für neue Geräte.
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)
}
Kuchen
Unser Charakter "rennt" und versucht Kuchen zu fangen, um so viele Punkte wie möglich zu erzielen. Wir verwenden die Standardtechnik beim Übergang zum Referenzsystem relativ zum Spieler: Er wird still stehen und die Kuchen werden auf ihn zufliegen. Wenn sich das Quadrat des Kuchens mit dem Quadrat des Spielers schneidet, wird der Punkt gezählt. Und wenn gleichzeitig mindestens ein Auge des Benutzers geschlossen ist - zwei Punkte ¯ \ _ (ツ) _ / ¯
Auch in unserem Universum wird es nur einen
//
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()
}
}
Was ist passiert
Machen wir die Anzeige von Punkten sehr einfach. Wir werden die Nummer in der Mitte des Bildschirms anzeigen. Sie müssen nur die Höhe des Textes berücksichtigen und die Oberseite für Schönheit einrücken.
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
}
Mal sehen, was für ein Spielzeug wir gemacht haben:
Vollschrittdiff.
Graphonium
Um es nicht zu schämen, das Spiel bei der Präsentation des Hackathons zu zeigen, fügen wir ein wenig Grafonium hinzu!
Bilder
Wir gehen davon aus, dass wir keine beeindruckenden Grafiken zeichnen können. Glücklicherweise gibt es Websites mit kostenlosen Gaming-Assets. Ich mochte dieses , obwohl es jetzt aus einem mir unbekannten Grund nicht direkt verfügbar ist.
Animation
Wir greifen auf Canvas zurück, was bedeutet, dass wir die Animation selbst implementieren müssen. Wenn es Bilder mit Animation gibt, ist es einfach zu programmieren. Wir führen eine Klasse für ein Objekt mit sich ändernden Bildern ein.
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()]
}
}
Um den Bewegungseffekt zu erzielen, muss auch der Hintergrund animiert werden. Eine Reihe von Hintergrundbildern im Speicher zu haben, ist eine Overhead-Geschichte. Machen wir es deshalb schlauer: Wir zeichnen ein Bild mit einer Zeitverschiebung. Ideenübersicht:
Vollständiger Schrittunterschied.
Endergebnis
Es ist schwer, es als Meisterwerk zu bezeichnen, aber für einen Prototyp über Nacht ist es in Ordnung. Den Code finden Sie hier . Läuft lokal ohne zusätzliche Spielereien.
Abschließend möchte ich hinzufügen, dass die ML Kit-Gesichtserkennung für andere Szenarien nützlich sein kann.
Zum Beispiel, um perfekte Selfies mit Freunden zu machen: Sie können alle Personen im Rahmen analysieren und sicherstellen, dass alle lächelten und die Augen öffneten. Das Erkennen mehrerer Gesichter in einem Videostream funktioniert sofort, sodass die Aufgabe nicht schwierig ist.
Mithilfe der Gesichtskonturerkennung aus dem Gesichtserkennungsmodul können Masken repliziert werden, die heute in fast allen Kameraanwendungen beliebt sind. Und wenn Sie interaktiv hinzufügen - durch die Definition eines Lächelns und eines Augenzwinkerns - dann macht die Verwendung doppelt Spaß.
Diese Funktionalität - Gesichtskonturierung - kann nicht nur zur Unterhaltung verwendet werden. Diejenigen, die versucht haben, ein Foto für Dokumente selbst auszuschneiden, werden es zu schätzen wissen. Wir nehmen die Gesichtskontur, schneiden das Foto automatisch mit dem gewünschten Seitenverhältnis und der richtigen Kopfposition aus. Der Gyroskopsensor hilft bei der Bestimmung des richtigen Aufnahmewinkels.