欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Unity实现游戏卡牌滚动效果

程序员文章站 2023-12-06 13:52:52
最近项目中的活动面板要做来回滚动卡牌预览效果,感觉自己来写的话,也能写,但是可能会比较耗时,看到github上有开源的项目,于是就借用了,github的资源地址,感谢作者的...

最近项目中的活动面板要做来回滚动卡牌预览效果,感觉自己来写的话,也能写,但是可能会比较耗时,看到github上有开源的项目,于是就借用了,github的资源地址,感谢作者的分享。

本篇博客旨在告诉大家如何利用这个插件。

插件的核心在于工程中的6个脚本,以下是六个脚本的源码:

dragenhanceview.cs

using unityengine;
using system.collections;
using unityengine.ui;
using unityengine.eventsystems;
 
public class uguienhanceitem : enhanceitem
{
 private button ubutton;
 private image image;
 
 protected override void onstart()
 {
 image = getcomponent<image>();
 ubutton = getcomponent<button>();
 ubutton.onclick.addlistener(onclickuguibutton);
 }
 
 private void onclickuguibutton()
 {
 onclickenhanceitem();
 }
 
 // set the item "depth" 2d or 3d
 protected override void setitemdepth(float depthcurvevalue, int depthfactor, float itemcount)
 {
 int newdepth = (int)(depthcurvevalue * itemcount);
 this.transform.setsiblingindex(newdepth);
 }
 
 public override void setselectstate(bool iscenter)
 {
 if (image == null)
  image = getcomponent<image>();
 image.color = iscenter ? color.white : color.gray;
 }
}

enhancescrollviewdragcontroller.cs

using unityengine;
using system.collections;
 
public class enhancescrollviewdragcontroller : monobehaviour
{
 private vector2 lastposition = vector2.zero;
 private vector2 cachedposition = vector2.zero;
 private gameobject dragtarget;
 
 private camera targetcamera;
 private int raycastmask = 0;
 private bool dragstart = false;
 
 public void settargetcameraandmask(camera camera, int mask)
 {
 this.targetcamera = camera;
 this.raycastmask = mask;
 }
 
 void update()
 {
 if (this.targetcamera == null)
  return;
#if unity_editor
 processmouseinput();
#elif unity_ios || unity_android
 processtouchinput();
#endif
 }
 
 /// <summary>
 /// process mouse input
 /// </summary>
 private void processmouseinput()
 {
 if (input.getmousebuttondown(0))
 {
  if (targetcamera == null)
  return;
  dragtarget = raycast(this.targetcamera, input.mouseposition);
  lastposition.x = input.mouseposition.x;
  lastposition.y = input.mouseposition.y;
 }
 if (input.getmousebutton(0))
 {
  if (dragtarget == null)
  return;
  cachedposition.x = input.mouseposition.x;
  cachedposition.y = input.mouseposition.y;
  vector2 delta = cachedposition - lastposition;
  if (!dragstart && delta.sqrmagnitude != 0f)
  dragstart = true;
 
  if (dragstart)
  {
  // notify target
  dragtarget.sendmessage("onenhanceviewdrag", delta, sendmessageoptions.dontrequirereceiver);
  }
  lastposition = cachedposition;
 }
 
 if (input.getmousebuttonup(0))
 {
  if (dragtarget != null && dragstart)
  {
  dragtarget.sendmessage("onenhaneviewdragend", sendmessageoptions.dontrequirereceiver);
  }
  dragtarget = null;
  dragstart = false;
 }
 }
 
 /// <summary>
 /// process touch input
 /// </summary>
 private void processtouchinput()
 {
 if (input.touchcount > 0)
 {
  touch touch = input.gettouch(0);
  if (touch.phase == touchphase.began)
  {
  if (targetcamera == null)
   return;
  dragtarget = raycast(this.targetcamera, input.mouseposition);
  }
  else if (touch.phase == touchphase.moved)
  {
  if (dragtarget == null)
   return;
  if (!dragstart && touch.deltaposition.sqrmagnitude != 0f)
  {
   dragstart = true;
  }
  if (dragstart)
  {
   // notify target
   dragtarget.sendmessage("onenhanceviewdrag", touch.deltaposition, sendmessageoptions.dontrequirereceiver);
  }
  }
  else if (touch.phase == touchphase.ended)
  {
  if (dragtarget != null && dragstart)
  {
   dragtarget.sendmessage("onenhaneviewdragend", sendmessageoptions.dontrequirereceiver);
  }
  dragtarget = null;
  dragstart = false;
  }
 }
 }
 
