前言
「 随着小游戏《羊了个羊》蹿红,年轻人打招呼的 方式也变成了、今天你“羊了个羊”了吗?简单来 说, “羊了个羊”是一款升级版消消乐,将玩家按 照地域分省排名。游戏不仅带上头了玩家,也席 卷了朋友圈,甚至带火了相关行业,令人匪夷所 思。
本人作为一名职业杠精,当然也不例外,从点开第一把开始就无法自拔,鬼知道我一天能看多少广告,经过几天碰壁后,我决定自己复刻一个出来」
不废话,最终效果如下
项目已上传Github,各位可自行下载和游玩
游戏逻辑
1、在思路上先在屏幕中间默认一个宽14×高16的坐标轴,之后就可以用坐标轴来判断棋子了。(每个棋子宽高为2×2,方便计算覆盖面)
2、无论是第一关还是第二关,首先需要创建3个list(棋子坐标list,图标list,层级list),当然官方版本是通过接口获取,我这边就自行想办法创造了。(用实体类的方式也可以)
3、从屏幕正上方实现棋子下落动画,落位之后实现棋子变暗动画
4、实现棋子点击事件,点击之后,实现位移动画,移动到底部木栏里,并计算是否消除
5、每次点击事件之后判断坐标轴之内的棋子,如果为零,回调过关。
实现过程
我把所有的过程的封装在Sheepview中,直接来看sheepview
① 初始化一些参数
private Context mContext;
private View sheepLayout;
private RelativeLayout rlAlllayout;
private SuperTextView stv_container;
private List<GrassView> GrassViewList; //草list
private List<Integer[]> mGrasslocationList; //草坐标列表
private int grassNum = 28; //草的数量
private final int ChessSize = 24; //棋子size的一半
private List<ImageView> showChessList; //棋牌区域image的list
private List<ImageView> takeinChessList; //收纳区image的list
private List<Integer> rescoureList; //图片资源list
private int MAX_DEGREE = 20; //第二关最多层数
private final int MAX_CHESSNUM = 36; //第二关 单层棋子最多数量
private int degreeNum = 0; //获取层数 (为0时,从10-MAX_DEGREE中随机获取层数,有传入用传入)
private final long downDelayTime = 500; //下落动画时间
private final long ToDarkTime = 500; //变暗动画时间
private final long ToLightTime = 500; //变亮动画时间
private final long takeinTime = 40; //移动到木栏动画时间
private final long leftTime = 40; //配对消失后其他棋子左移动画时间
private final long changeTime = 600; //旋转动画时间
private List<Double[]> locationList; //棋子坐标列表
private List<Integer> iconList; //图标列表
private List<Integer> degreeList; //等级列表
private int barrierNum; //关卡
private MySheepListener mySheepListener;
② 创建赋值棋子坐标list,图标list,层级list
第一关:
层数:9个第一层 9个第二层
for (int i = 0; i < 18; i++) {
if (i<9){
degreeList.add(1);
}else {
degreeList.add(2);
}
}
坐标:位置是确定的。
* 第一关的坐标为2层
* 底部:
* (3,3) (6,3) (9,3)
* (3,7) (6,7) (9,7)
* (3,11) (6,11) (9,11)
* 顶部:
* (3,4) (6,4) (9,4)
* (3,8) (6,8) (9,8)
* (3,12) (6,12) (9,12)
图标:从16个图标中随机获取3个即可(取的为resource的序号)
//取3个不同的随机数 选出3个icon
int i1 = (int) (Math.round(Math.random() * 15));
int i2 = (int) (Math.round(Math.random() * 15));
while (i2 == i1){
i2 = (int) (Math.round(Math.random() * 15));
}
int i3 = (int) (Math.round(Math.random() * 15));
while (i3 == i1||i3 == i2){
i3 = (int) (Math.round(Math.random() * 15));
}
for (int i = 0; i < 6; i++) {
iconList.add(i1);
iconList.add(i2);
iconList.add(i3);
}
Collections.shuffle(iconList); //打乱
第二关:
层数:获取一个小于max的随机数
//获取随机层数
if (degreeNum == 0){
degreeNum = (int) (Math.random() * (MAX_DEGREE - 10)) + 10;
}else if (degreeNum<5){
degreeNum = 5;
}
坐标:每层随机创建小于一个max的数量,从小到大排列后,每层循环给坐标list增加不重叠的坐标,当然,最后需要判断list是否整除3,如果不整除,从最上层去除,直到整除为止。(这边的坐标均为随机出来,以后如果要优化游戏可玩性,可在这部分优化)
//生成小于36的层数的随机数
List<Integer> degreeContentNum = new ArrayList<>();
for (int i = 0; i < degreeNum; i++) {
int num = (int) (Math.random() * (MAX_CHESSNUM-6)) + 6;
degreeContentNum.add(num);
}
//从小到大排列
Collections.sort(degreeContentNum,new Comparator(){
@Override
public int compare(Object o1, Object o2) {
int diff = (Integer)o1 -(Integer) o2;
if (diff>0){
return 1;
}
else if (diff <0){
return -1;
}
else {
return 0;
}
}
});
//生成坐标
for (int i = 0; i < degreeContentNum.size(); i++) { //每层
List<Double[]> everyDegreeLocationList = new ArrayList<>();
for (Integer x = 0; x < degreeContentNum.get(i); x++) { //每层中每个的判断
addSmallLocation(everyDegreeLocationList,i+1, 0);
}
// Log.i("孙", "每层数量: "+degreeContentNum.get(i));
locationList.addAll(everyDegreeLocationList);
}
//必须为3的倍数 删除前几个
if (locationList.size()%3 !=0){
for (int i = 0; i < locationList.size() % 3; i++) {
locationList.remove(i);
degreeList.remove(i);
}
}
(第二关还有底部额外的两行)
//第二层 底部额外的部分
int i = degreeNum / 3;
//获取底部层数
int bottomGegreeNum = i*3;
for (int x = 0; x < bottomGegreeNum; x++) {
locationList.add(new Double[]{3.0- ((double) 5.0)/ChessSize*x,17.0});
degreeList.add(x+1);
locationList.add(new Double[]{9.0+ ((double) 5.0)/ChessSize*x,17.0});
degreeList.add(x+1);
}
图标:根据数量随机取即可
for (int i = 0; i < locationList.size() / 3; i++) {
int i1 = (int) (Math.round(Math.random() * 15));
for (int x = 0; x < 3; x++) {
iconList.add(i1);
}
}
Collections.shuffle(iconList); //打乱
③ 所有list均需倒序,因为层数越小越在上层
Collections.reverse(locationList);
Collections.reverse(iconList);
Collections.reverse(degreeList);
④ 根据list创建所有imageview,实现下落动画和点击事件
for (int i = 0; i < iconList.size(); i++) {
ImageView imageView = new ImageView(mContext);
locationToMargin(imageView,locationList.get(i),degreeList.get(i));
imageView.setImageResource(rescoureList.get(iconList.get(i)));
imageView.setTag(R.id.sheepview_imageview_picrescoure,iconList.get(i));
imageView.setOnClickListener(new NoFastClickListener() {
@Override
protected void onSingleClick() {
PlayVoice.playClickVoice(mContext); //播放点击音效
showChessList.remove(imageView); //棋牌区域remove该imageview
scaleChangeAnim(imageView); //点击棋子时的,放大缩小动画
judgeCanClick(false); //当然,每次点击需要判断所有棋子是否变亮和可点击
}
});
showChessList.add(imageView);
rlAlllayout.addView(imageView);
}
for (ImageView imageView : showChessList) {
int mTime = (int) (Math.random() * 700);
startDownAnim(imageView,mTime); //下落动画,各棋子有不同的延迟
if (judgeNeedToDark(imageView)){ //判断是否需要变暗和不可点击
startToDarkAnim(imageView,mTime+downDelayTime);
}
}
judgeCanClick:(只需要判断层级比自己小的imageview的宽高差是否在坐标2之内即可)
private void judgeCanClick(boolean onlyChangeViewClickable){ //判断剩下棋子是否可以点击
for (int i = 0; i < showChessList.size(); i++) {
boolean canClick = true;
List<ImageView> mlist = new ArrayList<>();
mlist.addAll(showChessList);
mlist.remove(i);
Double[] ilocation = (Double[]) showChessList.get(i).getTag(R.id.sheepview_imageview_location);
int idegree = ((int) showChessList.get(i).getTag(R.id.sheepview_imageview_degree));
for (ImageView imageView : mlist) {
Double[] lt = (Double[]) imageView.getTag(R.id.sheepview_imageview_location);
int dg = ((int) imageView.getTag(R.id.sheepview_imageview_degree));
if (idegree > dg){
if (Math.abs(lt[0]-ilocation[0])<2&&Math.abs(lt[1]-ilocation[1])<2){
canClick = false;
break;
}
}
}
if (canClick){
showChessList.get(i).setEnabled(true);
if (!onlyChangeViewClickable){
if (null!=showChessList.get(i).getForeground()&&showChessList.get(i).getForeground().getAlpha() < 255){
startToLightAnim(showChessList.get(i));
}
}
}else {
showChessList.get(i).setEnabled(false);
}
}
}
⑤ 点击事件中,实现棋子位移到底部木栏的动画以及游戏成功失败的判断(使用的是属性动画,监听动画结束后,根据资源文件序号是否有3个相同,判断是否要消除,否则如果木栏中有7个即为失败,棋盘区域为0即为成功)
private void startTakeinAnim(ImageView imageView){ //放大缩小后,从上方容器到下方容器 移动动画
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 100f);
valueAnimator.setDuration(takeinTime);
valueAnimator.setInterpolator(new LinearInterpolator()); //线性变化
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float animatedValue = (float) valueAnimator.getAnimatedValue();
int targetLeftMargein = (PUtil.getScreenW(mContext)- PUtil.dip2px(mContext,14*ChessSize))/2 + (takeinChessList.size())*PUtil.dip2px(mContext,2*ChessSize);
int targettopMargein = PUtil.getScreenH(mContext)- PUtil.dip2px(mContext,25+2*ChessSize);
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) imageView.getLayoutParams();
int leftMargin = layoutParams.leftMargin;
float value1 = (((float) leftMargin)-targetLeftMargein)/100 *animatedValue;
layoutParams.leftMargin = (int) (leftMargin - value1);
int topMargin = layoutParams.topMargin;
float value2 = (((float) topMargin)-targettopMargein)/100 *animatedValue;
layoutParams.topMargin = (int) (topMargin - value2);
imageView.setLayoutParams(layoutParams);
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
takeinChessList.add(imageView);
Map<Integer,List<Integer>> mMap= new HashMap<>(); //key资源序号,list 图片在takeinChessList的位置
for (int i = 0; i < takeinChessList.size(); i++) {
if (null == mMap.get(((int) takeinChessList.get(i).getTag(R.id.sheepview_imageview_picrescoure)))){
List<Integer> picNumList = new ArrayList<>();
picNumList.add(i);
mMap.put(((int) takeinChessList.get(i).getTag(R.id.sheepview_imageview_picrescoure)),picNumList);
}else {
mMap.get(((int) takeinChessList.get(i).getTag(R.id.sheepview_imageview_picrescoure))).add(i);
}
}
boolean canClear = false; //能消除
for (Integer key : mMap.keySet()) {
if (mMap.get(key).size() == 3){
canClear = true;
startClearAnim(mMap.get(key));
break;
}
}
if (!canClear){
if (takeinChessList.size()>6){ //数量为7,即失败
if (null!=mySheepListener){
mySheepListener.failListener();
}
}
}else {
//冻结所有上层棋子的点击事件
for (ImageView view : showChessList) {
view.setEnabled(false);
}
}
}
});
valueAnimator.start();
}
结语
本文纯娱乐开发,考虑的东西都是边玩边做的,当然还有些没有说明的部分大家可以看看源码。
如果有侵犯了游戏厂商权益的地方,我会及时删除。
·最后,致敬所有被羊了个羊虐过的玩家们❤❤❤