塔防小遊戲
第一篇:一個防禦塔+多個野怪(簡易版)
1、canvas畫防禦塔,妖怪大道,妖怪行走路線
2、防禦塔攻擊範圍是按照妖怪與防禦塔中心距離計算的,大於防禦塔半徑則不攻擊,小於則攻擊
3、野怪被攻擊條件,血量>0 && 防禦塔範圍內
子彈要打在野怪身上,
下:y+移動距離/子彈攻速
上:y-移動距離/子彈攻速
左:x—移動距離/子彈攻速
右:x+移動距離/子彈攻速第二篇:防禦塔隨意放置
第三篇:防禦塔隨意放置+多組野怪
第四篇:多波野怪
第五篇:殺死野怪獲得金幣
第六篇:防禦塔可升級,增強攻擊力,增大射程
先上效果圖
由於原圖片過大,我剔除了其中的幀數,導致看著有些"瞬移"。
該篇是自定義View學習過程中做的簡單下游戲,目前分了6篇,全是自定義的view實現的,如果有同學有好的優化方案,歡迎留言。
目標:通過自定義View實現一個防禦塔攻擊多個野怪 思路:之前我有過View的文章,裡面的防禦塔都是用的圓代替,野怪用的矩形代替。我們分別建立防禦塔、妖怪大道、野怪,開啟動畫不斷重新整理View,不斷計算野怪和防禦塔的距離,只要小於防禦塔半徑就對野怪攻擊,攻擊樣式,我們可以動態建立imageview,使用移動動畫即可(塔xy -> 野怪xy)。最後皇帝血量100。
新建檔案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地址,我們一起學習。
下一篇是拖拽放置防禦塔,手動開啟、暫停遊戲。
持續書寫中........