 public gameobject raycast(camera cam, vector3 inpos)
 {
 vector3 pos = cam.screentoviewportpoint(inpos);
 if (float.isnan(pos.x) || float.isnan(pos.y))
  return null;
 if (pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y > 1f) return null;
 
 ray ray = cam.screenpointtoray(inpos);
 float dis = 100f;
 raycasthit[] hits = physics.raycastall(ray, dis, raycastmask);
 if (hits.length > 0)
 {
  for (int i = 0; i < hits.length; i++)
  {
  gameobject go = hits[i].collider.gameobject;
  dragenhanceview dragview = go.getcomponent<dragenhanceview>();
  if (dragview == null)
   continue;
  else
  {
   // just return current hover object our drag target
   return go;
  }
  }
 }
 return null;
 }
}

enhanceitem.cs

using unityengine;
using system.collections;
 
public class enhanceitem : monobehaviour
{
 // start index
 private int curveoffsetindex = 0;
 public int curveoffsetindex
 {
 get { return this.curveoffsetindex; }
 set { this.curveoffsetindex = value; }
 }
 
 // runtime real index(be calculated in runtime)
 private int currealindex = 0;
 public int realindex
 {
 get { return this.currealindex; }
 set { this.currealindex = value; }
 }
 
 // curve center offset 
 private float dcurvecenteroffset = 0.0f;
 public float centeroffset
 {
 get { return this.dcurvecenteroffset; }
 set { dcurvecenteroffset = value; }
 }
 private transform mtrs;
 
 void awake()
 {
 mtrs = this.transform;
 onawake();
 }
 
 void start()
 {
 onstart();
 }
 
 // update item's status
 // 1. position
 // 2. scale
 // 3. "depth" is 2d or z position in 3d to set the front and back item
 public void updatescrollviewitems(
 float xvalue,
 float depthcurvevalue,
 int depthfactor,
 float itemcount,
 float yvalue,
 float scalevalue)
 {
 vector3 targetpos = vector3.one;
 vector3 targetscale = vector3.one;
 // position
 targetpos.x = xvalue;
 targetpos.y = yvalue;
 mtrs.localposition = targetpos;
 
 // set the "depth" of item
 // targetpos.z = depthvalue;
 setitemdepth(depthcurvevalue, depthfactor, itemcount);
 // scale
 targetscale.x = targetscale.y = scalevalue;
 mtrs.localscale = targetscale;
 }
 
 protected virtual void onclickenhanceitem()
 {
 enhancescrollview.getinstance.sethorizontaltargetitemindex(this);
 }
 
 protected virtual void onstart()
 {
 }
 
 protected virtual void onawake()
 {
 }
 
 protected virtual void setitemdepth(float depthcurvevalue, int depthfactor, float itemcount)
 {
 }
 
 // set the item center state
 public virtual void setselectstate(bool iscenter)
 {
 }
}

enhancescrollview.cs

using unityengine;
using system.collections;
using system.collections.generic;
 
public class enhancescrollview : monobehaviour
{
 public enum inputsystemtype
 {
 nguiandworldinput, // use enhancescrollviewdragcontroller.cs to get the input(keyboard and touch)
 uguiinput,  // use udragenhanceview for each item to get drag event
 }
 
 // input system type(ngui or 3d world, ugui)
 public inputsystemtype inputtype = inputsystemtype.nguiandworldinput;
 // control the item's scale curve
 public animationcurve scalecurve;
 // control the position curve
 public animationcurve positioncurve;
 // control the "depth"'s curve(in 3d version just the z value, in 2d ui you can use the depth(ngui))
 // note:
 // 1. in ngui set the widget's depth may cause performance problem
 // 2. if you use 3d ui just set the item's z position
 public animationcurve depthcurve = new animationcurve(new keyframe(0, 0), new keyframe(0.5f, 1), new keyframe(1, 0));
 // the start center index
 [tooltip("the start center index")]
 public int startcenterindex = 0;
 // offset width between item
 public float cellwidth = 10f;
 private float totalhorizontalwidth = 500.0f;
 // vertical fixed position value 
 public float yfixedpositionvalue = 46.0f;
 
 // lerp duration
 public float lerpduration = 0.2f;
 private float mcurrentduration = 0.0f;
 private int mcenterindex = 0;
 public bool enablelerptween = true;
 
 // center and precentered item
 private enhanceitem curcenteritem;
 private enhanceitem precenteritem;
 
 // if we can change the target item
 private bool canchangeitem = true;
 private float dfactor = 0.2f;
 
