Unity Android 实现桌面精灵

Android全局桌面精灵Unity实现

网页上找了一圈实现方案,大家的实现方案都是类似的。发现一个问题就是,用的都是旧版本的unity。以下的发案是基于新版本unity去实现。我使用的unity版本是2022.3.15

大致思路:

悬浮窗绘制unity场景

中间遇到的问题:

(1):相机背景不透明

解决方案

相机修改颜色值00000000

//看到这个方法 
mUnityPlayer.setAlpha(0);

(2):进程切换导致画面不渲染

切换进程会执行一次暂停操作

    @Override protected void onPause()
    {
        super.onPause();

//        MultiWindowSupport.saveMultiWindowMode(this);

//        if (MultiWindowSupport.getAllowResizableWindow(this))
//            return;
//
//        FloatingService.mUnityPlayer.pause();
    }

暴力解决

直接屏蔽相关代码

1:准备Android工程

创建Android工程

相机配置修改

Android 工程导出配置

导出Android工程即可

2:android studio 工程配置

xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.unity3d.player" xmlns:tools="http://schemas.android.com/tools">
  <uses-feature android:glEsVersion="0x00030001" />
  <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
  <uses-feature android:name="android.hardware.touchscreen.multitouch" android:required="false" />
  <uses-feature android:name="android.hardware.touchscreen.multitouch.distinct" android:required="false" />
 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
  <application android:extractNativeLibs="true">
    <meta-data android:name="unity.splash-mode" android:value="0" />
    <meta-data android:name="unity.splash-enable" android:value="True" />
    <meta-data android:name="unity.launch-fullscreen" android:value="False" />
    <meta-data android:name="unity.allow-resizable-window" android:value="False" />
    <meta-data android:name="android.max_aspect" android:value="2.1" />
    <meta-data android:name="unity.auto-report-fully-drawn" android:value="true" />
    <activity android:name="com.unity3d.player.UnityPlayerActivity" android:theme="@style/UnityThemeSelector.Translucent" android:screenOrientation="fullUser" android:launchMode="singleTask" android:maxAspectRatio="2.1" android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale|layoutDirection|density" android:resizeableActivity="false" android:hardwareAccelerated="false" android:exported="true">
      <intent-filter>
        <category android:name="android.intent.category.LAUNCHER" />
        <action android:name="android.intent.action.MAIN" />
      </intent-filter>
      <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
      <layout android:defaultWidth="200px" android:defaultHeight="200px" android:minWidth="200px" android:minHeight="200px" />
    </activity>
    <service android:name=".FloatingService"/>
  </application>
</manifest>

Service

package com.unity3d.player;

import android.app.Notification;
import android.app.Service;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.IBinder;
import android.provider.Settings;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import com.unity3d.player.UnityPlayer;

public class FloatingService extends Service {

    private WindowManager mWindowManager;
    public static   UnityPlayer mUnityPlayer;



    @Override
    public void onCreate() {
        super.onCreate();

        // 检查权限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
            stopSelf();
            return;
        }

        // 创建UnityPlayer实例

        // 设置布局参数
        final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, // 使用TYPE_APPLICATION_OVERLAY
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT);

        // 指定悬浮窗的位置
        params.gravity = Gravity.TOP | Gravity.LEFT;
        params.x = 0;
        params.y = 100;
        params.width=500;
        params.height=500;
        params.alpha=1f;
        mUnityPlayer.setAlpha(0);
//        FloatingService.mUnityPlayer.resume();
        // 获取WindowManager服务
        mWindowManager = (WindowManager)getSystemService(WINDOW_SERVICE);
        mWindowManager.addView(mUnityPlayer.getView(), params);
//        FloatingService.mUnityPlayer.start();
        FloatingService.mUnityPlayer.resume();
        // 处理悬浮窗的触摸事件
        mUnityPlayer.setOnTouchListener(new View.OnTouchListener() {
            private int initialX;
            private int initialY;
            private float initialTouchX;
            private float initialTouchY;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        initialX = params.x;
                        initialY = params.y;
                        initialTouchX = event.getRawX();
                        initialTouchY = event.getRawY();
                        return true;
                    case MotionEvent.ACTION_MOVE:
                        params.x = initialX + (int) (event.getRawX() - initialTouchX);
                        params.y = initialY + (int) (event.getRawY() - initialTouchY);
                        mWindowManager.updateViewLayout(mUnityPlayer, params);
                        return true;
                }
                return false;
            }
        });
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        FloatingService.mUnityPlayer.resume();
        mUnityPlayer.windowFocusChanged(true);

        return super.onStartCommand(intent, flags, startId);
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mUnityPlayer.getView() != null) mUnityPlayer.quit();
        if (mUnityPlayer.getView() != null) mWindowManager.removeView(mUnityPlayer);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

Activity

package com.unity3d.player;

import android.app.Activity;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.provider.Settings;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.os.Process;

