这学期期中的时候,我被朋友介绍一个老师,这个老师在开发一款射击游戏,让我帮他做界面。秉持着有任务就接下,边学边做的原则,我开始了为期一个月的开发。下面是我对这个项目的总结(资源已上传,可以去我上传的资源查看下载)资源下载(虽然用安卓来实现这个比较鸡肋)
好了,开始介绍。
首先第一步就是布局,布局其实没什么好说的了,我这里的布局其实最基本的就是分了两个ImageView用来当作主画布和游戏时计分表的画布,在加上一个Chronometer用来计时,里面的VideoView是一个视频播放器,用于打中后靶子倒下视频的实现,这里先暂不考虑,毕竟这是需要一个专门的动画,但是我用了一个小段的视频去实验了,是可以实现的,不多说了,直接上代码和效果图吧
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.mypaint.MainActivity">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/bg"
android:background="@drawable/photo3">
</ImageView>
<Chronometer
android:id="@+id/chronometer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:textSize="20dp"
android:textColor="#FFFFFF">
</Chronometer>
<!-- <VideoView-->
<!-- android:id="@+id/bazi2"-->
<!-- android:layout_marginLeft="140px"-->
<!-- android:layout_marginTop="140px"-->
<!-- android:layout_height="300px"-->
<!-- android:layout_width="300px">-->
<!-- </VideoView>-->
<ImageView
android:id="@+id/view"
android:background="#3A3A3A"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</ImageView>
</RelativeLayout>
布局差不多了,接下来就是最主要的功能实现。最开始的设想是通过蓝牙连接,将设备连入电视等大屏幕,在用专门的射击工具来获取点坐标,到那时由于在开发过程中,老师那边的硬件有点问题,所以我就改成了用点击来获取坐标。
在游戏的开始界面,通过点击任意处进入游戏,然后显示靶子以及计分表,并开始计时。通过点击屏幕来达成射击效果,每打出一枪就画出分值以及击中区域。规定每十枪作为一轮,打完十枪之后就进行清除靶子以及计分表上的数据,并统计总分数以及总时间,算出总得分率。
那么综上所述,功能实现的第一步就是应该考虑靶子以及点。我这里的靶子是比较常见的圆靶,分四个区域:C区2分,B区5分,A区8分,靶心10分。而且为了营造出靶近靶远的效果,就必须去调整靶子的大小。但是这样的话这么多个靶子,如果每个都要去进行逻辑一样只是具体参数不一样的判断,就会造成我们的代码冗长且杂乱。所以我们就必须去自定义两个类:靶子类和点类。
其中靶子对应的代码如下:
public class Target {
private float ca,cb,cr;//靶子圆心坐标以及半径
public Target ( float ca, float cb,float cr)
{
this.setCa(ca);
this.setCb(cb);
this.setCr(cr);
}
public void setCa(float ca){
this.ca = ca;
}
public void setCb(float cb) {
this.cb = cb ;
}
public void setCr(float cr){
this.cr = cr ;
}
/** **/
public float getCa(){
return this.ca ;
}
public float getCb(){
return this.cb;
}
public float getCr(){
return this.cr ;
}
/**
*
* 在画布上画上靶子
*
* **/
public Bitmap draw(Canvas canvas, Paint paint,Target target,Bitmap alterBitmap){
paint.setColor(Color.WHITE);
canvas.drawCircle(target.getCa(),target.getCb(),target.getCr(),paint);
paint.setColor(Color.BLUE);
canvas.drawCircle(target.getCa(),target.getCb(), (float) (target.getCr()*0.8),paint);
paint.setColor(Color.WHITE);
canvas.drawCircle(target.getCa(),target.getCb(), (float) (target.getCr()*0.5),paint);
paint.setColor(Color.BLUE);
canvas.drawCircle(target.getCa(),target.getCb(), (float) (target.getCr()*0.1),paint);
return alterBitmap;
}
}
点的话其实不当作一个类也是可以的,只是为了方便和可读性。代码如下:
public class Point {
private int grade,num;//点对应的分数以及标号
private String where;//点对应的区域
public Point ( int grade,int num,String where)
{
this.setGrade(grade);
this.setNum(num);
this.setWhere(where);
}
public void setGrade(int grade){
this.grade = grade;
}
public void setNum(int num) {
this.num = num ;
}
public void setWhere(String where){
this.where = where ;
}
/** **/
public int getGrade(){
return this.grade ;
}
public int getNum(){
return this.num;
}
public String getWhere(){
return this.where;
}
/** **/
}
好,做到这准备工作就差不多了。接下来开始画画!主要代码如下,先对射击次数进行判断,如果还没有进行射击,则应该是显示的开始界面。当开始射击也就是选择开始之后,这个时候number还是等于0,开始画上靶子以及计分表并开始计时,同时通过MediaPlayer进行枪击声的同步播放以及枪击点的标记,并调用 is_hit 函数进行判断是否击中,代码如下:
if ( number == 0 ){
//刚开始玩游戏
paint.setColor(Color.WHITE);
paint.setTextSize(120);
canvas.drawText("点击此处开始游戏吧",bitmap.getWidth()/2,bitmap.getHeight()/2,paint);
canvas.drawBitmap(alterBitmap, new Matrix(), paint);
imageView.setImageBitmap(alterBitmap);
}
imageView.setOnTouchListener(new View.OnTouchListener() {
int startX;
int startY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (number == 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR); //清空画布
showTable();//画计分表
//画靶子一
target.draw(canvas, paint, target, alterBitmap);
//画靶子二
target1.draw(canvas, paint, target1, alterBitmap);
//画靶子三
target2.draw(canvas, paint, target2, alterBitmap);
// 先将背景画上
canvas.drawBitmap(alterBitmap, new Matrix(), paint);
imageView.setImageBitmap(alterBitmap);
//开始计时
chronometer.setBase(SystemClock.elapsedRealtime());
chronometer.start();
}
//创建MediaPlayer,设置数据源
mediaPlayer = MediaPlayer.create(MainActivity.this, R.raw.voice);
//设置声音流类型
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.start();
// 获取手按下时的坐标
startX = (int) event.getX();
startY = (int) event.getY();
if (number != 0){
//number=1时为点击开始游戏射出的一枪,不显示红点
paint.setColor(Color.RED);
canvas.drawCircle(startX, startY, 15, paint);
imageView.setImageBitmap(alterBitmap);
//判断是否打中,多个靶时只会击到一个靶,所以如果把这个判断放到is_hit函数里面会有误判的情况
if (is_hit(startX, startY, target) == false && is_hit(startX, startY, target1) == false && is_hit(startX, startY, target2) == false ) {
where = "未击中靶子";
message(0, where);
}
}else number++;
break;
//
// case MotionEvent.ACTION_UP:
// canvas.drawColor(0, PorterDuff.Mode.CLEAR); //清空画布
// break;
}
return true;
}
});
与此同时is_hit函数的代码如下:
/**
* 判断是否击中靶子 分为4个计分区域 分别为2,5,8,10分
* 1.方形靶:判断左上角与右下角
* 2.圆形靶:判断击中点与圆心的距离
* 3.不规则靶
**/
public boolean is_hit(int x, int y, Target cTarget) {
boolean is = true;
double e = Math.pow(x - cTarget.getCa(), 2); //点与圆心的距离
double f = Math.pow(y - cTarget.getCb(), 2);
if (e + f > Math.pow(cTarget.getCr(), 2)) {
is = false; //如果大于半径的平方,则判断为未击中靶
} else {
//正方形(长方形)时,分别比较这个点与左上角与左下角的关系,有多少个区域就比较多少次,不需要嵌套
//圆形时,比较点与圆心的距离
double k = Math.pow(0.8 * cTarget.getCr(), 2);
double l = Math.pow(0.5 * cTarget.getCr(), 2); //两个区域
mark = 2;
if (e + f <= k) {
mark += 3;
if (e + f <= l) {
mark += 3;
if (e + f <= Math.pow(0.1 * cTarget.getCr(), 2)) {
mark = 10;
where = "击中靶心";
message(mark, where);
} else {
where = "击中A区";
message(mark, where);
}
} else {
where = "击中B区";
message(mark, where);
}
} else {
where = "击中C区";
message(mark, where);
}
}
return is;
}
通过message函数来进行分数的绘制,每一次刷新界面其实也就是将计分表重新绘制了一遍,至于为什么选择以Canvas的形式来展示计分表,是因为老师说这样避免了分辨率导致表格变形的问题。
函数代码如下:
/**
* 刷新表格信息
* 将击中分数存储起来
* 每射击一次,总分数,以及得分率会刷新(重新建表);计分表也会随之刷新
* 总时间是一直变化
**/
ArrayList<Point> list = new ArrayList<Point>();
private Point point;
public void message(int mark, String where) {
number += 1;
//规定只能射十发
if( number == 11 ){
chronometer.stop(); //停止计时
canvas.drawColor(0, PorterDuff.Mode.CLEAR); //清空画布
double totalss = 0;
String time = chronometer.getText().toString();
String[] split = time.split(":");
String string3 = split[0];
double min = Integer.parseInt(string3);
double Mins =min*60;
double SS =Integer.parseInt(split[1]);
totalss = Mins+SS;
double k = all_mark/totalss;//得分率
DecimalFormat df = new DecimalFormat("######0.00");//取后两位小数
paint.setColor(Color.WHITE);
paint.setTextAlign(Paint.Align.CENTER);
canvas.drawText("耗时为"+time,bitmap.getWidth()/2,bitmap.getHeight()/2-100,paint);
canvas.drawText("总分数为:"+all_mark+"分",bitmap.getWidth()/2,bitmap.getHeight()/2+100,paint);
canvas.drawText("得分率为:"+df.format(k)+"%",bitmap.getWidth()/2,bitmap.getHeight()/2+300,paint);
imageView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
chronometer.setBase(SystemClock.elapsedRealtime());
number = 0;
all_mark = 0;
list.clear();
showDraw();
showTable();
break;
}
return true;
}
});
}
//number=1时为点击开始游戏射出的一枪,不记录
if (number != 1){
point = new Point(mark, number-1, where);
list.add(point);
all_mark += mark;
showGrade(c,number-1);
}
}
}
这就是几个最主要的功能函数,其实这个游戏从头到尾就只有一个界面,这些变化都是通过Canvas来实现的(将Canvas运用到极致哈哈哈)
感觉实现起来还是比较好实现的,主要的问题就是**点击的点在屏幕显示的时候偏差比较大,还没有得到解决。**如果大家有想法可以一起来讨论解决。
**如果有帮助的话,给我一个赞吧~**下面是效果展示: