最近做项目需要用到这个功能,就是在Unity中调用Android本地相册或直接打开摄像机拍照并且裁剪一部分用于用户头像,今天研究了一下,那么研究出成果了MOMO一定要分享给大家。Unity与Android的交互还有谁不会?? 如果有不会的朋友请看MOMO之前的文章喔,Unity3D研究院之打开Activity与调用JAVA代码传递参数(十八)这里有关交互的方式就不详细说明,主要将如何在Unity中打开摄像机、在Unity中打开本地相册,选一个照片后如何进行裁剪,最后将图片转换成Texture显示在U3D的世界当中。
首先看看Eclipse中的Android插件部分,我的包名是com.xys请大家与MOMO保持一致,Unity工程中也需要是这个包名噢。
UnityTestActivity.java 这个类是Unity的插件主类,在这里调用是打开摄像机 还是本地相册的方法。
package com.xys;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import com.unity3d.player.UnityPlayerActivity;
public class UnityTestActivity extends UnityPlayerActivity {
//public class UnityTestActivity extends Activity {
Context mContext = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
}
//Unity中会调用这个方法,用于区分打开摄像机 开始本地相册
public void TakePhoto(String str)
{
Intent intent = new Intent(mContext,WebViewActivity.class);
intent.putExtra("type", str);
this.startActivity(intent);
}
}
然后是WebViewActivity.java 这里主要处理用户打开摄像机或本地相册后如何进行裁剪图片,并且把裁剪的图片储存在本地文件中。
package com.xys;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import com.unity3d.player.UnityPlayer;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.view.KeyEvent;
import android.widget.ImageView;
public class WebViewActivity extends Activity
{
ImageView imageView = null;
public static final int NONE = 0;
public static final int PHOTOHRAPH = 1;// 拍照
public static final int PHOTOZOOM = 2; // 缩放
public static final int PHOTORESOULT = 3;// 结果
public static final String IMAGE_UNSPECIFIED = "image/*";
public final static String FILE_NAME = "image.png";
public final static String DATA_URL = "/data/data/";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
imageView = (ImageView) this.findViewById(R.id.imageID);
String type = this.getIntent().getStringExtra("type");
//在这里判断是打开本地相册还是直接照相
if(type.equals("takePhoto"))
{
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(Environment.getExternalStorageDirectory(), "temp.jpg")));
startActivityForResult(intent, PHOTOHRAPH);
}else
{
Intent intent = new Intent(Intent.ACTION_PICK, null);
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_UNSPECIFIED);
startActivityForResult(intent, PHOTOZOOM);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == NONE)
return;
// 拍照
if (requestCode == PHOTOHRAPH) {
//设置文件保存路径这里放在跟目录下
File picture = new File(Environment.getExternalStorageDirectory() + "/temp.jpg");
startPhotoZoom(Uri.fromFile(picture));
}
if (data == null)
return;
// 读取相册缩放图片
if (requestCode == PHOTOZOOM) {
startPhotoZoom(data.getData());
}
// 处理结果
if (requestCode == PHOTORESOULT) {
Bundle extras = data.getExtras();
if (extras != null) {
Bitmap photo = extras.getParcelable("data");
imageView.setImageBitmap(photo);
try {
SaveBitmap(photo);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
super.onActivityResult(requestCode, resultCode, data);
}
public void startPhotoZoom(Uri uri) {
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(uri, IMAGE_UNSPECIFIED);
intent.putExtra("crop", "true");
// aspectX aspectY 是宽高的比例
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
// outputX outputY 是裁剪图片宽高
intent.putExtra("outputX", 300);
intent.putExtra("outputY", 300);
intent.putExtra("return-data", true);
startActivityForResult(intent, PHOTORESOULT);
}
public void SaveBitmap(Bitmap bitmap) throws IOException {
FileOutputStream fOut = null;
//注解1
String path = "/mnt/sdcard/Android/data/com.xys/files";
try {
//查看这个路径是否存在,
//如果并没有这个路径,
//创建这个路径
File destDir = new File(path);
if (!destDir.exists())
{
destDir.mkdirs();
}
fOut = new FileOutputStream(path + "/" + FILE_NAME) ;
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//将Bitmap对象写入本地路径中,Unity在去相同的路径来读取这个文件
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut);
try {
fOut.flush();
} catch (IOException e) {
e.printStackTrace();
}
try {
fOut.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0)
{
//当用户点击返回键是 通知Unity开始在"/mnt/sdcard/Android/data/com.xys/files";路径中读取图片资源,并且现在在Unity中
UnityPlayer.UnitySendMessage("Main Camera","message",FILE_NAME);
}
return super.onKeyDown(keyCode, event);
}
}
注解1:主要是路径”/mnt/sdcard/Android/data/com.xys/files”,如下图所示,我们在这里把文件保存在这个路径下。为什么要把图片2进制文件写在这里呢? 还记得以前MOMO给大家说过在Unity中访问Android或IOS本地2进制文件时用到的这个路径,
Application.persistentDataPath 该路径等价于 /mnt/sdcard/Android/data/com.xys/files ,当然后者的包名是对应的工程包名,这样在Unity中可以找到对应裁剪后的图片文件,并且显示在Unity中。
AndroidManifest.xml 这个文件也没什么好说的,大家看看吧。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.xys"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="10" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".UnityTestActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".WebViewActivity">
</activity>
</application>
<!-- 连接互联网的权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!-- SDCard写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>
然后把上面的Android工程打包做成插件放在Unity中。如下图所示,这个我的Unity工程中对应的路径。如果看不懂的朋友请看我之前的文章哈。
然后看Test.cs脚本,它直接挂在摄像机身上。
using UnityEngine;
using System.Collections;
using System.IO;
public class Test : MonoBehaviour
{
public GUISkin skin;
Texture texture;
void Update ()
{
if (Input.GetKeyDown(KeyCode.Escape) || Input.GetKeyDown(KeyCode.Home))
{
Application.Quit();
}
}
void OnGUI()
{
GUI.skin = skin;
if(GUILayout.Button("打开手机相册"))
{
//调用我们制作的Android插件打开手机相册
AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
jo.Call("TakePhoto","takeSave");
}
if(GUILayout.Button("打开手机摄像机"))
{
//调用我们制作的Android插件打开手机摄像机
AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
jo.Call("TakePhoto","takePhoto");
}
if(texture != null)
{
//注意! 我们在这里绘制Texture对象,该对象是通过
//我们制作的Android插件得到的,当这个对象不等于空的时候
//直接绘制。
GUI.DrawTexture(new Rect(100,300,300,300),texture);
}
}
void messgae(string str)
{
//在Android插件中通知Unity开始去指定路径中找图片资源
StartCoroutine(LoadTexture(str));
}
IEnumerator LoadTexture(string name)
{
//注解1
string path = "file://" + Application.persistentDataPath +"/" + name;
WWW www = new WWW(path);
while (!www.isDone)
{
}
yield return www;
//为贴图赋值
texture = www.texture;
}
}
注解1:请大家一定要注意这个路径的写法, 前面一定要加 “File://” 不然无法读取。OK说了这么多我们看看这个项目运行的效果,激动人心的时刻来临啦 嚯嚯嚯嚯!!!
1.首次进入的画面, 这里的图片是我刚刚从相册选择的
3. 选择一张图片,我们进行裁剪
最后我们返回到Unity中界面。新的图片Unity已经完成读取,界面上已经修改成刚刚我裁剪的啦,哇咔咔。 怎么样,还不错啦? 哈哈后。这个做用户头像肯定给力 蛤蛤。
- 本文固定链接: https://www.xuanyusong.com/archives/1480
- 转载请注明: 雨松MOMO于 雨松MOMO程序研究院 发表







收了海飞丝多少钱?
哈哈。。 没多少。。
松神,例子怎么下不了
雨大,问个事啊,我把你代码拷贝下来 不管是拍照还是打开相册剪切图片点保存的时候程序会闪退 是为什么
最近公司项目也需要这个内容,安卓代码有限,雨神能指点下他问的问题吗?
拜托了
为什么一运行你的包到爆出来的apk jiu bengkui le
请问解决了吗?
大神,请教一下如何把截图发送到web服务器?
MOMO ,代码链接那里,那两个文件夹不打包无法下载呀!! 文章很不错,学习了。
我用你的程序竖屏是没问题的,换成横屏就出现问题了。不知道你试过没有?
同样啊,怎么解决?
直接用真机测试就不会有这个问题了。
雨神,如果要调用的 Android 方法要传递多个参数,像这样 jo.Call(“Buy”, “Test0001”, “钻石”, “金币”, “test01”, “test==”, “isnull==”, “100010”, “蜃梦楼oy”, 10, 20000, 10); 但是总是提示 AndroidJavaException: java.lang.NoSuchMethodError: ,Android 中的方法是有的
雨神,想请教一个问题。如果我想调用的Activity 不是主activity 是不是把 AndroidJavaObject jo = jc.GetStatic(“currentActivity”);中的currentActivity换成我的activity?我这样做了但我的Activity中的方法没有被调用,已经在 AndroidManifest.xml 中加入了 ,是不是要先启动我的Activity?请雨神指点一下
AndroidJavaObject jo = jc.GetStatic(“currentActivity”); 或者你还可以用 AndroidJavaClass class = new AndroidJavaClass(“com.koramgame.xxsr.myclass”); 去取一个类。。
AndroidJavaObject jo = jc.GetStatic(“currentActivity”); 或者你还可以用 AndroidJavaClass class = new AndroidJavaClass(“com.momo.momo.myclass”); 去取一个类。。
多谢雨神, 我是这样调用的 using (AndroidJavaClass aj = new AndroidJavaClass(“com.kingnet.qmonster.AndquitActivity”)) { AndroidJavaObject jo = aj.GetStatic(“currentActivity”); xValue = jo.Call(“getNum”); }但是 好像没有获得jo对象,在 AndroidJavaObject jo = aj.GetStatic(“”)中应该传什么参数呢?
AndroidJavaClass myClass = new AndroidJavaClass(“com.kingnet.qmonster.AndquitActivity”);myClass.CallStatic(“startFunc”); startFunc 要是静态方法。。
多谢雨神,但是如果这样,我只能调用Android的静态方法了,如果我想调用 Android 中 public void onBackPressed() { }这样不是静态的复写方法该怎么解决呢?
雨神,怎样调用非静态方法呢?或者怎样才能获得类的实例呢? 我在 Activity 的onCreate 方法中 instanse = this ; 给这个类的静态实例,但发现 onCreate 并没有执行。求雨神指点啊
AndroidJavaClass myClass= new AndroidJavaClass(“com.kingnet.qmonster.AndquitActivity”);myClass.CallStatic(“startFunc”); startFunc 要是静态方法。。
AndroidJavaObject jo = jc.GetStatic(“currentActivity”); 或者你还可以用 AndroidJavaClass class= new AndroidJavaClass(“com.koramgame.xxsr.myclass”); 去取一个类。。
AndroidJavaObject jo = jc.GetStatic(“currentActivity”); 或者你还可以用 AndroidJavaClass class= new AndroidJavaClass(“com.momo.momo.myclass”); 去取一个类。。
我也遇到这个问题了,跟上面的一样,用的机型是note 3,安卓版本4.3
我用你的程序竖屏是没问题的,换成横屏就出现问题了。不知道你试过没有?
你好,我是Android菜菜,这个照片剪切完后,会跳到一个显示界面,按返回键后才返回unity界面。请问我想裁剪保存后就直接跳到unity界面该怎么改?
你好,我是一个初学者,不知道为什么这个源码一直在我的eclipse和unity里都不能运行,而且在unity里调试的时候,还会显示说Exception: JNI: Init’d AndroidJavaClass with null ptr!这个,研究了好几天实在是不知道为什么,想请教一下原因。。。。。。谢谢
請問如果我在ASSET STORE下載的PLUGIN已經有ANDROIDMANIFEST, 那麼應該把自己PLUGIN再加上去?
能在Unity3d 调用 ios的相册吗
怪摘 . . 不知為何標點符號全不見了 . .
MOMO 你好好不容易在台灣這兒買到你的書獲益良多我想問問MOMO 是否有研究過 unity 該如何才能直接使用apple i系列產品的相機鏡頭呢 ??