塔防小游戏

第一篇:一个防御塔+多个野怪(简易版)
    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. [安洵杯 2019]easy_web-1

    1.首先打开题目如下: 2.观察访问的地址信息,发现img信息应该是加密字符串,进行尝试解密,最终得到img名称:555.png,如下: 3.获得文件名称之后,应该想到此处会存在文件包含漏洞,因为传输 ...

  2. BUUCTF-N种方法解决

    N种方法解决 这题提供的是一个key.exe 运行一下发现没办法运行,老办法,放到16进制打开看看. 这个data:image/jpg很明显了,base64转图片. 编码完成得到了一张二维码,再将得到 ...

  3. 基于Vue2.x的前端架构,我们是这么做的

    通过Vue CLI可以方便的创建一个Vue项目,但是对于实际项目来说还是不够的,所以一般都会根据业务的情况来在其基础上添加一些共性能力,减少创建新项目时的一些重复操作,本着学习和分享的目的,本文会介绍 ...

  4. 为什么不建议使用自定义Object作为HashMap的key?

    此前部门内的一个线上系统上线后内存一路飙高.一段时间后直接占满.协助开发人员去分析定位,发现内存中某个Object的量远远超出了预期的范围,很明显出现内存泄漏了. 结合代码分析发现,泄漏的这个对象,主 ...

  5. Python爬虫+数据可视化教学:分析猫咪交易数据

    猫猫这么可爱 不会有人不喜欢吧: 猫猫真的很可爱,和我女朋友一样可爱~你们可以和女朋友一起养一只可爱猫猫女朋友都有的吧?啊没有的话当我没说-咳咳网上的数据太多.太杂,而且我也不知道哪个网站的数据比较好 ...

  6. JS中通过id或者class获取文本内容

    一.JS通过id获取文本内容 二.JS通过class获取文本内容

  7. HDFS存储目录分析

    一.介绍 HDFS metadata以树状结构存储整个HDFS上的文件和目录,以及相应的权限.配额和副本因子(replication factor)等.本文基于Hadoop2.6版本介绍HDFS Na ...

  8. 腾讯云原生数据库TDSQL-C架构探索和实践

    作为云原生技术先驱,腾讯云数据库内核团队致力于不断提升产品的可用性.可靠性.性能和可扩展性,为用户提供更加极致的体验.为帮助用户了解极致体验背后的关键技术点,本期带来腾讯云数据库专家工程师王鲁俊给大家 ...

  9. 关于webapi调用wcf并发假死的分析

    原来IDFA(IOS推广获取到用户IOS手机的唯一标识,如果不刷机的话跟安卓的IMEI一样)在公司正常的页面是公用用一个网站和数据库的. 起初怀疑并发数太多,把数据库连接池的数量从一百设置到三百,确实 ...

  10. APISpace万券齐发,API采购大放价

    Eolink APISpace 是 Eolink 旗下专业的API 数据交易平台,上面拥有海量的API,开发者可以根据需求自由选择. 环境天气 全国天气预报,支持全国以及全球多个城市的天气查询,包含国 ...