一、图片上传功能的意义
随着梦幻西游、大话西游、问道等回合制游戏的兴起,手游制作团队越来越重视社交功能的开发。笔者前一篇文章介绍了如何在Unity中加入语音聊天功能,本篇文章将介绍下一个社交功能——图片上传。
有了图片上传玩家就可以自定义头像、聊天发送图片甚至还可以在Unity中做一个『朋友圈』。
二、获取图片到Unity
Unity没有提供直接的Api打开移动端的相机、相册功能,所以需要调用Android/IOS的系统Api来打开移动端的相机和相册
1. Android获取相机、相册图片
主要功能实现参考雨松momo的文章Unity3D研究院之打开照相机与本地相册进行裁剪显示(三十三)
第一步,建立Unity项目对应的Android工程
如果不会可以查看雨松momo的文章Unity3D研究院之与Android相互传递消息(十九)
第二步,书写打开相机、相册代码by Android
/**
* 打开相机 为什么要指定一个保存路径,是因为如果不指定onActivityResult只能从data参数中获取图片,可是获取到的是略缩图
*/
public void TakePhotoChoose() {
int hasCameraPermission = checkCallingOrSelfPermission(Manifest.permission.CAMERA);
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
File imageFile = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera/"
+ format.format(new Date()) + ".jpg");
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(imageFile));
PhotoPath = imageFile.getPath();
startActivityForResult(intent, PIC_TAKE_PHOTO);
}
/**
* 打开相册
*/
public void TakePickChoose() {
Intent intent = new Intent(Intent.ACTION_PICK, null);
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_TYPE);
startActivityForResult(intent, PIC_TAKE_PICK);
}
第三步,获取到图片处理图片发送到Unity
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == 0) {
// 取消接口
UnityPlayer.UnitySendMessage("GameMain", "CandleShowPhoto", "");
return;
}
if (requestCode == PIC_TAKE_PHOTO) {
// 获取到图片的资源使用bitmap压缩,并且转成base64的String直接将字符串发回去
Log.e("Unity", "GetPhotoPath");
UnityPlayer.UnitySendMessage("GameMain", "GetPath", PhotoPath);
Bitmap bm = PhotoUtil.getImageFromPath(PhotoPath, 40, 500, 750);
Bitmap bmSmall = PhotoUtil.getImageFromPath(PhotoPath, 40, 60, 80);
UnityPlayer.UnitySendMessage("GameMain", "ShowPhoto", PhotoUtil.getBitmapStrBase64(bm));
UnityPlayer.UnitySendMessage("GameMain", "SaveMiniPhoto", PhotoUtil.getBitmapStrBase64(bmSmall));
}
if (requestCode == PIC_TAKE_PICK) {
// 读取相册缩放图片
Bitmap bm = null;
ContentResolver resolver = getContentResolver();
Uri originalUri = data.getData();
Cursor cursor = getContentResolver().query(originalUri, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA));
bm = PhotoUtil.getImageFromPath(path, 40, 500, 750);
Bitmap bmSmall = PhotoUtil.getImageFromPath(path, 40, 60, 80);
UnityPlayer.UnitySendMessage("GameMain", "ShowPhoto", PhotoUtil.getBitmapStrBase64(bm));
UnityPlayer.UnitySendMessage("GameMain", "SaveMiniPhoto", PhotoUtil.getBitmapStrBase64(bmSmall));
} else {
try {
bm = MediaStore.Images.Media.getBitmap(resolver, originalUri);
} catch (IOException e) {
e.printStackTrace();
}
if (bm != null) {
Thread thread = new YaSuoPicThread(bm);
thread.start();
}
}
}
}
在对图片处理过程中与鱼松momo有些不同,我没有将图片再次保存,而是将bitmap转成了string直接发送回了unity,省去了保存图片的时间,并且效果上看运行速度还是可以接受的。大家可以自己试试。
第四步,Unity显示图片
由于显示图片功能放在了Unity,Android和IOS是公用的所以下文IOS中不再重复
/// <summary>
/// 从Android/IOS获取图片的base64串并显示图片
/// </summary>
/// <param name="base64">图片的base64串</param>
void ShowPhoto(string base64)
{
int targetHeight = 400;
int targetWidth = 600;
byte[] inputBytes = System.Convert.FromBase64String(base64);
base64 = "";
Texture2D text = BttetoPicByByte(inputBytes);
int curHeight = text.height;
int curWidth = text.width;
int newHeight = 0;
int newWidth = 0;
GetCutSize(targetHeight, targetWidth, curHeight, curWidth, ref newHeight, ref newWidth);
if (BigImage != null)
{
BigImage.gameObject.SetActive(true);
BigImage.rectTransform.sizeDelta = new Vector2(newWidth, newHeight);
BigImage.texture = text;
m_Stream = new MemoryStream(inputBytes);
inputBytes = null;
}
CloseDialogJuHua();
}
/// <summary>
/// 工具:将string转成图片
/// </summary>
/// <param name="base64"></param>
/// <returns></returns>
Texture2D BttetoPic(string base64)
{
Texture2D pic = new Texture2D(600, 400);
//将base64转码为byte[]
byte[] data = System.Convert.FromBase64String(base64);
//加载byte[]图片
pic.LoadImage(data);
return pic;
}
/// <summary>
/// 工具:将bytes转成图片
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
Texture2D BttetoPicByByte(byte[] bytes)
{
Texture2D pic = new Texture2D(600, 400);
//加载byte[]图片
pic.LoadImage(bytes);
return pic;
}
/// <summary>
/// 工具:返回给定高宽内的图片高度和宽度 比如给定400*600 肯定返回一个400*600之内的照片
/// </summary>
/// <param name="targetHeight"></param>
/// <param name="targetWidth"></param>
/// <param name="curHeight"></param>
/// <param name="curWidth"></param>
/// <param name="newHeight"></param>
/// <param name="newWidth"></param>
public static void GetCutSize(int targetHeight, int targetWidth, int curHeight, int curWidth, ref int newHeight, ref int newWidth)
{
int TargetHeight = targetHeight;
int TargetWidth = targetWidth;
int width = curWidth;
int height = curHeight;
float bili = 1f;
if (width > TargetWidth)
{
bili = (float)TargetWidth / (float)width;
}
newHeight = (int)((float)height * bili);
if (newHeight > TargetHeight)
{
bili = (float)TargetHeight / (float)height;
}
newHeight = (int)((float)height * bili);
newWidth = (int)((float)width * bili);
}
2. IOS获取相机、相册图片
如果不会IOS与Unity交互的同学请戳这里
第一步,书写打开相机、相册代码by IOS
需要用到Assetslibrary.framework
//
// GetPhotoControl.m
// image2
//
// Created by tengjiang on 16/4/26.
// Copyright © 2016年 All rights reserved.
//
#import <Foundation/Foundation.h>
#import "GetPhotoManager.h"
#include <sys/param.h>
#include <sys/mount.h>
#import <AssetsLibrary/AssetsLibrary.h>
#import <AVFoundation/AVCaptureDevice.h>
#import <AVFoundation/AVMediaFormat.h>
extern "C"
{
void _GetPhotoControl(int index);
}
static GetPhotoManager *getPhotoControl;
void _GetPhotoControl(int index)
{
if(getPhotoControl == NULL)
{
getPhotoControl = [[GetPhotoManager alloc] init];
}
[getPhotoControl GetPhotoChoose:index];
}
@implementation GetPhotoManager
typedef enum {
kCLAuthorizationStatusNotDetermined = 0, // 用户尚未做出选择这个应用程序的问候
kCLAuthorizationStatusRestricted, // 此应用程序没有被授权访问的照片数据。可能是家长控制权限
kCLAuthorizationStatusDenied, // 用户已经明确否认了这一照片数据的应用程序访问
kCLAuthorizationStatusAuthorized // 用户已经授权应用访问照片数据} CLAuthorizationStatus;
};
- (void)GetPhotoChoose:(NSInteger)ChooseIndex{
UIImagePickerControllerSourceType sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
// 判断是否支持相机
if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
switch (ChooseIndex) {
case 0:
//来源:相机
if([self GetCameraPermission] == -1)
{
UnitySendMessage("GameMain", "CandleShowPhoto", "");
return;
}
sourceType = UIImagePickerControllerSourceTypeCamera;
break;
case 1:
if([self GEtPickPermission] == -1)
{
UnitySendMessage("GameMain", "CandleShowPhoto", "");
return;
}
//来源:相册
sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
break;
case 2:
UnitySendMessage("GameMain", "CandleShowPhoto", "");
return;
}
}
else
{
if (ChooseIndex == 2) {
UnitySendMessage("GameMain", "CandleShowPhoto", "");
return;
}
else
{
if([self GEtPickPermission] == -1)
{
UnitySendMessage("GameMain", "CandleShowPhoto", "");
return;
}
sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
}
}
// 跳转到相机或相册页面
UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
imagePickerController.delegate = self;
//imagePickerController.allowsEditing = NO;
imagePickerController.sourceType = sourceType;
//仔细看这句话Unity中如果获取presentViewController并跳转,需要这样写
[UnityGetGLViewController() presentViewController:imagePickerController animated:YES completion:^{
}];
//↑↑↑↑↑↑↑↑↑↑↑↑↑仔细看↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
}
//获取图片后的回调
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
[picker dismissViewControllerAnimated:YES completion:^{
}];
UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
NSData *fData = [self imageCompressForWidth:image targetWidth:600 targetHeight:400];
NSString *stringPhoto = [fData base64EncodedStringWithOptions:0];
UnitySendMessage("GameMain", "ShowPhoto", [stringPhoto UTF8String]);
NSData *fDataSmall = [self imageCompressForWidth:image targetWidth:80 targetHeight:60];
NSString *stringPhotoSmall = [fDataSmall base64EncodedStringWithOptions:0];
UnitySendMessage("GameMain", "ShowMiniPhoto", [stringPhotoSmall UTF8String]);
}
//取消的回调
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
NSLog(@"您取消了选择图片");
[picker dismissViewControllerAnimated:YES completion:^{
}];
UnitySendMessage("GameMain", "CandleShowPhoto", "");
}
//工具:图片裁剪
- (NSData *) imageCompressForWidth:(UIImage *)sourceImage targetWidth:(CGFloat)defineWidth targetHeight:(CGFloat)defineHeight
{
CGSize imageSize = sourceImage.size;
CGFloat width = imageSize.width;
CGFloat height = imageSize.height;
CGFloat targetWidth = defineWidth;
CGFloat targetHeight = (targetWidth / width) * height;
if(targetHeight > defineHeight)
{
targetHeight = defineHeight;
targetWidth = (targetHeight / height) * width;
}
UIGraphicsBeginImageContext(CGSizeMake(targetWidth, targetHeight));
[sourceImage drawInRect:CGRectMake(0,0,targetWidth, targetHeight)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSData *fData = UIImageJPEGRepresentation(newImage, 1);
return fData;
}
//判断是否开启访问相机权限
-(int) GetCameraPermission
{
NSString *mediaType = AVMediaTypeVideo;
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];
if(authStatus == AVAuthorizationStatusRestricted|| authStatus == AVAuthorizationStatusDenied){
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示"
message:@"请在设备的设置-隐私-相机中允许访问相机。"
delegate:self
cancelButtonTitle:@"确定"
otherButtonTitles:nil];
[alert show];
return -1;
}
else if(authStatus == AVAuthorizationStatusAuthorized){//允许访问
return 0;
}
return 0;
}
//判断是否开启访问相册权限
-(int) GEtPickPermission
{
ALAuthorizationStatus author = [ALAssetsLibrary authorizationStatus];
if (author == kCLAuthorizationStatusRestricted || author == kCLAuthorizationStatusDenied){
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示"
message:@"请在设备的设置-隐私-照片中允许访问照片。"
delegate:self
cancelButtonTitle:@"确定"
otherButtonTitles:nil];
[alert show];
return -1;
}
return 0;
}
@end
三、上传图片
已经拿到了图片文件的Steam可以直接上传到服务器,生成CDN链接,将链接分发的其他用户。
如果觉得麻烦或者觉得没有必要自己实现,可以通过云储存功能,将图片直接存到云服务。笔者用的是UCloud。当然市场上还有很多厂家提供云储存功能:七牛、阿里云、腾讯云等。。
使用云储存的好处:
1.奉行“专业的人做专业的事”原则
2.节省了开发时间,减少服务器压力
3.云储存都会提供图像再次处理功能,可以再次压缩图片,使用户下来的图片更小,节省用户流量提高用户体验。
四、下载图片并显示
成功上传到云服务器会自动生成一个链接,可以通过链接直接访问图片
Unity加载外部资源主要依靠www协议。可以直接铜鼓www协议加载图片到游戏中,但是用户不可能永远只点开一个图片一次。笔者先将图片下载到本地,然后通过www协议将本地的图片加载到Unity,如果用户再次点开,可以节省用户的流量。
public void LoadPicByUrlBySmallImage(string _url)
{
string url = "";
#if UNITY_STANDALONE_WIN
url = "file:///" + _url;
#else
url = "file://" + _url;
#endif
StartCoroutine(LoadTextureBySmallImage(url));
}
/// <summary>
/// load大图携程
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
IEnumerator LoadTexture(string name)
{
string path = name;
WWW www = new WWW(path);
yield return www;
if (www.isDone)
{
Texture2D txt = www.texture;
int width = txt.width;
int height = txt.height;
int targetHeight = 400;
int targetWidth = 600;
int newHeight = 0;
int newWidth = 0;
GetCutSize(targetHeight, targetWidth, height, width, ref newHeight, ref newWidth);
if (BigImage != null)
{
BigImage.gameObject.SetActive(true);
BigImage.rectTransform.sizeDelta = new Vector2(newWidth, newHeight);
BigImage.texture = txt;
}
CloseDialogJuHua();
}
}