塔防小游戏

第一篇:一个防御塔+多个野怪(简易版)
    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地址,我们一起学习。

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

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

自定义View4-塔防小游戏第一篇:一个防御塔+多个野怪(简易版)*的更多相关文章

  1. 自定义View5 -塔防小游戏:第二篇防御塔随意放置

    第一篇:一个防御塔+多个野怪(简易版) 第二篇:防御塔随意放置 自定义View,处理事件分发,up,move,down. 第三篇:防御塔随意放置+多组野怪 第四篇:多波野怪 第五篇:杀死野怪获得金币 ...

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

    第一篇:一个防御塔+多个野怪(简易版)第二篇:防御塔随意放置第三篇:防御塔随意放置+多组野怪 1.动态addView防御塔 2.防御塔放置后不可以移动 3.弯曲道路 4.素材替换 第四篇:多波野怪 第 ...

  3. Python制作塔防小游戏

    开发工具 Python版本:3.6.4 相关模块: pygame模块: 以及一些Python自带的模块.

  4. [译]终极塔防——运用HTML5从头创建一个塔防游戏

    翻译共享一篇CodeProject的高星力作,原文地址:http://www.codeproject.com/Articles/737238/Ultimate-Tower-Defense 下载演示项目 ...

  5. 回合对战制游戏第一篇(初识java)

    回合对战制游戏第一篇 一,所谓的java. java是一门完全面向对象的编程语言,而之前所接触到的C语言是一门面向有一个过程的语音,对于两个的区别应该有一个清楚的认识. java的第一个内容. 类和对 ...

  6. 解剖SQLSERVER 第一篇 数据库恢复软件商的黑幕(有删减版)

    解剖SQLSERVER 第一篇  数据库恢复软件商的黑幕(有删减版) 这一系列,我们一起来解剖SQLSERVER 在系列的第一篇文章里本人可能会得罪某些人,但是作为一位SQLSERVER MVP,在我 ...

  7. unity3D游戏开发实战原创视频讲座系列9之塔防类游戏开发第一季

    解说文件夹 塔防游戏0基础篇... 第一讲  游戏演示和资源介绍... 第二讲  游戏场景的完毕... 第三讲  预制体的制作... 第四讲  敌人的随机产生和按路径行走... 第五讲  塔防工具的产 ...

  8. HTML5小游戏【是男人就下一百层】UI美化版

    之前写的小游戏,要么就比较简单,要么就是比较难看,或者人物本身是不会动的. 结合了其它人的经验,研究了一下精灵运动,就写一个简单的小游戏来试一下. 介绍一下几个主要的类: Frame:帧的定义,主要描 ...

  9. 前端—我的第一篇博客 梦开始的地方(面向对象版tab栏)

    这是我的第一篇博客 博客生涯才开始 但是人生已经过去了二十个年头了 才开始弄这个 也没搞得太懂 我原本的想法是想搞个源代码上来 但是看了半天好像就只能传html源代码 那我还有css js的部分呢 我 ...

随机推荐

  1. .NET混合开发解决方案24 WebView2对比CefSharp的超强优势

    系列目录     [已更新最新开发文章,点击查看详细] WebView2控件应用详解系列博客 .NET桌面程序集成Web网页开发的十种解决方案 .NET混合开发解决方案1 WebView2简介 .NE ...

  2. python小题目练习(八)

    题目:电视剧的收视率排行榜 需求:实现如下图所示需求  代码展示: """Author:mllContent:电视剧的收视率排行榜Date:2020-11-16" ...

  3. Linux for CentOS 下的 keepalived 安装与卸载以及相关命令操作之详细教程

    百度百科解释: keepalived 是一个类似于 layer3, 4 & 7 交换机制的软件,也就是我们平时说的第 3 层.第 4 层和第 7 层交换.Keepalived 的作用是检测 w ...

  4. Halcon 条形码识别

    read_image (Image, 'C:/Users/HJ/Desktop/test_image/image.png') create_bar_code_model([], [], BarCode ...

  5. springboot2+jpa+oracle实例

     pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="ht ...

  6. MySQL--用通配符进行过滤(LIKE操作符)

    1.LIKE操作符 怎样搜索产品名中包含文本anvil的所有产品?用简单的比较操作符肯定不行,必须使用通配符.利用通配符可创建比较特定数据的搜索模式.在这个例子中,如果你想找出名称包含anvil的所有 ...

  7. 如何用天气预警API接口进行快速开发

    天气预警能够指导人们出行.同一种类的气象灾害预警信号级别不同,对应的防御措施也不尽相同,人们通过气象灾害预警信号,合理安排出行.公众要提高防范意识,养成接收和关注预警信息的习惯,了解预警信息背后的意义 ...

  8. Note -「0/1 Fractional Programming」

    What is that? Let us pay attention to a common problem that we often meet in daily life: There are \ ...

  9. Object类中wait代餐方法和notifyAll方法和线程间通信

    Object类中wait代餐方法和notifyAll方法 package com.yang.Test.ThreadStudy; import lombok.SneakyThrows; /** * 进入 ...

  10. python 参数类型理解

    简介 大家都知道,在java中,函数或者方法的参数在调用时必须对其进行传参操作,也就是所谓的必选参数,也可以称为位置参数,除此之外,python还拥有其他语言不具有的一些参数类型,以下将进行一一介绍. ...