cocos2d-x for wp 之Box2D游戏-是男人就坚持60M(一)
搞了几天。发现cocos2d-x对c#(xna)真的是抛弃很彻底了。以后还是转c++吧。
废话结束,开始记录。
首先,跟上一节一样,先创建好一个世界并让这个世界开始模拟物理世界。
创建一个继承于CCLayer的一个层:PlayGame。并添加好下面三个引用、一个常量以及一些全局变量:
1 using cocos2d; 2 using Box2D.XNA; 3 using Microsoft.Xna.Framework; 4 5 6 public static double PTM_RATIO = 32.0; 7 8 World world; 9 CCSprite ball; 10 Body groundBody;
重写init函数、node函数
1 public override bool init() 2 { 3 if (!base.init()) 4 return false; 5 //获取窗口大小 6 CCSize winSize = CCDirector.sharedDirector().getWinSize(); 7 8 9 10 CCLabelTTF title = CCLabelTTF.labelWithString("Boxing", "Arial", 24); 11 title.position = new CCPoint(winSize.width / 2, winSize.height / 2 - 50); 12 this.addChild(title, 1); 13 14 Vector2 gravity = new Vector2(0.0f, 0.0f); 15 bool doSleep = true; 16 world = new World(gravity, doSleep); 17 18 19 BodyDef groundBodyDef = new BodyDef(); 20 groundBodyDef.position = new Vector2(0, 0); 21 groundBody = world.CreateBody(groundBodyDef); 22 PolygonShape groundBox = new PolygonShape();//凸多边形 23 FixtureDef boxShapeDef = new FixtureDef(); 24 boxShapeDef.shape = groundBox; 25 groundBox.SetAsEdge(new Vector2(0, 0), new Vector2((float)(winSize.width / PTM_RATIO), 0)); 26 groundBody.CreateFixture(boxShapeDef); 27 groundBox.SetAsEdge(new Vector2(0, 0), new Vector2(0, (float)(winSize.height / PTM_RATIO))); 28 groundBody.CreateFixture(boxShapeDef); 29 groundBox.SetAsEdge(new Vector2(0, (float)(winSize.height / PTM_RATIO)), 30 new Vector2((float)(winSize.width / PTM_RATIO), (float)(winSize.height / PTM_RATIO))); 31 groundBody.CreateFixture(boxShapeDef); 32 groundBox.SetAsEdge(new Vector2((float)(winSize.width / PTM_RATIO), (float)(winSize.height / PTM_RATIO)), 33 new Vector2((float)(winSize.width / PTM_RATIO), 0)); 34 groundBody.CreateFixture(boxShapeDef); 35 36 Body body1 = createBall(); 37 Body body2 = createBall(); 38 39 Player play1 = new Player(this,world,groundBody); 40 41 this.schedule(tick); 42 return true; 43 }
1 public static new CCLayer node() 2 { 3 PlayGame layer = new PlayGame(); 4 if (layer.init()) 5 { 6 return layer; 7 } 8 else 9 layer = null; 10 return layer; 11 }
基本上和上一节的内容差不多,但是我们这里把重力gravity设置为0,0.因为我们这个游戏并不需要重力的存在。 至于createBall是一个自定义函数,而Player是一个自定义的类,等下会讲到。
createBall函数:
1 public Body createBall() 2 { 3 Body body; 4 Random rand = new Random(); 5 ball = CCSprite.spriteWithFile("images/ball"); 6 this.addChild(ball); 7 int Vx = rand.Next(1, 7) * 100; 8 int Vy = rand.Next(1, 4) * 100; 9 BodyDef ballBodyDef = new BodyDef(); 10 ballBodyDef.type = BodyType.Dynamic;//动态类型 11 ballBodyDef.position = new Vector2((float)(Vx / PTM_RATIO), (float)(Vy / PTM_RATIO)); 12 ballBodyDef.userData = ball; 13 14 body = world.CreateBody(ballBodyDef); 15 CircleShape circle = new CircleShape(); 16 circle._radius = (float)(26.0 / PTM_RATIO); 17 18 FixtureDef ballShapeDef = new FixtureDef(); 19 ballShapeDef.shape = circle; 20 ballShapeDef.density = 1.0f;//密度 21 ballShapeDef.friction = 0.0f;//摩擦 22 ballShapeDef.restitution = 1.0f; 23 body.CreateFixture(ballShapeDef); 24 25 int Fx = rand.Next(1, 7) * 10; 26 int Fy = rand.Next(1, 4) * 10; 27 Vector2 force = new Vector2(Fx, Fy); 28 body.ApplyLinearImpulse(force, ballBodyDef.position); 29 30 return body; 31 }
这个函数的内容也是老内容了,除了设置了一个冲力force。
1 Vector2 force = new Vector2(Fx, Fy);
2 body.ApplyLinearImpulse(force, ballBodyDef.position);
有了这个冲力,我们的游戏中初始化的球就会自己动起来
添加好一个tick函数
1 void tick(float dt) 2 { 3 world.Step(dt, 10, 10); 4 5 for (Body b = world.GetBodyList(); b != null; b = b.GetNext()) 6 { 7 if (b.GetUserData() != null) 8 { 9 CCSprite ballData = (CCSprite)b.GetUserData(); 10 ballData.position = new CCPoint((float)(b.GetPosition().X * PTM_RATIO), 11 (float)(b.GetPosition().Y * PTM_RATIO)); 12 ballData.rotation = -1 * MathHelper.ToDegrees(b.GetAngle()); 13 } 14 } 15 }
以上都是一些老内容。不熟悉可以去看上一篇介绍。
最后修改一下程序入口applicationDidFinishLaunching
1 CCScene pScene = CCScene.node(); 2 pScene.addChild(Classes.PlayGame.node()); 3 pDirector.deviceOrientation = ccDeviceOrientation.CCDeviceOrientationLandscapeLeft; 4 pDirector.runWithScene(pScene); 5 return true;
运行,程序就会出现两个小球不停的在屏幕里来回跳动。
接下来,是该实现控制小球和各个小球之间的碰撞了。
构造Player自定义类。继承于CCSprite、ICCTargetedTouchDelegate。因为我们要手动(触屏)控制这个玩家,所以也要同时继承于ICCTargetedTouchDelegate。
这里也是我fengyun1989不同的地方。在他那边是直接在游戏中的游戏界面写touch事件(因为Layer里也有touch事件)。而我习惯于将各个对象分开。
首先,添加一些变量。方便后面调用,具体用处在用到时细说。
MouseJoint mouseJoint=null; Fixture ballFixture; Body spriteBody; World locWord; Body gBody; public static double PTM_RATIO = 32.0;
重写构造函数Player()
1 public Player(CCLayer layer,World world,Body groundBody) 2 { 3 gBody = groundBody; 4 locWord = world; 5 6 CCSize s = CCDirector.sharedDirector().getWinSize(); 7 this.initWithFile("images/ball"); 8 this.position = new CCPoint(s.width / 2, s.height / 2); 9 layer.addChild(this); 10 addBoxBodyForSprite(this, world); 11 }
同时将PlayGame里创建好的世界world和groundBody传过来,因为我们的玩家必须在同一个世界里才行。而且后面构造一些fixture也需要用到。
接下来,是该进行触摸事件的修改了。先是ccTouchBegan()
1 public virtual bool ccTouchBegan(CCTouch touch, CCEvent eventer) 2 { 3 //创建一个鼠标关节 4 if (mouseJoint != null) 5 return false; 6 CCPoint location = touch.locationInView(touch.view()); 7 location = CCDirector.sharedDirector().convertToGL(location); 8 Vector2 locationWorld = new Vector2((float)(location.x / PTM_RATIO), (float)(location.y / PTM_RATIO)); 9 10 if (ballFixture.TestPoint(locationWorld)) 11 { 12 MouseJointDef md = new MouseJointDef(); 13 md.bodyA = gBody; 14 md.bodyB = spriteBody; 15 md.collideConnected = true; 16 md.target = locationWorld; 17 md.maxForce = 1000.0f * spriteBody.GetMass(); 18 mouseJoint = (MouseJoint)locWord.CreateJoint(md); 19 spriteBody.SetAwake(true); 20 return true; 21 } 22 else 23 return false;
首先,我们把touch坐标转换成coocs2d坐标(convertToGL)然后,再转换成Box2d坐标(locationWorld)。
如果是的话,我们就创建一个所谓的”鼠标关节“。在Box2d里面,一个鼠标关节用来让一个body朝着一个指定的点移动---在这里个例子中,就是用户点的方向。
当你创建一个mousejoint后,你赋值给它两个body。第一个没有被使用,通常都是设置成groundbody(前面提到这里用到了)。第二个,就是你想让它移动的body(这个类里面的精灵的body),在这个例子中就是spriteBody。
你指定移动的终点---这个例子中就是用户点击的位置locationWorld。
然后,设置bodyA和bodyB碰撞的时候,把它当成是碰撞,而不是忽略它。这个很重要!
如果没有设置,当我们用鼠标拖动这个你的小球的时候,它并不会与屏幕的边界相碰撞。
指定移动body的最大的力是多少。如果你减少这个数值的话,spritebody响应鼠标移动时就会慢一些。但是,我们想让spritebody快速地响应鼠标的变化。
最后,我们把这个关节加入到world中。同时,我们还要把body设置成苏醒的(awake)。之所以要这么做,是因为如果body在睡觉的话,那么它就不会响应鼠标的移动!
上面用到了一个鼠标关节mouseJoint。这是一个自定义的类。内容如下:
1 class MyContact 2 { 3 public Fixture fixtureA; 4 public Fixture fixtureB; 5 6 } 7 class MyContactListener : IContactListener 8 { 9 public List<MyContact> contacts = new List<MyContact>(); 10 11 public void BeginContact(Contact contact) 12 { 13 MyContact myContact = new MyContact() 14 { 15 fixtureA = contact.GetFixtureA(), 16 fixtureB = contact.GetFixtureB() 17 }; 18 contacts.Add(myContact); 19 20 } 21 22 public void EndContact(Contact contact) 23 { 24 contacts.Clear(); 25 } 26 27 public void PostSolve(Contact contact, ref ContactImpulse impulse) 28 { 29 } 30 public void PreSolve(Contact contact, ref Manifold oldManifold) 31 { 32 } 33 }
接下来,让我们添加ccTouchesMoved方法:
1 public virtual void ccTouchMoved(CCTouch touch, CCEvent eventer) 2 { 3 if (mouseJoint == null) 4 return; 5 Vector2 point = new Vector2(this.convertTouchToNodeSpace(touch).x, 6 this.convertTouchToNodeSpace(touch).y); 7 CCPoint location = touch.locationInView(touch.view()); 8 location = CCDirector.sharedDirector().convertToGL(location); 9 Vector2 locationWorld = new Vector2((float)(location.x / PTM_RATIO), (float)(location.y / PTM_RATIO)); 10 11 mouseJoint.SetTarget(locationWorld); 12 }
我们更新了鼠标关节的目标位置(也就是我们想让玩家移动的位置的)。
我们添加ccTouchesCacelled和ccTouchesEnded方法:这里只实现一件事,就是在我们移动完paddle或者取消移动之后销毁mouse joint。
1 public virtual void ccTouchEnded(CCTouch touch, CCEvent eventer) 2 { 3 if (mouseJoint != null) 4 { 5 mouseJoint = null; 6 locWord.DestroyJoint(mouseJoint); 7 } 8 } 9 10 public void ccTouchCancelled(CCTouch touch, CCEvent eventer) 11 { 12 if (mouseJoint != null) 13 { 14 mouseJoint = null; 15 locWord.DestroyJoint(mouseJoint); 16 17 } 18 }
编译并运行,你现在可以用手指控制属于你的精灵了,同时可以让它与篮球相互碰撞!