塔防小游戏
第一篇:一个防御塔+多个野怪(简易版)
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<TowerBean> towerList = new ArrayList<>();//防御塔数量 private List<BlameBean> 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地址,我们一起学习。
下一篇是拖拽放置防御塔,手动开启、暂停游戏。
持续书写中........