自定义头像
2017-08-13 16:20
113 查看
好久没写博客了,真的好忙啊,没有一点下班时间,这公司好拼!!
游戏中用户的头像不仅能显示系统定义好的头像,而且如果能显示用户自定义的头像肯定能丰富游戏的表现。今天就来讨论下Unity游戏如何实现游戏中显示用户自定义头像的实现。
调用系统原生接口弹出相机或相册供用户获取头像图片
对用户得到的头像进行裁剪压缩
上传CDN服务器或者存到本地目录(Unity项目一般存到Application.persistentDataPath目录里,这个路径不同平台的具体路径网上都有详细的介绍)
用3W从CDN服务器或本地下载头像并渲染
关于cdn服务器,游戏运营商一般会提供给游戏开发商。其大致流程是:游戏客户端上传成功后会返回一个图片的url,之后cdn服务器会进入审核流程(有自动审核和人工审核)以防止用户上传一些涉黄的头像,审核通过与否会通知(这个说法可能不好,有些cdn服务器并不支持把游戏服务器的一个函数注册到cdn服务器,这样可以主动通知游戏服务器。我们现在的做法是游戏服务器会定时去查询cdn服务器头像是否审核通过,这种轮询的做法我感觉很不好)游戏服务器,再由游戏服告诉客户端,此时客户端再做处理。
下载的时候可以做一些缓存处理,即每下载到一个新的头像,可以把它存到本地,这样做的好处是避免频繁访问cdn服务器、节省用户流量,从本地下载的速度快。坏处就是会占用设备一定的内存空间。我们现在的项目一张头像大概4kb,一千个头像大概4M,也还行。现在的手机不缺这点内存了。当然如果觉得这样不好,也可以只缓存特定的头像,比如好友的。
至于上传和下载要不要开线程那要看需求了,如果一次下载量不是很大就算了,我们现在也没开线程。如果一次下载量很大的话最好还是单开线程来处理比较稳。
在java的主类里,注意需要事先(onCreate里)实例化headImage =new MY_HeadImage()
当然还需要在主类里提供一个方法给C#调用,这个方法会弹出系统的一个Alert,供用户选择拍照还是相册里选择头像。
效果图就不上了,项目已用。
另外上传的时候需要把Texture2D转化为byte[],方法是Texture2D.EncodeToJPG()。
游戏中用户的头像不仅能显示系统定义好的头像,而且如果能显示用户自定义的头像肯定能丰富游戏的表现。今天就来讨论下Unity游戏如何实现游戏中显示用户自定义头像的实现。
流程分析
Unity中触发选择自定义头像(相机or相册)调用系统原生接口弹出相机或相册供用户获取头像图片
对用户得到的头像进行裁剪压缩
上传CDN服务器或者存到本地目录(Unity项目一般存到Application.persistentDataPath目录里,这个路径不同平台的具体路径网上都有详细的介绍)
用3W从CDN服务器或本地下载头像并渲染
关于cdn服务器,游戏运营商一般会提供给游戏开发商。其大致流程是:游戏客户端上传成功后会返回一个图片的url,之后cdn服务器会进入审核流程(有自动审核和人工审核)以防止用户上传一些涉黄的头像,审核通过与否会通知(这个说法可能不好,有些cdn服务器并不支持把游戏服务器的一个函数注册到cdn服务器,这样可以主动通知游戏服务器。我们现在的做法是游戏服务器会定时去查询cdn服务器头像是否审核通过,这种轮询的做法我感觉很不好)游戏服务器,再由游戏服告诉客户端,此时客户端再做处理。
下载的时候可以做一些缓存处理,即每下载到一个新的头像,可以把它存到本地,这样做的好处是避免频繁访问cdn服务器、节省用户流量,从本地下载的速度快。坏处就是会占用设备一定的内存空间。我们现在的项目一张头像大概4kb,一千个头像大概4M,也还行。现在的手机不缺这点内存了。当然如果觉得这样不好,也可以只缓存特定的头像,比如好友的。
至于上传和下载要不要开线程那要看需求了,如果一次下载量不是很大就算了,我们现在也没开线程。如果一次下载量很大的话最好还是单开线程来处理比较稳。
实践
Android
写一个头像管理类,负责调用系统相机和相册,裁剪压缩,保存图片。public class MY_HeadImage { public static int HEAD_IMAGE_TAKEPHOTO = 1; public static int HEAD_IMAGE_PICK = 2; public static int HEAD_IMAGE_CROP = 3; public MY_HeadImage() { } /** * 使用摄像机拍照 */ public void takePhoto() { Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); intent.putExtra("output", Uri.fromFile(new File(Environment.getExternalStorageDirectory(), "temp.jpg"))); Activity act = (Activity) UnityPlayerActivity.MainContext; act.startActivityForResult(intent,HEAD_IMAGE_TAKEPHOTO); } /** * 从相册中选择 */ public void pickFromAlbum() { Log.i("TEST","pickFromAlbum"); Intent intent = new Intent("android.intent.action.PICK"); intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); Activity act = (Activity) UnityPlayerActivity.MainContext; act.startActivityForResult(intent, HEAD_IMAGE_PICK); } /** * 裁剪 * @param uri */ public void startPhotoZoom(Uri uri) { Intent intent = new Intent("com.android.camera.action.CROP"); intent.setDataAndType(uri, "image/*"); intent.putExtra("crop", "true"); intent.putExtra("aspectX", 1); intent.putExtra("aspectY", 1); intent.putExtra("outputX", 256); intent.putExtra("outputY", 256); intent.putExtra("return-data", true); Activity act = (Activity) UnityPlayerActivity.MainContext; act.startActivityForResult(intent, HEAD_IMAGE_CROP); } public void SaveBitmap(Bitmap bitmap) throws IOException { Log.i("TEST", "保存文件"); FileOutputStream fOut = null; Activity act = (Activity) UnityPlayerActivity.MainContext; String packageName = act.getPackageName(); //注解 String path = "/mnt/sdcard/Android/data/"+ packageName +"/files/"; try { //查看这个路径是否存在, //如果并没有这个路径, //创建这个路径 File destDir = new File(path); if (!destDir.exists()) { destDir.mkdirs(); } fOut = new FileOutputStream(path + "/image.jpg") ; } catch (FileNotFoundException e) { e.printStackTrace(); } //将Bitmap对象写入本地路径中,Unity在去相同的路径来读取这个文件 bitmap.compress(Bitmap.CompressFormat.JPEG, 5, fOut); try { fOut.flush(); Log.i("TEST", "保存路径:" + path + "/image.jpg"); Log.i("TEST", "success"); //告诉Unity选择头像成功 UnityPlayer.UnitySendMessage("Camera", "PickHeadImgSucc","success!"); } catch (IOException e) { e.printStackTrace(); } try { fOut.close(); } catch (IOException e) { e.printStackTrace(); } } }
在java的主类里,注意需要事先(onCreate里)实例化headImage =new MY_HeadImage()
@Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); if(resultCode != 0) { File uri; if(requestCode == 1) { uri = new File(Environment.getExternalStorageDirectory() + "/temp.jpg"); headImage.startPhotoZoom(Uri.fromFile(uri)); } if(intent != null) { if(requestCode == 2) { headImage.startPhotoZoom(intent.getData()); } Bitmap e; if(requestCode == 3) { Bundle uri2 = intent.getExtras(); if(uri2 != null) { e = (Bitmap)uri2.getParcelable("data"); try { headImage.SaveBitmap(e); } catch (IOException var8) { var8.printStackTrace(); } } } } } }
当然还需要在主类里提供一个方法给C#调用,这个方法会弹出系统的一个Alert,供用户选择拍照还是相册里选择头像。
public void TakePhoto() { Dialog dlg = new AlertDialog.Builder(this).setIcon(R.drawable.app_icon) .setTitle("选择图像").setPositiveButton("相机", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { headImage.takePhoto(); } }).setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }).setNeutralButton("相册", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { headImage.pickFromAlbum(); } }).create(); dlg.show(); }
IOS
IOS就比较简单了,写一个.mm文件即可,一样的做一个头像管理类,负责调用系统相机和相册,裁剪压缩,保存头像等工作@interface MY_HeadImage : UIViewController<UIImagePickerControllerDelegate,UINavigationControllerDelegate> +(MY_HeadImage *)sharedInstance; -(void)MenuSelect; @end //暴露接口,供C#调用 extern "C" void MY_OpenHeadImage(){[[MY_HeadImage sharedInstance] MenuSelect];} @implementation MY_HeadImage static MY_HeadImage *instance = nil; UIViewController * selfView; +(MY_HeadImage *)sharedInstance{ @synchronized(self) { if(instance == nil) { instance = [[[self class] alloc] init]; selfView = UnityGetGLViewController(); } } return instance; } -(void)MenuSelect{ UIAlertController * alertController = [UIAlertController alertControllerWithTitle:@"选择头像" message:@"" preferredStyle:UIAlertControllerStyleActionSheet]; // UIAlertControllerStyleAlert在中央屏幕。 // UIAlertControllerStyleActionSheet在屏幕底部。 UIAlertAction *useCamera = [UIAlertAction actionWithTitle:@"相机" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { NSLog(@"拍照"); [instance pickFromCamera]; }]; UIAlertAction *usePhoto = [UIAlertAction actionWithTitle:@"相册" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { NSLog(@"相册"); [instance pickFromAlbum]; }]; UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil]; [alertController addAction:useCamera]; [alertController addAction:usePhoto]; [alertController addAction:cancelAction]; [selfView presentViewController:alertController animated:YES completion:nil]; } //从相机选择 -(void)pickFromCamera { UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init]; imagePicker.delegate = self; imagePicker.allowsEditing = YES; imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; [selfView presentViewController:imagePicker animated:YES completion:nil]; } //从相册选择 -(void)pickFromAlbum { UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init]; imagePicker.delegate = self; imagePicker.allowsEditing = YES; imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; [selfView presentViewController:imagePicker animated:YES completion:nil]; } //选择完成回调(系统自己调用的,别找了) - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { NSLog(@"选择头像完成"); UIImage *img = [info objectForKey:UIImagePickerControllerEditedImage]; [self performSelector:@selector(saveImage:) withObject:img afterDelay:0.5]; [picker dismissViewControllerAnimated:YES completion:nil]; } //保存图片 - (void)saveImage:(UIImage *)image { BOOL success; NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSString *imageFilePath = [documentsDirectory stringByAppendingPathComponent:@"image.jpg"]; NSLog(@"头像写入[IOS]->>%@",imageFilePath); success = [fileManager fileExistsAtPath:imageFilePath]; if(success) { success = [fileManager removeItemAtPath:imageFilePath error:&error]; } UIImage *smallImage = [self thumbnailWithImageWithoutScale:image size:CGSizeMake(93, 93)]; [UIImageJPEGRepresentation(smallImage, 1.0f) writeToFile:imageFilePath atomically:YES];//写入文件 UnitySendMessage("Camera", "PickHeadImgSucc", "image.jpg"); } //保持原来的长宽比,生成一个缩略图 - (UIImage *)thumbnailWithImageWithoutScale:(UIImage *)image size:(CGSize)asize { UIImage *newimage; if (nil == image) { newimage = nil; } else{ CGSize oldsize = image.size; CGRect rect; if (asize.width/asize.height > oldsize.width/oldsize.height) { rect.size.width = asize.height*oldsize.width/oldsize.height; rect.size.height = asize.height; rect.origin.x = (asize.width - rect.size.width)/2; rect.origin.y = 0; } else{ rect.size.width = asize.width; rect.size.height = asize.width*oldsize.height/oldsize.width; rect.origin.x = 0; rect.origin.y = (asize.height - rect.size.height)/2; } UIGraphicsBeginImageContext(asize); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetFillColorWithColor(context, [[UIColor clearColor] CGColor]); UIRectFill(CGRectMake(0, 0, asize.width, asize.height));//clear background [image drawInRect:rect]; newimage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); } return newimage; } @end
Unity
写一个类挂摄像机上(Camera),主要逻辑:#if UNITY_ANDROID && !UNITY_EDITOR private AndroidJavaClass jc = null; public void OpenSelectorMenu() { jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); AndroidJavaObject JO = jc.GetStatic<AndroidJavaObject>("currentActivity"); JO.Call("TakePhoto"); } #elif UNITY_IPHONE && !UNITY_EDITOR [DllImport("__Internal")] private static extern void MY_OpenHeadImage (); public void OpenSelectorMenu () { MY_OpenHeadImage(); } #else public void OpenSelectorMenu () { } #endif
//在android或者IOS原生代码里调用了 //所以Unity侧引用为0,勿删 //在移动设备上选择好头像后回调到unity public void PickHeadImgSucc ( string str ) { Debug.Log("保存图片成功 image.jpg: " + str); string url = "file://" + Application.persistentDataPath + "/image.jpg"; //在此用3W下载本地头像并渲染 }
效果图就不上了,项目已用。
另外上传的时候需要把Texture2D转化为byte[],方法是Texture2D.EncodeToJPG()。
相关文章推荐
- Android 自定义圆形文字头像
- 安卓自定义圆形头像
- 环信SDK 头像、昵称、表情自定义和群聊设置的实现 一(附源码)
- iOS的Cocos2d-x工程载入CocoStudio制作的动画素材'xxx.csb/csd'文件,添加自定义用户头像图片到动画。
- 解析自定义头像、链接、处理微博上类似 “@” 和 “#” 的特殊转义字符并在UIWebView显示的例子
- 自定义带 vip 标识的 圆形头像(圆形ImageView)
- 根据点击头像的手势获取自定义Cell 的属性, UITableViewCell, 头像, iOS
- [置顶] Android开发之制作圆形头像自定义View,直接引用工具类,加快开发速度。带有源代码学习
- Android开发之制作圆形头像自定义View,直接引用工具类,加快开发速度。带有源代码学习
- 自定义ImageView完成圆形头像自定义
- 环信SDK 头像、昵称、表情自定义和群聊设置的实现 一(附源码)
- Android 高仿微信头像截取 打造不一样的自定义控件
- 自定义圆形头像
- 利用自定义View实现头像截取页面
- 自定义实现圆头像
- 两个比较给力的开源框架(1.头像选择,拍照,裁剪 2.自定义对话框)
- Android 高仿微信头像截取 打造不一样的自定义控件
- 自定义头像圆角控件
- Android 自定义圆形头像