技术标签: unity 安卓 Android Unity开发 java android 移动开发
最近接到一个任务是Android设备上实现一个全局的指引动画,开始想着就用普通动画控件或者svga、lottie控件实现,最近正好在学习Unity,所以试着用unity实现。经过三天努力,居然实现了。话不多说,马上开始:
在爱给网找到一个蝴蝶3D模型,然后通过3Dmax导出为FBX模型,然后倒入到unity里,具体操作相对比较简单,只是说明一下,模型纹理需要跟导出文件放在一起,否者到unity里面没有纹理皮肤。然后就是在unity修改Animation Type为Legacy,以及动画循环设置:Wrap Mode设置为Loop。
模型下载地址:http://www.aigei.com/3d/model/lactation/
Unity导出透明应用比较麻烦,耗时最多,找了很久只有这个文章有提及:
https://www.jianshu.com/p/a67f77cd2e62
也看了英文原地址:
https://forum.unity.com/threads/unity3d-export-to-android-with-transparent-background.512129/
于是开始尝试,按照文档所说,只需要修改两个地方就能实现:
1、修改Main Camera 的背景颜色为 Solid Color,并且透明度设置为0。
2、导出设置勾选preserveFramebufferAlpha。
我装的Unity 5.5.0f3 (64-bit)和Unity 2019.1.0a8 (64-bit),没有找到文档所说的preserveFramebufferAlpha选项,按文档所说的2018.1版本可以,我又装了Unity 2018.1.1f1 (64-bit),选项是有了,但是按照设置导出还是不透明。
反复排查和对比,发现我的颜色设置透明度不起作用,只显示6位颜色,而没有透明度显示:
只能尝试下载其他版本。
找到官方操作手册,发现这个版本也有设置选项
https://docs.unity3d.com/cn/2017.4/Manual/class-PlayerSettingsAndroid.html
于是尝试下了Unity 2017.4.2f2 (64-bit)版本,看到了8位的颜色值,心里大喜:
本来想着直接导出apk运行,但是一直报错,导出失败,怀疑是版本比较旧,尝试更换了旧版本NDKK和jdk版本,以及自定义了Gradle,都不行。最后只好导出工程,然后自己创建Android工程编译。还算顺利很快运行实现了效果。
中间还出现个小问题,就是渲染的图像颜色不对,有点过曝光。后来发现是camera颜色值问题,设置成#00000000后解决。
Unity直接导出的工程是activity显示动画,要做全局widow,需要把UnityPlayer放service里生成。我直接拷贝相关方法,放到service里生成,然后放入系统window,结果什么都不显示。
看UnityPlayer生成传入的context居然是activity类型,顿时心的都凉了。
我不会轻易放弃,我通过Application保存了全局的activity和UnityPlayer,然后从window里面获取,验证可行性,多次尝试都是显示空白。
public class MainApp extends Application {
private static Activity mActivity;
static UnityPlayer mUnityPlayer;
public static Application app;
@Override
public void onCreate() {
super.onCreate();
app = this;
}
public static Activity getActivity() {
return mActivity;
}
public static void setActivity(Activity activity) {
mActivity = activity;
}
public static UnityPlayer getUnityPlayer() {
return mUnityPlayer;
}
public static void setUnityPlayer(UnityPlayer unityPlayer) {
mUnityPlayer = unityPlayer;
}
}
偶然间发现,UnityPlayer在activity里面生成,并且生命周期全部放activity,只是把UnityPlayer加载到window窗口,居然可以实现。只是activity不能切换后台,切换到后台动画就暂停了。
尝试各参数,最后发现是 mUnityPlayer.windowFocusChanged(true);这句对显示有关键作用。
于是推到重来,new UnityPlayer用了getApplicationContext,成功!
所以改变service全部生成,结果成功。
代码如下:
package com.Company.bgTest;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.unity3d.player.UnityPlayer;
/**
* @author hardy
* @name My Application
* @class name:com.Company.bgTest
* @class describe:
* @time 2020/7/10 15:47
* @change
* @chang time
* @class describe
*/
public class MainService extends Service {
//Log用的TAG
private static final String TAG = "MainService";
//要引用的布局文件.
LinearLayout toucherLayout;
//布局参数.
WindowManager.LayoutParams params;
//实例化的WindowManager.
WindowManager windowManager;
//状态栏高度.(接下来会用到)
int statusBarHeight = -1;
protected UnityPlayer mUnityPlayer;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "MainService Created");
//OnCreate中来生成悬浮窗.
createToucher();
}
private void createToucher() {
//赋值WindowManager&LayoutParam.
params = new WindowManager.LayoutParams();
windowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE);
//设置type.系统提示型窗口,一般都在应用程序窗口之上.
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
//设置效果为背景透明.
params.format = PixelFormat.RGBA_8888;
//设置flags.不可聚焦及不可使用按钮对悬浮窗进行操控.
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
//设置窗口初始停靠位置.
params.gravity = Gravity.LEFT | Gravity.TOP;
params.x = 0;
params.y = 0;
//设置悬浮窗口长宽数据.
//注意,这里的width和height均使用px而非dp.这里我偷了个懒
//如果你想完全对应布局设置,需要先获取到机器的dpi
//px与dp的换算为px = dp * (dpi / 160).
params.width = 400;
params.height = 600;
LayoutInflater inflater = LayoutInflater.from(getApplication());
//获取浮动窗口视图所在布局.
toucherLayout = (LinearLayout) inflater.inflate(R.layout.pet_window, null);
//添加toucherlayout
windowManager.addView(toucherLayout, params);
Log.i(TAG, "toucherlayout-->left:" + toucherLayout.getLeft());
Log.i(TAG, "toucherlayout-->right:" + toucherLayout.getRight());
Log.i(TAG, "toucherlayout-->top:" + toucherLayout.getTop());
Log.i(TAG, "toucherlayout-->bottom:" + toucherLayout.getBottom());
//主动计算出当前View的宽高信息.
toucherLayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
//用于检测状态栏高度.
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = getResources().getDimensionPixelSize(resourceId);
}
Log.i(TAG, "状态栏高度为:" + statusBarHeight);
mUnityPlayer = new UnityPlayer(this.getApplicationContext());
// mUnityPlayer = MainApp.getUnityPlayer();
((RelativeLayout) toucherLayout.findViewById(R.id.rl_pet)).addView(mUnityPlayer);
mUnityPlayer.start();
mUnityPlayer.resume();
mUnityPlayer.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
//ImageButton我放在了布局中心,布局一共300dp
params.x = (int) event.getRawX() - 150;
//这就是状态栏偏移量用的地方
params.y = (int) event.getRawY() - 150 - statusBarHeight;
windowManager.updateViewLayout(toucherLayout,params);
return false;
}
});
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
mUnityPlayer.windowFocusChanged(true);
return super.onStartCommand(intent, flags, startId);
}
// Quit Unity
@Override public void onDestroy ()
{
mUnityPlayer.pause();
mUnityPlayer.stop();
mUnityPlayer.quit();
super.onDestroy();
}
}
另外值得一提的是全局悬浮窗需要设置权限,参考 https://www.jianshu.com/p/ac63c57d2555:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW"/>
以及代码判断用户手动开启:
//当AndroidSDK>=23及Android版本6.0及以上时,需要获取OVERLAY_PERMISSION.
//使用canDrawOverlays用于检查,下面为其源码。其中也提醒了需要在manifest文件中添加权限.
/**
* Checks if the specified context can draw on top of other apps. As of API
* level 23, an app cannot draw on top of other apps unless it declares the
* {@link android.Manifest.permission#SYSTEM_ALERT_WINDOW} permission in its
* manifest, <em>and</em> the user specifically grants the app this
* capability. To prompt the user to grant this approval, the app must send an
* intent with the action
* {@link android.provider.Settings#ACTION_MANAGE_OVERLAY_PERMISSION}, which
* causes the system to display a permission management screen.
*
*/
if (Build.VERSION.SDK_INT >= 23) {
if (Settings.canDrawOverlays(UnityPlayerActivity.this)) {
Intent intent = new Intent(UnityPlayerActivity.this, MainService.class);
Toast.makeText(UnityPlayerActivity.this, "已开启Toucher", Toast.LENGTH_SHORT).show();
// startService(intent);
// finish();
// moveTaskToBack(true);
} else {
//若没有权限,提示获取.
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
Toast.makeText(UnityPlayerActivity.this, "需要取得权限以使用悬浮窗", Toast.LENGTH_SHORT).show();
startActivity(intent);
}
} else {
//SDK在23以下,不用管.
Intent intent = new Intent(UnityPlayerActivity.this, MainService.class);
startService(intent);
moveTaskToBack(true);
// finish();
}
/题目:判断 101-200 之间有多少个素数,并输出所有素数。程序分析:判断素数的方法:用一个数分别去除 2 到 sqrt(这个数),如果能被整除,则表明此数不是素数,反之是素数。/public class work02 { public static void main(String[] args) { for (int i = 101; i < 200; i++) { ..._筛选出文档中的质数,并且以从小到大的顺序排列写入文档10_2.txt中。
FTP命令是Internet用户使用最频繁的命令之一,不论是在DOS还是UNIX操作系统下使用 FTP,都会遇到大量的FTP内部命令。熟悉并灵活应用FTP的内部命令,可以大大方便使用者,并收到事半功倍之效。 FTP的命令行格式为: ftp -v -d -i -n -g [主机名] ,其中 -v 显示远程服务器的所有响应信息; -d 使用调试方式; -i 限制ftp的自动登录,即不使用; _linux ftp命令写进函数
为什么要把云函数 SCF 与 API 网关进行结合?本文告诉你答案!通常,我们用云函数 SCF 写一个函数应用,这个应用可能多种多样。例如之前介绍过的 OJ 系统判题功能,通过 NLP 实现文本摘要功能......,那么,怎么把这些功能简单快速地结合到我们的项目中,尤其是 Web 项目中呢?本文通过一个简单小例子实现云函数 SCF 与 API 网关的结合,希望能给到大家一个参考。▎任务说明通过云函..._# -*- coding: utf8 -*- import json def main_handler(event, context): print(
git fetch --all //只是下载代码到本地,不进行合并操作git reset --hard origin/master //把HEAD指向最新下载的版本_冲突时,丢弃本地,强制拉取remote
SCOTT用户是我们学习Oracle过程中一个非常重要的实验对象,在我们建立数据库的时候,如果是选择定制模式的话,SCOTT用户是不会默认出现的,不过我们可以通过使用几个简单命令来使这个用户出现。以下是解决方法(基于windows): 1.开始——运行——cmd 输入:sqlplus / as sysdba连接到数据库 SQL>conn sco_scott不存在
接口功能:xxxx列表查询URLhttp://localhost:9090/query支持格式JSONHTTP请求方式GET请求参数无返回字段返回字段 字段类型 说明 接口示例..._接口文档制表doc
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言一、采用的数据结构?二、首次适应算法三、循环首次适应算法四、最佳适应算法1.引入库2.读入数据总结前言编写一个内存动态分区分配模拟程序,分别实现:首次适应、循环首次适应、最佳适应算法,对内存的分配和回收过程,此程序没有实现“紧凑”。每次分配和回收后把空闲分区的变化情况以及进程的申请、释放情况最好以图形方式显示,尽可能设计一个友好的用户界面,直观显示内存区域经分配、回收的动态变化情况。一、采用的数据结构?采用的数_图形化界面动态分区分配方式的模拟实验
RapidXml 试图成为最快的 XML DOM 解析工具包,同时保证解析结果的可用性、可移植性以及与 W3C 标准的兼容性。RapidXml 使用 C++ 编写,因此在操作同一数据时,其解析速度接近于 strlen() 函数。整个解析工具包包含在一个头文件中,所以使用时不用编译也不用连接。要想使用 RapidXml 只要包含 rapidxml.hpp 即可,当然如果要用附加功能(如打印函_rapidxml 遍历
首先用到两个文件:1、asm.s 文件,内容为: AREA MYADD,CODE,READONLY IMPORT Cal;导入C语言函数 ENTRY CODE32Start MOV R0,#1 MOV R1,#2 MOV R2,#3 BL Cal;调用C语言函数,计算结果保存在R0当中
一旦开启了Nginx日志功能,每天Nginx都会生成一定大小的日志文件,如果系统稳定运行,没有任何问题,那么日志基本上不会去查看。但这些日志如不及时清理,日渐积累,对服务器的磁盘空间占用也将是比较恐怖的。为了解决这个问题,利用Shell脚本对Nginx日志文件定时备份和删除,只保留一段时间。图1:#!/bin/bash#auth:lzq#desc:把当前日志按日期备份,重新生成第二天的日志文件#d..._清空nginx日志 linux
一、写在前面 这篇文章主要介绍一下python WSGI, 学习python wsgi规范的时候读到了几篇介绍的很好的入门教程,内容不长,整理了一下。由于能力和时间有限,错误之处在所难免,欢迎指正! 如果转载,请保留作者信息。 邮箱地址:[email protected]二、WSGI 介绍Pyth_python wsgi
Parameter type : IntegerDefault value : 0Modifiable : No Range of values : 50 to an operating system-specific maxi...