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

相关文章

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

基于Python实现一个图片拆分工具

《基于Python实现一个图片拆分工具》这篇文章主要为大家详细介绍了如何基于Python实现一个图片拆分工具,可以根据需要的行数和列数进行拆分,感兴趣的小伙伴可以跟随小编一起学习一下... 简单介绍先自己选择输入的图片,默认是输出到项目文件夹中,可以自己选择其他的文件夹,选择需要拆分的行数和列数,可以通过

Python中将嵌套列表扁平化的多种实现方法

《Python中将嵌套列表扁平化的多种实现方法》在Python编程中,我们常常会遇到需要将嵌套列表(即列表中包含列表)转换为一个一维的扁平列表的需求,本文将给大家介绍了多种实现这一目标的方法,需要的朋... 目录python中将嵌套列表扁平化的方法技术背景实现步骤1. 使用嵌套列表推导式2. 使用itert

Python使用pip工具实现包自动更新的多种方法

《Python使用pip工具实现包自动更新的多种方法》本文深入探讨了使用Python的pip工具实现包自动更新的各种方法和技术,我们将从基础概念开始,逐步介绍手动更新方法、自动化脚本编写、结合CI/C... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核