3D动画实现游戏翻牌功能

2024-03-28 17:32

本文主要是介绍3D动画实现游戏翻牌功能,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言:最近项目要做一个类似游戏翻取宝箱的功能来代替以前的签到打卡的功能,一开始完全没有思路,就连3D的翻转动画都不知道怎么实现,更别说还要结合一些特别的UI交互,更是无从下手;两天按我的思路实现之后,写到最后逻辑越来越复杂进行不下去,后来在小组组长的指点下,对牌进行了抽象简化了不少逻辑,进我实现最终完成了,^0^,先看下效果哈

1.积分类型(简单的积分上漂)
themeLove

2.入职红包(从当前位置移动到屏幕右下角我的模块)


3.实物类型(弹出对话框,分享后才能领取)


注:关于翻牌子的奖励规则:一般分为真随机和伪随机。1:真随机就是后台控制概率,完全随机,当然贵重物品的概率都是很低的或者直接是0,只是用来展示的,你永远抽不到的。。;2:伪随机就是不是完全的随机了,比如你的中奖概率更你的等级呀,你的充值金额呀相关联,比如把你的充值金额当做一个计算因数包含在计算你的中奖概率上,这样你充值消费的越多,中奖概率越大。当然这些概率之类的控制都是服务器端控制的,我们做Android前端只是负责调用接口,结合设计UI做展示而已,大头还是在服务器

下面是具体的实现过程步骤(为了用户体验:我们的这个抽奖页面是在用于调用抽奖接口成功之后才会出现的,其实就是只要这个抽奖的Activity能弹出,你的此次抽奖结果就已经确定了,剩下的就是我们客户端实现了,只要用户不管点击哪一个,我们就把你此次抽中的结果放到哪个位置,就是客户端根据调用接口返回来的数据来重新构造数据刷新Adapter)


(一)对不同类型牌进行抽象,把公用的属性和方法放在父类,让每种牌自身都具有执行不同动画的功能,提供不同的方法:(比如根据当前牌是否是抽奖结果决定是否延时翻转,根据当前牌的类型决定牌全部翻            转过来之后执行不同的动画)
(二)3D动画的实现
(三)整体布局是用GridView,设置用户未点击时GridView的默认显示效果
(四)然后是每个item的点击事件,根据抽奖结果,重新构造数据,刷新Adapter,同时根据抽中奖品的类型,来执行不同的UI交互


具体实现由于是公司项目,项目较大源代码不变给出,只是做部分截图

(一)抽象牌:

1.牌的类型:空牌(未点击时的默认牌);现金牌;蛙币牌;入职红包牌;实物牌;谢谢参与牌;目录结构如下
2.抽象牌的父类(Card):由于不同类型的牌要展示的效果不一样,而且点击过后翻转过来的View并不是一张简单的图片;比如说入职红包牌,其中的88元,88大礼包,还有背景图片都是不同的控件,因为这些字段都是从服务器获取我们用控件设置上去的,并不是只有一张图片的url让我们去加载,那样的话就简单很多了。这样的话,我们就不能只写一个type的布局了,应为那样的话,我们按正常的做法就是根据服务器给我们对象的类型来显示隐藏或显示不同的布局(可是这里我们有6中类型了呀,以后可能还要加),那样逻辑代码肯定会把你写晕的,而且bug百出,当然这也是对多类型数据对象展示最low的一种方法了;我们当然可以重新适配器中getTypeCount的方法在getView的时候更具不同的type来inflater不同的layout文件,这样做法显然比只用一中type要好,(ps:楼主开始也是这么做的,可是写到中间发现也是比较复杂),但是这样写不便于扩展,后面要加牌的类型我们还是要得改adapter里面getTypeCount和getView里面的代码,不便于扩展,而且每种牌要根据类型 要执行不同的后续操作,考虑再三我们还是得将其抽象成一个Card父类,并在父类中定义一些共有的方法,比如说getView方法返回翻转过来要展示的视图。

