自定义View6 -塔防小游戏:第三篇防御塔随意放置+多组野怪

第一篇:一个防御塔+多个野怪(简易版)
第二篇:防御塔随意放置
第三篇:防御塔随意放置+多组野怪

  1、动态addView防御塔

  2、防御塔放置后不可以移动

  3、弯曲道路

  4、素材替换

第四篇:多波野怪

第五篇:杀死野怪获得金币

第六篇:防御塔可升级,增强攻击力,增大射程

自定义View6 -塔防小游戏:第三篇防御塔随意放置+多组野怪

描述:防御塔可以放置多个,每一个都是独立的,他们的攻击互不影响(防御塔随意拖动在第二篇),这里用到的知识是,自定义view的拖动,防御塔是否可以攻击的计算,防御塔的攻击路径。

1、放置防御塔

  • 新建类ActivityTower5,主要控制放置塔的回调
  • 新建BattlefieldView5,主要渲染战场
  • 新建TowerView5,主要绘制防御塔,(其实野怪也需要单独创建view)

1.1ActivityTower5首页该做些什么?

这次我们想要做成动态的,由用户自行开启,玩累了还能暂停,而且有钱可以创建多个防御塔(后续加入攻击野怪获得金币),所以创建开启按钮,暂停按钮,创建A炮(后续有B炮,C炮...),代码如下

<?xml version="1.0" encoding="utf-8"?> <layout> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"     android:id="@+id/layout_relative"     ......     android:gravity="center"     ......>      <com.liu.lib_view.tower.tower4.BattlefieldView4         android:id="@+id/TowerView"         ......         />     <LinearLayout         android:id="@+id/bottom"         ......>         <Button             android:id="@+id/start"             ......             android:text="开始"/>         <Button             android:id="@+id/pause"             ......             android:text="暂停"/>         <Button             android:id="@+id/create"             ......             android:text="创建A炮"/>     </LinearLayout> </RelativeLayout> </layout>

这次添加一些素材,这些都是在网上随便找的,一个背景图片,一个防御塔,一个野怪,这次做成横屏的,我们需要记录一下弯曲道路的xy坐标,封装成一个list(下面有解)。

自定义View6 -塔防小游戏:第三篇防御塔随意放置+多组野怪

自定义View6 -塔防小游戏:第三篇防御塔随意放置+多组野怪

自定义View6 -塔防小游戏:第三篇防御塔随意放置+多组野怪

1.2、BattlefieldView5渲染战场

集成ViewGroup,因为我们要在里面添加其他View,只有ViewGroup才有addView方法,这里我们声明一些属性,妖怪大道、野怪、防御塔画笔这些必不可少,我们这次是多个防御塔就要创建towerList来存储我们创建的防御塔,野怪数量也是如此。

注意:集成ViewGroup这里要写setWillNotDraw方法,不然onDraw()不执行。

我们设置完背景图片后,开始渲染战场,首先绘制道路,这次是弯曲的,会用到Path类,

  •   moveTo(x,y)  移动的起始点
  •   lineTo(x,y)  从起始点到该点画一条线。

我们按照背景图路线琢磨一下路线坐标(每个手机可能存在差异),大概是如下

@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {     super.onSizeChanged(w, h, oldw, oldh);     towerX = w / 2;     towerY = h / 2;      roadPath = new Path();     roadPath.moveTo(0,900);     roadPath.lineTo(500,900);     roadPath.lineTo(500,200);     roadPath.lineTo(800,200);     roadPath.lineTo(800,800);     roadPath.lineTo(1600,800);     roadPath.lineTo(1600,200);     roadPath.lineTo(towerX*2,200); }

自定义View6 -塔防小游戏:第三篇防御塔随意放置+多组野怪

大体路线已经出来了,我们需要获取这条线上的坐标点,pathMeasure类可以获取path路径上的点数

源码解析

