基于Sprite Kit的游戏开发全流程指南
1. 碰撞检测与处理基础
在游戏开发中,碰撞检测与处理是非常重要的环节。其核心思想是判断两个碰撞对象是否属于同一类别。若属于同一类别,它们可视为“友方”;若属于不同类别,则需确定谁是攻击者。在类别定义中,存在“攻击优先级”顺序,例如玩家节点可被敌人节点攻击,而敌人节点又可被玩家导弹节点攻击,我们可通过简单的大小比较来确定攻击者。
为了实现代码的简洁性和模块化,我们不希望场景直接决定每个对象被攻击或碰撞后的反应,而是将这些细节封装到受影响的节点类中。为此,我们可以利用多态性,让每个节点类以自己的方式处理碰撞。具体做法是为
SKNode
添加类别方法,默认实现为空,子类可根据需要进行重写。
为SKNode添加类别
以下是为
SKNode
添加类别的具体步骤:
1. 在Xcode的项目导航器中,右键点击
TextShooter
文件夹,从弹出菜单中选择
New File…
。
2. 在助手的
iOS/Source
部分,选择
Objective-C File
,然后点击
Next
。
3. 将文件名设置为
Extra
,选择
Category
作为文件类型,并选择
SKNode
作为要添加类别的类。
4. 再次点击
Next
并创建文件。
5. 选择类别头文件
SKNode+Extra.h
,添加以下方法声明:
#import <SpriteKit/SpriteKit.h>
@interface SKNode (Extra)
- (void)receiveAttacker:(SKNode *)attacker contact:(SKPhysicsContact *)contact;
- (void)friendlyBumpFrom:(SKNode *)node;
@end
-
切换到对应的
.m文件,输入以下空定义:
#import "SKNode+Extra.h"
@implementation SKNode (Extra)
- (void)receiveAttacker:(SKNode *)attacker contact:(SKPhysicsContact *)contact {
// default implementation does nothing
}
- (void)friendlyBumpFrom:(SKNode *)node {
// default implementation does nothing
}
@end
2. 完善GameScene中的碰撞处理
回到
GameScene.m
文件,完成碰撞处理部分。首先在文件顶部添加新的头文件:
#import "GameScene.h"
#import "PlayerNode.h"
#import "EnemyNode.h"
#import "BulletNode.h"
#import "SKNode+Extra.h"
然后在
didBeginContact:
方法中添加实际处理逻辑:
- (void)didBeginContact:(SKPhysicsContact *)contact {
if (contact.bodyA.categoryBitMask == contact.bodyB.categoryBitMask) {
// Both bodies are in the same category
SKNode *nodeA = contact.bodyA.node;
SKNode *nodeB = contact.bodyB.node;
// What do we do with these nodes?
[nodeA friendlyBumpFrom:nodeB];
[nodeB friendlyBumpFrom:nodeA];
} else {
SKNode *attacker = nil;
SKNode *attackee = nil;
if (contact.bodyA.categoryBitMask > contact.bodyB.categoryBitMask) {
// Body A is attacking Body B
attacker = contact.bodyA.node;
attackee = contact.bodyB.node;
} else {
// Body B is attacking Body A
attacker = contact.bodyB.node;
attackee = contact.bodyA.node;
}
if ([attackee isKindOfClass:[PlayerNode class]]) {
self.playerLives--;
}
// What do we do with the attacker and the attackee?
[attackee receiveAttacker:attacker contact:contact];
[self.playerBullets removeChildrenInArray:@[attacker]];
[self.enemies removeChildrenInArray:@[attacker]];
}
}
在上述代码中,如果碰撞属于“友方碰撞”,我们会通知双方节点;否则,确定攻击者和被攻击者后,通知被攻击者并从相应节点中移除攻击者。
3. 为敌人节点添加自定义碰撞行为
现在可以通过重写为
SKNode
添加的类别方法,为节点实现特定行为。打开
EnemyNode.m
文件,在文件顶部添加
Geometry.h
的导入:
#import "PhysicsCategories.h"
#import "Geometry.h"
@implementation EnemyNode
然后添加以下两个方法:
- (void)friendlyBumpFrom:(SKNode *)node {
self.physicsBody.affectedByGravity = YES;
}
- (void)receiveAttacker:(SKNode *)attacker contact:(SKPhysicsContact *)contact {
self.physicsBody.affectedByGravity = YES;
CGVector force = VectorMultiply(attacker.physicsBody.velocity,
contact.collisionImpulse);
CGPoint myContact = [self.scene convertPoint:contact.contactPoint
toNode:self];
[self.physicsBody applyForce:force
atPoint:myContact];
}
friendlyBumpFrom:
方法会在敌人被友方碰撞时开启重力,使其开始下落;
receiveAttacker:contact:
方法会在敌人被子弹击中时,不仅开启重力,还会根据碰撞数据施加额外的力。
4. 显示准确的玩家生命值
在游戏运行过程中,我们发现玩家生命值显示存在问题,始终显示为5。这是因为生命值显示在关卡创建时设置,但之后未更新。我们可以通过在
GameScene.m
中实现
setPlayerLives:
方法来解决这个问题:
- (void)setPlayerLives:(NSUInteger)playerLives {
_playerLives = playerLives;
SKLabelNode *lives = (id)[self childNodeWithName:@"LivesLabel"];
lives.text = [NSString stringWithFormat:@"Lives: %lu",
(unsigned long)_playerLives];
}
运行游戏后,玩家生命值会随着敌人的攻击而准确更新。但当生命值降为0后,游戏并未结束,而是出现了异常的大量生命值,这是因为我们尚未编写检测游戏结束的代码。
5. 使用粒子系统增强游戏视觉效果
Sprite Kit提供了粒子系统,可用于创建烟雾、火焰、爆炸等视觉效果。目前游戏中,子弹击中敌人或敌人击中玩家时,攻击对象只是简单消失,我们可以创建粒子系统来改善这种情况。
创建粒子系统
以下是创建粒子系统的步骤:
1. 按下
⌘N
打开新文件助手。
2. 选择左侧的
iOS/Resource
部分,然后在右侧选择
SpriteKit Particle File
。
3. 点击
Next
,在接下来的屏幕中选择
Spark
粒子模板。
4. 再次点击
Next
,将文件命名为
MissileExplosion.sks
。
创建完成后,Xcode会生成粒子文件并添加
spark.png
资源。我们需要对粒子效果进行重新配置,使其更符合游戏需求。具体操作如下:
1. 按下
Opt-Cmd-7
打开
SKNode Inspector
。
2. 点击底部
Color Ramp
中的小颜色框,将颜色设置为黑色。
3. 将背景颜色改为白色,混合模式改为
Alpha
。
4. 逐个更改其他数值参数,直到达到目标效果。
按照相同的步骤,创建另一个名为
EnemyExplosion.sks
的粒子系统,并设置其参数。
将粒子系统应用到游戏中
在
EnemyNode.m
的
receiveAttacker:contact:
方法底部添加以下代码:
- (void)receiveAttacker:(SKNode *)attacker contact:(SKPhysicsContact *)contact {
self.physicsBody.affectedByGravity = YES;
CGVector force = VectorMultiply(attacker.physicsBody.velocity,
contact.collisionImpulse);
CGPoint myContact = [self.scene convertPoint:contact.contactPoint
toNode:self];
[self.physicsBody applyForce:force
atPoint:myContact];
NSString *path = [[NSBundle mainBundle] pathForResource:@"MissileExplosion"
ofType:@"sks"];
SKEmitterNode *explosion = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
explosion.numParticlesToEmit = 20;
explosion.position = contact.contactPoint;
[self.scene addChild:explosion];
}
在
PlayerNode.m
中添加以下方法:
- (void)receiveAttacker:(SKNode *)attacker contact:(SKPhysicsContact *)contact {
NSString *path = [[NSBundle mainBundle] pathForResource:@"EnemyExplosion"
ofType:@"sks"];
SKEmitterNode *explosion =
[NSKeyedUnarchiver unarchiveObjectWithFile:path];
explosion.numParticlesToEmit = 50;
explosion.position = contact.contactPoint;
[self.scene addChild:explosion];
}
运行游戏后,子弹击中敌人会出现小爆炸效果,敌人击中玩家会出现红色飞溅效果,大大增强了游戏的视觉体验。
以下是整个流程的mermaid流程图:
graph LR
A[碰撞检测] --> B{是否同一类别}
B -- 是 --> C[友方碰撞处理]
B -- 否 --> D[确定攻击者和被攻击者]
D --> E[处理攻击行为]
E --> F[更新玩家生命值]
F --> G{生命值是否为0}
G -- 是 --> H[游戏结束处理]
G -- 否 --> I[继续游戏]
I --> J[粒子效果处理]
6. 实现游戏结束与开始场景
为了处理游戏结束的情况,我们需要创建一个新的场景类
GameOverScene
。具体步骤如下:
1. 创建一个新的
iOS/Cocoa Touch
类,使用
SKScene
作为父类,命名为
GameOverScene
。
2. 在
GameOverScene.m
的
@implementation
中添加以下代码:
- (instancetype)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.backgroundColor = [SKColor purpleColor];
SKLabelNode *text = [SKLabelNode labelNodeWithFontNamed:@"Courier"];
text.text = @"Game Over";
text.fontColor = [SKColor whiteColor];
text.fontSize = 50;
text.position = CGPointMake(self.frame.size.width * 0.5,
self.frame.size.height * 0.5);
[self addChild:text];
}
return self;
}
在
GameScene.m
中,我们需要导入
GameOverScene.h
头文件,并添加以下方法:
#import "GameScene.h"
#import "PlayerNode.h"
#import "EnemyNode.h"
#import "BulletNode.h"
#import "SKNode+Extra.h"
#import "GameOverScene.h"
- (void)triggerGameOver {
self.finished = YES;
NSString *path = [[NSBundle mainBundle] pathForResource:@"EnemyExplosion"
ofType:@"sks"];
SKEmitterNode *explosion =
[NSKeyedUnarchiver unarchiveObjectWithFile:path];
explosion.numParticlesToEmit = 200;
explosion.position = _playerNode.position;
[self addChild:explosion];
[_playerNode removeFromParent];
SKTransition *transition =
[SKTransition doorsOpenVerticalWithDuration:1.0];
SKScene *gameOver = [[GameOverScene alloc] initWithSize:self.frame.size];
[self.view presentScene:gameOver transition:transition];
}
- (BOOL)checkForGameOver {
if (self.playerLives == 0) {
[self triggerGameOver];
return YES;
}
return NO;
}
- (void)update:(CFTimeInterval)currentTime {
if (self.finished) return;
[self updateBullets];
[self updateEnemies];
if (![self checkForGameOver]) {
[self checkForNextLevel];
}
}
triggerGameOver
方法用于触发游戏结束,包括显示额外的爆炸效果和切换到游戏结束场景;
checkForGameOver
方法用于检查游戏是否结束;
update
方法在每次更新时检查游戏状态,确保游戏结束时不会出现异常的场景切换。
7. 创建开始场景
为了让游戏有一个开始界面,避免玩家在启动时直接进入游戏,我们创建一个
StartScene
。具体步骤如下:
1. 创建一个新的
iOS/Cocoa Touch
类,使用
SKScene
作为父类,命名为
StartScene
。
2. 在
StartScene.m
中添加以下代码:
#import "StartScene.h"
#import "GameScene.h"
@implementation StartScene
- (instancetype)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.backgroundColor = [SKColor greenColor];
SKLabelNode *topLabel = [SKLabelNode labelNodeWithFontNamed:@"Courier"];
topLabel.text = @"TextShooter";
topLabel.fontColor = [SKColor blackColor];
topLabel.fontSize = 48;
topLabel.position = CGPointMake(self.frame.size.width * 0.5,
self.frame.size.height * 0.7);
[self addChild:topLabel];
SKLabelNode *bottomLabel = [SKLabelNode labelNodeWithFontNamed:
@"Courier"];
bottomLabel.text = @"Touch anywhere to start";
bottomLabel.fontColor = [SKColor blackColor];
bottomLabel.fontSize = 20;
bottomLabel.position = CGPointMake(self.frame.size.width * 0.5,
self.frame.size.height * 0.3);
[self addChild:bottomLabel];
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
SKTransition *transition = [SKTransition doorwayWithDuration:1.0];
SKScene *game = [[GameScene alloc] initWithSize:self.frame.size];
[self.view presentScene:game transition:transition];
}
@end
在
GameOverScene.m
中,我们需要导入
StartScene.h
头文件,并添加以下代码,使游戏结束场景在一段时间后返回开始场景:
#import "GameOverScene.h"
#import "StartScene.h"
- (void)didMoveToView:(SKView *)view {
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
SKTransition *transition = [SKTransition flipVerticalWithDuration:1.0];
SKScene *start = [[StartScene alloc] initWithSize:self.frame.size];
[self.view presentScene:start transition:transition];
});
}
最后,在
GameViewController.m
中,我们需要导入
StartScene.h
头文件,并修改
viewDidLoad
方法,使应用启动时显示开始场景:
#import "GameViewController.h"
#import "GameScene.h"
#import "StartScene.h"
// 修改前
// GameScene *scene = [GameScene sceneWithSize:self.view.frame.size levelNumber:1];
// 修改后
SKScene * scene = [StartScene sceneWithSize:skView.bounds.size];
现在,启动应用后会显示开始场景,玩家触摸屏幕即可开始游戏,游戏结束后等待几秒会返回开始场景,形成一个完整的游戏循环。
以下是场景切换的mermaid流程图:
graph LR
A[开始场景] --> B[游戏场景]
B --> C{游戏是否结束}
C -- 是 --> D[游戏结束场景]
D --> E[等待3秒]
E --> A[开始场景]
C -- 否 --> B[游戏场景]
通过以上步骤,我们完成了一个基于Sprite Kit的完整游戏开发流程,包括碰撞检测、粒子效果、游戏结束和开始场景的实现,大大提升了游戏的趣味性和用户体验。
基于Sprite Kit的游戏开发全流程指南
8. 总结与回顾
在本次游戏开发过程中,我们逐步实现了多个关键功能,让游戏从简单的基础框架变得更加完善和有趣。下面我们对整个开发流程进行一个总结回顾。
| 功能模块 | 主要实现内容 |
|---|---|
| 碰撞检测与处理 |
判断碰撞对象类别,区分友方碰撞和攻击行为,通过为
SKNode
添加类别方法实现多态处理
|
| 玩家生命值显示 | 修复生命值显示问题,确保生命值随攻击准确更新,并添加游戏结束检测逻辑 |
| 粒子系统应用 | 创建并配置粒子系统,增强子弹击中敌人和敌人击中玩家时的视觉效果 |
| 游戏场景管理 |
实现游戏结束场景
GameOverScene
和开始场景
StartScene
,并处理场景之间的切换
|
通过这些功能的实现,我们不仅让游戏在玩法上更加完整,还在视觉效果上有了很大的提升,为玩家带来了更好的游戏体验。
9. 可能的优化方向
虽然我们已经完成了一个基本的游戏,但仍然有很多可以优化的地方,以下是一些可能的优化方向:
-
性能优化
:在游戏中,粒子系统和物理模拟可能会消耗较多的性能。我们可以通过减少粒子数量、优化物理模拟参数等方式来提高游戏的性能。例如,在粒子系统中,可以适当减少
numParticlesToEmit的值,或者优化粒子的生命周期和发射频率。 - 游戏玩法扩展 :可以添加更多的游戏元素和玩法,如道具系统、关卡设计、排行榜等。例如,添加道具系统可以让玩家在游戏中获得各种增益效果,增加游戏的趣味性和策略性。
- 用户界面优化 :改善游戏的用户界面,使其更加美观和易用。可以设计更加精美的开始场景和游戏结束场景,添加更多的交互元素,如按钮、动画等。
10. 代码复用与模块化
在开发过程中,我们采用了一些模块化的设计思想,如为
SKNode
添加类别方法,将碰撞处理逻辑封装到节点类中。这种模块化的设计不仅提高了代码的可读性和可维护性,还方便了代码的复用。
例如,我们可以将粒子系统的创建和配置代码封装成一个独立的工具类,在需要使用粒子效果的地方直接调用该工具类的方法,这样可以避免代码的重复编写。以下是一个简单的粒子系统工具类示例:
#import <SpriteKit/SpriteKit.h>
@interface ParticleSystemTool : NSObject
+ (SKEmitterNode *)createMissileExplosionParticle;
+ (SKEmitterNode *)createEnemyExplosionParticle;
@end
@implementation ParticleSystemTool
+ (SKEmitterNode *)createMissileExplosionParticle {
NSString *path = [[NSBundle mainBundle] pathForResource:@"MissileExplosion" ofType:@"sks"];
SKEmitterNode *explosion = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
explosion.numParticlesToEmit = 20;
return explosion;
}
+ (SKEmitterNode *)createEnemyExplosionParticle {
NSString *path = [[NSBundle mainBundle] pathForResource:@"EnemyExplosion" ofType:@"sks"];
SKEmitterNode *explosion = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
explosion.numParticlesToEmit = 50;
return explosion;
}
@end
在需要使用粒子效果的地方,可以直接调用这些方法:
// 在EnemyNode.m的receiveAttacker:contact:方法中
- (void)receiveAttacker:(SKNode *)attacker contact:(SKPhysicsContact *)contact {
// 其他代码...
SKEmitterNode *explosion = [ParticleSystemTool createMissileExplosionParticle];
explosion.position = contact.contactPoint;
[self.scene addChild:explosion];
}
// 在PlayerNode.m的receiveAttacker:contact:方法中
- (void)receiveAttacker:(SKNode *)attacker contact:(SKPhysicsContact *)contact {
SKEmitterNode *explosion = [ParticleSystemTool createEnemyExplosionParticle];
explosion.position = contact.contactPoint;
[self.scene addChild:explosion];
}
通过这种方式,我们可以提高代码的复用性,减少代码的冗余,同时也方便了代码的维护和扩展。
11. 未来展望
随着游戏开发技术的不断发展,我们可以在现有基础上进一步拓展游戏的功能和玩法。例如,结合虚拟现实(VR)或增强现实(AR)技术,为玩家带来更加沉浸式的游戏体验;利用网络技术,实现多人在线对战功能,增加游戏的社交性和互动性。
此外,我们还可以不断优化游戏的性能和用户体验,根据玩家的反馈和市场需求,持续更新和改进游戏。相信通过不断的努力和创新,我们可以开发出更加优秀的游戏作品。
12. 开发经验分享
在本次游戏开发过程中,我们积累了一些宝贵的经验,希望能对其他开发者有所帮助。
- 注重代码结构和模块化 :良好的代码结构和模块化设计可以提高代码的可读性、可维护性和复用性。在开发过程中,要尽量将不同的功能模块分离,避免代码的耦合度过高。
- 多进行测试和调试 :在开发过程中,要及时进行测试和调试,发现并解决问题。可以使用模拟器和真机进行测试,确保游戏在不同设备上都能正常运行。
- 参考优秀的开源项目 :参考优秀的开源项目可以学习到其他开发者的经验和技巧,同时也可以借鉴他们的代码结构和设计思路。
总之,游戏开发是一个充满挑战和乐趣的过程,通过不断学习和实践,我们可以不断提高自己的开发水平,开发出更加优秀的游戏作品。
以下是整个游戏开发流程的总结mermaid流程图:
graph LR
A[初始化项目] --> B[碰撞检测与处理]
B --> C[玩家生命值管理]
C --> D[粒子系统应用]
D --> E[游戏场景管理]
E --> F[性能优化与扩展]
F --> G[代码复用与模块化]
G --> H[持续更新与改进]
通过以上的开发流程和优化方向,我们可以不断完善游戏,为玩家带来更好的游戏体验。希望本次的开发经验能对大家有所帮助,让我们一起在游戏开发的道路上不断前进!
超级会员免费看
325

被折叠的 条评论
为什么被折叠?