代码如下:
public abstract class Card {Context mContext;LotteryItem mItem;View mView;boolean isHit;//是否是选中的那个View mFlopView;VLImageView mFlopView1;View mFlopView2;int mIndex = 0;int mRotateX;int mRotateY;public Card(Context context) {mContext = context;
//        getView();}public Card(Context context, LotteryItem item) {mContext = context;mItem = item;
//        getView();}public void setHit(boolean b) {isHit = b;}public abstract View getView();public abstract void prepareAnimation();public abstract void startAnimation();public abstract void endAnimation();public abstract void endAnimation(int left, int itemWidth, int top, int itemHeight);
/*    public View getViewDiaplay(){if (mView!=null){return mView;}else{return null;}}*/public abstract void setRotateXY(int x, int y);
}
3.具体每种类型牌的具体类型(这里只拿其中2种来说明,一种默认图(未翻转类型),一种较为复杂的入职红包类型)。
EmptyCard:(这里只要实现getView方法即可,其余可以不实现,因为这里只是EmptyCard只是未点击时的默认展示牌,且图片也是本地的)
public class EmptyCard extends  Card {public EmptyCard(Context context) {super(context);}@Overridepublic void endAnimation() {<span style="color:#ff6666;"> </span>}@Overridepublic View getView() {<span style="white-space:pre">	</span><span style="color:#ff0000;">//空牌是一张默认图片,直接加载本地图片,这里是用fresco封装的加载默认图片方法</span>I90ImageLoaderModel mI90ImageLoaderModel = VLApplication.instance().getModel(I90ImageLoaderModel.class);View view = View.inflate(mContext, R.layout.item_empty_card, null);VLImageView cardPic = (VLImageView) view.findViewById(R.id.emptyCardPic);int rail= VLUtils.dip2px(4);cardPic.setCornersRadii(rail,rail,rail,rail);cardPic.setVlScaleType(VLImageView.VLSCALE_TYPE_CENTER_CROP);cardPic.apply();mI90ImageLoaderModel.renderDrawableImage(R.drawable.card_default,200,200,cardPic);mView=view;return view;}@Overridepublic void prepareAnimation() {if (isHit){startAnimation();}else{  <span style="color:#ff0000;"> // VLScheduler是项目中封装的类似Handler类</span>VLScheduler.instance.schedule(1000, VLScheduler.THREAD_MAIN, new VLBlock() {@Overrideprotected void process(boolean canceled) {startAnimation();}});}}@Overridepublic void startAnimation() {<span style="color:#ff0000;">//结合Rotate3dAnimation实现3D翻转效果</span>Rotate3dAnimation rotation = new Rotate3dAnimation(0, 90, mRotateX, mRotateY, 0.0f, true);rotation.setDuration(500);rotation.setFillAfter(true);rotation.setInterpolator(new AccelerateInterpolator());
//      rotation.setAnimationListener(new);rotation.setAnimationListener(new Animation.AnimationListener(){@Overridepublic void onAnimationEnd(Animation animation) {mView.post(new SwapViews());}@Overridepublic void onAnimationRepeat(Animation animation) {}@Overridepublic void onAnimationStart(Animation animation) {}});mFlopView.startAnimation(rotation);}private class SwapViews implements Runnable{@Overridepublic void run() {mFlopView1.setVisibility(View.GONE);mFlopView2.setVisibility(View.GONE);mIndex++;if (0 == mIndex % 2) {mFlopView = mFlopView1;} else {mFlopView = mFlopView2;}mFlopView.setVisibility(View.VISIBLE);mFlopView.requestFocus();Rotate3dAnimation rotation = new Rotate3dAnimation(-90, 0, mRotateX, mRotateY, 0.0f, false);rotation.setDuration(500);rotation.setFillAfter(true);rotation.setInterpolator(new DecelerateInterpolator());mFlopView.startAnimation(rotation);}}public void endAnimation(int left,int itemWidth,int top,int itemHeight) {AnimationSet floatAnimSet = new AnimationSet(true);TranslateAnimation transAnim = new TranslateAnimation(Animation.RELATIVE_TO_SELF,0, Animation.ABSOLUTE, VLUtils.getScreenWidth(mContext)-left-itemWidth,Animation.RELATIVE_TO_SELF,0, Animation.ABSOLUTE, VLUtils.getScreenHeight(mContext)-top-itemHeight);transAnim.setDuration(2000);ScaleAnimation scaleAnim = new ScaleAnimation(1.0f, 0.0f, 1.0f, 0.0f,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);scaleAnim.setDuration(2000);
//        scaleAnim.setFillAfter(true);AlphaAnimation alphaAnim = new AlphaAnimation(1.0f, 0.0f);alphaAnim.setDuration(2000);floatAnimSet.addAnimation(scaleAnim);floatAnimSet.addAnimation(transAnim);floatAnimSet.addAnimation(alphaAnim);mView.startAnimation(floatAnimSet);}@Overridepublic void setRotateXY(int x, int y) {mRotateX=x;mRotateY=y;}
}

JobCard
public class JobCard extends Card {public JobCard(Context context,LotteryItem item) {<span style="color:#ff0000;">//LotteryItem是服务器获取的奖品对象</span>super(context,item);}public void endAnimation(int left,int itemWidth,int top,int itemHeight) {<span style="color:#ff0000;">//入职红包牌对应的是要从所点击的地方执行动画到右下角,其中的参数是从外部调用的地方经过动态测量后传递进来的,另外特别的是在gridView直接执行子View的移动,子View是不会超出GridView这个大的父布局的,而且是从gridView的底部移动的,(分割线的底部,要有多丑就有多丑。。。),所以我们要根据点击的位置重新创建一个牌对象放在整个Activity的根布局上并且它的位置要和我们点击的位置要完全重合,这样才不会漏出破绽;</span>AnimationSet floatAnimSet = new AnimationSet(true);TranslateAnimation transAnim = new TranslateAnimation(Animation.RELATIVE_TO_SELF,0, Animation.ABSOLUTE, VLUtils.getScreenWidth(mContext)-left-itemWidth,Animation.RELATIVE_TO_SELF,0, Animation.ABSOLUTE, VLUtils.getScreenHeight(mContext)-top-itemHeight);transAnim.setDuration(2000);ScaleAnimation scaleAnim = new ScaleAnimation(1.0f, 0.0f, 1.0f, 0.0f,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);scaleAnim.setDuration(2000);
//        scaleAnim.setFillAfter(true);AlphaAnimation alphaAnim = new AlphaAnimation(1.0f, 0.0f);alphaAnim.setDuration(2000);floatAnimSet.addAnimation(scaleAnim);floatAnimSet.addAnimation(transAnim);floatAnimSet.addAnimation(alphaAnim);floatAnimSet.setFillAfter(true);mView.startAnimation(floatAnimSet);}@Overridepublic View getView() {<span style="color:#ff0000;">//根据传递进来的LotteryItem属性,初始化View视图</span>I90ImageLoaderModel mI90ImageLoaderModel = VLApplication.instance().getModel(I90ImageLoaderModel.class);int rail = VLUtils.dip2px(4);View view = View.inflate(mContext, R.layout.item_job_card, null);VLImageView cardEmpty = (VLImageView) view.findViewById(R.id.jobEmpty);cardEmpty.setCornersRadii(rail,rail,rail,rail);cardEmpty.setVlScaleType(VLImageView.VLSCALE_TYPE_CENTER_CROP);cardEmpty.apply();mI90ImageLoaderModel.renderDrawableImage(R.drawable.card_default,200,200,cardEmpty);View cardView = view.findViewById(R.id.jobView);VLImageView cardTypeImage = (VLImageView) view.findViewById(R.id.jobCardTypeImage);TextView cardCount =(TextView) view.findViewById(R.id.jobCardCount);TextView cardType = (TextView)view.findViewById(R.id.jobCardType);ImageView cardBg = (ImageView)view.findViewById(R.id.jobCardBg);cardTypeImage.setCornersRadii(0, 0, rail, rail);cardTypeImage.setVlScaleType(VLImageView.VLSCALE_TYPE_CENTER_CROP);cardTypeImage.apply();mI90ImageLoaderModel.renderDrawableImage(R.drawable.card_image1, 200, 52, cardTypeImage);cardCount.setText(VLUtils.androidSizeSpan((int)mItem.getPrototype().getCash()+"", 0xfff55c3d, 0, 60));cardCount.append(VLUtils.androidSizeSpan("元", 0xfff55c3d, 0, 24));cardBg.setVisibility(isHit ? View.GONE : View.VISIBLE);if (TextUtils.isEmpty(mItem.getPrototype().getName())){cardType.setText("入职红包");}else{cardType.setText(mItem.getPrototype().getName());}mFlopView=cardEmpty;mFlopView1=cardEmpty;mFlopView2=cardView;mView=view;return view;}@Overridepublic void prepareAnimation() {VLDebug.logD("startAnimation=" + " class=" + getClass().getSimpleName() + "isHit="+isHit);if (isHit){<span style="color:#ff0000;">//根据当前牌是否是用户本次抽中的牌,来决定是否来延迟翻转,从gif图中可以看出,用户点中的牌立刻执行翻转动画,而其他的牌有1秒钟的延迟</span>startAnimation();}else{<span style="white-space:pre">	</span>  <span style="color:#ff0000;"> //延迟翻转</span>VLScheduler.instance.schedule(1000, VLScheduler.THREAD_MAIN, new VLBlock() {@Overrideprotected void process(boolean canceled) {startAnimation();}});}}@Overridepublic void startAnimation() {<span style="color:#ff0000;">//真正翻转动画</span>VLDebug.logD("startAnimation= do" + " class=" + getClass().getSimpleName() + "isHit="+isHit);Rotate3dAnimation rotation = new Rotate3dAnimation(0, 90, mRotateX, mRotateY, 0.0f, true);rotation.setDuration(500);rotation.setFillAfter(true);rotation.setInterpolator(new AccelerateInterpolator());
//      rotation.setAnimationListener(new);rotation.setAnimationListener(new Animation.AnimationListener(){@Overridepublic void onAnimationEnd(Animation animation) {mView.post(new SwapViews());}@Overridepublic void onAnimationRepeat(Animation animation) {}@Overridepublic void onAnimationStart(Animation animation) {}});mFlopView.startAnimation(rotation);}@Overridepublic void endAnimation() {}
<span style="color:#ff0000;">//  Rotate3dAnimation结合该类实现3d翻转,原理是2个View同时执行翻转动画,到一定角度显示隐藏一个布局</span>private class SwapViews implements Runnable{@Overridepublic void run() {mFlopView1.setVisibility(View.GONE);mFlopView2.setVisibility(View.GONE);mIndex++;if (0 == mIndex % 2) {mFlopView = mFlopView1;} else {mFlopView = mFlopView2;}mFlopView.setVisibility(View.VISIBLE);Rotate3dAnimation rotation = new Rotate3dAnimation(-90, 0, mRotateX, mRotateY, 0.0f, false);rotation.setDuration(500);rotation.setFillAfter(true);rotation.setInterpolator(new DecelerateInterpolator());mFlopView.startAnimation(rotation);}}@Overridepublic void setRotateXY(int x, int y) {<span style="color:#ff0000;">//设置旋转的轴线(属性动画的同学应该都知道执行动画应该按照一定的轴线旋转)这里的值也是外部调用时动态测量每个GridView的Item的宽高后设置进来的</span>mRotateX=x;mRotateY=y;}
}

(二 )3D动画的执行

3D翻转的动画Android本身并不支持,可以利用valueAnimator和objectAnimator中rotationY,rotationX,rotationZ可以实现由3d的效果,但是只支持一张图片,所以用的时候还是要封装,楼主当时做的时候对valueAnimator和objectAnimator还不是很了解,所以还是从网上搜罗了一个自定义动画Rotate3dAnimation,这个应该是一个老外写的ps:注释全是英文。。。后来楼主看了这位大神的blog: http://my.csdn.net/harvic880925的博客后自己实现了一个支持3d动画的自定义控件( http://blog.csdn.net/themelove/article/details/50619771)效果还不错

(三)每个item的点击事件(根据抽奖结果,重新构造数据,刷新Adapter,同时根据抽中奖品的类型,来执行不同的UI交互,当然是在Activity和Adapter里进行了)


MyFlopAdaper
public class MyFlopAdapter extends BaseAdapter {private LayoutInflater mInflater;private ArrayList<Card> mDatas;private int mHeight;VLAsyncHandler mVlAsyncHandler;public void setLoadEnd(VLAsyncHandler vlAsyncHandler) {mVlAsyncHandler = vlAsyncHandler;}public MyFlopAdapter(Context context, int height) {mInflater = LayoutInflater.from(context);mHeight = height;}public void setData(ArrayList<Card> datas) {mDatas = datas;}@Overridepublic int getCount() {return mDatas.size();}@Overridepublic Object getItem(int position) {return null;}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {if (convertView == null) {convertView = mInflater.inflate(R.layout.group_daydayflop_row_item, parent, false);convertView.setLayoutParams(new AbsListView.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, mHeight / 3- VLUtils.dip2px(8)));}if (position== parent.getChildCount()) {FrameLayout frameLayout = (FrameLayout) convertView;frameLayout.removeAllViews();frameLayout.addView(mDatas.get(position).getView(), new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));if (null != mVlAsyncHandler && position == mDatas.size() - 1) {mVlAsyncHandler.handlerSuccess();}}VLDebug.logD("getView  position= " + position);return convertView;}}