pathMeasure.getLength()获取点数 pathMeasure.getPosTan(距离,pos,tan); 源码解释:    * @param distance The distance along the current contour to sample 沿轮廓到样本的距离      * @param pos If not null, returns the sampled position (x==[0], y==[1])      * @param tan If not null, returns the sampled tangent (x==[0], y==[1])      * @return false if there was no path associated with this measure object     */     public boolean getPosTan(float distance, float pos[], float tan[]) {         if (pos != null && pos.length < 2 ||             tan != null && tan.length < 2) {             throw new ArrayIndexOutOfBoundsException();         }         return native_getPosTan(native_instance, distance, pos, tan);     }
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {     super.onSizeChanged(w, h, oldw, oldh);     ......     pathMeasure = new PathMeasure();     pathMeasure.setPath(roadPath,false); }

 到这里,我们就可以拿到路径了,我们还需要把妖怪大道path集合存入到每个野怪属性里,让野怪沿着这条路线走,

public class BlameBean {     private int blameId;     public int x;     public int y;     public int speed;//行走速度     public int HP;//血量     public boolean isAttacks;//是否可以被攻击     public boolean wounded;//受伤效果          public int position=0;     public List<RoadXY> roadXYList = new ArrayList<>();

 position是走到第几步,roadXYList就是路线,动态添加6个野怪,路线别忘记添加了,3000可以理解为整条路线野怪需要走3000步才能到终点。

1.3、动态添加野怪

/**  * 添加一个野怪  */ private void addBlame() {     if (countDownTimer != null) {         return;     }     countDownTimer = new CountDownTimer(12000, 2000) {          @Override         public void onTick(long millisUntilFinished) {             if (blameList.size() >= 6) {                 return;             }             BlameBean blameBean = new BlameBean();             blameBean.setHP(100);             blameBean.setSpeed(1);             blameBean.setX(towerX + 200);             blameBean.setY(0);              List<BlameBean.RoadXY> roadXYList = blameBean.getRoadXYList();             for (int i = 0; i < 3000; i++) {                 pathMeasure.getPosTan(i/3000f * pathMeasure.getLength(),pos,tan);                 BlameBean.RoadXY roadXY = new BlameBean.RoadXY();                 roadXY.setRoadX((int) pos[0]);                 roadXY.setRoadY((int) pos[1]);                 roadXYList.add(roadXY);             }             blameBean.setRoadXYList(roadXYList);              blameList.add(blameBean);         }          @Override         public void onFinish() {          }     };  }

1.4、添加防御塔 ,动态创建A炮

自定义View6 -塔防小游戏:第三篇防御塔随意放置+多组野怪

Activity中 mBinding.create.setOnClickListener(v ->  {     TowerView5 towerView = new TowerView5(activity);     RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);     lp.width = 700;     lp.height = 700;     towerView.setLayoutParams(lp);     //一旦放置成功后调用     towerView.setTowerListener(() -> {         int raduis = 350;         towerView.setMove(false);         mBinding.TowerView.addTower((int)towerView.getX()+raduis,(int)towerView.getY()+raduis,raduis);     });     mBinding.layoutRelative.addView(towerView);     listTower.add(towerView); });  View中    /**      * 添加一个防御塔      */     public void addTower(int x, int y, int raduis) {         TowerBean towerBean = new TowerBean();         towerBean.setTowerId(towerList.size());         towerBean.setAttacksSpeed(500);         towerBean.setHarm(5);         towerBean.setX(x);         towerBean.setY(y);         towerBean.setRaduis(raduis);         towerList.add(towerBean);     }

我们添加完成需要在ondraw方法中绘制出来

@Override     protected void onDraw(Canvas canvas) {         super.onDraw(canvas);         //野怪路线         canvas.drawPath(roadPath,roadPaint);         //皇帝         for (int i = 0; i < blameList.size(); i++) {             if (blameList.get(i).getX() > (towerX * 2 - 100) && blameList.get(i).getHP() > 0) {                 kingHP -= blameList.get(i).getHP();             }         }         if (kingHP <= 0) {             kingHP = 0;             canvas.drawText("失败", towerX, towerY, tp);             valueAnimator.cancel();         }         canvas.drawText("皇帝" + kingHP, towerX * 2 - 100, 200, tp);          //野怪移动         for (int i = 0; i < blameList.size(); i++) {             BlameBean blameBean = blameList.get(i);             if (blameBean.getHP() > 0) {                 canvas.drawRect(blameBean.getX() - 40, blameBean.getY()-15, blameBean.getX() + 60, blameBean.getY()-5, towerPaint);                 canvas.drawRect(blameBean.getX() - 39, blameBean.getY() - 10, blameBean.getX() + 58 - (100 - blameBean.getHP()), blameBean.getY() - 10, hpPaint);                 bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.yeguai2);                 canvas.drawBitmap(bitmap, blameBean.getX() - 20, blameBean.getY() , tp);             }         }     }

写到这里还没有写刷新view的代码,带着疑问,如何刷新数据,如何更新野怪行走的数据,如何判断是否在开炮射程内。

public BattlefieldView5(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {     super(context, attrs, defStyleAttr);     //ViewGroup不跳过onDraw()方法,默认是跳过     setWillNotDraw(false);     ......     valueAnimator = ValueAnimator.ofInt(0, 10);     valueAnimator.setDuration(5000);     valueAnimator.setInterpolator(new LinearInterpolator());     valueAnimator.setRepeatCount(-1);     valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {         @Override         public void onAnimationUpdate(ValueAnimator valueAnimator) {             updateParticle();             invalidate();         }     });  }

解释:我们开启一个动画,让其不断的重绘。 updateParticle方法最关键,记录了野怪移动数据,开炮动画等

private void updateParticle() {     //野怪移动     for (int i = 0; i < blameList.size(); i++) {         BlameBean blameBean = blameList.get(i);         if(blameBean.getPosition()>=3000){             break;         }         int roadX = blameBean.getRoadXYList().get(blameBean.position).getRoadX();         int roadY = blameBean.getRoadXYList().get(blameBean.position).getRoadY();         blameBean.setPosition(blameBean.getPosition()+1);         blameBean.setX(roadX);         blameBean.setY(roadY);         //野怪进入防御塔范围         isAttacks(i);         //开炮动画         if (blameList.get(i) != null && blameList.get(i).getMapAttacksTower().size() > 0) {             Map<Integer, Integer> listAttacksTower = blameList.get(i).getMapAttacksTower();             for (Integer j : listAttacksTower.keySet()) {                 shotMove(towerList.get(listAttacksTower.get(j)).getX(), towerList.get(listAttacksTower.get(j)).getY(), blameBean.getX(), blameBean.getY(), i, listAttacksTower.get(j));             }         }     } }

是否进入防御塔范围,这里我们使用map来存int raduis = (int) Math.hypot(Math.abs(x), Math.abs(y));常用于勾股定理,如果在防御塔范围内,野怪就记录一下id,如果在两个防御塔内,就把两个防御塔的id记录一下,map的特性,不会有key重复。也就是不会有防御塔重复攻击。

public class BlameBean { /** *使用map的好处是唯一 * 被哪些防御塔攻击 * */ public Map<Integer,Integer> mapAttacksTower=new HashMap<>();
private void isAttacks(int position) {     for (int j = 0; j < towerList.size(); j++) {         int x = blameList.get(position).getX() - towerList.get(j).getX();         int y = blameList.get(position).getY() - towerList.get(j).getY();         int raduis = (int) Math.hypot(Math.abs(x), Math.abs(y));         Map<Integer, Integer> mapAttacksTower = blameList.get(position).getMapAttacksTower();         if (raduis < towerList.get(j).getRaduis() && blameList.get(position).getHP() > 0) {             mapAttacksTower.put(towerList.get(j).getTowerId(), towerList.get(j).getTowerId());             blameList.get(position).setMapAttacksTower(mapAttacksTower);         } else {             //移除防御塔             //攻击到该野怪  塔的集合             for (Integer key : mapAttacksTower.keySet()) {                 if (mapAttacksTower.get(key) == towerList.get(j).getTowerId()) {                     mapAttacksTower.remove(key);                     blameList.get(position).setMapAttacksTower(mapAttacksTower);                     break;                 }             }         }     } }

开炮动画,遍历野怪可被攻击的集合即可

//开炮动画 if (blameList.get(i) != null && blameList.get(i).getMapAttacksTower().size() > 0) {     Map<Integer, Integer> listAttacksTower = blameList.get(i).getMapAttacksTower();     for (Integer j : listAttacksTower.keySet()) {         shotMove(towerList.get(listAttacksTower.get(j)).getX(), towerList.get(listAttacksTower.get(j)).getY(), blameBean.getX(), blameBean.getY(), i, listAttacksTower.get(j));     } }

1.5、炮弹动画

判断如果可以攻击了,就开启一个从xy(防御塔),移动到x2y2 (野怪)的动画 ,动画结束后掉血。动画开始时不可能再次开启,要符合防御塔一次只能攻击一个野怪的效果,这里开炮动画有点问题,就是视觉上老是打偏,有的时候炮弹慢的话,就会打在野怪身后,也没有好的解决方式。博友有想法请留言。

//炮弹动画     private void shotMove(float x, float y, float x2, float y2, int blamePosition, int towerPosition) {         if (!towerList.get(towerPosition).isAttacking()) {             towerList.get(towerPosition).setAttacking(true);             shotView = new ImageView(this.getContext());             shotView.setImageDrawable(getContext().getDrawable(R.drawable.shot));             shotView.layout(0, 0, 20, 20);             addView(shotView);             //开炮音效回调             iShotService.shot();             translateAnimation = new TranslateAnimation(x, x2, y, y2);             translateAnimation.setDuration(towerList.get(0).getAttacksSpeed());             translateAnimation.setAnimationListener(new Animation.AnimationListener() {                 @Override                 public void onAnimationStart(Animation animation) {                  }                  @Override                 public void onAnimationEnd(Animation animation) {                     blameList.get(blamePosition).setHP(blameList.get(blamePosition).getHP() - towerList.get(towerPosition).getHarm());                     towerList.get(towerPosition).setAttacking(false);                     int childCount = getChildCount();                     if (childCount > 1) {                         removeView(getChildAt(childCount - 1));                     }                 }                  @Override                 public void onAnimationRepeat(Animation animation) {                  }             });             shotView.startAnimation(translateAnimation);         }     }

我们需要控制开始与暂停和onResume下动画的释放等

    public void start() {         if (valueAnimator != null) {             //开启动画             addBlame(); //            addTower(450,450);             valueAnimator.start();             countDownTimer.start();         }     }      public void pause() {         if (valueAnimator != null) {             //开启动画             valueAnimator.pause();             countDownTimer.cancel();         }     }      /**      * hasWindowFocus:true 获得焦点,开启动画;false 失去焦点,停止动画      */     @Override     public void onWindowFocusChanged(boolean hasWindowFocus) {         super.onWindowFocusChanged(hasWindowFocus);         if (!hasWindowFocus) {             if (valueAnimator != null) {                 //开启动画                 valueAnimator.pause();                 countDownTimer.cancel();                 countDownTimer = null;             }             if (countDownTimer != null) {                 countDownTimer.cancel();                 countDownTimer = null;             }         }     }

 

总结:这里加入了新的背景图、多个防御塔随意摆放、一旦摆放就无法移动(后续加入拆除、升级)等功能。难点还是在野怪移动上,还有多个防御塔攻击互相不影响。

问题:现在的思路是刷新一下,野怪走一步,后续如果加入减速防御塔的话,应该怎么走呢,多个野怪如何做到行走速度互不影响呢。

个人思路:可以正常野怪一次走5步,如果被减速防御塔打中后,就把5步见为2步,position+5调整为position+2。这里只是记录学习View的过程,不要较真哦。

 

 

 

 

 

 

发表评论

相关文章