Canvas
save()和restore()
Save()函数保存住当前Canvas的状态,之后对Canvas的操作都不会影响保存的Canvas状态,所以当restore()之后Canvas会恢复为保存之间的状态。
举例:在Canvas上画一个三角形-->save()-->绘制三角形的y轴镜像-->restore()
过程:restore之后,恢复save之前的canvas状态,即把三角形绘制到原来位置,此时canvas就是save之前的三角形和其save之后的y轴镜像。
镜像处理
通过public final void scale (float sx, float sy, float px, float py)
X轴镜像:scale(-1,1,px,py)
Y轴镜像:scale(1,-1,px,py)
注意,要绘制好镜像,必须把拉伸基点(px,py)设置好,并根据此点形成镜像后,对镜像操作。
注:对(px,py)的选择--例如将某一clip区域中的图像X轴镜像翻转,则
Px = left+frameW/2 left是clip区的左上角的x坐标,frameW是clip区的宽度
Py = top +frameH/2 top是clip区的左上角的y坐标,frameH是clip区的长度
即:(px,py)是clip区的中心点,以此点翻转后的图像刚好形成镜像图像
设置可视区域
注:必须要在clip之后才能绘制图形,不然不能显示效果
1.clipRect--正方形
Canvas.clipRect(left,top,right,bottom);
注:当canvas是正常状态,此方法是设置可视区域,当为y轴镜像状态(scale(1,-1)y轴反向拉伸)此方法是设置不可视区域。
2.ClipPath--由path指定路径
3.ClipRegion--设置区域块
Region表示几个区域块的集合,所以可以设置多个区域块,并且多个区域块之间可以通过逻辑处理来设置几个区域块之间的关系如Region.Op.UNION/INTERSECT/XOR
UNION:
INTERSECT:相同区域显示
XOR:不同区域才显示
出XOR指定的范围之外都显示,可以用于只显示某一范围的事物 Region r = new Region(); r.op(new Rect(0,0,scrWidth,scrHeight),Region.Op.UNION); r.op(new Rect(100,100,100+bmpW,100+bmpH/3),Region.Op.XOR); mCanvas.clipRegion(r); mCanvas.drawBitmap(bmp, 100, 100, paint); |
动画
通过设置图片的绘制位置来让图片动起来
通过连续的图片循环播放实现动画
通过设置可视区域实现动画和让人物动起来
1.实现动画
通过canvas.clipRect()设置可视区域,之后让可视区域固定在某一位置,不断改变图片的绘制位置实现动画。
如上图,设置一个大小范围是一条鱼的大小的可视区域,通过不断设置该图片的位置来实现鱼儿运动的效果
2.控制主角
1)首先让主角处于运动状态
2)根据用户的控制向左,向右,向上,向下
3)处理控制:根据当前机器人的坐标(rx,ry)和当前绘制帧。
计算出图片的绘制位置:(rx-x,ry-y)其中(x,y)为当前帧相对于第一帧的位置坐标
如上图,固定图片于一处,设置一个大小范围是一个机器人的大小的可视区域,不断移动可视区域来实现人物运动的效果。
Box2D学习
1.物理世界与手机屏幕的坐标系转换
物理世界:默认中心是重心位置
手机屏幕:默认绘制点是屏幕左上角
如果默认绘制的话,将会以物理世界的重心与屏幕的绘制点为基础,绘制物体,那么,将会使得部分不可见。
注:
1)调整物理世界的坐标,使之适应手机屏幕绘制
假设物理世界中要绘制的物体 宽为 width ,高为 height 则调整该物体的物理坐标为
(x+width/2,y+height/2)
调用方法 BodyDef.position.set(x,y);重置该物体的物理坐标
2)Box2D的物理世界中使用的单位是米,如果调用BOX2D函数,传入单位是米;
手机屏幕的单位是像素,BOX2D函数的返回值 米的单位 需转换成 像素
公式: 像素-->米 : 米 = 像素 / 30
米----->像素: 像素 = 米 * 30;
注:30 是建议转换比例
创建一个BOX2D物理世界
1.相关类: World(AABB aabb,Vec2 gravity,boolean doSleep)
--AABB:aabb表物理世界的范围
--Vec2:gravity表重力方向,即(x,y)-(0,0)的向量方向,一般为(0,10)表垂直向下的重力
--doSleep:静止的物体是否休眠,如果为true则只有施加力和碰撞才能唤醒。
/** * 物理世界构造初始化 */ public void init(){ aabb = new AABB(); //范围大小为400m2 aabb.lowerBound.set(-100, 100);//设置物理世界的左上角坐标,以米为单位 aabb.upperBound.set(100, 100);//物理世界的右下角坐标设置 gravity = new Vec2(0,10);//向下的10倍重力 world = new World(aabb,gravity,false); //开始模拟物理世界step(float timeStep,int iterations)中timeStep--模拟频率,应与刷屏频率,否则将不同步,一般60帧每秒 //iterations--迭代次数,单次模拟中遍历模拟运算数据的次数,值越大,模拟越精确,性能降低。一般10次 world.step(1/60, 10);//放于线程中,持续模拟,因为物理世界是持续的,所以模拟的物理世界也该持续 } |
注:修改重力方向world.setGravity(vec2 gravity); |
创建基本图形
步骤
1.声明多边形,即创建多边形皮肤
2.声明物体,即创建刚体
3.创建物体,即由刚体创建一个物体,在为物体添加多边形皮肤
代码
/** * 创建一个矩形 */ public void createRect(int x, int y,int width, int height,boolean isStatic){ //===创建多边形皮肤==== PolygonDef polyDef = new PolygonDef(); if(isStatic){//是静态物体,如山 polyDef.density = 0;//表示是静态物体 } else polyDef.density = 1; polyDef.friction=0.5f;//抹茶李为0.5 polyDef.restitution = 0.2f;//恢复力为0.2 //定义多边形的形状 polyDef.setAsBox(width/2/RATE, height/2/RATE);//为物体的宽和长的一半? //===定义刚体===== BodyDef bodeDef = new BodyDef(); bodeDef.position.set((x+width/2)/RATE, (y+height/2)/RATE);//重新设定物体的物理坐标 Body body = world.createBody(bodeDef); //利用刚体创建一个物体 body.createShape(polyDef);//为物体添加一个多边形皮肤 body.setMassFromShapes();//打包整个物体 } |
创建自定义多边形 在上所述的代码中,去掉红色代码段,添加如下即可创建一个三角形 polyDef.addVertx(new Vec2(X1,Y1)); polyDef.addVertex(new Vec2(X2,Y2)); polyDef.addVertex(new Vec2(X3,Y3)); |
//圆形绘制 将PolygonDef类换成CircleDef类 |
绘制图形
注:Box2D只负责提供绘制所需的数据,绘制具体图形还是由Canvas绘制
例如:当一个物体受力后,Box2D世界中个body的位置将发生改变,所以可以通过Box2D提供的受力之后的数据来绘制出现实世界中物体受力后相同的表现,如两小球相撞后会反弹。
步骤
1.获取物体在BOX2D中的中心点
Vec2 position = body.getPosition(); |
2.把中心点转换成绘制点
int x = position.x -width/2;//其中width是图形的宽 int y = position.y-height/2//其中height是图形的高 |
3.由canvas根据(x,y)绘制
绘制自定义类
public Body createBitmap(int x,int y,Bitmap bmp,int width,int height,boolean isStatic){ PolygonDef pd=new PolygonDef(); if(isStatic){ pd.density = 0; } else pd.density = 1; pd.friction = 0.5f; pd.restitution = 0.2f; pd.setAsBox(width/RATE/2, height/RATE/2); BodyDef bd = new BodyDef(); bd.position.set((x+width/2)/RATE, (y+height/2)/RATE); Body body = world.createBody(bd); body.createShape(pd); body.m_userData = bmp;//将body的m_userData设为图片 body.setMassFromShapes(); return body; } |
注:Body的m_UserData用于存储自定义的类对象, |
遍历World的Body
Body bd = world.getBodyList(); int len = world.getBodyCount();; for(int i = 1;i<len;i++){ MyCircle mc = ((MyCircle)bd.m_userData); mc.setX(bd.getPosition().x*Box2DWorld.RATE); mc.setY(bd.getPosition().y*Box2DWorld.RATE); bd= bd.m_next;//指针指向下一个body } |
注: 1)Body的链表中,最后一个节点为空,所以遍历只遍历world.getBodyCount()-1次,因为此方法默认返回1 2)getPosition()返回的是body在物理世界中的中心点 3)getWorldCenter()返回的是在物理世界中所处位置 |
设置物体坐标和给Body施加力和改变重力方向
设置坐标:body.setXForm();
方法:body.applyForce(Vec2 force,Vec2 point);
--------force:x和y轴的力的方向,有正负之分,正数表示x/y轴正向的力
--------point:body所在的物理位置
获取body在物理世界中所处的位置:getWorldCenter();
改变重力方向:world.setGravity(Vec2 gravity);
碰撞监听,筛选,Body传感器
碰撞监听
1.接口:ContactListner || ContactFilter
2.接口方法
2.1 public void add(ContactPoint arg0)--发生了碰撞,新的碰撞接触点
public void persist(ContactPoint arg0) --已有碰撞接触点再次碰撞
public void remove(ContactPoint arg0) --碰撞接触点删除,即物体分离开
public void result(ContactResult arg0) --是否还有接触点,由则响应
注:
1)参数 arg0--相撞物体的接触点集合,可求出相撞两物体Body b1 = arg0.shape1.getBody(); Body b2 = arg0.shape2.getBody();
2)设置监听函数:world.setContactListener(ContactListener listener);
2.2 public boolean shouldCollide(Shape arg0, Shape arg1) {
return false;
}
注:使用该监听器则会使得arg0,arg1的body失去碰撞效果,但是具有碰撞检测
碰撞筛选
1.类:FilterData
Class FilterData{ Private int categoryBits; Private int maskBits; Private int groupIndex; Public void set(FilterData data){ CategoryBits = data.categoryBits; maskBits = data.maskBits; groupIndex = data.groupIndex; } } |
注解: groupIndex:1)groupIndex>=0,body之间可以发生碰撞 2)groupIndex<0,同组不发生碰撞,不同组发生碰撞 3)groupIndex各不相同,则都会发生碰撞 categoryBits:设置碰撞种类 maskBits:指定碰撞种类 例:body1的categoryBits=2,body2的categoryBits = 4,则body3想和body1,body2相撞则其categoryBits=2+2(body1,body2的categoryBits之和); 注:category必须是2的倍数,groupIndex分组只有16个种类 |
|
2.设置方法
1)在皮肤定义时如 CircleDef cd =new CircleDef(); cd.filter.groupIndex = 1;......
2)通过body:body.getShapeList().getFilterData().groupIndex = 1;......
Body的传感器
body.getShapeList().m_isSensor = true;
如果设置m_isSensor=true则有碰撞检测,无碰撞效果
关节
距离关节(DistanceJoint)
限制两个Body的质心距离保持不变
1.类:DistanceJoint/DistanceJointDef
2.步骤:
1)关节定义DistanceJoint/
2)是否可以相撞
3)创建关节world.createJoint(JointDef jd);
/** * 创建距离关节 */ public DistanceJoint createDistanceJoint(Body body1,Body body2,boolean collideConnected){ DistanceJointDef djd = new DistanceJointDef(); djd.initialize(body1, body2, body1.getWorldCenter(), body2.getWorldCenter()); djd.collideConnected = false;//该属性表示距离关节上的两个Body是否可以相撞 DistanceJoint dj = (DistanceJoint) world.createJoint(djd); return dj; } |
2.常用方法
getAnchor1():获取第一个Body的中心点,即称之为锚点
getAnchor2();获取第二个锚点
旋转关节(RevoluteJoint)
1.类:RevoluteJointDef/RevoluteJoint
一个Body围绕另一个body旋转
/** * 创建选择关节 */ public RevoluteJoint createRevoluteJoint(Body body1,Body body2 ){ RevoluteJointDef rjd = new RevoluteJointDef(); rjd.initialize(body1, body2, body1.getWorldCenter()); rjd.maxMotorTorque = 1;//最大预期马达扭矩 rjd.motorSpeed = 20;//最终扭矩 rjd.enableMotor = true;//启动马达 RevoluteJoint rj = (RevoluteJoint) world.createJoint(rjd); return rj; } |
注: 1)启动马达才能旋转--需rjd.enableMotor = true启动;设定最大扭矩;设定马达速度 2)maxMotorTorque:旋转速度的初始值,太小则无法正常旋转, 3)motorSpeed:旋转速度的最终值,即最终扭矩,又初始值慢慢增大到最终值。默认旋转方向为逆时针,如果该值为负,则顺时针旋转 4)enableLimite:是否启动关节限制 lowerAngle:旋转关节角最小角度 upperAngle:旋转关节角最大角度 旋转关节角为正则逆时针旋转,为负顺时针旋转 |
齿轮关节(GearJoint)
1.方法:使用两个旋转关节,一个主动轮,一个从动轮
2.代码
/** * 创建齿轮关节的主动轮 */ public RevoluteJoint createRevoluteJoint1(Body body1 ){ RevoluteJointDef rjd = new RevoluteJointDef(); rjd.initialize(world.getGroundBody(), body1, body1.getWorldCenter()); rjd.maxMotorTorque = 1;//最大马达扭矩 rjd.motorSpeed = -20;//马达速度 rjd.upperAngle = 3; rjd.enableMotor = true;//启动马达 RevoluteJoint rj = (RevoluteJoint) world.createJoint(rjd); return rj; } /** * 创建选择关节的从动轮 */ public RevoluteJoint createRevoluteJoint2(Body body2 ){ RevoluteJointDef rjd = new RevoluteJointDef(); rjd.initialize(world.getGroundBody(), body2, body2.getWorldCenter()); rjd.enableMotor = true;//启动马达 RevoluteJoint rj = (RevoluteJoint) world.createJoint(rjd); return rj; } /** * 创建齿轮关节 */ public GearJoint createGearJoint(Body body1,Body body2,RevoluteJoint ...rj){ GearJointDef gjd = new GearJointDef(); gjd.body1 = body1; gjd.body2 = body2; gjd.joint1 = rj[0]; gjd.joint2 = rj[1]; gjd.ratio = 10;//旋转角度比,主动轮转动10转,从动轮转动1转 GearJoint gj = (GearJoint) world.createJoint(gjd); return gj; } |
RevoluteJoint rj1 = world.createRevoluteJoint1(body1); RevoluteJoint rj2 = world.createRevoluteJoint2( body2); gj = world.createGearJoint(body2, body1, rj2,rj1); |
注:world.getGroundBody()所获取的是Box2D所提供的默认body,可以代替各种情况下的所需默认body |
滑轮关节(PulleyJoint)
public PulleyJoint createPulleyJoint(Body body1,Body body2,Vec2 anchor1,Vec2 anchor2){ PulleyJointDef pjd = new PulleyJointDef(); anchor1 = new Vec2(anchor1.x/RATE,anchor1.y/RATE); anchor2 = new Vec2(anchor2.x/RATE,anchor2.y/RATE); pjd.initialize(body1, body2, anchor1, anchor2, body1.getWorldCenter(), body2.getWorldCenter(), 1f); return (PulleyJoint) world.createJoint(pjd); } |
注: 1)initialize(Body,Body,Vec2 anchor1,Vec2 anchor2,float r); --anchor1:滑轮1的中心点 --anchor2:滑轮2的中心点 --r:拉伸长度比例 例:r = 1,len1 = 5,len2 = 21; r = 2,len1 = 5,len2 = 26; R = 3,len1 = 5,len2 = 31 2) pjd.initialize(body1, body2, anchor1, anchor2, body1.getWorldCenter(), body2.getWorldCenter(), 1f); 如果参数body2在后则body2比body1重;body1在body2后则表示body1比body2重 例: pjd.initialize(body2, body1, anchor1, anchor2, body2.getWorldCenter(), body1.getWorldCenter(), 1f); |
移动关节(PrismaticJoint)
移动
public PrismaticJoint createPristamicJoint(Body body1,Vec2 axis){ PrismaticJointDef pjd = new PrismaticJointDef(); pjd.initialize(world.getGroundBody(),body1,body1.getWorldCenter(),new Vec2(1,0)); pjd.maxMotorForce = 10;//初始力 pjd.motorSpeed = 10;//最终力 pjd.enableMotor = true;//启动力 pjd.lowerTranslation = -80f/RATE; pjd.upperTranslation = 80f/RATE; pjd.enableLimit = true; return (PrismaticJoint)world.createJoint(pjd); } |
注: 1)maxMotorForce:初始力的大小 2)motorSpeed :最终力的大小,motorSpeed>0,则new Vec2(1,0)表示沿X轴正向;motorSpeed<0则new Vec2(1,0)表示沿X轴负方向 3) |
绑定在移动关节上的两个Body
将上述移动关节创建的初始化修改为
pjd.initialize(body1,body2,body1.getWorldCenter(),new Vec2(1,0));
即将body1和body2绑定在一起
鼠标节点
鼠标提供力,使得body向鼠标点移动
public MouseJoint createMouseJoint(Body body){ MouseJointDef mjd = new MouseJointDef(); mjd.body1 = world.getGroundBody();//相当于鼠标 mjd.body2 = body; mjd.target.x = body.getPosition().x; mjd.target.y = body.getPosition().y;//设置目标点即鼠标点击点的x,y坐标 mjd.maxForce = 100;//拉力 return (MouseJoint)world.createJoint(mjd); } |
@Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub body1.wakeUp();//唤醒body1 mj.m_target.set(event.getX()/RATE,event.getY()/RATE);//设置目标点的坐标 Log.d(tag, "onTouched!"); return super.onTouchEvent(event); } |
注: 1)必须要向鼠标关节设置目标点,才能使得鼠标关节指定的Body向目标点运动,即上述的屏幕触摸函数代码 2)鼠标关节向目标点移动时,会有一定弹性,最终趋于目标点,通过mj.m_gamma=1.0f设置弹性大小 |
获取AABB指定范围内所有的Shape实例
public Shape[]query(float x,float y, float w,float h,int maxCount){ AABB aabb = new AABB(); aabb.lowerBound = new Vec2(x,y); aabb.upperBound = new Vec2(x+w,y+h); Shape []shape=world.query(aabb, maxCount);//aabb表示查询范围,maxCount表示最大重叠数 for(int i = 0;i<shape.length;i++){ Body body= shape[i].getBody(); if(body.isStatic()){//判定条件,是否为静态物 ... } if(body.isSleeping()){//是否在休眠 ... } } } |
注: 1)通过AABB指定范围查询所有的Shape实例,maxCount为最大重叠数 world.query(aabb,maxCount); 2)对body筛选 body.isStatic()---是否为静态物 body.isSleeping()--是否在休眠 |
摧毁物体和关节
摧毁物体:world.destroyBody(Body body);
摧毁关节:world.destroyJoint(Joint joint);
注:摧毁物体和关节,必须先放入一个需删除的容器中,然后在world.step()后遍历容器并删除容器中的Body或Joint,否则会抛出异常,因为如果在任意位置删除Body或关节,但是物理世界正在进行模拟,需要改Body或Joint时没有就会出现异常。