 // originhorizontalvalue lerp to horizontaltargetvalue
 private float originhorizontalvalue = 0.1f;
 public float curhorizontalvalue = 0.5f;
 
 // "depth" factor (2d widget depth or 3d z value)
 private int depthfactor = 5;
 
 // drag enhance scroll view
 [tooltip("camera for drag ray cast")]
 public camera sourcecamera;
 private enhancescrollviewdragcontroller dragcontroller;
 
 public void enabledrag(bool isenabled)
 {
 if (isenabled)
 {
  if (inputtype == inputsystemtype.nguiandworldinput)
  {
  if (sourcecamera == null)
  {
   debug.logerror("## source camera for drag scroll view is null ##");
   return;
  }
 
  if (dragcontroller == null)
   dragcontroller = gameobject.addcomponent<enhancescrollviewdragcontroller>();
  dragcontroller.enabled = true;
  // set the camera and mask
  dragcontroller.settargetcameraandmask(sourcecamera, (1 << layermask.nametolayer("ui")));
  }
 }
 else
 {
  if (dragcontroller != null)
  dragcontroller.enabled = false;
 }
 }
 
 // targets enhance item in scroll view
 public list<enhanceitem> listenhanceitems;
 // sort to get right index
 private list<enhanceitem> listsorteditems = new list<enhanceitem>();
 
 private static enhancescrollview instance;
 public static enhancescrollview getinstance
 {
 get { return instance; }
 }
 
 void awake()
 {
 instance = this;
 }
 
 void start()
 {
 canchangeitem = true;
 int count = listenhanceitems.count;
 dfactor = (mathf.roundtoint((1f / count) * 10000f)) * 0.0001f;
 mcenterindex = count / 2;
 if (count % 2 == 0)
  mcenterindex = count / 2 - 1;
 int index = 0;
 for (int i = count - 1; i >= 0; i--)
 {
  listenhanceitems[i].curveoffsetindex = i;
  listenhanceitems[i].centeroffset = dfactor * (mcenterindex - index);
  listenhanceitems[i].setselectstate(false);
  gameobject obj = listenhanceitems[i].gameobject;
 
  if (inputtype == inputsystemtype.nguiandworldinput)
  {
  dragenhanceview script = obj.getcomponent<dragenhanceview>();
  if (script != null)
   script.setscrollview(this);
  }
  else
  {
  udragenhanceview script = obj.getcomponent<udragenhanceview>();
  if (script != null)
   script.setscrollview(this);
  }
  index++;
 }
 
 // set the center item with startcenterindex
 if (startcenterindex < 0 || startcenterindex >= count)
 {
  debug.logerror("## startcenterindex < 0 || startcenterindex >= listenhanceitems.count out of index ##");
  startcenterindex = mcenterindex;
 }
 
 // sorted items
 listsorteditems = new list<enhanceitem>(listenhanceitems.toarray());
 totalhorizontalwidth = cellwidth * count;
 curcenteritem = listenhanceitems[startcenterindex];
 curhorizontalvalue = 0.5f - curcenteritem.centeroffset;
 lerptweentotarget(0f, curhorizontalvalue, false);
 
 // 
 // enable the drag actions
 // 
 enabledrag(true);
 }
 
 private void lerptweentotarget(float originvalue, float targetvalue, bool needtween = false)
 {
 if (!needtween)
 {
  sortenhanceitem();
  originhorizontalvalue = targetvalue;
  updateenhancescrollview(targetvalue);
  this.ontweenover();
 }
 else
 {
  originhorizontalvalue = originvalue;
  curhorizontalvalue = targetvalue;
  mcurrentduration = 0.0f;
 }
 enablelerptween = needtween;
 }
 
 public void disablelerptween()
 {
 this.enablelerptween = false;
 }
 
 /// 
 /// update enhanceitem state with curve ftime value
 /// 
 public void updateenhancescrollview(float fvalue)
 {
 for (int i = 0; i < listenhanceitems.count; i++)
 {
  enhanceitem itemscript = listenhanceitems[i];
  float xvalue = getxposvalue(fvalue, itemscript.centeroffset);
  float scalevalue = getscalevalue(fvalue, itemscript.centeroffset);
  float depthcurvevalue = depthcurve.evaluate(fvalue + itemscript.centeroffset);
  itemscript.updatescrollviewitems(xvalue, depthcurvevalue, depthfactor, listenhanceitems.count, yfixedpositionvalue, scalevalue);
 }
 }
 
