塔防小游戏

第一篇:一个防御塔+多个野怪(简易版)
    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. GitHub-SSH密钥获取

    SSH密钥 需要先安装git的客户端,下载: http://git-scm.com/download/ 使用下列步骤完成密钥的添加. 检查系统是否存在密钥 运行 Git Bash, 在弹出的终端中输入 ...

  2. 密码学系列之:PKI的证书格式表示X.509

    目录 简介 一个证书的例子 X.509证书的后缀 .pem .cer, .crt, .der .p7b, .p7c .p12 .pfx 证书的层级结构和交叉认证 x.509证书的使用范围 总结 简介 ...

  3. 重学ES系列之字符串方面的处理

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. 关于各种Vue UI框架中加载进度条的正确使用

    这里拿MUSE UI 中的进度条举例 <mu-circular-progress :size="40" class="icon" v-if="i ...

  5. Vue路由的模块自动化与统一加载

    首先呢,我们来看看一般项目路由是怎么划分的. 为什么这么划分呢?如果大项目业务非常多,单纯的单页面很难维护,我们只有这样规范化,才能高效率. 模块自动化与统一加载的好处: 规范化命名(模块名.业务名. ...

  6. go: 如何编写一个正确的udp服务端

    udp的服务端有一个大坑,即如果收包不及时,在系统缓冲写满后,将大量丢包. 在网上通常的示例中,一般在for循环中执行操作逻辑.这在生产环境将是一个隐患.是的,俺就翻车了. go强大简易的并发能力可以 ...

  7. NC24017 [USACO 2016 Jan S]Angry Cows

    NC24017 [USACO 2016 Jan S]Angry Cows 题目 题目描述 Bessie the cow has designed what she thinks will be the ...

  8. 爬虫(6) - 网页数据解析(2) | BeautifulSoup4在爬虫中的使用

    什么是Beautiful Soup库 Beautiful Soup提供一些简单的.python式的函数用来处理导航.搜索.修改分析树等功能 它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简 ...

  9. eclipse使用小记录

    (手动狗头)之前用eclipse的时候左侧的project栏不知道为什么整没了....记录一下 1.击Window--how View--other 2.Project Explorer,就可以了

  10. nginx 日志按日期分隔

    #user nobody; user root; worker_processes 1; error_log /spdblogs/nginx/logs/error.log; error_log /sp ...