曾经经典的微信打飞机游戏还有人记得吗?

语言: CN / TW / HK

theme: geek-black

我正在参加掘金社区游戏创意投稿大赛个人赛,详情请看:游戏创意投稿大赛

前言

记得很多年前,微信推出来打飞机的小游戏,当时在社交软件中加入了这样一个游戏确实挺新鲜的。为了能让自己能在排行榜前列,一遍又一遍的打飞机,有时候次数用完了,还会分享给很久没有联系的朋友。讲真,当时社交+游戏的结合真的很棒。是否也有小伙伴像我一样一次又一次的刷榜呢?

RPReplay_Final1649419258.2022-04-21 18_48_37.gif

偶然有一天突然想完微信打飞机的游戏,然后就自己写了个简单的Demo,简单的实现了打飞机的玩法。玩家可以通过接受降落伞包获取炸弹以及加强的子弹,玩家击毁更多的敌机以获取更多的得分。

这个项目具体的实现是通过iOS中游戏引擎框架SpriteKit。在这篇文章中,我将介绍下游戏的整体实现。 在介绍之前,我先简单介绍下SpriteKit中基础的几个概念以便我们更好的理解一个游戏的实现。

首先在游戏中,一般会有由一个场景(Scene)或者多个场景组成,在场景中我们可以添加很多节点(Node),每个节点可以绘制自己的纹理(Texture),有自己的行为(Action)。这样我们就可以根据这些元素来构建一个物理世界。但是似乎还少了些什么吧。其实就是物理碰撞检测,在各个节点的物理边界发生碰撞时,由游戏引擎提供的方法回调给开发者来响应。

在我们简单了解这些之后,就可以上手撸一个飞机了。

微信打飞机游戏

我们将飞机大战游戏的实现大体分为一下几个步骤:

  • 构建游戏的场景
  • 初始化背景
  • 初始化飞机、敌机
  • 初始化降落伞
  • 物理碰撞检测
  • 丰富玩法

构建游戏的场景

我们需要先初始化游戏视图SKView,使用SKView呈现游戏场景SKScene

[self.view addSubview:self.skView]; [self.skView presentScene:self.scene]; 在游戏场景中,我们引入物理世界SKPhysicsWorld- (void)initPhysicsWorld {     self.physicsWorld.contactDelegate = self;     self.physicsWorld.gravity = CGVectorMake(0, 0); }

创建纹理集

游戏中经常会把多个图片资源打包成一个纹理集,纹理集包含一个图片和一个plist文件(包含每个图片的位置、大小,角度,偏移等关键信息)。如果有人问为什么使用纹理集呢?那么建议你动手来制作一个小游戏,这样一定会有所收获。

我们需要把游戏中所需要用到的纹理提前通过SKTextureAtlas来获取出来。这里,我们创建了一个单例类SKSharedAtles