 void update()
 {
 if (enablelerptween)
  tweenviewtotarget();
 }
 
 private void tweenviewtotarget()
 {
 mcurrentduration += time.deltatime;
 if (mcurrentduration > lerpduration)
  mcurrentduration = lerpduration;
 
 float percent = mcurrentduration / lerpduration;
 float value = mathf.lerp(originhorizontalvalue, curhorizontalvalue, percent);
 updateenhancescrollview(value);
 if (mcurrentduration >= lerpduration)
 {
  canchangeitem = true;
  enablelerptween = false;
  ontweenover();
 }
 }
 
 private void ontweenover()
 {
 if (precenteritem != null)
  precenteritem.setselectstate(false);
 if (curcenteritem != null)
  curcenteritem.setselectstate(true);
 }
 
 // get the evaluate value to set item's scale
 private float getscalevalue(float slidervalue, float added)
 {
 float scalevalue = scalecurve.evaluate(slidervalue + added);
 return scalevalue;
 }
 
 // get the x value set the item's position
 private float getxposvalue(float slidervalue, float added)
 {
 float evaluatevalue = positioncurve.evaluate(slidervalue + added) * totalhorizontalwidth;
 return evaluatevalue;
 }
 
 private int getmovecurvefactorcount(enhanceitem precenteritem, enhanceitem newcenteritem)
 {
 sortenhanceitem();
 int factorcount = mathf.abs(newcenteritem.realindex) - mathf.abs(precenteritem.realindex);
 return mathf.abs(factorcount);
 }
 
 // sort item with x so we can know how much distance we need to move the timeline(curve time line)
 static public int sortposition(enhanceitem a, enhanceitem b) { return a.transform.localposition.x.compareto(b.transform.localposition.x); }
 private void sortenhanceitem()
 {
 listsorteditems.sort(sortposition);
 for (int i = listsorteditems.count - 1; i >= 0; i--)
  listsorteditems[i].realindex = i;
 }
 
 public void sethorizontaltargetitemindex(enhanceitem selectitem)
 {
 if (!canchangeitem)
  return;
 
 if (curcenteritem == selectitem)
  return;
 
 canchangeitem = false;
 precenteritem = curcenteritem;
 curcenteritem = selectitem;
 
 // calculate the direction of moving
 float centerxvalue = positioncurve.evaluate(0.5f) * totalhorizontalwidth;
 bool isright = false;
 if (selectitem.transform.localposition.x > centerxvalue)
  isright = true;
 
 // calculate the offset * dfactor
 int moveindexcount = getmovecurvefactorcount(precenteritem, selectitem);
 float dvalue = 0.0f;
 if (isright)
 {
  dvalue = -dfactor * moveindexcount;
 }
 else
 {
  dvalue = dfactor * moveindexcount;
 }
 float originvalue = curhorizontalvalue;
 lerptweentotarget(originvalue, curhorizontalvalue + dvalue, true);
 }
 
 // click the right button to select the next item.
 public void onbtnrightclick()
 {
 if (!canchangeitem)
  return;
 int targetindex = curcenteritem.curveoffsetindex + 1;
 if (targetindex > listenhanceitems.count - 1)
  targetindex = 0;
 sethorizontaltargetitemindex(listenhanceitems[targetindex]);
 }
 
 // click the left button the select next next item.
 public void onbtnleftclick()
 {
 if (!canchangeitem)
  return;
 int targetindex = curcenteritem.curveoffsetindex - 1;
 if (targetindex < 0)
  targetindex = listenhanceitems.count - 1;
 sethorizontaltargetitemindex(listenhanceitems[targetindex]);
 }
 
 public float factor = 0.001f;
 // on drag move
 public void ondragenhanceviewmove(vector2 delta)
 {
 // in developing
 if (mathf.abs(delta.x) > 0.0f)
 {
  curhorizontalvalue += delta.x * factor;
  lerptweentotarget(0.0f, curhorizontalvalue, false);
 }
 }
 
 // on drag end
 public void ondragenhanceviewend()
 {
 // find closed item to be centered
 int closestindex = 0;
 float value = (curhorizontalvalue - (int)curhorizontalvalue);
 float min = float.maxvalue;
 float tmp = 0.5f * (curhorizontalvalue < 0 ? -1 : 1);
 for (int i = 0; i < listenhanceitems.count; i++)
 {
  float dis = mathf.abs(mathf.abs(value) - mathf.abs((tmp - listenhanceitems[i].centeroffset)));
  if (dis < min)
  {
  closestindex = i;
  min = dis;
  }
 }
 originhorizontalvalue = curhorizontalvalue;
 float target = ((int)curhorizontalvalue + (tmp - listenhanceitems[closestindex].centeroffset));
 precenteritem = curcenteritem;
 curcenteritem = listenhanceitems[closestindex];
 lerptweentotarget(originhorizontalvalue, target, true);
 canchangeitem = false;
 }
}

