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和OpenCV库实现实时颜色识别系统

《使用Python和OpenCV库实现实时颜色识别系统》:本文主要介绍使用Python和OpenCV库实现的实时颜色识别系统,这个系统能够通过摄像头捕捉视频流,并在视频中指定区域内识别主要颜色(红... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间详解

PostgreSQL中MVCC 机制的实现

《PostgreSQL中MVCC机制的实现》本文主要介绍了PostgreSQL中MVCC机制的实现,通过多版本数据存储、快照隔离和事务ID管理实现高并发读写,具有一定的参考价值,感兴趣的可以了解一下... 目录一 MVCC 基本原理python1.1 MVCC 核心概念1.2 与传统锁机制对比二 Postg

SpringBoot整合Flowable实现工作流的详细流程

《SpringBoot整合Flowable实现工作流的详细流程》Flowable是一个使用Java编写的轻量级业务流程引擎,Flowable流程引擎可用于部署BPMN2.0流程定义,创建这些流程定义的... 目录1、流程引擎介绍2、创建项目3、画流程图4、开发接口4.1 Java 类梳理4.2 查看流程图4

C++中零拷贝的多种实现方式

《C++中零拷贝的多种实现方式》本文主要介绍了C++中零拷贝的实现示例,旨在在减少数据在内存中的不必要复制,从而提高程序性能、降低内存使用并减少CPU消耗,零拷贝技术通过多种方式实现,下面就来了解一下... 目录一、C++中零拷贝技术的核心概念二、std::string_view 简介三、std::stri

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

OpenCV实现实时颜色检测的示例

《OpenCV实现实时颜色检测的示例》本文主要介绍了OpenCV实现实时颜色检测的示例,通过HSV色彩空间转换和色调范围判断实现红黄绿蓝颜色检测,包含视频捕捉、区域标记、颜色分析等功能,具有一定的参考... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间

苹果macOS 26 Tahoe主题功能大升级:可定制图标/高亮文本/文件夹颜色

《苹果macOS26Tahoe主题功能大升级:可定制图标/高亮文本/文件夹颜色》在整体系统设计方面,macOS26采用了全新的玻璃质感视觉风格,应用于Dock栏、应用图标以及桌面小部件等多个界面... 科技媒体 MACRumors 昨日(6 月 13 日)发布博文,报道称在 macOS 26 Tahoe 中

Python实例题之pygame开发打飞机游戏实例代码

《Python实例题之pygame开发打飞机游戏实例代码》对于python的学习者,能够写出一个飞机大战的程序代码,是不是感觉到非常的开心,:本文主要介绍Python实例题之pygame开发打飞机... 目录题目pygame-aircraft-game使用 Pygame 开发的打飞机游戏脚本代码解释初始化部

Python实现精准提取 PDF中的文本,表格与图片

《Python实现精准提取PDF中的文本,表格与图片》在实际的系统开发中,处理PDF文件不仅限于读取整页文本,还有提取文档中的表格数据,图片或特定区域的内容,下面我们来看看如何使用Python实... 目录安装 python 库提取 PDF 文本内容:获取整页文本与指定区域内容获取页面上的所有文本内容获取

基于Python实现一个Windows Tree命令工具

《基于Python实现一个WindowsTree命令工具》今天想要在Windows平台的CMD命令终端窗口中使用像Linux下的tree命令,打印一下目录结构层级树,然而还真有tree命令,但是发现... 目录引言实现代码使用说明可用选项示例用法功能特点添加到环境变量方法一:创建批处理文件并添加到PATH1