這篇文章主要介紹了android studio遊戲搖桿開發教程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨着小編來一起學習學習吧
最近在做一個山寨版的王者榮耀,剛開始做的時候毫無頭緒 搖桿的多點觸控做的特別爛
經過幾天的思考已完美解決所有問題,下面就和大家分享下這個搖桿的開發思路
若有不正之處,請多多諒解並歡迎指正。
首先這個搖桿要用到較多的數學知識,小編的數學特別爛也就高中水平吧
我們這個搖桿一共就五個按鈕,一個移動搖桿、三個技能搖桿和一個普通攻擊按鈕
最終效果
好了廢話少說讓我們開始吧
新建一個項目
建好項目之後,我們先新建一個類叫做“畫”。也是我們的主View
修改Hua.java的代碼
public class Hua extends RelativeLayout implements Runnable{ //繼承RelativeLayout 實現Runnable接口 private Paint p;//畫筆 public Hua(Context context) { super(context); p=new Paint(); setBackgroundColor(Color.BLACK);//背景顏色設爲黑色 } @Override protected void onDraw(Canvas g) {//重寫onDraw方法 super.onDraw(g); } @Override public void run() { } }
接下來我們做移動搖桿
我們要準備一張圖片(待會我會把圖片都丟在附件裏)
首先用ps畫一個這樣半透明的圓ps部分就不做教程了
把背景隱藏掉 保存爲png格式
把我們剛剛製作的圖片添加進來
先新建一個類 my.java
public class my { //這個類當一個全局變量使用 public static int w,h;//屏幕的寬高 public static float bili; public static MainActivity main; public static RectF re=new RectF(); public static int ontouchAlpha=100;//觸控區透明度0-255 0爲透明,爲了測試我們先設爲100 }
修改 MainActivity 的代碼
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); my.main=this; getSupportActionBar().hide();//隱藏標題欄 this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);//全屏 隱藏狀態欄 //判斷當前是否橫屏 如果不是就設爲橫屏,設爲橫屏之後會自動調用onCreate方法 if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { //獲取屏幕的寬高 DisplayMetrics dis = getResources().getDisplayMetrics(); my.w = dis.widthPixels; my.h = dis.heightPixels; //獲取屏幕分辨率和1920*1080的比例 以便適應不同大小的屏幕 my.bili = (float) (Math.sqrt(my.w * my.h) / Math.sqrt(1920 * 1080)); setContentView(new Hua(this)); } else { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);// 橫屏 } } }
新建類 Move.java
package com.yaogan.liziguo.yaogan; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; /** * Created by Liziguo on 2018/6/15. */ public class Move { private float x1,y1;//按下時的座標 大圓 private float x2,y2;//移動後的座標 小圓 private final float r1,r2;//r1大圓的半徑 r2小圓的半徑 public float angle;//x1y1指向x2y2的角度 弧度制 public boolean down=false;//判斷是否被按下 public boolean in=false;//判斷小圓是否在大圓裏面,簡單的說就是防止小圓被脫太遠 public boolean move=false;//判斷手指按下後是否移動(MY實際開發中用到,該教程用不到此變量) public Bitmap img;//大圓小圓的圖片 public Move(){ r1 = 480 * 0.5f * my.bili;//乘上一個比例 適應不同大小的屏幕 r2 = 300 * 0.5f * my.bili; img= BitmapFactory.decodeResource(my.main.getResources(),R.mipmap.yaogan);//初始化搖桿圖片 } public void down(float xx,float yy){ //搖桿按下後的操作 if(xx<r1) x1=r1; else x1 = xx; if(my.h-yy<r1) y1=my.h-r1; else y1 = yy; //上面的代碼是防止按下的位置太靠近屏幕邊緣 //跟x1=xx;y1=yy;區別不大,待會可以改成x1=xx;y1=yy;看看效果有什麼不同 down=true; } public void move(float xx,float yy){ //按下搖桿後移動的操作 angle=getAngle(xx,yy); in=in(xx, yy); move=isMove(xx,yy); if (!in) { //下面會做解釋 xx= (float) (x1+ Math.sin(angle)*r1*0.7f); yy= (float) (y1+ Math.cos(angle)*r1*0.7f); } x2=xx; y2=yy; } public void up(){ //鬆開後的操作 down=false; } public float getAngle(float xx,float yy){ //獲取x1y1指向x2y2的角度 double angle,k; if (y1==yy)//斜率不存在時 if (x1 > xx)//判斷x1指向x2的方向 angle=-Math.PI/2; else angle=Math.PI/2; else{ k=(x1-xx)/(y1-yy); //兩點的座標求斜率,至於爲什麼是(x1-x2)/(y1-y2)不是(y1-y2)/(x1-x2)待會我們再做解釋 if (y1 > yy) {//判斷x1y1指向x2y2的方向 // 用反tan求角度 這個高中好像沒學過 既然Math類已經幫我們封裝好了就直接拿來用吧 angle=Math.atan(k) + Math.PI; } else { angle=Math.atan(k); } //這段可寫可不寫 讓計算出來的角度屬於-PI/2到PI/2 if(angle>Math.PI) angle-=Math.PI*2; else if(angle<-Math.PI) angle+=Math.PI*2; } return (float) angle; } public boolean in(float xx, float yy) { //防止小圓被脫太遠 拖動範圍不超出r1的70% double r = Math.sqrt((x1 - xx) * (x1 - xx) + (y1 - yy) * (y1 - yy));//兩點間距離公式 if (r < r1*0.7f) return true; else return false; } public boolean isMove(float xx, float yy) { //判斷按下搖桿後 是否移動,如果x1y1 x2y2的距離大於r1*0.15視爲移動 // MY實際開發中用到,該教程用不到此變量 double r = Math.sqrt((x1 - xx) * (x1 - xx) + (y1 - yy) * (y1 - yy));//兩點間距離公式 if (r > r1*0.15f) return true; else return false; } public void onDraw(Canvas g, Paint p){ //畫搖桿 if(down) { //當搖桿被按下時 才顯示 //怎麼用Canvas畫圖這裏就不說了 my.re.left = x1 - r1; my.re.top = y1 - r1; my.re.right = x1 + r1; my.re.bottom = y1 + r1; g.drawBitmap(img, null, my.re, p); //畫大圓 my.re.left = x2 - r2; my.re.top = y2 - r2; my.re.right = x2 + r2; my.re.bottom = y2 + r2; g.drawBitmap(img, null, my.re, p); //畫小圓 } } }
新建類 OnTouchMove.java
package com.yaogan.liziguo.yaogan; import android.content.Context; import android.graphics.Color; import android.view.MotionEvent; import android.view.View; /** * Created by Liziguo on 2018/6/16. */ public class OnTouchMove extends View { //這個view負責監聽移動搖桿的手勢 private Move m; public OnTouchMove(Context context,Move move) { super(context); this.m=move; setBackgroundColor(Color.WHITE);//背景色設爲白色 getBackground().setAlpha(my.ontouchAlpha);//設置觸控區透明度 setOnTouchListener(new OnTouchListener() { //設置觸控監聽 @Override public boolean onTouch(View v, MotionEvent ev) { //加上getX() getY()因爲這個view不是分佈在左上角的 final float xx = ev.getX() + getX(), yy = ev.getY() + getY(); if (ev.getAction() == MotionEvent.ACTION_DOWN) { m.down(xx, yy);//按下時的操作 // m.move(xx, yy); } m.move(xx, yy);//移動時的操作 if (ev.getAction() == MotionEvent.ACTION_UP) { m.up();//鬆開時的操作 } return true;//不要返回false } }); } }
修改 Hua.java 的代碼
package com.yaogan.liziguo.yaogan; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.widget.RelativeLayout; /** * Created by Liziguo on 2018/6/15. */ public class Hua extends RelativeLayout implements Runnable{ //繼承RelativeLayout 實現Runnable接口 private Paint p;//畫筆 private Move m=new Move();//移動搖桿 public Hua(Context context) { super(context); p=new Paint(); setBackgroundColor(Color.BLACK);//背景顏色設爲黑色 //實例化一個OnTouchMove OnTouchMove onTouchMove=new OnTouchMove(context,m); //把onTouchMove添加進來 寬度爲屏幕的1/3 高度爲屏幕的1/2 addView(onTouchMove,my.w/3,my.h/2); //設置onTouchMove的位置 onTouchMove.setX(0); onTouchMove.setY(my.h/2); new Thread(this).start();//啓動重繪線程 } @Override protected void onDraw(Canvas g) {//重寫onDraw方法 super.onDraw(g); m.onDraw(g,p);//畫移動搖桿 } @Override public void run() { //每隔20毫秒刷新一次畫布 while(true){ try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();} postInvalidate();//重繪 在子線程重繪不能調用Invalidate()方法 } } }
好的 現在我們的搖桿可以說已經做好一大半了,因爲剩下的原理都一樣
先運行一遍看看效果吧
左下角的白色矩形是我們的OnTouchMove類,爲了更好的測試我們先讓他顯示出來 等做好了再隱藏掉
下面我們來解釋一下爲什麼斜率k=(x1-x2)/(y1-y2)而不是(y1-y2)/(x1-x2)吧
因爲我們手機上的平面直角座標系跟數學上的平面直角座標系不一樣
數學上的平面直角座標系是這樣的
而我們手機是這樣的
有沒有發現把手機的座標系 逆時針旋轉一下就是數學裏的座標系了,不過x跟y調了一下位置
所以我們在寫代碼的時候把x y換一下就行了
數學座標系中k=1的直線
程序中k=1的直線
再解釋下 Move 類的 move方法
public void move(float xx,float yy){ //按下搖桿後移動的操作 angle=getAngle(xx,yy); in=in(xx, yy); move=isMove(xx,yy); if (!in) { //下面會做解釋 xx= (float) (x1+ Math.sin(angle)*r1*0.7f); yy= (float) (y1+ Math.cos(angle)*r1*0.7f); } x2=xx; y2=yy; }
好的下面我們開始做技能搖桿,這教程做的比較累啊
下面的技能類是我直接從我遊戲裏拷貝過來的並做了些小修改
解釋可能沒那麼清楚畢竟原理都一樣
只不過是多了幾個功能而已
準備圖片
添加到工程裏
由於圖片比較多 我們加載圖的代碼位置改一下
修改 MainActivity。java 和 my.java
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); my.main=this; getSupportActionBar().hide();//隱藏標題欄 this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);//全屏 隱藏狀態欄 //判斷當前是否橫屏 如果不是就設爲橫屏,設爲橫屏之後會自動調用onCreate方法 if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { //獲取屏幕的寬高 DisplayMetrics dis = getResources().getDisplayMetrics(); my.w = dis.widthPixels; my.h = dis.heightPixels; //獲取屏幕分辨率和1920*1080的比例 以便適應不同大小的屏幕 my.bili = (float) (Math.sqrt(my.w * my.h) / Math.sqrt(1920 * 1080)); //加載圖片 my.border= BitmapFactory.decodeResource(my.main.getResources(),R.mipmap.border); my.cancel= BitmapFactory.decodeResource(my.main.getResources(),R.mipmap.cancel); my.down= BitmapFactory.decodeResource(my.main.getResources(),R.mipmap.down); my.yaogan= BitmapFactory.decodeResource(my.main.getResources(),R.mipmap.yaogan); my.cd= BitmapFactory.decodeResource(my.main.getResources(),R.mipmap.cd); setContentView(new Hua(this)); } else { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);// 橫屏 } } }
public class my { //這個類當一個全局變量使用 public static int w,h;//屏幕的寬高 public static float bili; public static MainActivity main; public static RectF re=new RectF(); public static int ontouchAlpha=100;//觸控區透明度0-255 0爲透明,爲了測試我們先設爲100 public static Bitmap border,cancel,down,yaogan,cd; public static Skill skill;//當前正在使用的技能 現在會報錯 因爲我們還沒新建技能Skill類 }
修改 Move 類的構造方法
public Move(){ r1 = 480 * 0.5f * my.bili;//乘上一個比例 適應不同大小的屏幕 r2 = 300 * 0.5f * my.bili; // img= BitmapFactory.decodeResource(my.main.getResources(),R.mipmap.yaogan);//初始化搖桿圖片//////////////////////// img=my.yaogan;//////////////////////////////////////////////////// }
新建技能類 Skill.java
package com.yaogan.liziguo.yaogan; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; /** * Created by Liziguo on 2018/6/16. */ public abstract class Skill { public int jineng; private final float x,y;//技能圖標中心位置,不是按下時的位置 private float x2, y2;//技能按下移動後手指的座標 private float xxx,yyy;//判斷拖動點是否超出兩倍r的範圍 private final float calcelx, cancely; public float angle;//技能按下後 x y指向xx yy的角度 public Bitmap img, imgborder, imgdown, imgyaogan,imgcd,imgcancel; private final float r2; private final float r3=50*my.bili; public boolean down=false; public boolean down_main=false;//down_main 只觸發一次; public boolean cancel=false; public int cdmax; public long last,cd=0;//last最後一次釋放技能的時間 /* 0 普通攻擊 1 技能1 2 技能2 3 技能3 */ public Skill(int jineng, int cd, Bitmap image){ this.jineng=jineng; switch (jineng){ case 0: x= my.w*0.87f; y= my.h*0.8f; break; case 1: x= my.w*0.7f; y= my.h*0.88f; break; case 2: x= my.w*0.75f; y= my.h*0.62f; break; case 3: x= my.w*0.9f; y= my.h*0.5f; break; default:x=y=0; } cdmax=cd; if(jineng == 0) r2=125*my.bili; else r2=80*my.bili; calcelx =my.w-r2*2; cancely =my.h/4; img=image; imgborder=my.border; imgdown=my.down; imgyaogan=my.yaogan; imgcd=my.cd; imgcancel=my.cancel; } // public abstract void down(); // public abstract void move(); // public abstract void up(); public void down(){ //DOWN 由ontouch觸發 if(cd>0)return; down=true; my.skill=this; } public abstract void down_main(); //DOWN 教程用不到該抽象方法 public void move(float x,float y){//按下技能後 由ontouch觸發 x2 =x; y2 =y; angle=getAngle(x2, y2); cancel=incancel(x,y); if (jineng !=0 && !in2(x,y)) { xxx= (float) (this.x+ Math.sin(angle)*r2*2); yyy= (float) (this.y+ Math.cos(angle)*r2*2); }else{ xxx=x; yyy=y; } } public abstract void move_main();//按下技能後 由MyActor觸發 教程用不到該抽象方法 public abstract void up(); //鬆開後 由MyActor觸發 釋放技能 public boolean in(float xx,float yy){ //判斷是否被點中 double r= Math.sqrt((x - xx)*(x-xx)+(y-yy)*(y-yy)); if(r<r2) return true; else return false; } public boolean in2(float xx, float yy) { //判斷拖動點是否超出兩倍r的範圍 double r = Math.sqrt((x - xx) * (x - xx) + (y - yy) * (y - yy)); if (r < r2 * 2) return true; else return false; } public boolean incancel(float xx,float yy){ //判斷是否取消 double r= Math.sqrt((calcelx - xx)*(calcelx -xx)+(cancely -yy)*(cancely -yy)); if(r<r2) return true; else return false; } public float getAngle(float xx,float yy){ //x y指向xx yy的角度 float angle,k; if (y==yy) if (x > xx) angle= (float) (-Math.PI/2); else angle= (float) (Math.PI/2); else{ k=(x-xx)/(y-yy); if (y > yy) { angle= (float) (Math.atan(k) + Math.PI); } else { angle= (float) Math.atan(k); } if(angle>Math.PI) angle-=Math.PI*2; else if(angle<-Math.PI) angle+=Math.PI*2; } return angle; } private float drawpx=10*my.bili; public void next(){ //計算技能冷卻時間 cd=cdmax-System.currentTimeMillis()+last; } //按下的時候技能圖標下移 顯示藍色框框 public void onDraw(Canvas g, Paint p){ my.re.left=x-r2; my.re.top=y-r2; my.re.right=x+r2; my.re.bottom=y+r2; if(down){ // new RectF(x-r2,y-r2,x+r2,y+r2); // new RectF(x-r2,y-r2+10*my.bili,x+r2,y+r2+10*my.bili); // my.re.left=x-r2; // my.re.top=y-r2; // my.re.right=x+r2; // my.re.bottom=y+r2; if(jineng!=0){ final float bl=2; my.re.left=x-r2*bl; my.re.top=y-r2*bl; my.re.right=x+r2*bl; my.re.bottom=y+r2*bl; //藍色框框未下移 g.drawBitmap(imgdown,null,my.re,p); } my.re.left=x-r2; my.re.top=y-r2; my.re.right=x+r2; my.re.bottom=y+r2; /////////////////////////////////////////////////////////// //技能圖片和技能邊框下移 my.re.top+=drawpx; my.re.bottom+=drawpx; g.drawBitmap(img,null,my.re,p); my.re.left-=drawpx; my.re.top-=drawpx; my.re.right+=drawpx; my.re.bottom+=drawpx; g.drawBitmap(imgborder,null,my.re,p); if(jineng!=0){ my.re.left=xxx-r3; my.re.top=yyy-r3; my.re.right=xxx+r3; my.re.bottom=yyy+r3; g.drawBitmap(imgyaogan,null,my.re,p); //cancle my.re.left= calcelx -r2; my.re.top= cancely -r2; my.re.right= calcelx +r2; my.re.bottom= cancely +r2; g.drawBitmap(imgcancel,null,my.re,p); } }else{ g.drawBitmap(img,null,my.re,p); if(jineng!=0 && cd>0) { p.setTextSize(40*my.bili); p.setColor(Color.WHITE); g.drawBitmap(imgcd,null,my.re,p); float f=cd/100f; f=(int)f; f=f/10; g.drawText(String.valueOf(f),x-p.getTextSize()*4/5,y+p.getTextSize()/3,p); } my.re.left-=drawpx; my.re.top-=drawpx; my.re.right+=drawpx; my.re.bottom+=drawpx; g.drawBitmap(imgborder,null,my.re,p); } } }
新建一個類 OnTouchSkill.java 他也是一個監聽view
這樣多點觸控會好寫很多,剛開始我是用一個view做監聽的 寫到我心態爆炸。。。
package com.yaogan.liziguo.yaogan; import android.content.Context; import android.graphics.Color; import android.view.MotionEvent; import android.view.View; /** * Created by Liziguo on 2018/6/16. */ public class OnTouchSkill extends View { /* A 普通攻擊 Q 技能1 W 技能2 E 技能3 R 沒有R */ public Skill A,Q,W,E; public OnTouchSkill(Context context,Skill a,Skill q,Skill w,Skill e) { super(context); A=a;Q=q;W=w;E=e; setBackgroundColor(Color.WHITE); getBackground().setAlpha(my.ontouchAlpha);//0-255 setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent ev) { final float xx = ev.getX() + getX(), yy = ev.getY() + getY(); if (ev.getAction() == MotionEvent.ACTION_DOWN) { if ( A.in(xx, yy)) { A.down(); } else if ( Q.in(xx, yy)) { Q.down(); } else if ( W.in(xx, yy)) { W.down(); } else if ( E.in(xx, yy)) { E.down(); } } if (my.skill != null) my.skill.move(xx, yy); if(ev.getAction()==MotionEvent.ACTION_UP){ A.down = false; Q.down = false; W.down = false; E.down = false; } return true; } }); } }
把監聽控件添加到Hua,修改 Hua.java
package com.yaogan.liziguo.yaogan; import android.content.Context; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.widget.RelativeLayout; /** * Created by Liziguo on 2018/6/15. */ public class Hua extends RelativeLayout implements Runnable{ //繼承RelativeLayout 實現Runnable接口 private Paint p;//畫筆 private Move m=new Move();//移動搖桿 /* A 普通攻擊 Q 技能1 W 技能2 E 技能3 R 沒有R */ public Skill A=new Skill(0,100, BitmapFactory.decodeResource(getResources(),R.mipmap.putonggongji)) { @Override public void down_main() { } @Override public void move_main() { } @Override public void up() { } }; public Skill Q=new Skill(1,1000, BitmapFactory.decodeResource(getResources(),R.mipmap.skill1)) { @Override public void down_main() { } @Override public void move_main() { } @Override public void up() { down_main=false; if(!cancel){ //技能冷卻時間 last= System.currentTimeMillis(); } } }; public Skill W=new Skill(2,1000, BitmapFactory.decodeResource(getResources(),R.mipmap.skill2)) { @Override public void down_main() { } @Override public void move_main() { } @Override public void up() { down_main=false; if(!cancel){ last= System.currentTimeMillis(); } } }; public Skill E=new Skill(3,1000, BitmapFactory.decodeResource(getResources(),R.mipmap.skill3)) { @Override public void down_main() { } @Override public void move_main() { } @Override public void up() { down_main=false; if(!cancel){ last= System.currentTimeMillis(); } } }; public Hua(Context context) { super(context); p=new Paint(); setBackgroundColor(Color.BLACK);//背景顏色設爲黑色 //實例化一個OnTouchMove OnTouchMove onTouchMove=new OnTouchMove(context,m); //把onTouchMove添加進來 寬度爲屏幕的1/3 高度爲屏幕的1/2 addView(onTouchMove,my.w/3,my.h/2); //設置onTouchMove的位置 onTouchMove.setX(0); onTouchMove.setY(my.h/2); //添加技能搖桿監聽 OnTouchSkill onTouchSkill=new OnTouchSkill(context,A,Q,W,E);//後添加的優先級高 addView(onTouchSkill); onTouchSkill.setX(my.w*0.7f-85*my.bili); onTouchSkill.setY(my.h/2-85*my.bili); new Thread(this).start();//啓動重繪線程 } @Override protected void onDraw(Canvas g) {//重寫onDraw方法 super.onDraw(g); m.onDraw(g,p);//畫移動搖桿 //畫技能 A.onDraw(g,p); Q.onDraw(g,p); W.onDraw(g,p); E.onDraw(g,p); } @Override public void run() { //每隔20毫秒刷新一次畫布 while(true){ try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();} //計算冷卻時間 A.next(); Q.next(); W.next(); E.next(); //釋放技能 if (my.skill != null) { my.skill.down_main();//教程用不到該方法 my.skill.move_main();//教程用不到該方法 if (my.skill.down == false) { my.skill.up(); my.skill = null; } } postInvalidate();//重繪 在子線程重繪不能調用Invalidate()方法 } } }
運行下看看效果吧
修改 my.java
public class my { //這個類當一個全局變量使用 public static int w,h;//屏幕的寬高 public static float bili; public static MainActivity main; public static RectF re=new RectF(); public static int ontouchAlpha=0;//把觸控區透明度改成0 public static Bitmap border,cancel,down,yaogan,cd; public static Skill skill;//當前正在使用的技能 }
再運行下
大功告成
下載地址:
android studio遊戲搖桿開發教程 仿王者榮耀搖桿
以上所述是小編給大家介紹的android studio遊戲搖桿開發教程詳解整合,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回覆大家的。在此也非常感謝大家對神馬文庫網站的支持!