nguienhanceitem.cs

using unityengine;
using system.collections;
 
/// <summary>
/// ngui enhance item example
/// </summary>
public class nguienhanceitem : enhanceitem
{
 private uitexture mtexture;
 
 protected override void onawake()
 {
 this.mtexture = getcomponent<uitexture>();
 uieventlistener.get(this.gameobject).onclick = onclicknguiitem;
 }
 
 private void onclicknguiitem(gameobject obj)
 {
 this.onclickenhanceitem();
 }
 
 // set the item "depth" 2d or 3d
 protected override void setitemdepth(float depthcurvevalue, int depthfactor, float itemcount)
 {
 if (mtexture.depth != (int)mathf.abs(depthcurvevalue * depthfactor))
  mtexture.depth = (int)mathf.abs(depthcurvevalue * depthfactor);
 }
 
 // item is centered
 public override void setselectstate(bool iscenter)
 {
 if (mtexture == null)
  mtexture = this.getcomponent<uitexture>();
 if (mtexture != null)
  mtexture.color = iscenter ? color.white : color.gray;
 }
 
 protected override void onclickenhanceitem()
 {
 // item was clicked
 base.onclickenhanceitem();
 }
}

uguienhanceitem.cs

using unityengine;
using system.collections;
using unityengine.ui;
using unityengine.eventsystems;
 
public class uguienhanceitem : enhanceitem
{
 private button ubutton;
 private image image;
 
 protected override void onstart()
 {
 image = getcomponent<image>();
 ubutton = getcomponent<button>();
 ubutton.onclick.addlistener(onclickuguibutton);
 }
 
 private void onclickuguibutton()
 {
 onclickenhanceitem();
 }
 
 // set the item "depth" 2d or 3d
 protected override void setitemdepth(float depthcurvevalue, int depthfactor, float itemcount)
 {
 int newdepth = (int)(depthcurvevalue * itemcount);
 this.transform.setsiblingindex(newdepth);
 }
 
 public override void setselectstate(bool iscenter)
 {
 if (image == null)
  image = getcomponent<image>();
 image.color = iscenter ? color.white : color.gray;
 }
}

导入以上6个脚本以后,我们开始制作效果,先从ngui开始,我们先在场景中,随便添加一个背景,然后,我们在uiroot下面添加一个空物体,取名scrollview,添加enhancescrollview.cs脚本,然后制作六个texture作为scrollview的子物体,添加dragenhanceview.cs脚本,nguienhanceitem.cs脚本,boxcollider组件。接着,我们在六个图片下方添加两个button,作为左右切换卡牌的按钮,在点击事件中,拖入scrollview,分别添加onbtnleftclick,onbtnrightclick方法。做完以上操作以后,场景大概是这样:

Unity实现游戏卡牌滚动效果

接着,我们选中scrollview,调整脚本参数:

Unity实现游戏卡牌滚动效果

scalecurve图像参数,设置为如下,左右循环都为pingpong:

Unity实现游戏卡牌滚动效果

positioncurve图像参数如下,左右循环都为loop:

Unity实现游戏卡牌滚动效果

depthcurve图像参数如下,左右循环都为loop:

Unity实现游戏卡牌滚动效果

然后把scrollview的子物体都拖到listenhanceitems这个公开数组下:

Unity实现游戏卡牌滚动效果

这样,我们就把配置工作都做好了,运行游戏:

Unity实现游戏卡牌滚动效果

可以看到,效果还不错,左右滑动或者点击切换按钮,就能实现切换卡牌的功能。

接着,我们看一下ugui的实现,ugui的ui布局基本和ngui保持一致,所不同的是scrollview的子物体添加的脚本不一样,所需要的脚本及组件如下图所示:

Unity实现游戏卡牌滚动效果

然后,还有需要注意的一点是,在scrollview上的参数配置上,我们需要把inputtype这个属性调整为ugui input

Unity实现游戏卡牌滚动效果

曲线设置和子物体数组设置和ngui一样,这里就不再重复了,配置完这些操作以后,运行,ugui也能实现一样的卡牌滚动效果:

Unity实现游戏卡牌滚动效果

以上,感谢github。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。