• 自定义View4-塔防小游戏第一篇:一个防御塔+多个野怪(简易版)*


    塔防小游戏 

    第一篇:一个防御塔+多个野怪(简易版)
        1、canvas画防御塔,妖怪大道,妖怪行走路线
        2、防御塔攻击范围是按照妖怪与防御塔中心距离计算的,大于防御塔半径则不攻击,小于则攻击
        3、野怪被攻击条件,血量>0 && 防御塔范围内
        子弹要打在野怪身上,
        下:y+移动距离/子弹攻速
        上:y-移动距离/子弹攻速
        左:x—移动距离/子弹攻速
        右:x+移动距离/子弹攻速

    第二篇:防御塔随意放置
    第三篇:防御塔随意放置+多组野怪
    第四篇:多波野怪
    第五篇:杀死野怪获得金币
    第六篇:防御塔可升级,增强攻击力,增大射程

    先上效果图

    由于原图片过大,我剔除了其中的帧数,导致看着有些"瞬移"。

    该篇是自定义View学习过程中做的简单下游戏,目前分了6篇,全是自定义的view实现的,如果有同学有好的优化方案,欢迎留言。

    目标:通过自定义View实现一个防御塔攻击多个野怪 思路:之前我有过View的文章,里面的防御塔都是用的圆代替,野怪用的矩形代替。我们分别创建防御塔、妖怪大道、野怪,开启动画不断刷新View,不断计算野怪和防御塔的距离,只要小于防御塔半径就对野怪攻击,攻击样式,我们可以动态创建imageview,使用移动动画即可(塔xy -> 野怪xy)。最后皇帝血量100。

    • 创建一个防御塔(画圆),同时保存防御塔的属性值,比如射程、攻击力、塔xy轴,伤害、攻击范围、攻击速度等。
    • 创建一个妖怪大道,画一个矩形,第一篇妖怪大道是直线,后期将会做成弯弯曲曲。
    • 创建6个野怪,可开启一个定时器,2秒创建一个,可以达到有间隔排队的效果。野怪属性行走速度、血量、是否可被攻击、受伤效果等。

    1、创建防御塔,野怪,妖怪大道、皇帝

    新建文件BattlefieldView2,(我后面会持续更新,BattlefieldView3,4,5)一定要继承ViewGroup(View没有addView),下面代码需要注意的是onDraw()是否执行问题。我们查看ViewGroup源码可以知道,默认是跳过onDraw方法的,我们需要手动开启 setWillNotDraw(false);

    public class BattlefieldView2 extends ViewGroup {
       
    
        public BattlefieldView2(Context context) {
            this(context, null);
        }
    
        public BattlefieldView2(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public BattlefieldView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            //ViewGroup不跳过onDraw()方法,默认是跳过
            setWillNotDraw(false);
           
        }
    
      
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //        measureChildren(widthMeasureSpec, heightMeasureSpec);
            final int count = getChildCount();
            for (int i = 0; i < count; i++) {
                getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
            }
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
        }
    }

    所有用到的属性

    private Paint roadPaint; //路paint
    private Paint towerPaint; //塔paint
    private Paint blamePaint; //野怪paint
    private int towerX, towerY;//防御塔初始坐标
    private TextPaint kingPaint;//文字画笔
    private List towerList = new ArrayList<>();//防御塔数量
    private List blameList = new ArrayList<>();//野怪数量
    private ImageView shotView;
    private ValueAnimator valueAnimator;
    private TranslateAnimation translateAnimation;
    private boolean shotStart;//开炮
    private CountDownTimer countDownTimer;
    private int kingHP=100;//皇帝血量
    private float distance=0;//炮弹偏移量

    创建画笔

    public BattlefieldView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            //ViewGroup不跳过onDraw()方法,默认是跳过
            setWillNotDraw(false);
            //妖怪大道
            roadPaint = new Paint();
            roadPaint.setColor(0xffFFcAF9);
            roadPaint.setAntiAlias(true);
            roadPaint.setStrokeWidth(100f);
            roadPaint.setStyle(Paint.Style.STROKE);
            //妖怪本身
            blamePaint = new Paint();
            blamePaint.setColor(0x000000);
            blamePaint.setAntiAlias(true);
            blamePaint.setStrokeWidth(100f);
            blamePaint.setStyle(Paint.Style.STROKE);
            //皇帝
            kingPaint = new TextPaint();
            kingPaint.setColor(Color.BLUE);
            kingPaint.setStyle(Paint.Style.FILL);
            kingPaint.setTextSize(50);
            //防御塔
            towerPaint = new Paint();
            towerPaint.setColor(Color.RED);
            towerPaint.setAntiAlias(true);
            towerPaint.setStrokeWidth(2f);
            towerPaint.setStyle(Paint.Style.STROKE);
        }
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            towerX = w / 2;
            towerY = h / 2;
        }

     然后把这些东西在onDraw中画出来

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //野怪路线
        canvas.drawRect(towerX + 200, 0, towerX + 220, towerY * 2, roadPaint);
        ·······
        canvas.drawText("皇帝"+kingHP, towerX + 130, towerY * 2 - 100, kingPaint);
        //防御塔
        canvas.drawCircle(towerX - 150, towerY, 500, towerPaint);
        canvas.drawCircle(towerX - 150, towerY, 5, towerPaint);
        canvas.drawText("意大利炮", towerX - 350, towerY + 100, kingPaint);
    }

     到现在可以运行一下,看是否有东西绘制出来,不出意外,一个静态画面就出来了,我们需要让他动起来,那就开启一个动画吧,当然有其他方法可以留言探讨。

    初始化一些野怪,初始化防御塔,我们就在onSizeChanged方法中吧,生命周期中他在构造方法后执行,也只会被调动一次。我们先来定义野怪的属性,野怪坐标、行走速度、血量。防御塔也有攻击速度,攻击力,攻击范围等。

    BlameBean.class
        public int x;
        public int y;
        public int speed;//行走速度
        public int HP;//血量
        public boolean isAttacks;//是否可以被攻击
        public boolean wounded;//受伤效果
        
    TowerBean.class
        private int x;
        private int y;
        private float attacksX;//攻击X
        private float attacksY;//攻击Y
        private int attacksSpeed;//攻击速度
        private int harm;//伤害
        private int raduis;//攻击范围
    /**
     * 添加一个野怪
     * */
    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);
                blameList.add(blameBean);
            }
    
            @Override
            public void onFinish() {
    
            }
        }.start();
    
    }
      /**
         * 添加一个防御塔
         * */
        private void addTower() {
            TowerBean towerBean = new TowerBean();
            towerBean.setAttacksSpeed(500);
            towerBean.setHarm(5);
            towerBean.setX(towerX - 150);
            towerBean.setY(towerY);
            towerBean.setRaduis(500);
            towerList.add(towerBean);
        }

    OK,上面创建添加野怪和防御塔,我们现在就可以让他动起来了。

    public BattlefieldView2(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();
                    //2秒走的距离
                    if(valueAnimator.getCurrentPlayTime()>=1000 && valueAnimator.getCurrentPlayTime()<=3000){
                        distance += blameList.get(0).getSpeed();
                    }
                }
            });
        }
    
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        
        ·······
            
        addBlame();
        addTower();
        //开启动画
        valueAnimator.start();
    
    }

    到这里,动态创建野怪,就完成了,动画会不断的重绘View,达到野怪行走的效果,updateParticle()方法是控制野怪行走、是否进入防御塔范围的方法,野怪行走简单,就是Y轴不断的递增。是否可被攻击,计算公示:Math.hypot,计算x2 + y2的平方根(即,斜边)并将其返回。R^2 = (x1-x)^2 + (y-y1)^2。很好理解如果大于R就说明没在攻击范围内。反之。

    private void updateParticle() {
        //野怪移动
        for (int i = 0; i < blameList.size(); i++) {
            BlameBean blameBean = blameList.get(i);
            blameBean.setY(blameBean.getSpeed() + blameBean.getY());
            //野怪进入防御塔范围
            isAttacks(i);
            
        }
    }

     最后在onDraw方法中把修改后的数据渲染出来就可以了

    //野怪移动
    for (int i = 0; i < blameList.size(); i++) {
        BlameBean blameBean = blameList.get(i);
        if(blameBean.getHP()>0){
            canvas.drawRect(blameBean.getX() - 40, blameBean.getY(), blameBean.getX() + 60, blameBean.getY() + 80, towerPaint);
            canvas.drawText(blameBean.getHP() + "", blameBean.getX() - 30, blameBean.getY() + 50, kingPaint);
        }
    }

    到这就可以运行了,而且都动起来了,只不过没有攻击效果,我们需要开炮效果,再来一个动画,

    //炮弹动画
    private void shotMove(float x, float y, float x2, float y2,int blamePosition) {
    
        if (!shotStart) {
            shotStart = 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 - 10, x2, y, y2 + (distance * (Float.parseFloat(towerList.get(0).getAttacksSpeed()+"")/2000f)));
            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(0).getHarm());
                    shotStart = false;
                    int childCount = getChildCount();
                    if (childCount > 1) {
                        removeView(getChildAt(childCount - 1));
                    }
                }
    
                @Override
                public void onAnimationRepeat(Animation animation) {
    
                }
            });
            shotView.startAnimation(translateAnimation);
        }
    }

     使用

    private void updateParticle() {
        //野怪移动
        for (int i = 0; i < blameList.size(); i++) {
             ......
            //野怪进入防御塔范围
            isAttacks(i);
            if (blameList.get(i).isAttacks()) {
                shotMove(towerList.get(0).getX(), towerList.get(0).getY(), blameBean.getX(), blameBean.getY(),i);
            }
        }
    }

    写到这里,这一篇就结束了,最后皇帝死的画面可有可无。

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        .......
        //皇帝
        for (int i = 0; i < blameList.size(); i++) {
            if(blameList.get(i).getY()>(towerY * 2 - 100) && blameList.get(i).getHP()>0){
                kingHP-=blameList.get(i).getHP();
            }
        }
        if(kingHP<=0){
            kingHP = 0;
            canvas.drawText("失败", towerX, towerY, kingPaint);
            valueAnimator.cancel();
        }
        canvas.drawText("皇帝"+kingHP, towerX + 130, towerY * 2 - 100, kingPaint);
      .......
    }

     

    这篇主要是练习自定义View,里面好多没考虑到性能方面的问题,请见谅,如果有好的方案,欢迎留言,我会发您git地址,我们一起学习。

    下一篇是拖拽放置防御塔,手动开启、暂停游戏。

    持续书写中........

  • 相关阅读:
    html的使用
    《合成孔径雷达成像算法与实现》Figure5.3
    Uniapp零基础开发学习笔记(2) - 简单格式检查和事件响应
    java里面获取map的key和value的方法
    【DesignMode】装饰者模式(Decorator pattern)
    【亲妈教学】配置Gzip压缩,含前后端步骤
    学自动化测试可以用这几个练手项目
    【云原生之Docker实战】使用docker部署DokuWiki知识库系统
    Altium Designer 22 修改选中元件的层属性
    深度学习调参指南
  • 原文地址:https://www.cnblogs.com/cmusketeer/p/16658235.html