自定义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<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-塔防小游戏第一篇:一个防御塔+多个野怪(简易版)*的更多相关文章
- 自定义View5 -塔防小游戏:第二篇防御塔随意放置
第一篇:一个防御塔+多个野怪(简易版) 第二篇:防御塔随意放置 自定义View,处理事件分发,up,move,down. 第三篇:防御塔随意放置+多组野怪 第四篇:多波野怪 第五篇:杀死野怪获得金币 ...
- 自定义View6 -塔防小游戏:第三篇防御塔随意放置+多组野怪
第一篇:一个防御塔+多个野怪(简易版)第二篇:防御塔随意放置第三篇:防御塔随意放置+多组野怪 1.动态addView防御塔 2.防御塔放置后不可以移动 3.弯曲道路 4.素材替换 第四篇:多波野怪 第 ...
- Python制作塔防小游戏
开发工具 Python版本:3.6.4 相关模块: pygame模块: 以及一些Python自带的模块.
- [译]终极塔防——运用HTML5从头创建一个塔防游戏
翻译共享一篇CodeProject的高星力作,原文地址:http://www.codeproject.com/Articles/737238/Ultimate-Tower-Defense 下载演示项目 ...
- 回合对战制游戏第一篇(初识java)
回合对战制游戏第一篇 一,所谓的java. java是一门完全面向对象的编程语言,而之前所接触到的C语言是一门面向有一个过程的语音,对于两个的区别应该有一个清楚的认识. java的第一个内容. 类和对 ...
- 解剖SQLSERVER 第一篇 数据库恢复软件商的黑幕(有删减版)
解剖SQLSERVER 第一篇 数据库恢复软件商的黑幕(有删减版) 这一系列,我们一起来解剖SQLSERVER 在系列的第一篇文章里本人可能会得罪某些人,但是作为一位SQLSERVER MVP,在我 ...
- unity3D游戏开发实战原创视频讲座系列9之塔防类游戏开发第一季
解说文件夹 塔防游戏0基础篇... 第一讲 游戏演示和资源介绍... 第二讲 游戏场景的完毕... 第三讲 预制体的制作... 第四讲 敌人的随机产生和按路径行走... 第五讲 塔防工具的产 ...
- HTML5小游戏【是男人就下一百层】UI美化版
之前写的小游戏,要么就比较简单,要么就是比较难看,或者人物本身是不会动的. 结合了其它人的经验,研究了一下精灵运动,就写一个简单的小游戏来试一下. 介绍一下几个主要的类: Frame:帧的定义,主要描 ...
- 前端—我的第一篇博客 梦开始的地方(面向对象版tab栏)
这是我的第一篇博客 博客生涯才开始 但是人生已经过去了二十个年头了 才开始弄这个 也没搞得太懂 我原本的想法是想搞个源代码上来 但是看了半天好像就只能传html源代码 那我还有css js的部分呢 我 ...
随机推荐
- GitHub-SSH密钥获取
SSH密钥 需要先安装git的客户端,下载: http://git-scm.com/download/ 使用下列步骤完成密钥的添加. 检查系统是否存在密钥 运行 Git Bash, 在弹出的终端中输入 ...
- 密码学系列之:PKI的证书格式表示X.509
目录 简介 一个证书的例子 X.509证书的后缀 .pem .cer, .crt, .der .p7b, .p7c .p12 .pfx 证书的层级结构和交叉认证 x.509证书的使用范围 总结 简介 ...
- 重学ES系列之字符串方面的处理
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 关于各种Vue UI框架中加载进度条的正确使用
这里拿MUSE UI 中的进度条举例 <mu-circular-progress :size="40" class="icon" v-if="i ...
- Vue路由的模块自动化与统一加载
首先呢,我们来看看一般项目路由是怎么划分的. 为什么这么划分呢?如果大项目业务非常多,单纯的单页面很难维护,我们只有这样规范化,才能高效率. 模块自动化与统一加载的好处: 规范化命名(模块名.业务名. ...
- go: 如何编写一个正确的udp服务端
udp的服务端有一个大坑,即如果收包不及时,在系统缓冲写满后,将大量丢包. 在网上通常的示例中,一般在for循环中执行操作逻辑.这在生产环境将是一个隐患.是的,俺就翻车了. go强大简易的并发能力可以 ...
- 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 ...
- 爬虫(6) - 网页数据解析(2) | BeautifulSoup4在爬虫中的使用
什么是Beautiful Soup库 Beautiful Soup提供一些简单的.python式的函数用来处理导航.搜索.修改分析树等功能 它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简 ...
- eclipse使用小记录
(手动狗头)之前用eclipse的时候左侧的project栏不知道为什么整没了....记录一下 1.击Window--how View--other 2.Project Explorer,就可以了
- nginx 日志按日期分隔
#user nobody; user root; worker_processes 1; error_log /spdblogs/nginx/logs/error.log; error_log /sp ...