最近在做一个山寨版的王者荣耀,刚开始做的时候毫无头绪 摇杆的多点触控做的特别烂
经过几天的思考已完美解决所有问题,下面就和大家分享下这个摇杆的开发思路
若有不正之处,请多多谅解并欢迎指正。
首先这个摇杆要用到较多的数学知识,小编的数学特别烂也就高中水平吧
我们这个摇杆一共就五个按钮,一个移动摇杆、三个技能摇杆和一个普通攻击按钮
最终效果
好了废话少说让我们开始吧
新建一个项目
建好项目之后,我们先新建一个类叫做“画”。也是我们的主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游戏摇杆开发教程详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对自由互联网站的支持!