+ (instancetype)shared {     static dispatch_once_t onceToken;     dispatch_once(&onceToken, ^{         // 创建纹理集         _shared = (SKSharedAtles *)[SKSharedAtles atlasNamed:@"gameArts-hd"];     });     return _shared; }

然后依次取出背景、飞机、子弹、降落伞等等的纹理 - (SKTexture *)textureNamed:(NSString *)name;

``` + (SKTexture *)textureWithBomb {     return [[[self class] shared] textureNamed:@"bomb.png"]; }

  • (SKTexture *)textureWithSKTextureTypeBulletType:(NSInteger )type {     return [[[self class] shared] textureNamed:[NSString stringWithFormat:@"bullet%ld.png",type]]; }

... ```

有些纹理是用来组成一个动画的,比如说,飞机被击中后爆炸会有一个序列帧动画,那么在这里我们先将纹理装入数组中,然后通过一系列的纹理来初始化一个动画。 ``` + (SKAction )playerPlaneAction{     NSMutableArray textures = [[NSMutableArray alloc]init];     for (int i= 1; i<=2; i++) {         SKTexture *texture = [[self class] playerPlaneTextureWithIndex:i];         [textures addObject:texture];     }     return [SKAction repeatActionForever:[SKAction animateWithTextures:textures timePerFrame:0.1]]; }

... ```

初始化背景

通常在飞机大战中游戏背景会一直随着游戏的进行而移动,并且播放背景音效。这个也是游戏世界构建的第一步。 在SpriteKit中万物皆是精灵,背景也是SKSpriteNode,所以我们只需取出背景的纹理,并且设置好锚点以及比例。背景设置完之后,我们开始循环播放背景音效。 ``` - (void)initBackground {     _adjustmentBackgroundPosition = self.size.height;     _background1 = [SKSpriteNode spriteNodeWithTexture:[SKSharedAtles textureWithType:SKTextureTypeBackground]];     _background1.position = CGPointMake(self.size.width/2, 0);     _background1.anchorPoint = CGPointMake(0.5, 0.0);     _background1.zPosition = 0;     _background1.xScale=1.5;     _background1.yScale=1.5;

_background2 = [SKSpriteNode spriteNodeWithTexture:[SKSharedAtles textureWithType:SKTextureTypeBackground]];     _background2.anchorPoint = CGPointMake(0.5, 0.0);     _background2.position = CGPointMake(self.size.width / 2, HEIGHT - 1);     _background2.zPosition = 0;     _background2.xScale=1.5;     _background2.yScale=1.5;     [self addChild:_background1];     [self addChild:_background2];     [_NearbyArray addObject:_background1];     [_NearbyArray addObject:_background2];     [self runAction:[SKAction repeatActionForever:[SKAction playSoundFileNamed:@"game_music.mp3" waitForCompletion:YES]]]; } ```

初始化玩家飞机、敌机

在初始化玩家飞机以及敌机时,需要设置物理体。 categoryBitMask:设置物理体标识符,collisionBitMask:设置碰撞标识符,contactTestBitMask:设置可以那类物理体碰撞; 物理体这里的设置决定后面的物理碰撞检测。

// 初始化玩家飞机 - (**void**)initPlayerPlane{     _playerPlane = [SKSpriteNode spriteNodeWithTexture:[SKSharedAtles textureWithType:SKTextureTypePlayerPlane]];     _playerPlane.position = CGPointMake(160, 50);     _playerPlane.zPosition = 1;     _playerPlane.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(_playerPlane.size.width-20, _playerPlane.size.height) ];     _playerPlane.physicsBody.categoryBitMask = SKRoleCategoryPlayerPlane;     _playerPlane.physicsBody.collisionBitMask = 0;     _playerPlane.physicsBody.contactTestBitMask = SKRoleCategoryFoePlane;     [self addChild:_playerPlane];     [_playerPlane runAction:[SKSharedAtles playerPlaneAction]]; }

初始化完玩家飞机之后,我们需要对玩家飞机进行操作,这里就需要用到在拖动的回调中处玩家飞机的位置。

``` // 移动 - (void)touchesMoved:(NSSet )touches withEvent:(UIEvent )event{

for (UITouch touch in touches) {         CGPoint location = [touch locationInNode:self];         // 超出屏幕         if (location.x >= self.size.width - (_playerPlane.size.width / 2)) {             location.x = self.size.width - (_playerPlane.size.width / 2);         } else if (location.x <= (_playerPlane.size.width / 2)) {

location.x = _playerPlane.size.width / 2;         }

if (location.y >= self.size.height - (_playerPlane.size.height / 2)) {             location.y = self.size.height - (_playerPlane.size.height / 2);         } else if (location.y <= (_playerPlane.size.height / 2)) {             location.y = (_playerPlane.size.height / 2);         }         SKAction *action = [SKAction moveTo:CGPointMake(location.x, location.y) duration:0];         [_playerPlane runAction:action];     } } ```

敌机一般都是在场景中随机出现的,这个需要我们要在SKScene中的场景更新中- (void)update:(NSTimeInterval)currentTime;随机初始化敌人飞机,以及滚动背景。

- (void)update:(NSTimeInterval)currentTime {     [self createFoePlane];     [self createParachute];     [self scrollBackground]; }

初始化炮弹

玩家的飞机已经初始化完成了,但飞机还需要射出一梭梭的子弹。我们可以先将子弹创建方法写好,设置子弹的物理体,以及子弹的音效。最后我们重复的发射子弹就可以。

``` // 创建炮弹 - (void)createBulletsWithType:(NSInteger)bulletType {     _bullet = [SKSpriteNode spriteNodeWithTexture:[SKSharedAtles textureWithSKTextureTypeBulletType:bulletType]];     _bullet.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_bullet.size];     _bullet.physicsBody.categoryBitMask = SKRoleCategoryBullet;     _bullet.physicsBody.collisionBitMask = SKRoleCategoryFoePlane;     _bullet.physicsBody.contactTestBitMask = SKRoleCategoryFoePlane;     _bullet.zPosition = 1;     _bullet.position = CGPointMake(_playerPlane.position.x, _playerPlane.position.y+ (_playerPlane.size.height/2));     [self addChild:_bullet];     SKAction actionMove = [SKAction moveTo:CGPointMake(_playerPlane.position.x, self.size.height) duration:0.8];     SKAction actionMoveDone = [SKAction removeFromParent];     [_bullet runAction:[SKAction sequence:@[actionMove,actionMoveDone]]];     [self runAction:[SKAction playSoundFileNamed:@"bullet.mp3" waitForCompletion:NO]]; }

// 初始化炮弹 - (void)initBullets {     _bulletType = 1; //默认1发炮弹     SKAction action = [SKAction runBlock:^{         [self createBulletsWithType:_bulletType];     }];     SKAction interval = [SKAction waitForDuration:0.2];     //场景循环炮弹的动画     [self runAction:[SKAction repeatActionForever:[SKAction sequence:@[action,interval]]]withKey:@"Bullets"]; } ```

物理碰撞检测

物理碰撞检测是游戏中的最复杂的部分,这里需要处理物体之间的碰撞,所用的判断条件也是在前面设置物理体时的一些设置。在飞机大战中,我们的飞机不能和敌机发生碰撞,一旦发生碰撞即GameOver,玩家飞机的子弹和敌机发生碰撞即减少敌机血量,血量为零则爆炸击毁。

- (void)didBeginContact:(SKPhysicsContact *)contact{ if (contact.bodyA.categoryBitMask & SKRoleCategoryFoePlane || contact.bodyB.categoryBitMask & SKRoleCategoryFoePlane) {         SKFoePlane *sprite = (contact.bodyA.categoryBitMask & SKRoleCategoryFoePlane) ? (SKFoePlane *)contact.bodyA.node : (SKFoePlane *)contact.bodyB.node;         if (contact.bodyA.categoryBitMask & SKRoleCategoryPlayerPlane || contact.bodyB.categoryBitMask & SKRoleCategoryPlayerPlane) {             SKSpriteNode *playerPlane = (contact.bodyA.categoryBitMask & SKRoleCategoryPlayerPlane) ? (SKSpriteNode *)contact.bodyA.node : (SKSpriteNode *)contact.bodyB.node;             [self playerPlaneCollisionAnimation:playerPlane];         } else {             SKSpriteNode *bullet = (contact.bodyA.categoryBitMask & SKRoleCategoryFoePlane) ? (SKFoePlane *)contact.bodyB.node : (SKFoePlane *)contact.bodyA.node;             [bullet removeFromParent];             [self foePlaneCollisionAnimation:sprite];         }     } else if (...) { ... } ... }

丰富玩法

飞机大战的游戏玩法其实还可以在丰富一些。我们可以通过将一些飞机速度、数量,子弹速度等参数调教。

然后设置简单,中等,困难的等级去挑战。在游戏中,设置合适的数值,将大大增加游戏的可玩性。除了调参以外,我们也可以将降落伞的轨迹飘忽不定,来增加用户的紧张感,以及犯错误的几率。

RPReplay_Final1649419258.2022-04-21 18_51_57.gif

结尾

在本片文章介绍了使用iOS中的SpriteKit完成一个微信打飞机的游戏的整体思路。小伙伴们感兴趣的,可以下载 Demo试玩下。