制作微笑控制游戏

你好!我叫Ivan Shafran,我最近以Android开发人员的身份加入VK视频团队。我参与产品应用程序和SDK的创建。我会不时访问黑客马拉松,您可以在其中实施任何疯狂的想法。今天,我将告诉您如何在几个小时内制作出具有非常规控件的手机游戏原型:角色将对微笑和眨眼产生反应。







这个主意是如何产生的



创建此类游戏的想法恰好是在黑客马拉松期间提出的。该格式假定开发需要一个工作日,即8个小时。为了及时制作原型,我选择了Android SDK。也许游戏引擎会更适合,但我不理解它们。



借助另一种游戏提出了借助情感进行控制的概念:在那里,可以通过改变声音的音量来设置角色的动作。也许有人已经在游戏控制中运用了情绪。但是我知道这样的例子很少,所以我选择了这种格式。



当心大声的视频!




搭建开发环境



我们只需要在计算机上安装Android Studio如果没有真正的Android设备可以运行,则可以使用启用了网络摄像头模拟器



使用ML Kit创建项目







ML Kit是打动黑客马拉松评审团的绝佳工具:您在原型中使用AI!通常,它有助于将基于机器学习的解决方案嵌入到项目中,例如,用于确定框架中对象,翻译和文本识别的功能。



对于我们而言,重要的是ML Kit具有免费的脱机API,用于识别微笑和睁开或闭上的眼睛。



以前,要使用ML Kit创建任何项目,您首先必须在Firebase控制台中注册现在可以跳过此步骤以使用脱机功能。



Android应用程式



删除不必要的



为了避免从头开始编写使用相机的逻辑,让我们从官方样本中删除不需要的样本







首先,下载示例并尝试运行。探索人脸检测模式:它看起来像文章预览。



宣言



让我们开始编辑AndroidManifest.xml。删除除第一个活动标签之外的所有活动标签。并将其放置在CameraXLivePreviewActivity中,以立即从相机开始。在android:value属性的值中,我们只保留面孔,以便从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>


全步差异



相机



节省时间-我们将不会删除不必要的文件,而是将重点放在CameraXLivePreviewActivity屏幕的元素上。



  • 在第117行,设置人脸检测模式:

    private String selectedModel = FACE_DETECTION;
  • 在第118行,打开前置摄像头:

    private int lensFacing = CameraSelector.LENS_FACING_FRONT;
  • 在第198-199行的onCreate方法的最后,隐藏设置

    findViewById( R.id.settings_button ).setVisibility( View.GONE );
    findViewById( R.id.control ).setVisibility( View.GONE );


我们可以在这里停下来。但是,如果FPS渲染和面部网格在视觉上分散了注意力,则可以将其关闭,如下所示:



  • 在VisionProcessorBase.java文件中,删除第213-215行以隐藏FPS:

    graphicOverlay.add(
           new InferenceInfoGraphic(
              graphicOverlay, currentLatencyMs, shouldShowFps ? framesPerSecond : null));
  • 在FaceDetectorProcessor.java文件中,删除第75–78行以隐藏面部网格:

    for (Face face : faces) {
        graphicOverlay.add(new FaceGraphic(graphicOverlay, face));
        logExtrasForTesting(face);
    }


全步差异



识别情绪



微笑检测默认情况下处于关闭状态,但是启动它非常容易。我们以示例代码为基础并非没有!让我们在一个单独的类中选择所需的参数,并声明侦听器接口:



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()
                ));
            }
        }
    }
}


要启用情感分类,请在CameraXLivePreviewActivity类中设置FaceDetectorProcessor并订阅以接收情感状态。然后,我们将概率转换为布尔标志。为了进行测试,让我们在布局中添加一个TextView,在其中我们将通过表情显示情绪。







全步差异



分玩



由于我们正在制作游戏,因此我们需要一个放置元素的地方。假设它以纵向模式在手机上运行。因此,让我们将屏幕分为两部分:顶部是摄像头,底部是游戏。



用微笑来控制角色非常困难,此外,黑客马拉松几乎没有时间实施高级技巧。因此,我们的角色将在比赛场地的顶部或底部一路收集nishtyak。我们将添加闭眼或睁开眼睛的动作作为游戏的复杂性:如果您闭眼捉住nishtyak,点数将加倍(或者屏幕的一半不可见,并且您可以抢劫母牛)。



如果您想实现不同的游戏玩法,那么我可以建议一些有趣的选择:



  • Guitar Hero / Just Dance-类似吉他,您需要对音乐表现出某种情感;
  • 克服障碍的比赛,您需要在一定时间内到达终点或不撞车;
  • 射击者,玩家眨眨眼并射击敌人。


我们将在自定义的Android视图中显示游戏-在此,在onDraw方法中,我们将在Canvas上绘制角色。在第一个原型中,我们将自己局限于几何图元。



播放器







我们的角色是正方形。在初始化期间,我们将其尺寸和位置设置在左侧,因为它就位。Y轴的位置取决于玩家的微笑。所有绝对值将相对于游戏区域的大小进行计算。这比选择特定尺寸更容易-我们将在新设备上获得可接受的外观。



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)
}


蛋糕



我们的角色“奔跑”并试图抓住蛋糕,以便获得尽可能多的分数。我们使用标准技术,并相对于玩家过渡到参考系统:他将静止不动,蛋糕将飞向他。如果蛋糕的平方与玩家的平方相交,则该点被计数。而且,如果同时至少有一只用户的眼睛闭着-两点\ _(ツ)_ /¯



在我们的宇宙中,也将只有一个电子饼。一旦角色吃掉它,它就会从屏幕上移到具有随机坐标的随机条带上。这将防止玩家的微笑与蛋糕的可预测外观产生共鸣。



//        
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()
    }
}


发生了什么



让我们使点的显示非常简单。我们将在屏幕中央显示数字。您只需要考虑文字的高度,并在顶部缩进即可。



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
}


让我们看看我们制作了哪种玩具:





全步差异



石墨烯



为了使在骇客马拉松比赛中展示游戏不感到羞耻,让我们添加一些吧!







图片



我们从不能绘制令人印象深刻的图形这一事实出发。幸运的是,有些网站拥有免费游戏资产。我喜欢这个,尽管现在由于我未知的原因而无法直接使用。







动画



我们使用Canvas,这意味着我们需要自己实现动画。如果有带有动画的图片,将很容易对其进行编程。我们为图像变化的对象引入了一个类。



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()]
    }
}


为了获得运动效果,还必须对背景进行动画处理。在内存中具有一系列背景框架是一个开销的故事。因此,让我们更巧妙地做到这一点:我们将绘制一张带有时移的图像。想法大纲:







完成步骤比较。



最后结果



很难称其为杰作,但晚上制作原型很合适。该代码可以在这里找到在本地运行,没有其他的恶作剧。





最后,我将补充说ML Kit人脸检测在其他情况下可能会有用。



例如,与朋友进行完美的自拍照:您可以分析框架中的所有人,并确保每个人都笑着睁开眼睛。开箱即用可以检测视频流中的多个面孔,因此任务并不困难。



使用人脸检测模块中的人脸轮廓识​​别功能,可以复制几乎在所有相机应用中都非常流行的蒙版。如果您添加了互动功能(通过微笑和眨眼的定义),那么使用它们将会倍加有趣。



此功能-面部轮廓-可以用于娱乐之外。那些试图将照片切成文档的人会很感激的。我们采用面部轮廓,以所需的宽高比和正确的头部位置自动剪切照片。陀螺仪传感器将帮助确定正确的拍摄角度。



All Articles