public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecycleEvents
{

    private static final int OVERLAY_PERMISSION_REQ_CODE = 2084;
//    public UnityPlayer  mUnityPlayer; // don't change the name of this variable; referenced from native code

    // Override this in your custom UnityPlayerActivity to tweak the command line arguments passed to the Unity Android Player
    // The command line arguments are passed as a string, separated by spaces
    // UnityPlayerActivity calls this from 'onCreate'
    // Supported: -force-gles20, -force-gles30, -force-gles31, -force-gles31aep, -force-gles32, -force-gles, -force-vulkan
    // See https://docs.unity3d.com/Manual/CommandLineArguments.html
    // @param cmdLine the current command line arguments, may be null
    // @return the modified command line string or null
    protected String updateUnityCommandLineArguments(String cmdLine)
    {
        return cmdLine;
    }

    // Setup activity layout
    @Override protected void onCreate(Bundle savedInstanceState)
    {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        super.onCreate(savedInstanceState);
        FloatingService.mUnityPlayer=  new UnityPlayer(this);
//        setContentView(FloatingService.mUnityPlayer);
        // 启动 FloatingService
        Intent intent = new Intent(this, FloatingService.class);
        startService(intent);

        // 结束当前活动

       onUnityPlayerUnloaded();
        FloatingService.mUnityPlayer.windowFocusChanged(true);
//
        // 启动悬浮窗服务
//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//            if (!Settings.canDrawOverlays(this)) {
//                Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
//                startActivityForResult(intent, 2084);
//            } else {
//                startFloatingService();
//
//            }
//        } else {
//            startFloatingService();
//        }

//
//        String cmdLine = updateUnityCommandLineArguments(getIntent().getStringExtra("unity"));
//        getIntent().putExtra("unity", cmdLine);
//
//        mUnityPlayer = new UnityPlayer(this, this);
//        setContentView(mUnityPlayer);
//        mUnityPlayer.requestFocus();
    }
    private void startFloatingService() {
        Log.d("create", "onCreate:  floating1");
        Intent intent = new Intent(this, FloatingService.class);
        startService(intent);
        Log.d("create", "onCreate:  floating2");

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 2084) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                if (Settings.canDrawOverlays(this)) {
                    startFloatingService();
                } else {
                    // 权限未授予,显示错误消息或禁用相关功能
                }
            }
        }
    }
    // When Unity player unloaded move task to background
    @Override public void onUnityPlayerUnloaded() {
        moveTaskToBack(true);
    }

    // Callback before Unity player process is killed
    @Override public void onUnityPlayerQuitted() {
    }

    @Override protected void onNewIntent(Intent intent)
    {
        // To support deep linking, we need to make sure that the client can get access to
        // the last sent intent. The clients access this through a JNI api that allows them
        // to get the intent set on launch. To update that after launch we have to manually
        // replace the intent with the one caught here.
        setIntent(intent);
        FloatingService.mUnityPlayer.newIntent(intent);
        Log.d("resume","App is inint");
    }

    // Quit Unity
    @Override protected void onDestroy ()
    {
//        FloatingService.mUnityPlayer.destroy();
        super.onDestroy();
    }

    // If the activity is in multi window mode or resizing the activity is allowed we will use
    // onStart/onStop (the visibility callbacks) to determine when to pause/resume.
    // Otherwise it will be done in onPause/onResume as Unity has done historically to preserve
    // existing behavior.
    @Override protected void onStop()
    {
        super.onStop();

        if (!MultiWindowSupport.getAllowResizableWindow(this))
            return;

        FloatingService.mUnityPlayer.pause();
        Log.d("start","App is stop");
    }

    @Override protected void onStart()
    {
        super.onStart();

        if (!MultiWindowSupport.getAllowResizableWindow(this))
            return;
        if(FloatingService.mUnityPlayer!=null)
        {
            FloatingService.mUnityPlayer.resume();
        }
        Log.d("start","App is start");
//
    }

    // Pause Unity
    @Override protected void onPause()
    {
        super.onPause();

//        MultiWindowSupport.saveMultiWindowMode(this);

//        if (MultiWindowSupport.getAllowResizableWindow(this))
//            return;
//
//        FloatingService.mUnityPlayer.pause();
        Log.d("start","App is pause");
    }

    // Resume Unity
    @Override protected void onResume()
    {

        super.onResume();
//
//        if (MultiWindowSupport.getAllowResizableWindow(this) && !MultiWindowSupport.isMultiWindowModeChangedToTrue(this))
//            return;
        if(FloatingService.mUnityPlayer!=null)
        {
            FloatingService.mUnityPlayer.resume();
        }
        Log.d("resume","App is resume");
    }

    // Low Memory Unity
    @Override public void onLowMemory()
    {
        super.onLowMemory();
        FloatingService.mUnityPlayer.lowMemory();
    }

    // Trim Memory Unity
    @Override public void onTrimMemory(int level)
    {
        super.onTrimMemory(level);
        if (level == TRIM_MEMORY_RUNNING_CRITICAL)
        {
            FloatingService.mUnityPlayer.lowMemory();
        }
    }

    // This ensures the layout will be correct.
    @Override public void onConfigurationChanged(Configuration newConfig)
    {
        super.onConfigurationChanged(newConfig);
        FloatingService.mUnityPlayer.configurationChanged(newConfig);
    }

    // Notify Unity of the focus change.
    @Override public void onWindowFocusChanged(boolean hasFocus)
    {
        super.onWindowFocusChanged(hasFocus);
        FloatingService.mUnityPlayer.windowFocusChanged(hasFocus);
    }

    // For some reason the multiple keyevent type is not supported by the ndk.
    // Force event injection by overriding dispatchKeyEvent().
    @Override public boolean dispatchKeyEvent(KeyEvent event)
    {
        if (event.getAction() == KeyEvent.ACTION_MULTIPLE)
            return FloatingService.mUnityPlayer.injectEvent(event);
        return super.dispatchKeyEvent(event);
    }

    // Pass any events not handled by (unfocused) views straight to UnityPlayer
    @Override public boolean onKeyUp(int keyCode, KeyEvent event)     { return FloatingService.mUnityPlayer.onKeyUp(keyCode, event); }
    @Override public boolean onKeyDown(int keyCode, KeyEvent event)   { return FloatingService.mUnityPlayer.onKeyDown(keyCode, event); }
    @Override public boolean onTouchEvent(MotionEvent event)          { return FloatingService.mUnityPlayer.onTouchEvent(event); }
    @Override public boolean onGenericMotionEvent(MotionEvent event)  { return FloatingService.mUnityPlayer.onGenericMotionEvent(event); }
}

结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值