DaydayFlopActivity(代码太多,直接上这几个相关类的压缩包吧)

相关类压缩包 http://download.csdn.net/detail/themelove/9424747




这篇关于3D动画实现游戏翻牌功能的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/856251

相关文章

python实现svg图片转换为png和gif

《python实现svg图片转换为png和gif》这篇文章主要为大家详细介绍了python如何实现将svg图片格式转换为png和gif,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录python实现svg图片转换为png和gifpython实现图片格式之间的相互转换延展:基于Py

Python利用ElementTree实现快速解析XML文件

《Python利用ElementTree实现快速解析XML文件》ElementTree是Python标准库的一部分,而且是Python标准库中用于解析和操作XML数据的模块,下面小编就来和大家详细讲讲... 目录一、XML文件解析到底有多重要二、ElementTree快速入门1. 加载XML的两种方式2.

Java的栈与队列实现代码解析

《Java的栈与队列实现代码解析》栈是常见的线性数据结构,栈的特点是以先进后出的形式,后进先出,先进后出,分为栈底和栈顶,栈应用于内存的分配,表达式求值,存储临时的数据和方法的调用等,本文给大家介绍J... 目录栈的概念(Stack)栈的实现代码队列(Queue)模拟实现队列(双链表实现)循环队列(循环数组

C++如何通过Qt反射机制实现数据类序列化

《C++如何通过Qt反射机制实现数据类序列化》在C++工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作,所以本文就来聊聊C++如何通过Qt反射机制实现数据类序列化吧... 目录设计预期设计思路代码实现使用方法在 C++ 工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作。由于数据类

Python实现图片分割的多种方法总结

《Python实现图片分割的多种方法总结》图片分割是图像处理中的一个重要任务,它的目标是将图像划分为多个区域或者对象,本文为大家整理了一些常用的分割方法,大家可以根据需求自行选择... 目录1. 基于传统图像处理的分割方法(1) 使用固定阈值分割图片(2) 自适应阈值分割(3) 使用图像边缘检测分割(4)

Android实现在线预览office文档的示例详解

《Android实现在线预览office文档的示例详解》在移动端展示在线Office文档(如Word、Excel、PPT)是一项常见需求,这篇文章为大家重点介绍了两种方案的实现方法,希望对大家有一定的... 目录一、项目概述二、相关技术知识三、实现思路3.1 方案一:WebView + Office Onl

C# foreach 循环中获取索引的实现方式

《C#foreach循环中获取索引的实现方式》:本文主要介绍C#foreach循环中获取索引的实现方式,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、手动维护索引变量二、LINQ Select + 元组解构三、扩展方法封装索引四、使用 for 循环替代

Spring Security+JWT如何实现前后端分离权限控制

《SpringSecurity+JWT如何实现前后端分离权限控制》本篇将手把手教你用SpringSecurity+JWT搭建一套完整的登录认证与权限控制体系,具有很好的参考价值,希望对大家... 目录Spring Security+JWT实现前后端分离权限控制实战一、为什么要用 JWT?二、JWT 基本结构

Java实现优雅日期处理的方案详解

《Java实现优雅日期处理的方案详解》在我们的日常工作中,需要经常处理各种格式,各种类似的的日期或者时间,下面我们就来看看如何使用java处理这样的日期问题吧,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言一、日期的坑1.1 日期格式化陷阱1.2 时区转换二、优雅方案的进阶之路2.1 线程安全重构2

Android实现两台手机屏幕共享和远程控制功能

《Android实现两台手机屏幕共享和远程控制功能》在远程协助、在线教学、技术支持等多种场景下,实时获得另一部移动设备的屏幕画面,并对其进行操作,具有极高的应用价值,本项目旨在实现两台Android手... 目录一、项目概述二、相关知识2.1 MediaProjection API2.2 Socket 网络