曾經經典的微信打飛機遊戲還有人記得嗎?
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 元件化方案