曾经经典的微信打飞机游戏还有人记得吗?
theme: geek-black
我正在参加掘金社区游戏创意投稿大赛个人赛,详情请看:游戏创意投稿大赛
前言
记得很多年前,微信推出来打飞机的小游戏,当时在社交软件中加入了这样一个游戏确实挺新鲜的。为了能让自己能在排行榜前列,一遍又一遍的打飞机,有时候次数用完了,还会分享给很久没有联系的朋友。讲真,当时社交+游戏的结合真的很棒。是否也有小伙伴像我一样一次又一次的刷榜呢?
偶然有一天突然想完微信打飞机的游戏,然后就自己写了个简单的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 (...) {
...
}
...
}
丰富玩法
飞机大战的游戏玩法其实还可以在丰富一些。我们可以通过将一些飞机速度、数量,子弹速度等参数调教。
然后设置简单,中等,困难的等级去挑战。在游戏中,设置合适的数值,将大大增加游戏的可玩性。除了调参以外,我们也可以将降落伞的轨迹飘忽不定,来增加用户的紧张感,以及犯错误的几率。
结尾
在本片文章介绍了使用iOS中的SpriteKit
完成一个微信打飞机的游戏的整体思路。小伙伴们感兴趣的,可以下载 Demo试玩下。
- LeetCode 初级算法之数组(上),看看你都学会了吗?
- LeetCode 初级算法之链表,看看你都学会了吗?
- LeetCode 初级算法之字符串(上),看看你都学会了吗?
- 纯代码布局,也可以一样的简洁
- UIStackView之一问一答
- 使用UIStackView来简化iOS的界面布局
- 夏天来了,iOS开发者们该如何减少App耗电?(上)
- 夏天来了,App开发者们如何看待手机发烫问题?
- 聊聊iOS中UITableView复用的那些事
- 曾经经典的微信打飞机游戏还有人记得吗?
- iOS 原生渲染与 Flutter 有什么区别 (上)
- 了解 Mach-O文件
- CocoaPods中podsepc文件设置详解
- iOS 原生渲染与 Flutter 有什么区别 (下)
- 简单了解 iOS CVPixelBuffer (上)
- 谈谈 iOS 包瘦身方案
- 播放器重构的探索之路
- 如何使用CocoaPods制作私有库
- iOS 组件化方案