松江上死猪的余香还未散去,昨天所有大型的禽类交易市场都被关闭了,扑杀了n万只生鸡,哎!鸡这种动物在中国真的有够悲剧,一遇到什么头疼脑热,扫黄打非,最先倒霉的总是这种生物,自己不检点出了问题,关鸡屁事啊!今天又感染了2个,病毒在上海这种密度的地方,都是以指数数量级传播的,一场新的生化危机即将爆发,这已经不是什么时候爆发的问题了,而是立刻爆发,还是明天爆发的问题了。我平时每天吃鸡腿饭的时候都要加两块素鸡的,今天吓的连素鸡也没敢让老板加。。太悲剧了
要说悲剧这还不算悲剧,悲剧的是我每天加班到10点多,回来还得熬夜给你们这帮看了博客不顶的白眼狼写教程,我的命真是贱,我估计要不了多久,我就会过劳死了,等我死后,我会保佑所有顶了贴的朋友,没有顶贴的朋友也不用害怕,我夜里会经常去找你们玩的。。。。
今天的内容比较多,所以就不扯蛋蛋了,直插主题。
今天我们来做一个散射子弹的效果,因为是散射,所以就需要有子弹的方向和旋转,这里我们需要引入向量来进行描述,具体的原理如下:
假如我们要让飞机或者子弹往A向量的方向飞,我们该怎么做呢?如图,a(x1,y1)点就可以代表当前的飞机或者子弹的位置, 我们用b点(目标点)的坐标减去a点的坐标(x2-x1,y2-y1)就可以得到A向量了,之后我们将A向量归一化,如果有同学不知道归一化是什么的,那么我觉得,传销或者公务员的工作会比较适合你,归一化后,得到A'向量(dx,dy),此时我们用a点的坐标分量,分别加上A'向量的模向量的分量(x1+dx,y1+dy)就可以使飞机向A向量的方向,移动一个单位的距离了,如果飞机有一个固定的速度speed,只要让它乘以A'向量就可以了,即(x1+dx*speed,y1+dy*speed)就可以得到飞机往A向量方向移动speed个单位a'模向量的位置了!
ok下面我们来动手实现,恰好java自己有一个自带的类叫Vector就是向量的意思,但是我怎么看怎么觉得这货是个集合,擦!这货还真他娘的是个集合,这个语法还真和c里面的指针*有的一拼啊,这些java大神们的想法岂是吾等鼠辈可以理解的?难怪有本书叫做 Thinking in Java ,好吧,木有办法,我只好自己写一个简单的向量类,并且实现一些向量中的基本算法与功能,暂且叫它PVector类吧
package planet;
public class PVector {
public float x, y;
public PVector(double x1,double x2) {
this.x = (float) x1;
this.y = (float) x2;
}
//向量相加
public PVector add(PVector v) {
float x = this.x + v.x;
float y = this.y + v.y;
return new PVector(x, y);
}
//向量相减
public PVector sub(PVector v) {
float x = this.x - v.x;
float y = this.y - v.y;
return new PVector(x, y);
}
//返回一个新的PVector的副本
public PVector newPVector(){
return new PVector(this.x,this.y);
}
//将向量按逆时针绕原点旋转
public void rotateCW(double radian) {
double rx = (this.x* Math.cos(radian))+ (this.y* Math.sin(radian));
double ry =(this.y* Math.cos(radian))- (this.x* Math.sin(radian));
x= (float) rx;
y= (float) ry;
this.x=x;
this.y=y;
}
//将向量旋转任意角度
public void rotateAngle(int angle){
if (angle >0){
this.rotateCWAngle(angle);
}else{
angle =-angle;
this.rotateCCWAngle(angle);
}
}
//将向量逆时针绕原点旋转
public void rotateCWAngle(double angle) {
double radian = PVector.transAngle(angle);
rotateCW(radian);
}
// 将向量逆时针绕原点旋转
public void rotateCCW(double radian) {
double rx = (this.x* Math.cos(radian))- (this.y* Math.sin(radian));
double ry = (this.x* Math.sin(radian))+ (this.y* Math.cos(radian));
x= (float) rx;
y= (float) ry;
this.x=x;
this.y=y;
}
//将向量逆时针绕原点旋转
public void rotateCCWAngle(double angle) {
double radian = PVector.transAngle(angle);
rotateCCW(radian);
}
//角度换成弧度
public static double transAngle(double angle){
return Math.PI/180*angle;
}
// 检测两个向量之间的夹角(返回角度)
public double checkVectorAngle(PVector p2){
double n = this.x*p2.x+this.y*p2.y;
double m =Math.sqrt(this.x*this.x+this.y*this.y)*Math.sqrt(p2.x*p2.x+p2.y*p2.y);
return Math.acos(n/m);
}
//取模运算
public float getLength (){
float dis = Calculate.getDistance(this.x, this.y).floatValue();
return dis;
}
// 归一化
public PVector normalize() {
float dis =this.getLength();
if (dis == 0)
dis = 0.00000000001f;
float x1 = this.x / dis;
float x2 = this.y / dis;
return new PVector(x1, x2);
}
//设置向量的x,y分量
public void setVector(float x, float y){
this.x=x;
this.y=y;
}
}
ok工具有了,下面我们来开始实现,我们首先在Bullet类中添加两个属性
//目标向量
public static PVector targetV = new PVector(0,0);
//初始速度向量(默认是垂直往上的向量)
private PVector initV=new PVector(0, -1);
在update函数中 我们按照上面的原理改写原来的子弹移动算法
public void update(){
this.checkDead();
this.checkEnemyCollision();
//将x,y坐标,分别加上初始方向模向量的x,y分量*速度值
this.x = this.x+initV.x*speed;
this.y = this.y+initV.y*speed;
}
这样一写的话,我们发现一个问题,我们加入一种新的子弹的时候,原来的子弹就得删除掉,而我们希望游戏中能有多种多样的子弹射击方式和子弹样式,这该咋办呢?
这里我们需要将Bullet类的update和draw函数改写一下,以适应实现多种子弹的功能!我们首先创建一个接口TypeConst接口,在里面定义各种子弹和射击方式
package planet;
public interface TypeConst {
//我方普通子弹
int MY_BULLET_NORMAL_1 =1;
//我方直线向量子弹
int MY_BULLET_LINE =3;
//敌方的普通子弹
int ENEMY_BULLET_NORMAL_1 =2;
//发射一颗子弹的射击方式
int MY_SHOT_SINGLE_BULLET_1 =1;
//发射多颗子弹的射击方式
int MY_SHOT_MULITI_BULLET_1 =2;
}
上面的静态变量MY_BULLET_NORMAL_1中,第一位MY-代表是敌方还是我方打出的子弹,第二位可以是BULLET 也可以是SHOT,如果是BULLET就代表是指的子弹类型,SHOT的话就代表射击方式,第三位代表特点,比如我们这里要做的子弹就是直线向量子弹,上节课的就叫普通子弹,最后一位就是编号了。
我们在Bullet类中添加一个属性int bulletType = TypeConst.MY_BULLET_LINE;用来代表子弹的类型
下面我们将Bullet类中的逻辑循环如此改写,
public void update(){
this.checkDead();
this.checkEnemyCollision();
this.updateBulletType();
//将x,y坐标,分别加上初始方向模向量的x,y分量*速度值
this.x = this.x+initV.x*speed;
this.y = this.y+initV.y*speed;
}
//设置子弹的类型的更新内容
public void updateBulletType(){
switch (this.bulletType){
case TypeConst.MY_BULLET_NORMAL_1:
//设置初始方向向量
initV.setVector(0,0);
this.y-=speed;
break;
case TypeConst.ENEMY_BULLET_NORMAL_1:
initV.setVector(0,0);
this.y+=speed/2;
break;
case TypeConst.MY_BULLET_LINE:
//改变子弹速度
this.setSpeed(15);
break;
}
}
将渲染循环如此改写 ,由于我们期望子弹不只是圆形的还可能是长方形的,我们把子弹直径radius,改变为两个变量rdiusW, rdiusH
public void draw(Graphics g){
this.drawBulletType(g);
}
//设置子弹的类型的渲染内容
public void drawBulletType(Graphics g){
switch (this.bulletType){
case TypeConst.MY_BULLET_NORMAL_1:
g.setColor(Color.blue);
g.fillOval((int)this.x,(int)this.y, rdiusW, rdiusH);
break;
case TypeConst.ENEMY_BULLET_NORMAL_1:
g.setColor(Color.YELLOW);
g.fillOval((int)this.x,(int)this.y, rdiusW, rdiusH);
break;
case TypeConst.MY_BULLET_LINE:
g.setColor(Color.blue);
g2d.drawRect((int)this.x,(int)this.y, rdiusW, rdiusH);
break;
}
}
ok,现在你只要改动Bullet类中的int bulletType = TypeConst.MY_BULLET_LINE;的类型就可以切换不同的子弹类型了。
此时运行,我们发现子弹垂直的往上打和之前的没有神马区别,现在咱们把initV改成(-1,-1),也就是让初始的方向向量变为偏飞机左边45度角的地方,再测试一下!ok子弹可以斜着飞了,不过看起来有一些奇怪,那是因为,你的发射角度旋转了45度,而咱们的图形没有旋转45度,只要我们把图形也旋转45度看着就很舒服了,由于Graphics 没有旋转图形的功能,所以我要将它转型为Graphics2d ,他是具有旋转图形一定角度的函数的,这个地方大家不必理会,也不用去研究,因为我不是在教大家如何用java编写pc游戏,大家肯定可以在自己开发的游戏平台或引擎里轻松的找到这个旋转图形的函数或功能的,所以你只记住这里计算出旋转角度的方法和算法就可以了!
这里我们现在Bullet类中添加一个rotateAngle属性,用来记录图形旋转的角度,然后在update()函数中,通过求子弹向量与垂直向量的夹角,这个夹角也就是图形需要旋转的角度了,这里要特别注意一下,向量是左偏于垂直向量,还是右偏。
public void update(){
this.checkDead();
this.checkEnemyCollision();
this.updateBulletType();
//将x,y坐标,分别加上初始方向模向量的x,y分量*速度值
this.x = this.x+initV.x*speed;
this.y = this.y+initV.y*speed;
//区分左偏垂直线,还是右偏
if (initV.x<0)
//求出initV向量与垂直向量之间的夹角
this.rotateAngle=-new PVector(0,-1).checkVectorAngle(initV);
else
this.rotateAngle=new PVector(0,-1).checkVectorAngle(initV);
}
然后我们把drawBulletType()函数中的直线子弹改成这样,主要我们旋转的是画笔,这个画笔是所有Bullet公用的一个,并不是每一个Bullet子弹实例都有一个自己的画笔,所以这里我们旋转完每一颗子弹的画笔后,都要复原回去
case TypeConst.MY_BULLET_LINE:
g.setColor(Color.blue);
//先旋转计算出的rotateAngle角度
g2d.rotate(this.rotateAngle,this.x, this.y);
//设置子弹的长宽
this.setRdius(5, 50);
g2d.drawRect((int)this.x,(int)this.y, rdiusW, rdiusH);
//复原rotateAngle角度
g2d.rotate(-this.rotateAngle,this.x, this.y);
break;
下面我们为Bullet类新增一个构造函数,让外部创建的时候能够直接指定,子弹的类型
public Bullet(double x,double y,boolean isEnemy,int bulletType){
this(x, y,isEnemy);
//传入子弹的类型
this.bulletType = bulletType;
}
ok现在的子弹已经可以斜射并且旋转了,不过只能打一颗子弹实在是不爽,我们下面来给我们的飞机添一种新的射击方式,让它可以自动产生多个方向不同角度的直线向量子弹,当然,我们还需要对MyPlanet类进行类似Bullet类的改造,让它具有能够拥有不同射击方式的功能
我们需要给MyPlanet类增加一个public int shotType =2;属性表示它的射击方式,然后我们改写fire()函数
public void fire(){
switch(this.shotType){
case TypeConst.MY_SHOT_SINGLE_BULLET_1:
//产生一颗子弹,位置就在自己飞机的正前方
if(this.fireCDTimer.act())
//这里的+20和-5用来调整子弹的初始位置,让它从飞机的正前方打出来
new Bullet(this.x+20,this.y-5);
break;
case TypeConst.MY_SHOT_MULITI_BULLET_1:
}
}
下面我们写一个能够自动生成多方向子弹的函数
/***
* 产生多颗子弹
* @param bulletNum 子弹数量
* @param amongAngle 子弹角度间隔
*/
public void makeMulitiBullet(int bulletNum,int amongAngle){
if(bulletNum<=1)return;
//先建一颗垂直向上的子弹
new Bullet(this.x+20,this.y-5,false,TypeConst.MY_BULLET_LINE).setInitV(new PVector(0,-1));
//用来判断左右偏转的标志
boolean flag=false;
//用来计算偏移角度
int num=1 ;
while(bulletNum-->0){
int rotateNum=++num/2;
//设置初始向量
PVector initV = new PVector(0,-1);
//如果flag是true就将向量向左边偏转,否则向右偏转
if(flag)
initV.rotateAngle(amongAngle*rotateNum);
else
initV.rotateAngle(-amongAngle*rotateNum);
//新建一个旋转的直线向量子弹
new Bullet(this.x+20,this.y-5,false,TypeConst.MY_BULLET_LINE).setInitV(initV);
//转换标记
flag = !flag;
}
}
这样,我们只需要调用这个函数,并传入子弹的数量,和子弹间的角度间隔,那么它就会自动帮我们生成散射的子弹了!
最后别忘了,Enemy类创建子弹时,也要给它传入初始的bulletType。这时我们再演示一下,看到了吧,我们的飞机变得凶残起来了!
源码地址:http://download.csdn.net/detail/azhangzhengtong/5231740